sloanelybutsurely.com/lib/core/accounts/user_token.ex
2025-03-24 07:39:09 -04:00

68 lines
2.2 KiB
Elixir

defmodule Core.Accounts.UserToken do
@moduledoc false
@rand_size 32
@doc """
Generates a token that will be stored in a signed place,
such as session or cookie. As they are signed, those
tokens do not need to be hashed.
The reason why we store session tokens in the database, even
though Phoenix already provides a session cookie, is because
Phoenix' default session cookies are not persisted, they are
simply signed and potentially encrypted. This means they are
valid indefinitely, unless you change the signing/encryption
salt.
Therefore, storing them allows individual user
sessions to be expired. The token system can also be extended
to store additional data, such as the device used for logging in.
You could then use this information to display all valid sessions
and devices in the UI and allow users to explicitly expire any
session they deem invalid.
"""
def build_session_token(user) do
token = :crypto.strong_rand_bytes(@rand_size)
{token, %Schema.UserToken{token: token, context: "session", user_id: user.id}}
end
defmodule Query do
@moduledoc false
import Ecto.Query
@session_validity_in_days 60
def base do
from Schema.UserToken, as: :user_tokens
end
def join_users(query \\ base()) do
join(query, :inner, [user_tokens: ut], u in assoc(ut, :user), as: :users)
end
@doc """
Checks if the token is valid and returns its underlying lookup query.
The query returns the user found by the token, if any.
The token is valid if it matches the value in the database and it has
not expired (after @session_validity_in_days).
"""
def valid_session_token(query \\ base(), token) do
query
|> where_token_and_context(token, "session")
|> join_users()
|> where([user_tokens: ut], ut.inserted_at > ago(@session_validity_in_days, "day"))
|> select([users: user], user)
end
def where_token_and_context(query \\ base(), token, context) do
where(query, [user_tokens: ut], ut.token == ^token and ut.context == ^context)
end
def for_user(query \\ base(), user) do
where(query, [user_tokens: t], t.user_id == ^user.id)
end
end
end