@bajustone/fortress 1.0.0-rc.1

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 (465) hide show
  1. package/CHANGELOG.md +1011 -0
  2. package/LICENSE +21 -0
  3. package/README.md +760 -0
  4. package/SECURITY.md +197 -0
  5. package/bin/fortress.ts +667 -0
  6. package/dist/auth-service-BcyQ2PuZ.d.cts +307 -0
  7. package/dist/auth-service-BkzZkWfH.d.ts +307 -0
  8. package/dist/config-B2366s_G.d.ts +665 -0
  9. package/dist/config-GtlVt6ZD.d.cts +665 -0
  10. package/dist/convert-routes-BLCQT57f.d.ts +72 -0
  11. package/dist/convert-routes-BdMvP2_X.d.cts +72 -0
  12. package/dist/crypto.cjs +61 -0
  13. package/dist/crypto.d.cts +36 -0
  14. package/dist/crypto.d.ts +36 -0
  15. package/dist/crypto.js +35 -0
  16. package/dist/drift-BT1wtMDJ.d.cts +22 -0
  17. package/dist/drift-k9LiYpRP.d.ts +22 -0
  18. package/dist/drizzle/pg.cjs +481 -0
  19. package/dist/drizzle/pg.d.cts +15 -0
  20. package/dist/drizzle/pg.d.ts +15 -0
  21. package/dist/drizzle/pg.js +454 -0
  22. package/dist/drizzle.cjs +1442 -0
  23. package/dist/drizzle.d.cts +85 -0
  24. package/dist/drizzle.d.ts +85 -0
  25. package/dist/drizzle.js +1411 -0
  26. package/dist/express.cjs +1357 -0
  27. package/dist/express.d.cts +333 -0
  28. package/dist/express.d.ts +333 -0
  29. package/dist/express.js +1314 -0
  30. package/dist/fetcher.cjs +77 -0
  31. package/dist/fetcher.d.cts +7 -0
  32. package/dist/fetcher.d.ts +7 -0
  33. package/dist/fetcher.js +41 -0
  34. package/dist/fortress-BmSvFfqK.d.cts +1089 -0
  35. package/dist/fortress-uA-_cheR.d.ts +1089 -0
  36. package/dist/hono.cjs +1530 -0
  37. package/dist/hono.d.cts +514 -0
  38. package/dist/hono.d.ts +514 -0
  39. package/dist/hono.js +1483 -0
  40. package/dist/index-CxEkfCA0.d.cts +77 -0
  41. package/dist/index-CxEkfCA0.d.ts +77 -0
  42. package/dist/index-D054pcb-.d.cts +146 -0
  43. package/dist/index-jAMbbUAY.d.ts +146 -0
  44. package/dist/index.cjs +9046 -0
  45. package/dist/index.d.cts +717 -0
  46. package/dist/index.d.ts +717 -0
  47. package/dist/index.js +8935 -0
  48. package/dist/jwt.cjs +255 -0
  49. package/dist/jwt.d.cts +90 -0
  50. package/dist/jwt.d.ts +90 -0
  51. package/dist/jwt.js +227 -0
  52. package/dist/otel.cjs +108 -0
  53. package/dist/otel.d.cts +62 -0
  54. package/dist/otel.d.ts +62 -0
  55. package/dist/otel.js +73 -0
  56. package/dist/plugins/account-lockout.cjs +322 -0
  57. package/dist/plugins/account-lockout.d.cts +42 -0
  58. package/dist/plugins/account-lockout.d.ts +42 -0
  59. package/dist/plugins/account-lockout.js +295 -0
  60. package/dist/plugins/admin.cjs +2378 -0
  61. package/dist/plugins/admin.d.cts +72 -0
  62. package/dist/plugins/admin.d.ts +72 -0
  63. package/dist/plugins/admin.js +2351 -0
  64. package/dist/plugins/api-key.cjs +792 -0
  65. package/dist/plugins/api-key.d.cts +121 -0
  66. package/dist/plugins/api-key.d.ts +121 -0
  67. package/dist/plugins/api-key.js +765 -0
  68. package/dist/plugins/audit-log.cjs +493 -0
  69. package/dist/plugins/audit-log.d.cts +86 -0
  70. package/dist/plugins/audit-log.d.ts +86 -0
  71. package/dist/plugins/audit-log.js +468 -0
  72. package/dist/plugins/data-isolation.cjs +211 -0
  73. package/dist/plugins/data-isolation.d.cts +49 -0
  74. package/dist/plugins/data-isolation.d.ts +49 -0
  75. package/dist/plugins/data-isolation.js +186 -0
  76. package/dist/plugins/email-verification.cjs +273 -0
  77. package/dist/plugins/email-verification.d.cts +44 -0
  78. package/dist/plugins/email-verification.d.ts +44 -0
  79. package/dist/plugins/email-verification.js +246 -0
  80. package/dist/plugins/magic-link.cjs +231 -0
  81. package/dist/plugins/magic-link.d.cts +39 -0
  82. package/dist/plugins/magic-link.d.ts +39 -0
  83. package/dist/plugins/magic-link.js +204 -0
  84. package/dist/plugins/oauth.cjs +1696 -0
  85. package/dist/plugins/oauth.d.cts +406 -0
  86. package/dist/plugins/oauth.d.ts +406 -0
  87. package/dist/plugins/oauth.js +1669 -0
  88. package/dist/plugins/openapi.cjs +1185 -0
  89. package/dist/plugins/openapi.d.cts +6 -0
  90. package/dist/plugins/openapi.d.ts +6 -0
  91. package/dist/plugins/openapi.js +1158 -0
  92. package/dist/plugins/rate-limit/express.cjs +55 -0
  93. package/dist/plugins/rate-limit/express.d.cts +64 -0
  94. package/dist/plugins/rate-limit/express.d.ts +64 -0
  95. package/dist/plugins/rate-limit/express.js +30 -0
  96. package/dist/plugins/rate-limit/hono.cjs +51 -0
  97. package/dist/plugins/rate-limit/hono.d.cts +59 -0
  98. package/dist/plugins/rate-limit/hono.d.ts +59 -0
  99. package/dist/plugins/rate-limit/hono.js +26 -0
  100. package/dist/plugins/rate-limit/sveltekit.cjs +49 -0
  101. package/dist/plugins/rate-limit/sveltekit.d.cts +59 -0
  102. package/dist/plugins/rate-limit/sveltekit.d.ts +59 -0
  103. package/dist/plugins/rate-limit/sveltekit.js +24 -0
  104. package/dist/plugins/rate-limit.cjs +354 -0
  105. package/dist/plugins/rate-limit.d.cts +140 -0
  106. package/dist/plugins/rate-limit.d.ts +140 -0
  107. package/dist/plugins/rate-limit.js +325 -0
  108. package/dist/plugins/social-login.cjs +905 -0
  109. package/dist/plugins/social-login.d.cts +114 -0
  110. package/dist/plugins/social-login.d.ts +114 -0
  111. package/dist/plugins/social-login.js +880 -0
  112. package/dist/plugins/tenancy.cjs +780 -0
  113. package/dist/plugins/tenancy.d.cts +108 -0
  114. package/dist/plugins/tenancy.d.ts +108 -0
  115. package/dist/plugins/tenancy.js +753 -0
  116. package/dist/plugins/two-factor.cjs +555 -0
  117. package/dist/plugins/two-factor.d.cts +67 -0
  118. package/dist/plugins/two-factor.d.ts +67 -0
  119. package/dist/plugins/two-factor.js +526 -0
  120. package/dist/plugins/webauthn.cjs +786 -0
  121. package/dist/plugins/webauthn.d.cts +72 -0
  122. package/dist/plugins/webauthn.d.ts +72 -0
  123. package/dist/plugins/webauthn.js +766 -0
  124. package/dist/plugins/webhook.cjs +819 -0
  125. package/dist/plugins/webhook.d.cts +254 -0
  126. package/dist/plugins/webhook.d.ts +254 -0
  127. package/dist/plugins/webhook.js +789 -0
  128. package/dist/protect-D1v_0upK.d.cts +123 -0
  129. package/dist/protect-DsxV_-dJ.d.ts +123 -0
  130. package/dist/sveltekit.cjs +1258 -0
  131. package/dist/sveltekit.d.cts +420 -0
  132. package/dist/sveltekit.d.ts +420 -0
  133. package/dist/sveltekit.js +1207 -0
  134. package/dist/testing.cjs +4294 -0
  135. package/dist/testing.d.cts +197 -0
  136. package/dist/testing.d.ts +197 -0
  137. package/dist/testing.js +4264 -0
  138. package/dist/types-et0R8tsC.d.cts +217 -0
  139. package/dist/types-et0R8tsC.d.ts +217 -0
  140. package/docs/adapter-typed-helpers.md +192 -0
  141. package/docs/admin-recipes.md +227 -0
  142. package/docs/architecture.md +434 -0
  143. package/docs/ci/github-actions.yml +59 -0
  144. package/docs/ci.md +109 -0
  145. package/docs/compatibility.md +115 -0
  146. package/docs/deployment.md +305 -0
  147. package/docs/hardening.md +118 -0
  148. package/docs/host-owned-routes.md +154 -0
  149. package/docs/migrations/0001-schema-version.md +54 -0
  150. package/docs/migrations/0002-initial-schema.md +84 -0
  151. package/docs/migrations/upgrade-guide.md +160 -0
  152. package/docs/observability.md +394 -0
  153. package/docs/plugins/account-lockout.md +131 -0
  154. package/docs/plugins/admin.md +402 -0
  155. package/docs/plugins/api-key.md +327 -0
  156. package/docs/plugins/audit-log.md +261 -0
  157. package/docs/plugins/data-isolation.md +186 -0
  158. package/docs/plugins/email-verification.md +121 -0
  159. package/docs/plugins/magic-link.md +123 -0
  160. package/docs/plugins/oauth.md +544 -0
  161. package/docs/plugins/openapi.md +250 -0
  162. package/docs/plugins/rate-limit.md +223 -0
  163. package/docs/plugins/social-login.md +337 -0
  164. package/docs/plugins/tenancy.md +122 -0
  165. package/docs/plugins/two-factor.md +191 -0
  166. package/docs/plugins/webauthn.md +188 -0
  167. package/docs/plugins/webhook.md +242 -0
  168. package/docs/policy-as-code.md +156 -0
  169. package/docs/route-manifest.md +46 -0
  170. package/docs/security.md +261 -0
  171. package/docs/threat-model.md +161 -0
  172. package/examples/README.md +119 -0
  173. package/examples/express-app/index.ts +747 -0
  174. package/examples/hono-app/docker-compose.yml +14 -0
  175. package/examples/hono-app/index.ts +1009 -0
  176. package/examples/policy/fortress.policy.json +47 -0
  177. package/examples/sveltekit-app/README.md +125 -0
  178. package/examples/sveltekit-app/src/app.d.ts +16 -0
  179. package/examples/sveltekit-app/src/hooks.server.ts +23 -0
  180. package/examples/sveltekit-app/src/lib/server/fortress.ts +81 -0
  181. package/examples/sveltekit-app/src/routes/api/echo/[id]/+server.ts +32 -0
  182. package/examples/sveltekit-app/src/routes/api/fortress/[...path]/+server.ts +15 -0
  183. package/examples/sveltekit-app/src/routes/dashboard/+page.server.ts +20 -0
  184. package/examples/sveltekit-app/src/routes/login/+page.server.ts +48 -0
  185. package/examples/sveltekit-app/src/routes/oauth/consent/+page.server.ts +69 -0
  186. package/examples/sveltekit-app/src/routes/oauth/consent/+page.svelte +83 -0
  187. package/migrations/pg/0001_schema_version.down.sql +2 -0
  188. package/migrations/pg/0001_schema_version.sql +8 -0
  189. package/migrations/pg/0002_initial_schema.down.sql +36 -0
  190. package/migrations/pg/0002_initial_schema.sql +376 -0
  191. package/migrations/pg/0003_auth_continuation.down.sql +6 -0
  192. package/migrations/pg/0003_auth_continuation.sql +30 -0
  193. package/migrations/pg/0004_tenant_default_unique.down.sql +1 -0
  194. package/migrations/pg/0004_tenant_default_unique.sql +14 -0
  195. package/migrations/pg/0005_hot_indexes_timestamptz.down.sql +71 -0
  196. package/migrations/pg/0005_hot_indexes_timestamptz.sql +71 -0
  197. package/migrations/pg/0006_canonical_email.down.sql +3 -0
  198. package/migrations/pg/0006_canonical_email.sql +8 -0
  199. package/migrations/pg/0007_audit_chain_anchor.down.sql +1 -0
  200. package/migrations/pg/0007_audit_chain_anchor.sql +8 -0
  201. package/migrations/pg/0008_two_factor_hardening.down.sql +8 -0
  202. package/migrations/pg/0008_two_factor_hardening.sql +8 -0
  203. package/migrations/pg/0009_encrypt_totp_secrets.down.sql +2 -0
  204. package/migrations/pg/0009_encrypt_totp_secrets.sql +5 -0
  205. package/migrations/pg/0010_bigint_append_only_ids.down.sql +2 -0
  206. package/migrations/pg/0010_bigint_append_only_ids.sql +23 -0
  207. package/migrations/sqlite/0001_schema_version.down.sql +2 -0
  208. package/migrations/sqlite/0001_schema_version.sql +8 -0
  209. package/migrations/sqlite/0002_initial_schema.down.sql +36 -0
  210. package/migrations/sqlite/0002_initial_schema.sql +376 -0
  211. package/migrations/sqlite/0003_auth_continuation.down.sql +27 -0
  212. package/migrations/sqlite/0003_auth_continuation.sql +45 -0
  213. package/migrations/sqlite/0004_tenant_default_unique.down.sql +1 -0
  214. package/migrations/sqlite/0004_tenant_default_unique.sql +13 -0
  215. package/migrations/sqlite/0005_hot_indexes_timestamptz.down.sql +10 -0
  216. package/migrations/sqlite/0005_hot_indexes_timestamptz.sql +10 -0
  217. package/migrations/sqlite/0006_canonical_email.down.sql +3 -0
  218. package/migrations/sqlite/0006_canonical_email.sql +8 -0
  219. package/migrations/sqlite/0007_audit_chain_anchor.down.sql +1 -0
  220. package/migrations/sqlite/0007_audit_chain_anchor.sql +8 -0
  221. package/migrations/sqlite/0008_two_factor_hardening.down.sql +14 -0
  222. package/migrations/sqlite/0008_two_factor_hardening.sql +7 -0
  223. package/migrations/sqlite/0009_encrypt_totp_secrets.down.sql +2 -0
  224. package/migrations/sqlite/0009_encrypt_totp_secrets.sql +5 -0
  225. package/migrations/sqlite/0010_bigint_append_only_ids.down.sql +1 -0
  226. package/migrations/sqlite/0010_bigint_append_only_ids.sql +2 -0
  227. package/package.json +398 -0
  228. package/src/adapters/database/index.ts +63 -0
  229. package/src/adapters/database/types.ts +22 -0
  230. package/src/changelog-script.test.ts +21 -0
  231. package/src/cli.test.ts +79 -0
  232. package/src/core/auth/auth-admin.test.ts +247 -0
  233. package/src/core/auth/auth-endpoints.ts +558 -0
  234. package/src/core/auth/auth-observer.test.ts +191 -0
  235. package/src/core/auth/auth-service.ts +1464 -0
  236. package/src/core/auth/email.test.ts +17 -0
  237. package/src/core/auth/email.ts +11 -0
  238. package/src/core/auth/hooks.test.ts +251 -0
  239. package/src/core/auth/impersonation.test.ts +179 -0
  240. package/src/core/auth/jwt.test.ts +184 -0
  241. package/src/core/auth/jwt.ts +239 -0
  242. package/src/core/auth/login-timing.test.ts +77 -0
  243. package/src/core/auth/password-policy.test.ts +253 -0
  244. package/src/core/auth/password-policy.ts +220 -0
  245. package/src/core/auth/password.test.ts +42 -0
  246. package/src/core/auth/password.ts +62 -0
  247. package/src/core/auth/post-auth-gate.test.ts +258 -0
  248. package/src/core/auth/post-auth-gate.ts +228 -0
  249. package/src/core/auth/refresh-token.test.ts +86 -0
  250. package/src/core/auth/refresh-token.ts +105 -0
  251. package/src/core/auth/timing-safe.ts +23 -0
  252. package/src/core/config.ts +212 -0
  253. package/src/core/endpoint.ts +149 -0
  254. package/src/core/errors.ts +199 -0
  255. package/src/core/fetcher-interop.test.ts +106 -0
  256. package/src/core/fortress.test.ts +354 -0
  257. package/src/core/fortress.ts +550 -0
  258. package/src/core/http/call.test.ts +148 -0
  259. package/src/core/http/call.ts +149 -0
  260. package/src/core/http/cookie-serialize.test.ts +198 -0
  261. package/src/core/http/cookie-serialize.ts +107 -0
  262. package/src/core/http/csrf.test.ts +140 -0
  263. package/src/core/http/csrf.ts +173 -0
  264. package/src/core/http/dispatch.ts +652 -0
  265. package/src/core/http/error-response.test.ts +69 -0
  266. package/src/core/http/error-response.ts +93 -0
  267. package/src/core/http/fortress-rbac.test.ts +43 -0
  268. package/src/core/http/fortress-rbac.ts +127 -0
  269. package/src/core/http/handle-request.test.ts +441 -0
  270. package/src/core/http/handle-request.ts +354 -0
  271. package/src/core/http/match.test.ts +122 -0
  272. package/src/core/http/match.ts +126 -0
  273. package/src/core/http/outbound.test.ts +34 -0
  274. package/src/core/http/outbound.ts +105 -0
  275. package/src/core/http/plugin-middleware.ts +67 -0
  276. package/src/core/http/principal.test.ts +345 -0
  277. package/src/core/http/principal.ts +86 -0
  278. package/src/core/http/protect.test.ts +220 -0
  279. package/src/core/http/protect.ts +394 -0
  280. package/src/core/http/token-extraction.test.ts +59 -0
  281. package/src/core/http/token-extraction.ts +53 -0
  282. package/src/core/iam/explain.test.ts +177 -0
  283. package/src/core/iam/explain.ts +302 -0
  284. package/src/core/iam/iam-endpoints.ts +779 -0
  285. package/src/core/iam/iam-service.test.ts +829 -0
  286. package/src/core/iam/iam-service.ts +1137 -0
  287. package/src/core/iam/permission-cache.test.ts +115 -0
  288. package/src/core/iam/permission-cache.ts +71 -0
  289. package/src/core/iam/permission-check-observer.test.ts +90 -0
  290. package/src/core/iam/permission-evaluator.test.ts +291 -0
  291. package/src/core/iam/permission-evaluator.ts +198 -0
  292. package/src/core/iam/permission-sync.test.ts +166 -0
  293. package/src/core/iam/permission-sync.ts +192 -0
  294. package/src/core/iam/resource-sync.test.ts +70 -0
  295. package/src/core/iam/resource-sync.ts +182 -0
  296. package/src/core/iam/service-account-http.test.ts +529 -0
  297. package/src/core/iam/service-account.test.ts +282 -0
  298. package/src/core/internal-adapter.test.ts +389 -0
  299. package/src/core/internal-adapter.ts +435 -0
  300. package/src/core/json-schema.ts +50 -0
  301. package/src/core/manifest/__snapshots__/route-manifest.test.ts.snap +192 -0
  302. package/src/core/manifest/drift.ts +103 -0
  303. package/src/core/manifest/route-manifest.test.ts +142 -0
  304. package/src/core/manifest/route-manifest.ts +154 -0
  305. package/src/core/migrate.test.ts +72 -0
  306. package/src/core/migrations/engine.test.ts +308 -0
  307. package/src/core/migrations/engine.ts +548 -0
  308. package/src/core/migrations/migrations.ts +1771 -0
  309. package/src/core/migrations/pg-migration.integration-test.ts +353 -0
  310. package/src/core/migrations/upgrade-fixture.test.ts +378 -0
  311. package/src/core/observability/db-instrumentation.ts +205 -0
  312. package/src/core/observability/listener-list.test.ts +124 -0
  313. package/src/core/observability/listener-list.ts +73 -0
  314. package/src/core/observability/logger.test.ts +43 -0
  315. package/src/core/observability/logger.ts +49 -0
  316. package/src/core/observability/spans.test.ts +193 -0
  317. package/src/core/observability/types.ts +80 -0
  318. package/src/core/openapi-drift.test.ts +41 -0
  319. package/src/core/openapi.ts +47 -0
  320. package/src/core/plugin-methods-map.ts +75 -0
  321. package/src/core/plugin-runner.test.ts +233 -0
  322. package/src/core/plugin-runner.ts +276 -0
  323. package/src/core/plugin.ts +210 -0
  324. package/src/core/policy/apply.ts +272 -0
  325. package/src/core/policy/diff.ts +288 -0
  326. package/src/core/policy/loader.test.ts +63 -0
  327. package/src/core/policy/loader.ts +214 -0
  328. package/src/core/policy/policy.test.ts +232 -0
  329. package/src/core/policy/types.ts +105 -0
  330. package/src/core/schema-builder.test.ts +660 -0
  331. package/src/core/schema-builder.ts +881 -0
  332. package/src/core/standard-schema.ts +56 -0
  333. package/src/core/types.ts +258 -0
  334. package/src/core/validation.test.ts +195 -0
  335. package/src/core/validation.ts +192 -0
  336. package/src/drizzle/adapter.test.ts +425 -0
  337. package/src/drizzle/adapter.ts +474 -0
  338. package/src/drizzle/index.ts +31 -0
  339. package/src/drizzle/pg/adapter.integration-test.ts +456 -0
  340. package/src/drizzle/pg/index.ts +13 -0
  341. package/src/drizzle/pg/pg.integration-test.ts +1539 -0
  342. package/src/drizzle/pg/schema.ts +539 -0
  343. package/src/drizzle/pg-error-map.test.ts +218 -0
  344. package/src/drizzle/pg-error-map.ts +151 -0
  345. package/src/drizzle/schema.ts +544 -0
  346. package/src/drizzle/sqlite-serialization.test.ts +106 -0
  347. package/src/express/express-api-key-user-routes.test.ts +241 -0
  348. package/src/express/express.test.ts +442 -0
  349. package/src/express/handle.ts +191 -0
  350. package/src/express/index.ts +62 -0
  351. package/src/express/middleware.ts +551 -0
  352. package/src/express/protect.ts +154 -0
  353. package/src/express/validated.test.ts +86 -0
  354. package/src/express/validated.ts +99 -0
  355. package/src/fetcher/index.ts +81 -0
  356. package/src/hono/convert-routes.test.ts +220 -0
  357. package/src/hono/convert-routes.ts +196 -0
  358. package/src/hono/converters.test.ts +50 -0
  359. package/src/hono/converters.ts +43 -0
  360. package/src/hono/handle.ts +79 -0
  361. package/src/hono/helpers.ts +2 -0
  362. package/src/hono/hono-api-key-user-routes.test.ts +225 -0
  363. package/src/hono/hono-handlers.test.ts +861 -0
  364. package/src/hono/hono.test.ts +454 -0
  365. package/src/hono/index.ts +101 -0
  366. package/src/hono/middleware/auth.ts +236 -0
  367. package/src/hono/middleware/auth.types.test.ts +94 -0
  368. package/src/hono/middleware/csrf.test.ts +127 -0
  369. package/src/hono/middleware/csrf.ts +68 -0
  370. package/src/hono/middleware/error-handler.ts +12 -0
  371. package/src/hono/middleware/plugin-middleware.ts +35 -0
  372. package/src/hono/middleware/rbac.ts +131 -0
  373. package/src/hono/middleware/security-headers.test.ts +88 -0
  374. package/src/hono/middleware/security-headers.ts +68 -0
  375. package/src/hono/openapi.ts +237 -0
  376. package/src/hono/protect.ts +87 -0
  377. package/src/hono/validated.test.ts +123 -0
  378. package/src/hono/validated.ts +62 -0
  379. package/src/index.ts +309 -0
  380. package/src/integration.test.ts +718 -0
  381. package/src/internal/changelog.ts +56 -0
  382. package/src/otel/index.ts +158 -0
  383. package/src/otel/otel-types.test.ts +35 -0
  384. package/src/otel/otel.test.ts +235 -0
  385. package/src/plugins/account-lockout/account-lockout.test.ts +367 -0
  386. package/src/plugins/account-lockout/index.ts +267 -0
  387. package/src/plugins/admin/admin-api-key.test.ts +438 -0
  388. package/src/plugins/admin/index.ts +1612 -0
  389. package/src/plugins/api-key/api-key.test.ts +684 -0
  390. package/src/plugins/api-key/core.ts +336 -0
  391. package/src/plugins/api-key/index.ts +315 -0
  392. package/src/plugins/audit-log/audit-log.test.ts +749 -0
  393. package/src/plugins/audit-log/index.ts +680 -0
  394. package/src/plugins/data-isolation/data-isolation.test.ts +197 -0
  395. package/src/plugins/data-isolation/index.ts +157 -0
  396. package/src/plugins/email-verification/email-verification.test.ts +199 -0
  397. package/src/plugins/email-verification/index.ts +190 -0
  398. package/src/plugins/magic-link/index.ts +129 -0
  399. package/src/plugins/magic-link/magic-link.test.ts +156 -0
  400. package/src/plugins/oauth/id-token.ts +86 -0
  401. package/src/plugins/oauth/index.ts +2145 -0
  402. package/src/plugins/oauth/jwks.test.ts +32 -0
  403. package/src/plugins/oauth/jwks.ts +182 -0
  404. package/src/plugins/oauth/oauth.test.ts +2220 -0
  405. package/src/plugins/oauth/pkce.ts +58 -0
  406. package/src/plugins/openapi/index.ts +298 -0
  407. package/src/plugins/openapi/openapi.test.ts +425 -0
  408. package/src/plugins/openapi/spec-builder.ts +287 -0
  409. package/src/plugins/rate-limit/express.test.ts +89 -0
  410. package/src/plugins/rate-limit/express.ts +86 -0
  411. package/src/plugins/rate-limit/hono.test.ts +60 -0
  412. package/src/plugins/rate-limit/hono.ts +74 -0
  413. package/src/plugins/rate-limit/index.ts +383 -0
  414. package/src/plugins/rate-limit/memory-store.ts +61 -0
  415. package/src/plugins/rate-limit/rate-limit.test.ts +756 -0
  416. package/src/plugins/rate-limit/sveltekit.test.ts +58 -0
  417. package/src/plugins/rate-limit/sveltekit.ts +66 -0
  418. package/src/plugins/social-login/index.ts +715 -0
  419. package/src/plugins/social-login/providers/apple.ts +34 -0
  420. package/src/plugins/social-login/providers/discord.ts +26 -0
  421. package/src/plugins/social-login/providers/github.ts +54 -0
  422. package/src/plugins/social-login/providers/google.ts +23 -0
  423. package/src/plugins/social-login/providers/index.ts +22 -0
  424. package/src/plugins/social-login/providers/microsoft.ts +35 -0
  425. package/src/plugins/social-login/providers/oidc.ts +37 -0
  426. package/src/plugins/social-login/providers/providers.test.ts +211 -0
  427. package/src/plugins/social-login/social-login.test.ts +675 -0
  428. package/src/plugins/social-login/types.ts +80 -0
  429. package/src/plugins/tenancy/index.ts +544 -0
  430. package/src/plugins/tenancy/tenancy.test.ts +323 -0
  431. package/src/plugins/two-factor/index.ts +569 -0
  432. package/src/plugins/two-factor/two-factor.test.ts +235 -0
  433. package/src/plugins/webauthn/index.ts +554 -0
  434. package/src/plugins/webauthn/webauthn.test.ts +472 -0
  435. package/src/plugins/webhook/builtin-events.ts +29 -0
  436. package/src/plugins/webhook/delivery.test.ts +51 -0
  437. package/src/plugins/webhook/delivery.ts +154 -0
  438. package/src/plugins/webhook/hooks.ts +89 -0
  439. package/src/plugins/webhook/index.ts +445 -0
  440. package/src/plugins/webhook/queue/database.ts +67 -0
  441. package/src/plugins/webhook/queue/in-memory.test.ts +48 -0
  442. package/src/plugins/webhook/queue/in-memory.ts +71 -0
  443. package/src/plugins/webhook/queue/types.ts +51 -0
  444. package/src/plugins/webhook/registry.test.ts +48 -0
  445. package/src/plugins/webhook/registry.ts +64 -0
  446. package/src/plugins/webhook/signing.ts +45 -0
  447. package/src/plugins/webhook/ssrf.ts +198 -0
  448. package/src/plugins/webhook/types.ts +138 -0
  449. package/src/plugins/webhook/webhook.test.ts +324 -0
  450. package/src/sveltekit/actions.ts +233 -0
  451. package/src/sveltekit/catch-all.ts +43 -0
  452. package/src/sveltekit/cookies.ts +136 -0
  453. package/src/sveltekit/handle.ts +356 -0
  454. package/src/sveltekit/helpers.ts +69 -0
  455. package/src/sveltekit/index.ts +74 -0
  456. package/src/sveltekit/protect.ts +80 -0
  457. package/src/sveltekit/sveltekit-types.test.ts +31 -0
  458. package/src/sveltekit/sveltekit.test.ts +799 -0
  459. package/src/sveltekit/types.ts +134 -0
  460. package/src/sveltekit/validated.test.ts +117 -0
  461. package/src/sveltekit/validated.ts +78 -0
  462. package/src/testing/adapter-conformance.test.ts +408 -0
  463. package/src/testing/checks.test.ts +124 -0
  464. package/src/testing/checks.ts +304 -0
  465. package/src/testing/index.ts +107 -0
