session.ex (3990B)
1 defmodule Plug.Session do 2 @moduledoc """ 3 A plug to handle session cookies and session stores. 4 5 The session is accessed via functions on `Plug.Conn`. Cookies and 6 session have to be fetched with `Plug.Conn.fetch_session/1` before the 7 session can be accessed. 8 9 The session is also lazy. Once configured, a cookie header with the 10 session will only be sent to the client if something is written to the 11 session in the first place. 12 13 When using `Plug.Session`, also consider using `Plug.CSRFProtection` 14 to avoid Cross Site Request Forgery attacks. 15 16 ## Session stores 17 18 See `Plug.Session.Store` for the specification session stores are required to 19 implement. 20 21 Plug ships with the following session stores: 22 23 * `Plug.Session.ETS` 24 * `Plug.Session.COOKIE` 25 26 ## Options 27 28 * `:store` - session store module (required); 29 * `:key` - session cookie key (required); 30 * `:domain` - see `Plug.Conn.put_resp_cookie/4`; 31 * `:max_age` - see `Plug.Conn.put_resp_cookie/4`; 32 * `:path` - see `Plug.Conn.put_resp_cookie/4`; 33 * `:secure` - see `Plug.Conn.put_resp_cookie/4`; 34 * `:http_only` - see `Plug.Conn.put_resp_cookie/4`; 35 * `:same_site` - see `Plug.Conn.put_resp_cookie/4`; 36 * `:extra` - see `Plug.Conn.put_resp_cookie/4`; 37 38 Additional options can be given to the session store, see the store's 39 documentation for the options it accepts. 40 41 ## Examples 42 43 plug Plug.Session, store: :ets, key: "_my_app_session", table: :session 44 """ 45 46 alias Plug.Conn 47 @behaviour Plug 48 49 @cookie_opts [:domain, :max_age, :path, :secure, :http_only, :extra, :same_site] 50 51 @impl true 52 def init(opts) do 53 store = Plug.Session.Store.get(Keyword.fetch!(opts, :store)) 54 key = Keyword.fetch!(opts, :key) 55 cookie_opts = Keyword.take(opts, @cookie_opts) 56 store_opts = Keyword.drop(opts, [:store, :key] ++ @cookie_opts) 57 store_config = store.init(store_opts) 58 59 %{ 60 store: store, 61 store_config: store_config, 62 key: key, 63 cookie_opts: cookie_opts 64 } 65 end 66 67 @impl true 68 def call(conn, config) do 69 Conn.put_private(conn, :plug_session_fetch, fetch_session(config)) 70 end 71 72 defp fetch_session(config) do 73 %{store: store, store_config: store_config, key: key} = config 74 75 fn conn -> 76 {sid, session} = 77 if cookie = conn.cookies[key] do 78 store.get(conn, cookie, store_config) 79 else 80 {nil, %{}} 81 end 82 83 session = Map.merge(session, Map.get(conn.private, :plug_session, %{})) 84 85 conn 86 |> Conn.put_private(:plug_session, session) 87 |> Conn.put_private(:plug_session_fetch, :done) 88 |> Conn.register_before_send(before_send(sid, config)) 89 end 90 end 91 92 defp before_send(sid, config) do 93 fn conn -> 94 case Map.get(conn.private, :plug_session_info) do 95 :write -> 96 value = put_session(sid, conn, config) 97 put_cookie(value, conn, config) 98 99 :drop -> 100 drop_session(sid, conn, config) 101 102 :renew -> 103 renew_session(sid, conn, config) 104 105 :ignore -> 106 conn 107 108 nil -> 109 conn 110 end 111 end 112 end 113 114 defp drop_session(sid, conn, config) do 115 if sid do 116 delete_session(sid, conn, config) 117 delete_cookie(conn, config) 118 else 119 conn 120 end 121 end 122 123 defp renew_session(sid, conn, config) do 124 if sid, do: delete_session(sid, conn, config) 125 value = put_session(nil, conn, config) 126 put_cookie(value, conn, config) 127 end 128 129 defp put_session(sid, conn, %{store: store, store_config: store_config}), 130 do: store.put(conn, sid, conn.private[:plug_session], store_config) 131 132 defp delete_session(sid, conn, %{store: store, store_config: store_config}), 133 do: store.delete(conn, sid, store_config) 134 135 defp put_cookie(value, conn, %{cookie_opts: cookie_opts, key: key}), 136 do: Conn.put_resp_cookie(conn, key, value, cookie_opts) 137 138 defp delete_cookie(conn, %{cookie_opts: cookie_opts, key: key}), 139 do: Conn.delete_resp_cookie(conn, key, cookie_opts) 140 end