@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,49 @@
1
+ import { e as PluginContext, F as FortressPlugin } from '../config-GtlVt6ZD.cjs';
2
+ import '../index-CxEkfCA0.cjs';
3
+ import '../jwt.cjs';
4
+ import '../types-et0R8tsC.cjs';
5
+ import '../auth-service-BcyQ2PuZ.cjs';
6
+
7
+ /**
8
+ * Row-level data isolation plugin for fortress.
9
+ *
10
+ * Lets you scope read/write access to a database row by per-user assignments,
11
+ * for example "user can only see rows where `org_id` equals their assigned
12
+ * org". Works with any database adapter via the core `scopeRules` capability.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ interface DataIsolationScope {
18
+ /** Scope name for identification and bypass control */
19
+ name: string;
20
+ /** Column name that holds the scoping value in the target tables */
21
+ field: string;
22
+ /** Which models (tables) this scope applies to. Use ['*'] for all models. */
23
+ models: string[];
24
+ /** Resolve the current user's value for this scope */
25
+ resolveValue: (userId: string, ctx: PluginContext) => Promise<unknown>;
26
+ }
27
+ interface DataIsolationConfig {
28
+ scopes: DataIsolationScope[];
29
+ /**
30
+ * Behavior when an applicable scope resolves to `null` or `undefined`.
31
+ * Defaults to `'deny'`. `'skip'` is an unsafe legacy compatibility mode
32
+ * that removes that scope's row isolation for the operation.
33
+ */
34
+ unresolvedScope?: 'deny' | 'skip';
35
+ }
36
+ interface DataIsolationMethods {
37
+ withoutScope: <T>(scopeName: string, fn: () => Promise<T>) => Promise<T>;
38
+ unscoped: <T>(fn: () => Promise<T>) => Promise<T>;
39
+ }
40
+ /**
41
+ * Data isolation plugin factory. Returns a {@link FortressPlugin} that scopes
42
+ * read and write access to a database row by per-user scope assignments,
43
+ * enforcing isolation through the core `scopeRules` capability.
44
+ */
45
+ declare function dataIsolation(config: DataIsolationConfig): FortressPlugin & {
46
+ readonly name: 'data-isolation';
47
+ };
48
+
49
+ export { type DataIsolationConfig, type DataIsolationMethods, type DataIsolationScope, dataIsolation };
@@ -0,0 +1,49 @@
1
+ import { e as PluginContext, F as FortressPlugin } from '../config-B2366s_G.js';
2
+ import '../index-CxEkfCA0.js';
3
+ import '../jwt.js';
4
+ import '../types-et0R8tsC.js';
5
+ import '../auth-service-BkzZkWfH.js';
6
+
7
+ /**
8
+ * Row-level data isolation plugin for fortress.
9
+ *
10
+ * Lets you scope read/write access to a database row by per-user assignments,
11
+ * for example "user can only see rows where `org_id` equals their assigned
12
+ * org". Works with any database adapter via the core `scopeRules` capability.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ interface DataIsolationScope {
18
+ /** Scope name for identification and bypass control */
19
+ name: string;
20
+ /** Column name that holds the scoping value in the target tables */
21
+ field: string;
22
+ /** Which models (tables) this scope applies to. Use ['*'] for all models. */
23
+ models: string[];
24
+ /** Resolve the current user's value for this scope */
25
+ resolveValue: (userId: string, ctx: PluginContext) => Promise<unknown>;
26
+ }
27
+ interface DataIsolationConfig {
28
+ scopes: DataIsolationScope[];
29
+ /**
30
+ * Behavior when an applicable scope resolves to `null` or `undefined`.
31
+ * Defaults to `'deny'`. `'skip'` is an unsafe legacy compatibility mode
32
+ * that removes that scope's row isolation for the operation.
33
+ */
34
+ unresolvedScope?: 'deny' | 'skip';
35
+ }
36
+ interface DataIsolationMethods {
37
+ withoutScope: <T>(scopeName: string, fn: () => Promise<T>) => Promise<T>;
38
+ unscoped: <T>(fn: () => Promise<T>) => Promise<T>;
39
+ }
40
+ /**
41
+ * Data isolation plugin factory. Returns a {@link FortressPlugin} that scopes
42
+ * read and write access to a database row by per-user scope assignments,
43
+ * enforcing isolation through the core `scopeRules` capability.
44
+ */
45
+ declare function dataIsolation(config: DataIsolationConfig): FortressPlugin & {
46
+ readonly name: 'data-isolation';
47
+ };
48
+
49
+ export { type DataIsolationConfig, type DataIsolationMethods, type DataIsolationScope, dataIsolation };
@@ -0,0 +1,186 @@
1
+ // src/plugins/data-isolation/index.ts
2
+ import { AsyncLocalStorage } from "async_hooks";
3
+
4
+ // src/core/errors.ts
5
+ var FortressError = class extends Error {
6
+ code;
7
+ statusCode;
8
+ retryAfter;
9
+ details;
10
+ /** RFC 6749 §5.2 / §4.1.2.1 machine code. Set by `Errors.oauth()`. */
11
+ oauthError;
12
+ /** Human-readable description for the OAuth `error_description` field. */
13
+ oauthDescription;
14
+ /** Optional URI for the OAuth `error_uri` field (§5.2). */
15
+ oauthErrorUri;
16
+ constructor(code, message, statusCode, options) {
17
+ super(message, { cause: options?.cause });
18
+ this.code = code;
19
+ this.statusCode = statusCode;
20
+ this.retryAfter = options?.retryAfter;
21
+ this.details = options?.details;
22
+ this.oauthError = options?.oauthError;
23
+ this.oauthDescription = options?.oauthDescription;
24
+ this.oauthErrorUri = options?.oauthErrorUri;
25
+ }
26
+ toJSON() {
27
+ return {
28
+ code: this.code,
29
+ message: this.message,
30
+ statusCode: this.statusCode,
31
+ ...this.details !== void 0 && { details: this.details }
32
+ };
33
+ }
34
+ };
35
+ var Errors = {
36
+ unauthorized: (message = "Unauthorized") => new FortressError("UNAUTHORIZED", message, 401),
37
+ tokenReuse: () => new FortressError("TOKEN_REUSE", "Token reuse detected", 401),
38
+ sessionIdleTimeout: () => new FortressError("SESSION_IDLE_TIMEOUT", "Session idle timeout exceeded", 401),
39
+ sessionAbsoluteTimeout: () => new FortressError("SESSION_ABSOLUTE_TIMEOUT", "Session absolute timeout exceeded", 401),
40
+ forbidden: (message = "Forbidden") => new FortressError("FORBIDDEN", message, 403),
41
+ badRequest: (message = "Bad request") => new FortressError("BAD_REQUEST", message, 400),
42
+ notFound: (message = "Not found") => new FortressError("NOT_FOUND", message, 404),
43
+ conflict: (message = "Conflict", options) => new FortressError("CONFLICT", message, 409, options),
44
+ unprocessable: (message = "Unprocessable entity", options) => new FortressError("UNPROCESSABLE_ENTITY", message, 422, options),
45
+ rateLimited: (retryAfter) => new FortressError("RATE_LIMITED", "Too many requests", 429, { retryAfter }),
46
+ database: (message = "Database error", cause) => new FortressError("DATABASE_ERROR", message, 500, { cause }),
47
+ serviceUnavailable: (message = "Service unavailable", options) => new FortressError("SERVICE_UNAVAILABLE", message, 503, options),
48
+ validationError: (issues) => new FortressError("VALIDATION_ERROR", "Validation failed", 422, { details: issues }),
49
+ /**
50
+ * RFC 6749 §5.2 / §4.1.2.1 OAuth error.
51
+ *
52
+ * The HTTP error mapper detects `oauthError` and emits the OAuth-spec
53
+ * JSON body `{ error, error_description, error_uri? }` instead of the
54
+ * default fortress `{ code, message }` shape, so strict OAuth clients
55
+ * (Moodle, openid-client, Spring Security, etc.) can switch behaviour
56
+ * on the machine-readable `error` field.
57
+ *
58
+ * Status is inferred from the OAuth code per the spec table:
59
+ * - `invalid_client` → 401
60
+ * - everything else → 400
61
+ */
62
+ oauth: (error, description, options) => {
63
+ const status = options?.status ?? (error === "invalid_client" ? 401 : 400);
64
+ const code = status === 401 ? "UNAUTHORIZED" : "BAD_REQUEST";
65
+ return new FortressError(code, description ?? error, status, {
66
+ oauthError: error,
67
+ oauthDescription: description,
68
+ oauthErrorUri: options?.errorUri,
69
+ cause: options?.cause
70
+ });
71
+ },
72
+ /**
73
+ * Reconstruct a {@link FortressError} from a JSON error body emitted by
74
+ * `errorToResponse`. Used by the in-process `fortress.call.*` client to
75
+ * convert non-2xx responses into typed throws that mirror what a direct
76
+ * service-method call would produce.
77
+ *
78
+ * Maps by HTTP status when the body doesn't carry a recognizable
79
+ * `{ code, message }` payload, so network errors and opaque upstream
80
+ * failures still become structured `FortressError`s.
81
+ */
82
+ fromHttpResponse: (status, body) => {
83
+ const payload = body && typeof body === "object" ? body : {};
84
+ const message = typeof payload.message === "string" ? payload.message : `HTTP ${status}`;
85
+ const code = typeof payload.code === "string" && isFortressErrorCode(payload.code) ? payload.code : statusToErrorCode(status);
86
+ return new FortressError(code, message, status, { details: payload.details });
87
+ }
88
+ };
89
+ function isFortressErrorCode(code) {
90
+ 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";
91
+ }
92
+ function statusToErrorCode(status) {
93
+ if (status === 401)
94
+ return "UNAUTHORIZED";
95
+ if (status === 403)
96
+ return "FORBIDDEN";
97
+ if (status === 404)
98
+ return "NOT_FOUND";
99
+ if (status === 409)
100
+ return "CONFLICT";
101
+ if (status === 422)
102
+ return "UNPROCESSABLE_ENTITY";
103
+ if (status === 429)
104
+ return "RATE_LIMITED";
105
+ if (status === 503)
106
+ return "SERVICE_UNAVAILABLE";
107
+ if (status >= 500)
108
+ return "DATABASE_ERROR";
109
+ return "BAD_REQUEST";
110
+ }
111
+
112
+ // src/plugins/data-isolation/index.ts
113
+ var bypassStore = new AsyncLocalStorage();
114
+ function currentBypassState() {
115
+ return bypassStore.getStore();
116
+ }
117
+ function dataIsolation(config) {
118
+ return {
119
+ name: "data-isolation",
120
+ models: [{
121
+ name: "user_scope_assignment",
122
+ fields: {
123
+ id: { type: "number", required: true },
124
+ userId: { type: "number", required: true, references: { model: "user", field: "id" } },
125
+ scopeName: { type: "string", required: true },
126
+ scopeValue: { type: "string", required: true },
127
+ createdAt: { type: "date", required: true }
128
+ }
129
+ }],
130
+ async scopeRules(userId, model, ctx) {
131
+ const state = currentBypassState();
132
+ if (state?.all)
133
+ return null;
134
+ const filters = [];
135
+ const defaults = {};
136
+ for (const scope of config.scopes) {
137
+ if (state?.scopes.has(scope.name))
138
+ continue;
139
+ const applies = scope.models.includes("*") || scope.models.includes(model);
140
+ if (!applies)
141
+ continue;
142
+ const value = await scope.resolveValue(userId, ctx);
143
+ if (value === void 0 || value === null) {
144
+ if (config.unresolvedScope === "skip")
145
+ continue;
146
+ throw Errors.forbidden(`Data isolation scope '${scope.name}' could not be resolved`);
147
+ }
148
+ filters.push({ field: scope.field, operator: "=", value });
149
+ defaults[scope.field] = value;
150
+ }
151
+ return filters.length > 0 ? { filters, defaults } : null;
152
+ },
153
+ methods: () => ({
154
+ /**
155
+ * Execute a callback with a specific scope bypassed.
156
+ * Queries within the callback will not have the named scope filter applied.
157
+ *
158
+ * The bypass is async-context-local: it applies only to async work
159
+ * spawned inside `fn` and never leaks across concurrent requests.
160
+ */
161
+ async withoutScope(scopeName, fn) {
162
+ const existing = currentBypassState();
163
+ const nextScopes = new Set(existing?.scopes ?? []);
164
+ nextScopes.add(scopeName);
165
+ const next = { all: existing?.all ?? false, scopes: nextScopes };
166
+ return bypassStore.run(next, fn);
167
+ },
168
+ /**
169
+ * Execute a callback with all scopes bypassed.
170
+ * Use with caution \u2014 no row-level isolation is applied.
171
+ *
172
+ * The bypass is async-context-local. Calls outside the callback (and
173
+ * concurrent requests running on different async contexts) are
174
+ * unaffected.
175
+ */
176
+ async unscoped(fn) {
177
+ const existing = currentBypassState();
178
+ const next = { all: true, scopes: new Set(existing?.scopes ?? []) };
179
+ return bypassStore.run(next, fn);
180
+ }
181
+ })
182
+ };
183
+ }
184
+ export {
185
+ dataIsolation
186
+ };
@@ -0,0 +1,273 @@
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/email-verification/index.ts
21
+ var email_verification_exports = {};
22
+ __export(email_verification_exports, {
23
+ emailVerification: () => emailVerification
24
+ });
25
+ module.exports = __toCommonJS(email_verification_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/email-verification/index.ts
157
+ function emailVerification(config = {}) {
158
+ const tokenExpirySeconds = config.tokenExpirySeconds ?? 86400;
159
+ const requireVerification = config.requireVerification ?? true;
160
+ async function consumeVerificationToken(db, rawToken, expectedUserId) {
161
+ const hash = await hashToken(rawToken);
162
+ const usedAt = /* @__PURE__ */ new Date();
163
+ const record = await db.update({
164
+ model: "email_verification_token",
165
+ where: [
166
+ { field: "token", operator: "=", value: hash },
167
+ { field: "usedAt", operator: "isNull", value: null },
168
+ { field: "expiresAt", operator: "gt", value: usedAt },
169
+ ...expectedUserId ? [{ field: "userId", operator: "=", value: expectedUserId }] : []
170
+ ],
171
+ data: { usedAt }
172
+ });
173
+ if (!record)
174
+ throw Errors.notFound("Invalid or expired verification token");
175
+ await db.update({
176
+ model: "user",
177
+ where: [{ field: "id", operator: "=", value: record.userId }],
178
+ data: { email: record.email, emailVerified: true }
179
+ });
180
+ return record;
181
+ }
182
+ return {
183
+ name: "email-verification",
184
+ models: [{
185
+ name: "email_verification_token",
186
+ fields: {
187
+ id: { type: "number", required: true },
188
+ userId: { type: "number", required: true, references: { model: "user", field: "id" } },
189
+ token: { type: "string", required: true },
190
+ email: { type: "string", required: true },
191
+ expiresAt: { type: "date", required: true },
192
+ usedAt: { type: "date" },
193
+ createdAt: { type: "date", required: true }
194
+ }
195
+ }],
196
+ hooks: {
197
+ postAuthGate: {
198
+ reason: "email-verification",
199
+ async evaluate(ctx) {
200
+ if (!requireVerification || ctx.user.emailVerified)
201
+ return;
202
+ const tokens = await ctx.db.findMany({
203
+ model: "email_verification_token",
204
+ where: [{ field: "userId", operator: "=", value: ctx.user.id }]
205
+ });
206
+ return tokens.some((token) => token.usedAt !== null) ? void 0 : { pluginData: { requiresEmailVerification: true } };
207
+ },
208
+ async verify(ctx, completion) {
209
+ if (typeof completion !== "string")
210
+ throw Errors.unauthorized("Invalid verification token");
211
+ await consumeVerificationToken(ctx.db, completion, ctx.user.id);
212
+ }
213
+ },
214
+ async afterRegister(ctx, user) {
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: user.id,
221
+ token: hash,
222
+ email: user.email,
223
+ expiresAt,
224
+ usedAt: null
225
+ }
226
+ });
227
+ if (config.onSendVerification) {
228
+ await config.onSendVerification(user.email, raw, user.id);
229
+ }
230
+ }
231
+ },
232
+ methods: (ctx) => ({
233
+ async sendVerification(userId, email) {
234
+ const user = await ctx.db.findOne({
235
+ model: "user",
236
+ where: [{ field: "id", operator: "=", value: userId }]
237
+ });
238
+ if (!user)
239
+ throw Errors.notFound("User not found");
240
+ const targetEmail = email ?? user.email;
241
+ const { raw, hash } = await generateRefreshToken();
242
+ const expiresAt = new Date(Date.now() + tokenExpirySeconds * 1e3);
243
+ await ctx.db.create({
244
+ model: "email_verification_token",
245
+ data: {
246
+ userId,
247
+ token: hash,
248
+ email: targetEmail,
249
+ expiresAt,
250
+ usedAt: null
251
+ }
252
+ });
253
+ if (config.onSendVerification) {
254
+ await config.onSendVerification(targetEmail, raw, userId);
255
+ }
256
+ return { sent: true };
257
+ },
258
+ async verify(rawToken) {
259
+ const record = await ctx.db.transaction((tx) => consumeVerificationToken(tx, rawToken));
260
+ return { userId: record.userId, email: record.email };
261
+ },
262
+ async completeVerification(continuationToken, verificationToken, meta) {
263
+ if (!ctx.auth)
264
+ throw Errors.badRequest("Auth service is unavailable");
265
+ return ctx.auth.completePendingAuth(continuationToken, verificationToken, meta);
266
+ }
267
+ })
268
+ };
269
+ }
270
+ // Annotate the CommonJS export names for ESM import in node:
271
+ 0 && (module.exports = {
272
+ emailVerification
273
+ });
@@ -0,0 +1,44 @@
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
+ * Email verification plugin for fortress.
9
+ *
10
+ * Issues hashed, time-limited verification tokens, exposes
11
+ * `requestVerification` and `verifyEmail` methods on the fortress instance,
12
+ * and adds the corresponding HTTP routes when mounted via a framework adapter.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ interface EmailVerificationConfig {
18
+ /** Token expiry in seconds (default: 86400 = 24h) */
19
+ tokenExpirySeconds?: number;
20
+ /** Block login for unverified users (default: true) */
21
+ requireVerification?: boolean;
22
+ /** Called when a verification token is created */
23
+ onSendVerification?: (email: string, token: string, userId: string) => Promise<void>;
24
+ }
25
+ interface EmailVerificationMethods {
26
+ sendVerification: (userId: string, email?: string) => Promise<{
27
+ sent: true;
28
+ }>;
29
+ verify: (rawToken: string) => Promise<{
30
+ userId: string;
31
+ email: string;
32
+ }>;
33
+ completeVerification: (continuationToken: string, verificationToken: string, meta?: RequestMeta) => Promise<AuthResult>;
34
+ }
35
+ /**
36
+ * Email verification plugin factory. Returns a {@link FortressPlugin} that
37
+ * issues hashed verification tokens, exposes `requestVerification` /
38
+ * `verifyEmail` methods, and (when mounted) the corresponding HTTP routes.
39
+ */
40
+ declare function emailVerification(config?: EmailVerificationConfig): FortressPlugin & {
41
+ readonly name: 'email-verification';
42
+ };
43
+
44
+ export { type EmailVerificationConfig, type EmailVerificationMethods, emailVerification };
@@ -0,0 +1,44 @@
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
+ * Email verification plugin for fortress.
9
+ *
10
+ * Issues hashed, time-limited verification tokens, exposes
11
+ * `requestVerification` and `verifyEmail` methods on the fortress instance,
12
+ * and adds the corresponding HTTP routes when mounted via a framework adapter.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ interface EmailVerificationConfig {
18
+ /** Token expiry in seconds (default: 86400 = 24h) */
19
+ tokenExpirySeconds?: number;
20
+ /** Block login for unverified users (default: true) */
21
+ requireVerification?: boolean;
22
+ /** Called when a verification token is created */
23
+ onSendVerification?: (email: string, token: string, userId: string) => Promise<void>;
24
+ }
25
+ interface EmailVerificationMethods {
26
+ sendVerification: (userId: string, email?: string) => Promise<{
27
+ sent: true;
28
+ }>;
29
+ verify: (rawToken: string) => Promise<{
30
+ userId: string;
31
+ email: string;
32
+ }>;
33
+ completeVerification: (continuationToken: string, verificationToken: string, meta?: RequestMeta) => Promise<AuthResult>;
34
+ }
35
+ /**
36
+ * Email verification plugin factory. Returns a {@link FortressPlugin} that
37
+ * issues hashed verification tokens, exposes `requestVerification` /
38
+ * `verifyEmail` methods, and (when mounted) the corresponding HTTP routes.
39
+ */
40
+ declare function emailVerification(config?: EmailVerificationConfig): FortressPlugin & {
41
+ readonly name: 'email-verification';
42
+ };
43
+
44
+ export { type EmailVerificationConfig, type EmailVerificationMethods, emailVerification };