auth/daemon_token_middleware.ts

Daemon token middleware — the credential consumer.

Validates a presented X-Daemon-Token and resolves the keeper account. The producer half (rotation + file persistence) lives in testing/daemon_token_rotation.ts behind the dev-env guard: no production assembly mints daemon tokens — the credential's only remaining role is the cross-process test harness's keeper channel — mirroring the Rust spine, whose producer is confined to fuz_testing.

Pure token primitives (schema, generation, validation) live in auth/daemon_token.ts. See docs/identity.md for design rationale.

view source

Declarations
#

2 declarations

create_daemon_token_middleware
#

auth/daemon_token_middleware.ts view source

(state: DaemonTokenState, deps: QueryDeps, log: Logger): MiddlewareHandler import {create_daemon_token_middleware} from '@fuzdev/fuz_app/auth/daemon_token_middleware.js';

Create middleware that authenticates via daemon token.

Checks the X-Daemon-Token header. Behavior:

  • No header: pass through (don't touch existing context).
  • Header present + Origin / Referer present: discard the credential (browser context) and pass through — daemon tokens are loopback-only and never carry an Origin in production, so a header-bearing request is not a legitimate daemon caller. Mirrors the bearer guard: next() rather than 401, so downstream auth enforcement returns credential_type_required (not a hard fail). Silent on the wire (anti-enumeration); in DEV only, sets X-Fuz-Auth-Debug: daemon_token_discarded_browser_context.
  • Header present + Zod-invalid (malformed): soft-fail discard (pass through, not 401) — mirrors the bearer guard and the Rust spine's resolve.rs (None). Downstream a daemon-gated action returns credential_type_required; a public action proceeds anonymous.
  • Header present + invalid value (not the current/previous token): soft-fail discard (pass through, not 401) — same downstream behavior.
  • Header present + valid + keeper_account_id null (still pre-bootstrap after the lazy refresh): soft-fail discard (pass through, not 503) — mirrors the Rust spine's resolve.rs (None), so the request falls through to anonymous and a daemon-gated action returns credential_type_required downstream.
  • Header present + valid + ok: set `c.var.auth_account_id = state.keeper_account_id, CREDENTIAL_TYPE_KEY = 'daemon_token'` (overrides any existing session / bearer identity).

Acting-actor resolution + RequestContext construction are deferred to the dispatcher's authorization phase. Multi-actor keeper accounts surface actor_required from there if a daemon caller doesn't pass an explicit acting value.

state

the daemon token runtime state

deps

query dependencies (pool-level db for keeper-account resolution)

log

the logger instance

type Logger

returns

MiddlewareHandler

mutates

  • Hono — context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on a valid token

resolve_keeper_account_id
#

auth/daemon_token_middleware.ts view source

(deps: QueryDeps): Promise<string | null> import {resolve_keeper_account_id} from '@fuzdev/fuz_app/auth/daemon_token_middleware.js';

Resolve the keeper account ID by querying for the account with an active keeper role_grant.

There is exactly one keeper account (the bootstrap account). Runs once at server startup — the result is cached in DaemonTokenState.keeper_account_id. The acting actor is resolved per-request by the dispatcher's authorization phase (which runs resolve_acting_actor against this account id), so multi-actor keeper accounts surface actor_required if a daemon caller doesn't pass an explicit acting.

deps

query dependencies

returns

Promise<string | null>

the keeper account ID, or null if no keeper exists yet (pre-bootstrap)

Depends on
#

Imported by
#