defmodule OhMyExOhIrcd.Scrobbles.Token do
@moduledoc """
API token for the scrobble HTTP endpoint, bound to BOTH a channel and a
user. Only the SHA-256 hash is persisted; the plaintext is shown to the
minting user exactly once. Authentication hashes the presented bearer
token and looks it up by `token_hash`, mirroring
`OhMyExOhIrcd.Accounts.UserToken`.
The announced identity (`who`) is fixed on the token at mint time, so a
client holding the token cannot spoof who is scrobbling — it can only
post tracks as the bound user/name.
"""
use Ecto.Schema
import Ecto.Changeset
alias OhMyExOhIrcd.Accounts.User
alias OhMyExOhIrcd.Channels.ChannelRecord
@hash_algorithm :sha256
@rand_size 32
schema "scrobble_tokens" do
field :token_hash, :binary
field :who, :string
field :label, :string
field :last_used_at, :utc_datetime
field :revoked_at, :utc_datetime
belongs_to :channel, ChannelRecord
belongs_to :user, User
timestamps(type: :utc_datetime)
end
@doc """
Build a fresh token bound to `channel_id` + `user_id`, announcing as
`who`. Returns `{display_token, struct}` where `display_token` is the
URL-safe base64 string to hand to the user (shown once) and `struct`
carries only the hash for persistence.
"""
def build(channel_id, user_id, who, label) do
raw = :crypto.strong_rand_bytes(@rand_size)
struct = %__MODULE__{
token_hash: hash(raw),
who: who,
label: label,
channel_id: channel_id,
user_id: user_id
}
{Base.url_encode64(raw, padding: false), struct}
end
@doc "Hash a raw (decoded) token for storage/lookup."
def hash(raw) when is_binary(raw), do: :crypto.hash(@hash_algorithm, raw)
@doc """
Hash a presented bearer token (base64url string) for lookup. Returns
`{:ok, hash}` or `:error` if the token isn't valid base64url.
"""
def hash_presented(token) when is_binary(token) do
case Base.url_decode64(token, padding: false) do
{:ok, raw} -> {:ok, hash(raw)}
:error -> :error
end
end
def changeset(record, attrs) do
record
|> cast(attrs, [:token_hash, :who, :label, :channel_id, :user_id])
|> validate_required([:token_hash, :who, :channel_id, :user_id])
|> validate_length(:who, max: 64)
|> validate_length(:label, max: 100)
|> unique_constraint(:token_hash)
|> assoc_constraint(:channel)
|> assoc_constraint(:user)
end
end
neiam
/oh-my-ex-oh-ircd
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
2.4 KiB · text
History
4b6fe3b