@revealui/auth 0.4.9 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,10 @@
1
1
  /**
2
2
  * CSRF Double-Submit Token Helper
3
3
  *
4
- * Shared by the react auth hooks (`usePasskey`, `useMFA`, `useSignOut`) whose
5
- * POSTs target the RevealUI admin proxy's CSRF-gated endpoints.
4
+ * Re-exports the canonical browser cookie reader from
5
+ * `@revealui/core/admin/utils/csrf` (fleet-redundancy P2-C). Shared by the
6
+ * react auth hooks (`usePasskey`, `useMFA`, `useSignOut`) whose POSTs target
7
+ * the RevealUI admin proxy's CSRF-gated endpoints.
6
8
  */
7
- /**
8
- * Read the JS-readable `revealui-csrf` cookie. The RevealUI admin proxy
9
- * (`apps/admin/src/proxy.ts`) requires it as an `X-CSRF-Token` header on any
10
- * session-cookie-bearing unsafe request, and may re-issue the cookie on any
11
- * response — so call this immediately before each fetch rather than once per
12
- * flow. Returns undefined outside the browser or when the cookie is
13
- * absent/empty. Mirrors the cookie-read in `@revealui/core`'s admin APIClient
14
- * (`request()`) and `@revealui/ai`'s `useAgentStream`.
15
- */
16
- export declare function readCsrfToken(): string | undefined;
9
+ export { readCsrfToken } from '@revealui/core/admin/utils/csrf';
17
10
  //# sourceMappingURL=csrf.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../../src/react/csrf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAgBlD"}
1
+ {"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../../src/react/csrf.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC"}
@@ -1,36 +1,9 @@
1
1
  /**
2
2
  * CSRF Double-Submit Token Helper
3
3
  *
4
- * Shared by the react auth hooks (`usePasskey`, `useMFA`, `useSignOut`) whose
5
- * POSTs target the RevealUI admin proxy's CSRF-gated endpoints.
4
+ * Re-exports the canonical browser cookie reader from
5
+ * `@revealui/core/admin/utils/csrf` (fleet-redundancy P2-C). Shared by the
6
+ * react auth hooks (`usePasskey`, `useMFA`, `useSignOut`) whose POSTs target
7
+ * the RevealUI admin proxy's CSRF-gated endpoints.
6
8
  */
