@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,218 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { FortressError } from '../core/errors';
3
+ import { findSqlstate, rethrowDbError } from './pg-error-map';
4
+
5
+ describe('findSqlstate', () => {
6
+ it('returns the code from the top-level error', () => {
7
+ expect(findSqlstate({ code: '23505' })).toBe('23505');
8
+ });
9
+
10
+ it('walks the cause chain to find the SQLSTATE', () => {
11
+ const err = new Error('drizzle wrapper');
12
+ (err as any).cause = new Error('postgres-js wrapper');
13
+ ((err as any).cause as any).cause = { code: '40P01', message: 'deadlock' };
14
+ expect(findSqlstate(err)).toBe('40P01');
15
+ });
16
+
17
+ it('only recognizes the 5-char alnum SQLSTATE shape', () => {
18
+ expect(findSqlstate({ code: 'CONFLICT' })).toBeNull();
19
+ expect(findSqlstate({ code: 'abc12' })).toBeNull(); // lowercase not allowed
20
+ expect(findSqlstate({ code: 12345 })).toBeNull(); // number not allowed
21
+ expect(findSqlstate(null)).toBeNull();
22
+ expect(findSqlstate(undefined)).toBeNull();
23
+ expect(findSqlstate('not an object')).toBeNull();
24
+ });
25
+
26
+ it('gives up after a fixed depth', () => {
27
+ // Build a chain longer than MAX_CAUSE_DEPTH (6)
28
+ let leaf: any = { code: '23505' };
29
+ for (let i = 0; i < 10; i++)
30
+ leaf = { cause: leaf };
31
+ expect(findSqlstate(leaf)).toBeNull();
32
+ });
33
+ });
34
+
35
+ describe('rethrowDbError', () => {
36
+ it('passes a non-constraint sqlite error through untouched', () => {
37
+ // A pg SQLSTATE is not a SQLite constraint code, so under the sqlite
38
+ // dialect it is not recognized and the original is re-thrown.
39
+ const original = { code: '23505', message: 'unique violation' };
40
+ expect(() => rethrowDbError(original, 'sqlite')).toThrow();
41
+ try {
42
+ rethrowDbError(original, 'sqlite');
43
+ }
44
+ catch (err) {
45
+ expect(err).toBe(original);
46
+ }
47
+ });
48
+
49
+ it('maps a sqlite UNIQUE violation to CONFLICT/409 (by code)', () => {
50
+ try {
51
+ rethrowDbError({ code: 'SQLITE_CONSTRAINT_UNIQUE', message: 'UNIQUE constraint failed: user.email' }, 'sqlite');
52
+ throw new Error('should have thrown');
53
+ }
54
+ catch (err) {
55
+ expect(err).toBeInstanceOf(FortressError);
56
+ expect((err as FortressError).code).toBe('CONFLICT');
57
+ expect((err as FortressError).statusCode).toBe(409);
58
+ }
59
+ });
60
+
61
+ it('maps a sqlite UNIQUE violation to CONFLICT by message alone', () => {
62
+ // Older drivers set only the message, not a SQLITE_CONSTRAINT_* code.
63
+ try {
64
+ rethrowDbError(new Error('UNIQUE constraint failed: social_account.provider'), 'sqlite');
65
+ throw new Error('should have thrown');
66
+ }
67
+ catch (err) {
68
+ expect((err as FortressError).code).toBe('CONFLICT');
69
+ }
70
+ });
71
+
72
+ it('maps sqlite PRIMARYKEY to CONFLICT and FOREIGNKEY to UNPROCESSABLE_ENTITY', () => {
73
+ try {
74
+ rethrowDbError({ code: 'SQLITE_CONSTRAINT_PRIMARYKEY' }, 'sqlite');
75
+ throw new Error('should have thrown');
76
+ }
77
+ catch (err) {
78
+ expect((err as FortressError).code).toBe('CONFLICT');
79
+ }
80
+ try {
81
+ rethrowDbError({ code: 'SQLITE_CONSTRAINT_FOREIGNKEY' }, 'sqlite');
82
+ throw new Error('should have thrown');
83
+ }
84
+ catch (err) {
85
+ expect((err as FortressError).code).toBe('UNPROCESSABLE_ENTITY');
86
+ expect((err as FortressError).statusCode).toBe(422);
87
+ }
88
+ });
89
+
90
+ it('walks the cause chain for a wrapped sqlite constraint error', () => {
91
+ const driver = new Error('UNIQUE constraint failed: user.email');
92
+ (driver as any).code = 'SQLITE_CONSTRAINT_UNIQUE';
93
+ const wrapper = new Error('DrizzleQueryError');
94
+ (wrapper as any).cause = driver;
95
+ try {
96
+ rethrowDbError(wrapper, 'sqlite');
97
+ throw new Error('should have thrown');
98
+ }
99
+ catch (err) {
100
+ expect((err as FortressError).code).toBe('CONFLICT');
101
+ }
102
+ });
103
+
104
+ it('passes through unrecognized SQLSTATEs on pg', () => {
105
+ const original = { code: 'P0001', message: 'raise exception' };
106
+ try {
107
+ rethrowDbError(original, 'pg');
108
+ throw new Error('should have thrown');
109
+ }
110
+ catch (err) {
111
+ expect(err).toBe(original);
112
+ }
113
+ });
114
+
115
+ it('maps 23505 (unique_violation) to CONFLICT/409', () => {
116
+ try {
117
+ rethrowDbError({ code: '23505' }, 'pg');
118
+ throw new Error('should have thrown');
119
+ }
120
+ catch (err) {
121
+ expect(err).toBeInstanceOf(FortressError);
122
+ expect((err as FortressError).code).toBe('CONFLICT');
123
+ expect((err as FortressError).statusCode).toBe(409);
124
+ }
125
+ });
126
+
127
+ it('maps 23503 (foreign_key_violation) to UNPROCESSABLE_ENTITY/422', () => {
128
+ try {
129
+ rethrowDbError({ code: '23503' }, 'pg');
130
+ throw new Error('should have thrown');
131
+ }
132
+ catch (err) {
133
+ expect(err).toBeInstanceOf(FortressError);
134
+ expect((err as FortressError).code).toBe('UNPROCESSABLE_ENTITY');
135
+ expect((err as FortressError).statusCode).toBe(422);
136
+ }
137
+ });
138
+
139
+ it('maps 23502 (not_null_violation) to BAD_REQUEST/400', () => {
140
+ try {
141
+ rethrowDbError({ code: '23502' }, 'pg');
142
+ throw new Error('should have thrown');
143
+ }
144
+ catch (err) {
145
+ expect((err as FortressError).code).toBe('BAD_REQUEST');
146
+ expect((err as FortressError).statusCode).toBe(400);
147
+ }
148
+ });
149
+
150
+ it('maps 23514 (check_violation) to UNPROCESSABLE_ENTITY/422', () => {
151
+ try {
152
+ rethrowDbError({ code: '23514' }, 'pg');
153
+ throw new Error('should have thrown');
154
+ }
155
+ catch (err) {
156
+ expect((err as FortressError).code).toBe('UNPROCESSABLE_ENTITY');
157
+ expect((err as FortressError).statusCode).toBe(422);
158
+ }
159
+ });
160
+
161
+ it('maps 40001 (serialization_failure) to CONFLICT/409', () => {
162
+ try {
163
+ rethrowDbError({ code: '40001' }, 'pg');
164
+ throw new Error('should have thrown');
165
+ }
166
+ catch (err) {
167
+ expect((err as FortressError).code).toBe('CONFLICT');
168
+ expect((err as FortressError).statusCode).toBe(409);
169
+ }
170
+ });
171
+
172
+ it('maps 40P01 (deadlock_detected) to CONFLICT/409', () => {
173
+ try {
174
+ rethrowDbError({ code: '40P01' }, 'pg');
175
+ throw new Error('should have thrown');
176
+ }
177
+ catch (err) {
178
+ expect((err as FortressError).code).toBe('CONFLICT');
179
+ expect((err as FortressError).statusCode).toBe(409);
180
+ }
181
+ });
182
+
183
+ it('maps 57014 (query_canceled) to SERVICE_UNAVAILABLE/503', () => {
184
+ try {
185
+ rethrowDbError({ code: '57014' }, 'pg');
186
+ throw new Error('should have thrown');
187
+ }
188
+ catch (err) {
189
+ expect((err as FortressError).code).toBe('SERVICE_UNAVAILABLE');
190
+ expect((err as FortressError).statusCode).toBe(503);
191
+ }
192
+ });
193
+
194
+ it('preserves the original error as cause on the FortressError', () => {
195
+ const original = new Error('postgres: duplicate key value');
196
+ (original as any).code = '23505';
197
+ try {
198
+ rethrowDbError(original, 'pg');
199
+ throw new Error('should have thrown');
200
+ }
201
+ catch (err) {
202
+ expect((err as FortressError).cause).toBe(original);
203
+ }
204
+ });
205
+
206
+ it('walks cause chain to find SQLSTATE on wrapped drizzle errors', () => {
207
+ const driver = { code: '23505', message: 'duplicate key' };
208
+ const wrapper = new Error('DrizzleQueryError');
209
+ (wrapper as any).cause = driver;
210
+ try {
211
+ rethrowDbError(wrapper, 'pg');
212
+ throw new Error('should have thrown');
213
+ }
214
+ catch (err) {
215
+ expect((err as FortressError).code).toBe('CONFLICT');
216
+ }
217
+ });
218
+ });
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Database constraint-error → {@link FortressError} translation for the
3
+ * Drizzle adapter.
4
+ *
5
+ * Drizzle wraps driver-level errors before re-throwing, so a constraint
6
+ * violation typically surfaces as a generic `DrizzleQueryError` with the
7
+ * real driver error tucked under `error.cause`. Both matchers walk the
8
+ * cause chain up to a small fixed depth: {@link findSqlstate} looks for a
9
+ * Postgres 5-character SQLSTATE `code`, and the SQLite matcher looks for a
10
+ * `SQLITE_CONSTRAINT_*` code or the driver's constraint message.
11
+ *
12
+ * The mapping tables cover the constraint/concurrency states that almost
13
+ * every CRUD endpoint cares about. Anything not in the table is re-thrown
14
+ * unchanged so callers can still observe the raw driver error.
15
+ *
16
+ * {@link rethrowDbError} routes on the adapter `dialect`: Postgres via
17
+ * SQLSTATE, SQLite via constraint code/message. Unique violations map to
18
+ * `CONFLICT` (409) on both dialects so a mixed-dialect call site gets a
19
+ * stable wire response without branching.
20
+ */
21
+
22
+ import type { FortressError } from '../core/errors';
23
+ import type { DrizzleDialect } from './adapter';
24
+ import { Errors } from '../core/errors';
25
+
26
+ /** How many `cause` levels to walk while hunting for the SQLSTATE. */
27
+ const MAX_CAUSE_DEPTH = 6;
28
+
29
+ const SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;
30
+
31
+ /**
32
+ * SQLSTATE → factory that produces the matched {@link FortressError}.
33
+ *
34
+ * Each entry maps a Postgres error class to the canonical Fortress HTTP
35
+ * status so downstream `protect()` middleware emits a stable wire response
36
+ * without the host writing a try/catch.
37
+ *
38
+ * | SQLSTATE | Meaning | Fortress error | HTTP |
39
+ * | -------- | -------------------- | ----------------------- | ---- |
40
+ * | `23505` | unique_violation | `CONFLICT` | 409 |
41
+ * | `23503` | foreign_key_violation| `UNPROCESSABLE_ENTITY` | 422 |
42
+ * | `23502` | not_null_violation | `BAD_REQUEST` | 400 |
43
+ * | `23514` | check_violation | `UNPROCESSABLE_ENTITY` | 422 |
44
+ * | `40001` | serialization_failure| `CONFLICT` | 409 |
45
+ * | `40P01` | deadlock_detected | `CONFLICT` | 409 |
46
+ * | `57014` | query_canceled | `SERVICE_UNAVAILABLE` | 503 |
47
+ */
48
+ const PG_SQLSTATE_MAP: Record<string, (cause: unknown) => FortressError> = {
49
+ '23505': cause => Errors.conflict('Resource already exists', { cause }),
50
+ '23503': cause => Errors.unprocessable('Referenced resource does not exist', { cause }),
51
+ '23502': _cause => Errors.badRequest('Required field is missing'),
52
+ '23514': cause => Errors.unprocessable('Constraint check failed', { cause }),
53
+ '40001': cause => Errors.conflict('Serialization failure, retry the request', { cause }),
54
+ '40P01': cause => Errors.conflict('Deadlock detected, retry the request', { cause }),
55
+ '57014': cause => Errors.serviceUnavailable('Query canceled', { cause }),
56
+ };
57
+
58
+ interface MaybeCoded {
59
+ code?: unknown;
60
+ message?: unknown;
61
+ cause?: unknown;
62
+ }
63
+
64
+ /**
65
+ * SQLite constraint matchers. better-sqlite3 and bun:sqlite both surface a
66
+ * `SQLITE_CONSTRAINT_*` `code`; older builds only set the message, so each
67
+ * entry matches either. Tested against `${code} ${message}` at every level
68
+ * of the cause chain.
69
+ */
70
+ const SQLITE_CONSTRAINT_MAP: { match: RegExp; factory: (cause: unknown) => FortressError }[] = [
71
+ {
72
+ match: /SQLITE_CONSTRAINT_(?:UNIQUE|PRIMARYKEY)|UNIQUE constraint failed/i,
73
+ factory: cause => Errors.conflict('Resource already exists', { cause }),
74
+ },
75
+ {
76
+ match: /SQLITE_CONSTRAINT_FOREIGNKEY|FOREIGN KEY constraint failed/i,
77
+ factory: cause => Errors.unprocessable('Referenced resource does not exist', { cause }),
78
+ },
79
+ {
80
+ match: /SQLITE_CONSTRAINT_NOTNULL|NOT NULL constraint failed/i,
81
+ factory: _cause => Errors.badRequest('Required field is missing'),
82
+ },
83
+ ];
84
+
85
+ /**
86
+ * Walk the `cause` chain looking for a SQLite constraint violation, testing
87
+ * each level's `code`/`message` against {@link SQLITE_CONSTRAINT_MAP}.
88
+ * Returns the matching {@link FortressError} or `null` when none is found
89
+ * within {@link MAX_CAUSE_DEPTH} hops.
90
+ */
91
+ function mapSqliteConstraint(err: unknown): FortressError | null {
92
+ let current: unknown = err;
93
+ for (let i = 0; i < MAX_CAUSE_DEPTH; i++) {
94
+ if (current === null || typeof current !== 'object')
95
+ return null;
96
+ const coded = current as MaybeCoded;
97
+ const code = typeof coded.code === 'string' ? coded.code : '';
98
+ const message = typeof coded.message === 'string' ? coded.message : '';
99
+ const haystack = `${code} ${message}`;
100
+ for (const { match, factory } of SQLITE_CONSTRAINT_MAP) {
101
+ if (match.test(haystack))
102
+ return factory(err);
103
+ }
104
+ current = coded.cause;
105
+ }
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Walk the `cause` chain of an unknown thrown value looking for a property
111
+ * named `code` that smells like a Postgres SQLSTATE (5 uppercase
112
+ * alphanumerics, e.g. `23505`, `40P01`). Returns the first match it finds
113
+ * or `null` when none is present within {@link MAX_CAUSE_DEPTH} hops.
114
+ */
115
+ export function findSqlstate(err: unknown): string | null {
116
+ let current: unknown = err;
117
+ for (let i = 0; i < MAX_CAUSE_DEPTH; i++) {
118
+ if (current === null || typeof current !== 'object')
119
+ return null;
120
+ const candidate = (current as MaybeCoded).code;
121
+ if (typeof candidate === 'string' && SQLSTATE_PATTERN.test(candidate))
122
+ return candidate;
123
+ current = (current as MaybeCoded).cause;
124
+ }
125
+ return null;
126
+ }
127
+
128
+ /**
129
+ * Translate a driver constraint error into a {@link FortressError} for the
130
+ * given `dialect`, then throw it. Postgres routes through
131
+ * {@link PG_SQLSTATE_MAP} by SQLSTATE; SQLite through
132
+ * {@link SQLITE_CONSTRAINT_MAP} by constraint code/message. Errors that
133
+ * don't match a known constraint are re-thrown unchanged so callers can
134
+ * still observe the raw driver error.
135
+ */
136
+ export function rethrowDbError(err: unknown, dialect: DrizzleDialect): never {
137
+ if (dialect === 'pg') {
138
+ const sqlstate = findSqlstate(err);
139
+ const factory = sqlstate ? PG_SQLSTATE_MAP[sqlstate] : undefined;
140
+ if (factory)
141
+ throw factory(err);
142
+ throw err;
143
+ }
144
+ if (dialect === 'sqlite') {
145
+ const mapped = mapSqliteConstraint(err);
146
+ if (mapped)
147
+ throw mapped;
148
+ throw err;
149
+ }
150
+ throw err;
151
+ }