@@ -0,0 +1,246 @@
1
+ // src/core/auth/refresh-token.ts
2
+ async function generateRefreshToken() {
3
+ const bytes = new Uint8Array(32);
4
+ crypto.getRandomValues(bytes);
5
+ const raw = base64UrlEncode(bytes);
6
+ const hash = await hashToken(raw);
7
+ return { raw, hash };
8
+ }
9
+ async function hashToken(raw) {
10
+ const data = new TextEncoder().encode(raw);
11
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
12
+ return hexEncode(new Uint8Array(hashBuffer));
13
+ }
14
+ function base64UrlEncode(bytes) {
15
+ const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join("");
16
+ return btoa(binString).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
17
+ }
18
+ function hexEncode(bytes) {
19
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
20
+ }
21
+
22
+ // src/core/errors.ts
23
+ var FortressError = class extends Error {
24
+ code;
25
+ statusCode;
26
+ retryAfter;
27
+ details;
28
+ /** RFC 6749 §5.2 / §4.1.2.1 machine code. Set by `Errors.oauth()`. */
29
+ oauthError;
30
+ /** Human-readable description for the OAuth `error_description` field. */
31
+ oauthDescription;
32
+ /** Optional URI for the OAuth `error_uri` field (§5.2). */
33
+ oauthErrorUri;
34
+ constructor(code, message, statusCode, options) {
35
+ super(message, { cause: options?.cause });
36
+ this.code = code;
37
+ this.statusCode = statusCode;
38
+ this.retryAfter = options?.retryAfter;
39
+ this.details = options?.details;
40
+ this.oauthError = options?.oauthError;
41
+ this.oauthDescription = options?.oauthDescription;
42
+ this.oauthErrorUri = options?.oauthErrorUri;
43
+ }
44
+ toJSON() {
45
+ return {
46
+ code: this.code,
47
+ message: this.message,
48
+ statusCode: this.statusCode,
49
+ ...this.details !== void 0 && { details: this.details }
50
+ };
51
+ }
52
+ };
53
+ var Errors = {
54
+ unauthorized: (message = "Unauthorized") => new FortressError("UNAUTHORIZED", message, 401),
55
+ tokenReuse: () => new FortressError("TOKEN_REUSE", "Token reuse detected", 401),
56
+ sessionIdleTimeout: () => new FortressError("SESSION_IDLE_TIMEOUT", "Session idle timeout exceeded", 401),
57
+ sessionAbsoluteTimeout: () => new FortressError("SESSION_ABSOLUTE_TIMEOUT", "Session absolute timeout exceeded", 401),
58
+ forbidden: (message = "Forbidden") => new FortressError("FORBIDDEN", message, 403),
59
+ badRequest: (message = "Bad request") => new FortressError("BAD_REQUEST", message, 400),
60
+ notFound: (message = "Not found") => new FortressError("NOT_FOUND", message, 404),
61
+ conflict: (message = "Conflict", options) => new FortressError("CONFLICT", message, 409, options),
62
+ unprocessable: (message = "Unprocessable entity", options) => new FortressError("UNPROCESSABLE_ENTITY", message, 422, options),
63
+ rateLimited: (retryAfter) => new FortressError("RATE_LIMITED", "Too many requests", 429, { retryAfter }),
64
+ database: (message = "Database error", cause) => new FortressError("DATABASE_ERROR", message, 500, { cause }),
65
+ serviceUnavailable: (message = "Service unavailable", options) => new FortressError("SERVICE_UNAVAILABLE", message, 503, options),
66
+ validationError: (issues) => new FortressError("VALIDATION_ERROR", "Validation failed", 422, { details: issues }),
67
+ /**
68
+ * RFC 6749 §5.2 / §4.1.2.1 OAuth error.
69
+ *
70
+ * The HTTP error mapper detects `oauthError` and emits the OAuth-spec
71
+ * JSON body `{ error, error_description, error_uri? }` instead of the
72
+ * default fortress `{ code, message }` shape, so strict OAuth clients
73
+ * (Moodle, openid-client, Spring Security, etc.) can switch behaviour
74
+ * on the machine-readable `error` field.
75
+ *
76
+ * Status is inferred from the OAuth code per the spec table:
77
+ * - `invalid_client` → 401
78
+ * - everything else → 400
79
+ */
80
+ oauth: (error, description, options) => {
81
+ const status = options?.status ?? (error === "invalid_client" ? 401 : 400);
82
+ const code = status === 401 ? "UNAUTHORIZED" : "BAD_REQUEST";
83
+ return new FortressError(code, description ?? error, status, {
84
+ oauthError: error,
85
+ oauthDescription: description,
86
+ oauthErrorUri: options?.errorUri,
87
+ cause: options?.cause
88
+ });
89
+ },
90
+ /**
91
+ * Reconstruct a {@link FortressError} from a JSON error body emitted by
92
+ * `errorToResponse`. Used by the in-process `fortress.call.*` client to
93
+ * convert non-2xx responses into typed throws that mirror what a direct
94
+ * service-method call would produce.
95
+ *
96
+ * Maps by HTTP status when the body doesn't carry a recognizable
97
+ * `{ code, message }` payload, so network errors and opaque upstream
98
+ * failures still become structured `FortressError`s.
99
+ */
100
+ fromHttpResponse: (status, body) => {
101
+ const payload = body && typeof body === "object" ? body : {};
102
+ const message = typeof payload.message === "string" ? payload.message : `HTTP ${status}`;
103
+ const code = typeof payload.code === "string" && isFortressErrorCode(payload.code) ? payload.code : statusToErrorCode(status);
104
+ return new FortressError(code, message, status, { details: payload.details });
105
+ }
106
+ };
107
+ function isFortressErrorCode(code) {
108
+ return code === "UNAUTHORIZED" || code === "TOKEN_REUSE" || code === "SESSION_IDLE_TIMEOUT" || code === "SESSION_ABSOLUTE_TIMEOUT" || code === "FORBIDDEN" || code === "BAD_REQUEST" || code === "NOT_FOUND" || code === "CONFLICT" || code === "UNPROCESSABLE_ENTITY" || code === "RATE_LIMITED" || code === "DATABASE_ERROR" || code === "VALIDATION_ERROR" || code === "SERVICE_UNAVAILABLE";
109
+ }
110
+ function statusToErrorCode(status) {
111
+ if (status === 401)
112
+ return "UNAUTHORIZED";
113
+ if (status === 403)
114
+ return "FORBIDDEN";
115
+ if (status === 404)
116
+ return "NOT_FOUND";
117
+ if (status === 409)
118
+ return "CONFLICT";
119
+ if (status === 422)
120
+ return "UNPROCESSABLE_ENTITY";
121
+ if (status === 429)
122
+ return "RATE_LIMITED";
123
+ if (status === 503)
124
+ return "SERVICE_UNAVAILABLE";
125
+ if (status >= 500)
126
+ return "DATABASE_ERROR";
127
+ return "BAD_REQUEST";
128
+ }
129
+
130
+ // src/plugins/email-verification/index.ts
131
+ function emailVerification(config = {}) {
132
+ const tokenExpirySeconds = config.tokenExpirySeconds ?? 86400;
133
+ const requireVerification = config.requireVerification ?? true;
134
+ async function consumeVerificationToken(db, rawToken, expectedUserId) {
135
+ const hash = await hashToken(rawToken);
136
+ const usedAt = /* @__PURE__ */ new Date();
137
+ const record = await db.update({
138
+ model: "email_verification_token",
139
+ where: [
140
+ { field: "token", operator: "=", value: hash },
141
+ { field: "usedAt", operator: "isNull", value: null },
142
+ { field: "expiresAt", operator: "gt", value: usedAt },
143
+ ...expectedUserId ? [{ field: "userId", operator: "=", value: expectedUserId }] : []
144
+ ],
145
+ data: { usedAt }
146
+ });
147
+ if (!record)
148
+ throw Errors.notFound("Invalid or expired verification token");
149
+ await db.update({
150
+ model: "user",
151
+ where: [{ field: "id", operator: "=", value: record.userId }],
152
+ data: { email: record.email, emailVerified: true }
153
+ });
154
+ return record;
155
+ }
156
+ return {
157
+ name: "email-verification",
158
+ models: [{
159
+ name: "email_verification_token",
160
+ fields: {
161
+ id: { type: "number", required: true },
162
+ userId: { type: "number", required: true, references: { model: "user", field: "id" } },
163
+ token: { type: "string", required: true },
164
+ email: { type: "string", required: true },
165
+ expiresAt: { type: "date", required: true },
166
+ usedAt: { type: "date" },
167
+ createdAt: { type: "date", required: true }
168
+ }
169
+ }],
170
+ hooks: {
171
+ postAuthGate: {
172
+ reason: "email-verification",
173
+ async evaluate(ctx) {
174
+ if (!requireVerification || ctx.user.emailVerified)
175
+ return;
176
+ const tokens = await ctx.db.findMany({
177
+ model: "email_verification_token",
178
+ where: [{ field: "userId", operator: "=", value: ctx.user.id }]
179
+ });
180
+ return tokens.some((token) => token.usedAt !== null) ? void 0 : { pluginData: { requiresEmailVerification: true } };
181
+ },
182
+ async verify(ctx, completion) {
183
+ if (typeof completion !== "string")
184
+ throw Errors.unauthorized("Invalid verification token");
185
+ await consumeVerificationToken(ctx.db, completion, ctx.user.id);
186
+ }
187
+ },
188
+ async afterRegister(ctx, user) {
189
+ const { raw, hash } = await generateRefreshToken();
190
+ const expiresAt = new Date(Date.now() + tokenExpirySeconds * 1e3);
191
+ await ctx.db.create({
192
+ model: "email_verification_token",
193
+ data: {
194
+ userId: user.id,
195
+ token: hash,
196
+ email: user.email,
197
+ expiresAt,
198
+ usedAt: null
199
+ }
200
+ });
201
+ if (config.onSendVerification) {
202
+ await config.onSendVerification(user.email, raw, user.id);
203
+ }
204
+ }
205
+ },
206
+ methods: (ctx) => ({
207
+ async sendVerification(userId, email) {
208
+ const user = await ctx.db.findOne({
209
+ model: "user",
210
+ where: [{ field: "id", operator: "=", value: userId }]
211
+ });
212
+ if (!user)
213
+ throw Errors.notFound("User not found");
214
+ const targetEmail = email ?? user.email;
215
+ const { raw, hash } = await generateRefreshToken();
216
+ const expiresAt = new Date(Date.now() + tokenExpirySeconds * 1e3);
217
+ await ctx.db.create({
218
+ model: "email_verification_token",
219
+ data: {
220
+ userId,
221
+ token: hash,
222
+ email: targetEmail,
223
+ expiresAt,
224
+ usedAt: null
225
+ }
226
+ });
227
+ if (config.onSendVerification) {
228
+ await config.onSendVerification(targetEmail, raw, userId);
229
+ }
230
+ return { sent: true };
231
+ },
232
+ async verify(rawToken) {
233
+ const record = await ctx.db.transaction((tx) => consumeVerificationToken(tx, rawToken));
234
+ return { userId: record.userId, email: record.email };
235
+ },
236
+ async completeVerification(continuationToken, verificationToken, meta) {
237
+ if (!ctx.auth)
238
+ throw Errors.badRequest("Auth service is unavailable");
239
+ return ctx.auth.completePendingAuth(continuationToken, verificationToken, meta);
240
+ }
241
+ })
242
+ };
243
+ }
244
+ export {
245
+ emailVerification
246
+ };
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/plugins/magic-link/index.ts
21
+ var magic_link_exports = {};
22
+ __export(magic_link_exports, {
23
+ magicLink: () => magicLink
24
+ });
25
+ module.exports = __toCommonJS(magic_link_exports);
26
+
27
+ // src/core/auth/refresh-token.ts
28
+ async function generateRefreshToken() {
29
+ const bytes = new Uint8Array(32);
30
+ crypto.getRandomValues(bytes);
31
+ const raw = base64UrlEncode(bytes);
32
+ const hash = await hashToken(raw);
33
+ return { raw, hash };
34
+ }
35
+ async function hashToken(raw) {
36
+ const data = new TextEncoder().encode(raw);
37
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
38
+ return hexEncode(new Uint8Array(hashBuffer));
39
+ }
40
+ function base64UrlEncode(bytes) {
41
+ const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join("");
42
+ return btoa(binString).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
43
+ }
44
+ function hexEncode(bytes) {
45
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
46
+ }
47
+
48
+ // src/core/errors.ts
49
+ var FortressError = class extends Error {
50
+ code;
51
+ statusCode;
52
+ retryAfter;
53
+ details;
54
+ /** RFC 6749 §5.2 / §4.1.2.1 machine code. Set by `Errors.oauth()`. */
55
+ oauthError;
56
+ /** Human-readable description for the OAuth `error_description` field. */
57
+ oauthDescription;
58
+ /** Optional URI for the OAuth `error_uri` field (§5.2). */
59
+ oauthErrorUri;
60
+ constructor(code, message, statusCode, options) {
61
+ super(message, { cause: options?.cause });
62
+ this.code = code;
63
+ this.statusCode = statusCode;
64
+ this.retryAfter = options?.retryAfter;
65
+ this.details = options?.details;
66
+ this.oauthError = options?.oauthError;
67
+ this.oauthDescription = options?.oauthDescription;
68
+ this.oauthErrorUri = options?.oauthErrorUri;
69
+ }
70
+ toJSON() {
71
+ return {
72
+ code: this.code,
73
+ message: this.message,
74
+ statusCode: this.statusCode,
75
+ ...this.details !== void 0 && { details: this.details }
76
+ };
77
+ }
78
+ };
79
+ var Errors = {
80
+ unauthorized: (message = "Unauthorized") => new FortressError("UNAUTHORIZED", message, 401),
81
+ tokenReuse: () => new FortressError("TOKEN_REUSE", "Token reuse detected", 401),
82
+ sessionIdleTimeout: () => new FortressError("SESSION_IDLE_TIMEOUT", "Session idle timeout exceeded", 401),
83
+ sessionAbsoluteTimeout: () => new FortressError("SESSION_ABSOLUTE_TIMEOUT", "Session absolute timeout exceeded", 401),
84
+ forbidden: (message = "Forbidden") => new FortressError("FORBIDDEN", message, 403),
85
+ badRequest: (message = "Bad request") => new FortressError("BAD_REQUEST", message, 400),
86
+ notFound: (message = "Not found") => new FortressError("NOT_FOUND", message, 404),
87
+ conflict: (message = "Conflict", options) => new FortressError("CONFLICT", message, 409, options),
88
+ unprocessable: (message = "Unprocessable entity", options) => new FortressError("UNPROCESSABLE_ENTITY", message, 422, options),
89
+ rateLimited: (retryAfter) => new FortressError("RATE_LIMITED", "Too many requests", 429, { retryAfter }),
90
+ database: (message = "Database error", cause) => new FortressError("DATABASE_ERROR", message, 500, { cause }),
91
+ serviceUnavailable: (message = "Service unavailable", options) => new FortressError("SERVICE_UNAVAILABLE", message, 503, options),
92
+ validationError: (issues) => new FortressError("VALIDATION_ERROR", "Validation failed", 422, { details: issues }),
93
+ /**
94
+ * RFC 6749 §5.2 / §4.1.2.1 OAuth error.
95
+ *
96
+ * The HTTP error mapper detects `oauthError` and emits the OAuth-spec
97
+ * JSON body `{ error, error_description, error_uri? }` instead of the
98
+ * default fortress `{ code, message }` shape, so strict OAuth clients
99
+ * (Moodle, openid-client, Spring Security, etc.) can switch behaviour
100
+ * on the machine-readable `error` field.
101
+ *
102
+ * Status is inferred from the OAuth code per the spec table:
103
+ * - `invalid_client` → 401
104
+ * - everything else → 400
105
+ */
106
+ oauth: (error, description, options) => {
107
+ const status = options?.status ?? (error === "invalid_client" ? 401 : 400);
108
+ const code = status === 401 ? "UNAUTHORIZED" : "BAD_REQUEST";
109
+ return new FortressError(code, description ?? error, status, {
110
+ oauthError: error,
111
+ oauthDescription: description,
112
+ oauthErrorUri: options?.errorUri,
113
+ cause: options?.cause
114
+ });
115
+ },
116
+ /**
117
+ * Reconstruct a {@link FortressError} from a JSON error body emitted by
118
+ * `errorToResponse`. Used by the in-process `fortress.call.*` client to
119
+ * convert non-2xx responses into typed throws that mirror what a direct
120
+ * service-method call would produce.
121
+ *
122
+ * Maps by HTTP status when the body doesn't carry a recognizable
123
+ * `{ code, message }` payload, so network errors and opaque upstream
124
+ * failures still become structured `FortressError`s.
125
+ */
126
+ fromHttpResponse: (status, body) => {
127
+ const payload = body && typeof body === "object" ? body : {};
128
+ const message = typeof payload.message === "string" ? payload.message : `HTTP ${status}`;
129
+ const code = typeof payload.code === "string" && isFortressErrorCode(payload.code) ? payload.code : statusToErrorCode(status);
130
+ return new FortressError(code, message, status, { details: payload.details });
131
+ }
132
+ };
133
+ function isFortressErrorCode(code) {
134
+ return code === "UNAUTHORIZED" || code === "TOKEN_REUSE" || code === "SESSION_IDLE_TIMEOUT" || code === "SESSION_ABSOLUTE_TIMEOUT" || code === "FORBIDDEN" || code === "BAD_REQUEST" || code === "NOT_FOUND" || code === "CONFLICT" || code === "UNPROCESSABLE_ENTITY" || code === "RATE_LIMITED" || code === "DATABASE_ERROR" || code === "VALIDATION_ERROR" || code === "SERVICE_UNAVAILABLE";
135
+ }
136
+ function statusToErrorCode(status) {
137
+ if (status === 401)
138
+ return "UNAUTHORIZED";
139
+ if (status === 403)
140
+ return "FORBIDDEN";
141
+ if (status === 404)
142
+ return "NOT_FOUND";
143
+ if (status === 409)
144
+ return "CONFLICT";
145
+ if (status === 422)
146
+ return "UNPROCESSABLE_ENTITY";
147
+ if (status === 429)
148
+ return "RATE_LIMITED";
149
+ if (status === 503)
150
+ return "SERVICE_UNAVAILABLE";
151
+ if (status >= 500)
152
+ return "DATABASE_ERROR";
153
+ return "BAD_REQUEST";
154
+ }
155
+
156
+ // src/plugins/magic-link/index.ts
157
+ function magicLink(config = {}) {
158
+ const tokenExpirySeconds = config.tokenExpirySeconds ?? 600;
159
+ return {
160
+ name: "magic-link",
161
+ models: [{
162
+ name: "magic_link_token",
163
+ fields: {
164
+ id: { type: "number", required: true },
165
+ email: { type: "string", required: true },
166
+ token: { type: "string", required: true },
167
+ expiresAt: { type: "date", required: true },
168
+ usedAt: { type: "date" },
169
+ createdAt: { type: "date", required: true }
170
+ }
171
+ }],
172
+ methods: (ctx) => ({
173
+ async sendMagicLink(email) {
174
+ const { raw, hash } = await generateRefreshToken();
175
+ const expiresAt = new Date(Date.now() + tokenExpirySeconds * 1e3);
176
+ await ctx.db.create({
177
+ model: "magic_link_token",
178
+ data: {
179
+ email,
180
+ token: hash,
181
+ expiresAt,
182
+ usedAt: null
183
+ }
184
+ });
185
+ if (config.onSendMagicLink) {
186
+ await config.onSendMagicLink(email, raw);
187
+ }
188
+ return { sent: true };
189
+ },
190
+ async verify(rawToken, meta) {
191
+ const hash = await hashToken(rawToken);
192
+ const usedAt = /* @__PURE__ */ new Date();
193
+ const record = await ctx.db.transaction((tx) => tx.update({
194
+ model: "magic_link_token",
195
+ where: [
196
+ { field: "token", operator: "=", value: hash },
197
+ { field: "usedAt", operator: "isNull", value: null },
198
+ { field: "expiresAt", operator: "gt", value: usedAt }
199
+ ],
200
+ data: { usedAt }
201
+ }));
202
+ if (!record)
203
+ throw Errors.notFound("Invalid or expired magic link token");
204
+ let user = await ctx.db.findOne({
205
+ model: "user",
206
+ where: [{ field: "email", operator: "=", value: record.email }]
207
+ });
208
+ if (!user) {
209
+ user = await ctx.auth.createUser({
210
+ email: record.email,
211
+ name: record.email.split("@")[0]
212
+ });
213
+ }
214
+ if (!user.emailVerified) {
215
+ await ctx.db.update({
216
+ model: "user",
217
+ where: [{ field: "id", operator: "=", value: user.id }],
218
+ data: { emailVerified: true }
219
+ });
220
+ }
221
+ if (!ctx.auth)
222
+ throw Errors.badRequest("Auth service is unavailable");
223
+ return ctx.auth.completePluginAuth(user.id, "magic-link", meta);
224
+ }
225
+ })
226
+ };
227
+ }
228
+ // Annotate the CommonJS export names for ESM import in node:
229
+ 0 && (module.exports = {
230
+ magicLink
231
+ });
@@ -0,0 +1,39 @@
1
+ import { F as FortressPlugin } from '../config-GtlVt6ZD.cjs';
2
+ import { R as RequestMeta, A as AuthResult } from '../types-et0R8tsC.cjs';
3
+ import '../index-CxEkfCA0.cjs';
4
+ import '../jwt.cjs';
5
+ import '../auth-service-BcyQ2PuZ.cjs';
6
+
7
+ /**
8
+ * Passwordless magic-link plugin for fortress.
9
+ *
10
+ * Issues short-lived hashed tokens delivered out-of-band (typically by email)
11
+ * and exchanges a valid token for a fortress access/refresh token pair.
12
+ * Useful as a passwordless sign-in flow or as a recovery mechanism.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ interface MagicLinkConfig {
18
+ /** Token expiry in seconds. Default: 600 (10 minutes). */
19
+ tokenExpirySeconds?: number;
20
+ /** Called when a magic link token is created -- send the link to the user */
21
+ onSendMagicLink?: (email: string, token: string) => Promise<void>;
22
+ }
23
+ interface MagicLinkMethods {
24
+ sendMagicLink: (email: string) => Promise<{
25
+ sent: true;
26
+ }>;
27
+ verify: (rawToken: string, meta?: RequestMeta) => Promise<AuthResult>;
28
+ }
29
+ /**
30
+ * Magic link plugin factory. Returns a {@link FortressPlugin} that issues
31
+ * short-lived hashed tokens for passwordless sign-in. Tokens are typically
32
+ * delivered out-of-band (email) and exchanged for fortress access/refresh
33
+ * tokens via the verify endpoint.
34
+ */
35
+ declare function magicLink(config?: MagicLinkConfig): FortressPlugin & {
36
+ readonly name: 'magic-link';
37
+ };
38
+
39
+ export { type MagicLinkConfig, type MagicLinkMethods, magicLink };
@@ -0,0 +1,39 @@
1
+ import { F as FortressPlugin } from '../config-B2366s_G.js';
2
+ import { R as RequestMeta, A as AuthResult } from '../types-et0R8tsC.js';
3
+ import '../index-CxEkfCA0.js';
4
+ import '../jwt.js';
5
+ import '../auth-service-BkzZkWfH.js';
6
+
7
+ /**
8
+ * Passwordless magic-link plugin for fortress.
9
+ *
10
+ * Issues short-lived hashed tokens delivered out-of-band (typically by email)
11
+ * and exchanges a valid token for a fortress access/refresh token pair.
12
+ * Useful as a passwordless sign-in flow or as a recovery mechanism.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ interface MagicLinkConfig {
18
+ /** Token expiry in seconds. Default: 600 (10 minutes). */
19
+ tokenExpirySeconds?: number;
20
+ /** Called when a magic link token is created -- send the link to the user */
21
+ onSendMagicLink?: (email: string, token: string) => Promise<void>;
22
+ }
23
+ interface MagicLinkMethods {
24
+ sendMagicLink: (email: string) => Promise<{
25
+ sent: true;
26
+ }>;
27
+ verify: (rawToken: string, meta?: RequestMeta) => Promise<AuthResult>;
28
+ }
29
+ /**
30
+ * Magic link plugin factory. Returns a {@link FortressPlugin} that issues
31
+ * short-lived hashed tokens for passwordless sign-in. Tokens are typically
32
+ * delivered out-of-band (email) and exchanged for fortress access/refresh
33
+ * tokens via the verify endpoint.
34
+ */
35
+ declare function magicLink(config?: MagicLinkConfig): FortressPlugin & {
36
+ readonly name: 'magic-link';
37
+ };
38
+
39
+ export { type MagicLinkConfig, type MagicLinkMethods, magicLink };