AshOnetime.Token (ash_onetime v0.5.0)

Copy Markdown View Source

Mints, signs, and verifies bounded self-identifying canonical tokens.

The signed body binds the algorithm, key identifier, namespace, keyed-effect key, issuance instant, and optional expiry instant. Verification requires an expected algorithm and namespace supplied outside the token.

Summary

Functions

Mints a token struct from a key and a keyword of binding options.

Signs a minted token into a self-identifying wire string under the resolved key material.

Verifies a wire token against expected algorithm and namespace, returning the bound token.

Types

algorithm()

@type algorithm() :: :hmac_sha256 | :ed25519

result(value)

@type result(value) :: {:ok, value} | {:error, AshOnetime.Error.t()}

t()

@type t() :: %AshOnetime.Token{
  algorithm: algorithm(),
  expires_at: DateTime.t() | nil,
  issued_at: DateTime.t(),
  key: binary(),
  key_id: binary(),
  namespace: binary()
}

Functions

mint(key, options)

@spec mint(
  binary(),
  keyword()
) :: result(t())

Mints a token struct from a key and a keyword of binding options.

The options carry the security-relevant fields the signed body will bind: :algorithm (:hmac_sha256 or :ed25519), :key_id (the resolver-scoped key identifier, ≤128 bytes), :namespace (the caller-supplied namespace, ≤128 bytes), :issued_at (a DateTime, defaulting to Clock.now/0), and optional :expires_at (a DateTime strictly after issued_at). Every field is validated before the struct is built; an invalid option or a non-binary key returns {:error, %AshOnetime.Error{}} without minting.

sign(token, resolver, resolver_context)

@spec sign(t(), module(), term()) :: result(binary())

Signs a minted token into a self-identifying wire string under the resolved key material.

resolver is a module implementing the key-resolution callback (resolve/4 for :sign), invoked as resolver.resolve(:sign, token, resolver_context) to obtain the signing material. The body is canonical-encoded, signed by the token's algorithm (HMAC or Ed25519), and wrapped in a base64url envelope prefixed ash_onetime.. The signature binds exactly the canonical body bytes — the algorithm, key identifier, namespace, key, issuance instant, and expiry.

verify(encoded, resolver, options)

@spec verify(binary(), module(), keyword()) :: result(t())

Verifies a wire token against expected algorithm and namespace, returning the bound token.

options MUST supply :algorithm and :namespace from outside the token (these are replay-binding expectations, not token-supplied facts), and MAY supply :max_age, :clock_skew, and :resolver_context. The body is re-derived, the expected algorithm and namespace are bound against the token's, the window is validated, and the signature is verified under the resolved key material (resolver.resolve(:verify, token, context)). Every field of the decoded body is re-validated before the signature check, so a tampered body cannot reach verification. Returns {:ok, token} or {:error, %AshOnetime.Error{}}.