@xtandard/webhooks 0.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 (279) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +315 -0
  3. package/bin/xtandard-webhooks.mjs +3 -0
  4. package/dist/basic-BIW3Rvuz.cjs +199 -0
  5. package/dist/basic-BIW3Rvuz.cjs.map +1 -0
  6. package/dist/basic-DKk0Xfuu.mjs +176 -0
  7. package/dist/basic-DKk0Xfuu.mjs.map +1 -0
  8. package/dist/chunk-D7D4PA-g.mjs +13 -0
  9. package/dist/cli.cjs +655 -0
  10. package/dist/cli.cjs.map +1 -0
  11. package/dist/cli.d.cts +42 -0
  12. package/dist/cli.d.mts +42 -0
  13. package/dist/cli.mjs +653 -0
  14. package/dist/cli.mjs.map +1 -0
  15. package/dist/contract-8h-Azxa5.d.cts +71 -0
  16. package/dist/contract-9XpcwcCn.mjs +22 -0
  17. package/dist/contract-9XpcwcCn.mjs.map +1 -0
  18. package/dist/contract-B2d5dNU3.cjs +33 -0
  19. package/dist/contract-B2d5dNU3.cjs.map +1 -0
  20. package/dist/contract-BEhDcd_5.mjs +28 -0
  21. package/dist/contract-BEhDcd_5.mjs.map +1 -0
  22. package/dist/contract-Bf1qguwt.cjs +57 -0
  23. package/dist/contract-Bf1qguwt.cjs.map +1 -0
  24. package/dist/contract-Bnb3fgRJ.d.cts +177 -0
  25. package/dist/contract-C2r2Xzwp.d.mts +46 -0
  26. package/dist/contract-CiPskNvS.d.cts +46 -0
  27. package/dist/contract-DhQ4JjGG.d.mts +71 -0
  28. package/dist/contract-T1kcZNdG.d.mts +177 -0
  29. package/dist/contract-lETlIuXo.d.cts +30 -0
  30. package/dist/contract-lETlIuXo.d.mts +30 -0
  31. package/dist/core-CMpnmI5Q.mjs +1605 -0
  32. package/dist/core-CMpnmI5Q.mjs.map +1 -0
  33. package/dist/core-DT4ppWh8.d.mts +502 -0
  34. package/dist/core-KJawHjFF.d.cts +502 -0
  35. package/dist/core-ZGhH6Vs2.cjs +1790 -0
  36. package/dist/core-ZGhH6Vs2.cjs.map +1 -0
  37. package/dist/core.cjs +8 -0
  38. package/dist/core.d.cts +2 -0
  39. package/dist/core.d.mts +2 -0
  40. package/dist/core.mjs +2 -0
  41. package/dist/create-fetch-handler-BIdk9P30.mjs +1724 -0
  42. package/dist/create-fetch-handler-BIdk9P30.mjs.map +1 -0
  43. package/dist/create-fetch-handler-CmooujQo.cjs +1771 -0
  44. package/dist/create-fetch-handler-CmooujQo.cjs.map +1 -0
  45. package/dist/create-fetch-handler-Dlkhustu.d.cts +162 -0
  46. package/dist/create-fetch-handler-jy3hy5nZ.d.mts +162 -0
  47. package/dist/dispatcher-B0xTEHt1.cjs +212 -0
  48. package/dist/dispatcher-B0xTEHt1.cjs.map +1 -0
  49. package/dist/dispatcher-Coubwrka.mjs +196 -0
  50. package/dist/dispatcher-Coubwrka.mjs.map +1 -0
  51. package/dist/entry-auth-basic.cjs +5 -0
  52. package/dist/entry-auth-basic.d.cts +83 -0
  53. package/dist/entry-auth-basic.d.mts +83 -0
  54. package/dist/entry-auth-basic.mjs +2 -0
  55. package/dist/entry-auth-delegated.cjs +28 -0
  56. package/dist/entry-auth-delegated.cjs.map +1 -0
  57. package/dist/entry-auth-delegated.d.cts +36 -0
  58. package/dist/entry-auth-delegated.d.mts +36 -0
  59. package/dist/entry-auth-delegated.mjs +27 -0
  60. package/dist/entry-auth-delegated.mjs.map +1 -0
  61. package/dist/entry-auth-none.cjs +4 -0
  62. package/dist/entry-auth-none.d.cts +25 -0
  63. package/dist/entry-auth-none.d.mts +25 -0
  64. package/dist/entry-auth-none.mjs +2 -0
  65. package/dist/entry-authorization-delegated.cjs +27 -0
  66. package/dist/entry-authorization-delegated.cjs.map +1 -0
  67. package/dist/entry-authorization-delegated.d.cts +31 -0
  68. package/dist/entry-authorization-delegated.d.mts +31 -0
  69. package/dist/entry-authorization-delegated.mjs +26 -0
  70. package/dist/entry-authorization-delegated.mjs.map +1 -0
  71. package/dist/entry-authorization-none.cjs +3 -0
  72. package/dist/entry-authorization-none.d.cts +18 -0
  73. package/dist/entry-authorization-none.d.mts +18 -0
  74. package/dist/entry-authorization-none.mjs +2 -0
  75. package/dist/entry-authorization-roles.cjs +6 -0
  76. package/dist/entry-authorization-roles.d.cts +65 -0
  77. package/dist/entry-authorization-roles.d.mts +65 -0
  78. package/dist/entry-authorization-roles.mjs +2 -0
  79. package/dist/entry-bun.cjs +24 -0
  80. package/dist/entry-bun.cjs.map +1 -0
  81. package/dist/entry-bun.d.cts +8 -0
  82. package/dist/entry-bun.d.mts +8 -0
  83. package/dist/entry-bun.mjs +23 -0
  84. package/dist/entry-bun.mjs.map +1 -0
  85. package/dist/entry-drizzle-mysql.cjs +20 -0
  86. package/dist/entry-drizzle-mysql.cjs.map +1 -0
  87. package/dist/entry-drizzle-mysql.d.cts +27 -0
  88. package/dist/entry-drizzle-mysql.d.mts +27 -0
  89. package/dist/entry-drizzle-mysql.mjs +19 -0
  90. package/dist/entry-drizzle-mysql.mjs.map +1 -0
  91. package/dist/entry-drizzle-pg.cjs +21 -0
  92. package/dist/entry-drizzle-pg.cjs.map +1 -0
  93. package/dist/entry-drizzle-pg.d.cts +26 -0
  94. package/dist/entry-drizzle-pg.d.mts +26 -0
  95. package/dist/entry-drizzle-pg.mjs +20 -0
  96. package/dist/entry-drizzle-pg.mjs.map +1 -0
  97. package/dist/entry-drizzle-sqlite.cjs +21 -0
  98. package/dist/entry-drizzle-sqlite.cjs.map +1 -0
  99. package/dist/entry-drizzle-sqlite.d.cts +23 -0
  100. package/dist/entry-drizzle-sqlite.d.mts +23 -0
  101. package/dist/entry-drizzle-sqlite.mjs +20 -0
  102. package/dist/entry-drizzle-sqlite.mjs.map +1 -0
  103. package/dist/entry-elysia.cjs +125 -0
  104. package/dist/entry-elysia.cjs.map +1 -0
  105. package/dist/entry-elysia.d.cts +1017 -0
  106. package/dist/entry-elysia.d.mts +1017 -0
  107. package/dist/entry-elysia.mjs +123 -0
  108. package/dist/entry-elysia.mjs.map +1 -0
  109. package/dist/entry-express.cjs +57 -0
  110. package/dist/entry-express.cjs.map +1 -0
  111. package/dist/entry-express.d.cts +15 -0
  112. package/dist/entry-express.d.mts +15 -0
  113. package/dist/entry-express.mjs +56 -0
  114. package/dist/entry-express.mjs.map +1 -0
  115. package/dist/entry-hono.cjs +35 -0
  116. package/dist/entry-hono.cjs.map +1 -0
  117. package/dist/entry-hono.d.cts +16 -0
  118. package/dist/entry-hono.d.mts +16 -0
  119. package/dist/entry-hono.mjs +34 -0
  120. package/dist/entry-hono.mjs.map +1 -0
  121. package/dist/entry-hooks-log.cjs +22 -0
  122. package/dist/entry-hooks-log.cjs.map +1 -0
  123. package/dist/entry-hooks-log.d.cts +23 -0
  124. package/dist/entry-hooks-log.d.mts +23 -0
  125. package/dist/entry-hooks-log.mjs +21 -0
  126. package/dist/entry-hooks-log.mjs.map +1 -0
  127. package/dist/entry-storage-cloudflare-kv.cjs +47 -0
  128. package/dist/entry-storage-cloudflare-kv.cjs.map +1 -0
  129. package/dist/entry-storage-cloudflare-kv.d.cts +42 -0
  130. package/dist/entry-storage-cloudflare-kv.d.mts +42 -0
  131. package/dist/entry-storage-cloudflare-kv.mjs +46 -0
  132. package/dist/entry-storage-cloudflare-kv.mjs.map +1 -0
  133. package/dist/entry-storage-drizzle.cjs +78 -0
  134. package/dist/entry-storage-drizzle.cjs.map +1 -0
  135. package/dist/entry-storage-drizzle.d.cts +30 -0
  136. package/dist/entry-storage-drizzle.d.mts +30 -0
  137. package/dist/entry-storage-drizzle.mjs +77 -0
  138. package/dist/entry-storage-drizzle.mjs.map +1 -0
  139. package/dist/entry-storage-file.cjs +4 -0
  140. package/dist/entry-storage-file.d.cts +30 -0
  141. package/dist/entry-storage-file.d.mts +30 -0
  142. package/dist/entry-storage-file.mjs +2 -0
  143. package/dist/entry-storage-libsql.cjs +3 -0
  144. package/dist/entry-storage-libsql.d.cts +48 -0
  145. package/dist/entry-storage-libsql.d.mts +48 -0
  146. package/dist/entry-storage-libsql.mjs +2 -0
  147. package/dist/entry-storage-memory.cjs +3 -0
  148. package/dist/entry-storage-memory.d.cts +2 -0
  149. package/dist/entry-storage-memory.d.mts +2 -0
  150. package/dist/entry-storage-memory.mjs +2 -0
  151. package/dist/entry-storage-mongodb.cjs +3 -0
  152. package/dist/entry-storage-mongodb.d.cts +55 -0
  153. package/dist/entry-storage-mongodb.d.mts +55 -0
  154. package/dist/entry-storage-mongodb.mjs +2 -0
  155. package/dist/entry-storage-postgres.cjs +3 -0
  156. package/dist/entry-storage-postgres.d.cts +62 -0
  157. package/dist/entry-storage-postgres.d.mts +62 -0
  158. package/dist/entry-storage-postgres.mjs +2 -0
  159. package/dist/entry-storage-redis.cjs +4 -0
  160. package/dist/entry-storage-redis.d.cts +77 -0
  161. package/dist/entry-storage-redis.d.mts +77 -0
  162. package/dist/entry-storage-redis.mjs +2 -0
  163. package/dist/entry-storage-sqlite.cjs +3 -0
  164. package/dist/entry-storage-sqlite.d.cts +36 -0
  165. package/dist/entry-storage-sqlite.d.mts +36 -0
  166. package/dist/entry-storage-sqlite.mjs +2 -0
  167. package/dist/entry-storage-unstorage.cjs +42 -0
  168. package/dist/entry-storage-unstorage.cjs.map +1 -0
  169. package/dist/entry-storage-unstorage.d.cts +29 -0
  170. package/dist/entry-storage-unstorage.d.mts +29 -0
  171. package/dist/entry-storage-unstorage.mjs +41 -0
  172. package/dist/entry-storage-unstorage.mjs.map +1 -0
  173. package/dist/file-COBYZA4Q.cjs +148 -0
  174. package/dist/file-COBYZA4Q.cjs.map +1 -0
  175. package/dist/file-fi02eFHk.mjs +131 -0
  176. package/dist/file-fi02eFHk.mjs.map +1 -0
  177. package/dist/index.cjs +123 -0
  178. package/dist/index.cjs.map +1 -0
  179. package/dist/index.d.cts +368 -0
  180. package/dist/index.d.mts +366 -0
  181. package/dist/index.mjs +61 -0
  182. package/dist/index.mjs.map +1 -0
  183. package/dist/keys-Byyj4quQ.mjs +111 -0
  184. package/dist/keys-Byyj4quQ.mjs.map +1 -0
  185. package/dist/keys-FiKpaVHX.cjs +302 -0
  186. package/dist/keys-FiKpaVHX.cjs.map +1 -0
  187. package/dist/libsql-bpVi0bXN.mjs +113 -0
  188. package/dist/libsql-bpVi0bXN.mjs.map +1 -0
  189. package/dist/libsql-pPJEo1e4.cjs +124 -0
  190. package/dist/libsql-pPJEo1e4.cjs.map +1 -0
  191. package/dist/memory-8Ef-PL5a.cjs +137 -0
  192. package/dist/memory-8Ef-PL5a.cjs.map +1 -0
  193. package/dist/memory-BMsSSwqn.mjs +127 -0
  194. package/dist/memory-BMsSSwqn.mjs.map +1 -0
  195. package/dist/memory-FnMJWCmB.d.cts +28 -0
  196. package/dist/memory-qIvANEs_.d.mts +28 -0
  197. package/dist/mongodb-Cy8yo0uk.cjs +108 -0
  198. package/dist/mongodb-Cy8yo0uk.cjs.map +1 -0
  199. package/dist/mongodb-Ddaq9mml.mjs +97 -0
  200. package/dist/mongodb-Ddaq9mml.mjs.map +1 -0
  201. package/dist/none-BnZtaGNJ.mjs +23 -0
  202. package/dist/none-BnZtaGNJ.mjs.map +1 -0
  203. package/dist/none-CAsxCOWN.cjs +49 -0
  204. package/dist/none-CAsxCOWN.cjs.map +1 -0
  205. package/dist/none-CZVrfnmF.cjs +33 -0
  206. package/dist/none-CZVrfnmF.cjs.map +1 -0
  207. package/dist/none-GhVIoh_s.mjs +33 -0
  208. package/dist/none-GhVIoh_s.mjs.map +1 -0
  209. package/dist/postgres-C8WbchFa.cjs +134 -0
  210. package/dist/postgres-C8WbchFa.cjs.map +1 -0
  211. package/dist/postgres-c3pAhmhr.mjs +123 -0
  212. package/dist/postgres-c3pAhmhr.mjs.map +1 -0
  213. package/dist/react.css +1 -0
  214. package/dist/react.js +31465 -0
  215. package/dist/receiver.cjs +43 -0
  216. package/dist/receiver.cjs.map +1 -0
  217. package/dist/receiver.d.cts +36 -0
  218. package/dist/receiver.d.mts +36 -0
  219. package/dist/receiver.mjs +40 -0
  220. package/dist/receiver.mjs.map +1 -0
  221. package/dist/redis-CFJkuSgB.cjs +270 -0
  222. package/dist/redis-CFJkuSgB.cjs.map +1 -0
  223. package/dist/redis-CvLi0KF7.mjs +254 -0
  224. package/dist/redis-CvLi0KF7.mjs.map +1 -0
  225. package/dist/roles-D0G9XqBq.cjs +128 -0
  226. package/dist/roles-D0G9XqBq.cjs.map +1 -0
  227. package/dist/roles-vp361lTk.mjs +99 -0
  228. package/dist/roles-vp361lTk.mjs.map +1 -0
  229. package/dist/schema-mo__wv4P.d.cts +233 -0
  230. package/dist/schema-mo__wv4P.d.mts +233 -0
  231. package/dist/schema.cjs +13 -0
  232. package/dist/schema.cjs.map +1 -0
  233. package/dist/schema.d.cts +2 -0
  234. package/dist/schema.d.mts +2 -0
  235. package/dist/schema.mjs +11 -0
  236. package/dist/schema.mjs.map +1 -0
  237. package/dist/signing.cjs +162 -0
  238. package/dist/signing.cjs.map +1 -0
  239. package/dist/signing.d.cts +73 -0
  240. package/dist/signing.d.mts +73 -0
  241. package/dist/signing.mjs +156 -0
  242. package/dist/signing.mjs.map +1 -0
  243. package/dist/sqlite-Cmqnrjes.mjs +67 -0
  244. package/dist/sqlite-Cmqnrjes.mjs.map +1 -0
  245. package/dist/sqlite-Dcufk0x3.cjs +78 -0
  246. package/dist/sqlite-Dcufk0x3.cjs.map +1 -0
  247. package/dist/table-Ce3Tzwqs.d.cts +11 -0
  248. package/dist/table-Ce3Tzwqs.d.mts +11 -0
  249. package/dist/testing.cjs +134 -0
  250. package/dist/testing.cjs.map +1 -0
  251. package/dist/testing.d.cts +80 -0
  252. package/dist/testing.d.mts +80 -0
  253. package/dist/testing.mjs +131 -0
  254. package/dist/testing.mjs.map +1 -0
  255. package/dist/types-react/react.d.ts +98 -0
  256. package/dist/types-react/schema.d.ts +229 -0
  257. package/dist/types-react/ui/App.d.ts +22 -0
  258. package/dist/types-react/ui/api.d.ts +97 -0
  259. package/dist/types-react/ui/components/JsonCodeEditor.d.ts +12 -0
  260. package/dist/types-react/ui/components/ThemeToggle.d.ts +2 -0
  261. package/dist/types-react/ui/components/Toast.d.ts +16 -0
  262. package/dist/types-react/ui/components/primitives.d.ts +50 -0
  263. package/dist/types-react/ui/components/ui-bits.d.ts +22 -0
  264. package/dist/types-react/ui/components/webhook-bits.d.ts +51 -0
  265. package/dist/types-react/ui/lib/format.d.ts +39 -0
  266. package/dist/types-react/ui/lib/nav-guard.d.ts +20 -0
  267. package/dist/types-react/ui/lib/utils.d.ts +3 -0
  268. package/dist/types-react/ui/theme.d.ts +12 -0
  269. package/dist/types-react/ui/types.d.ts +80 -0
  270. package/dist/types-react/ui/views/AuditView.d.ts +6 -0
  271. package/dist/types-react/ui/views/DeliveriesView.d.ts +12 -0
  272. package/dist/types-react/ui/views/EndpointsView.d.ts +11 -0
  273. package/dist/types-react/ui/views/EventTypesView.d.ts +11 -0
  274. package/dist/types-react/ui/views/MessagesView.d.ts +10 -0
  275. package/dist/types-react/ui/views/OverviewView.d.ts +12 -0
  276. package/dist/ui/assets/index-B0eoQX2U.css +1 -0
  277. package/dist/ui/assets/index-S5t_CLOe.js +209 -0
  278. package/dist/ui/index.html +14 -0
  279. package/package.json +487 -0
