@heavybit/shared-lib 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) 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 +20 -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/http/index.js +90 -0
  52. package/dist/cjs/http/index.js.map +1 -0
  53. package/dist/cjs/http/safe-fetch.js +199 -0
  54. package/dist/cjs/http/safe-fetch.js.map +1 -0
  55. package/dist/cjs/identity/index.js +117 -0
  56. package/dist/cjs/identity/index.js.map +1 -0
  57. package/dist/cjs/index.js +29 -0
  58. package/dist/cjs/index.js.map +1 -0
  59. package/dist/cjs/knex/index.js +76 -0
  60. package/dist/cjs/knex/index.js.map +1 -0
  61. package/dist/cjs/logging/index.js +75 -0
  62. package/dist/cjs/logging/index.js.map +1 -0
  63. package/dist/cjs/messaging/connection.js +115 -0
  64. package/dist/cjs/messaging/connection.js.map +1 -0
  65. package/dist/cjs/messaging/consumer.js +134 -0
  66. package/dist/cjs/messaging/consumer.js.map +1 -0
  67. package/dist/cjs/messaging/envelope.js +23 -0
  68. package/dist/cjs/messaging/envelope.js.map +1 -0
  69. package/dist/cjs/messaging/idempotency.js +57 -0
  70. package/dist/cjs/messaging/idempotency.js.map +1 -0
  71. package/dist/cjs/messaging/index.js +62 -0
  72. package/dist/cjs/messaging/index.js.map +1 -0
  73. package/dist/cjs/messaging/monitor.js +48 -0
  74. package/dist/cjs/messaging/monitor.js.map +1 -0
  75. package/dist/cjs/messaging/outbox.js +135 -0
  76. package/dist/cjs/messaging/outbox.js.map +1 -0
  77. package/dist/cjs/messaging/publisher.js +149 -0
  78. package/dist/cjs/messaging/publisher.js.map +1 -0
  79. package/dist/cjs/messaging/rpc.js +125 -0
  80. package/dist/cjs/messaging/rpc.js.map +1 -0
  81. package/dist/cjs/messaging/staff-notifier.js +78 -0
  82. package/dist/cjs/messaging/staff-notifier.js.map +1 -0
  83. package/dist/cjs/messaging/topology.js +22 -0
  84. package/dist/cjs/messaging/topology.js.map +1 -0
  85. package/dist/cjs/package.json +1 -0
  86. package/dist/cjs/rate-limit/index.js +61 -0
  87. package/dist/cjs/rate-limit/index.js.map +1 -0
  88. package/dist/cjs/scheduling/index.js +88 -0
  89. package/dist/cjs/scheduling/index.js.map +1 -0
  90. package/dist/cjs/security/index.js +39 -0
  91. package/dist/cjs/security/index.js.map +1 -0
  92. package/dist/cjs/socket/index.js +53 -0
  93. package/dist/cjs/socket/index.js.map +1 -0
  94. package/dist/cjs/testing/index.js +73 -0
  95. package/dist/cjs/testing/index.js.map +1 -0
  96. package/dist/cjs/types/index.js +3 -0
  97. package/dist/cjs/types/index.js.map +1 -0
  98. package/dist/cjs/validation/index.js +41 -0
  99. package/dist/cjs/validation/index.js.map +1 -0
  100. package/dist/esm/access/index.js +104 -0
  101. package/dist/esm/access/index.js.map +1 -0
  102. package/dist/esm/alerting/index.js +52 -0
  103. package/dist/esm/alerting/index.js.map +1 -0
  104. package/dist/esm/cache/index.js +163 -0
  105. package/dist/esm/cache/index.js.map +1 -0
  106. package/dist/esm/common/async.js +33 -0
  107. package/dist/esm/common/async.js.map +1 -0
  108. package/dist/esm/common/audit.js +18 -0
  109. package/dist/esm/common/audit.js.map +1 -0
  110. package/dist/esm/common/crypto.js +64 -0
  111. package/dist/esm/common/crypto.js.map +1 -0
  112. package/dist/esm/common/errors.js +75 -0
  113. package/dist/esm/common/errors.js.map +1 -0
  114. package/dist/esm/common/id.js +33 -0
  115. package/dist/esm/common/id.js.map +1 -0
  116. package/dist/esm/common/index.js +12 -0
  117. package/dist/esm/common/index.js.map +1 -0
  118. package/dist/esm/common/jwt.js +49 -0
  119. package/dist/esm/common/jwt.js.map +1 -0
  120. package/dist/esm/common/password.js +30 -0
  121. package/dist/esm/common/password.js.map +1 -0
  122. package/dist/esm/common/phone.js +33 -0
  123. package/dist/esm/common/phone.js.map +1 -0
  124. package/dist/esm/common/response.js +24 -0
  125. package/dist/esm/common/response.js.map +1 -0
  126. package/dist/esm/common/text.js +39 -0
  127. package/dist/esm/common/text.js.map +1 -0
  128. package/dist/esm/common/time.js +96 -0
  129. package/dist/esm/common/time.js.map +1 -0
  130. package/dist/esm/config/index.js +88 -0
  131. package/dist/esm/config/index.js.map +1 -0
  132. package/dist/esm/contracts/editorial-draft.js +108 -0
  133. package/dist/esm/contracts/editorial-draft.js.map +1 -0
  134. package/dist/esm/contracts/index.js +4 -0
  135. package/dist/esm/contracts/index.js.map +1 -0
  136. package/dist/esm/contracts/notification-dispatch.js +89 -0
  137. package/dist/esm/contracts/notification-dispatch.js.map +1 -0
  138. package/dist/esm/contracts/upload-import.js +51 -0
  139. package/dist/esm/contracts/upload-import.js.map +1 -0
  140. package/dist/esm/database/index.js +48 -0
  141. package/dist/esm/database/index.js.map +1 -0
  142. package/dist/esm/express/auth.js +63 -0
  143. package/dist/esm/express/auth.js.map +1 -0
  144. package/dist/esm/express/health.js +27 -0
  145. package/dist/esm/express/health.js.map +1 -0
  146. package/dist/esm/express/index.js +4 -0
  147. package/dist/esm/express/index.js.map +1 -0
  148. package/dist/esm/express/middleware.js +84 -0
  149. package/dist/esm/express/middleware.js.map +1 -0
  150. package/dist/esm/http/index.js +73 -0
  151. package/dist/esm/http/index.js.map +1 -0
  152. package/dist/esm/http/safe-fetch.js +193 -0
  153. package/dist/esm/http/safe-fetch.js.map +1 -0
  154. package/dist/esm/identity/index.js +111 -0
  155. package/dist/esm/identity/index.js.map +1 -0
  156. package/dist/esm/index.js +13 -0
  157. package/dist/esm/index.js.map +1 -0
  158. package/dist/esm/knex/index.js +66 -0
  159. package/dist/esm/knex/index.js.map +1 -0
  160. package/dist/esm/logging/index.js +67 -0
  161. package/dist/esm/logging/index.js.map +1 -0
  162. package/dist/esm/messaging/connection.js +108 -0
  163. package/dist/esm/messaging/connection.js.map +1 -0
  164. package/dist/esm/messaging/consumer.js +130 -0
  165. package/dist/esm/messaging/consumer.js.map +1 -0
  166. package/dist/esm/messaging/envelope.js +19 -0
  167. package/dist/esm/messaging/envelope.js.map +1 -0
  168. package/dist/esm/messaging/idempotency.js +52 -0
  169. package/dist/esm/messaging/idempotency.js.map +1 -0
  170. package/dist/esm/messaging/index.js +45 -0
  171. package/dist/esm/messaging/index.js.map +1 -0
  172. package/dist/esm/messaging/monitor.js +44 -0
  173. package/dist/esm/messaging/monitor.js.map +1 -0
  174. package/dist/esm/messaging/outbox.js +129 -0
  175. package/dist/esm/messaging/outbox.js.map +1 -0
  176. package/dist/esm/messaging/publisher.js +142 -0
  177. package/dist/esm/messaging/publisher.js.map +1 -0
  178. package/dist/esm/messaging/rpc.js +120 -0
  179. package/dist/esm/messaging/rpc.js.map +1 -0
  180. package/dist/esm/messaging/staff-notifier.js +73 -0
  181. package/dist/esm/messaging/staff-notifier.js.map +1 -0
  182. package/dist/esm/messaging/topology.js +16 -0
  183. package/dist/esm/messaging/topology.js.map +1 -0
  184. package/dist/esm/package.json +1 -0
  185. package/dist/esm/rate-limit/index.js +56 -0
  186. package/dist/esm/rate-limit/index.js.map +1 -0
  187. package/dist/esm/scheduling/index.js +83 -0
  188. package/dist/esm/scheduling/index.js.map +1 -0
  189. package/dist/esm/security/index.js +36 -0
  190. package/dist/esm/security/index.js.map +1 -0
  191. package/dist/esm/socket/index.js +47 -0
  192. package/dist/esm/socket/index.js.map +1 -0
  193. package/dist/esm/testing/index.js +66 -0
  194. package/dist/esm/testing/index.js.map +1 -0
  195. package/dist/esm/types/index.js +2 -0
  196. package/dist/esm/types/index.js.map +1 -0
  197. package/dist/esm/validation/index.js +36 -0
  198. package/dist/esm/validation/index.js.map +1 -0
  199. package/dist/types/access/index.d.ts +50 -0
  200. package/dist/types/access/index.d.ts.map +1 -0
  201. package/dist/types/alerting/index.d.ts +43 -0
  202. package/dist/types/alerting/index.d.ts.map +1 -0
  203. package/dist/types/cache/index.d.ts +45 -0
  204. package/dist/types/cache/index.d.ts.map +1 -0
  205. package/dist/types/common/async.d.ts +14 -0
  206. package/dist/types/common/async.d.ts.map +1 -0
  207. package/dist/types/common/audit.d.ts +17 -0
  208. package/dist/types/common/audit.d.ts.map +1 -0
  209. package/dist/types/common/crypto.d.ts +18 -0
  210. package/dist/types/common/crypto.d.ts.map +1 -0
  211. package/dist/types/common/errors.d.ts +71 -0
  212. package/dist/types/common/errors.d.ts.map +1 -0
  213. package/dist/types/common/id.d.ts +14 -0
  214. package/dist/types/common/id.d.ts.map +1 -0
  215. package/dist/types/common/index.d.ts +12 -0
  216. package/dist/types/common/index.d.ts.map +1 -0
  217. package/dist/types/common/jwt.d.ts +28 -0
  218. package/dist/types/common/jwt.d.ts.map +1 -0
  219. package/dist/types/common/password.d.ts +4 -0
  220. package/dist/types/common/password.d.ts.map +1 -0
  221. package/dist/types/common/phone.d.ts +10 -0
  222. package/dist/types/common/phone.d.ts.map +1 -0
  223. package/dist/types/common/response.d.ts +12 -0
  224. package/dist/types/common/response.d.ts.map +1 -0
  225. package/dist/types/common/text.d.ts +10 -0
  226. package/dist/types/common/text.d.ts.map +1 -0
  227. package/dist/types/common/time.d.ts +40 -0
  228. package/dist/types/common/time.d.ts.map +1 -0
  229. package/dist/types/config/index.d.ts +115 -0
  230. package/dist/types/config/index.d.ts.map +1 -0
  231. package/dist/types/contracts/editorial-draft.d.ts +577 -0
  232. package/dist/types/contracts/editorial-draft.d.ts.map +1 -0
  233. package/dist/types/contracts/index.d.ts +4 -0
  234. package/dist/types/contracts/index.d.ts.map +1 -0
  235. package/dist/types/contracts/notification-dispatch.d.ts +175 -0
  236. package/dist/types/contracts/notification-dispatch.d.ts.map +1 -0
  237. package/dist/types/contracts/upload-import.d.ts +96 -0
  238. package/dist/types/contracts/upload-import.d.ts.map +1 -0
  239. package/dist/types/database/index.d.ts +25 -0
  240. package/dist/types/database/index.d.ts.map +1 -0
  241. package/dist/types/express/auth.d.ts +27 -0
  242. package/dist/types/express/auth.d.ts.map +1 -0
  243. package/dist/types/express/health.d.ts +14 -0
  244. package/dist/types/express/health.d.ts.map +1 -0
  245. package/dist/types/express/index.d.ts +4 -0
  246. package/dist/types/express/index.d.ts.map +1 -0
  247. package/dist/types/express/middleware.d.ts +20 -0
  248. package/dist/types/express/middleware.d.ts.map +1 -0
  249. package/dist/types/http/index.d.ts +34 -0
  250. package/dist/types/http/index.d.ts.map +1 -0
  251. package/dist/types/http/safe-fetch.d.ts +58 -0
  252. package/dist/types/http/safe-fetch.d.ts.map +1 -0
  253. package/dist/types/identity/index.d.ts +51 -0
  254. package/dist/types/identity/index.d.ts.map +1 -0
  255. package/dist/types/index.d.ts +10 -0
  256. package/dist/types/index.d.ts.map +1 -0
  257. package/dist/types/knex/index.d.ts +25 -0
  258. package/dist/types/knex/index.d.ts.map +1 -0
  259. package/dist/types/logging/index.d.ts +22 -0
  260. package/dist/types/logging/index.d.ts.map +1 -0
  261. package/dist/types/messaging/connection.d.ts +48 -0
  262. package/dist/types/messaging/connection.d.ts.map +1 -0
  263. package/dist/types/messaging/consumer.d.ts +56 -0
  264. package/dist/types/messaging/consumer.d.ts.map +1 -0
  265. package/dist/types/messaging/envelope.d.ts +16 -0
  266. package/dist/types/messaging/envelope.d.ts.map +1 -0
  267. package/dist/types/messaging/idempotency.d.ts +29 -0
  268. package/dist/types/messaging/idempotency.d.ts.map +1 -0
  269. package/dist/types/messaging/index.d.ts +39 -0
  270. package/dist/types/messaging/index.d.ts.map +1 -0
  271. package/dist/types/messaging/monitor.d.ts +23 -0
  272. package/dist/types/messaging/monitor.d.ts.map +1 -0
  273. package/dist/types/messaging/outbox.d.ts +51 -0
  274. package/dist/types/messaging/outbox.d.ts.map +1 -0
  275. package/dist/types/messaging/publisher.d.ts +84 -0
  276. package/dist/types/messaging/publisher.d.ts.map +1 -0
  277. package/dist/types/messaging/rpc.d.ts +46 -0
  278. package/dist/types/messaging/rpc.d.ts.map +1 -0
  279. package/dist/types/messaging/staff-notifier.d.ts +56 -0
  280. package/dist/types/messaging/staff-notifier.d.ts.map +1 -0
  281. package/dist/types/messaging/topology.d.ts +23 -0
  282. package/dist/types/messaging/topology.d.ts.map +1 -0
  283. package/dist/types/rate-limit/index.d.ts +28 -0
  284. package/dist/types/rate-limit/index.d.ts.map +1 -0
  285. package/dist/types/scheduling/index.d.ts +33 -0
  286. package/dist/types/scheduling/index.d.ts.map +1 -0
  287. package/dist/types/security/index.d.ts +20 -0
  288. package/dist/types/security/index.d.ts.map +1 -0
  289. package/dist/types/socket/index.d.ts +30 -0
  290. package/dist/types/socket/index.d.ts.map +1 -0
  291. package/dist/types/testing/index.d.ts +41 -0
  292. package/dist/types/testing/index.d.ts.map +1 -0
  293. package/dist/types/types/index.d.ts +65 -0
  294. package/dist/types/types/index.d.ts.map +1 -0
  295. package/dist/types/validation/index.d.ts +34 -0
  296. package/dist/types/validation/index.d.ts.map +1 -0
  297. package/package.json +189 -4
  298. package/README.md +0 -3
