/** * Tier-2 authenticator slot management. * * Each slot independently wraps the SAME KEK under a method-specific * derived key (LUKS pattern). Enrolling adds a slot; removing drops * one. Both are constant-time keyring writes — no DEK re-keying. * * The crypto for each method lives in its `@noy-db/on-*` package * (`on-webauthn`, `on-oidc`, `on-password`); this module accepts the * package's `wrapped_kek` ciphertext + `meta` payload and persists it. * * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/session-tiers.md → Tier 2 — Authenticate * * @module */ import type { NoydbStore, KeyringAuthenticator } from '../../kernel/types.js'; import type { UnlockedKeyring } from './keyring.js'; /** Fields shared across both wrap-KEK and wrap-DEKs enroll inputs. */ interface EnrollAuthenticatorBase { readonly id: string; readonly method: KeyringAuthenticator['method']; /** Method-specific metadata (cred id, salt, …). */ readonly meta: Record; /** Tier the active session held when enrolling. Defaults to 1. */ readonly enrolled_via_tier?: 1 | 2; } /** Wrap-KEK enroll input (WebAuthn, OIDC). */ export interface EnrollAuthenticatorWrappingKEKOptions extends EnrollAuthenticatorBase { /** Already-wrapped KEK ciphertext (base64) — produced by the on-* package. */ readonly wrapped_kek: string; readonly wrapKind?: 'kek'; } /** Wrap-DEKs enroll input (password, future on-* using the unified wrap-DEKs primitive). */ export interface EnrollAuthenticatorWrappingDEKsOptions extends EnrollAuthenticatorBase { readonly wrapKind: 'deks'; /** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */ readonly wrapped_deks: string; /** Base64 AES-GCM IV used for the `wrapped_deks` ciphertext. */ readonly iv: string; } /** Discriminated union over the two enroll input shapes. */ export type EnrollAuthenticatorOptions = EnrollAuthenticatorWrappingKEKOptions | EnrollAuthenticatorWrappingDEKsOptions; /** * Append a new authenticator slot to the keyring file. Throws * `ValidationError` if a slot with the same id already exists — the * caller decides whether to remove + re-enroll. * * Accepts either wrap-KEK (WebAuthn, OIDC) or wrap-DEKs (password) * input. The variant is preserved verbatim into `KeyringAuthenticator`. */ export declare function enrollAuthenticator(store: NoydbStore, vault: string, keyring: UnlockedKeyring, options: EnrollAuthenticatorOptions): Promise; /** * Caller payload for {@link updateAuthenticator}. Mutates only * `meta` — the slot's id, method, and wrap material are immutable * through this primitive, preserving the anti-slot-swap guard. * * `meta` is **merged** at the top level: keys absent from the patch * are preserved, keys present overwrite. To clear a meta key, pass * `null` for that key explicitly. (Same top-level merge semantics as * `UserApi.updateMe`, non-recursive — meta is a flat label bag.) */ export interface UpdateAuthenticatorOptions { readonly meta?: Record; } /** * Mutate a tier-2 authenticator slot's `meta` blob (slot rename, * label changes). The slot's `id`, `method`, and wrap material * (`wrapped_kek` for wrap-KEK; `wrapped_deks` + `iv` for wrap-DEKs) * are immutable through this entry point — the anti-slot-swap guard * is structural, not gate-driven, so even if the policy gate is * weakened a future caller cannot use this path to swap one slot's * crypto for another's. * * `meta` patch semantics: * - Top-level merge — absent keys preserved, present keys overwrite * - `null` value — delete that meta key * - Non-object values (string, number, boolean, array) — replace verbatim * * @throws `NoAccessError` when no slot with the given id exists. * @throws `ValidationError` when no patch field is provided. * */ export declare function updateAuthenticator(store: NoydbStore, vault: string, keyring: UnlockedKeyring, slotId: string, options: UpdateAuthenticatorOptions): Promise; /** * Drop a slot by id. No-op if the slot doesn't exist (idempotent — * removing a non-existent slot is a recoverable retry, not an error). */ export declare function removeAuthenticator(store: NoydbStore, vault: string, keyring: UnlockedKeyring, slotId: string): Promise; /** * Look up a slot by id. Returns `undefined` when no slot matches. * Used by tier-2 unlock dispatchers to fetch the wrapped KEK + meta * before invoking the method-specific verifier. */ export declare function findAuthenticator(keyring: UnlockedKeyring, slotId: string): KeyringAuthenticator | undefined; export {};