stable

auth-introspection

Read-only views of a vault's authentication setup: a plain-English summary, a three-tier model diagram, and a policy-gated per-user enrollment view.
Subpath@noy-db/hub (root export)
TypeCore feature — describeAuthConfig() and diagramAuthConfig() are always available.

What it is

auth-introspection answers the question "how is authentication configured in this vault, and what has each user actually enrolled?" — as ready-to-display text, so an admin screen, CLI, or audit report never has to re-derive it from raw keyring files.

Two pieces of context, if this is your first noy-db auth page:

  • noy-db authenticates users against a three-tier model: tier 1 is the master secret, tier 2 is routine login through enrolled authenticators (passkey, OIDC, password), tier 3 is a PIN for quick resume after idle. See session-tiers for the full model.
  • Sensitive operations are guarded by policy gates — per-action rules stored with the vault that declare which tier (and which extra factor proofs) an action requires. See policy.

The service exposes three read-only surfaces over that model:

  1. A vault-wide English summary — the configured tiers, phrase-format rules, enrolled recovery profiles, and every configured gate, as plain text.
  2. A vault-wide Mermaid diagram — the same auth graph as Mermaid flowchart TB source, ready to render as an SVG.
  3. A per-user enrollment view — which authenticator slots a given user has enrolled. This surface is doubly protected: it is gated by the view-user-auth policy gate (disabled by default — calls fail until you enable it), and its output passes through a strict field allowlist so credential material can never leak into a rendered string.

It is always on within its layer — there is no with*() factory to opt into and no bundle-size decision to make.

Setup

Nothing to wire for the two vault-wide surfaces. The only opt-in is the view-user-auth gate for the per-user surface: both built-in policy presets (PERSONAL_POLICY, STRICT_POLICY) ship it as { minTier: 1, enabled: false }, so per-user introspection fails closed until you enable it deliberately:

import { createNoydb } from '@noy-db/hub'
import { toFile } from '@noy-db/to-file'

const db = await createNoydb({
  store: toFile({ dir: './data' }),
  user: 'alice',
  secret: 'orange river candle mountain silver fox',
  policy: {
    gates: {
      // Enable per-user introspection for tier-1 (secret-opened) sessions.
      'view-user-auth': { minTier: 1 },
    },
  },
})

The policy option is merged gate-by-gate over PERSONAL_POLICY and persisted at _meta/policy on the vault's first open; from then on the on-disk document wins. On an existing vault, change it with db.updatePolicy(vault, { gates: { 'view-user-auth': { minTier: 1 } } }) — the override merges into the current policy, leaving the other gates intact.

Usage

Vault-wide English summary

import { createNoydb } from '@noy-db/hub'
import { toFile } from '@noy-db/to-file'

const db = await createNoydb({
  store: toFile({ dir: './data' }),
  user: 'alice',
  secret: 'orange river candle mountain silver fox',
})
await db.openVault('acme')

const summary = await db.team.describeAuthConfig('acme')
console.log(summary)
Vault "acme" — three-tier authentication

Tier 1 — Secret (master)
  Phrase format: 6+ words, lowercase letters, ≥3 chars/word
  Strength validator: enforced (override available for tests only)

Tier 2 — Authenticate (routine login)
  Allowed methods: WebAuthn (passkey), OIDC, Password
  Slots per user: unlimited

Tier 3 — Unlock (quick resume)
  Method: PIN (per-app configurable)

Recovery profiles enrolled: none
Managed-secret mode: off (post-1.0)

Sensitive-action gates:
  rotate-secret — tier 1 + 1× totp|email-otp|recovery|webauthn-roaming|webauthn-platform|password|pin
  recover-secret — tier 1
  rotate-unlock — tier 2
  export-plaintext — tier 1 + 1× totp|email-otp
  view-user-auth — disabled
  …one line per configured gate

The summary reflects the policy persisted at _meta/policy. If no policy document exists on disk (a store written outside createNoydb), the summary falls back to the defaults (6+ words, ≥3 chars/word, no gates listed).

Vault-wide Mermaid diagram

diagramAuthConfig() returns Mermaid source, not an image — pipe it through Mermaid (CLI, browser bundle, or a docs renderer) to get the SVG. Each enabled gate becomes a node attached to the tier it requires; disabled gates are omitted.

const mmd = await db.team.diagramAuthConfig('acme')
console.log(mmd)
flowchart TB
  vault["Vault: acme"]
  tier1["Tier 1<br/>Secret"]
  tier2["Tier 2<br/>Multi-slot Authenticate"]
  tier3["Tier 3<br/>PIN / Quick-resume"]
  vault --> tier1
  tier1 --> tier2
  tier2 --> tier3
  rotate_secret["rotate-secret<br/>tier ≥ 1"]
  tier1 --> rotate_secret
  rotate_unlock["rotate-unlock<br/>tier ≥ 2"]
  tier2 --> rotate_unlock
  …one node + one edge per enabled gate

Per-user enrollment view

