@vivero/stoma 0.1.0-rc.5

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 (251) hide show
  1. package/dist/adapters/bun.d.ts +9 -0
  2. package/dist/adapters/bun.js +8 -0
  3. package/dist/adapters/bun.js.map +1 -0
  4. package/dist/adapters/cloudflare.d.ts +49 -0
  5. package/dist/adapters/cloudflare.js +85 -0
  6. package/dist/adapters/cloudflare.js.map +1 -0
  7. package/dist/adapters/deno.d.ts +9 -0
  8. package/dist/adapters/deno.js +8 -0
  9. package/dist/adapters/deno.js.map +1 -0
  10. package/dist/adapters/durable-object.d.ts +63 -0
  11. package/dist/adapters/durable-object.js +46 -0
  12. package/dist/adapters/durable-object.js.map +1 -0
  13. package/dist/adapters/index.d.ts +13 -0
  14. package/dist/adapters/index.js +53 -0
  15. package/dist/adapters/index.js.map +1 -0
  16. package/dist/adapters/memory.d.ts +9 -0
  17. package/dist/adapters/memory.js +14 -0
  18. package/dist/adapters/memory.js.map +1 -0
  19. package/dist/adapters/node.d.ts +9 -0
  20. package/dist/adapters/node.js +8 -0
  21. package/dist/adapters/node.js.map +1 -0
  22. package/dist/adapters/postgres.d.ts +109 -0
  23. package/dist/adapters/postgres.js +242 -0
  24. package/dist/adapters/postgres.js.map +1 -0
  25. package/dist/adapters/redis.d.ts +116 -0
  26. package/dist/adapters/redis.js +194 -0
  27. package/dist/adapters/redis.js.map +1 -0
  28. package/dist/adapters/testing.d.ts +32 -0
  29. package/dist/adapters/testing.js +33 -0
  30. package/dist/adapters/testing.js.map +1 -0
  31. package/dist/adapters/types.d.ts +4 -0
  32. package/dist/adapters/types.js +1 -0
  33. package/dist/adapters/types.js.map +1 -0
  34. package/dist/config/index.d.ts +11 -0
  35. package/dist/config/index.js +21 -0
  36. package/dist/config/index.js.map +1 -0
  37. package/dist/config/merge.d.ts +48 -0
  38. package/dist/config/merge.js +83 -0
  39. package/dist/config/merge.js.map +1 -0
  40. package/dist/config/schema.d.ts +254 -0
  41. package/dist/config/schema.js +109 -0
  42. package/dist/config/schema.js.map +1 -0
  43. package/dist/core/errors.d.ts +66 -0
  44. package/dist/core/errors.js +47 -0
  45. package/dist/core/errors.js.map +1 -0
  46. package/dist/core/gateway.d.ts +44 -0
  47. package/dist/core/gateway.js +390 -0
  48. package/dist/core/gateway.js.map +1 -0
  49. package/dist/core/health.d.ts +78 -0
  50. package/dist/core/health.js +65 -0
  51. package/dist/core/health.js.map +1 -0
  52. package/dist/core/pipeline.d.ts +62 -0
  53. package/dist/core/pipeline.js +214 -0
  54. package/dist/core/pipeline.js.map +1 -0
  55. package/dist/core/protocol.d.ts +4 -0
  56. package/dist/core/protocol.js +1 -0
  57. package/dist/core/protocol.js.map +1 -0
  58. package/dist/core/scope.d.ts +67 -0
  59. package/dist/core/scope.js +44 -0
  60. package/dist/core/scope.js.map +1 -0
  61. package/dist/core/types.d.ts +252 -0
  62. package/dist/core/types.js +1 -0
  63. package/dist/core/types.js.map +1 -0
  64. package/dist/index.d.ts +57 -0
  65. package/dist/index.js +158 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/observability/admin.d.ts +32 -0
  68. package/dist/observability/admin.js +85 -0
  69. package/dist/observability/admin.js.map +1 -0
  70. package/dist/observability/metrics.d.ts +78 -0
  71. package/dist/observability/metrics.js +107 -0
  72. package/dist/observability/metrics.js.map +1 -0
  73. package/dist/observability/tracing.d.ts +149 -0
  74. package/dist/observability/tracing.js +191 -0
  75. package/dist/observability/tracing.js.map +1 -0
  76. package/dist/policies/auth/api-key-auth.d.ts +64 -0
  77. package/dist/policies/auth/api-key-auth.js +93 -0
  78. package/dist/policies/auth/api-key-auth.js.map +1 -0
  79. package/dist/policies/auth/basic-auth.d.ts +33 -0
  80. package/dist/policies/auth/basic-auth.js +96 -0
  81. package/dist/policies/auth/basic-auth.js.map +1 -0
  82. package/dist/policies/auth/crypto.d.ts +29 -0
  83. package/dist/policies/auth/crypto.js +100 -0
  84. package/dist/policies/auth/crypto.js.map +1 -0
  85. package/dist/policies/auth/generate-http-signature.d.ts +30 -0
  86. package/dist/policies/auth/generate-http-signature.js +79 -0
  87. package/dist/policies/auth/generate-http-signature.js.map +1 -0
  88. package/dist/policies/auth/generate-jwt.d.ts +44 -0
  89. package/dist/policies/auth/generate-jwt.js +99 -0
  90. package/dist/policies/auth/generate-jwt.js.map +1 -0
  91. package/dist/policies/auth/http-signature-base.d.ts +55 -0
  92. package/dist/policies/auth/http-signature-base.js +140 -0
  93. package/dist/policies/auth/http-signature-base.js.map +1 -0
  94. package/dist/policies/auth/jws.d.ts +46 -0
  95. package/dist/policies/auth/jws.js +317 -0
  96. package/dist/policies/auth/jws.js.map +1 -0
  97. package/dist/policies/auth/jwt-auth.d.ts +64 -0
  98. package/dist/policies/auth/jwt-auth.js +268 -0
  99. package/dist/policies/auth/jwt-auth.js.map +1 -0
  100. package/dist/policies/auth/oauth2.d.ts +38 -0
  101. package/dist/policies/auth/oauth2.js +254 -0
  102. package/dist/policies/auth/oauth2.js.map +1 -0
  103. package/dist/policies/auth/rbac.d.ts +30 -0
  104. package/dist/policies/auth/rbac.js +115 -0
  105. package/dist/policies/auth/rbac.js.map +1 -0
  106. package/dist/policies/auth/verify-http-signature.d.ts +30 -0
  107. package/dist/policies/auth/verify-http-signature.js +147 -0
  108. package/dist/policies/auth/verify-http-signature.js.map +1 -0
  109. package/dist/policies/index.d.ts +51 -0
  110. package/dist/policies/index.js +109 -0
  111. package/dist/policies/index.js.map +1 -0
  112. package/dist/policies/mock.d.ts +60 -0
  113. package/dist/policies/mock.js +29 -0
  114. package/dist/policies/mock.js.map +1 -0
  115. package/dist/policies/observability/assign-metrics.d.ts +37 -0
  116. package/dist/policies/observability/assign-metrics.js +29 -0
  117. package/dist/policies/observability/assign-metrics.js.map +1 -0
  118. package/dist/policies/observability/metrics-reporter.d.ts +25 -0
  119. package/dist/policies/observability/metrics-reporter.js +62 -0
  120. package/dist/policies/observability/metrics-reporter.js.map +1 -0
  121. package/dist/policies/observability/request-log.d.ts +135 -0
  122. package/dist/policies/observability/request-log.js +124 -0
  123. package/dist/policies/observability/request-log.js.map +1 -0
  124. package/dist/policies/observability/server-timing.d.ts +35 -0
  125. package/dist/policies/observability/server-timing.js +89 -0
  126. package/dist/policies/observability/server-timing.js.map +1 -0
  127. package/dist/policies/proxy.d.ts +59 -0
  128. package/dist/policies/proxy.js +47 -0
  129. package/dist/policies/proxy.js.map +1 -0
  130. package/dist/policies/resilience/circuit-breaker.d.ts +4 -0
  131. package/dist/policies/resilience/circuit-breaker.js +280 -0
  132. package/dist/policies/resilience/circuit-breaker.js.map +1 -0
  133. package/dist/policies/resilience/latency-injection.d.ts +35 -0
  134. package/dist/policies/resilience/latency-injection.js +26 -0
  135. package/dist/policies/resilience/latency-injection.js.map +1 -0
  136. package/dist/policies/resilience/retry.d.ts +71 -0
  137. package/dist/policies/resilience/retry.js +79 -0
  138. package/dist/policies/resilience/retry.js.map +1 -0
  139. package/dist/policies/resilience/timeout.d.ts +32 -0
  140. package/dist/policies/resilience/timeout.js +46 -0
  141. package/dist/policies/resilience/timeout.js.map +1 -0
  142. package/dist/policies/sdk/define-policy.d.ts +176 -0
  143. package/dist/policies/sdk/define-policy.js +42 -0
  144. package/dist/policies/sdk/define-policy.js.map +1 -0
  145. package/dist/policies/sdk/helpers.d.ts +132 -0
  146. package/dist/policies/sdk/helpers.js +87 -0
  147. package/dist/policies/sdk/helpers.js.map +1 -0
  148. package/dist/policies/sdk/index.d.ts +10 -0
  149. package/dist/policies/sdk/index.js +35 -0
  150. package/dist/policies/sdk/index.js.map +1 -0
  151. package/dist/policies/sdk/priority.d.ts +44 -0
  152. package/dist/policies/sdk/priority.js +36 -0
  153. package/dist/policies/sdk/priority.js.map +1 -0
  154. package/dist/policies/sdk/testing.d.ts +53 -0
  155. package/dist/policies/sdk/testing.js +41 -0
  156. package/dist/policies/sdk/testing.js.map +1 -0
  157. package/dist/policies/sdk/trace.d.ts +73 -0
  158. package/dist/policies/sdk/trace.js +25 -0
  159. package/dist/policies/sdk/trace.js.map +1 -0
  160. package/dist/policies/traffic/cache.d.ts +4 -0
  161. package/dist/policies/traffic/cache.js +224 -0
  162. package/dist/policies/traffic/cache.js.map +1 -0
  163. package/dist/policies/traffic/dynamic-routing.d.ts +54 -0
  164. package/dist/policies/traffic/dynamic-routing.js +36 -0
  165. package/dist/policies/traffic/dynamic-routing.js.map +1 -0
  166. package/dist/policies/traffic/geo-ip-filter.d.ts +37 -0
  167. package/dist/policies/traffic/geo-ip-filter.js +74 -0
  168. package/dist/policies/traffic/geo-ip-filter.js.map +1 -0
  169. package/dist/policies/traffic/http-callout.d.ts +59 -0
  170. package/dist/policies/traffic/http-callout.js +69 -0
  171. package/dist/policies/traffic/http-callout.js.map +1 -0
  172. package/dist/policies/traffic/interrupt.d.ts +46 -0
  173. package/dist/policies/traffic/interrupt.js +38 -0
  174. package/dist/policies/traffic/interrupt.js.map +1 -0
  175. package/dist/policies/traffic/ip-filter.d.ts +47 -0
  176. package/dist/policies/traffic/ip-filter.js +57 -0
  177. package/dist/policies/traffic/ip-filter.js.map +1 -0
  178. package/dist/policies/traffic/json-threat-protection.d.ts +51 -0
  179. package/dist/policies/traffic/json-threat-protection.js +173 -0
  180. package/dist/policies/traffic/json-threat-protection.js.map +1 -0
  181. package/dist/policies/traffic/rate-limit.d.ts +4 -0
  182. package/dist/policies/traffic/rate-limit.js +145 -0
  183. package/dist/policies/traffic/rate-limit.js.map +1 -0
  184. package/dist/policies/traffic/regex-threat-protection.d.ts +54 -0
  185. package/dist/policies/traffic/regex-threat-protection.js +109 -0
  186. package/dist/policies/traffic/regex-threat-protection.js.map +1 -0
  187. package/dist/policies/traffic/request-limit.d.ts +27 -0
  188. package/dist/policies/traffic/request-limit.js +41 -0
  189. package/dist/policies/traffic/request-limit.js.map +1 -0
  190. package/dist/policies/traffic/resource-filter.d.ts +38 -0
  191. package/dist/policies/traffic/resource-filter.js +184 -0
  192. package/dist/policies/traffic/resource-filter.js.map +1 -0
  193. package/dist/policies/traffic/ssl-enforce.d.ts +27 -0
  194. package/dist/policies/traffic/ssl-enforce.js +38 -0
  195. package/dist/policies/traffic/ssl-enforce.js.map +1 -0
  196. package/dist/policies/traffic/traffic-shadow.d.ts +40 -0
  197. package/dist/policies/traffic/traffic-shadow.js +87 -0
  198. package/dist/policies/traffic/traffic-shadow.js.map +1 -0
  199. package/dist/policies/transform/assign-attributes.d.ts +33 -0
  200. package/dist/policies/transform/assign-attributes.js +38 -0
  201. package/dist/policies/transform/assign-attributes.js.map +1 -0
  202. package/dist/policies/transform/assign-content.d.ts +40 -0
  203. package/dist/policies/transform/assign-content.js +185 -0
  204. package/dist/policies/transform/assign-content.js.map +1 -0
  205. package/dist/policies/transform/cors.d.ts +57 -0
  206. package/dist/policies/transform/cors.js +23 -0
  207. package/dist/policies/transform/cors.js.map +1 -0
  208. package/dist/policies/transform/json-validation.d.ts +50 -0
  209. package/dist/policies/transform/json-validation.js +125 -0
  210. package/dist/policies/transform/json-validation.js.map +1 -0
  211. package/dist/policies/transform/override-method.d.ts +33 -0
  212. package/dist/policies/transform/override-method.js +48 -0
  213. package/dist/policies/transform/override-method.js.map +1 -0
  214. package/dist/policies/transform/request-validation.d.ts +59 -0
  215. package/dist/policies/transform/request-validation.js +121 -0
  216. package/dist/policies/transform/request-validation.js.map +1 -0
  217. package/dist/policies/transform/transform.d.ts +75 -0
  218. package/dist/policies/transform/transform.js +116 -0
  219. package/dist/policies/transform/transform.js.map +1 -0
  220. package/dist/policies/types.d.ts +4 -0
  221. package/dist/policies/types.js +1 -0
  222. package/dist/policies/types.js.map +1 -0
  223. package/dist/protocol-2fD3DJrL.d.ts +725 -0
  224. package/dist/utils/cidr.d.ts +58 -0
  225. package/dist/utils/cidr.js +107 -0
  226. package/dist/utils/cidr.js.map +1 -0
  227. package/dist/utils/debug.d.ts +1 -0
  228. package/dist/utils/debug.js +13 -0
  229. package/dist/utils/debug.js.map +1 -0
  230. package/dist/utils/headers.d.ts +68 -0
  231. package/dist/utils/headers.js +25 -0
  232. package/dist/utils/headers.js.map +1 -0
  233. package/dist/utils/ip.d.ts +58 -0
  234. package/dist/utils/ip.js +28 -0
  235. package/dist/utils/ip.js.map +1 -0
  236. package/dist/utils/redact.d.ts +30 -0
  237. package/dist/utils/redact.js +52 -0
  238. package/dist/utils/redact.js.map +1 -0
  239. package/dist/utils/request-id.d.ts +11 -0
  240. package/dist/utils/request-id.js +7 -0
  241. package/dist/utils/request-id.js.map +1 -0
  242. package/dist/utils/timing-safe.d.ts +31 -0
  243. package/dist/utils/timing-safe.js +17 -0
  244. package/dist/utils/timing-safe.js.map +1 -0
  245. package/dist/utils/timing.d.ts +27 -0
  246. package/dist/utils/timing.js +12 -0
  247. package/dist/utils/timing.js.map +1 -0
  248. package/dist/utils/trace-context.d.ts +51 -0
  249. package/dist/utils/trace-context.js +37 -0
  250. package/dist/utils/trace-context.js.map +1 -0
  251. package/package.json +202 -0
