@heavybit/shared-lib 0.0.0-stage → 1.2.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 (310) hide show
  1. package/dist/cjs/access/index.js +118 -0
  2. package/dist/cjs/access/index.js.map +1 -0
  3. package/dist/cjs/alerting/index.js +56 -0
  4. package/dist/cjs/alerting/index.js.map +1 -0
  5. package/dist/cjs/cache/index.js +167 -0
  6. package/dist/cjs/cache/index.js.map +1 -0
  7. package/dist/cjs/common/async.js +40 -0
  8. package/dist/cjs/common/async.js.map +1 -0
  9. package/dist/cjs/common/audit.js +21 -0
  10. package/dist/cjs/common/audit.js.map +1 -0
  11. package/dist/cjs/common/crypto.js +75 -0
  12. package/dist/cjs/common/crypto.js.map +1 -0
  13. package/dist/cjs/common/errors.js +90 -0
  14. package/dist/cjs/common/errors.js.map +1 -0
  15. package/dist/cjs/common/id.js +40 -0
  16. package/dist/cjs/common/id.js.map +1 -0
  17. package/dist/cjs/common/index.js +28 -0
  18. package/dist/cjs/common/index.js.map +1 -0
  19. package/dist/cjs/common/jwt.js +59 -0
  20. package/dist/cjs/common/jwt.js.map +1 -0
  21. package/dist/cjs/common/password.js +38 -0
  22. package/dist/cjs/common/password.js.map +1 -0
  23. package/dist/cjs/common/phone.js +42 -0
  24. package/dist/cjs/common/phone.js.map +1 -0
  25. package/dist/cjs/common/response.js +32 -0
  26. package/dist/cjs/common/response.js.map +1 -0
  27. package/dist/cjs/common/text.js +46 -0
  28. package/dist/cjs/common/text.js.map +1 -0
  29. package/dist/cjs/common/time.js +109 -0
  30. package/dist/cjs/common/time.js.map +1 -0
  31. package/dist/cjs/config/index.js +96 -0
  32. package/dist/cjs/config/index.js.map +1 -0
  33. package/dist/cjs/contracts/editorial-draft.js +111 -0
  34. package/dist/cjs/contracts/editorial-draft.js.map +1 -0
  35. package/dist/cjs/contracts/index.js +20 -0
  36. package/dist/cjs/contracts/index.js.map +1 -0
  37. package/dist/cjs/contracts/notification-dispatch.js +92 -0
  38. package/dist/cjs/contracts/notification-dispatch.js.map +1 -0
  39. package/dist/cjs/contracts/upload-import.js +54 -0
  40. package/dist/cjs/contracts/upload-import.js.map +1 -0
  41. package/dist/cjs/database/index.js +57 -0
  42. package/dist/cjs/database/index.js.map +1 -0
  43. package/dist/cjs/express/auth.js +72 -0
  44. package/dist/cjs/express/auth.js.map +1 -0
  45. package/dist/cjs/express/health.js +31 -0
  46. package/dist/cjs/express/health.js.map +1 -0
  47. package/dist/cjs/express/index.js +21 -0
  48. package/dist/cjs/express/index.js.map +1 -0
  49. package/dist/cjs/express/middleware.js +95 -0
  50. package/dist/cjs/express/middleware.js.map +1 -0
  51. package/dist/cjs/express/security.js +18 -0
  52. package/dist/cjs/express/security.js.map +1 -0
  53. package/dist/cjs/http/index.js +90 -0
  54. package/dist/cjs/http/index.js.map +1 -0
  55. package/dist/cjs/http/safe-fetch.js +199 -0
  56. package/dist/cjs/http/safe-fetch.js.map +1 -0
  57. package/dist/cjs/identity/index.js +117 -0
  58. package/dist/cjs/identity/index.js.map +1 -0
  59. package/dist/cjs/index.js +29 -0
  60. package/dist/cjs/index.js.map +1 -0
  61. package/dist/cjs/knex/index.js +76 -0
  62. package/dist/cjs/knex/index.js.map +1 -0
  63. package/dist/cjs/logging/index.js +75 -0
  64. package/dist/cjs/logging/index.js.map +1 -0
  65. package/dist/cjs/messaging/connection.js +115 -0
  66. package/dist/cjs/messaging/connection.js.map +1 -0
  67. package/dist/cjs/messaging/consumer.js +134 -0
  68. package/dist/cjs/messaging/consumer.js.map +1 -0
  69. package/dist/cjs/messaging/envelope.js +23 -0
  70. package/dist/cjs/messaging/envelope.js.map +1 -0
  71. package/dist/cjs/messaging/idempotency.js +57 -0
  72. package/dist/cjs/messaging/idempotency.js.map +1 -0
  73. package/dist/cjs/messaging/index.js +62 -0
  74. package/dist/cjs/messaging/index.js.map +1 -0
  75. package/dist/cjs/messaging/monitor.js +48 -0
  76. package/dist/cjs/messaging/monitor.js.map +1 -0
  77. package/dist/cjs/messaging/outbox.js +135 -0
  78. package/dist/cjs/messaging/outbox.js.map +1 -0
  79. package/dist/cjs/messaging/publisher.js +149 -0
  80. package/dist/cjs/messaging/publisher.js.map +1 -0
  81. package/dist/cjs/messaging/rpc.js +125 -0
  82. package/dist/cjs/messaging/rpc.js.map +1 -0
  83. package/dist/cjs/messaging/staff-notifier.js +78 -0
  84. package/dist/cjs/messaging/staff-notifier.js.map +1 -0
  85. package/dist/cjs/messaging/topology.js +22 -0
  86. package/dist/cjs/messaging/topology.js.map +1 -0
  87. package/dist/cjs/metrics/index.js +15 -0
  88. package/dist/cjs/metrics/index.js.map +1 -0
  89. package/dist/cjs/package.json +1 -0
  90. package/dist/cjs/rate-limit/index.js +61 -0
  91. package/dist/cjs/rate-limit/index.js.map +1 -0
  92. package/dist/cjs/scheduling/index.js +88 -0
  93. package/dist/cjs/scheduling/index.js.map +1 -0
  94. package/dist/cjs/security/index.js +39 -0
  95. package/dist/cjs/security/index.js.map +1 -0
  96. package/dist/cjs/socket/index.js +53 -0
  97. package/dist/cjs/socket/index.js.map +1 -0
  98. package/dist/cjs/testing/index.js +73 -0
  99. package/dist/cjs/testing/index.js.map +1 -0
  100. package/dist/cjs/types/index.js +3 -0
  101. package/dist/cjs/types/index.js.map +1 -0
  102. package/dist/cjs/validation/index.js +41 -0
  103. package/dist/cjs/validation/index.js.map +1 -0
  104. package/dist/esm/access/index.js +104 -0
  105. package/dist/esm/access/index.js.map +1 -0
  106. package/dist/esm/alerting/index.js +52 -0
  107. package/dist/esm/alerting/index.js.map +1 -0
  108. package/dist/esm/cache/index.js +163 -0
  109. package/dist/esm/cache/index.js.map +1 -0
  110. package/dist/esm/common/async.js +33 -0
  111. package/dist/esm/common/async.js.map +1 -0
  112. package/dist/esm/common/audit.js +18 -0
  113. package/dist/esm/common/audit.js.map +1 -0
  114. package/dist/esm/common/crypto.js +64 -0
  115. package/dist/esm/common/crypto.js.map +1 -0
  116. package/dist/esm/common/errors.js +75 -0
  117. package/dist/esm/common/errors.js.map +1 -0
  118. package/dist/esm/common/id.js +33 -0
  119. package/dist/esm/common/id.js.map +1 -0
  120. package/dist/esm/common/index.js +12 -0
  121. package/dist/esm/common/index.js.map +1 -0
  122. package/dist/esm/common/jwt.js +49 -0
  123. package/dist/esm/common/jwt.js.map +1 -0
  124. package/dist/esm/common/password.js +30 -0
  125. package/dist/esm/common/password.js.map +1 -0
  126. package/dist/esm/common/phone.js +33 -0
  127. package/dist/esm/common/phone.js.map +1 -0
  128. package/dist/esm/common/response.js +24 -0
  129. package/dist/esm/common/response.js.map +1 -0
  130. package/dist/esm/common/text.js +39 -0
  131. package/dist/esm/common/text.js.map +1 -0
  132. package/dist/esm/common/time.js +96 -0
  133. package/dist/esm/common/time.js.map +1 -0
  134. package/dist/esm/config/index.js +88 -0
  135. package/dist/esm/config/index.js.map +1 -0
  136. package/dist/esm/contracts/editorial-draft.js +108 -0
  137. package/dist/esm/contracts/editorial-draft.js.map +1 -0
  138. package/dist/esm/contracts/index.js +4 -0
  139. package/dist/esm/contracts/index.js.map +1 -0
  140. package/dist/esm/contracts/notification-dispatch.js +89 -0
  141. package/dist/esm/contracts/notification-dispatch.js.map +1 -0
  142. package/dist/esm/contracts/upload-import.js +51 -0
  143. package/dist/esm/contracts/upload-import.js.map +1 -0
  144. package/dist/esm/database/index.js +48 -0
  145. package/dist/esm/database/index.js.map +1 -0
  146. package/dist/esm/express/auth.js +63 -0
  147. package/dist/esm/express/auth.js.map +1 -0
  148. package/dist/esm/express/health.js +27 -0
  149. package/dist/esm/express/health.js.map +1 -0
  150. package/dist/esm/express/index.js +5 -0
  151. package/dist/esm/express/index.js.map +1 -0
  152. package/dist/esm/express/middleware.js +84 -0
  153. package/dist/esm/express/middleware.js.map +1 -0
  154. package/dist/esm/express/security.js +11 -0
  155. package/dist/esm/express/security.js.map +1 -0
  156. package/dist/esm/http/index.js +73 -0
  157. package/dist/esm/http/index.js.map +1 -0
  158. package/dist/esm/http/safe-fetch.js +193 -0
  159. package/dist/esm/http/safe-fetch.js.map +1 -0
  160. package/dist/esm/identity/index.js +111 -0
  161. package/dist/esm/identity/index.js.map +1 -0
  162. package/dist/esm/index.js +13 -0
  163. package/dist/esm/index.js.map +1 -0
  164. package/dist/esm/knex/index.js +66 -0
  165. package/dist/esm/knex/index.js.map +1 -0
  166. package/dist/esm/logging/index.js +67 -0
  167. package/dist/esm/logging/index.js.map +1 -0
  168. package/dist/esm/messaging/connection.js +108 -0
  169. package/dist/esm/messaging/connection.js.map +1 -0
  170. package/dist/esm/messaging/consumer.js +130 -0
  171. package/dist/esm/messaging/consumer.js.map +1 -0
  172. package/dist/esm/messaging/envelope.js +19 -0
  173. package/dist/esm/messaging/envelope.js.map +1 -0
  174. package/dist/esm/messaging/idempotency.js +52 -0
  175. package/dist/esm/messaging/idempotency.js.map +1 -0
  176. package/dist/esm/messaging/index.js +45 -0
  177. package/dist/esm/messaging/index.js.map +1 -0
  178. package/dist/esm/messaging/monitor.js +44 -0
  179. package/dist/esm/messaging/monitor.js.map +1 -0
  180. package/dist/esm/messaging/outbox.js +129 -0
  181. package/dist/esm/messaging/outbox.js.map +1 -0
  182. package/dist/esm/messaging/publisher.js +142 -0
  183. package/dist/esm/messaging/publisher.js.map +1 -0
  184. package/dist/esm/messaging/rpc.js +120 -0
  185. package/dist/esm/messaging/rpc.js.map +1 -0
  186. package/dist/esm/messaging/staff-notifier.js +73 -0
  187. package/dist/esm/messaging/staff-notifier.js.map +1 -0
  188. package/dist/esm/messaging/topology.js +16 -0
  189. package/dist/esm/messaging/topology.js.map +1 -0
  190. package/dist/esm/metrics/index.js +6 -0
  191. package/dist/esm/metrics/index.js.map +1 -0
  192. package/dist/esm/package.json +1 -0
  193. package/dist/esm/rate-limit/index.js +56 -0
  194. package/dist/esm/rate-limit/index.js.map +1 -0
  195. package/dist/esm/scheduling/index.js +83 -0
  196. package/dist/esm/scheduling/index.js.map +1 -0
  197. package/dist/esm/security/index.js +36 -0
  198. package/dist/esm/security/index.js.map +1 -0
  199. package/dist/esm/socket/index.js +47 -0
  200. package/dist/esm/socket/index.js.map +1 -0
  201. package/dist/esm/testing/index.js +66 -0
  202. package/dist/esm/testing/index.js.map +1 -0
  203. package/dist/esm/types/index.js +2 -0
  204. package/dist/esm/types/index.js.map +1 -0
  205. package/dist/esm/validation/index.js +36 -0
  206. package/dist/esm/validation/index.js.map +1 -0
  207. package/dist/types/access/index.d.ts +50 -0
  208. package/dist/types/access/index.d.ts.map +1 -0
  209. package/dist/types/alerting/index.d.ts +43 -0
  210. package/dist/types/alerting/index.d.ts.map +1 -0
  211. package/dist/types/cache/index.d.ts +45 -0
  212. package/dist/types/cache/index.d.ts.map +1 -0
  213. package/dist/types/common/async.d.ts +14 -0
  214. package/dist/types/common/async.d.ts.map +1 -0
  215. package/dist/types/common/audit.d.ts +17 -0
  216. package/dist/types/common/audit.d.ts.map +1 -0
  217. package/dist/types/common/crypto.d.ts +18 -0
  218. package/dist/types/common/crypto.d.ts.map +1 -0
  219. package/dist/types/common/errors.d.ts +71 -0
  220. package/dist/types/common/errors.d.ts.map +1 -0
  221. package/dist/types/common/id.d.ts +14 -0
  222. package/dist/types/common/id.d.ts.map +1 -0
  223. package/dist/types/common/index.d.ts +12 -0
  224. package/dist/types/common/index.d.ts.map +1 -0
  225. package/dist/types/common/jwt.d.ts +28 -0
  226. package/dist/types/common/jwt.d.ts.map +1 -0
  227. package/dist/types/common/password.d.ts +4 -0
  228. package/dist/types/common/password.d.ts.map +1 -0
  229. package/dist/types/common/phone.d.ts +10 -0
  230. package/dist/types/common/phone.d.ts.map +1 -0
  231. package/dist/types/common/response.d.ts +12 -0
  232. package/dist/types/common/response.d.ts.map +1 -0
  233. package/dist/types/common/text.d.ts +10 -0
  234. package/dist/types/common/text.d.ts.map +1 -0
  235. package/dist/types/common/time.d.ts +40 -0
  236. package/dist/types/common/time.d.ts.map +1 -0
  237. package/dist/types/config/index.d.ts +115 -0
  238. package/dist/types/config/index.d.ts.map +1 -0
  239. package/dist/types/contracts/editorial-draft.d.ts +577 -0
  240. package/dist/types/contracts/editorial-draft.d.ts.map +1 -0
  241. package/dist/types/contracts/index.d.ts +4 -0
  242. package/dist/types/contracts/index.d.ts.map +1 -0
  243. package/dist/types/contracts/notification-dispatch.d.ts +175 -0
  244. package/dist/types/contracts/notification-dispatch.d.ts.map +1 -0
  245. package/dist/types/contracts/upload-import.d.ts +96 -0
  246. package/dist/types/contracts/upload-import.d.ts.map +1 -0
  247. package/dist/types/database/index.d.ts +25 -0
  248. package/dist/types/database/index.d.ts.map +1 -0
  249. package/dist/types/express/auth.d.ts +27 -0
  250. package/dist/types/express/auth.d.ts.map +1 -0
  251. package/dist/types/express/health.d.ts +14 -0
  252. package/dist/types/express/health.d.ts.map +1 -0
  253. package/dist/types/express/index.d.ts +5 -0
  254. package/dist/types/express/index.d.ts.map +1 -0
  255. package/dist/types/express/middleware.d.ts +20 -0
  256. package/dist/types/express/middleware.d.ts.map +1 -0
  257. package/dist/types/express/security.d.ts +9 -0
  258. package/dist/types/express/security.d.ts.map +1 -0
  259. package/dist/types/http/index.d.ts +34 -0
  260. package/dist/types/http/index.d.ts.map +1 -0
  261. package/dist/types/http/safe-fetch.d.ts +58 -0
  262. package/dist/types/http/safe-fetch.d.ts.map +1 -0
  263. package/dist/types/identity/index.d.ts +51 -0
  264. package/dist/types/identity/index.d.ts.map +1 -0
  265. package/dist/types/index.d.ts +10 -0
  266. package/dist/types/index.d.ts.map +1 -0
  267. package/dist/types/knex/index.d.ts +25 -0
  268. package/dist/types/knex/index.d.ts.map +1 -0
  269. package/dist/types/logging/index.d.ts +22 -0
  270. package/dist/types/logging/index.d.ts.map +1 -0
  271. package/dist/types/messaging/connection.d.ts +48 -0
  272. package/dist/types/messaging/connection.d.ts.map +1 -0
  273. package/dist/types/messaging/consumer.d.ts +56 -0
  274. package/dist/types/messaging/consumer.d.ts.map +1 -0
  275. package/dist/types/messaging/envelope.d.ts +16 -0
  276. package/dist/types/messaging/envelope.d.ts.map +1 -0
  277. package/dist/types/messaging/idempotency.d.ts +29 -0
  278. package/dist/types/messaging/idempotency.d.ts.map +1 -0
  279. package/dist/types/messaging/index.d.ts +39 -0
  280. package/dist/types/messaging/index.d.ts.map +1 -0
  281. package/dist/types/messaging/monitor.d.ts +23 -0
  282. package/dist/types/messaging/monitor.d.ts.map +1 -0
  283. package/dist/types/messaging/outbox.d.ts +51 -0
  284. package/dist/types/messaging/outbox.d.ts.map +1 -0
  285. package/dist/types/messaging/publisher.d.ts +84 -0
  286. package/dist/types/messaging/publisher.d.ts.map +1 -0
  287. package/dist/types/messaging/rpc.d.ts +46 -0
  288. package/dist/types/messaging/rpc.d.ts.map +1 -0
  289. package/dist/types/messaging/staff-notifier.d.ts +56 -0
  290. package/dist/types/messaging/staff-notifier.d.ts.map +1 -0
  291. package/dist/types/messaging/topology.d.ts +23 -0
  292. package/dist/types/messaging/topology.d.ts.map +1 -0
  293. package/dist/types/metrics/index.d.ts +7 -0
  294. package/dist/types/metrics/index.d.ts.map +1 -0
  295. package/dist/types/rate-limit/index.d.ts +28 -0
  296. package/dist/types/rate-limit/index.d.ts.map +1 -0
  297. package/dist/types/scheduling/index.d.ts +33 -0
  298. package/dist/types/scheduling/index.d.ts.map +1 -0
  299. package/dist/types/security/index.d.ts +20 -0
  300. package/dist/types/security/index.d.ts.map +1 -0
  301. package/dist/types/socket/index.d.ts +30 -0
  302. package/dist/types/socket/index.d.ts.map +1 -0
  303. package/dist/types/testing/index.d.ts +41 -0
  304. package/dist/types/testing/index.d.ts.map +1 -0
  305. package/dist/types/types/index.d.ts +65 -0
  306. package/dist/types/types/index.d.ts.map +1 -0
  307. package/dist/types/validation/index.d.ts +34 -0
  308. package/dist/types/validation/index.d.ts.map +1 -0
  309. package/package.json +181 -4
  310. package/README.md +0 -3
