Records the one-time claim of an instance by its first account.
Use this for invitation-only registration, where the first account has nobody to invite it.
The bootstrap row and its unique index enforce a single claim, including concurrent attempts.
Open-registration applications need not use it. The default initial_claim: :open keeps existing
integrations compatible. An application exposed before its first account exists should enable
initial_claim: :operator_code and apply schema version 3; claim/2 then requires a short-lived
authorization from authorize_code/1. See Invitations.
alias Ecto.Multi
alias MyApp.Accounts.User
Multi.new()
|> Multi.insert(:account, User.changeset(%User{}, attrs))
|> Ithibati.Identity.Instance.claim()
|> Ithibati.Identity.Grant.with_key_and_codes(key_attrs)
|> MyApp.Repo.transaction()Place claim/2 before the grant so a rejected claim does not generate recovery codes.
The bootstrap record survives deletion of the account that claimed it.
Summary
Functions
Exchanges the current operator code for a ten-minute authorization.
Returns whether a session authorization is unexpired and matches the current code.
Appends a :bootstrap step to the multi and returns the multi.
Issues or replaces the first-account operator code.
Returns true when the instance has no bootstrap claim, and false once it has been claimed.
Functions
Exchanges the current operator code for a ten-minute authorization.
Returns {:ok, proof}, {:error, :invalid_setup_code}, or {:error, :claim_is_open} when
initial_claim is not :operator_code. Store the proof only in a protected,
signed browser session. authorized?/1 rechecks it before the passkey challenge; claim/2
checks and consumes it again inside the registration transaction. The plaintext code is never
placed in that session.
Returns whether a session authorization is unexpired and matches the current code.
Appends a :bootstrap step to the multi and returns the multi.
The :account option names an earlier step providing the account and defaults to :account.
That step is required. In initial_claim: :operator_code mode, pass the proof from
authorize_code/1 as authorization:. Without a current, unexpired proof the :bootstrap
step returns :setup_authorization_required and the account insert rolls back. In :open
mode no proof is required. On success, :bootstrap contains the inserted
Ithibati.Bootstrap row.
An existing claim makes Repo.transaction/1 return
{:error, :bootstrap, :already_claimed, changes_so_far}. Other insert errors return a changeset
as the reason under the same step name. The transaction rolls back on either failure.
The unique index enforces this result even when callers attempt the first claim concurrently.
Issues or replaces the first-account operator code.
Returns {:ok, code} before the instance is claimed, {:error, :already_claimed} afterwards,
and {:error, :claim_is_open} when initial_claim is not :operator_code — an instance that
leaves its claim open has no code to issue. A value that is neither mode raises: that is a
mistake in the configuration rather than a state a caller can be in.
The code contains 32 random bytes encoded as Base64url. Only its SHA-256 digest is stored. Print the plaintext once from an explicit application-owned operator command; never include it in a web response or startup log.
Rotation invalidates both the old code and session authorizations made from it. Issuance and claim synchronize through the code row. Errors from the repo propagate.
Returns true when the instance has no bootstrap claim, and false once it has been claimed.
This checks Ithibati.Bootstrap, not the account or passkey tables. An application that never
calls claim/2 continues to need setup according to this function, regardless of account count.
Deleting the account that made the claim does not reset it.
Use this result to choose what a setup page displays. claim/2 enforces the one-time claim
inside the transaction; a prior true result does not reserve it.
Ithibati.Identity.Passkeys.authentication_challenge/3 separately checks for stored passkeys.
A claimed instance can have no passkeys and still be accessible through recovery codes.