AttestoPhoenix.Schema.DPoPNonce (AttestoPhoenix v0.6.1)

Copy Markdown View Source

Ecto schema for a single server-issued DPoP nonce (RFC 9449 §8).

Each row records one nonce, the instant it was issued, and the instant it was consumed (nil while still unused). The single-use guarantee of Attesto.DPoP.NonceStore is implemented at the storage layer by a conditional update against used_at; this schema only describes the row shape and does not embed any consumption policy.

Columns

  • nonce - the opaque, unpredictable value returned to the client in the DPoP-Nonce response header (RFC 9449 §8.1). A unique index on this column is required so a nonce can be issued at most once.
  • issued_at - issuance instant. Combined with a caller-supplied TTL at consume time it defines the freshness window (RFC 9449 §8).
  • expires_at - precomputed expiry (issued_at + ttl at issuance) so a stateless freshness check has no TTL argument to supply.
  • used_at - consumption instant, or nil while unused. The transition from nil to non-nil happens exactly once.

Summary

Types

t()

A persisted DPoP nonce row.

Functions

Changeset that marks an issued nonce as consumed at used_at.

Changeset for inserting a freshly issued nonce.

Types

t()

@type t() :: %AttestoPhoenix.Schema.DPoPNonce{
  __meta__: term(),
  expires_at: DateTime.t() | nil,
  id: Ecto.UUID.t() | nil,
  issued_at: DateTime.t() | nil,
  nonce: String.t() | nil,
  used_at: DateTime.t() | nil
}

A persisted DPoP nonce row.

Functions

consume_changeset(nonce, used_at)

@spec consume_changeset(t(), DateTime.t()) :: Ecto.Changeset.t()

Changeset that marks an issued nonce as consumed at used_at.

Single-use acceptance (RFC 9449 §8): a nonce may be spent exactly once. The caller performs the load-and-stamp atomically (a conditional update_all guarded on used_at IS NULL) so two concurrent nodes cannot both observe the same nonce as unused; this changeset only describes the field write.

issue_changeset(nonce \\ %__MODULE__{}, attrs)

@spec issue_changeset(
  t()
  | %AttestoPhoenix.Schema.DPoPNonce{
      __meta__: term(),
      expires_at: term(),
      id: term(),
      issued_at: term(),
      nonce: term(),
      used_at: term()
    },
  map()
) :: Ecto.Changeset.t()

Changeset for inserting a freshly issued nonce.

Requires the opaque :nonce value and both bounding instants. A nonce with no expiry would never fail closed, so a missing :expires_at (or :issued_at) is a hard validation error rather than a silently issued unlimited nonce. The unique_constraint/3 on :nonce surfaces a duplicate issuance as a changeset error rather than a raised exception, so a caller can treat a collision as a generation retry.