Current section

Files

Jump to
hologram lib hologram runtime csrf_protection.ex
Raw

lib/hologram/runtime/csrf_protection.ex

defmodule Hologram.Runtime.CSRFProtection do
@moduledoc false
@session_key "hologram_csrf_token"
# Use the same constants as Plug.CSRFProtection for compatibility
@token_size 18
@doc """
Ensures CSRF tokens exist for the given Plug connection.
Returns `{conn, {masked_token, unmasked_token}}` where the masked token should be
sent to the client and the unmasked token is stored in the session.
"""
@spec ensure_tokens(Plug.Conn.t()) :: {Plug.Conn.t(), {String.t(), String.t()}}
def ensure_tokens(conn) do
case Plug.Conn.get_session(conn, @session_key) do
nil ->
{masked_token, unmasked_token} = generate_tokens()
updated_conn = Plug.Conn.put_session(conn, @session_key, unmasked_token)
{updated_conn, {masked_token, unmasked_token}}
unmasked_token ->
masked_token = get_masked_token(unmasked_token)
{conn, {masked_token, unmasked_token}}
end
end
@doc """
Generates both masked and unmasked CSRF tokens.
Returns `{masked_token, unmasked_token}` where the masked token should be
sent to the client and the unmasked token should be stored in the session.
"""
@spec generate_tokens :: {String.t(), String.t()}
def generate_tokens do
unmasked_token = generate_unmasked_token()
masked_token = get_masked_token(unmasked_token)
{masked_token, unmasked_token}
end
@doc """
Generates an unmasked CSRF token for session storage.
"""
@spec generate_unmasked_token :: String.t()
def generate_unmasked_token do
# Use the same token generation as Plug.CSRFProtection
@token_size
|> :crypto.strong_rand_bytes()
|> Base.url_encode64()
end
@doc """
Generates a masked CSRF token for client use from an unmasked token.
"""
@spec get_masked_token(String.t()) :: String.t()
def get_masked_token(unmasked_token) do
mask = generate_unmasked_token()
# Use the same masking algorithm as Plug.CSRFProtection
encoded_masked_token =
unmasked_token
|> Plug.Crypto.mask(mask)
|> Base.url_encode64()
encoded_masked_token <> mask
end
@doc """
Returns the session key used to store the CSRF token.
"""
@spec session_key :: String.t()
def session_key, do: @session_key
@doc """
Validates a client-provided CSRF token against the session token.
Returns `true` if the masked client token is valid for the given session token.
"""
@spec validate_token(String.t(), String.t()) :: boolean
defdelegate validate_token(session_token, client_token),
to: Plug.CSRFProtection,
as: :valid_state_and_csrf_token?
end