Users can access their items via API. Authentication via API token. No public access to items.

This commit is contained in:
2026-04-22 10:32:42 +02:00
parent 6076654aa4
commit b077a1c81c
7 changed files with 143 additions and 1 deletions

View File

@ -281,6 +281,32 @@ defmodule GenericRestServer.Accounts do
:ok
end
## API
@doc """
Creates a new api token for a user.
The token returned must be saved somewhere safe.
This token cannot be recovered from the database.
"""
def create_user_api_token(user) do
{encoded_token, user_token} = UserToken.build_email_token(user, "api-token")
Repo.insert!(user_token)
encoded_token
end
@doc """
Fetches the user by API token.
"""
def fetch_user_by_api_token(token) do
with {:ok, query} <- UserToken.verify_api_token_query(token),
%User{} = user <- Repo.one(query) do
{:ok, user}
else
_ -> :error
end
end
## Token helper
defp update_user_and_delete_all_tokens(changeset) do

View File

@ -11,6 +11,7 @@ defmodule GenericRestServer.Accounts.UserToken do
@magic_link_validity_in_minutes 15
@change_email_validity_in_days 7
@session_validity_in_days 14
@api_token_validity_in_days 30
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@ -155,4 +156,35 @@ defmodule GenericRestServer.Accounts.UserToken do
defp by_token_and_context_query(token, context) do
from UserToken, where: [token: ^token, context: ^context]
end
## API
@doc """
Checks if the API token is valid and returns its underlying lookup query.
The query returns the user found by the token, if any.
The given token is valid if it matches its hashed counterpart in the
database and the user email has not changed. This function also checks
if the token is being used within 365 days.
"""
def verify_api_token_query(token) do
case Base.url_decode64(token, padding: false) do
{:ok, decoded_token} ->
hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
query =
from token in by_token_and_context_query(hashed_token, "api-token"),
join: user in assoc(token, :user),
where:
token.inserted_at > ago(^@api_token_validity_in_days, "day") and
token.sent_to == user.email,
select: user
{:ok, query}
:error ->
:error
end
end
end