@@ -0,0 +1,93 @@
1
+ import { GatewayError } from "../../core/errors";
2
+ import { sanitizeHeaderValue, withModifiedHeaders } from "../../utils/headers";
3
+ import { definePolicy, Priority } from "../sdk";
4
+ const apiKeyAuth = /* @__PURE__ */ definePolicy({
5
+ name: "api-key-auth",
6
+ priority: Priority.AUTH,
7
+ defaults: { headerName: "x-api-key" },
8
+ phases: ["request-headers"],
9
+ handler: async (c, next, { config, debug, trace }) => {
10
+ let key = c.req.header(config.headerName);
11
+ let source = "header";
12
+ if (!key && config.queryParam) {
13
+ const url = new URL(c.req.url);
14
+ key = url.searchParams.get(config.queryParam) ?? void 0;
15
+ source = "query";
16
+ }
17
+ if (!key) {
18
+ trace("rejected", { reason: "missing" });
19
+ throw new GatewayError(401, "unauthorized", "Missing API key");
20
+ }
21
+ const isValid = await config.validate(key);
22
+ if (!isValid) {
23
+ trace("rejected", { reason: "invalid" });
24
+ throw new GatewayError(403, "forbidden", "Invalid API key");
25
+ }
26
+ trace("authenticated", { source });
27
+ if (config.forwardKeyIdentity) {
28
+ const fwd = config.forwardKeyIdentity;
29
+ const identity = await fwd.identityFn(key);
30
+ withModifiedHeaders(c, (headers) => {
31
+ headers.set(fwd.headerName, sanitizeHeaderValue(identity));
32
+ });
33
+ debug(
34
+ `forwarded key identity as ${config.forwardKeyIdentity.headerName}`
35
+ );
36
+ }
37
+ await next();
38
+ },
39
+ evaluate: {
40
+ onRequest: async (input, { config, debug, trace }) => {
41
+ let key = input.headers.get(config.headerName) ?? void 0;
42
+ let source = "header";
43
+ if (!key && config.queryParam) {
44
+ const url = new URL(input.path, "http://localhost");
45
+ key = url.searchParams.get(config.queryParam) ?? void 0;
46
+ source = "query";
47
+ }
48
+ if (!key) {
49
+ trace("rejected", { reason: "missing" });
50
+ return {
51
+ action: "reject",
52
+ status: 401,
53
+ code: "unauthorized",
54
+ message: "Missing API key"
55
+ };
56
+ }
57
+ const isValid = await config.validate(key);
58
+ if (!isValid) {
59
+ trace("rejected", { reason: "invalid" });
60
+ return {
61
+ action: "reject",
62
+ status: 403,
63
+ code: "forbidden",
64
+ message: "Invalid API key"
65
+ };
66
+ }
67
+ trace("authenticated", { source });
68
+ if (config.forwardKeyIdentity) {
69
+ const fwd = config.forwardKeyIdentity;
70
+ const identity = await fwd.identityFn(key);
71
+ debug(
72
+ `forwarded key identity as ${config.forwardKeyIdentity.headerName}`
73
+ );
74
+ return {
75
+ action: "continue",
76
+ mutations: [
77
+ {
78
+ type: "header",
79
+ op: "set",
80
+ name: fwd.headerName,
81
+ value: sanitizeHeaderValue(identity)
82
+ }
83
+ ]
84
+ };
85
+ }
86
+ return { action: "continue" };
87
+ }
88
+ }
89
+ });
90
+ export {
91
+ apiKeyAuth
92
+ };
93
+ //# sourceMappingURL=api-key-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/auth/api-key-auth.ts"],"sourcesContent":["/**\n * API key authentication policy.\n *\n * **Security note:** When implementing `validate`, use\n * {@link timingSafeEqual} from `@vivero/stoma` for constant-time\n * key comparison to prevent timing side-channel attacks.\n *\n * @module api-key-auth\n */\nimport { GatewayError } from \"../../core/errors\";\nimport { sanitizeHeaderValue, withModifiedHeaders } from \"../../utils/headers\";\nimport { definePolicy, Priority } from \"../sdk\";\nimport type { PolicyConfig } from \"../types\";\n\nexport interface ApiKeyAuthConfig extends PolicyConfig {\n /** Header name to read the API key from. Default: \"X-API-Key\" */\n headerName?: string;\n /** Query parameter name as fallback. Default: undefined (disabled) */\n queryParam?: string;\n /** Validator function - return true if the key is valid */\n validate: (key: string) => boolean | Promise<boolean>;\n /**\n * After successful validation, derive an identity string from the key\n * and set it as a request header for upstream consumption.\n *\n * @example\n * ```ts\n * apiKeyAuth({\n * validate: (key) => keys.has(key),\n * forwardKeyIdentity: {\n * headerName: \"x-api-client\",\n * identityFn: (key) => keyToClientMap.get(key) ?? \"unknown\",\n * },\n * });\n * ```\n */\n forwardKeyIdentity?: {\n /** Header name to set on the request. */\n headerName: string;\n /** Derive an identity string from the validated key. Can be async. */\n identityFn: (key: string) => string | Promise<string>;\n };\n}\n\n/**\n * Validate API keys from headers or query parameters.\n *\n * Checks the `X-API-Key` header by default, with an optional query parameter\n * fallback. The `validate` function can be async to support remote key lookups.\n *\n * @param config - API key settings with a required `validate` function.\n * @returns A {@link Policy} at priority 10.\n *\n * @example\n * ```ts\n * // Static key validation\n * apiKeyAuth({\n * validate: (key) => key === env.API_KEY,\n * });\n *\n * // Async validation with query parameter fallback\n * apiKeyAuth({\n * headerName: \"Authorization\",\n * queryParam: \"api_key\",\n * validate: async (key) => {\n * const result = await kv.get(`api-key:${key}`);\n * return result !== null;\n * },\n * });\n * ```\n */\nexport const apiKeyAuth = /*#__PURE__*/ definePolicy<ApiKeyAuthConfig>({\n name: \"api-key-auth\",\n priority: Priority.AUTH,\n defaults: { headerName: \"x-api-key\" },\n phases: [\"request-headers\"],\n handler: async (c, next, { config, debug, trace }) => {\n // Try header first\n let key = c.req.header(config.headerName!);\n let source = \"header\";\n\n // Fall back to query parameter if configured\n if (!key && config.queryParam) {\n const url = new URL(c.req.url);\n key = url.searchParams.get(config.queryParam) ?? undefined;\n source = \"query\";\n }\n\n if (!key) {\n trace(\"rejected\", { reason: \"missing\" });\n throw new GatewayError(401, \"unauthorized\", \"Missing API key\");\n }\n\n const isValid = await config.validate(key);\n if (!isValid) {\n trace(\"rejected\", { reason: \"invalid\" });\n throw new GatewayError(403, \"forbidden\", \"Invalid API key\");\n }\n\n trace(\"authenticated\", { source });\n\n // Forward key identity as a request header if configured\n if (config.forwardKeyIdentity) {\n const fwd = config.forwardKeyIdentity;\n const identity = await fwd.identityFn(key);\n withModifiedHeaders(c, (headers) => {\n headers.set(fwd.headerName, sanitizeHeaderValue(identity));\n });\n debug(\n `forwarded key identity as ${config.forwardKeyIdentity.headerName}`\n );\n }\n\n await next();\n },\n evaluate: {\n onRequest: async (input, { config, debug, trace }) => {\n // Try header first\n let key = input.headers.get(config.headerName!) ?? undefined;\n let source = \"header\";\n\n // Fall back to query parameter if configured\n if (!key && config.queryParam) {\n const url = new URL(input.path, \"http://localhost\");\n key = url.searchParams.get(config.queryParam!) ?? undefined;\n source = \"query\";\n }\n\n if (!key) {\n trace(\"rejected\", { reason: \"missing\" });\n return {\n action: \"reject\",\n status: 401,\n code: \"unauthorized\",\n message: \"Missing API key\",\n };\n }\n\n const isValid = await config.validate(key);\n if (!isValid) {\n trace(\"rejected\", { reason: \"invalid\" });\n return {\n action: \"reject\",\n status: 403,\n code: \"forbidden\",\n message: \"Invalid API key\",\n };\n }\n\n trace(\"authenticated\", { source });\n\n // Forward key identity as a header if configured\n if (config.forwardKeyIdentity) {\n const fwd = config.forwardKeyIdentity;\n const identity = await fwd.identityFn(key);\n debug(\n `forwarded key identity as ${config.forwardKeyIdentity.headerName}`\n );\n return {\n action: \"continue\",\n mutations: [\n {\n type: \"header\",\n op: \"set\",\n name: fwd.headerName,\n value: sanitizeHeaderValue(identity),\n },\n ],\n };\n }\n\n return { action: \"continue\" };\n },\n },\n});\n"],"mappings":"AASA,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB,2BAA2B;AACzD,SAAS,cAAc,gBAAgB;AA4DhC,MAAM,aAA2B,6BAA+B;AAAA,EACrE,MAAM;AAAA,EACN,UAAU,SAAS;AAAA,EACnB,UAAU,EAAE,YAAY,YAAY;AAAA,EACpC,QAAQ,CAAC,iBAAiB;AAAA,EAC1B,SAAS,OAAO,GAAG,MAAM,EAAE,QAAQ,OAAO,MAAM,MAAM;AAEpD,QAAI,MAAM,EAAE,IAAI,OAAO,OAAO,UAAW;AACzC,QAAI,SAAS;AAGb,QAAI,CAAC,OAAO,OAAO,YAAY;AAC7B,YAAM,MAAM,IAAI,IAAI,EAAE,IAAI,GAAG;AAC7B,YAAM,IAAI,aAAa,IAAI,OAAO,UAAU,KAAK;AACjD,eAAS;AAAA,IACX;AAEA,QAAI,CAAC,KAAK;AACR,YAAM,YAAY,EAAE,QAAQ,UAAU,CAAC;AACvC,YAAM,IAAI,aAAa,KAAK,gBAAgB,iBAAiB;AAAA,IAC/D;AAEA,UAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AACzC,QAAI,CAAC,SAAS;AACZ,YAAM,YAAY,EAAE,QAAQ,UAAU,CAAC;AACvC,YAAM,IAAI,aAAa,KAAK,aAAa,iBAAiB;AAAA,IAC5D;AAEA,UAAM,iBAAiB,EAAE,OAAO,CAAC;AAGjC,QAAI,OAAO,oBAAoB;AAC7B,YAAM,MAAM,OAAO;AACnB,YAAM,WAAW,MAAM,IAAI,WAAW,GAAG;AACzC,0BAAoB,GAAG,CAAC,YAAY;AAClC,gBAAQ,IAAI,IAAI,YAAY,oBAAoB,QAAQ,CAAC;AAAA,MAC3D,CAAC;AACD;AAAA,QACE,6BAA6B,OAAO,mBAAmB,UAAU;AAAA,MACnE;AAAA,IACF;AAEA,UAAM,KAAK;AAAA,EACb;AAAA,EACA,UAAU;AAAA,IACR,WAAW,OAAO,OAAO,EAAE,QAAQ,OAAO,MAAM,MAAM;AAEpD,UAAI,MAAM,MAAM,QAAQ,IAAI,OAAO,UAAW,KAAK;AACnD,UAAI,SAAS;AAGb,UAAI,CAAC,OAAO,OAAO,YAAY;AAC7B,cAAM,MAAM,IAAI,IAAI,MAAM,MAAM,kBAAkB;AAClD,cAAM,IAAI,aAAa,IAAI,OAAO,UAAW,KAAK;AAClD,iBAAS;AAAA,MACX;AAEA,UAAI,CAAC,KAAK;AACR,cAAM,YAAY,EAAE,QAAQ,UAAU,CAAC;AACvC,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AACzC,UAAI,CAAC,SAAS;AACZ,cAAM,YAAY,EAAE,QAAQ,UAAU,CAAC;AACvC,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,iBAAiB,EAAE,OAAO,CAAC;AAGjC,UAAI,OAAO,oBAAoB;AAC7B,cAAM,MAAM,OAAO;AACnB,cAAM,WAAW,MAAM,IAAI,WAAW,GAAG;AACzC;AAAA,UACE,6BAA6B,OAAO,mBAAmB,UAAU;AAAA,QACnE;AACA,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,WAAW;AAAA,YACT;AAAA,cACE,MAAM;AAAA,cACN,IAAI;AAAA,cACJ,MAAM,IAAI;AAAA,cACV,OAAO,oBAAoB,QAAQ;AAAA,YACrC;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,aAAO,EAAE,QAAQ,WAAW;AAAA,IAC9B;AAAA,EACF;AACF,CAAC;","names":[]}
@@ -0,0 +1,33 @@
1
+ import { g as PolicyConfig, P as Policy } from '../../protocol-2fD3DJrL.js';
2
+ import { Context } from 'hono';
3
+ import '../sdk/trace.js';
4
+ import '@vivero/stoma-core';
5
+
6
+ interface BasicAuthConfig extends PolicyConfig {
7
+ /** Validate username/password. Return true if valid. */
8
+ validate: (username: string, password: string, c: Context) => boolean | Promise<boolean>;
9
+ /** Realm for the WWW-Authenticate header. Default: "Restricted" */
10
+ realm?: string;
11
+ }
12
+ /**
13
+ * Basic Authentication policy - validate base64-encoded credentials.
14
+ *
15
+ * Sends a `WWW-Authenticate` header on failure to prompt browser credential dialogs.
16
+ * The realm is sanitized to prevent header injection.
17
+ *
18
+ * @param config - Validation function and optional realm name.
19
+ * @returns A {@link Policy} at priority 10.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * basicAuth({
24
+ * realm: "Admin Area",
25
+ * validate: async (username, password) => {
26
+ * return username === "admin" && password === env.ADMIN_PASSWORD;
27
+ * },
28
+ * });
29
+ * ```
30
+ */
31
+ declare const basicAuth: (config: BasicAuthConfig) => Policy;
32
+
33
+ export { type BasicAuthConfig, basicAuth };
@@ -0,0 +1,96 @@
1
+ import { GatewayError } from "../../core/errors";
2
+ import { escapeHeaderValue, sanitizeHeaderValue } from "../../utils/headers";
3
+ import { definePolicy, Priority } from "../sdk";
4
+ const basicAuth = /* @__PURE__ */ definePolicy({
5
+ name: "basic-auth",
6
+ priority: Priority.AUTH,
7
+ defaults: { realm: "Restricted" },
8
+ phases: ["request-headers"],
9
+ handler: async (c, next, { config }) => {
10
+ const realm = escapeHeaderValue(
11
+ sanitizeHeaderValue(config.realm ?? "Restricted")
12
+ );
13
+ const authHeader = c.req.header("authorization");
14
+ if (!authHeader || !authHeader.startsWith("Basic ")) {
15
+ c.header("www-authenticate", `Basic realm="${realm}"`);
16
+ throw new GatewayError(
17
+ 401,
18
+ "unauthorized",
19
+ "Basic authentication required"
20
+ );
21
+ }
22
+ let username;
23
+ let password;
24
+ try {
25
+ const decoded = atob(authHeader.slice(6));
26
+ const colonIndex = decoded.indexOf(":");
27
+ if (colonIndex === -1) {
28
+ throw new Error("Invalid format");
29
+ }
30
+ username = decoded.slice(0, colonIndex);
31
+ password = decoded.slice(colonIndex + 1);
32
+ } catch {
33
+ throw new GatewayError(
34
+ 401,
35
+ "unauthorized",
36
+ "Malformed Basic authentication header"
37
+ );
38
+ }
39
+ const isValid = await config.validate(username, password, c);
40
+ if (!isValid) {
41
+ c.header("www-authenticate", `Basic realm="${realm}"`);
42
+ throw new GatewayError(403, "forbidden", "Invalid credentials");
43
+ }
44
+ await next();
45
+ },
46
+ evaluate: {
47
+ onRequest: async (input, { config }) => {
48
+ const realm = escapeHeaderValue(
49
+ sanitizeHeaderValue(config.realm ?? "Restricted")
50
+ );
51
+ const authHeader = input.headers.get("authorization");
52
+ if (!authHeader || !authHeader.startsWith("Basic ")) {
53
+ return {
54
+ action: "reject",
55
+ status: 401,
56
+ code: "unauthorized",
57
+ message: "Basic authentication required",
58
+ headers: { "www-authenticate": `Basic realm="${realm}"` }
59
+ };
60
+ }
61
+ let username;
62
+ let password;
63
+ try {
64
+ const decoded = atob(authHeader.slice(6));
65
+ const colonIndex = decoded.indexOf(":");
66
+ if (colonIndex === -1) {
67
+ throw new Error("Invalid format");
68
+ }
69
+ username = decoded.slice(0, colonIndex);
70
+ password = decoded.slice(colonIndex + 1);
71
+ } catch {
72
+ return {
73
+ action: "reject",
74
+ status: 401,
75
+ code: "unauthorized",
76
+ message: "Malformed Basic authentication header"
77
+ };
78
+ }
79
+ const isValid = await config.validate(username, password, {});
80
+ if (!isValid) {
81
+ return {
82
+ action: "reject",
83
+ status: 403,
84
+ code: "forbidden",
85
+ message: "Invalid credentials",
86
+ headers: { "www-authenticate": `Basic realm="${realm}"` }
87
+ };
88
+ }
89
+ return { action: "continue" };
90
+ }
91
+ }
92
+ });
93
+ export {
94
+ basicAuth
95
+ };
96
+ //# sourceMappingURL=basic-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/auth/basic-auth.ts"],"sourcesContent":["/**\n * HTTP Basic authentication policy.\n *\n * @module basic-auth\n */\n\nimport type { Context } from \"hono\";\nimport { GatewayError } from \"../../core/errors\";\nimport { escapeHeaderValue, sanitizeHeaderValue } from \"../../utils/headers\";\nimport { definePolicy, Priority } from \"../sdk\";\nimport type { PolicyConfig } from \"../types\";\n\nexport interface BasicAuthConfig extends PolicyConfig {\n /** Validate username/password. Return true if valid. */\n validate: (\n username: string,\n password: string,\n c: Context\n ) => boolean | Promise<boolean>;\n /** Realm for the WWW-Authenticate header. Default: \"Restricted\" */\n realm?: string;\n}\n\n/**\n * Basic Authentication policy - validate base64-encoded credentials.\n *\n * Sends a `WWW-Authenticate` header on failure to prompt browser credential dialogs.\n * The realm is sanitized to prevent header injection.\n *\n * @param config - Validation function and optional realm name.\n * @returns A {@link Policy} at priority 10.\n *\n * @example\n * ```ts\n * basicAuth({\n * realm: \"Admin Area\",\n * validate: async (username, password) => {\n * return username === \"admin\" && password === env.ADMIN_PASSWORD;\n * },\n * });\n * ```\n */\nexport const basicAuth = /*#__PURE__*/ definePolicy<BasicAuthConfig>({\n name: \"basic-auth\",\n priority: Priority.AUTH,\n defaults: { realm: \"Restricted\" },\n phases: [\"request-headers\"],\n handler: async (c, next, { config }) => {\n // Sanitize realm to prevent header injection (escape quotes, strip control chars)\n const realm = escapeHeaderValue(\n sanitizeHeaderValue(config.realm ?? \"Restricted\")\n );\n\n const authHeader = c.req.header(\"authorization\");\n\n if (!authHeader || !authHeader.startsWith(\"Basic \")) {\n c.header(\"www-authenticate\", `Basic realm=\"${realm}\"`);\n throw new GatewayError(\n 401,\n \"unauthorized\",\n \"Basic authentication required\"\n );\n }\n\n let username: string;\n let password: string;\n try {\n const decoded = atob(authHeader.slice(6));\n const colonIndex = decoded.indexOf(\":\");\n if (colonIndex === -1) {\n throw new Error(\"Invalid format\");\n }\n username = decoded.slice(0, colonIndex);\n password = decoded.slice(colonIndex + 1);\n } catch {\n throw new GatewayError(\n 401,\n \"unauthorized\",\n \"Malformed Basic authentication header\"\n );\n }\n\n const isValid = await config.validate(username, password, c);\n if (!isValid) {\n c.header(\"www-authenticate\", `Basic realm=\"${realm}\"`);\n throw new GatewayError(403, \"forbidden\", \"Invalid credentials\");\n }\n\n await next();\n },\n evaluate: {\n onRequest: async (input, { config }) => {\n // Sanitize realm to prevent header injection\n const realm = escapeHeaderValue(\n sanitizeHeaderValue(config.realm ?? \"Restricted\")\n );\n\n const authHeader = input.headers.get(\"authorization\");\n\n if (!authHeader || !authHeader.startsWith(\"Basic \")) {\n return {\n action: \"reject\",\n status: 401,\n code: \"unauthorized\",\n message: \"Basic authentication required\",\n headers: { \"www-authenticate\": `Basic realm=\"${realm}\"` },\n };\n }\n\n let username: string;\n let password: string;\n try {\n const decoded = atob(authHeader.slice(6));\n const colonIndex = decoded.indexOf(\":\");\n if (colonIndex === -1) {\n throw new Error(\"Invalid format\");\n }\n username = decoded.slice(0, colonIndex);\n password = decoded.slice(colonIndex + 1);\n } catch {\n return {\n action: \"reject\",\n status: 401,\n code: \"unauthorized\",\n message: \"Malformed Basic authentication header\",\n };\n }\n\n // validate function requires Hono Context - use a no-op for evaluate\n // Users needing evaluate should use a different auth policy\n const isValid = await config.validate(username, password, {} as Context);\n if (!isValid) {\n return {\n action: \"reject\",\n status: 403,\n code: \"forbidden\",\n message: \"Invalid credentials\",\n headers: { \"www-authenticate\": `Basic realm=\"${realm}\"` },\n };\n }\n\n return { action: \"continue\" };\n },\n },\n});\n"],"mappings":"AAOA,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB,2BAA2B;AACvD,SAAS,cAAc,gBAAgB;AAiChC,MAAM,YAA0B,6BAA8B;AAAA,EACnE,MAAM;AAAA,EACN,UAAU,SAAS;AAAA,EACnB,UAAU,EAAE,OAAO,aAAa;AAAA,EAChC,QAAQ,CAAC,iBAAiB;AAAA,EAC1B,SAAS,OAAO,GAAG,MAAM,EAAE,OAAO,MAAM;AAEtC,UAAM,QAAQ;AAAA,MACZ,oBAAoB,OAAO,SAAS,YAAY;AAAA,IAClD;AAEA,UAAM,aAAa,EAAE,IAAI,OAAO,eAAe;AAE/C,QAAI,CAAC,cAAc,CAAC,WAAW,WAAW,QAAQ,GAAG;AACnD,QAAE,OAAO,oBAAoB,gBAAgB,KAAK,GAAG;AACrD,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,YAAM,UAAU,KAAK,WAAW,MAAM,CAAC,CAAC;AACxC,YAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,UAAI,eAAe,IAAI;AACrB,cAAM,IAAI,MAAM,gBAAgB;AAAA,MAClC;AACA,iBAAW,QAAQ,MAAM,GAAG,UAAU;AACtC,iBAAW,QAAQ,MAAM,aAAa,CAAC;AAAA,IACzC,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,OAAO,SAAS,UAAU,UAAU,CAAC;AAC3D,QAAI,CAAC,SAAS;AACZ,QAAE,OAAO,oBAAoB,gBAAgB,KAAK,GAAG;AACrD,YAAM,IAAI,aAAa,KAAK,aAAa,qBAAqB;AAAA,IAChE;AAEA,UAAM,KAAK;AAAA,EACb;AAAA,EACA,UAAU;AAAA,IACR,WAAW,OAAO,OAAO,EAAE,OAAO,MAAM;AAEtC,YAAM,QAAQ;AAAA,QACZ,oBAAoB,OAAO,SAAS,YAAY;AAAA,MAClD;AAEA,YAAM,aAAa,MAAM,QAAQ,IAAI,eAAe;AAEpD,UAAI,CAAC,cAAc,CAAC,WAAW,WAAW,QAAQ,GAAG;AACnD,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS,EAAE,oBAAoB,gBAAgB,KAAK,IAAI;AAAA,QAC1D;AAAA,MACF;AAEA,UAAI;AACJ,UAAI;AACJ,UAAI;AACF,cAAM,UAAU,KAAK,WAAW,MAAM,CAAC,CAAC;AACxC,cAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,YAAI,eAAe,IAAI;AACrB,gBAAM,IAAI,MAAM,gBAAgB;AAAA,QAClC;AACA,mBAAW,QAAQ,MAAM,GAAG,UAAU;AACtC,mBAAW,QAAQ,MAAM,aAAa,CAAC;AAAA,MACzC,QAAQ;AACN,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAIA,YAAM,UAAU,MAAM,OAAO,SAAS,UAAU,UAAU,CAAC,CAAY;AACvE,UAAI,CAAC,SAAS;AACZ,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS,EAAE,oBAAoB,gBAAgB,KAAK,IAAI;AAAA,QAC1D;AAAA,MACF;AAEA,aAAO,EAAE,QAAQ,WAAW;AAAA,IAC9B;AAAA,EACF;AACF,CAAC;","names":[]}
@@ -0,0 +1,29 @@
1
+ /** Decode a base64url string to a UTF-8 string. */
2
+ declare function base64UrlDecode(str: string): string;
3
+ /** Decode a base64url string to a Uint8Array. */
4
+ declare function base64UrlToBuffer(str: string): Uint8Array;
5
+ /** Encode a Uint8Array to a base64url string (no padding). */
6
+ declare function base64UrlEncodeBytes(data: Uint8Array): string;
7
+ /** Encode a string to a base64url string (no padding). */
8
+ declare function base64UrlEncodeString(str: string): string;
9
+ /** Map a JWT/JWS HMAC algorithm string to a WebCrypto hash name. Returns `null` for unsupported algorithms. */
10
+ declare function hmacAlgorithm(alg: string): string | null;
11
+ /** Map a JWT/JWS RSA algorithm string to WebCrypto import parameters. Returns `null` for unsupported algorithms. */
12
+ declare function rsaAlgorithm(alg: string): {
13
+ name: string;
14
+ hash: string;
15
+ } | null;
16
+ /**
17
+ * Fetch and cache a JWKS endpoint.
18
+ *
19
+ * @param url - The JWKS endpoint URL.
20
+ * @param cacheTtlMs - Cache TTL in milliseconds. Default: 300000 (5 minutes).
21
+ * @param timeoutMs - Fetch timeout in milliseconds. Default: 10000 (10 seconds).
22
+ * @returns An array of JWK keys from the endpoint.
23
+ * @throws {GatewayError} 502 on fetch failure or timeout.
24
+ */
25
+ declare function fetchJwks(url: string, cacheTtlMs?: number, timeoutMs?: number): Promise<JsonWebKey[]>;
26
+ /** Clear the unified JWKS cache. Exported for testing. */
27
+ declare function clearJwksCache(): void;
28
+
29
+ export { base64UrlDecode, base64UrlEncodeBytes, base64UrlEncodeString, base64UrlToBuffer, clearJwksCache, fetchJwks, hmacAlgorithm, rsaAlgorithm };
@@ -0,0 +1,100 @@
1
+ import { GatewayError } from "../../core/errors";
2
+ function base64UrlDecode(str) {
3
+ const padded = str.replace(/-/g, "+").replace(/_/g, "/");
4
+ const paddedLength = padded + "=".repeat((4 - padded.length % 4) % 4);
5
+ return atob(paddedLength);
6
+ }
7
+ function base64UrlToBuffer(str) {
8
+ const binary = base64UrlDecode(str);
9
+ const bytes = new Uint8Array(binary.length);
10
+ for (let i = 0; i < binary.length; i++) {
11
+ bytes[i] = binary.charCodeAt(i);
12
+ }
13
+ return bytes;
14
+ }
15
+ function base64UrlEncodeBytes(data) {
16
+ let binary = "";
17
+ for (let i = 0; i < data.length; i++) {
18
+ binary += String.fromCharCode(data[i]);
19
+ }
20
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
21
+ }
22
+ function base64UrlEncodeString(str) {
23
+ return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
24
+ }
25
+ function hmacAlgorithm(alg) {
26
+ switch (alg) {
27
+ case "HS256":
28
+ return "SHA-256";
29
+ case "HS384":
30
+ return "SHA-384";
31
+ case "HS512":
32
+ return "SHA-512";
33
+ default:
34
+ return null;
35
+ }
36
+ }
37
+ function rsaAlgorithm(alg) {
38
+ switch (alg) {
39
+ case "RS256":
40
+ return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
41
+ case "RS384":
42
+ return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384" };
43
+ case "RS512":
44
+ return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-512" };
45
+ default:
46
+ return null;
47
+ }
48
+ }
49
+ const DEFAULT_JWKS_CACHE_TTL_MS = 3e5;
50
+ const DEFAULT_JWKS_TIMEOUT_MS = 1e4;
51
+ const jwksCache = /* @__PURE__ */ new Map();
52
+ async function fetchJwks(url, cacheTtlMs, timeoutMs) {
53
+ const cached = jwksCache.get(url);
54
+ if (cached && cached.expiresAt > Date.now()) {
55
+ return cached.keys;
56
+ }
57
+ const timeout = timeoutMs ?? DEFAULT_JWKS_TIMEOUT_MS;
58
+ let response;
59
+ try {
60
+ response = await fetch(url, { signal: AbortSignal.timeout(timeout) });
61
+ } catch (err) {
62
+ if (err instanceof DOMException && err.name === "TimeoutError") {
63
+ throw new GatewayError(
64
+ 502,
65
+ "jwks_error",
66
+ `JWKS fetch timed out after ${timeout}ms: ${url}`
67
+ );
68
+ }
69
+ throw new GatewayError(
70
+ 502,
71
+ "jwks_error",
72
+ `Failed to fetch JWKS from ${url}: ${err instanceof Error ? err.message : String(err)}`
73
+ );
74
+ }
75
+ if (!response.ok) {
76
+ throw new GatewayError(
77
+ 502,
78
+ "jwks_error",
79
+ `Failed to fetch JWKS from ${url}: ${response.status}`
80
+ );
81
+ }
82
+ const ttl = cacheTtlMs ?? DEFAULT_JWKS_CACHE_TTL_MS;
83
+ const data = await response.json();
84
+ jwksCache.set(url, { keys: data.keys, expiresAt: Date.now() + ttl });
85
+ return data.keys;
86
+ }
87
+ function clearJwksCache() {
88
+ jwksCache.clear();
89
+ }
90
+ export {
91
+ base64UrlDecode,
92
+ base64UrlEncodeBytes,
93
+ base64UrlEncodeString,
94
+ base64UrlToBuffer,
95
+ clearJwksCache,
96
+ fetchJwks,
97
+ hmacAlgorithm,
98
+ rsaAlgorithm
99
+ };
100
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/auth/crypto.ts"],"sourcesContent":["/**\n * Shared cryptographic utilities for auth policies (JWT, JWS).\n *\n * Consolidates base64url encoding/decoding, HMAC/RSA algorithm mapping,\n * and JWKS fetching with a unified cache. Extracted to avoid duplication\n * between jwt-auth and jws.\n *\n * @module auth/crypto\n */\nimport { GatewayError } from \"../../core/errors\";\n\n// --- Base64URL ---\n\n/** Decode a base64url string to a UTF-8 string. */\nexport function base64UrlDecode(str: string): string {\n const padded = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const paddedLength = padded + \"=\".repeat((4 - (padded.length % 4)) % 4);\n return atob(paddedLength);\n}\n\n/** Decode a base64url string to a Uint8Array. */\nexport function base64UrlToBuffer(str: string): Uint8Array {\n const binary = base64UrlDecode(str);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/** Encode a Uint8Array to a base64url string (no padding). */\nexport function base64UrlEncodeBytes(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\n/** Encode a string to a base64url string (no padding). */\nexport function base64UrlEncodeString(str: string): string {\n return btoa(str).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n// --- Algorithm mapping ---\n\n/** Map a JWT/JWS HMAC algorithm string to a WebCrypto hash name. Returns `null` for unsupported algorithms. */\nexport function hmacAlgorithm(alg: string): string | null {\n switch (alg) {\n case \"HS256\":\n return \"SHA-256\";\n case \"HS384\":\n return \"SHA-384\";\n case \"HS512\":\n return \"SHA-512\";\n default:\n return null;\n }\n}\n\n/** Map a JWT/JWS RSA algorithm string to WebCrypto import parameters. Returns `null` for unsupported algorithms. */\nexport function rsaAlgorithm(\n alg: string\n): { name: string; hash: string } | null {\n switch (alg) {\n case \"RS256\":\n return { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-256\" };\n case \"RS384\":\n return { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-384\" };\n case \"RS512\":\n return { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-512\" };\n default:\n return null;\n }\n}\n\n// --- JWKS fetching + cache ---\n\nconst DEFAULT_JWKS_CACHE_TTL_MS = 300_000; // 5 minutes\nconst DEFAULT_JWKS_TIMEOUT_MS = 10_000; // 10 seconds\n\n/** Unified JWKS cache shared between jwt-auth and jws. */\nconst jwksCache = new Map<string, { keys: JsonWebKey[]; expiresAt: number }>();\n\n/**\n * Fetch and cache a JWKS endpoint.\n *\n * @param url - The JWKS endpoint URL.\n * @param cacheTtlMs - Cache TTL in milliseconds. Default: 300000 (5 minutes).\n * @param timeoutMs - Fetch timeout in milliseconds. Default: 10000 (10 seconds).\n * @returns An array of JWK keys from the endpoint.\n * @throws {GatewayError} 502 on fetch failure or timeout.\n */\nexport async function fetchJwks(\n url: string,\n cacheTtlMs?: number,\n timeoutMs?: number\n): Promise<JsonWebKey[]> {\n const cached = jwksCache.get(url);\n if (cached && cached.expiresAt > Date.now()) {\n return cached.keys;\n }\n\n const timeout = timeoutMs ?? DEFAULT_JWKS_TIMEOUT_MS;\n let response: Response;\n try {\n response = await fetch(url, { signal: AbortSignal.timeout(timeout) });\n } catch (err) {\n if (err instanceof DOMException && err.name === \"TimeoutError\") {\n throw new GatewayError(\n 502,\n \"jwks_error\",\n `JWKS fetch timed out after ${timeout}ms: ${url}`\n );\n }\n throw new GatewayError(\n 502,\n \"jwks_error\",\n `Failed to fetch JWKS from ${url}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n if (!response.ok) {\n throw new GatewayError(\n 502,\n \"jwks_error\",\n `Failed to fetch JWKS from ${url}: ${response.status}`\n );\n }\n\n const ttl = cacheTtlMs ?? DEFAULT_JWKS_CACHE_TTL_MS;\n const data = (await response.json()) as { keys: JsonWebKey[] };\n jwksCache.set(url, { keys: data.keys, expiresAt: Date.now() + ttl });\n return data.keys;\n}\n\n/** Clear the unified JWKS cache. Exported for testing. */\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n"],"mappings":"AASA,SAAS,oBAAoB;AAKtB,SAAS,gBAAgB,KAAqB;AACnD,QAAM,SAAS,IAAI,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACvD,QAAM,eAAe,SAAS,IAAI,QAAQ,IAAK,OAAO,SAAS,KAAM,CAAC;AACtE,SAAO,KAAK,YAAY;AAC1B;AAGO,SAAS,kBAAkB,KAAyB;AACzD,QAAM,SAAS,gBAAgB,GAAG;AAClC,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AACA,SAAO;AACT;AAGO,SAAS,qBAAqB,MAA0B;AAC7D,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,cAAU,OAAO,aAAa,KAAK,CAAC,CAAC;AAAA,EACvC;AACA,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAGO,SAAS,sBAAsB,KAAqB;AACzD,SAAO,KAAK,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC5E;AAKO,SAAS,cAAc,KAA4B;AACxD,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAGO,SAAS,aACd,KACuC;AACvC,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,aAAO,EAAE,MAAM,qBAAqB,MAAM,UAAU;AAAA,IACtD,KAAK;AACH,aAAO,EAAE,MAAM,qBAAqB,MAAM,UAAU;AAAA,IACtD,KAAK;AACH,aAAO,EAAE,MAAM,qBAAqB,MAAM,UAAU;AAAA,IACtD;AACE,aAAO;AAAA,EACX;AACF;AAIA,MAAM,4BAA4B;AAClC,MAAM,0BAA0B;AAGhC,MAAM,YAAY,oBAAI,IAAuD;AAW7E,eAAsB,UACpB,KACA,YACA,WACuB;AACvB,QAAM,SAAS,UAAU,IAAI,GAAG;AAChC,MAAI,UAAU,OAAO,YAAY,KAAK,IAAI,GAAG;AAC3C,WAAO,OAAO;AAAA,EAChB;AAEA,QAAM,UAAU,aAAa;AAC7B,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,OAAO,EAAE,CAAC;AAAA,EACtE,SAAS,KAAK;AACZ,QAAI,eAAe,gBAAgB,IAAI,SAAS,gBAAgB;AAC9D,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA,8BAA8B,OAAO,OAAO,GAAG;AAAA,MACjD;AAAA,IACF;AACA,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,6BAA6B,GAAG,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACvF;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,6BAA6B,GAAG,KAAK,SAAS,MAAM;AAAA,IACtD;AAAA,EACF;AAEA,QAAM,MAAM,cAAc;AAC1B,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,YAAU,IAAI,KAAK,EAAE,MAAM,KAAK,MAAM,WAAW,KAAK,IAAI,IAAI,IAAI,CAAC;AACnE,SAAO,KAAK;AACd;AAGO,SAAS,iBAAuB;AACrC,YAAU,MAAM;AAClB;","names":[]}
@@ -0,0 +1,30 @@
1
+ import { g as PolicyConfig, P as Policy } from '../../protocol-2fD3DJrL.js';
2
+ import 'hono';
3
+ import '../sdk/trace.js';
4
+ import '@vivero/stoma-core';
5
+
6
+ interface GenerateHttpSignatureConfig extends PolicyConfig {
7
+ /** Key identifier included in signature parameters. */
8
+ keyId: string;
9
+ /** HMAC secret for signing. */
10
+ secret?: string;
11
+ /** RSA private key as JWK. */
12
+ privateKey?: JsonWebKey;
13
+ /** Signing algorithm identifier (e.g. "hmac-sha256", "rsa-pss-sha512", "rsa-v1_5-sha256"). */
14
+ algorithm: string;
15
+ /** Components to include in signature. Default: ["@method", "@path", "@authority"]. */
16
+ components?: string[];
17
+ /** Signature header name. Default: "Signature". */
18
+ signatureHeaderName?: string;
19
+ /** Signature-Input header name. Default: "Signature-Input". */
20
+ signatureInputHeaderName?: string;
21
+ /** Signature label. Default: "sig1". */
22
+ label?: string;
23
+ /** Signature expiry in seconds from creation. Optional. */
24
+ expires?: number;
25
+ /** Include a nonce parameter. Default: false. */
26
+ nonce?: boolean;
27
+ }
28
+ declare const generateHttpSignature: (config: GenerateHttpSignatureConfig) => Policy;
29
+
30
+ export { type GenerateHttpSignatureConfig, generateHttpSignature };
@@ -0,0 +1,79 @@
1
+ import { GatewayError } from "../../core/errors";
2
+ import { withModifiedHeaders } from "../../utils/headers";
3
+ import { definePolicy, Priority } from "../sdk";
4
+ import {
5
+ algorithmToCrypto,
6
+ buildSignatureBase,
7
+ buildSignatureParams,
8
+ importSigningKey,
9
+ toBase64
10
+ } from "./http-signature-base";
11
+ const generateHttpSignature = /* @__PURE__ */ definePolicy({
12
+ name: "generate-http-signature",
13
+ priority: Priority.PROXY,
14
+ defaults: {
15
+ components: ["@method", "@path", "@authority"],
16
+ signatureHeaderName: "Signature",
17
+ signatureInputHeaderName: "Signature-Input",
18
+ label: "sig1",
19
+ nonce: false
20
+ },
21
+ handler: async (c, _next, { config, debug }) => {
22
+ if (!config.secret && !config.privateKey) {
23
+ throw new GatewayError(
24
+ 500,
25
+ "config_error",
26
+ "generateHttpSignature requires either 'secret' or 'privateKey'"
27
+ );
28
+ }
29
+ const components = config.components;
30
+ const label = config.label;
31
+ const created = Math.floor(Date.now() / 1e3);
32
+ const paramsObj = {
33
+ created,
34
+ keyId: config.keyId,
35
+ algorithm: config.algorithm
36
+ };
37
+ if (config.expires !== void 0) {
38
+ paramsObj.expires = created + config.expires;
39
+ }
40
+ if (config.nonce) {
41
+ paramsObj.nonce = crypto.randomUUID().replace(/-/g, "");
42
+ }
43
+ const signatureParamsStr = buildSignatureParams(components, paramsObj);
44
+ const signatureBase = buildSignatureBase(
45
+ components,
46
+ signatureParamsStr,
47
+ c.req.raw
48
+ );
49
+ debug(
50
+ `signing with ${config.algorithm}, components: ${components.join(", ")}`
51
+ );
52
+ const key = await importSigningKey(
53
+ config.algorithm,
54
+ config.secret,
55
+ config.privateKey
56
+ );
57
+ const { signAlg } = algorithmToCrypto(config.algorithm);
58
+ const encoder = new TextEncoder();
59
+ const signatureBytes = await crypto.subtle.sign(
60
+ signAlg,
61
+ key,
62
+ encoder.encode(signatureBase)
63
+ );
64
+ const signatureB64 = toBase64(signatureBytes);
65
+ withModifiedHeaders(c, (headers) => {
66
+ headers.set(
67
+ config.signatureInputHeaderName,
68
+ `${label}=${signatureParamsStr}`
69
+ );
70
+ headers.set(config.signatureHeaderName, `${label}=:${signatureB64}:`);
71
+ });
72
+ debug("signature headers attached");
73
+ await _next();
74
+ }
75
+ });
76
+ export {
77
+ generateHttpSignature
78
+ };
79
+ //# sourceMappingURL=generate-http-signature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/auth/generate-http-signature.ts"],"sourcesContent":["/**\n * Generate HTTP Message Signatures per RFC 9421.\n *\n * Signs outbound requests by computing a signature over selected\n * request components and attaching `Signature` + `Signature-Input` headers.\n *\n * @module generate-http-signature\n */\n\nimport { GatewayError } from \"../../core/errors\";\nimport { withModifiedHeaders } from \"../../utils/headers\";\nimport { definePolicy, Priority } from \"../sdk\";\nimport type { PolicyConfig } from \"../types\";\nimport {\n algorithmToCrypto,\n buildSignatureBase,\n buildSignatureParams,\n importSigningKey,\n toBase64,\n} from \"./http-signature-base\";\n\nexport interface GenerateHttpSignatureConfig extends PolicyConfig {\n /** Key identifier included in signature parameters. */\n keyId: string;\n /** HMAC secret for signing. */\n secret?: string;\n /** RSA private key as JWK. */\n privateKey?: JsonWebKey;\n /** Signing algorithm identifier (e.g. \"hmac-sha256\", \"rsa-pss-sha512\", \"rsa-v1_5-sha256\"). */\n algorithm: string;\n /** Components to include in signature. Default: [\"@method\", \"@path\", \"@authority\"]. */\n components?: string[];\n /** Signature header name. Default: \"Signature\". */\n signatureHeaderName?: string;\n /** Signature-Input header name. Default: \"Signature-Input\". */\n signatureInputHeaderName?: string;\n /** Signature label. Default: \"sig1\". */\n label?: string;\n /** Signature expiry in seconds from creation. Optional. */\n expires?: number;\n /** Include a nonce parameter. Default: false. */\n nonce?: boolean;\n}\n\nexport const generateHttpSignature =\n /*#__PURE__*/ definePolicy<GenerateHttpSignatureConfig>({\n name: \"generate-http-signature\",\n priority: Priority.PROXY,\n defaults: {\n components: [\"@method\", \"@path\", \"@authority\"],\n signatureHeaderName: \"Signature\",\n signatureInputHeaderName: \"Signature-Input\",\n label: \"sig1\",\n nonce: false,\n },\n handler: async (c, _next, { config, debug }) => {\n // Validate key material at request time\n if (!config.secret && !config.privateKey) {\n throw new GatewayError(\n 500,\n \"config_error\",\n \"generateHttpSignature requires either 'secret' or 'privateKey'\"\n );\n }\n\n const components = config.components!;\n const label = config.label!;\n const created = Math.floor(Date.now() / 1000);\n\n // Build signature params\n const paramsObj: {\n created: number;\n keyId: string;\n expires?: number;\n nonce?: string;\n algorithm?: string;\n } = {\n created,\n keyId: config.keyId,\n algorithm: config.algorithm,\n };\n\n if (config.expires !== undefined) {\n paramsObj.expires = created + config.expires;\n }\n\n if (config.nonce) {\n paramsObj.nonce = crypto.randomUUID().replace(/-/g, \"\");\n }\n\n const signatureParamsStr = buildSignatureParams(components, paramsObj);\n\n // Build signature base\n const signatureBase = buildSignatureBase(\n components,\n signatureParamsStr,\n c.req.raw\n );\n\n debug(\n `signing with ${config.algorithm}, components: ${components.join(\", \")}`\n );\n\n // Import key and sign\n const key = await importSigningKey(\n config.algorithm,\n config.secret,\n config.privateKey\n );\n const { signAlg } = algorithmToCrypto(config.algorithm);\n const encoder = new TextEncoder();\n const signatureBytes = await crypto.subtle.sign(\n signAlg,\n key,\n encoder.encode(signatureBase)\n );\n\n const signatureB64 = toBase64(signatureBytes);\n\n withModifiedHeaders(c, (headers) => {\n headers.set(\n config.signatureInputHeaderName!,\n `${label}=${signatureParamsStr}`\n );\n headers.set(config.signatureHeaderName!, `${label}=:${signatureB64}:`);\n });\n\n debug(\"signature headers attached\");\n\n await _next();\n },\n });\n"],"mappings":"AASA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,cAAc,gBAAgB;AAEvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAyBA,MAAM,wBACG,6BAA0C;AAAA,EACtD,MAAM;AAAA,EACN,UAAU,SAAS;AAAA,EACnB,UAAU;AAAA,IACR,YAAY,CAAC,WAAW,SAAS,YAAY;AAAA,IAC7C,qBAAqB;AAAA,IACrB,0BAA0B;AAAA,IAC1B,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAAA,EACA,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,MAAM,MAAM;AAE9C,QAAI,CAAC,OAAO,UAAU,CAAC,OAAO,YAAY;AACxC,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,aAAa,OAAO;AAC1B,UAAM,QAAQ,OAAO;AACrB,UAAM,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAG5C,UAAM,YAMF;AAAA,MACF;AAAA,MACA,OAAO,OAAO;AAAA,MACd,WAAW,OAAO;AAAA,IACpB;AAEA,QAAI,OAAO,YAAY,QAAW;AAChC,gBAAU,UAAU,UAAU,OAAO;AAAA,IACvC;AAEA,QAAI,OAAO,OAAO;AAChB,gBAAU,QAAQ,OAAO,WAAW,EAAE,QAAQ,MAAM,EAAE;AAAA,IACxD;AAEA,UAAM,qBAAqB,qBAAqB,YAAY,SAAS;AAGrE,UAAM,gBAAgB;AAAA,MACpB;AAAA,MACA;AAAA,MACA,EAAE,IAAI;AAAA,IACR;AAEA;AAAA,MACE,gBAAgB,OAAO,SAAS,iBAAiB,WAAW,KAAK,IAAI,CAAC;AAAA,IACxE;AAGA,UAAM,MAAM,MAAM;AAAA,MAChB,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AACA,UAAM,EAAE,QAAQ,IAAI,kBAAkB,OAAO,SAAS;AACtD,UAAM,UAAU,IAAI,YAAY;AAChC,UAAM,iBAAiB,MAAM,OAAO,OAAO;AAAA,MACzC;AAAA,MACA;AAAA,MACA,QAAQ,OAAO,aAAa;AAAA,IAC9B;AAEA,UAAM,eAAe,SAAS,cAAc;AAE5C,wBAAoB,GAAG,CAAC,YAAY;AAClC,cAAQ;AAAA,QACN,OAAO;AAAA,QACP,GAAG,KAAK,IAAI,kBAAkB;AAAA,MAChC;AACA,cAAQ,IAAI,OAAO,qBAAsB,GAAG,KAAK,KAAK,YAAY,GAAG;AAAA,IACvE,CAAC;AAED,UAAM,4BAA4B;AAElC,UAAM,MAAM;AAAA,EACd;AACF,CAAC;","names":[]}
@@ -0,0 +1,44 @@
1
+ import { g as PolicyConfig, P as Policy } from '../../protocol-2fD3DJrL.js';
2
+ import { Context } from 'hono';
3
+ import '../sdk/trace.js';
4
+ import '@vivero/stoma-core';
5
+
6
+ interface GenerateJwtConfig extends PolicyConfig {
7
+ /** Signing algorithm */
8
+ algorithm: "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512";
9
+ /** HMAC secret (for HS* algorithms) */
10
+ secret?: string;
11
+ /** RSA private key as JWK (for RS* algorithms) */
12
+ privateKey?: JsonWebKey;
13
+ /** Claims to include. Static record or dynamic function. */
14
+ claims?: Record<string, unknown> | ((c: Context) => Record<string, unknown> | Promise<Record<string, unknown>>);
15
+ /** Token lifetime in seconds. Default: 3600 (1 hour) */
16
+ expiresIn?: number;
17
+ /** Issuer claim */
18
+ issuer?: string;
19
+ /** Audience claim */
20
+ audience?: string;
21
+ /** Header name for the generated token. Default: "Authorization" */
22
+ headerName?: string;
23
+ /** Token prefix. Default: "Bearer" */
24
+ tokenPrefix?: string;
25
+ }
26
+ /**
27
+ * Mint JWTs and attach them to the request for upstream consumption.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { generateJwt } from "@vivero/stoma";
32
+ *
33
+ * generateJwt({
34
+ * algorithm: "HS256",
35
+ * secret: env.JWT_SIGNING_SECRET,
36
+ * claims: (c) => ({ sub: c.req.header("x-user-id") }),
37
+ * issuer: "my-gateway",
38
+ * expiresIn: 300,
39
+ * });
40
+ * ```
41
+ */
42
+ declare const generateJwt: (config: GenerateJwtConfig) => Policy;
43
+
44
+ export { type GenerateJwtConfig, generateJwt };