@opensyber/tokenforge 0.1.2 → 1.1.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.
Files changed (184) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +329 -52
  3. package/dist/adapters/astro.d.ts +64 -0
  4. package/dist/adapters/astro.d.ts.map +1 -0
  5. package/dist/adapters/astro.js +104 -0
  6. package/dist/adapters/astro.js.map +1 -0
  7. package/dist/adapters/cloud-v2-bridge.d.ts +8 -0
  8. package/dist/adapters/cloud-v2-bridge.d.ts.map +1 -0
  9. package/dist/adapters/cloud-v2-bridge.js +24 -0
  10. package/dist/adapters/cloud-v2-bridge.js.map +1 -0
  11. package/dist/adapters/express.d.ts +30 -2
  12. package/dist/adapters/express.d.ts.map +1 -1
  13. package/dist/adapters/express.js +65 -0
  14. package/dist/adapters/express.js.map +1 -1
  15. package/dist/adapters/fastify.d.ts +32 -6
  16. package/dist/adapters/fastify.d.ts.map +1 -1
  17. package/dist/adapters/fastify.js +65 -0
  18. package/dist/adapters/fastify.js.map +1 -1
  19. package/dist/adapters/hono.d.ts +13 -1
  20. package/dist/adapters/hono.d.ts.map +1 -1
  21. package/dist/adapters/hono.js +14 -1
  22. package/dist/adapters/hono.js.map +1 -1
  23. package/dist/adapters/index.d.ts +5 -3
  24. package/dist/adapters/index.d.ts.map +1 -1
  25. package/dist/adapters/index.js +5 -3
  26. package/dist/adapters/index.js.map +1 -1
  27. package/dist/adapters/nextjs.d.ts +31 -0
  28. package/dist/adapters/nextjs.d.ts.map +1 -1
  29. package/dist/adapters/nextjs.js +83 -30
  30. package/dist/adapters/nextjs.js.map +1 -1
  31. package/dist/adapters/sveltekit.d.ts +66 -0
  32. package/dist/adapters/sveltekit.d.ts.map +1 -0
  33. package/dist/adapters/sveltekit.js +101 -0
  34. package/dist/adapters/sveltekit.js.map +1 -0
  35. package/dist/client/action-signer.d.ts +53 -0
  36. package/dist/client/action-signer.d.ts.map +1 -0
  37. package/dist/client/action-signer.js +48 -0
  38. package/dist/client/action-signer.js.map +1 -0
  39. package/dist/client/binding.d.ts +24 -7
  40. package/dist/client/binding.d.ts.map +1 -1
  41. package/dist/client/binding.js +17 -6
  42. package/dist/client/binding.js.map +1 -1
  43. package/dist/client/cloud-device.d.ts +54 -0
  44. package/dist/client/cloud-device.d.ts.map +1 -0
  45. package/dist/client/cloud-device.js +301 -0
  46. package/dist/client/cloud-device.js.map +1 -0
  47. package/dist/client/crypto.d.ts.map +1 -1
  48. package/dist/client/crypto.js.map +1 -1
  49. package/dist/client/index.d.ts +14 -0
  50. package/dist/client/index.d.ts.map +1 -1
  51. package/dist/client/index.js +30 -2
  52. package/dist/client/index.js.map +1 -1
  53. package/dist/client/interceptor.d.ts.map +1 -1
  54. package/dist/client/interceptor.js +27 -3
  55. package/dist/client/interceptor.js.map +1 -1
  56. package/dist/client/signer.d.ts +40 -3
  57. package/dist/client/signer.d.ts.map +1 -1
  58. package/dist/client/signer.js +56 -5
  59. package/dist/client/signer.js.map +1 -1
  60. package/dist/client/storage.d.ts.map +1 -1
  61. package/dist/client/storage.js.map +1 -1
  62. package/dist/client/webauthn.d.ts +45 -0
  63. package/dist/client/webauthn.d.ts.map +1 -0
  64. package/dist/client/webauthn.js +118 -0
  65. package/dist/client/webauthn.js.map +1 -0
  66. package/dist/protocol/v2.d.ts +23 -0
  67. package/dist/protocol/v2.d.ts.map +1 -0
  68. package/dist/protocol/v2.js +157 -0
  69. package/dist/protocol/v2.js.map +1 -0
  70. package/dist/react/index.d.ts.map +1 -1
  71. package/dist/react/index.js.map +1 -1
  72. package/dist/react/provider.d.ts.map +1 -1
  73. package/dist/react/provider.js.map +1 -1
  74. package/dist/server/action-verify.d.ts +56 -0
  75. package/dist/server/action-verify.d.ts.map +1 -0
  76. package/dist/server/action-verify.js +47 -0
  77. package/dist/server/action-verify.js.map +1 -0
  78. package/dist/server/activity-histogram.d.ts +47 -0
  79. package/dist/server/activity-histogram.d.ts.map +1 -0
  80. package/dist/server/activity-histogram.js +76 -0
  81. package/dist/server/activity-histogram.js.map +1 -0
  82. package/dist/server/aitm-heuristics.d.ts +57 -0
  83. package/dist/server/aitm-heuristics.d.ts.map +1 -0
  84. package/dist/server/aitm-heuristics.js +120 -0
  85. package/dist/server/aitm-heuristics.js.map +1 -0
  86. package/dist/server/bind-with-id-token.d.ts +55 -0
  87. package/dist/server/bind-with-id-token.d.ts.map +1 -0
  88. package/dist/server/bind-with-id-token.js +70 -0
  89. package/dist/server/bind-with-id-token.js.map +1 -0
  90. package/dist/server/bound-cookie.d.ts +39 -0
  91. package/dist/server/bound-cookie.d.ts.map +1 -0
  92. package/dist/server/bound-cookie.js +59 -0
  93. package/dist/server/bound-cookie.js.map +1 -0
  94. package/dist/server/cbor.d.ts +20 -0
  95. package/dist/server/cbor.d.ts.map +1 -0
  96. package/dist/server/cbor.js +110 -0
  97. package/dist/server/cbor.js.map +1 -0
  98. package/dist/server/cloud-bind.d.ts +42 -0
  99. package/dist/server/cloud-bind.d.ts.map +1 -0
  100. package/dist/server/cloud-bind.js +387 -0
  101. package/dist/server/cloud-bind.js.map +1 -0
  102. package/dist/server/cloud-middleware.d.ts +46 -0
  103. package/dist/server/cloud-middleware.d.ts.map +1 -0
  104. package/dist/server/cloud-middleware.js +344 -0
  105. package/dist/server/cloud-middleware.js.map +1 -0
  106. package/dist/server/cloud-verify.d.ts +30 -0
  107. package/dist/server/cloud-verify.d.ts.map +1 -0
  108. package/dist/server/cloud-verify.js +206 -0
  109. package/dist/server/cloud-verify.js.map +1 -0
  110. package/dist/server/dbsc-challenge.d.ts +51 -0
  111. package/dist/server/dbsc-challenge.d.ts.map +1 -0
  112. package/dist/server/dbsc-challenge.js +78 -0
  113. package/dist/server/dbsc-challenge.js.map +1 -0
  114. package/dist/server/dbsc-endpoint.d.ts +20 -0
  115. package/dist/server/dbsc-endpoint.d.ts.map +1 -0
  116. package/dist/server/dbsc-endpoint.js +85 -0
  117. package/dist/server/dbsc-endpoint.js.map +1 -0
  118. package/dist/server/dbsc-verify.d.ts +32 -0
  119. package/dist/server/dbsc-verify.d.ts.map +1 -0
  120. package/dist/server/dbsc-verify.js +65 -0
  121. package/dist/server/dbsc-verify.js.map +1 -0
  122. package/dist/server/identity-profile.d.ts +18 -0
  123. package/dist/server/identity-profile.d.ts.map +1 -0
  124. package/dist/server/identity-profile.js +113 -0
  125. package/dist/server/identity-profile.js.map +1 -0
  126. package/dist/server/index.d.ts +12 -0
  127. package/dist/server/index.d.ts.map +1 -1
  128. package/dist/server/index.js +7 -0
  129. package/dist/server/index.js.map +1 -1
  130. package/dist/server/jws-verify.d.ts +46 -0
  131. package/dist/server/jws-verify.d.ts.map +1 -0
  132. package/dist/server/jws-verify.js +64 -0
  133. package/dist/server/jws-verify.js.map +1 -0
  134. package/dist/server/middleware.d.ts +24 -0
  135. package/dist/server/middleware.d.ts.map +1 -1
  136. package/dist/server/middleware.js +87 -20
  137. package/dist/server/middleware.js.map +1 -1
  138. package/dist/server/oidc-verify.d.ts +71 -0
  139. package/dist/server/oidc-verify.d.ts.map +1 -0
  140. package/dist/server/oidc-verify.js +115 -0
  141. package/dist/server/oidc-verify.js.map +1 -0
  142. package/dist/server/policy.d.ts +47 -0
  143. package/dist/server/policy.d.ts.map +1 -0
  144. package/dist/server/policy.js +84 -0
  145. package/dist/server/policy.js.map +1 -0
  146. package/dist/server/step-up-policy.d.ts +36 -0
  147. package/dist/server/step-up-policy.d.ts.map +1 -0
  148. package/dist/server/step-up-policy.js +101 -0
  149. package/dist/server/step-up-policy.js.map +1 -0
  150. package/dist/server/storage/index.d.ts +3 -0
  151. package/dist/server/storage/index.d.ts.map +1 -0
  152. package/dist/server/storage/index.js +2 -0
  153. package/dist/server/storage/index.js.map +1 -0
  154. package/dist/server/storage/interface.d.ts +73 -0
  155. package/dist/server/storage/interface.d.ts.map +1 -0
  156. package/dist/server/storage/interface.js +2 -0
  157. package/dist/server/storage/interface.js.map +1 -0
  158. package/dist/server/storage/memory.d.ts +50 -0
  159. package/dist/server/storage/memory.d.ts.map +1 -0
  160. package/dist/server/storage/memory.js +121 -0
  161. package/dist/server/storage/memory.js.map +1 -0
  162. package/dist/server/webauthn-verify.d.ts +41 -0
  163. package/dist/server/webauthn-verify.d.ts.map +1 -0
  164. package/dist/server/webauthn-verify.js +175 -0
  165. package/dist/server/webauthn-verify.js.map +1 -0
  166. package/dist/server/webhooks.d.ts +42 -0
  167. package/dist/server/webhooks.d.ts.map +1 -0
  168. package/dist/server/webhooks.js +77 -0
  169. package/dist/server/webhooks.js.map +1 -0
  170. package/dist/shared/action-hash.d.ts +23 -0
  171. package/dist/shared/action-hash.d.ts.map +1 -0
  172. package/dist/shared/action-hash.js +35 -0
  173. package/dist/shared/action-hash.js.map +1 -0
  174. package/dist/shared/identity.d.ts +45 -0
  175. package/dist/shared/identity.d.ts.map +1 -0
  176. package/dist/shared/identity.js +176 -0
  177. package/dist/shared/identity.js.map +1 -0
  178. package/dist/shared/types-internal.d.ts.map +1 -1
  179. package/dist/shared/types-internal.js.map +1 -1
  180. package/dist/shared/types.d.ts +49 -2
  181. package/dist/shared/types.d.ts.map +1 -1
  182. package/dist/shared/types.js +1 -0
  183. package/dist/shared/types.js.map +1 -1
  184. package/package.json +31 -33