@@ -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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"}
@@ -0,0 +1,13 @@
1
+ // Root entry: dependency-light building blocks usable from any Node service.
2
+ // Modules that need optional peers (express, ioredis, knex, socket.io, rate-limiter-flexible)
3
+ // live behind subpaths: `@heavybit/shared-lib/express`, `/cache`, `/messaging`, …
4
+ export * from './types/index.js';
5
+ export * from './common/index.js';
6
+ export * from './identity/index.js';
7
+ export * from './access/index.js';
8
+ export * from './validation/index.js';
9
+ export * from './logging/index.js';
10
+ export * from './config/index.js';
11
+ export * from './contracts/index.js';
12
+ export * from './alerting/index.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,8FAA8F;AAC9F,kFAAkF;AAClF,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Migration helpers (usable from CommonJS `.cjs` migrations via `require('@heavybit/shared-lib/knex')`).
3
+ * They keep ID, timestamp and audit column definitions identical across services.
4
+ */
5
+ /** `id CHAR(26)` primary key for ULIDs. */
6
+ export function ulidPrimary(table, name = 'id') {
7
+ table.specificType(name, 'char(26)').notNullable().primary();
8
+ }
9
+ /** `CHAR(26)` column referencing another ULID (FK constraints only within the same database). */
10
+ export function ulidColumn(table, name) {
11
+ return table.specificType(name, 'char(26)');
12
+ }
13
+ /** `created_at` / `updated_at` as `DATETIME(3)` in UTC, maintained by MySQL. */
14
+ export function timestamps(table, knex) {
15
+ table.specificType('created_at', 'datetime(3)').notNullable().defaultTo(knex.raw('CURRENT_TIMESTAMP(3)'));
16
+ table.specificType('updated_at', 'datetime(3)').notNullable().defaultTo(knex.raw('CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)'));
17
+ }
18
+ /** `created_by` / `updated_by` actor columns (null for system operations). */
19
+ export function actorColumns(table) {
20
+ ulidColumn(table, 'created_by').nullable();
21
+ ulidColumn(table, 'updated_by').nullable();
22
+ }
23
+ /** Nullable `DATETIME(3)` column. */
24
+ export function datetime(table, name) {
25
+ return table.specificType(name, 'datetime(3)');
26
+ }
27
+ /**
28
+ * Creates `<parent>_audits`: every parent column (defined again by `columns`) plus `parent_id`
29
+ * and `action`. Audit rows are append-only history, so no unique constraints are copied.
30
+ */
31
+ export async function createAuditTable(knex, parentTable, columns) {
32
+ const name = `${parentTable}_audits`;
33
+ if (await knex.schema.hasTable(name))
34
+ return;
35
+ await knex.schema.createTable(name, (table) => {
36
+ ulidPrimary(table);
37
+ ulidColumn(table, 'parent_id').notNullable().index();
38
+ table.enu('action', ['create', 'update', 'delete']).notNullable();
39
+ columns(table);
40
+ actorColumns(table);
41
+ timestamps(table, knex);
42
+ table.index(['parent_id', 'created_at']);
43
+ });
44
+ }
45
+ /** Transactional outbox table used by `OutboxRelay`. */
46
+ export async function createOutboxTable(knex, name = 'event_outbox') {
47
+ if (await knex.schema.hasTable(name))
48
+ return;
49
+ await knex.schema.createTable(name, (table) => {
50
+ ulidPrimary(table);
51
+ table.string('routing_key', 191).notNullable();
52
+ table.json('payload').notNullable();
53
+ table.integer('attempts').unsigned().notNullable().defaultTo(0);
54
+ table.string('last_error', 500).nullable();
55
+ table.specificType('created_at', 'datetime(3)').notNullable().defaultTo(knex.raw('CURRENT_TIMESTAMP(3)'));
56
+ datetime(table, 'published_at').nullable();
57
+ table.index(['published_at', 'id']);
58
+ });
59
+ }
60
+ /** Standard table options: InnoDB, utf8mb4. Call inside `createTable`. */
61
+ export function utf8mb4(table) {
62
+ table.engine('InnoDB');
63
+ table.charset('utf8mb4');
64
+ table.collate('utf8mb4_unicode_ci');
65
+ }
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/knex/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import winston from 'winston';
2
+ const { combine, timestamp, json, errors, colorize, printf, splat } = winston.format;
3
+ /** Keys whose values are replaced with `[REDACTED]` anywhere in log metadata. */
4
+ export const DEFAULT_REDACT_KEYS = [
5
+ 'password', 'newPassword', 'currentPassword', 'passwordHash', 'token', 'accessToken', 'refreshToken',
6
+ 'secret', 'clientSecret', 'apiKey', 'authorization', 'cookie', 'otp', 'pass', 'smtpPassword',
7
+ ];
8
+ function redactValue(value, keys, depth = 0) {
9
+ if (depth > 6 || value === null || typeof value !== 'object')
10
+ return value;
11
+ if (Array.isArray(value))
12
+ return value.map((v) => redactValue(v, keys, depth + 1));
13
+ if (value instanceof Error)
14
+ return value;
15
+ const out = {};
16
+ for (const [k, v] of Object.entries(value)) {
17
+ out[k] = keys.has(k) || k.endsWith('Enc') || k.endsWith('_enc') ? '[REDACTED]' : redactValue(v, keys, depth + 1);
18
+ }
19
+ return out;
20
+ }
21
+ const redact = (keys) => winston.format((info) => {
22
+ for (const k of Object.keys(info)) {
23
+ if (keys.has(k))
24
+ info[k] = '[REDACTED]';
25
+ else if (k !== 'message' && k !== 'level')
26
+ info[k] = redactValue(info[k], keys);
27
+ }
28
+ return info;
29
+ })();
30
+ const devFormat = printf((info) => {
31
+ const { level, message, timestamp: ts, service, stack, ...rest } = info;
32
+ const meta = Object.fromEntries(Object.entries(rest).filter(([k]) => typeof k === 'string' && k !== 'splat'));
33
+ const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';
34
+ return `${String(ts ?? '')} ${String(level)}: ${service ? `[${String(service)}] ` : ''}${String(message)}${metaStr}${stack ? `\n${String(stack)}` : ''}`;
35
+ });
36
+ /** Structured logger with secret redaction. JSON to stdout in production, pretty otherwise. */
37
+ export function createLogger(service, options = {}) {
38
+ const isProduction = process.env.NODE_ENV === 'production';
39
+ const level = options.level ?? process.env.LOG_LEVEL ?? (isProduction ? 'info' : 'debug');
40
+ const format = options.format ?? process.env.LOG_FORMAT ?? (isProduction ? 'json' : 'pretty');
41
+ const keys = new Set([...DEFAULT_REDACT_KEYS, ...(options.redactKeys ?? [])]);
42
+ return winston.createLogger({
43
+ level,
44
+ defaultMeta: { service },
45
+ format: combine(errors({ stack: true }), splat(), redact(keys), timestamp()),
46
+ transports: [
47
+ new winston.transports.Console({
48
+ silent: process.env.NODE_ENV === 'test' && !process.env.LOG_IN_TESTS,
49
+ format: format === 'json' ? json() : combine(colorize({ level: true }), devFormat),
50
+ }),
51
+ ],
52
+ });
53
+ }
54
+ /** Logger that discards everything — handy default for libraries and tests. */
55
+ export const silentLogger = { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } };
56
+ /**
57
+ * A logger that adds `context` (for example pipeline IDs) to every line it writes; a line's own
58
+ * meta wins on a key clash. `undefined` context values are dropped so lines never carry empty keys.
59
+ * Works over any {@link Logger} (winston, a test spy, or another child), so handlers can create one
60
+ * per message and pass it down.
61
+ */
62
+ export function childLogger(logger, context) {
63
+ const base = Object.fromEntries(Object.entries(context).filter(([, v]) => v !== undefined));
64
+ const write = (level) => (message, meta) => logger[level](message, { ...base, ...meta });
65
+ return { debug: write('debug'), info: write('info'), warn: write('warn'), error: write('error') };
66
+ }
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/logging/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ import amqp from 'amqplib';
2
+ import { sleep } from '../common/async.js';
3
+ import { silentLogger } from '../logging/index.js';
4
+ /**
5
+ * One AMQP connection per process with automatic reconnect. Components register channels
6
+ * with a `setup` callback (assert topology, start consuming); setups re-run after every
7
+ * reconnect so consumers and publishers heal without restarts.
8
+ */
9
+ export class AmqpConnection {
10
+ options;
11
+ model = null;
12
+ registrations = [];
13
+ closing = false;
14
+ connecting = null;
15
+ logger;
16
+ constructor(options) {
17
+ this.options = options;
18
+ this.logger = options.logger ?? silentLogger;
19
+ }
20
+ get isConnected() {
21
+ return this.model !== null;
22
+ }
23
+ /** Registers a channel. If already connected it is opened immediately. */
24
+ async registerChannel(name, setup, options = {}) {
25
+ const registration = { name, confirm: options.confirm ?? false, setup: setup, channel: null };
26
+ this.registrations.push(registration);
27
+ if (this.model)
28
+ await this.openChannel(registration);
29
+ return { name, current: () => registration.channel };
30
+ }
31
+ /**
32
+ * Connects, retrying with back-off until it succeeds or `close()` is called. Resolves once the
33
+ * first connection is up; later drops reconnect in the background.
34
+ */
35
+ connect() {
36
+ if (!this.connecting) {
37
+ this.connecting = this.connectWithRetry().finally(() => { this.connecting = null; });
38
+ }
39
+ return this.connecting;
40
+ }
41
+ /** Throws when the broker is not reachable — used by readiness checks. */
42
+ assertReady() {
43
+ if (!this.model)
44
+ throw new Error('RabbitMQ is not connected');
45
+ }
46
+ async close() {
47
+ this.closing = true;
48
+ for (const reg of this.registrations) {
49
+ await reg.channel?.close().catch(() => undefined);
50
+ reg.channel = null;
51
+ }
52
+ await this.model?.close().catch(() => undefined);
53
+ this.model = null;
54
+ }
55
+ async connectWithRetry() {
56
+ const delay = this.options.reconnectDelayMs ?? ((n) => Math.min(30_000, 1000 * 2 ** (n - 1)));
57
+ for (let attempt = 1; !this.closing; attempt += 1) {
58
+ try {
59
+ await this.establish();
60
+ return;
61
+ }
62
+ catch (error) {
63
+ this.logger.warn('RabbitMQ connect failed; retrying', { attempt, error: error.message });
64
+ await sleep(delay(attempt));
65
+ }
66
+ }
67
+ }
68
+ async establish() {
69
+ const model = await amqp.connect(this.options.url, {
70
+ clientProperties: { connection_name: this.options.connectionName },
71
+ heartbeat: this.options.heartbeatSec ?? 30,
72
+ });
73
+ model.on('error', (error) => this.logger.error('RabbitMQ connection error', { error: error.message }));
74
+ model.on('close', () => {
75
+ if (this.model !== model)
76
+ return;
77
+ this.model = null;
78
+ for (const reg of this.registrations)
79
+ reg.channel = null;
80
+ if (!this.closing) {
81
+ this.logger.warn('RabbitMQ connection closed; reconnecting');
82
+ void this.connect();
83
+ }
84
+ });
85
+ this.model = model;
86
+ for (const reg of this.registrations)
87
+ await this.openChannel(reg);
88
+ this.logger.info('Connected to RabbitMQ', { connection: this.options.connectionName, channels: this.registrations.length });
89
+ }
90
+ async openChannel(reg) {
91
+ if (!this.model)
92
+ return;
93
+ const channel = reg.confirm ? await this.model.createConfirmChannel() : await this.model.createChannel();
94
+ channel.on('error', (error) => this.logger.error('RabbitMQ channel error', { channel: reg.name, error: error.message }));
95
+ channel.on('close', () => {
96
+ if (reg.channel !== channel)
97
+ return;
98
+ reg.channel = null;
99
+ // A channel can close on its own (e.g. precondition failure) while the connection lives on.
100
+ if (this.model && !this.closing) {
101
+ setTimeout(() => { void this.openChannel(reg).catch((e) => this.logger.error('Channel reopen failed', { channel: reg.name, error: e.message })); }, 1000);
102
+ }
103
+ });
104
+ await reg.setup(channel);
105
+ reg.channel = channel;
106
+ }
107
+ }
108
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../../src/messaging/connection.ts"],"names":[],"mappings":"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"}