@@ -0,0 +1,63 @@
1
+ import { hasAllPermissions, hasPermission, hasRole } from '../access/index.js';
2
+ import { safeEqual } from '../common/crypto.js';
3
+ import { errorResponse } from '../common/response.js';
4
+ import { CONTEXT_HEADERS, verifyIdentityHeaders } from '../identity/index.js';
5
+ function resolveIdentity(req, options) {
6
+ const result = verifyIdentityHeaders(req.headers, options.secret, { maxSkewMs: options.maxSkewMs });
7
+ if (result.status === 'authenticated') {
8
+ req.user = result.user;
9
+ return 'ok';
10
+ }
11
+ if (result.status === 'invalid') {
12
+ options.logger?.warn('Rejected forged or stale gateway identity', { reason: result.reason, path: req.path });
13
+ return 'invalid';
14
+ }
15
+ return 'anonymous';
16
+ }
17
+ /** Requires a valid gateway-signed identity; 401 otherwise. */
18
+ export function authenticateFromGateway(options) {
19
+ return (req, res, next) => {
20
+ if (resolveIdentity(req, options) === 'ok')
21
+ return next();
22
+ res.status(401).json(errorResponse('Authentication required', undefined, 'UNAUTHENTICATED'));
23
+ };
24
+ }
25
+ /** Attaches `req.user` when a valid identity is present; continues anonymously otherwise. */
26
+ export function optionalAuthFromGateway(options) {
27
+ return (req, res, next) => {
28
+ if (resolveIdentity(req, options) === 'invalid') {
29
+ res.status(401).json(errorResponse('Invalid identity', undefined, 'UNAUTHENTICATED'));
30
+ return;
31
+ }
32
+ next();
33
+ };
34
+ }
35
+ function guard(check, message = 'You do not have permission to perform this action') {
36
+ return (req, res, next) => {
37
+ if (!req.user) {
38
+ res.status(401).json(errorResponse('Authentication required', undefined, 'UNAUTHENTICATED'));
39
+ return;
40
+ }
41
+ if (!check(req.user)) {
42
+ res.status(403).json(errorResponse(message, undefined, 'FORBIDDEN'));
43
+ return;
44
+ }
45
+ next();
46
+ };
47
+ }
48
+ /** Allows callers holding any of `permissions`. */
49
+ export const requirePermission = (...permissions) => guard((u) => hasPermission(u, ...permissions));
50
+ /** Allows callers holding every one of `permissions`. */
51
+ export const requireAllPermissions = (...permissions) => guard((u) => hasAllPermissions(u, ...permissions));
52
+ /** Allows callers with any of `roles`. Prefer permissions; roles are for coarse gates only. */
53
+ export const requireRole = (...roles) => guard((u) => hasRole(u, ...roles), 'Insufficient role');
54
+ /** Guards `/internal/*` routes: requires the shared service secret header. */
55
+ export function requireInternalServiceSecret(secret) {
56
+ return (req, res, next) => {
57
+ const provided = req.headers[CONTEXT_HEADERS.INTERNAL_SECRET];
58
+ if (typeof provided === 'string' && secret && safeEqual(provided, secret))
59
+ return next();
60
+ res.status(401).json(errorResponse('Invalid internal service credentials', undefined, 'UNAUTHENTICATED'));
61
+ };
62
+ }
63
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/express/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { withTimeout } from '../common/async.js';
2
+ /** `GET /health` — liveness: the process is up. Never touches dependencies. */
3
+ export function livenessHandler(options) {
4
+ const startedAt = Date.now();
5
+ return (_req, res) => {
6
+ res.json({ status: 'ok', service: options.service, version: options.version, uptimeSec: Math.round((Date.now() - startedAt) / 1000) });
7
+ };
8
+ }
9
+ /** `GET /health/ready` — readiness: every dependency responds. 503 when any fails. */
10
+ export function readinessHandler(options) {
11
+ return async (_req, res) => {
12
+ const entries = Object.entries(options.checks ?? {});
13
+ const results = await Promise.all(entries.map(async ([name, check]) => {
14
+ const started = Date.now();
15
+ try {
16
+ const detail = await withTimeout(Promise.resolve(check()), options.timeoutMs ?? 3000, () => new Error('timeout'));
17
+ return [name, { status: 'up', latencyMs: Date.now() - started, ...(detail && typeof detail === 'object' ? { detail } : {}) }];
18
+ }
19
+ catch (error) {
20
+ return [name, { status: 'down', latencyMs: Date.now() - started, error: error.message }];
21
+ }
22
+ }));
23
+ const healthy = results.every(([, r]) => r.status === 'up');
24
+ res.status(healthy ? 200 : 503).json({ status: healthy ? 'ok' : 'degraded', service: options.service, checks: Object.fromEntries(results) });
25
+ };
26
+ }
27
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../../src/express/health.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAYjD,+EAA+E;AAC/E,MAAM,UAAU,eAAe,CAAC,OAAsB;IACpD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;QACtC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IACzI,CAAC,CAAC;AACJ,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACrD,OAAO,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,EAAE;QAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;YACpE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;gBAClH,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAU,CAAC;YACzI,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAU,CAAC;YAC/G,CAAC;QACH,CAAC,CAAC,CAAC,CAAC;QACJ,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;QAC5D,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/I,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './auth.js';
2
+ export * from './middleware.js';
3
+ export * from './health.js';
4
+ export * from './security.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/express/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC"}
@@ -0,0 +1,84 @@
1
+ import { isAppError, publicMessage } from '../common/errors.js';
2
+ import { newId } from '../common/id.js';
3
+ import { errorResponse } from '../common/response.js';
4
+ import { CONTEXT_HEADERS } from '../identity/index.js';
5
+ import { zodIssuesToErrors } from '../validation/index.js';
6
+ /** Ensures every request carries `x-correlation-id` (reused from the gateway when present). */
7
+ export function correlationMiddleware() {
8
+ return (req, res, next) => {
9
+ const incoming = req.headers[CONTEXT_HEADERS.CORRELATION_ID];
10
+ const id = typeof incoming === 'string' && incoming.length <= 128 ? incoming : newId();
11
+ req.headers[CONTEXT_HEADERS.CORRELATION_ID] = id;
12
+ res.setHeader(CONTEXT_HEADERS.CORRELATION_ID, id);
13
+ next();
14
+ };
15
+ }
16
+ export function correlationId(req) {
17
+ const value = req.headers[CONTEXT_HEADERS.CORRELATION_ID];
18
+ return typeof value === 'string' ? value : undefined;
19
+ }
20
+ /** Real client IP: gateway-forwarded header first, then Express's `req.ip`. */
21
+ export function clientIp(req) {
22
+ const forwarded = req.headers[CONTEXT_HEADERS.CLIENT_IP];
23
+ if (typeof forwarded === 'string' && forwarded)
24
+ return forwarded;
25
+ return req.ip ?? null;
26
+ }
27
+ export function clientTimezone(req) {
28
+ const tz = req.headers[CONTEXT_HEADERS.TIMEZONE];
29
+ return typeof tz === 'string' && tz ? tz : null;
30
+ }
31
+ export function anonId(req) {
32
+ const id = req.headers[CONTEXT_HEADERS.ANON_ID];
33
+ return typeof id === 'string' && id ? id : null;
34
+ }
35
+ /** Validates and replaces `req[part]` with the parsed value; 422 with field errors on failure. */
36
+ export function validate(schema, part = 'body') {
37
+ return (req, res, next) => {
38
+ const result = schema.safeParse(req[part]);
39
+ if (!result.success) {
40
+ res.status(422).json(errorResponse('Validation failed', zodIssuesToErrors(result.error), 'VALIDATION_FAILED'));
41
+ return;
42
+ }
43
+ // Express 5 exposes req.query as a getter; redefine to store the parsed value.
44
+ Object.defineProperty(req, part, { value: result.data, writable: true, configurable: true, enumerable: true });
45
+ next();
46
+ };
47
+ }
48
+ /** Wraps an async handler so rejections reach the error middleware (needed on Express 4). */
49
+ export function asyncHandler(fn) {
50
+ return (req, res, next) => {
51
+ fn(req, res, next).catch(next);
52
+ };
53
+ }
54
+ /** Maps AppErrors to their status; everything else becomes a logged 500 with a generic message. */
55
+ export function errorHandler(logger) {
56
+ return (err, req, res, _next) => {
57
+ if (res.headersSent)
58
+ return;
59
+ const bodyError = err;
60
+ if (bodyError?.type === 'entity.parse.failed') {
61
+ res.status(400).json(errorResponse('Malformed JSON body', undefined, 'BAD_REQUEST'));
62
+ return;
63
+ }
64
+ if (bodyError?.type === 'entity.too.large') {
65
+ res.status(413).json(errorResponse('Request body too large', undefined, 'PAYLOAD_TOO_LARGE'));
66
+ return;
67
+ }
68
+ if (isAppError(err)) {
69
+ if (err.statusCode >= 500)
70
+ logger?.error(err.message, { path: req.path, code: err.code, correlationId: correlationId(req) });
71
+ res.status(err.statusCode).json(errorResponse(publicMessage(err), err.errors, err.code));
72
+ return;
73
+ }
74
+ const error = err instanceof Error ? err : new Error(String(err));
75
+ logger?.error('Unhandled error', { error: error.message, stack: error.stack, path: req.path, correlationId: correlationId(req) });
76
+ res.status(500).json(errorResponse('Internal server error', undefined, 'INTERNAL_ERROR'));
77
+ };
78
+ }
79
+ export function notFoundHandler() {
80
+ return (_req, res) => {
81
+ res.status(404).json(errorResponse('Resource not found', undefined, 'NOT_FOUND'));
82
+ };
83
+ }
84
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/express/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import cors from 'cors';
2
+ import helmet from 'helmet';
3
+ /** Standard security headers (helmet). Pass options only to relax a header a service genuinely needs. */
4
+ export function securityHeaders(options) {
5
+ return helmet(options);
6
+ }
7
+ /** CORS for browser-facing services. An empty origin list disables CORS (no `Access-Control-*` headers). */
8
+ export function corsMiddleware(origins, options = {}) {
9
+ return cors({ ...options, origin: origins.length ? origins : false });
10
+ }
11
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../../src/express/security.ts"],"names":[],"mappings":"AAAA,OAAO,IAA0B,MAAM,MAAM,CAAC;AAC9C,OAAO,MAA8B,MAAM,QAAQ,CAAC;AAKpD,yGAAyG;AACzG,MAAM,UAAU,eAAe,CAAC,OAAuB;IACrD,OAAO,MAAM,CAAC,OAAO,CAA8B,CAAC;AACtD,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,cAAc,CAAC,OAAiB,EAAE,UAAuC,EAAE;IACzF,OAAO,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAA8B,CAAC;AACrG,CAAC"}
@@ -0,0 +1,73 @@
1
+ import { AppError, ServiceUnavailableError } from '../common/errors.js';
2
+ import { sleep } from '../common/async.js';
3
+ import { CONTEXT_HEADERS } from '../identity/index.js';
4
+ import { silentLogger } from '../logging/index.js';
5
+ export * from './safe-fetch.js';
6
+ const RETRYABLE_STATUS = new Set([502, 503, 504]);
7
+ const IDEMPOTENT = new Set(['GET', 'PUT', 'DELETE']);
8
+ /**
9
+ * Typed client for `/internal/*` calls between services. Unwraps the `{ success, data }`
10
+ * envelope, maps upstream errors to AppErrors with the same status, and turns network failures
11
+ * and timeouts into 503s.
12
+ */
13
+ export function createServiceClient(options) {
14
+ const logger = options.logger ?? silentLogger;
15
+ const base = options.baseUrl.replace(/\/+$/, '');
16
+ async function request(method, path, opts = {}) {
17
+ const url = new URL(base + path);
18
+ for (const [key, value] of Object.entries(opts.query ?? {}))
19
+ if (value !== undefined)
20
+ url.searchParams.set(key, String(value));
21
+ const retries = IDEMPOTENT.has(method) ? options.retries ?? 2 : 0;
22
+ for (let attempt = 0;; attempt += 1) {
23
+ const controller = new AbortController();
24
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? options.timeoutMs ?? 5000);
25
+ try {
26
+ const response = await fetch(url, {
27
+ method,
28
+ signal: controller.signal,
29
+ headers: {
30
+ accept: 'application/json',
31
+ ...(opts.body !== undefined ? { 'content-type': 'application/json' } : {}),
32
+ [CONTEXT_HEADERS.INTERNAL_SECRET]: options.internalSecret,
33
+ [CONTEXT_HEADERS.CALLER_SERVICE]: options.callerService,
34
+ ...(opts.correlationId ? { [CONTEXT_HEADERS.CORRELATION_ID]: opts.correlationId } : {}),
35
+ ...opts.headers,
36
+ },
37
+ body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
38
+ });
39
+ if (RETRYABLE_STATUS.has(response.status) && attempt < retries) {
40
+ await sleep(200 * 2 ** attempt);
41
+ continue;
42
+ }
43
+ const text = await response.text();
44
+ const payload = text ? JSON.parse(text) : { success: response.ok, message: '' };
45
+ if (!response.ok) {
46
+ throw new AppError(response.status, payload.message || `Upstream error ${response.status}`, { code: payload.code, errors: payload.errors });
47
+ }
48
+ return (payload.data ?? payload);
49
+ }
50
+ catch (error) {
51
+ if (error instanceof AppError)
52
+ throw error;
53
+ if (attempt < retries) {
54
+ await sleep(200 * 2 ** attempt);
55
+ continue;
56
+ }
57
+ logger.warn('Internal call failed', { method, url: url.pathname, error: error.message });
58
+ throw new ServiceUnavailableError('Upstream service unavailable', { cause: error });
59
+ }
60
+ finally {
61
+ clearTimeout(timer);
62
+ }
63
+ }
64
+ }
65
+ return {
66
+ get: (path, opts) => request('GET', path, opts),
67
+ post: (path, opts) => request('POST', path, opts),
68
+ put: (path, opts) => request('PUT', path, opts),
69
+ patch: (path, opts) => request('PATCH', path, opts),
70
+ delete: (path, opts) => request('DELETE', path, opts),
71
+ };
72
+ }
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/http/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,193 @@
1
+ import { lookup as dnsLookup } from 'node:dns';
2
+ import { isIP } from 'node:net';
3
+ import { Agent, request } from 'undici';
4
+ /** A fetch rejected for an SSRF guard, a limit, or a transport failure. */
5
+ export class SafeFetchError extends Error {
6
+ code;
7
+ constructor(code, message, options) {
8
+ super(message, options);
9
+ this.name = 'SafeFetchError';
10
+ this.code = code;
11
+ }
12
+ }
13
+ const DEFAULT_PROTOCOLS = ['https:', 'http:'];
14
+ const defaultLookup = (hostname, options, callback) => {
15
+ dnsLookup(hostname, { all: true, family: options.family }, (err, addresses) => {
16
+ if (err)
17
+ return callback(err, []);
18
+ callback(null, addresses.map((a) => ({ address: a.address, family: a.family })));
19
+ });
20
+ };
21
+ /**
22
+ * Returns a human-readable reason if `ip` is a disallowed destination (private, loopback,
23
+ * link-local, CGNAT, multicast, unspecified, IPv4-mapped IPv6, cloud metadata), otherwise `null`.
24
+ */
25
+ export function blockedAddressReason(ip) {
26
+ const family = isIP(ip);
27
+ if (family === 4)
28
+ return blockedIpv4Reason(ip);
29
+ if (family === 6)
30
+ return blockedIpv6Reason(ip.toLowerCase());
31
+ return 'not an IP address';
32
+ }
33
+ function blockedIpv4Reason(ip) {
34
+ const parts = ip.split('.').map((p) => Number(p));
35
+ if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255))
36
+ return 'malformed IPv4';
37
+ const [a, b] = parts;
38
+ if (a === 0)
39
+ return 'unspecified';
40
+ if (a === 10)
41
+ return 'private (10.0.0.0/8)';
42
+ if (a === 127)
43
+ return 'loopback';
44
+ if (a === 100 && b >= 64 && b <= 127)
45
+ return 'CGNAT (100.64.0.0/10)';
46
+ if (a === 169 && b === 254)
47
+ return 'link-local (incl. cloud metadata)';
48
+ if (a === 172 && b >= 16 && b <= 31)
49
+ return 'private (172.16.0.0/12)';
50
+ if (a === 192 && b === 168)
51
+ return 'private (192.168.0.0/16)';
52
+ if (a >= 224)
53
+ return 'multicast or reserved';
54
+ return null;
55
+ }
56
+ function blockedIpv6Reason(ip) {
57
+ if (ip === '::' || ip === '::0')
58
+ return 'unspecified';
59
+ if (ip === '::1')
60
+ return 'loopback';
61
+ if (ip.startsWith('fe80') || ip.startsWith('fe9') || ip.startsWith('fea') || ip.startsWith('feb'))
62
+ return 'link-local';
63
+ if (ip.startsWith('fc') || ip.startsWith('fd'))
64
+ return 'unique-local (incl. fd00:ec2::254 metadata)';
65
+ if (ip.startsWith('ff'))
66
+ return 'multicast';
67
+ // IPv4-mapped (::ffff:a.b.c.d) and IPv4-compatible (::a.b.c.d) — unwrap and re-check.
68
+ const mapped = ip.match(/::(?:ffff:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
69
+ if (mapped)
70
+ return `IPv4-mapped IPv6 (${blockedIpv4Reason(mapped[1]) ?? 'maps to public IPv4'})`;
71
+ return null;
72
+ }
73
+ export function createSafeFetcher(options) {
74
+ const { userAgent, maxBytes, timeoutMs, maxRedirects = 5, allowedProtocols = DEFAULT_PROTOCOLS, lookup = defaultLookup, allowedAddresses = [], } = options;
75
+ const allowSet = new Set(allowedAddresses);
76
+ const isBlocked = (ip) => !allowSet.has(ip) && blockedAddressReason(ip) !== null;
77
+ function resolveAndCheck(hostname) {
78
+ // A literal IP resolves to itself; still subject to the blocklist.
79
+ return new Promise((resolve, reject) => {
80
+ lookup(hostname, { all: true }, (err, addresses) => {
81
+ if (err)
82
+ return reject(new SafeFetchError('NETWORK', `DNS lookup failed for ${hostname}`, { cause: err }));
83
+ if (!addresses.length)
84
+ return reject(new SafeFetchError('NETWORK', `No addresses for ${hostname}`));
85
+ for (const a of addresses) {
86
+ if (isBlocked(a.address))
87
+ return reject(new SafeFetchError('BLOCKED_ADDRESS', `Blocked address ${a.address} for ${hostname}`));
88
+ }
89
+ resolve();
90
+ });
91
+ });
92
+ }
93
+ // Re-validates at connect time so a DNS rebind between the pre-check and the socket is blocked.
94
+ // `net.connect` calls this with `{ all: true }` (and expects an array) on modern Node; support both.
95
+ const connectLookup = (hostname, opts, callback) => {
96
+ lookup(hostname, { all: true }, (err, addresses) => {
97
+ if (err)
98
+ return callback(err, undefined, 0);
99
+ const blocked = addresses.find((a) => isBlocked(a.address));
100
+ if (blocked) {
101
+ return callback(new SafeFetchError('BLOCKED_ADDRESS', `Blocked address ${blocked.address} for ${hostname}`), undefined, 0);
102
+ }
103
+ if (opts?.all)
104
+ return callback(null, addresses);
105
+ const first = addresses[0];
106
+ callback(null, first.address, first.family);
107
+ });
108
+ };
109
+ const agent = new Agent({ connect: { lookup: connectLookup } });
110
+ async function fetchOnce(url, headers, signal) {
111
+ if (!allowedProtocols.includes(url.protocol)) {
112
+ throw new SafeFetchError('PROTOCOL_NOT_ALLOWED', `Protocol ${url.protocol} not allowed`);
113
+ }
114
+ await resolveAndCheck(url.hostname);
115
+ let res;
116
+ try {
117
+ // `request` does not follow redirects; we handle each 3xx hop manually and re-validate it.
118
+ res = await request(url, {
119
+ method: 'GET',
120
+ dispatcher: agent,
121
+ headers,
122
+ signal,
123
+ });
124
+ }
125
+ catch (error) {
126
+ if (error instanceof SafeFetchError)
127
+ throw error;
128
+ // undici wraps a thrown connect-lookup error as the cause.
129
+ const cause = error.cause;
130
+ if (cause instanceof SafeFetchError)
131
+ throw cause;
132
+ if (signal.aborted)
133
+ throw new SafeFetchError('TIMEOUT', `Request to ${url.href} timed out`, { cause: error });
134
+ throw new SafeFetchError('NETWORK', `Request to ${url.href} failed`, { cause: error });
135
+ }
136
+ const chunks = [];
137
+ let total = 0;
138
+ try {
139
+ for await (const chunk of res.body) {
140
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
141
+ total += buf.length;
142
+ if (total > maxBytes) {
143
+ res.body.destroy();
144
+ throw new SafeFetchError('TOO_LARGE', `Response from ${url.href} exceeds ${maxBytes} bytes`);
145
+ }
146
+ chunks.push(buf);
147
+ }
148
+ }
149
+ catch (error) {
150
+ if (error instanceof SafeFetchError)
151
+ throw error;
152
+ if (signal.aborted)
153
+ throw new SafeFetchError('TIMEOUT', `Request to ${url.href} timed out`, { cause: error });
154
+ throw new SafeFetchError('NETWORK', `Reading response from ${url.href} failed`, { cause: error });
155
+ }
156
+ const location = typeof res.headers.location === 'string' ? res.headers.location : undefined;
157
+ return { status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks), finalUrl: url.href, redirects: 0, location };
158
+ }
159
+ return {
160
+ async fetch(rawUrl, req = {}) {
161
+ const controller = new AbortController();
162
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
163
+ try {
164
+ let current = new URL(rawUrl);
165
+ let redirects = 0;
166
+ for (;;) {
167
+ const headers = {
168
+ 'user-agent': userAgent,
169
+ accept: req.accept ?? '*/*',
170
+ ...req.headers,
171
+ };
172
+ // Never forward cookies or credentials.
173
+ delete headers.cookie;
174
+ delete headers.authorization;
175
+ const result = await fetchOnce(current, headers, controller.signal);
176
+ const isRedirect = result.status >= 300 && result.status < 400 && result.location;
177
+ if (!isRedirect) {
178
+ return { status: result.status, headers: result.headers, body: result.body, finalUrl: current.href, redirects };
179
+ }
180
+ redirects += 1;
181
+ if (redirects > maxRedirects) {
182
+ throw new SafeFetchError('TOO_MANY_REDIRECTS', `More than ${maxRedirects} redirects from ${rawUrl}`);
183
+ }
184
+ current = new URL(result.location, current);
185
+ }
186
+ }
187
+ finally {
188
+ clearTimeout(timer);
189
+ }
190
+ },
191
+ };
192
+ }
193
+ //# sourceMappingURL=safe-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-fetch.js","sourceRoot":"","sources":["../../../src/http/safe-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import { hmacSha256, safeEqual } from '../common/crypto.js';
2
+ /**
3
+ * Header contract between the gateway and downstream services.
4
+ *
5
+ * The gateway strips any client-supplied identity headers, then writes these and signs them with
6
+ * `x-gateway-auth` = HMAC-SHA256(secret, canonical string). Services verify the signature and
7
+ * timestamp before trusting anything, so a request that bypasses the gateway on the internal
8
+ * network cannot forge an identity.
9
+ */
10
+ export const IDENTITY_HEADERS = {
11
+ USER_ID: 'x-user-id',
12
+ USER_EMAIL: 'x-user-email',
13
+ USER_ROLES: 'x-user-roles',
14
+ USER_PERMISSIONS: 'x-user-permissions',
15
+ USER_SCOPES: 'x-user-scopes',
16
+ USER_ATTRS: 'x-user-attrs',
17
+ SESSION_ID: 'x-session-id',
18
+ TIMESTAMP: 'x-gateway-ts',
19
+ SIGNATURE: 'x-gateway-auth',
20
+ };
21
+ export const CONTEXT_HEADERS = {
22
+ CORRELATION_ID: 'x-correlation-id',
23
+ ANON_ID: 'x-anon-id',
24
+ TIMEZONE: 'x-timezone',
25
+ CLIENT_IP: 'x-client-ip',
26
+ INTERNAL_SECRET: 'x-internal-service-secret',
27
+ CALLER_SERVICE: 'x-caller-service',
28
+ };
29
+ /** Every header a client must never be allowed to set. */
30
+ export const PROTECTED_HEADERS = [...Object.values(IDENTITY_HEADERS), CONTEXT_HEADERS.INTERNAL_SECRET, CONTEXT_HEADERS.CLIENT_IP];
31
+ const DEFAULT_MAX_SKEW_MS = 5 * 60_000;
32
+ function header(headers, name) {
33
+ const value = headers[name];
34
+ return Array.isArray(value) ? value[0] : value;
35
+ }
36
+ function canonical(values) {
37
+ return [
38
+ values[IDENTITY_HEADERS.TIMESTAMP],
39
+ values[IDENTITY_HEADERS.USER_ID],
40
+ values[IDENTITY_HEADERS.USER_EMAIL],
41
+ values[IDENTITY_HEADERS.USER_ROLES],
42
+ values[IDENTITY_HEADERS.USER_PERMISSIONS],
43
+ values[IDENTITY_HEADERS.USER_SCOPES],
44
+ values[IDENTITY_HEADERS.USER_ATTRS],
45
+ values[IDENTITY_HEADERS.SESSION_ID],
46
+ ].join('\n');
47
+ }
48
+ const encodeList = (items) => items.join(',');
49
+ const decodeList = (value) => (value ? value.split(',').map((s) => s.trim()).filter(Boolean) : []);
50
+ const encodeJson = (value) => Buffer.from(JSON.stringify(value ?? {})).toString('base64url');
51
+ function decodeJson(value, fallback) {
52
+ if (!value)
53
+ return fallback;
54
+ try {
55
+ return JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
56
+ }
57
+ catch {
58
+ return fallback;
59
+ }
60
+ }
61
+ /** Gateway side: headers (including signature) that represent `user`. */
62
+ export function signIdentityHeaders(user, secret, now = Date.now()) {
63
+ const values = {
64
+ [IDENTITY_HEADERS.TIMESTAMP]: String(now),
65
+ [IDENTITY_HEADERS.USER_ID]: user.id,
66
+ [IDENTITY_HEADERS.USER_EMAIL]: user.email ?? '',
67
+ [IDENTITY_HEADERS.USER_ROLES]: encodeList(user.roles),
68
+ [IDENTITY_HEADERS.USER_PERMISSIONS]: encodeList(user.permissions),
69
+ [IDENTITY_HEADERS.USER_SCOPES]: encodeJson(user.scopes),
70
+ [IDENTITY_HEADERS.USER_ATTRS]: encodeJson(user.attrs),
71
+ [IDENTITY_HEADERS.SESSION_ID]: user.sessionId ?? '',
72
+ };
73
+ values[IDENTITY_HEADERS.SIGNATURE] = hmacSha256(secret, canonical(values));
74
+ return values;
75
+ }
76
+ /** Service side: verifies and decodes the gateway identity headers. */
77
+ export function verifyIdentityHeaders(headers, secret, options = {}) {
78
+ const userId = header(headers, IDENTITY_HEADERS.USER_ID);
79
+ if (!userId)
80
+ return { status: 'anonymous' };
81
+ const values = {};
82
+ for (const name of Object.values(IDENTITY_HEADERS))
83
+ values[name] = header(headers, name) ?? '';
84
+ const signature = values[IDENTITY_HEADERS.SIGNATURE];
85
+ if (!signature || !safeEqual(signature, hmacSha256(secret, canonical(values)))) {
86
+ return { status: 'invalid', reason: 'bad-signature' };
87
+ }
88
+ const ts = Number(values[IDENTITY_HEADERS.TIMESTAMP]);
89
+ const now = options.now ?? Date.now();
90
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > (options.maxSkewMs ?? DEFAULT_MAX_SKEW_MS)) {
91
+ return { status: 'invalid', reason: 'stale-signature' };
92
+ }
93
+ return {
94
+ status: 'authenticated',
95
+ user: {
96
+ id: userId,
97
+ email: values[IDENTITY_HEADERS.USER_EMAIL] ?? '',
98
+ roles: decodeList(values[IDENTITY_HEADERS.USER_ROLES]),
99
+ permissions: decodeList(values[IDENTITY_HEADERS.USER_PERMISSIONS]),
100
+ scopes: decodeJson(values[IDENTITY_HEADERS.USER_SCOPES], {}),
101
+ attrs: decodeJson(values[IDENTITY_HEADERS.USER_ATTRS], {}),
102
+ sessionId: values[IDENTITY_HEADERS.SESSION_ID] || undefined,
103
+ },
104
+ };
105
+ }
106
+ /** Removes every protected header in place (gateway ingress). */
107
+ export function stripProtectedHeaders(headers) {
108
+ for (const name of PROTECTED_HEADERS)
109
+ delete headers[name];
110
+ }
111
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/identity/index.ts"],"names":[],"mappings":"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"}