@@ -13,6 +13,15 @@ export interface TokenForgeOptions {
13
13
  allowThreshold?: number;
14
14
  /** Trust score threshold for step-up (default: 40). */
15
15
  stepUpThreshold?: number;
16
+ /**
17
+ * Behavior when the request cannot be positively verified — missing TF
18
+ * headers on a protected path, or an unreachable/erroring TokenForge API.
19
+ *
20
+ * - `'closed'` (default): default-deny. Reject with 401 (missing binding)
21
+ * or 503 (backend unavailable). Bulletproof posture.
22
+ * - `'open'`: legacy fail-open — degrade through. Staged rollout only.
23
+ */
24
+ failMode?: 'closed' | 'open';
16
25
  }
17
26
  /**
18
27
  * TokenForge Hono middleware — verifies device-bound sessions via the
@@ -23,6 +32,21 @@ export interface TokenForgeOptions {
23
32
  * @returns Hono middleware handler.
24
33
  */
25
34
  export declare function tokenForgeMiddleware(options: TokenForgeOptions): MiddlewareHandler;
35
+ /**
36
+ * Per-route step-up gate. Use after `tokenForgeMiddleware` to require a
37
+ * higher trust score on sensitive routes than the global allow threshold.
38
+ *
39
+ * ```ts
40
+ * app.use(tokenForgeMiddleware({ apiKey }));
41
+ * app.use('/admin/*', requireFreshSig({ minTrustScore: 90 }));
42
+ * ```
43
+ *
44
+ * Responds with 403 `elevated_trust_required` when the cached `tf` context
45
+ * is missing or below the threshold.
46
+ */
47
+ export declare function requireFreshSig(opts?: {
48
+ minTrustScore?: number;
49
+ }): MiddlewareHandler;
26
50
  /** @internal */
