# `Ithibati.Identity.Instance`
[🔗](https://github.com/oliverandrich/ithibati/blob/v0.6.2/lib/ithibati/identity/instance.ex#L1)

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](invitations.md#protect-the-first-account-claim).

    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.

# `authorize_code`

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.

# `authorized?`

Returns whether a session authorization is unexpired and matches the current code.

# `claim`

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.

# `issue_code`

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.

# `needs_setup?`

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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