7
- /**
8
- * Read the JS-readable `revealui-csrf` cookie. The RevealUI admin proxy
9
- * (`apps/admin/src/proxy.ts`) requires it as an `X-CSRF-Token` header on any
10
- * session-cookie-bearing unsafe request, and may re-issue the cookie on any
11
- * response — so call this immediately before each fetch rather than once per
12
- * flow. Returns undefined outside the browser or when the cookie is
13
- * absent/empty. Mirrors the cookie-read in `@revealui/core`'s admin APIClient
14
- * (`request()`) and `@revealui/ai`'s `useAgentStream`.
15
- */
16
- export function readCsrfToken() {
17
- if (typeof document === 'undefined')
18
- return undefined;
19
- for (const part of document.cookie.split(';')) {
20
- const eqIdx = part.indexOf('=');
21
- if (eqIdx === -1)
22
- continue;
23
- if (part.slice(0, eqIdx).trim() === 'revealui-csrf') {
24
- const raw = part.slice(eqIdx + 1).trim();
25
- if (!raw)
26
- return undefined;
27
- try {
28
- return decodeURIComponent(raw);
29
- }
30
- catch {
31
- return raw;
32
- }
33
- }
34
- }
35
- return undefined;
36
- }
9
+ export { readCsrfToken } from '@revealui/core/admin/utils/csrf';
@@ -1 +1 @@
1
- {"version":3,"file":"audit-bridge.d.ts","sourceRoot":"","sources":["../../src/server/audit-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyCH;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASnF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjF;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS/E;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAShF;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAUf"}
1
+ {"version":3,"file":"audit-bridge.d.ts","sourceRoot":"","sources":["../../src/server/audit-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA2CH;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASnF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjF;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS/E;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAShF;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAUf"}
@@ -6,6 +6,7 @@
6
6
  * avoid circular dependency issues at module load time.
7
7
  */
8
8
  import { logger } from '@revealui/core/observability/logger';
9
+ import { evaluateSecurityAlert } from './security-alerts.js';
9
10
  /**
10
11
  * Lazily resolve the global audit system from @revealui/security/server.
11
12
  * Returns null if the module cannot be loaded (e.g. in test environments
@@ -28,17 +29,18 @@ async function getAudit() {
28
29
  */
29
30
  async function logAuditEvent(event) {
30
31
  const auditSystem = await getAudit();
31
- if (!auditSystem) {
32
- return;
33
- }
34
- try {
35
- await auditSystem.log(event);
36
- }
37
- catch (error) {
38
- logger.error('Failed to write audit event', error instanceof Error ? error : undefined, {
39
- type: event.type,
40
- });
32
+ if (auditSystem) {
33
+ try {
34
+ await auditSystem.log(event);
35
+ }
36
+ catch (error) {
37
+ logger.error('Failed to write audit event', error instanceof Error ? error : undefined, {
38
+ type: event.type,
39
+ });
40
+ }
41
41
  }
42
+ // Threshold alerts (failed logins, MFA disable, lockouts, …) — C11 WIRE
43
+ await evaluateSecurityAlert(event);
42
44
  }
43
45
  /**
44
46
  * Record a successful login to the audit trail.
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Audit storage boundary + signer composition — the ONE shared home (GAP-338).
3
+ *
4
+ * Moved here from `apps/server/src/lib/{audit-storage,audit-signer}.ts` so BOTH
5
+ * server processes (Hono api + Next.js admin) can swap the process-wide
6
+ * `@revealui/security` AuditSystem onto persistent storage. Before this move the
7
+ * boundary was stranded in apps/server, so every admin-process audit emit
8
+ * (including the GAP-334 login receipts wired through `audit-bridge.ts` in this
9
+ * package) landed in that process's default `InMemoryAuditStorage` and
10
+ * evaporated on restart. `@revealui/auth` is the home because it already sits
11
+ * above `@revealui/security`, `@revealui/db`, and `@revealui/core`, is consumed
12
+ * by both apps, and owns the auth→audit bridge whose emits this rescues.
13
+ *
14
+ * This is THE boundary between two audit models that intentionally differ
15
+ * (see docs/decisions/2026-07-12-audit-receipt-architecture.md §5):
16
+ *
17
+ * - `@revealui/security` emits `AuditEvent` with severity
18
+ * `low | medium | high | critical` (the request-middleware vocabulary).
19
+ * - `@revealui/db` / `@revealui/ai` own `audit_log`, whose CHECK constraint
20
+ * accepts only `info | warn | critical`. That model wins; the security
21
+ * vocabulary is mapped here, at the boundary, so every emitted event lands
22
+ * instead of being silently rejected by the constraint.
23
+ *
24
+ * Signing (GAP-355 Stage 3): the `DrizzleAuditStore` is built via
25
+ * `createAuditStore`, which injects the process-wide Ed25519 row signer. On a
26
+ * signing deployment every row lands with a `v1.ed25519` signature verifiable
27
+ * offline from the published public key; in dev/test (no key) rows stay honestly
28
+ * unsigned. `previous_signature` is never written (the hash chain is abandoned),
29
+ * which is what makes the two-writer topology (api + admin processes appending
30
+ * to one `audit_log`) safe: rows are independent, each signed at its own door
31
+ * with the same env-derived key. Ruled for GAP-338 — see the gap file.
32
+ */
33
+ import type { AuditRowSignerFn, DrizzleAuditStore as DrizzleAuditStoreType } from '@revealui/db';
34
+ import type { Database } from '@revealui/db/client';
35
+ import type { AuditEvent, AuditQuery, AuditSeverity, AuditStorage, AuditSystem } from '@revealui/security/server';
36
+ /**
37
+ * The process-wide audit-row signer (composed once from `process.env`), or
38
+ * `undefined` in unsigned mode. Cached so the mode is resolved and logged
39
+ * exactly once per process (GAP-355 Stage 3, spec D5/D6: key present → SIGNING;
40
+ * absent → UNSIGNED, legal only in dev/test — a production signing deployment
41
+ * refuses to boot without the key).
42
+ */
43
+ export declare function getAuditRowSigner(): AuditRowSignerFn | undefined;
44
+ /**
45
+ * Construct a `DrizzleAuditStore` wired to the process-wide signer. Every audit
46
+ * writer builds its store through this helper so a row written through the one
47
+ * door on a signing deployment always carries a signature.
48
+ */
49
+ export declare function createAuditStore(db: Database): DrizzleAuditStoreType;
50
+ /** Test-only reset of the cached signer (re-reads env on next `getAuditRowSigner`). */
51
+ export declare function __resetAuditSignerForTest(): void;
52
+ /** DB-side severity vocabulary — matches the `audit_log_severity_check` constraint. */
53
+ type DbSeverity = 'info' | 'warn' | 'critical';
54
+ export declare function mapSeverityToDb(severity: AuditSeverity): DbSeverity;
55
+ /**
56
+ * `AuditStorage` implementation backed by `DrizzleAuditStore`. Re-homes the
57
+ * row-storing responsibility onto `DrizzleAuditStore.append()` and keeps the
58
+ * security-model boundary mapping (severity + columns) here, so `@revealui/db`
59
+ * stays free of a dependency on the security package.
60
+ */
61
+ export declare class DrizzleBackedAuditStorage implements AuditStorage {
62
+ private readonly store;
63
+ constructor(db: Database);
64
+ write(event: AuditEvent): Promise<void>;
65
+ query(query: AuditQuery): Promise<AuditEvent[]>;
66
+ count(query: AuditQuery): Promise<number>;
67
+ }
68
+ /**
69
+ * Synchronous env-parity assertion for the audit write path — GAP-355 Stage 1
70
+ * closure, owner-ruled 2026-07-17.
71
+ *
72
+ * A serving process installs audit storage but cannot always run the async
73
+ * round-trip self-test (serverless has no clean "refuse to serve" for a
74
+ * boot-time DB round trip). This asserts the audit path's ENV preconditions
75
+ * SYNCHRONOUSLY at the install point, so a deploy whose audit-critical env has
76
+ * diverged (a required var missing or empty) FAILS THE DEPLOY rather than
77
+ * serving with an audit sink that silently drops every row.
78
+ *
79
+ * Scope, honestly: this catches env-var ABSENCE/emptiness synchronously — which
80
+ * `installAuditStorage()` itself does not, because `getClient()` is a lazy pool
81
+ * factory, so a missing connection URL would otherwise surface only on the
82
+ * first write, at request time. It does NOT, and on serverless cannot, catch
83
+ * migration-state divergence (the `audit_log` table missing or misshapen on the
84
+ * target DB); that needs the write-read round trip of `auditStorageSelfTest`.
85
+ */
86
+ export declare function assertAuditStorageEnv(env?: NodeJS.ProcessEnv): void;
87
+ /**
88
+ * Swap the process-wide `audit` system onto persistent Postgres storage.
89
+ * Synchronous and side-effect-free at call time: `getClient()` is a lazy pool
90
+ * factory (no connection opened here), so this is safe to run on a serverless
91
+ * cold-start path. WITHOUT this call, audit events fall into the default
92
+ * `InMemoryAuditStorage` and evaporate on restart — which is exactly what
93
+ * happened to every admin-process emit before GAP-338.
94
+ *
95
+ * Call `assertAuditStorageEnv()` before this at each install site so a
96
+ * diverged-env deploy fails loudly instead of installing a store that can
97
+ * never write.
98
+ */
99
+ export declare function installAuditStorage(): void;
100
+ /**
101
+ * Boot-time round-trip self-test. Writes a synthetic audit event through the
102
+ * REAL installed path (AuditSystem → boundary → DrizzleAuditStore → audit_log)
103
+ * and reads it back. Throws if the round trip fails, so a runtime that cannot
104
+ * record agent actions REFUSES TO SERVE rather than silently dropping them
105
+ * (fail-closed integrity — ADR §2a).
106
+ *
107
+ * Uses `severity: 'low'` deliberately: that value would be rejected by the DB
108
+ * CHECK constraint if the boundary mapping regressed, so this exercises the
109
+ * exact defect Stage 1 fixes. A healthy audit path lets startup proceed.
110
+ *
111
+ * Runs on the long-running worker (once per deploy) and the dev boot path,
112
+ * where an async boot chain already exists and `process.exit(1)` gives
113
+ * "refuse to serve" clean semantics. It is NOT run on serverless cold-start
114
+ * paths, which stay free of per-invocation DB round-trips (storage is still
115
+ * installed there synchronously by `installAuditStorage()`).
116
+ */
117
+ export declare function auditStorageSelfTest(auditSystem?: AuditSystem): Promise<void>;
118
+ export {};
119
+ //# sourceMappingURL=audit-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-storage.d.ts","sourceRoot":"","sources":["../../src/server/audit-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAEjG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACZ,MAAM,2BAA2B,CAAC;AAWnC;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,gBAAgB,GAAG,SAAS,CAgBhE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,qBAAqB,CAEpE;AAED,uFAAuF;AACvF,wBAAgB,yBAAyB,IAAI,IAAI,CAGhD;AAED,uFAAuF;AACvF,KAAK,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAe/C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,aAAa,GAAG,UAAU,CAEnE;AASD;;;;;GAKG;AACH,qBAAa,yBAA0B,YAAW,YAAY;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;gBAElC,EAAE,EAAE,QAAQ;IAOlB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BvC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IA4B/C,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;CAMhD;AAgCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CA0ChF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,CAAC,WAAW,GAAE,WAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB1F"}
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Audit storage boundary + signer composition — the ONE shared home (GAP-338).
3
+ *
4
+ * Moved here from `apps/server/src/lib/{audit-storage,audit-signer}.ts` so BOTH
5
+ * server processes (Hono api + Next.js admin) can swap the process-wide
6
+ * `@revealui/security` AuditSystem onto persistent storage. Before this move the
7
+ * boundary was stranded in apps/server, so every admin-process audit emit
8
+ * (including the GAP-334 login receipts wired through `audit-bridge.ts` in this
9
+ * package) landed in that process's default `InMemoryAuditStorage` and
10
+ * evaporated on restart. `@revealui/auth` is the home because it already sits
11
+ * above `@revealui/security`, `@revealui/db`, and `@revealui/core`, is consumed
12
+ * by both apps, and owns the auth→audit bridge whose emits this rescues.
13
+ *
14
+ * This is THE boundary between two audit models that intentionally differ
15
+ * (see docs/decisions/2026-07-12-audit-receipt-architecture.md §5):
16
+ *
17
+ * - `@revealui/security` emits `AuditEvent` with severity
18
+ * `low | medium | high | critical` (the request-middleware vocabulary).
19
+ * - `@revealui/db` / `@revealui/ai` own `audit_log`, whose CHECK constraint
20
+ * accepts only `info | warn | critical`. That model wins; the security
21
+ * vocabulary is mapped here, at the boundary, so every emitted event lands
22
+ * instead of being silently rejected by the constraint.
23
+ *
24
+ * Signing (GAP-355 Stage 3): the `DrizzleAuditStore` is built via
25
+ * `createAuditStore`, which injects the process-wide Ed25519 row signer. On a
26
+ * signing deployment every row lands with a `v1.ed25519` signature verifiable
27
+ * offline from the published public key; in dev/test (no key) rows stay honestly
28
+ * unsigned. `previous_signature` is never written (the hash chain is abandoned),
29
+ * which is what makes the two-writer topology (api + admin processes appending
30
+ * to one `audit_log`) safe: rows are independent, each signed at its own door
31
+ * with the same env-derived key. Ruled for GAP-338 — see the gap file.
32
+ */
33
+ import { randomUUID } from 'node:crypto';
34
+ import { logger } from '@revealui/core/observability/logger';
35
+ import { DrizzleAuditStore, getClient, hasDatabaseConnectionEnv } from '@revealui/db';
36
+ import { audit, classifyAuditWriteFailure, createAuditRowSignerFromEnv, recordAuditWriteResult, } from '@revealui/security/server';
37
+ let cachedSigner;
38
+ let resolved = false;
39
+ /**
40
+ * The process-wide audit-row signer (composed once from `process.env`), or
41
+ * `undefined` in unsigned mode. Cached so the mode is resolved and logged
42
+ * exactly once per process (GAP-355 Stage 3, spec D5/D6: key present → SIGNING;
43
+ * absent → UNSIGNED, legal only in dev/test — a production signing deployment
44
+ * refuses to boot without the key).
45
+ */
46
+ export function getAuditRowSigner() {
47
+ if (!resolved) {
48
+ const { signer, mode, kid } = createAuditRowSignerFromEnv(process.env);
49
+ if (mode === 'signed') {
50
+ logger.info(`AUDIT SIGNING: ENABLED (alg=ed25519, kid=${kid})`);
51
+ }
52
+ else {
53
+ logger.warn('AUDIT SIGNING: DISABLED — audit rows will be written UNSIGNED (no ' +
54
+ 'REVEALUI_AUDIT_SIGNING_KEY). Legal only in dev/test; a production signing ' +
55
+ 'deployment refuses to boot without the key.');
56
+ }
57
+ cachedSigner = signer;
58
+ resolved = true;
59
+ }
60
+ return cachedSigner;
61
+ }
62
+ /**
63
+ * Construct a `DrizzleAuditStore` wired to the process-wide signer. Every audit
64
+ * writer builds its store through this helper so a row written through the one
65
+ * door on a signing deployment always carries a signature.
66
+ */
67
+ export function createAuditStore(db) {
68
+ return new DrizzleAuditStore(db, getAuditRowSigner());
69
+ }
70
+ /** Test-only reset of the cached signer (re-reads env on next `getAuditRowSigner`). */
71
+ export function __resetAuditSignerForTest() {
72
+ cachedSigner = undefined;
73
+ resolved = false;
74
+ }
75
+ /**
76
+ * Severity mapping AT THE BOUNDARY (ADR §5). Total over the security
77
+ * vocabulary; every value lands in the DB CHECK vocabulary. Declared as a
78
+ * `Record<AuditSeverity, ...>` so the compiler rejects any future severity
79
+ * that is not mapped.
80
+ */
81
+ const SEVERITY_TO_DB = {
82
+ low: 'info',
83
+ medium: 'warn',
84
+ high: 'critical',
85
+ critical: 'critical',
86
+ };
87
+ export function mapSeverityToDb(severity) {
88
+ return SEVERITY_TO_DB[severity];
89
+ }
90
+ /** Reverse map for reconstructing legacy rows written without a full payload. */
91
+ const DB_TO_SEVERITY = {
92
+ info: 'low',
93
+ warn: 'medium',
94
+ critical: 'critical',
95
+ };
96
+ /**
97
+ * `AuditStorage` implementation backed by `DrizzleAuditStore`. Re-homes the
98
+ * row-storing responsibility onto `DrizzleAuditStore.append()` and keeps the
99
+ * security-model boundary mapping (severity + columns) here, so `@revealui/db`
100
+ * stays free of a dependency on the security package.
101
+ */
102
+ export class DrizzleBackedAuditStorage {
103
+ store;
104
+ constructor(db) {
105
+ // createAuditStore injects the process-wide Ed25519 signer (GAP-355 Stage 3),
106
+ // so rows land signed on a signing deployment and NULL-signature in unsigned
107
+ // (dev/test) mode. The severity/column boundary mapping stays here.
108
+ this.store = createAuditStore(db);
109
+ }
110
+ async write(event) {
111
+ try {
112
+ // The injected signer (createAuditStore) signs the row at the door on a
113
+ // signing deployment; a signer failure makes append THROW (fail-closed),
114
+ // caught below and routed through the write-result rails.
115
+ await this.store.append({
116
+ id: event.id,
117
+ timestamp: new Date(event.timestamp),
118
+ eventType: event.type,
119
+ severity: mapSeverityToDb(event.severity),
120
+ agentId: event.actor.id,
121
+ payload: event,
122
+ policyViolations: [],
123
+ });
124
+ recordAuditWriteResult({ ok: true, eventId: event.id, eventType: event.type });
125
+ }
126
+ catch (err) {
127
+ recordAuditWriteResult({
128
+ ok: false,
129
+ reason: classifyAuditWriteFailure(err),
130
+ eventId: event.id,
131
+ eventType: event.type,
132
+ });
133
+ throw err;
134
+ }
135
+ }
136
+ async query(query) {
137
+ const dbSeverities = query.severity && query.severity.length > 0
138
+ ? [...new Set(query.severity.map(mapSeverityToDb))]
139
+ : undefined;
140
+ const entries = await this.store.query({
141
+ agentId: query.actorId,
142
+ eventTypes: query.types ? [...query.types] : undefined,
143
+ severity: dbSeverities,
144
+ startTime: query.startDate,
145
+ endTime: query.endDate,
146
+ limit: query.limit ?? 100,
147
+ offset: query.offset ?? 0,
148
+ });
149
+ return entries
150
+ .map((entry) => reconstructEvent(entry))
151
+ .filter((event) => {
152
+ if (query.resourceType && event.resource?.type !== query.resourceType)
153
+ return false;
154
+ if (query.resourceId && event.resource?.id !== query.resourceId)
155
+ return false;
156
+ if (query.result && query.result.length > 0 && !query.result.includes(event.result)) {
157
+ return false;
158
+ }
159
+ return true;
160
+ });
161
+ }
162
+ async count(query) {
163
+ // Count ignores pagination (matches InMemoryAuditStorage semantics). Query
164
+ // with an unbounded limit and count the post-filtered results.
165
+ const events = await this.query({ ...query, limit: Number.MAX_SAFE_INTEGER, offset: 0 });
166
+ return events.length;
167
+ }
168
+ }
169
+ /**
170
+ * Reconstruct the full `AuditEvent` from a stored row. The complete event is
171
+ * persisted in the `payload` column, so it round-trips exactly. Rows written
172
+ * before this adapter (or by another writer without a full-event payload) fall
173
+ * back to a column-derived event.
174
+ */
175
+ function reconstructEvent(entry) {
176
+ const stored = entry.payload;
177
+ if (stored && typeof stored === 'object' && 'type' in stored) {
178
+ return stored;
179
+ }
180
+ return {
181
+ id: entry.id,
182
+ timestamp: entry.timestamp.toISOString(),
183
+ type: entry.eventType,
184
+ severity: DB_TO_SEVERITY[entry.severity] ?? 'low',
185
+ actor: { id: entry.agentId, type: 'system' },
186
+ action: entry.eventType,
187
+ result: 'success',
188
+ metadata: stored ?? undefined,
189
+ };
190
+ }
191
+ /**
192
+ * Synchronous env-parity assertion for the audit write path — GAP-355 Stage 1
193
+ * closure, owner-ruled 2026-07-17.
194
+ *
195
+ * A serving process installs audit storage but cannot always run the async
196
+ * round-trip self-test (serverless has no clean "refuse to serve" for a
197
+ * boot-time DB round trip). This asserts the audit path's ENV preconditions
198
+ * SYNCHRONOUSLY at the install point, so a deploy whose audit-critical env has
199
+ * diverged (a required var missing or empty) FAILS THE DEPLOY rather than
200
+ * serving with an audit sink that silently drops every row.
201
+ *
202
+ * Scope, honestly: this catches env-var ABSENCE/emptiness synchronously — which
203
+ * `installAuditStorage()` itself does not, because `getClient()` is a lazy pool
204
+ * factory, so a missing connection URL would otherwise surface only on the
205
+ * first write, at request time. It does NOT, and on serverless cannot, catch
206
+ * migration-state divergence (the `audit_log` table missing or misshapen on the
207
+ * target DB); that needs the write-read round trip of `auditStorageSelfTest`.
208
+ */
209
+ export function assertAuditStorageEnv(env = process.env) {
210
+ // GAP-417 item 5: the predicate is OWNED by @revealui/db and matches
211
+ // getClient()'s resolution exactly (config url, then POSTGRES_URL /
212
+ // DATABASE_URL). The previous local triple also accepted DATABASE_HOST,
213
+ // which getClient() never consults — so the assert passed, the install then
214
+ // threw, and production silently kept the in-memory sink (proven in the
215
+ // #2161 re-review). Never re-inline this check.
216
+ if (!hasDatabaseConnectionEnv(env)) {
217
+ throw new Error('AUDIT STORAGE ENV PARITY FAILED: no usable database connection is configured ' +
218
+ '(set POSTGRES_URL or DATABASE_URL, or provide @revealui/config database.url), ' +
219
+ 'so the audit write path cannot persist rows. Refusing to serve — an agent ' +
220
+ 'action that cannot be recorded must not execute (fail-closed integrity, ' +
221
+ 'docs/decisions/2026-07-12-audit-receipt-architecture.md §2a).');
222
+ }
223
+ // Signing key (GAP-355 Stage 3): a production signing deployment must have the
224
+ // Ed25519 key so rows land signed. Absence is a diverged-env deploy — fail it
225
+ // synchronously here (the serverless serving process's refuse-to-serve), the
226
+ // same rail the DB-connection check above uses. Full Ed25519 PKCS#8 parsing is
227
+ // done by validate-startup; this is the audit-owned presence parity check, so
228
+ // the contract cannot silently drift on one serving process. Dev/test
229
+ // (NODE_ENV !== 'production') runs unsigned by design and is exempt.
230
+ //
231
+ // GAP-417 items 1-2 (owner-countersigned 2026-07-25): SKIP_ENV_VALIDATION no
232
+ // longer exempts this check. The audit path has NO escape hatch — a
233
+ // production process that cannot sign must not boot, because unsigned rows
234
+ // are indistinguishable from tampering AND permanently stall anchor
235
+ // contiguity once the sweep filters them. SKIP still covers non-audit env
236
+ // validation elsewhere; it never buys an unsigned production audit log.
237
+ if (env.NODE_ENV === 'production') {
238
+ if (!env.REVEALUI_AUDIT_SIGNING_KEY) {
239
+ throw new Error('AUDIT STORAGE ENV PARITY FAILED: REVEALUI_AUDIT_SIGNING_KEY is not set on a ' +
240
+ 'production deployment, so the audit write path cannot sign rows. Refusing to ' +
241
+ 'serve — an unsigned row in the post-Stage-3 era is indistinguishable from ' +
242
+ 'tampering (docs/decisions/2026-07-12-audit-receipt-architecture.md §2a). ' +
243
+ 'SKIP_ENV_VALIDATION does not exempt the audit path (GAP-417).');
244
+ }
245
+ }
246
+ }
247
+ /**
248
+ * Swap the process-wide `audit` system onto persistent Postgres storage.
249
+ * Synchronous and side-effect-free at call time: `getClient()` is a lazy pool
250
+ * factory (no connection opened here), so this is safe to run on a serverless
251
+ * cold-start path. WITHOUT this call, audit events fall into the default
252
+ * `InMemoryAuditStorage` and evaporate on restart — which is exactly what
253
+ * happened to every admin-process emit before GAP-338.
254
+ *
255
+ * Call `assertAuditStorageEnv()` before this at each install site so a
256
+ * diverged-env deploy fails loudly instead of installing a store that can
257
+ * never write.
258
+ */
259
+ export function installAuditStorage() {
260
+ audit.setStorage(new DrizzleBackedAuditStorage(getClient()));
261
+ }
262
+ /**
263
+ * Boot-time round-trip self-test. Writes a synthetic audit event through the
264
+ * REAL installed path (AuditSystem → boundary → DrizzleAuditStore → audit_log)
265
+ * and reads it back. Throws if the round trip fails, so a runtime that cannot
266
+ * record agent actions REFUSES TO SERVE rather than silently dropping them
267
+ * (fail-closed integrity — ADR §2a).
268
+ *
269
+ * Uses `severity: 'low'` deliberately: that value would be rejected by the DB
270
+ * CHECK constraint if the boundary mapping regressed, so this exercises the
271
+ * exact defect Stage 1 fixes. A healthy audit path lets startup proceed.
272
+ *
273
+ * Runs on the long-running worker (once per deploy) and the dev boot path,
274
+ * where an async boot chain already exists and `process.exit(1)` gives
275
+ * "refuse to serve" clean semantics. It is NOT run on serverless cold-start
276
+ * paths, which stay free of per-invocation DB round-trips (storage is still
277
+ * installed there synchronously by `installAuditStorage()`).
278
+ */
279
+ export async function auditStorageSelfTest(auditSystem = audit) {
280
+ const marker = `__audit-self-test__:${randomUUID()}`;
281
+ const written = await auditSystem.log({
282
+ type: 'security.audit_self_test',
283
+ severity: 'low',
284
+ actor: { id: marker, type: 'system' },
285
+ action: 'audit-storage-self-test',
286
+ result: 'success',
287
+ metadata: { synthetic: true },
288
+ });
289
+ const found = await auditSystem.query({ actorId: marker, limit: 5 });
290
+ if (!found.some((event) => event.id === written.id)) {
291
+ throw new Error('AUDIT STORAGE SELF-TEST FAILED: wrote a synthetic audit event but could not read it ' +
292
+ 'back. Refusing to serve — a runtime that cannot record agent actions must not accept ' +
293
+ 'traffic (fail-closed integrity, docs/decisions/2026-07-12-audit-receipt-architecture.md §2a).');
294
+ }
295
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAQ,MAAM,aAAa,CAAC;AAUpE;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,YAAY,CAAC,CAyLvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBtD;AAED;;;;;;;;GAQG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,YAAY,CAAC,CAoOvB"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAQ,MAAM,aAAa,CAAC;AAUpE;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,YAAY,CAAC,CAyLvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBtD;AAED;;;;;;;;GAQG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,YAAY,CAAC,CAqOvB"}
@@ -398,9 +398,10 @@ export async function signUp(email, password, name, options) {
398
398
  // exists from signup. Best-effort: the Stripe billing webhook
399
399
  // (ensureHostedAccount) is idempotent on an active membership and backfills
400
400
  // this if it fails here, so a transient error never blocks signup. Skipped on
401
- // self-hosted (Forge) deployments — single-tenant, no per-user accounts (mode
402
- // detected by the license signing key, as in apps/server validate-startup).
403
- if (process.env.REVEALUI_LICENSE_PRIVATE_KEY) {
401
+ // self-hosted (Forge) deployments — single-tenant, no per-user accounts
402
+ // (GAP-260 P4-1: REVEALUI_DEPLOYMENT_MODE, fallback private-key presence).
403
+ const { isHostedDeployment } = await import('@revealui/core/deployment-mode');
404
+ if (isHostedDeployment(process.env)) {
404
405
  try {
405
406
  const accountId = crypto.randomUUID();
406
407
  const now = new Date();
@@ -14,7 +14,7 @@ export { configureMagicLink, createMagicLink, resetMagicLinkConfig, verifyMagicL
14
14
  export type { MFAConfig, MFADisableProof, MFASetupResult } from './mfa.js';
15
15
  export { configureMFA, disableMFA, initiateMFASetup, isMFAEnabled, regenerateBackupCodes, resetMFAConfig, verifyBackupCode, verifyMFACode, verifyMFASetup, } from './mfa.js';
16
16
  export type { MfaCheckResult, MfaEnforcementOptions, MfaErrorResponse, MfaRequest, MfaSession, MfaSessionUser, } from './mfa-enforcement.js';
17
- export { requireMfa } from './mfa-enforcement.js';
17
+ export { checkSessionMfa, requireMfa, toMfaSession } from './mfa-enforcement.js';
18
18
  export { buildAuthUrl, exchangeCode, fetchProviderUser, generateOAuthState, getLinkedProviders, linkOAuthAccount, type ProviderUser, type UpsertOAuthOptions, unlinkOAuthAccount, upsertOAuthUser, verifyOAuthState, } from './oauth.js';
19
19
  export type { PasskeyConfig } from './passkey.js';
20
20
  export { configurePasskey, countUserCredentials, deletePasskey, generateAuthenticationChallenge, generateRegistrationChallenge, listPasskeys, renamePasskey, resetPasskeyConfig, storePasskey, verifyAuthentication, verifyRegistration, } from './passkey.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,YAAY,EACZ,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,EACL,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,+BAA+B,EAC/B,6BAA6B,EAC7B,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,4BAA4B,EAC5B,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gCAAgC,EAChC,wBAAwB,GACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,yBAAyB,EACzB,aAAa,EACb,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,YAAY,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,EACV,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAO3B,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,YAAY,EACZ,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,EACL,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,+BAA+B,EAC/B,6BAA6B,EAC7B,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,4BAA4B,EAC5B,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gCAAgC,EAChC,wBAAwB,GACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,yBAAyB,EACzB,aAAa,EACb,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,YAAY,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,EACV,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAC"}
@@ -6,12 +6,18 @@
6
6
  */
7
7
  // Audit bridge
8
8
  export { auditAccountLocked, auditLoginFailure, auditLoginSuccess, auditMfaDisabled, auditMfaEnabled, auditPasswordChange, auditPasswordReset, auditSessionRevoked, } from './audit-bridge.js';
9
+ // NOTE (GAP-338): the audit storage boundary lives at the DEDICATED subpath
10
+ // `@revealui/auth/audit-storage`, deliberately NOT re-exported from this
11
+ // barrel. `@revealui/auth/server` is the fleet's most-mocked module (route
12
+ // tests bare-mock it for getSession), and a bare vi.mock of this barrel must
13
+ // never swallow the audit write path (it broke the GAP-352 + mcp-endpoint
14
+ // integration suites when the boundary briefly lived here).
9
15
  export { isSignupAllowed, signIn, signUp } from './auth.js';
10
16
  export { clearFailedAttempts, configureBruteForce, getFailedAttemptCount, isAccountLocked, recordFailedAttempt, resetBruteForceConfig, } from './brute-force.js';
11
17
  export { AuthError, AuthenticationError, DatabaseError, OAuthAccountConflictError, SessionError, TokenError, } from './errors.js';
12
18
  export { configureMagicLink, createMagicLink, resetMagicLinkConfig, verifyMagicLink, } from './magic-link.js';
13
19
  export { configureMFA, disableMFA, initiateMFASetup, isMFAEnabled, regenerateBackupCodes, resetMFAConfig, verifyBackupCode, verifyMFACode, verifyMFASetup, } from './mfa.js';
14
- export { requireMfa } from './mfa-enforcement.js';
20
+ export { checkSessionMfa, requireMfa, toMfaSession } from './mfa-enforcement.js';
15
21
  export { buildAuthUrl, exchangeCode, fetchProviderUser, generateOAuthState, getLinkedProviders, linkOAuthAccount, unlinkOAuthAccount, upsertOAuthUser, verifyOAuthState, } from './oauth.js';
16
22
  export { configurePasskey, countUserCredentials, deletePasskey, generateAuthenticationChallenge, generateRegistrationChallenge, listPasskeys, renamePasskey, resetPasskeyConfig, storePasskey, verifyAuthentication, verifyRegistration, } from './passkey.js';
17
23
  export { changePassword, generatePasswordResetToken, invalidatePasswordResetToken, resetPasswordWithToken, validatePasswordResetToken, } from './password-reset.js';
@@ -73,4 +73,33 @@ export interface MfaCheckResult {
73
73
  * ```
74
74
  */
75
75
  export declare function requireMfa(options?: MfaEnforcementOptions): (request: MfaRequest) => MfaCheckResult;
76
+ /**
77
+ * Build MFA session shape from getSession / rotateSession results.
78
+ * `mfaVerified` comes from session.metadata set after MFA login (verify/backup).
79
+ */
80
+ export declare function toMfaSession(sessionData: {
81
+ user: {
82
+ id: string;
83
+ role: string;
84
+ mfaEnabled?: boolean | null;
85
+ };
86
+ session: {
87
+ metadata?: Record<string, unknown> | null;
88
+ };
89
+ }): MfaSession;
90
+ /**
91
+ * One-shot MFA check for a SessionData-like object (Next.js / Hono handlers).
92
+ */
93
+ export declare function checkSessionMfa(sessionData: {
94
+ user: {
95
+ id: string;
96
+ role: string;
97
+ mfaEnabled?: boolean | null;
98
+ };
99
+ session: {
100
+ metadata?: Record<string, unknown> | null;
101
+ };
102
+ }, options?: MfaEnforcementOptions & {
103
+ operation?: string;
104
+ }): MfaCheckResult;
76
105
  //# sourceMappingURL=mfa-enforcement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mfa-enforcement.d.ts","sourceRoot":"","sources":["../../src/server/mfa-enforcement.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,eAAe;IACf,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,gDAAgD;AAChD,MAAM,WAAW,UAAU;IACzB,mCAAmC;IACnC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,GAAG,qBAAqB,CAAC;CAC9C;AAED,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAQD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,UAAU,CACxB,OAAO,GAAE,qBAA0B,GAClC,CAAC,OAAO,EAAE,UAAU,KAAK,cAAc,CAgDzC"}
1
+ {"version":3,"file":"mfa-enforcement.d.ts","sourceRoot":"","sources":["../../src/server/mfa-enforcement.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,eAAe;IACf,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,gDAAgD;AAChD,MAAM,WAAW,UAAU;IACzB,mCAAmC;IACnC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,GAAG,qBAAqB,CAAC;CAC9C;AAED,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AASD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,UAAU,CACxB,OAAO,GAAE,qBAA0B,GAClC,CAAC,OAAO,EAAE,UAAU,KAAK,cAAc,CAgDzC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE;IACxC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;IAChE,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;CACxD,GAAG,UAAU,CAUb;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE;IACX,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;IAChE,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;CACxD,EACD,OAAO,CAAC,EAAE,qBAAqB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACvD,cAAc,CAMhB"}
@@ -7,7 +7,8 @@
7
7
  // =============================================================================
8
8
  // Middleware
9
9
  // =============================================================================
10
- const DEFAULT_ROLES = ['admin'];
10
+ /** Admin-level DB roles that require MFA for sensitive operations (C11). */
11
+ const DEFAULT_ROLES = ['admin', 'owner'];
11
12
  /**
12
13
  * Create an MFA enforcement checker.
13
14
  *
@@ -74,3 +75,28 @@ export function requireMfa(options = {}) {
74
75
  return { allowed: true };
75
76
  };
76
77
  }
78
+ /**
79
+ * Build MFA session shape from getSession / rotateSession results.
80
+ * `mfaVerified` comes from session.metadata set after MFA login (verify/backup).
81
+ */
82
+ export function toMfaSession(sessionData) {
83
+ const meta = sessionData.session.metadata ?? null;
84
+ return {
85
+ user: {
86
+ id: sessionData.user.id,
87
+ role: sessionData.user.role,
88
+ mfaEnabled: sessionData.user.mfaEnabled === true,
89
+ mfaVerified: meta?.mfaVerified === true,
90
+ },
91
+ };
92
+ }
93
+ /**
94
+ * One-shot MFA check for a SessionData-like object (Next.js / Hono handlers).
95
+ */
96
+ export function checkSessionMfa(sessionData, options) {
97
+ const check = requireMfa(options);
98
+ return check({
99
+ session: toMfaSession(sessionData),
100
+ operation: options?.operation,
101
+ });
102
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SecurityAlertService bridge (fleet-redundancy C11).
3
+ *
4
+ * Lazy singleton: evaluates auth/security audit events against DEFAULT_THRESHOLDS
5
+ * and logs via LogAlertHandler. Best-effort — never throws into the login path.
6
+ */
7
+ import type { AuditEvent } from '@revealui/security/server';
8
+ type AuditEventInput = Omit<AuditEvent, 'id' | 'timestamp'>;
9
+ /** Reset singleton (tests only). */
10
+ export declare function resetSecurityAlertService(): void;
11
+ /**
12
+ * Feed an audit-shaped event into SecurityAlertService.evaluateEvent.
13
+ * Never throws; failures are logged and swallowed.
14
+ */
15
+ export declare function evaluateSecurityAlert(event: AuditEventInput): Promise<void>;
16
+ export {};
17
+ //# sourceMappingURL=security-alerts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-alerts.d.ts","sourceRoot":"","sources":["../../src/server/security-alerts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,KAAK,eAAe,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,WAAW,CAAC,CAAC;AA6B5D,oCAAoC;AACpC,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAejF"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * SecurityAlertService bridge (fleet-redundancy C11).
3
+ *
4
+ * Lazy singleton: evaluates auth/security audit events against DEFAULT_THRESHOLDS
5
+ * and logs via LogAlertHandler. Best-effort — never throws into the login path.
6
+ */
7
+ import { logger } from '@revealui/core/observability/logger';
8
+ let service;
9
+ /**
10
+ * Lazily construct the process-wide SecurityAlertService, or null if the
11
+ * security package cannot be loaded (e.g. partial test mocks).
12
+ */
13
+ async function getAlertService() {
14
+ if (service !== undefined) {
15
+ return service;
16
+ }
17
+ try {
18
+ const { SecurityAlertService: AlertService, DEFAULT_THRESHOLDS, LogAlertHandler, } = await import('@revealui/security');
19
+ service = new AlertService({
20
+ thresholds: DEFAULT_THRESHOLDS,
21
+ handlers: [new LogAlertHandler()],
22
+ });
23
+ return service;
24
+ }
25
+ catch {
26
+ service = null;
27
+ return null;
28
+ }
29
+ }
30
+ /** Reset singleton (tests only). */
31
+ export function resetSecurityAlertService() {
32
+ service = undefined;
33
+ }
34
+ /**
35
+ * Feed an audit-shaped event into SecurityAlertService.evaluateEvent.
36
+ * Never throws; failures are logged and swallowed.
37
+ */
38
+ export async function evaluateSecurityAlert(event) {
39
+ try {
40
+ const alerts = await getAlertService();
41
+ if (!alerts)
42
+ return;
43
+ const full = {
44
+ id: crypto.randomUUID(),
45
+ timestamp: new Date().toISOString(),
46
+ ...event,
47
+ };
48
+ await alerts.evaluateEvent(full);
49
+ }
50
+ catch (error) {
51
+ logger.error('Failed to evaluate security alert', error instanceof Error ? error : undefined, {
52
+ type: event.type,
53
+ });
54
+ }
55
+ }
@@ -86,6 +86,8 @@ export declare function rotateSession(userId: string, options?: {
86
86
  persistent?: boolean;
87
87
  userAgent?: string;
88
88
  ipAddress?: string;
89
+ /** Session metadata (e.g. `{ mfaVerified: true }` after MFA login). */
90
+ metadata?: Record<string, unknown>;
89
91
  }): Promise<{
90
92
  token: string;
91
93
  session: Session;
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAQjD,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iFAAiF;IACjF,SAAS,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;CACzB;AAaD,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAEtF;AAED,qCAAqC;AACrC,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAMD,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CA0B3F;AAMD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAI1E;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,OAAO,EAChB,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAgI7B;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,GACA,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAmE9C;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR,+FAA+F;IAC/F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAoC9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BtE;AAED;;;;GAIG;AACH;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzE"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAQjD,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iFAAiF;IACjF,SAAS,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;CACzB;AAaD,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAEtF;AAED,qCAAqC;AACrC,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAMD,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CA0B3F;AAMD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAI1E;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,OAAO,EAChB,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAgI7B;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,GACA,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAmE9C;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR,+FAA+F;IAC/F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,GACA,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAqC9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BtE;AAED;;;;GAIG;AACH;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzE"}
@@ -312,6 +312,7 @@ export async function rotateSession(userId, options) {
312
312
  persistent: options?.persistent,
313
313
  userAgent: options?.userAgent,
314
314
  ipAddress: options?.ipAddress,
315
+ metadata: options?.metadata,
315
316
  });
316
317
  }
317
318
  catch (err) {
@@ -18,7 +18,7 @@ export declare function getTestDatabaseUrl(): string;
18
18
  /**
19
19
  * Creates a test database client
20
20
  */
21
- export declare function createTestDatabaseClient(): import("@revealui/db/client").Database;
21
+ export declare function createTestDatabaseClient(): import("@revealui/db").DatabaseClient;
22
22
  /**
23
23
  * Cleans up test data from database
24
24
  */
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAU3C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,2CAMvC;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBtE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B7E;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CASxE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAStF;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,aAAa,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,EAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAClC,OAAO,CAAC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAI3C"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAU3C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,0CAMvC;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBtE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B7E;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CASxE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAStF;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,aAAa,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,EAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAClC,OAAO,CAAC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAI3C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revealui/auth",
3
- "version": "0.4.9",
3
+ "version": "0.5.0",
4
4
  "description": "Database-backed session auth for Hono and Next.js — bcrypt, OAuth, brute-force protection, rate limiting, password reset. Ships with RevealUI.",
5
5
  "keywords": [
6
6
  "auth",
@@ -15,10 +15,10 @@
15
15
  "drizzle-orm": "^0.45.2",
16
16
  "zod": "^4.4.3",
17
17
  "@revealui/config": "0.6.0",
18
- "@revealui/contracts": "0.8.0",
19
- "@revealui/core": "0.12.0",
20
- "@revealui/db": "0.9.0",
21
- "@revealui/security": "0.5.0"
18
+ "@revealui/contracts": "0.8.1",
19
+ "@revealui/core": "0.12.2",
20
+ "@revealui/db": "0.10.0",
21
+ "@revealui/security": "0.6.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@simplewebauthn/browser": "^13.3.0",
@@ -38,15 +38,23 @@
38
38
  "exports": {
39
39
  ".": {
40
40
  "types": "./dist/index.d.ts",
41
- "import": "./dist/index.js"
41
+ "import": "./dist/index.js",
42
+ "default": "./dist/index.js"
42
43
  },
43
44
  "./server": {
44
45
  "types": "./dist/server/index.d.ts",
45
- "import": "./dist/server/index.js"
46
+ "import": "./dist/server/index.js",
47
+ "default": "./dist/server/index.js"
48
+ },
49
+ "./audit-storage": {
50
+ "types": "./dist/server/audit-storage.d.ts",
51
+ "import": "./dist/server/audit-storage.js",
52
+ "default": "./dist/server/audit-storage.js"
46
53
  },
47
54
  "./react": {
48
55
  "types": "./dist/react/index.d.ts",
49
- "import": "./dist/react/index.js"
56
+ "import": "./dist/react/index.js",
57
+ "default": "./dist/react/index.js"
50
58
  }
51
59
  },
52
60
  "files": [