27
51
  export declare function shouldSkip(path: string, skipPaths?: string[]): boolean;
28
52
  /** @internal */
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAI7D,wDAAwD;AACxD,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAcD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,GACzB,iBAAiB,CAgFnB;AAED,gBAAgB;AAChB,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAMtE;AAED,gBAAgB;AAChB,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,OAAO,CAaT"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/","sources":["server/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAI7D,wDAAwD;AACxD,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAC9B;AAcD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,GACzB,iBAAiB,CAwGnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,iBAAiB,CAcxF;AAsBD,gBAAgB;AAChB,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAMtE;AAED,gBAAgB;AAChB,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,OAAO,CAaT"}
@@ -11,6 +11,7 @@ export function tokenForgeMiddleware(options) {
11
11
  const apiBase = options.apiBase ?? DEFAULT_API_BASE;
12
12
  const allowThreshold = options.allowThreshold ?? 80;
13
13
  const stepUpThreshold = options.stepUpThreshold ?? 40;
14
+ const failMode = options.failMode ?? 'closed';
14
15
  return async (c, next) => {
15
16
  if (shouldSkip(c.req.path, options.skipPaths))
16
17
  return next();
@@ -20,30 +21,52 @@ export function tokenForgeMiddleware(options) {
20
21
  timestamp: c.req.header('X-TF-Timestamp') ?? null,
21
22
  deviceId: c.req.header('X-TF-Device-ID') ?? null,
22
23
  };
24
+ // Default-deny: a protected path (not in skipPaths) with no TF headers
25
+ // cannot be verified. Reject locally before spending an API round-trip.
26
+ if (!headers.signature || !headers.nonce || !headers.timestamp || !headers.deviceId) {
27
+ if (failMode === 'open') {
28
+ c.set('tf', { bound: false, trustScore: 0, deviceId: null });
29
+ return next();
30
+ }
31
+ return c.json({
32
+ error: 'device_binding_required',
33
+ action: 'bind',
34
+ message: 'This operation requires device verification',
35
+ }, 401);
36
+ }
23
37
  // Call TokenForge API for verification
24
- const res = await fetch(`${apiBase}/v1/edge/verify`, {
25
- method: 'POST',
26
- headers: {
27
- Authorization: `Bearer ${options.apiKey}`,
28
- 'Content-Type': 'application/json',
29
- },
30
- body: JSON.stringify({
31
- path: c.req.path,
32
- method: c.req.method,
33
- headers,
34
- ipAddress: c.req.header('cf-connecting-ip')
35
- ?? c.req.header('x-forwarded-for') ?? '',
36
- countryCode: c.req.header('cf-ipcountry') ?? '',
37
- userAgent: c.req.header('user-agent') ?? '',
38
- }),
39
- });
38
+ let res;
39
+ try {
40
+ res = await fetch(`${apiBase}/v1/edge/verify`, {
41
+ method: 'POST',
42
+ headers: {
43
+ Authorization: `Bearer ${options.apiKey}`,
44
+ 'Content-Type': 'application/json',
45
+ },
46
+ body: JSON.stringify({
47
+ path: c.req.path,
48
+ method: c.req.method,
49
+ headers,
50
+ ipAddress: c.req.header('cf-connecting-ip')
51
+ ?? c.req.header('x-forwarded-for') ?? '',
52
+ countryCode: c.req.header('cf-ipcountry') ?? '',
53
+ userAgent: c.req.header('user-agent') ?? '',
54
+ }),
55
+ });
56
+ }
57
+ catch (err) {
58
+ // Network failure reaching the verification backend.
59
+ return handleUnavailable(c, next, failMode, `fetch: ${String(err)}`);
60
+ }
40
61
  if (!res.ok) {
41
- // API unreachable degrade gracefully
42
- console.error('[TokenForge] API error:', res.status);
43
- c.set('tf', { bound: false, trustScore: 0, deviceId: null });
44
- return next();
62
+ return handleUnavailable(c, next, failMode, `status ${res.status}`);
45
63
  }
46
64
  const { data } = (await res.json());
65
+ // Backend could not complete verification. Under fail-closed this must
66
+ // NOT pass — an unverifiable session is treated as unavailable.
67
+ if (data.status === 'degraded') {
68
+ return handleUnavailable(c, next, failMode, 'edge_degraded');
69
+ }
47
70
  if (data.status === 'block') {
48
71
  return c.json({
49
72
  error: 'session_blocked',
@@ -77,6 +100,50 @@ export function tokenForgeMiddleware(options) {
77
100
  await next();
78
101
  };
79
102
  }
103
+ /**
104
+ * Per-route step-up gate. Use after `tokenForgeMiddleware` to require a
105
+ * higher trust score on sensitive routes than the global allow threshold.
106
+ *
107
+ * ```ts
108
+ * app.use(tokenForgeMiddleware({ apiKey }));
109
+ * app.use('/admin/*', requireFreshSig({ minTrustScore: 90 }));
110
+ * ```
111
+ *
112
+ * Responds with 403 `elevated_trust_required` when the cached `tf` context
113
+ * is missing or below the threshold.
114
+ */
115
+ export function requireFreshSig(opts = {}) {
116
+ const min = opts.minTrustScore ?? 90;
117
+ return async (c, next) => {
118
+ const tf = c.get('tf');
119
+ const score = tf?.trustScore ?? 0;
120
+ if (score < min) {
121
+ return c.json({
122
+ error: 'elevated_trust_required',
123
+ action: 'step_up_required',
124
+ trustScore: score,
125
+ }, 403);
126
+ }
127
+ await next();
128
+ };
129
+ }
130
+ /**
131
+ * Handle an unverifiable request caused by a backend failure (network error,
132
+ * non-2xx, or `degraded` verdict). Fail-closed → 503; fail-open → proceed.
133
+ * @internal
134
+ */
135
+ function handleUnavailable(c, next, failMode, detail) {
136
+ console.error('[TokenForge] verification unavailable:', detail);
137
+ if (failMode === 'open') {
138
+ c.set('tf', { bound: false, trustScore: 0, deviceId: null });
139
+ return next();
140
+ }
141
+ return c.json({
142
+ error: 'verification_unavailable',
143
+ action: 'retry',
144
+ message: 'Session verification is temporarily unavailable',
145
+ }, 503);
146
+ }
80
147
  /** @internal */
81
148
  export function shouldSkip(path, skipPaths) {
82
149
  if (!skipPaths)
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"../src/","sources":["server/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * OIDC ID-token verifier (Sprint 40 — workforce mode).
3
+ *
4
+ * Verifies an OIDC ID token against an IdP's JWKS by `kid`. Supports
5
+ * RS256 + ES256 — the algorithms that Okta, Entra, Google Workspace,
6
+ * and Auth0 ship by default. Stateless: the JWKS is passed in, so
7
+ * caching/HTTP lives one layer up (see jwks-cache.ts).
8
+ *
9
+ * Validates signature, `iss`, `aud`, `exp`, `nbf`, `iat`. Returns the
10
+ * verified claims so the caller can extract `sub` and link it to a
11
+ * subject row before issuing a DBSC challenge.
12
+ */
13
+ export interface OidcClaims {
14
+ iss: string;
15
+ sub: string;
16
+ aud: string | string[];
17
+ exp: number;
18
+ iat: number;
19
+ nbf?: number;
20
+ email?: string;
21
+ name?: string;
22
+ groups?: string[];
23
+ /** Google Workspace hosted domain. Absent for personal Google accounts. */
24
+ hd?: string;
25
+ [key: string]: unknown;
26
+ }
27
+ export interface JwksKey {
28
+ kid: string;
29
+ kty: 'RSA' | 'EC';
30
+ alg?: string;
31
+ use?: string;
32
+ n?: string;
33
+ e?: string;
34
+ crv?: string;
35
+ x?: string;
36
+ y?: string;
37
+ }
38
+ export interface VerifyOidcOptions {
39
+ /**
40
+ * Accepted `iss` value(s). A list is required because Google issues ID tokens
41
+ * with either `https://accounts.google.com` or the bare `accounts.google.com`
42
+ * — both documented as valid — so strict equality against one form rejects
43
+ * a large share of legitimate Google tokens.
44
+ */
45
+ expectedIssuer: string | string[];
46
+ expectedAudience: string;
47
+ /**
48
+ * Required Google Workspace hosted domain (`hd` claim). When set, a token
49
+ * without a matching `hd` is rejected: a Workspace app that skips this check
50
+ * accepts any Google account, including personal gmail.com, as a verified
51
+ * corporate identity.
52
+ */
53
+ expectedHostedDomain?: string;
54
+ jwks: {
55
+ keys: JwksKey[];
56
+ };
57
+ /** Override for tests. */
58
+ now?: Date;
59
+ /** Clock skew tolerance in seconds (default 60). */
60
+ clockSkewSeconds?: number;
61
+ }
62
+ export type VerifyOidcResult = {
63
+ ok: true;
64
+ claims: OidcClaims;
65
+ protectedHeader: Record<string, unknown>;
66
+ } | {
67
+ ok: false;
68
+ reason: string;
69
+ };
70
+ export declare function verifyOidcIdToken(jwt: string, opts: VerifyOidcOptions): Promise<VerifyOidcResult>;
71
+ //# sourceMappingURL=oidc-verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc-verify.d.ts","sourceRoot":"../src/","sources":["server/oidc-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,2EAA2E;IAC3E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;IAC1B,0BAA0B;IAC1B,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC1E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAIlC,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAkE3B"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * OIDC ID-token verifier (Sprint 40 — workforce mode).
3
+ *
4
+ * Verifies an OIDC ID token against an IdP's JWKS by `kid`. Supports
5
+ * RS256 + ES256 — the algorithms that Okta, Entra, Google Workspace,
6
+ * and Auth0 ship by default. Stateless: the JWKS is passed in, so
7
+ * caching/HTTP lives one layer up (see jwks-cache.ts).
8
+ *
9
+ * Validates signature, `iss`, `aud`, `exp`, `nbf`, `iat`. Returns the
10
+ * verified claims so the caller can extract `sub` and link it to a
11
+ * subject row before issuing a DBSC challenge.
12
+ */
13
+ const SUPPORTED_ALGS = new Set(['RS256', 'ES256']);
14
+ export async function verifyOidcIdToken(jwt, opts) {
15
+ const parts = jwt.split('.');
16
+ if (parts.length !== 3)
17
+ return { ok: false, reason: 'jwt_malformed' };
18
+ const [headerB64, payloadB64, sigB64] = parts;
19
+ let header;
20
+ let claims;
21
+ try {
22
+ header = JSON.parse(b64UrlDecodeString(headerB64));
23
+ claims = JSON.parse(b64UrlDecodeString(payloadB64));
24
+ }
25
+ catch {
26
+ return { ok: false, reason: 'jwt_malformed' };
27
+ }
28
+ const alg = header.alg;
29
+ const kid = header.kid;
30
+ if (typeof alg !== 'string' || !SUPPORTED_ALGS.has(alg)) {
31
+ return { ok: false, reason: 'jwt_unsupported_alg' };
32
+ }
33
+ if (typeof kid !== 'string')
34
+ return { ok: false, reason: 'jwt_missing_kid' };
35
+ const key = opts.jwks.keys.find((k) => k.kid === kid);
36
+ if (!key)
37
+ return { ok: false, reason: 'jwks_kid_not_found' };
38
+ const acceptedIssuers = Array.isArray(opts.expectedIssuer)
39
+ ? opts.expectedIssuer
40
+ : [opts.expectedIssuer];
41
+ if (!acceptedIssuers.includes(claims.iss))
42
+ return { ok: false, reason: 'iss_mismatch' };
43
+ if (opts.expectedHostedDomain) {
44
+ if (claims.hd !== opts.expectedHostedDomain) {
45
+ return { ok: false, reason: 'hosted_domain_mismatch' };
46
+ }
47
+ }
48
+ const audMatch = Array.isArray(claims.aud)
49
+ ? claims.aud.includes(opts.expectedAudience)
50
+ : claims.aud === opts.expectedAudience;
51
+ if (!audMatch)
52
+ return { ok: false, reason: 'aud_mismatch' };
53
+ const now = opts.now ?? new Date();
54
+ const nowSec = Math.floor(now.getTime() / 1000);
55
+ const skew = opts.clockSkewSeconds ?? 60;
56
+ if (typeof claims.exp !== 'number' || claims.exp + skew < nowSec) {
57
+ return { ok: false, reason: 'jwt_expired' };
58
+ }
59
+ if (typeof claims.nbf === 'number' && claims.nbf - skew > nowSec) {
60
+ return { ok: false, reason: 'jwt_not_yet_valid' };
61
+ }
62
+ if (typeof claims.iat !== 'number' || claims.iat - skew > nowSec) {
63
+ return { ok: false, reason: 'jwt_future_iat' };
64
+ }
65
+ const cryptoKey = await importJwk(key, alg);
66
+ if (!cryptoKey)
67
+ return { ok: false, reason: 'jwks_bad_key' };
68
+ const signingInput = toAb(new TextEncoder().encode(`${headerB64}.${payloadB64}`));
69
+ const signatureBytes = base64UrlToBytes(sigB64);
70
+ if (!signatureBytes)
71
+ return { ok: false, reason: 'jwt_bad_signature' };
72
+ const signature = toAb(signatureBytes);
73
+ const verified = alg === 'RS256'
74
+ ? await crypto.subtle.verify('RSASSA-PKCS1-v1_5', cryptoKey, signature, signingInput)
75
+ : await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, cryptoKey, signature, signingInput);
76
+ if (!verified)
77
+ return { ok: false, reason: 'jwt_bad_signature' };
78
+ return { ok: true, claims, protectedHeader: header };
79
+ }
80
+ async function importJwk(key, alg) {
81
+ try {
82
+ if (alg === 'RS256') {
83
+ return await crypto.subtle.importKey('jwk', { kty: key.kty, n: key.n, e: key.e, alg, ext: true }, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);
84
+ }
85
+ return await crypto.subtle.importKey('jwk', { kty: key.kty, crv: key.crv, x: key.x, y: key.y, ext: true }, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ }
91
+ function b64UrlDecodeString(b64) {
92
+ const s = b64.replace(/-/g, '+').replace(/_/g, '/');
93
+ const padded = s + '='.repeat((4 - (s.length % 4)) % 4);
94
+ return atob(padded);
95
+ }
96
+ function toAb(u8) {
97
+ const buf = new ArrayBuffer(u8.byteLength);
98
+ new Uint8Array(buf).set(u8);
99
+ return buf;
100
+ }
101
+ function base64UrlToBytes(b64) {
102
+ const s = b64.replace(/-/g, '+').replace(/_/g, '/');
103
+ const padded = s + '='.repeat((4 - (s.length % 4)) % 4);
104
+ try {
105
+ const bin = atob(padded);
106
+ const out = new Uint8Array(bin.length);
107
+ for (let i = 0; i < bin.length; i++)
108
+ out[i] = bin.charCodeAt(i);
109
+ return out;
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ }
115
+ //# sourceMappingURL=oidc-verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc-verify.js","sourceRoot":"../src/","sources":["server/oidc-verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Policy DSL evaluator (Sprint 38).
3
+ *
4
+ * Each policy is a JSON object: { if_any?: Rule[]; if_all?: Rule[]; then: Action }.
5
+ * Rules are predicate atoms over a flat `PolicyContext` describing the
6
+ * current refresh attempt. The evaluator is pure and synchronous — no
7
+ * IO, no time, no surprises — so it stays unit-testable and safe to
8
+ * call inside the request critical path.
9
+ *
10
+ * Action precedence: block > revoke_session > step_up > allow.
11
+ * Multiple policies for the same tenant are folded with `combineActions`
12
+ * so the most restrictive verdict wins.
13
+ */
14
+ export type PolicyAction = 'allow' | 'step_up' | 'block' | 'revoke_session';
15
+ export interface PolicyContext {
16
+ geoCountry: string | null;
17
+ asn: string | null;
18
+ bindingClass: 'native_dbsc' | 'webauthn' | 'webcrypto' | null;
19
+ signals: string[];
20
+ sensitivePath: boolean;
21
+ isoHour: number;
22
+ }
23
+ export type Rule = {
24
+ geo_country_in: string[];
25
+ } | {
26
+ geo_country_not_in: string[];
27
+ } | {
28
+ asn_in: string[];
29
+ } | {
30
+ binding_class: PolicyContext['bindingClass'];
31
+ } | {
32
+ has_signal: string;
33
+ } | {
34
+ sensitive_path: boolean;
35
+ } | {
36
+ hour_between: [number, number];
37
+ };
38
+ export interface Policy {
39
+ if_any?: Rule[];
40
+ if_all?: Rule[];
41
+ then: PolicyAction;
42
+ }
43
+ export declare function evaluatePolicy(policy: Policy, ctx: PolicyContext): PolicyAction;
44
+ export declare function evaluatePolicies(policies: Policy[], ctx: PolicyContext): PolicyAction;
45
+ export declare function combineActions(a: PolicyAction, b: PolicyAction): PolicyAction;
46
+ export declare function parsePolicyRules(raw: string): Policy | null;
47
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"../src/","sources":["server/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAE5E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,YAAY,EAAE,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,IAAI,CAAC;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,IAAI,GACZ;IAAE,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,GAC5B;IAAE,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAAE,GAChC;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GACpB;IAAE,aAAa,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;CAAE,GAChD;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GACtB;IAAE,cAAc,EAAE,OAAO,CAAA;CAAE,GAC3B;IAAE,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,MAAM;IACrB,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;IAChB,IAAI,EAAE,YAAY,CAAC;CACpB;AASD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CAU/E;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CAIrF;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,YAAY,CAE7E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc3D"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Policy DSL evaluator (Sprint 38).
3
+ *
4
+ * Each policy is a JSON object: { if_any?: Rule[]; if_all?: Rule[]; then: Action }.
5
+ * Rules are predicate atoms over a flat `PolicyContext` describing the
6
+ * current refresh attempt. The evaluator is pure and synchronous — no
7
+ * IO, no time, no surprises — so it stays unit-testable and safe to
8
+ * call inside the request critical path.
9
+ *
10
+ * Action precedence: block > revoke_session > step_up > allow.
11
+ * Multiple policies for the same tenant are folded with `combineActions`
12
+ * so the most restrictive verdict wins.
13
+ */
14
+ const PRECEDENCE = {
15
+ allow: 0,
16
+ step_up: 1,
17
+ revoke_session: 2,
18
+ block: 3,
19
+ };
20
+ export function evaluatePolicy(policy, ctx) {
21
+ const anyRules = policy.if_any ?? [];
22
+ const allRules = policy.if_all ?? [];
23
+ const anyMatch = anyRules.length > 0 && anyRules.some((r) => evalRule(r, ctx));
24
+ const allMatch = allRules.length > 0 && allRules.every((r) => evalRule(r, ctx));
25
+ const conditionTriggered = (anyRules.length > 0 && anyMatch) ||
26
+ (allRules.length > 0 && allMatch) ||
27
+ (anyRules.length === 0 && allRules.length === 0);
28
+ return conditionTriggered ? policy.then : 'allow';
29
+ }
30
+ export function evaluatePolicies(policies, ctx) {
31
+ return policies
32
+ .map((p) => evaluatePolicy(p, ctx))
33
+ .reduce(combineActions, 'allow');
34
+ }
35
+ export function combineActions(a, b) {
36
+ return PRECEDENCE[a] >= PRECEDENCE[b] ? a : b;
37
+ }
38
+ export function parsePolicyRules(raw) {
39
+ try {
40
+ const parsed = JSON.parse(raw);
41
+ if (!parsed || typeof parsed !== 'object')
42
+ return null;
43
+ const p = parsed;
44
+ if (p.then !== 'allow' && p.then !== 'step_up' && p.then !== 'block' && p.then !== 'revoke_session') {
45
+ return null;
46
+ }
47
+ if (p.if_any !== undefined && !Array.isArray(p.if_any))
48
+ return null;
49
+ if (p.if_all !== undefined && !Array.isArray(p.if_all))
50
+ return null;
51
+ return p;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ function evalRule(rule, ctx) {
58
+ if ('geo_country_in' in rule) {
59
+ return ctx.geoCountry !== null && rule.geo_country_in.includes(ctx.geoCountry);
60
+ }
61
+ if ('geo_country_not_in' in rule) {
62
+ return ctx.geoCountry !== null && !rule.geo_country_not_in.includes(ctx.geoCountry);
63
+ }
64
+ if ('asn_in' in rule) {
65
+ return ctx.asn !== null && rule.asn_in.includes(ctx.asn);
66
+ }
67
+ if ('binding_class' in rule) {
68
+ return ctx.bindingClass === rule.binding_class;
69
+ }
70
+ if ('has_signal' in rule) {
71
+ return ctx.signals.includes(rule.has_signal);
72
+ }
73
+ if ('sensitive_path' in rule) {
74
+ return ctx.sensitivePath === rule.sensitive_path;
75
+ }
76
+ if ('hour_between' in rule) {
77
+ const [start, end] = rule.hour_between;
78
+ if (start <= end)
79
+ return ctx.isoHour >= start && ctx.isoHour < end;
80
+ return ctx.isoHour >= start || ctx.isoHour < end;
81
+ }
82
+ return false;
83
+ }
84
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"../src/","sources":["server/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Per-action step-up policy (Sprint 39).
3
+ *
4
+ * Tenant-scoped JSON config describing which routes require fresh PoP
5
+ * (a recent action-signing JWS) vs WebAuthn re-prompt vs nothing
6
+ * special. Will eventually live on `tf_tenants.stepUpActions` (per
7
+ * CISCO-dua spec) — schema migration pending. The pure logic here
8
+ * is independent of where the actions blob is stored.
9
+ *
10
+ * Shape (validated by `parseStepUpActions`):
11
+ * [
12
+ * { path: '/checkout', requireFreshSig: true, freshSigMaxAgeSec: 60 },
13
+ * { path: '/admin/*', requireFreshSig: true, freshSigMaxAgeSec: 30, requireWebAuthn: true },
14
+ * { path: '/health', requireFreshSig: false, requireWebAuthn: false }
15
+ * ]
16
+ *
17
+ * Matching rules:
18
+ * - exact path match wins over glob-prefix
19
+ * - longest glob-prefix wins among prefix-matches
20
+ * - no match → default verdict (no step-up, no WebAuthn, default 60s)
21
+ */
22
+ export interface StepUpAction {
23
+ path: string;
24
+ requireFreshSig?: boolean;
25
+ freshSigMaxAgeSec?: number;
26
+ requireWebAuthn?: boolean;
27
+ }
28
+ export interface StepUpVerdict {
29
+ matched: boolean;
30
+ requireFreshSig: boolean;
31
+ freshSigMaxAgeSec: number;
32
+ requireWebAuthn: boolean;
33
+ }
34
+ export declare function parseStepUpActions(raw: string): StepUpAction[] | null;
35
+ export declare function evaluateStepUpPolicy(actions: readonly StepUpAction[], requestPath: string): StepUpVerdict;
36
+ //# sourceMappingURL=step-up-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-up-policy.d.ts","sourceRoot":"../src/","sources":["server/step-up-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAcD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI,CA4BrE;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,WAAW,EAAE,MAAM,GAClB,aAAa,CAiBf"}