@@ -0,0 +1,176 @@
1
+ import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
2
+ import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";
3
+ //#region src/auth/basic.ts
4
+ /**
5
+ * HTTP Basic authentication {@link AuthProvider}.
6
+ *
7
+ * Parses the `Authorization: Basic <base64>` header, looks the username up in a
8
+ * configured user list, and verifies the supplied password using one of three
9
+ * credential modes (in order of preference):
10
+ *
11
+ * 1. A custom `passwordVerifier(username, password)` callback — for delegating
12
+ * to your own user store.
13
+ * 2. A `passwordHash` produced by {@link hashPassword} — scrypt with a random
14
+ * salt, verified in constant time. **Recommended for production.**
15
+ * 3. A plain `password` field — **development only**. Never ship real
16
+ * credentials as plaintext; they are compared in constant time but stored as
17
+ * cleartext in your config.
18
+ *
19
+ * On success the matched user becomes a {@link Principal}; on any failure
20
+ * (missing/malformed header, unknown user, bad password) `authenticate` returns
21
+ * `null`. {@link AuthProvider.challenge} emits a `401` with a
22
+ * `WWW-Authenticate: Basic realm="…"` header so browsers prompt for credentials.
23
+ *
24
+ * Password hashing uses Node's `node:crypto` `scrypt`, which is available in
25
+ * both Node and Bun — no Bun-only APIs and no extra dependencies.
26
+ *
27
+ * @module
28
+ */
29
+ var basic_exports = /* @__PURE__ */ __exportAll({
30
+ basicAuth: () => basicAuth,
31
+ hashPassword: () => hashPassword,
32
+ verifyPassword: () => verifyPassword
33
+ });
34
+ /** scrypt key length (bytes) used by {@link hashPassword}. */
35
+ const KEY_LENGTH = 64;
36
+ /** Salt length (bytes) used by {@link hashPassword}. */
37
+ const SALT_LENGTH = 16;
38
+ /** Prefix identifying a {@link hashPassword} digest. */
39
+ const SCRYPT_PREFIX = "scrypt";
40
+ const scryptAsync = (password, salt, keylen) => new Promise((resolve, reject) => {
41
+ scrypt(password, salt, keylen, (err, derivedKey) => {
42
+ if (err) reject(err);
43
+ else resolve(derivedKey);
44
+ });
45
+ });
46
+ /** Lowercase hex encoding of a buffer. */
47
+ const toHex = (buf) => buf.toString("hex");
48
+ /**
49
+ * Hash a password with scrypt and a fresh random salt.
50
+ *
51
+ * The returned string is self-describing and safe to store in config or a
52
+ * database: `scrypt$<saltHex>$<hashHex>`. Pass it back to
53
+ * {@link verifyPassword} (or supply it as a user's `passwordHash`) to check a
54
+ * candidate password.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const stored = await hashPassword("correct horse battery staple");
59
+ * // → "scrypt$<32 hex chars>$<128 hex chars>"
60
+ * ```
61
+ */
62
+ async function hashPassword(password) {
63
+ const salt = randomBytes(SALT_LENGTH);
64
+ const derived = await scryptAsync(password, salt, KEY_LENGTH);
65
+ return `${SCRYPT_PREFIX}$${toHex(salt)}$${toHex(derived)}`;
66
+ }
67
+ /**
68
+ * Verify a candidate `password` against a digest produced by
69
+ * {@link hashPassword}.
70
+ *
71
+ * Re-derives the scrypt hash using the stored salt and compares it to the stored
72
+ * hash with `crypto.timingSafeEqual` (constant time). Returns `false` — rather
73
+ * than throwing — for malformed or unrecognized digests.
74
+ */
75
+ async function verifyPassword(password, stored) {
76
+ const parts = stored.split("$");
77
+ if (parts.length !== 3 || parts[0] !== SCRYPT_PREFIX) return false;
78
+ const saltHex = parts[1];
79
+ const hashHex = parts[2];
80
+ if (!saltHex || !hashHex) return false;
81
+ let salt;
82
+ let expected;
83
+ try {
84
+ salt = Buffer.from(saltHex, "hex");
85
+ expected = Buffer.from(hashHex, "hex");
86
+ } catch {
87
+ return false;
88
+ }
89
+ if (salt.length === 0 || expected.length === 0) return false;
90
+ const derived = await scryptAsync(password, salt, expected.length);
91
+ if (derived.length !== expected.length) return false;
92
+ return timingSafeEqual(derived, expected);
93
+ }
94
+ /** Constant-time string comparison that does not leak length via early exit. */
95
+ function constantTimeEquals(a, b) {
96
+ const bufA = Buffer.from(a, "utf8");
97
+ const bufB = Buffer.from(b, "utf8");
98
+ if (bufA.length !== bufB.length) {
99
+ timingSafeEqual(bufA, bufA);
100
+ return false;
101
+ }
102
+ return timingSafeEqual(bufA, bufB);
103
+ }
104
+ /** Decode the `Authorization: Basic <base64>` header, or `null` if absent/malformed. */
105
+ function parseBasicHeader(request) {
106
+ const header = request.headers.get("authorization");
107
+ if (!header) return null;
108
+ const [scheme, encoded] = header.split(" ", 2);
109
+ if (!scheme || scheme.toLowerCase() !== "basic" || !encoded) return null;
110
+ let decoded;
111
+ try {
112
+ decoded = Buffer.from(encoded, "base64").toString("utf8");
113
+ } catch {
114
+ return null;
115
+ }
116
+ const sep = decoded.indexOf(":");
117
+ if (sep < 0) return null;
118
+ return {
119
+ username: decoded.slice(0, sep),
120
+ password: decoded.slice(sep + 1)
121
+ };
122
+ }
123
+ /** Build the {@link Principal} for a successfully authenticated user. */
124
+ function principalFor(user) {
125
+ return {
126
+ id: user.id ?? user.username,
127
+ name: user.username,
128
+ ...user.email !== void 0 ? { email: user.email } : {},
129
+ ...user.roles !== void 0 ? { roles: user.roles } : {}
130
+ };
131
+ }
132
+ /**
133
+ * Create an HTTP Basic {@link AuthProvider}.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * import { basicAuth, hashPassword } from "@xtandard/webhooks/auth/basic";
138
+ *
139
+ * const auth = basicAuth({
140
+ * realm: "Webhooks Admin",
141
+ * users: [
142
+ * { username: "admin", passwordHash: await hashPassword("s3cret"), roles: ["admin"] },
143
+ * ],
144
+ * });
145
+ * ```
146
+ */
147
+ function basicAuth(options) {
148
+ const realm = options.realm ?? "xtandard-webhooks";
149
+ const usersByName = /* @__PURE__ */ new Map();
150
+ for (const user of options.users) usersByName.set(user.username, user);
151
+ return {
152
+ async authenticate(request) {
153
+ const creds = parseBasicHeader(request);
154
+ if (!creds) return null;
155
+ const user = usersByName.get(creds.username);
156
+ if (!user) {
157
+ if (options.passwordVerifier) await options.passwordVerifier(creds.username, creds.password);
158
+ return null;
159
+ }
160
+ if (options.passwordVerifier) return await options.passwordVerifier(creds.username, creds.password) ? principalFor(user) : null;
161
+ if (user.passwordHash !== void 0) return await verifyPassword(creds.password, user.passwordHash) ? principalFor(user) : null;
162
+ if (user.password !== void 0) return constantTimeEquals(creds.password, user.password) ? principalFor(user) : null;
163
+ return null;
164
+ },
165
+ challenge(_request) {
166
+ return new Response("Unauthorized", {
167
+ status: 401,
168
+ headers: { "WWW-Authenticate": `Basic realm="${realm}"` }
169
+ });
170
+ }
171
+ };
172
+ }
173
+ //#endregion
174
+ export { verifyPassword as i, basic_exports as n, hashPassword as r, basicAuth as t };
175
+
176
+ //# sourceMappingURL=basic-DKk0Xfuu.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"basic-DKk0Xfuu.mjs","names":[],"sources":["../src/auth/basic.ts"],"sourcesContent":["/**\n * HTTP Basic authentication {@link AuthProvider}.\n *\n * Parses the `Authorization: Basic <base64>` header, looks the username up in a\n * configured user list, and verifies the supplied password using one of three\n * credential modes (in order of preference):\n *\n * 1. A custom `passwordVerifier(username, password)` callback — for delegating\n * to your own user store.\n * 2. A `passwordHash` produced by {@link hashPassword} — scrypt with a random\n * salt, verified in constant time. **Recommended for production.**\n * 3. A plain `password` field — **development only**. Never ship real\n * credentials as plaintext; they are compared in constant time but stored as\n * cleartext in your config.\n *\n * On success the matched user becomes a {@link Principal}; on any failure\n * (missing/malformed header, unknown user, bad password) `authenticate` returns\n * `null`. {@link AuthProvider.challenge} emits a `401` with a\n * `WWW-Authenticate: Basic realm=\"…\"` header so browsers prompt for credentials.\n *\n * Password hashing uses Node's `node:crypto` `scrypt`, which is available in\n * both Node and Bun — no Bun-only APIs and no extra dependencies.\n *\n * @module\n */\n\nimport { randomBytes, scrypt as scryptCb, timingSafeEqual } from \"node:crypto\";\nimport type { AuthProvider, Principal } from \"./contract.ts\";\n\n/** scrypt key length (bytes) used by {@link hashPassword}. */\nconst KEY_LENGTH = 64;\n/** Salt length (bytes) used by {@link hashPassword}. */\nconst SALT_LENGTH = 16;\n/** Prefix identifying a {@link hashPassword} digest. */\nconst SCRYPT_PREFIX = \"scrypt\";\n\nconst scryptAsync = (password: string, salt: Buffer, keylen: number): Promise<Buffer> =>\n new Promise((resolve, reject) => {\n scryptCb(password, salt, keylen, (err, derivedKey) => {\n if (err) reject(err);\n else resolve(derivedKey);\n });\n });\n\n/** Lowercase hex encoding of a buffer. */\nconst toHex = (buf: Buffer): string => buf.toString(\"hex\");\n\n/**\n * Hash a password with scrypt and a fresh random salt.\n *\n * The returned string is self-describing and safe to store in config or a\n * database: `scrypt$<saltHex>$<hashHex>`. Pass it back to\n * {@link verifyPassword} (or supply it as a user's `passwordHash`) to check a\n * candidate password.\n *\n * @example\n * ```ts\n * const stored = await hashPassword(\"correct horse battery staple\");\n * // → \"scrypt$<32 hex chars>$<128 hex chars>\"\n * ```\n */\nexport async function hashPassword(password: string): Promise<string> {\n const salt = randomBytes(SALT_LENGTH);\n const derived = await scryptAsync(password, salt, KEY_LENGTH);\n return `${SCRYPT_PREFIX}$${toHex(salt)}$${toHex(derived)}`;\n}\n\n/**\n * Verify a candidate `password` against a digest produced by\n * {@link hashPassword}.\n *\n * Re-derives the scrypt hash using the stored salt and compares it to the stored\n * hash with `crypto.timingSafeEqual` (constant time). Returns `false` — rather\n * than throwing — for malformed or unrecognized digests.\n */\nexport async function verifyPassword(password: string, stored: string): Promise<boolean> {\n const parts = stored.split(\"$\");\n if (parts.length !== 3 || parts[0] !== SCRYPT_PREFIX) return false;\n const saltHex = parts[1];\n const hashHex = parts[2];\n if (!saltHex || !hashHex) return false;\n\n let salt: Buffer;\n let expected: Buffer;\n try {\n salt = Buffer.from(saltHex, \"hex\");\n expected = Buffer.from(hashHex, \"hex\");\n } catch {\n return false;\n }\n if (salt.length === 0 || expected.length === 0) return false;\n\n const derived = await scryptAsync(password, salt, expected.length);\n if (derived.length !== expected.length) return false;\n return timingSafeEqual(derived, expected);\n}\n\n/** A configured user for {@link basicAuth}. */\nexport interface BasicAuthUser {\n /** The login name, matched against the Basic credentials. */\n username: string;\n /**\n * A digest produced by {@link hashPassword} (`scrypt$<salt>$<hash>`).\n * Preferred for production.\n */\n passwordHash?: string;\n /**\n * A plaintext password. **Development only** — prefer `passwordHash`. Compared\n * in constant time but stored as cleartext in your config.\n */\n password?: string;\n /** Roles attached to the resulting {@link Principal}. */\n roles?: string[];\n /** Email attached to the resulting {@link Principal}. */\n email?: string;\n /** Principal id. Defaults to {@link BasicAuthUser.username} when omitted. */\n id?: string;\n}\n\n/** Options for {@link basicAuth}. */\nexport interface BasicAuthOptions {\n /** The known users. */\n users: BasicAuthUser[];\n /**\n * Realm advertised in the `WWW-Authenticate` header on challenge.\n * @default \"xtandard-webhooks\"\n */\n realm?: string;\n /**\n * Custom verifier. When supplied it takes precedence over `passwordHash` and\n * `password` for matched users — delegate to your own credential store and\n * return `true` to accept.\n */\n passwordVerifier?: (username: string, password: string) => Promise<boolean> | boolean;\n}\n\n/** Constant-time string comparison that does not leak length via early exit. */\nfunction constantTimeEquals(a: string, b: string): boolean {\n const bufA = Buffer.from(a, \"utf8\");\n const bufB = Buffer.from(b, \"utf8\");\n // timingSafeEqual requires equal lengths; hash to a fixed width first so the\n // comparison time does not depend on the inputs.\n if (bufA.length !== bufB.length) {\n // Still perform a comparison to keep timing uniform, then fail.\n timingSafeEqual(bufA, bufA);\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n}\n\n/** Result of parsing an `Authorization: Basic` header. */\ninterface BasicCredentials {\n username: string;\n password: string;\n}\n\n/** Decode the `Authorization: Basic <base64>` header, or `null` if absent/malformed. */\nfunction parseBasicHeader(request: Request): BasicCredentials | null {\n const header = request.headers.get(\"authorization\");\n if (!header) return null;\n const [scheme, encoded] = header.split(\" \", 2);\n if (!scheme || scheme.toLowerCase() !== \"basic\" || !encoded) return null;\n\n let decoded: string;\n try {\n decoded = Buffer.from(encoded, \"base64\").toString(\"utf8\");\n } catch {\n return null;\n }\n const sep = decoded.indexOf(\":\");\n if (sep < 0) return null;\n return { username: decoded.slice(0, sep), password: decoded.slice(sep + 1) };\n}\n\n/** Build the {@link Principal} for a successfully authenticated user. */\nfunction principalFor(user: BasicAuthUser): Principal {\n return {\n id: user.id ?? user.username,\n name: user.username,\n ...(user.email !== undefined ? { email: user.email } : {}),\n ...(user.roles !== undefined ? { roles: user.roles } : {}),\n };\n}\n\n/**\n * Create an HTTP Basic {@link AuthProvider}.\n *\n * @example\n * ```ts\n * import { basicAuth, hashPassword } from \"@xtandard/webhooks/auth/basic\";\n *\n * const auth = basicAuth({\n * realm: \"Webhooks Admin\",\n * users: [\n * { username: \"admin\", passwordHash: await hashPassword(\"s3cret\"), roles: [\"admin\"] },\n * ],\n * });\n * ```\n */\nexport function basicAuth(options: BasicAuthOptions): AuthProvider {\n const realm = options.realm ?? \"xtandard-webhooks\";\n const usersByName = new Map<string, BasicAuthUser>();\n for (const user of options.users) usersByName.set(user.username, user);\n\n return {\n async authenticate(request: Request): Promise<Principal | null> {\n const creds = parseBasicHeader(request);\n if (!creds) return null;\n\n const user = usersByName.get(creds.username);\n if (!user) {\n // Run a dummy verification to keep timing roughly uniform for unknown\n // users versus known users.\n if (options.passwordVerifier) {\n await options.passwordVerifier(creds.username, creds.password);\n }\n return null;\n }\n\n // (a) custom verifier wins.\n if (options.passwordVerifier) {\n const ok = await options.passwordVerifier(creds.username, creds.password);\n return ok ? principalFor(user) : null;\n }\n\n // (b) scrypt hash.\n if (user.passwordHash !== undefined) {\n const ok = await verifyPassword(creds.password, user.passwordHash);\n return ok ? principalFor(user) : null;\n }\n\n // (c) dev-only plaintext.\n if (user.password !== undefined) {\n const ok = constantTimeEquals(creds.password, user.password);\n return ok ? principalFor(user) : null;\n }\n\n return null;\n },\n\n challenge(_request: Request): Response {\n return new Response(\"Unauthorized\", {\n status: 401,\n headers: { \"WWW-Authenticate\": `Basic realm=\"${realm}\"` },\n });\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };