@revealui/auth 0.4.10 → 0.5.1

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.
Files changed (42) hide show
  1. package/dist/react/useSignIn.d.ts +1 -1
  2. package/dist/react/useSignIn.d.ts.map +1 -1
  3. package/dist/react/useSignIn.js +7 -3
  4. package/dist/server/audit-storage.d.ts +122 -0
  5. package/dist/server/audit-storage.d.ts.map +1 -0
  6. package/dist/server/audit-storage.js +298 -0
  7. package/dist/server/auth.d.ts.map +1 -1
  8. package/dist/server/auth.js +68 -10
  9. package/dist/server/index.d.ts +2 -0
  10. package/dist/server/index.d.ts.map +1 -1
  11. package/dist/server/index.js +9 -0
  12. package/dist/server/platform-roles.d.ts +42 -0
  13. package/dist/server/platform-roles.d.ts.map +1 -0
  14. package/dist/server/platform-roles.js +65 -0
  15. package/dist/server/session.js +1 -1
  16. package/dist/server/sso/__tests__/helpers/mock-oidc-idp.d.ts +31 -0
  17. package/dist/server/sso/__tests__/helpers/mock-oidc-idp.d.ts.map +1 -0
  18. package/dist/server/sso/__tests__/helpers/mock-oidc-idp.js +115 -0
  19. package/dist/server/sso/__tests__/helpers/mock-saml-idp.d.ts +28 -0
  20. package/dist/server/sso/__tests__/helpers/mock-saml-idp.d.ts.map +1 -0
  21. package/dist/server/sso/__tests__/helpers/mock-saml-idp.js +150 -0
  22. package/dist/server/sso/index.d.ts +13 -0
  23. package/dist/server/sso/index.d.ts.map +1 -0
  24. package/dist/server/sso/index.js +12 -0
  25. package/dist/server/sso/jit.d.ts +39 -0
  26. package/dist/server/sso/jit.d.ts.map +1 -0
  27. package/dist/server/sso/jit.js +141 -0
  28. package/dist/server/sso/oidc.d.ts +137 -0
  29. package/dist/server/sso/oidc.d.ts.map +1 -0
  30. package/dist/server/sso/oidc.js +345 -0
  31. package/dist/server/sso/roles.d.ts +48 -0
  32. package/dist/server/sso/roles.d.ts.map +1 -0
  33. package/dist/server/sso/roles.js +109 -0
  34. package/dist/server/sso/saml.d.ts +99 -0
  35. package/dist/server/sso/saml.d.ts.map +1 -0
  36. package/dist/server/sso/saml.js +392 -0
  37. package/dist/server/sso/state.d.ts +46 -0
  38. package/dist/server/sso/state.d.ts.map +1 -0
  39. package/dist/server/sso/state.js +101 -0
  40. package/dist/utils/database.d.ts +1 -1
  41. package/dist/utils/database.d.ts.map +1 -1
  42. package/package.json +15 -6
@@ -19,7 +19,7 @@ export interface UseSignInResult {
19
19
  } | {
20
20
  success: false;
21
21
  requiresMfa: true;
22
- mfaUserId: string;
22
+ mfaUserId?: string;
23
23
  }>;
24
24
  isLoading: boolean;
25
25
  error: Error | null;
@@ -1 +1 @@
1
- {"version":3,"file":"useSignIn.d.ts","sourceRoot":"","sources":["../../src/react/useSignIn.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAuCxC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,CACN,KAAK,EAAE,WAAW,KACf,OAAO,CACR;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,GAC7B;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAChD;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,WAAW,EAAE,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAC3D,CAAC;IACF,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,IAAI,eAAe,CA2E3C"}
1
+ {"version":3,"file":"useSignIn.d.ts","sourceRoot":"","sources":["../../src/react/useSignIn.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAuCxC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CACnC;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,GAC7B;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAGhD;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,WAAW,EAAE,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAC5D,CAAC;IACF,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,IAAI,eAAe,CA+E3C"}
@@ -94,13 +94,17 @@ export function useSignIn() {
94
94
  code: errorData.code,
95
95
  };
96
96
  }
97
- // Check for MFA challenge before parsing as full success
97
+ // MFA challenge is HTTP 200 with { requiresMfa: true } and no `user`.
98
+ // The server may omit mfaUserId from the body (identity lives in the
99
+ // httpOnly mfa-pending cookie). Requiring mfaUserId here forced the
100
+ // success schema parse, which threw "expected object, received undefined"
101
+ // on `user` and locked MFA-enabled accounts out of the login UI.
98
102
  const jsonObj = json;
99
- if (jsonObj.requiresMfa === true && typeof jsonObj.mfaUserId === 'string') {
103
+ if (jsonObj.requiresMfa === true) {
100
104
  return {
101
105
  success: false,
102
106
  requiresMfa: true,
103
- mfaUserId: jsonObj.mfaUserId,
107
+ ...(typeof jsonObj.mfaUserId === 'string' ? { mfaUserId: jsonObj.mfaUserId } : {}),
104
108
  };
105
109
  }
106
110
  const successData = SignInSuccessResponseSchema.parse(json);
@@ -0,0 +1,122 @@
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, DrizzleAuditStoreOptions, 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
+ * @param options Optional store options (GAP-417 residual): pass `targetEnv`
50
+ * from CLI writers that resolve a deployment target independent of NODE_ENV.
51
+ */
52
+ export declare function createAuditStore(db: Database, options?: DrizzleAuditStoreOptions): DrizzleAuditStoreType;
53
+ /** Test-only reset of the cached signer (re-reads env on next `getAuditRowSigner`). */
54
+ export declare function __resetAuditSignerForTest(): void;
55
+ /** DB-side severity vocabulary — matches the `audit_log_severity_check` constraint. */
56
+ type DbSeverity = 'info' | 'warn' | 'critical';
57
+ export declare function mapSeverityToDb(severity: AuditSeverity): DbSeverity;
58
+ /**
59
+ * `AuditStorage` implementation backed by `DrizzleAuditStore`. Re-homes the
60
+ * row-storing responsibility onto `DrizzleAuditStore.append()` and keeps the
61
+ * security-model boundary mapping (severity + columns) here, so `@revealui/db`
62
+ * stays free of a dependency on the security package.
63
+ */
64
+ export declare class DrizzleBackedAuditStorage implements AuditStorage {
65
+ private readonly store;
66
+ constructor(db: Database);
67
+ write(event: AuditEvent): Promise<void>;
68
+ query(query: AuditQuery): Promise<AuditEvent[]>;
69
+ count(query: AuditQuery): Promise<number>;
70
+ }
71
+ /**
72
+ * Synchronous env-parity assertion for the audit write path — GAP-355 Stage 1
73
+ * closure, owner-ruled 2026-07-17.
74
+ *
75
+ * A serving process installs audit storage but cannot always run the async
76
+ * round-trip self-test (serverless has no clean "refuse to serve" for a
77
+ * boot-time DB round trip). This asserts the audit path's ENV preconditions
78
+ * SYNCHRONOUSLY at the install point, so a deploy whose audit-critical env has
79
+ * diverged (a required var missing or empty) FAILS THE DEPLOY rather than
80
+ * serving with an audit sink that silently drops every row.
81
+ *
82
+ * Scope, honestly: this catches env-var ABSENCE/emptiness synchronously — which
83
+ * `installAuditStorage()` itself does not, because `getClient()` is a lazy pool
84
+ * factory, so a missing connection URL would otherwise surface only on the
85
+ * first write, at request time. It does NOT, and on serverless cannot, catch
86
+ * migration-state divergence (the `audit_log` table missing or misshapen on the
87
+ * target DB); that needs the write-read round trip of `auditStorageSelfTest`.
88
+ */
89
+ export declare function assertAuditStorageEnv(env?: NodeJS.ProcessEnv): void;
90
+ /**
91
+ * Swap the process-wide `audit` system onto persistent Postgres storage.
92
+ * Synchronous and side-effect-free at call time: `getClient()` is a lazy pool
93
+ * factory (no connection opened here), so this is safe to run on a serverless
94
+ * cold-start path. WITHOUT this call, audit events fall into the default
95
+ * `InMemoryAuditStorage` and evaporate on restart — which is exactly what
96
+ * happened to every admin-process emit before GAP-338.
97
+ *
98
+ * Call `assertAuditStorageEnv()` before this at each install site so a
99
+ * diverged-env deploy fails loudly instead of installing a store that can
100
+ * never write.
101
+ */
102
+ export declare function installAuditStorage(): void;
103
+ /**
104
+ * Boot-time round-trip self-test. Writes a synthetic audit event through the
105
+ * REAL installed path (AuditSystem → boundary → DrizzleAuditStore → audit_log)
106
+ * and reads it back. Throws if the round trip fails, so a runtime that cannot
107
+ * record agent actions REFUSES TO SERVE rather than silently dropping them
108
+ * (fail-closed integrity — ADR §2a).
109
+ *
110
+ * Uses `severity: 'low'` deliberately: that value would be rejected by the DB
111
+ * CHECK constraint if the boundary mapping regressed, so this exercises the
112
+ * exact defect Stage 1 fixes. A healthy audit path lets startup proceed.
113
+ *
114
+ * Runs on the long-running worker (once per deploy) and the dev boot path,
115
+ * where an async boot chain already exists and `process.exit(1)` gives
116
+ * "refuse to serve" clean semantics. It is NOT run on serverless cold-start
117
+ * paths, which stay free of per-invocation DB round-trips (storage is still
118
+ * installed there synchronously by `installAuditStorage()`).
119
+ */
120
+ export declare function auditStorageSelfTest(auditSystem?: AuditSystem): Promise<void>;
121
+ export {};
122
+ //# 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,EACV,gBAAgB,EAChB,wBAAwB,EACxB,iBAAiB,IAAI,qBAAqB,EAC3C,MAAM,cAAc,CAAC;AAEtB,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;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,EACZ,OAAO,CAAC,EAAE,wBAAwB,GACjC,qBAAqB,CAEvB;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,298 @@
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
+ * @param options Optional store options (GAP-417 residual): pass `targetEnv`
68
+ * from CLI writers that resolve a deployment target independent of NODE_ENV.
69
+ */
70
+ export function createAuditStore(db, options) {
71
+ return new DrizzleAuditStore(db, getAuditRowSigner(), options);
72
+ }
73
+ /** Test-only reset of the cached signer (re-reads env on next `getAuditRowSigner`). */
74
+ export function __resetAuditSignerForTest() {
75
+ cachedSigner = undefined;
76
+ resolved = false;
77
+ }
78
+ /**
79
+ * Severity mapping AT THE BOUNDARY (ADR §5). Total over the security
80
+ * vocabulary; every value lands in the DB CHECK vocabulary. Declared as a
81
+ * `Record<AuditSeverity, ...>` so the compiler rejects any future severity
82
+ * that is not mapped.
83
+ */
84
+ const SEVERITY_TO_DB = {
85
+ low: 'info',
86
+ medium: 'warn',
87
+ high: 'critical',
88
+ critical: 'critical',
89
+ };
90
+ export function mapSeverityToDb(severity) {
91
+ return SEVERITY_TO_DB[severity];
92
+ }
93
+ /** Reverse map for reconstructing legacy rows written without a full payload. */
94
+ const DB_TO_SEVERITY = {
95
+ info: 'low',
96
+ warn: 'medium',
97
+ critical: 'critical',
98
+ };
99
+ /**
100
+ * `AuditStorage` implementation backed by `DrizzleAuditStore`. Re-homes the
101
+ * row-storing responsibility onto `DrizzleAuditStore.append()` and keeps the
102
+ * security-model boundary mapping (severity + columns) here, so `@revealui/db`
103
+ * stays free of a dependency on the security package.
104
+ */
105
+ export class DrizzleBackedAuditStorage {
106
+ store;
107
+ constructor(db) {
108
+ // createAuditStore injects the process-wide Ed25519 signer (GAP-355 Stage 3),
109
+ // so rows land signed on a signing deployment and NULL-signature in unsigned
110
+ // (dev/test) mode. The severity/column boundary mapping stays here.
111
+ this.store = createAuditStore(db);
112
+ }
113
+ async write(event) {
114
+ try {
115
+ // The injected signer (createAuditStore) signs the row at the door on a
116
+ // signing deployment; a signer failure makes append THROW (fail-closed),
117
+ // caught below and routed through the write-result rails.
118
+ await this.store.append({
119
+ id: event.id,
120
+ timestamp: new Date(event.timestamp),
121
+ eventType: event.type,
122
+ severity: mapSeverityToDb(event.severity),
123
+ agentId: event.actor.id,
124
+ payload: event,
125
+ policyViolations: [],
126
+ });
127
+ recordAuditWriteResult({ ok: true, eventId: event.id, eventType: event.type });
128
+ }
129
+ catch (err) {
130
+ recordAuditWriteResult({
131
+ ok: false,
132
+ reason: classifyAuditWriteFailure(err),
133
+ eventId: event.id,
134
+ eventType: event.type,
135
+ });
136
+ throw err;
137
+ }
138
+ }
139
+ async query(query) {
140
+ const dbSeverities = query.severity && query.severity.length > 0
141
+ ? [...new Set(query.severity.map(mapSeverityToDb))]
142
+ : undefined;
143
+ const entries = await this.store.query({
144
+ agentId: query.actorId,
145
+ eventTypes: query.types ? [...query.types] : undefined,
146
+ severity: dbSeverities,
147
+ startTime: query.startDate,
148
+ endTime: query.endDate,
149
+ limit: query.limit ?? 100,
150
+ offset: query.offset ?? 0,
151
+ });
152
+ return entries
153
+ .map((entry) => reconstructEvent(entry))
154
+ .filter((event) => {
155
+ if (query.resourceType && event.resource?.type !== query.resourceType)
156
+ return false;
157
+ if (query.resourceId && event.resource?.id !== query.resourceId)
158
+ return false;
159
+ if (query.result && query.result.length > 0 && !query.result.includes(event.result)) {
160
+ return false;
161
+ }
162
+ return true;
163
+ });
164
+ }
165
+ async count(query) {
166
+ // Count ignores pagination (matches InMemoryAuditStorage semantics). Query
167
+ // with an unbounded limit and count the post-filtered results.
168
+ const events = await this.query({ ...query, limit: Number.MAX_SAFE_INTEGER, offset: 0 });
169
+ return events.length;
170
+ }
171
+ }
172
+ /**
173
+ * Reconstruct the full `AuditEvent` from a stored row. The complete event is
174
+ * persisted in the `payload` column, so it round-trips exactly. Rows written
175
+ * before this adapter (or by another writer without a full-event payload) fall
176
+ * back to a column-derived event.
177
+ */
178
+ function reconstructEvent(entry) {
179
+ const stored = entry.payload;
180
+ if (stored && typeof stored === 'object' && 'type' in stored) {
181
+ return stored;
182
+ }
183
+ return {
184
+ id: entry.id,
185
+ timestamp: entry.timestamp.toISOString(),
186
+ type: entry.eventType,
187
+ severity: DB_TO_SEVERITY[entry.severity] ?? 'low',
188
+ actor: { id: entry.agentId, type: 'system' },
189
+ action: entry.eventType,
190
+ result: 'success',
191
+ metadata: stored ?? undefined,
192
+ };
193
+ }
194
+ /**
195
+ * Synchronous env-parity assertion for the audit write path — GAP-355 Stage 1
196
+ * closure, owner-ruled 2026-07-17.
197
+ *
198
+ * A serving process installs audit storage but cannot always run the async
199
+ * round-trip self-test (serverless has no clean "refuse to serve" for a
200
+ * boot-time DB round trip). This asserts the audit path's ENV preconditions
201
+ * SYNCHRONOUSLY at the install point, so a deploy whose audit-critical env has
202
+ * diverged (a required var missing or empty) FAILS THE DEPLOY rather than
203
+ * serving with an audit sink that silently drops every row.
204
+ *
205
+ * Scope, honestly: this catches env-var ABSENCE/emptiness synchronously — which
206
+ * `installAuditStorage()` itself does not, because `getClient()` is a lazy pool
207
+ * factory, so a missing connection URL would otherwise surface only on the
208
+ * first write, at request time. It does NOT, and on serverless cannot, catch
209
+ * migration-state divergence (the `audit_log` table missing or misshapen on the
210
+ * target DB); that needs the write-read round trip of `auditStorageSelfTest`.
211
+ */
212
+ export function assertAuditStorageEnv(env = process.env) {
213
+ // GAP-417 item 5: the predicate is OWNED by @revealui/db and matches
214
+ // getClient()'s resolution exactly (config url, then POSTGRES_URL /
215
+ // DATABASE_URL). The previous local triple also accepted DATABASE_HOST,
216
+ // which getClient() never consults — so the assert passed, the install then
217
+ // threw, and production silently kept the in-memory sink (proven in the
218
+ // #2161 re-review). Never re-inline this check.
219
+ if (!hasDatabaseConnectionEnv(env)) {
220
+ throw new Error('AUDIT STORAGE ENV PARITY FAILED: no usable database connection is configured ' +
221
+ '(set POSTGRES_URL or DATABASE_URL, or provide @revealui/config database.url), ' +
222
+ 'so the audit write path cannot persist rows. Refusing to serve — an agent ' +
223
+ 'action that cannot be recorded must not execute (fail-closed integrity, ' +
224
+ 'docs/decisions/2026-07-12-audit-receipt-architecture.md §2a).');
225
+ }
226
+ // Signing key (GAP-355 Stage 3): a production signing deployment must have the
227
+ // Ed25519 key so rows land signed. Absence is a diverged-env deploy — fail it
228
+ // synchronously here (the serverless serving process's refuse-to-serve), the
229
+ // same rail the DB-connection check above uses. Full Ed25519 PKCS#8 parsing is
230
+ // done by validate-startup; this is the audit-owned presence parity check, so
231
+ // the contract cannot silently drift on one serving process. Dev/test
232
+ // (NODE_ENV !== 'production') runs unsigned by design and is exempt.
233
+ //
234
+ // GAP-417 items 1-2 (owner-countersigned 2026-07-25): SKIP_ENV_VALIDATION no
235
+ // longer exempts this check. The audit path has NO escape hatch — a
236
+ // production process that cannot sign must not boot, because unsigned rows
237
+ // are indistinguishable from tampering AND permanently stall anchor
238
+ // contiguity once the sweep filters them. SKIP still covers non-audit env
239
+ // validation elsewhere; it never buys an unsigned production audit log.
240
+ if (env.NODE_ENV === 'production') {
241
+ if (!env.REVEALUI_AUDIT_SIGNING_KEY) {
242
+ throw new Error('AUDIT STORAGE ENV PARITY FAILED: REVEALUI_AUDIT_SIGNING_KEY is not set on a ' +
243
+ 'production deployment, so the audit write path cannot sign rows. Refusing to ' +
244
+ 'serve — an unsigned row in the post-Stage-3 era is indistinguishable from ' +
245
+ 'tampering (docs/decisions/2026-07-12-audit-receipt-architecture.md §2a). ' +
246
+ 'SKIP_ENV_VALIDATION does not exempt the audit path (GAP-417).');
247
+ }
248
+ }
249
+ }
250
+ /**
251
+ * Swap the process-wide `audit` system onto persistent Postgres storage.
252
+ * Synchronous and side-effect-free at call time: `getClient()` is a lazy pool
253
+ * factory (no connection opened here), so this is safe to run on a serverless
254
+ * cold-start path. WITHOUT this call, audit events fall into the default
255
+ * `InMemoryAuditStorage` and evaporate on restart — which is exactly what
256
+ * happened to every admin-process emit before GAP-338.
257
+ *
258
+ * Call `assertAuditStorageEnv()` before this at each install site so a
259
+ * diverged-env deploy fails loudly instead of installing a store that can
260
+ * never write.
261
+ */
262
+ export function installAuditStorage() {
263
+ audit.setStorage(new DrizzleBackedAuditStorage(getClient()));
264
+ }
265
+ /**
266
+ * Boot-time round-trip self-test. Writes a synthetic audit event through the
267
+ * REAL installed path (AuditSystem → boundary → DrizzleAuditStore → audit_log)
268
+ * and reads it back. Throws if the round trip fails, so a runtime that cannot
269
+ * record agent actions REFUSES TO SERVE rather than silently dropping them
270
+ * (fail-closed integrity — ADR §2a).
271
+ *
272
+ * Uses `severity: 'low'` deliberately: that value would be rejected by the DB
273
+ * CHECK constraint if the boundary mapping regressed, so this exercises the
274
+ * exact defect Stage 1 fixes. A healthy audit path lets startup proceed.
275
+ *
276
+ * Runs on the long-running worker (once per deploy) and the dev boot path,
277
+ * where an async boot chain already exists and `process.exit(1)` gives
278
+ * "refuse to serve" clean semantics. It is NOT run on serverless cold-start
279
+ * paths, which stay free of per-invocation DB round-trips (storage is still
280
+ * installed there synchronously by `installAuditStorage()`).
281
+ */
282
+ export async function auditStorageSelfTest(auditSystem = audit) {
283
+ const marker = `__audit-self-test__:${randomUUID()}`;
284
+ const written = await auditSystem.log({
285
+ type: 'security.audit_self_test',
286
+ severity: 'low',
287
+ actor: { id: marker, type: 'system' },
288
+ action: 'audit-storage-self-test',
289
+ result: 'success',
290
+ metadata: { synthetic: true },
291
+ });
292
+ const found = await auditSystem.query({ actorId: marker, limit: 5 });
293
+ if (!found.some((event) => event.id === written.id)) {
294
+ throw new Error('AUDIT STORAGE SELF-TEST FAILED: wrote a synthetic audit event but could not read it ' +
295
+ 'back. Refusing to serve — a runtime that cannot record agent actions must not accept ' +
296
+ 'traffic (fail-closed integrity, docs/decisions/2026-07-12-audit-receipt-architecture.md §2a).');
297
+ }
298
+ }
@@ -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,CAqOvB"}
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;AAWpE;;;;;;;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,CA0PvB;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,CAyOvB"}
@@ -12,6 +12,7 @@ import { and, eq, isNull } from 'drizzle-orm';
12
12
  import { auditLoginFailure, auditLoginSuccess } from './audit-bridge.js';
13
13
  import { clearFailedAttempts, isAccountLocked, recordFailedAttempt } from './brute-force.js';
14
14
  import { validatePasswordStrength } from './password-validation.js';
15
+ import { ensureAccountOwnerPlatformAdmin } from './platform-roles.js';
15
16
  import { checkRateLimit } from './rate-limit.js';
16
17
  import { createSession, rotateSession } from './session.js';
17
18
  /** Grace period after signup during which unverified users can still sign in (24 hours) */
@@ -26,9 +27,27 @@ const EMAIL_VERIFICATION_GRACE_PERIOD_MS = 24 * 60 * 60 * 1000;
26
27
  */
27
28
  export async function signIn(email, password, options) {
28
29
  try {
29
- // Rate limiting by IP address
30
+ // Rate limiting by IP address. The storage backend (DatabaseStorage in
31
+ // production) can throw for reasons unrelated to the credential check
32
+ // itself (pool exhaustion, a transaction-pooling quirk, a storage
33
+ // misconfiguration) — guard it like every other DB-touching step below so
34
+ // a throw here fails closed with a diagnosable reason instead of falling
35
+ // through to the generic outer catch.
30
36
  const ipKey = options?.ipAddress || 'unknown';
31
- const rateLimit = await checkRateLimit(`signin:${ipKey}`);
37
+ let rateLimit;
38
+ try {
39
+ rateLimit = await checkRateLimit(`signin:${ipKey}`);
40
+ }
41
+ catch (rateLimitError) {
42
+ logger.error('Error checking rate limit', rateLimitError instanceof Error ? rateLimitError : undefined, {
43
+ message: rateLimitError instanceof Error ? rateLimitError.message : String(rateLimitError),
44
+ });
45
+ return {
46
+ success: false,
47
+ reason: 'database_error',
48
+ error: 'Database error',
49
+ };
50
+ }
32
51
  if (!rateLimit.allowed) {
33
52
  return {
34
53
  success: false,
@@ -36,8 +55,23 @@ export async function signIn(email, password, options) {
36
55
  error: 'Too many login attempts. Please try again later.',
37
56
  };
38
57
  }
39
- // Brute force protection by email
40
- const bruteForceCheck = await isAccountLocked(email);
58
+ // Brute force protection by email. Same rationale as the rate-limit
59
+ // guard above the storage backend can throw independently of whether
60
+ // the account is actually locked.
61
+ let bruteForceCheck;
62
+ try {
63
+ bruteForceCheck = await isAccountLocked(email);
64
+ }
65
+ catch (lockCheckError) {
66
+ logger.error('Error checking account lock status', lockCheckError instanceof Error ? lockCheckError : undefined, {
67
+ message: lockCheckError instanceof Error ? lockCheckError.message : String(lockCheckError),
68
+ });
69
+ return {
70
+ success: false,
71
+ reason: 'database_error',
72
+ error: 'Database error',
73
+ };
74
+ }
41
75
  if (bruteForceCheck.locked) {
42
76
  const lockMinutes = bruteForceCheck.lockUntil
43
77
  ? Math.ceil((bruteForceCheck.lockUntil - Date.now()) / (60 * 1000))
@@ -118,11 +152,27 @@ export async function signIn(email, password, options) {
118
152
  if (!isValid) {
119
153
  return failInvalidCredentials();
120
154
  }
121
- // Successful login - clear failed attempts
122
- await clearFailedAttempts(email);
123
- // Check email verification (with grace period for new accounts)
155
+ // Successful login - clear failed attempts. Best-effort: this is
156
+ // bookkeeping on an already-successful credential check, not a gate, so a
157
+ // storage hiccup here must not fail an otherwise-valid login (it can only
158
+ // make a future lockout trigger slightly sooner, never bypass one).
159
+ try {
160
+ await clearFailedAttempts(email);
161
+ }
162
+ catch (clearAttemptsError) {
163
+ logger.error('Error clearing failed attempts after successful login', clearAttemptsError instanceof Error ? clearAttemptsError : undefined, {
164
+ message: clearAttemptsError instanceof Error
165
+ ? clearAttemptsError.message
166
+ : String(clearAttemptsError),
167
+ });
168
+ }
169
+ // Check email verification (with grace period for new accounts).
170
+ // Coerce createdAt: the Postgres storage path returns timestamps as strings
171
+ // (not Date objects), so calling .getTime() directly threw a TypeError that
172
+ // fell through to the outer catch and turned a valid login into a generic
173
+ // "unexpected error" on real deploys. new Date() accepts a Date or a string.
124
174
  if (!user.emailVerified) {
125
- const accountAge = Date.now() - user.createdAt.getTime();
175
+ const accountAge = Date.now() - new Date(user.createdAt).getTime();
126
176
  if (accountAge > EMAIL_VERIFICATION_GRACE_PERIOD_MS) {
127
177
  return {
128
178
  success: false,
@@ -177,8 +227,12 @@ export async function signIn(email, password, options) {
177
227
  sessionToken: token,
178
228
  };
179
229
  }
180
- catch {
181
- logger.error('Unexpected error in signIn');
230
+ catch (err) {
231
+ logger.error('Unexpected error in signIn', err instanceof Error ? err : undefined, {
232
+ message: err instanceof Error ? err.message : String(err),
233
+ name: err instanceof Error ? err.name : 'unknown',
234
+ stack: err instanceof Error ? err.stack : undefined,
235
+ });
182
236
  return {
183
237
  success: false,
184
238
  reason: 'unexpected_error',
@@ -422,6 +476,10 @@ export async function signUp(email, password, name, options) {
422
476
  createdAt: now,
423
477
  updatedAt: now,
424
478
  });
479
+ // First membership owner of a hosted account gets platform shell
480
+ // admin (users.role), not super-admin. Unlocks /settings + API keys
481
+ // without granting fleet control-plane power.
482
+ await ensureAccountOwnerPlatformAdmin(db, user.id);
425
483
  }
426
484
  catch {
427
485
  logger.error('Failed to provision personal account at signup (billing webhook will backfill)', undefined, { userId: user.id });
@@ -21,10 +21,12 @@ export { configurePasskey, countUserCredentials, deletePasskey, generateAuthenti
21
21
  export type { ChangePasswordResult, PasswordResetResult, PasswordResetToken, } from './password-reset.js';
22
22
  export { changePassword, generatePasswordResetToken, invalidatePasswordResetToken, resetPasswordWithToken, validatePasswordResetToken, } from './password-reset.js';
23
23
  export { meetsMinimumPasswordRequirements, validatePasswordStrength, } from './password-validation.js';
24
+ export { ensureAccountOwnerPlatformAdmin, isPlatformShellAdminRole, PLATFORM_SHELL_ADMIN_COOKIE_ROLES, PLATFORM_SHELL_ADMIN_ROLES, platformRoleForAccountOwner, } from './platform-roles.js';
24
25
  export { checkRateLimit, configureRateLimit, getRateLimitStatus, resetRateLimit, resetRateLimitConfig, } from './rate-limit.js';
25
26
  export type { RequestContext, SessionBindingConfig, SessionData } from './session.js';
26
27
  export { configureSessionBinding, createSession, deleteAllUserSessions, deleteOtherUserSessions, deleteSession, getSession, isRecoverySession, resetSessionBindingConfig, rotateSession, validateSessionBinding, } from './session.js';
27
28
  export { signCookiePayload, verifyCookiePayload } from './signed-cookie.js';
29
+ export { type BuildOidcAuthorizationUrlInput, type BuildSamlAuthorizeUrlFailureReason, type BuildSamlAuthorizeUrlResult, buildOidcAuthorizationUrl, buildSamlAuthorizeUrl, buildSamlSpMetadata, createOidcRemoteJwkSet, type ExchangeOidcCodeFailureReason, type ExchangeOidcCodeInput, type ExchangeOidcCodeResult, type ExchangeOidcCodeSuccess, exchangeOidcCode, extractGroupsFromClaim, type FetchOidcDiscoveryOptions, type FetchOidcDiscoveryResult, fetchIdpMetadata, fetchOidcDiscovery, type GenerateSsoStateInput, type GenerateSsoStateResult, generateSsoState, type JWTPayload, type JWTVerifyGetKey, type KeyLike, type MapSsoGroupsFailureReason, type MapSsoGroupsInput, type MapSsoGroupsResult, mapSsoGroupsToRole, normalizeIdpCertPem, normalizeSsoUserRole, type OidcDiscoveryDocument, type OidcDiscoveryFailureReason, type ParseIdpMetadataFailureReason, type ParseIdpMetadataResult, parseIdpMetadataXml, type SamlSpConfig, type SsoStatePayload, type UpsertSsoUserInput, upsertSsoUser, type ValidatedIdTokenClaims, type ValidatedSamlAssertion, type ValidateIdTokenFailureReason, type ValidateIdTokenResult, type ValidateOidcIdTokenOptions, type ValidateSamlResponseFailureReason, type ValidateSamlResponseResult, type VerifiedSsoState, validateOidcIdToken, validateSamlPostResponse, verifySsoState, } from './sso/index.js';
28
30
  export type { Storage } from './storage/index.js';
29
31
  export { createStorage, DatabaseStorage, getStorage, InMemoryStorage, resetStorage, } from './storage/index.js';
30
32
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,12 @@
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';
@@ -16,8 +22,11 @@ export { buildAuthUrl, exchangeCode, fetchProviderUser, generateOAuthState, getL
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';
18
24
  export { meetsMinimumPasswordRequirements, validatePasswordStrength, } from './password-validation.js';
25
+ export { ensureAccountOwnerPlatformAdmin, isPlatformShellAdminRole, PLATFORM_SHELL_ADMIN_COOKIE_ROLES, PLATFORM_SHELL_ADMIN_ROLES, platformRoleForAccountOwner, } from './platform-roles.js';
19
26
  export { checkRateLimit, configureRateLimit, getRateLimitStatus, resetRateLimit, resetRateLimitConfig, } from './rate-limit.js';
20
27
  export { configureSessionBinding, createSession, deleteAllUserSessions, deleteOtherUserSessions, deleteSession, getSession, isRecoverySession, resetSessionBindingConfig, rotateSession, validateSessionBinding, } from './session.js';
21
28
  // Signed Cookie
22
29
  export { signCookiePayload, verifyCookiePayload } from './signed-cookie.js';
30
+ // Enterprise SSO pure layer (GAP-464) — OIDC + SAML SP helpers
31
+ export { buildOidcAuthorizationUrl, buildSamlAuthorizeUrl, buildSamlSpMetadata, createOidcRemoteJwkSet, exchangeOidcCode, extractGroupsFromClaim, fetchIdpMetadata, fetchOidcDiscovery, generateSsoState, mapSsoGroupsToRole, normalizeIdpCertPem, normalizeSsoUserRole, parseIdpMetadataXml, upsertSsoUser, validateOidcIdToken, validateSamlPostResponse, verifySsoState, } from './sso/index.js';
23
32
  export { createStorage, DatabaseStorage, getStorage, InMemoryStorage, resetStorage, } from './storage/index.js';