@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,308 @@
1
+ import type { DatabaseAdapter } from '../../adapters/database';
2
+ import { spawn } from 'node:child_process';
3
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { describe, expect, it } from 'vitest';
7
+ import { createDrizzleAdapter } from '../../drizzle/adapter';
8
+ import { createTestAdapter } from '../../testing';
9
+ import {
10
+ detectMigrationDrift,
11
+ getMigrationStatus,
12
+ hasMigrationDrift,
13
+ migrateDown,
14
+ migrateUp,
15
+ } from './engine';
16
+ import { FORTRESS_INDEXES, FORTRESS_TABLES, getFortressMigrations } from './migrations';
17
+
18
+ const dialect = 'sqlite';
19
+
20
+ function runMigrationChild(script: string, filename: string): Promise<void> {
21
+ return new Promise((resolve, reject) => {
22
+ const child = spawn(process.env.BUN_EXE ?? 'bun', [script, filename], {
23
+ cwd: process.cwd(),
24
+ stdio: ['ignore', 'pipe', 'pipe'],
25
+ });
26
+ let stderr = '';
27
+ child.stderr.on('data', chunk => stderr += String(chunk));
28
+ child.on('error', reject);
29
+ child.on('exit', code => code === 0 ? resolve() : reject(new Error(`migration child exited ${code}: ${stderr}`)));
30
+ });
31
+ }
32
+
33
+ function createBareSqliteAdapter(): DatabaseAdapter {
34
+ // eslint-disable-next-line ts/no-require-imports
35
+ const BetterSqlite3 = require('better-sqlite3');
36
+ // eslint-disable-next-line ts/no-require-imports
37
+ const { drizzle } = require('drizzle-orm/better-sqlite3');
38
+ return createDrizzleAdapter(drizzle(new BetterSqlite3(':memory:')));
39
+ }
40
+
41
+ function createFileSqliteAdapter(filename: string): { db: DatabaseAdapter; close: () => void } {
42
+ // eslint-disable-next-line ts/no-require-imports
43
+ const BetterSqlite3 = require('better-sqlite3');
44
+ // eslint-disable-next-line ts/no-require-imports
45
+ const { drizzle } = require('drizzle-orm/better-sqlite3');
46
+ const sqlite = new BetterSqlite3(filename);
47
+ sqlite.pragma('busy_timeout = 5000');
48
+ return {
49
+ db: createDrizzleAdapter(drizzle(sqlite)),
50
+ close: () => sqlite.close(),
51
+ };
52
+ }
53
+
54
+ describe('migration engine', () => {
55
+ it('reports pending migrations, applies up, and rolls down', async () => {
56
+ const db = createBareSqliteAdapter();
57
+
58
+ const before = await getMigrationStatus(db, dialect);
59
+ expect(before.currentVersion).toBe(0);
60
+ expect(before.latestVersion).toBe(10);
61
+ expect(before.pending.map(migration => migration.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
62
+
63
+ const up = await migrateUp(db, dialect);
64
+ expect(up).toMatchObject({ fromVersion: 0, toVersion: 10 });
65
+ expect(up.applied.map(migration => migration.name)).toEqual(['schema_version', 'initial_schema', 'auth_continuation', 'tenant_default_unique', 'hot_indexes_timestamptz', 'canonical_email', 'audit_chain_anchor', 'two_factor_hardening', 'encrypt_totp_secrets', 'bigint_append_only_ids']);
66
+
67
+ const after = await getMigrationStatus(db, dialect);
68
+ expect(after.currentVersion).toBe(10);
69
+ expect(after.upToDate).toBe(true);
70
+ expect(hasMigrationDrift(await detectMigrationDrift(db, dialect))).toBe(false);
71
+
72
+ const down = await migrateDown(db, dialect);
73
+ expect(down).toMatchObject({ fromVersion: 10, toVersion: 0 });
74
+ expect(down.rolledBack.map(migration => migration.name)).toEqual(['bigint_append_only_ids', 'encrypt_totp_secrets', 'two_factor_hardening', 'audit_chain_anchor', 'canonical_email', 'hot_indexes_timestamptz', 'tenant_default_unique', 'auth_continuation', 'initial_schema', 'schema_version']);
75
+
76
+ const final = await getMigrationStatus(db, dialect);
77
+ expect(final.hasVersionTable).toBe(false);
78
+ expect(final.currentVersion).toBe(0);
79
+ const metadata = await db.rawQuery!<{ name: string }>(
80
+ `SELECT name FROM sqlite_master WHERE type = 'table'
81
+ AND name IN ('fortress_migration_journal', 'fortress_migration_state', 'fortress_migration_lock')`,
82
+ );
83
+ expect(metadata).toEqual([]);
84
+ });
85
+
86
+ it('serializes concurrent SQLite migrators and re-reads version after the lock', async () => {
87
+ const dir = mkdtempSync(join(tmpdir(), 'fortress-migrate-'));
88
+ const filename = join(dir, 'shared.sqlite');
89
+ const first = createFileSqliteAdapter(filename);
90
+ const second = createFileSqliteAdapter(filename);
91
+ try {
92
+ const results = await Promise.all([
93
+ migrateUp(first.db, dialect),
94
+ migrateUp(second.db, dialect),
95
+ ]);
96
+ expect(results.map(result => result.applied.length).sort((a, b) => a - b)).toEqual([0, 10]);
97
+ expect(results.map(result => result.fromVersion).sort((a, b) => a - b)).toEqual([0, 10]);
98
+ expect((await getMigrationStatus(second.db, dialect)).currentVersion).toBe(10);
99
+ const journal = await second.db.rawQuery!<{ version: number }>(
100
+ 'SELECT version FROM fortress_migration_journal ORDER BY version',
101
+ );
102
+ expect(journal.map(row => Number(row.version))).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
103
+ }
104
+ finally {
105
+ first.close();
106
+ second.close();
107
+ rmSync(dir, { recursive: true, force: true });
108
+ }
109
+ });
110
+
111
+ it('serializes migrations across independent SQLite processes', async () => {
112
+ const dir = mkdtempSync(join(tmpdir(), 'fortress-migrate-process-'));
113
+ const filename = join(dir, 'shared.sqlite');
114
+ const script = join(dir, 'migrate-child.ts');
115
+ const adapterUrl = new URL('../../drizzle/adapter.ts', import.meta.url).href;
116
+ const engineUrl = new URL('./engine.ts', import.meta.url).href;
117
+ writeFileSync(script, `
118
+ import { Database } from 'bun:sqlite';
119
+ import { drizzle } from 'drizzle-orm/bun-sqlite';
120
+ import { createDrizzleAdapter } from ${JSON.stringify(adapterUrl)};
121
+ import { migrateUp } from ${JSON.stringify(engineUrl)};
122
+ const sqlite = new Database(process.argv[2]);
123
+ sqlite.exec('PRAGMA busy_timeout = 10000');
124
+ await migrateUp(createDrizzleAdapter(drizzle(sqlite)), 'sqlite');
125
+ sqlite.close();
126
+ `);
127
+
128
+ try {
129
+ await Promise.all([
130
+ runMigrationChild(script, filename),
131
+ runMigrationChild(script, filename),
132
+ ]);
133
+ const database = createFileSqliteAdapter(filename);
134
+ try {
135
+ expect((await getMigrationStatus(database.db, dialect)).currentVersion).toBe(10);
136
+ const journal = await database.db.rawQuery!<{ version: number }>(
137
+ 'SELECT version FROM fortress_migration_journal ORDER BY version',
138
+ );
139
+ expect(journal.map(row => Number(row.version))).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
140
+ }
141
+ finally {
142
+ database.close();
143
+ }
144
+ }
145
+ finally {
146
+ rmSync(dir, { recursive: true, force: true });
147
+ }
148
+ }, 30_000);
149
+
150
+ it('never advances migrateDown when the requested target is above current', async () => {
151
+ const db = createBareSqliteAdapter();
152
+ await migrateUp(db, dialect, 2);
153
+ await db.rawQuery!('UPDATE fortress_schema_version SET applied_at = 123 WHERE id = 1');
154
+
155
+ const result = await migrateDown(db, dialect, 99);
156
+ expect(result).toMatchObject({ fromVersion: 2, toVersion: 2, rolledBack: [] });
157
+ expect((await getMigrationStatus(db, dialect)).currentVersion).toBe(2);
158
+ const [checkpoint] = await db.rawQuery!<{ applied_at: number }>(
159
+ 'SELECT applied_at FROM fortress_schema_version WHERE id = 1',
160
+ );
161
+ expect(checkpoint.applied_at).toBe(123);
162
+ });
163
+
164
+ it('rejects invalid migration targets without mutation', async () => {
165
+ const db = createBareSqliteAdapter();
166
+ await migrateUp(db, dialect, 2);
167
+ for (const target of [-1, 1.5, Number.NaN])
168
+ await expect(migrateDown(db, dialect, target)).rejects.toThrow(/non-negative safe integer/);
169
+ expect((await getMigrationStatus(db, dialect)).currentVersion).toBe(2);
170
+ });
171
+
172
+ it('records deterministic checksums and removes journal rows on rollback', async () => {
173
+ const db = createBareSqliteAdapter();
174
+ await migrateUp(db, dialect);
175
+ const journal = await db.rawQuery!<{ version: number; name: string; checksum: string }>(
176
+ 'SELECT version, name, checksum FROM fortress_migration_journal ORDER BY version',
177
+ );
178
+ expect(journal).toHaveLength(10);
179
+ expect(journal.map(row => row.name)).toEqual([
180
+ 'schema_version',
181
+ 'initial_schema',
182
+ 'auth_continuation',
183
+ 'tenant_default_unique',
184
+ 'hot_indexes_timestamptz',
185
+ 'canonical_email',
186
+ 'audit_chain_anchor',
187
+ 'two_factor_hardening',
188
+ 'encrypt_totp_secrets',
189
+ 'bigint_append_only_ids',
190
+ ]);
191
+ expect(journal.every(row => /^[a-f0-9]{64}$/.test(row.checksum))).toBe(true);
192
+
193
+ await migrateDown(db, dialect, 4);
194
+ const remaining = await db.rawQuery!<{ version: number }>(
195
+ 'SELECT version FROM fortress_migration_journal ORDER BY version',
196
+ );
197
+ expect(remaining.map(row => Number(row.version))).toEqual([1, 2, 3, 4]);
198
+ });
199
+
200
+ it('fails closed on a corrupted or incomplete migration journal', async () => {
201
+ const db = createBareSqliteAdapter();
202
+ await migrateUp(db, dialect);
203
+ await db.rawQuery!(
204
+ 'UPDATE fortress_migration_journal SET checksum = ? WHERE version = 3',
205
+ ['0'.repeat(64)],
206
+ );
207
+ await expect(migrateUp(db, dialect)).rejects.toThrow(/integrity check failed/);
208
+ expect((await getMigrationStatus(db, dialect)).currentVersion).toBe(10);
209
+
210
+ await db.rawQuery!('UPDATE fortress_migration_journal SET checksum = (SELECT checksum FROM fortress_migration_journal WHERE version = 2) WHERE version = 3');
211
+ // The copied checksum is still invalid for v3; replace via a fresh legacy
212
+ // database to exercise the separate missing-row path deterministically.
213
+ const legacy = createTestAdapter();
214
+ await migrateUp(legacy, dialect); // backfills the pre-journal fixture
215
+ await legacy.rawQuery!('DELETE FROM fortress_migration_journal WHERE version = 4');
216
+ await expect(migrateUp(legacy, dialect)).rejects.toThrow(/missing version 4/);
217
+
218
+ const completeLoss = createTestAdapter();
219
+ await migrateUp(completeLoss, dialect); // legacy backfill sets initialized
220
+ await completeLoss.rawQuery!('DELETE FROM fortress_migration_journal');
221
+ await expect(migrateUp(completeLoss, dialect)).rejects.toThrow(/missing version 1/);
222
+ });
223
+
224
+ it('backfills journal rows for a legacy checkpoint without reapplying migrations', async () => {
225
+ const db = createTestAdapter();
226
+ const result = await migrateUp(db, dialect);
227
+ expect(result.applied).toEqual([]);
228
+ const journal = await db.rawQuery!<{ version: number }>(
229
+ 'SELECT version FROM fortress_migration_journal ORDER BY version',
230
+ );
231
+ expect(journal.map(row => Number(row.version))).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
232
+ });
233
+
234
+ it('fails closed when a data migration is applied as SQL without its runtime step', async () => {
235
+ const db = createBareSqliteAdapter();
236
+ await migrateUp(db, dialect, 5);
237
+ const migration = getFortressMigrations(dialect).find(item => item.version === 6)!;
238
+ const sentinelStatement = migration.up
239
+ .split(';')
240
+ .map(statement => statement.split('\n').filter(line => !line.trimStart().startsWith('--')).join('\n').trim())
241
+ .find(Boolean)!;
242
+
243
+ await expect(db.rawQuery!(sentinelStatement)).rejects.toThrow();
244
+ expect((await getMigrationStatus(db, dialect)).currentVersion).toBe(5);
245
+ });
246
+
247
+ it('reports missing Fortress tables as drift', async () => {
248
+ const db = createTestAdapter();
249
+ // Drop a Fortress-owned table to simulate a partial / stale migration.
250
+ await db.rawQuery!('DROP TABLE fortress_audit_log');
251
+
252
+ const drift = await detectMigrationDrift(db, dialect);
253
+ expect(drift.missingTables).toEqual(['fortress_audit_log']);
254
+ expect(hasMigrationDrift(drift)).toBe(true);
255
+ });
256
+
257
+ it('every expected Fortress table is created by the test adapter', async () => {
258
+ const db = createTestAdapter();
259
+ const drift = await detectMigrationDrift(db, dialect);
260
+ expect(drift.missingTables).toEqual([]);
261
+ expect(drift.missingColumns).toEqual([]);
262
+ expect(drift.missingIndexes).toEqual([]);
263
+ expect(FORTRESS_TABLES.length).toBeGreaterThan(30);
264
+ });
265
+
266
+ it('creates every required hot index with the expected column order', async () => {
267
+ const db = createTestAdapter();
268
+ for (const expected of FORTRESS_INDEXES) {
269
+ const rows = await db.rawQuery!<{ name: string }>(`PRAGMA index_info(${expected.name})`);
270
+ expect(rows.map(row => row.name), expected.name).toEqual(expected.columns);
271
+ }
272
+ });
273
+
274
+ it('reports a missing required hot index as drift', async () => {
275
+ const db = createTestAdapter();
276
+ await db.rawQuery!('DROP INDEX refresh_token_family_idx');
277
+
278
+ const drift = await detectMigrationDrift(db, dialect);
279
+ expect(drift.missingIndexes).toEqual(['refresh_token_family_idx']);
280
+ expect(hasMigrationDrift(drift)).toBe(true);
281
+ });
282
+
283
+ it('reports a present-but-stale table missing a column as drift', async () => {
284
+ const db = createTestAdapter();
285
+ // Recreate fortress_oauth_pending_flow without the H6 `user_id` column to
286
+ // simulate a database provisioned before that migration landed.
287
+ await db.rawQuery!('DROP TABLE fortress_oauth_pending_flow');
288
+ await db.rawQuery!(`CREATE TABLE fortress_oauth_pending_flow (
289
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
290
+ flow_id TEXT NOT NULL UNIQUE,
291
+ client_id TEXT NOT NULL,
292
+ redirect_uri TEXT NOT NULL,
293
+ scope TEXT,
294
+ state TEXT NOT NULL,
295
+ code_challenge TEXT,
296
+ code_challenge_method TEXT,
297
+ nonce TEXT,
298
+ expires_at INTEGER NOT NULL,
299
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
300
+ )`);
301
+
302
+ const drift = await detectMigrationDrift(db, dialect);
303
+ expect(drift.missingTables).toEqual([]);
304
+ const flowDrift = drift.missingColumns.find(entry => entry.table === 'fortress_oauth_pending_flow');
305
+ expect(flowDrift?.columns).toEqual(expect.arrayContaining(['user_id', 'used_at']));
306
+ expect(hasMigrationDrift(drift)).toBe(true);
307
+ });
308
+ });