Gated by view-user-auth (see Setup). Output is allowlist-sanitized: only the slot id, method, enrollment date, and enrolling tier are rendered — wrapped key material, WebAuthn credential ids, OIDC subject ids, password hashes, and TOTP secrets are dropped at the allowlist boundary, never redacted into placeholders.

import { PolicyDeniedError } from '@noy-db/hub'

try {
  const view = await db.team.describeUserAuth('acme', 'alice')
  console.log(view)
} catch (err) {
  if (err instanceof PolicyDeniedError) {
    // err.gate === 'view-user-auth', err.reason e.g. 'disabled' | 'insufficient-tier'
  }
  throw err
}
User: alice (joined 2026-04-15, role: owner)

Tier 2 enrollments:
  - webauthn (id=webauthn-yubikey-blue, enrolled 2026-04-15, via tier 1)

A user with a keyring but no tier-2 slots renders (none enrolled); an unknown user id returns the empty string rather than throwing, so the call alone never confirms or denies that a user exists.

For owner dashboards there is a bulk variant:

const all = await db.team.describeAllUsersAuth('acme')
// → [{ userId: 'alice', description: 'User: alice (joined 2026-04-15, role: owner)\n…' }, …]

API reference

Instance methods (on the Noydb handle)

MethodReturnsPurpose
db.team.describeAuthConfig(vault)Promise<string>Vault-wide English summary of the configured auth model. Ungated.
db.team.diagramAuthConfig(vault)Promise<string>Vault-wide auth graph as Mermaid flowchart TB source. Ungated.
db.team.describeUserAuth(vault, userId, factors?)Promise<string>Per-user enrollment summary. Checks the view-user-auth gate first; throws PolicyDeniedError on denial. Empty string for unknown users.
db.team.describeAllUsersAuth(vault, factors?)Promise<Array<{ userId: string; description: string }>>Bulk per-user summaries (users with no keyring file are skipped). Same gate check.

factors is an optional FactorProofBundle ({ factors?: ReadonlyArray<FactorProof>; sharedDevice?: boolean }) — only needed when your view-user-auth gate policy demands extra factor proofs beyond the active session tier.

Standalone functions (exported from @noy-db/hub)

The same four surfaces are exported as free functions that take a raw store — the low-level building blocks the instance methods delegate to:

import {
  describeAuthConfig,     // (store: NoydbStore, vault: string) => Promise<string>
  diagramAuthConfig,      // (store: NoydbStore, vault: string) => Promise<string>
  describeUserAuth,       // (store: NoydbStore, vault: string, userId: string) => Promise<string>
  describeAllUsersAuth,   // (store: NoydbStore, vault: string) => Promise<Array<{ userId, description }>>
} from '@noy-db/hub'

They read the vault's policy document (_meta/policy) and keyring headers directly from the store. Note the security scoping below: the gate check lives on the instance methods, not here.

view-user-auth

  • Gate: view-user-auth is a built-in gate name. Built-in gates with no configured policy fail closed, and both presets configure it as { minTier: 1, enabled: false } — so per-user introspection is off until enabled. Denials throw PolicyDeniedError carrying gate, reason ('disabled', 'insufficient-tier', 'missing-factor', 'stale-proof', 'shared-device-blocked'), and the required gate policy.
  • Allowlist: per authenticator slot, exactly four fields can ever reach the rendered string: id, method, enrolled_at, enrolled_via_tier. Everything else in the keyring file — wrapped_kek, the method-specific meta blob (credential ids, subject ids, hashes, secrets), the KDF salt, recovery codes — is projected out before rendering. Dropped, not redacted: an off-allowlist field leaves no trace in the output.

Limits & gotchas

  • Per-user introspection is off by default. Calling db.team.describeUserAuth() / db.team.describeAllUsersAuth() without enabling view-user-auth throws PolicyDeniedError with reason: 'disabled'.
  • The gate protects the app surface, not the store. The standalone functions take a raw NoydbStore and perform no gate check — anyone holding the store can already read the plaintext keyring headers they summarize. Route application code through the db.* methods so the policy applies.
  • Strings, not structures. All four surfaces return display text (or Mermaid source), formatted for humans. There is no stable machine-readable schema — don't parse the summary; read the policy document via the policy APIs instead.
  • The diagram is Mermaid source. You must render it yourself; nothing in noy-db produces the SVG.
  • Unknown user ⇒ empty string. describeUserAuth deliberately does not throw for a missing keyring, so probing it does not reveal whether a user id exists. Check for '' before displaying.
  • The summary mirrors _meta/policy. On a vault created by createNoydb() the merged policy is persisted there at creation; edits made with db.updatePolicy() show up on the next call. A store with no policy document renders the built-in defaults.

See also

  • session-tiers — the three-tier authentication model this service describes.
  • policy — the VaultPolicy / GatePolicy gate DSL, including view-user-auth.
  • team — multi-user grant/revoke, whose keyring files feed the per-user view.
  • directory — the vault-wide "who's on this team" listing, filtered by its own visibility flags rather than the view-user-auth gate.
  • Services overview · Orientation map