141 lines
3.9 KiB
Elixir
141 lines
3.9 KiB
Elixir
defmodule Plug.Session do
|
|
@moduledoc """
|
|
A plug to handle session cookies and session stores.
|
|
|
|
The session is accessed via functions on `Plug.Conn`. Cookies and
|
|
session have to be fetched with `Plug.Conn.fetch_session/1` before the
|
|
session can be accessed.
|
|
|
|
The session is also lazy. Once configured, a cookie header with the
|
|
session will only be sent to the client if something is written to the
|
|
session in the first place.
|
|
|
|
When using `Plug.Session`, also consider using `Plug.CSRFProtection`
|
|
to avoid Cross Site Request Forgery attacks.
|
|
|
|
## Session stores
|
|
|
|
See `Plug.Session.Store` for the specification session stores are required to
|
|
implement.
|
|
|
|
Plug ships with the following session stores:
|
|
|
|
* `Plug.Session.ETS`
|
|
* `Plug.Session.COOKIE`
|
|
|
|
## Options
|
|
|
|
* `:store` - session store module (required);
|
|
* `:key` - session cookie key (required);
|
|
* `:domain` - see `Plug.Conn.put_resp_cookie/4`;
|
|
* `:max_age` - see `Plug.Conn.put_resp_cookie/4`;
|
|
* `:path` - see `Plug.Conn.put_resp_cookie/4`;
|
|
* `:secure` - see `Plug.Conn.put_resp_cookie/4`;
|
|
* `:http_only` - see `Plug.Conn.put_resp_cookie/4`;
|
|
* `:same_site` - see `Plug.Conn.put_resp_cookie/4`;
|
|
* `:extra` - see `Plug.Conn.put_resp_cookie/4`;
|
|
|
|
Additional options can be given to the session store, see the store's
|
|
documentation for the options it accepts.
|
|
|
|
## Examples
|
|
|
|
plug Plug.Session, store: :ets, key: "_my_app_session", table: :session
|
|
"""
|
|
|
|
alias Plug.Conn
|
|
@behaviour Plug
|
|
|
|
@cookie_opts [:domain, :max_age, :path, :secure, :http_only, :extra, :same_site]
|
|
|
|
@impl true
|
|
def init(opts) do
|
|
store = Plug.Session.Store.get(Keyword.fetch!(opts, :store))
|
|
key = Keyword.fetch!(opts, :key)
|
|
cookie_opts = Keyword.take(opts, @cookie_opts)
|
|
store_opts = Keyword.drop(opts, [:store, :key] ++ @cookie_opts)
|
|
store_config = store.init(store_opts)
|
|
|
|
%{
|
|
store: store,
|
|
store_config: store_config,
|
|
key: key,
|
|
cookie_opts: cookie_opts
|
|
}
|
|
end
|
|
|
|
@impl true
|
|
def call(conn, config) do
|
|
Conn.put_private(conn, :plug_session_fetch, fetch_session(config))
|
|
end
|
|
|
|
defp fetch_session(config) do
|
|
%{store: store, store_config: store_config, key: key} = config
|
|
|
|
fn conn ->
|
|
{sid, session} =
|
|
if cookie = Plug.Conn.get_cookies(conn)[key] do
|
|
store.get(conn, cookie, store_config)
|
|
else
|
|
{nil, %{}}
|
|
end
|
|
|
|
session = Map.merge(session, Map.get(conn.private, :plug_session, %{}))
|
|
|
|
conn
|
|
|> Conn.put_private(:plug_session, session)
|
|
|> Conn.put_private(:plug_session_fetch, :done)
|
|
|> Conn.register_before_send(before_send(sid, config))
|
|
end
|
|
end
|
|
|
|
defp before_send(sid, config) do
|
|
fn conn ->
|
|
case Map.get(conn.private, :plug_session_info) do
|
|
:write ->
|
|
value = put_session(sid, conn, config)
|
|
put_cookie(value, conn, config)
|
|
|
|
:drop ->
|
|
drop_session(sid, conn, config)
|
|
|
|
:renew ->
|
|
renew_session(sid, conn, config)
|
|
|
|
:ignore ->
|
|
conn
|
|
|
|
nil ->
|
|
conn
|
|
end
|
|
end
|
|
end
|
|
|
|
defp drop_session(sid, conn, config) do
|
|
if sid do
|
|
delete_session(sid, conn, config)
|
|
delete_cookie(conn, config)
|
|
else
|
|
conn
|
|
end
|
|
end
|
|
|
|
defp renew_session(sid, conn, config) do
|
|
if sid, do: delete_session(sid, conn, config)
|
|
value = put_session(nil, conn, config)
|
|
put_cookie(value, conn, config)
|
|
end
|
|
|
|
defp put_session(sid, conn, %{store: store, store_config: store_config}),
|
|
do: store.put(conn, sid, conn.private[:plug_session], store_config)
|
|
|
|
defp delete_session(sid, conn, %{store: store, store_config: store_config}),
|
|
do: store.delete(conn, sid, store_config)
|
|
|
|
defp put_cookie(value, conn, %{cookie_opts: cookie_opts, key: key}),
|
|
do: Conn.put_resp_cookie(conn, key, value, cookie_opts)
|
|
|
|
defp delete_cookie(conn, %{cookie_opts: cookie_opts, key: key}),
|
|
do: Conn.delete_resp_cookie(conn, key, cookie_opts)
|
|
end
|