Current section

Files

Jump to
hologram lib hologram runtime session.ex
Raw

lib/hologram/runtime/session.ex

defmodule Hologram.Runtime.Session do
@moduledoc false
alias Plug.Crypto.KeyGenerator
alias Plug.Crypto.MessageEncryptor
@type op :: :delete | {:put, any}
@doc """
Initializes or retrieves an existing session ID from the "hologram_session" cookie.
Returns `{conn, session_id}` where:
- `conn` is the connection with the session cookie set
- `session_id` is the UUID of the session
"""
@spec init(Plug.Conn.t()) :: {Plug.Conn.t(), String.t()}
def init(initial_conn) do
# This is idempotent
conn = Plug.Conn.fetch_cookies(initial_conn)
case conn.req_cookies["hologram_session"] do
nil ->
create_new_session(conn)
encrypted_session ->
case decrypt_session_data(encrypted_session) do
{:ok, session_data} ->
session_id = session_data.session_id
{conn, session_id}
{:error, _reason} ->
create_new_session(conn)
end
end
end
defp create_new_session(conn) do
session_id = UUID.uuid4()
session_data = %{session_id: session_id}
encrypted_session = encrypt_session_data(session_data)
new_conn = put_session_cookie(conn, encrypted_session)
{new_conn, session_id}
end
# sobelow_skip ["Misc.BinToTerm"]
defp decrypt_session_data(encrypted_data) do
encryption_key = derive_encryption_key()
# last param (sign key) in encrypt/3 is not used
case MessageEncryptor.decrypt(encrypted_data, encryption_key, "") do
{:ok, decrypted_data} ->
{:ok, :erlang.binary_to_term(decrypted_data)}
:error ->
{:error, :invalid_session}
end
rescue
_error -> {:error, :invalid_session}
end
defp derive_encryption_key do
"SECRET_KEY_BASE"
|> System.fetch_env!()
|> KeyGenerator.generate("hologram session")
end
defp encrypt_session_data(data) do
serialized_data = :erlang.term_to_binary(data, compressed: 6)
encryption_key = derive_encryption_key()
# last param (sign key) in encrypt/3 is not used
MessageEncryptor.encrypt(serialized_data, encryption_key, "")
end
defp put_session_cookie(conn, encrypted_session) do
Plug.Conn.put_resp_cookie(conn, "hologram_session", encrypted_session,
http_only: true,
same_site: "Lax",
secure: conn.scheme == :https
)
end
end