Current section
Files
Jump to
Current section
Files
lib/session_header_plug.ex
defmodule SessionHeaderPlug do
@moduledoc """
A plug to handle session headers and session stores.
The session is accessed via functions on `Plug.Conn`. Headers and session have
to be fetched with `Plug.Conn.fetch_session/1` before the session can be
accessed.
## Session stores
See `Plug.Session.Store` for the specification session stores are required to
implement.
Client-side session storage (e.g., `Plug.Session.COOKIE`) is inherently
insecure. Consider using `SessionServerStore`.
## Options
* `:store` - session store module (required)
* `:key` - session header key (required)
Additional options can be given to the session store, see the store’s
documentation for the options it accepts.
## Examples
plug SessionHeaderPlug, store: SessionServerStore, key: "session-id"
"""
alias Plug.Conn
@behaviour Plug
@type config :: %{key: String.t, store: module, store_config: keyword}
@typep conn :: Plug.Conn.t
@typep sid :: Plug.Session.Store.sid
## Callbacks
@spec init(keyword) :: config
def init(opts) do
key = Keyword.fetch!(opts, :key)
store = Keyword.fetch!(opts, :store)
store_config =
opts
|> Keyword.drop([:key, :store])
|> store.init()
%{key: key, store: store, store_config: store_config}
end
@spec call(conn, config) :: Plug.Conn.t
def call(conn, %{key: key} = config) do
conn
|> Conn.put_resp_header("access-control-allow-headers", key)
|> Conn.put_private(:plug_session_fetch, &fetch_session(&1, config))
end
@spec fetch_session(conn, config) :: conn
defp fetch_session(conn, config) do
{sid, session} = do_fetch_session(conn, config)
plug_session = Map.merge(session, Map.get(conn.private, :plug_session, %{}))
conn
|> Conn.put_private(:plug_session, plug_session)
|> Conn.put_private(:plug_session_fetch, :done)
|> Conn.register_before_send(&before_send(&1, sid, config))
end
@spec do_fetch_session(conn, config) :: {sid, map}
defp do_fetch_session(conn, %{key: key, store: store, store_config: store_config}) do
if req_sid = :proplists.get_value(key, conn.req_headers, nil) do
store.get(conn, req_sid, store_config)
else
{nil, %{}}
end
end
@spec before_send(conn, sid, config) :: conn
defp before_send(conn, sid, config) do
case Map.get(conn.private, :plug_session_info) do
:renew -> renew_session(conn, sid, config)
:drop -> drop_session(conn, sid, config)
:write -> write_session(conn, sid, config)
:ignore -> conn
nil -> conn
end
end
@spec renew_session(conn, sid, config) :: conn
defp renew_session(conn, sid, config) do
conn
|> drop_session(sid, config)
|> write_session(nil, config)
end
@spec drop_session(conn, sid, config) :: conn
defp drop_session(conn, sid, %{store: store, store_config: store_config}) do
if sid, do: store.delete(conn, sid, store_config)
conn
end
@spec write_session(conn, sid, config) :: conn
defp write_session(conn, sid, %{key: key, store: store, store_config: store_config}) do
value = store.put(conn, sid, conn.private[:plug_session], store_config)
conn
|> Conn.put_resp_header(key, value)
|> Conn.put_resp_header("access-control-expose-headers", key)
end
end