@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,304 @@
1
+ /**
2
+ * CI-ready check utilities for Fortress projects (P1-10).
3
+ *
4
+ * Wraps the drift detectors and manifests Fortress already builds for
5
+ * itself so consumer apps can wire them into their own CI pipeline. The
6
+ * functions are framework-agnostic — they consume a `Fortress` instance
7
+ * and / or a `DatabaseAdapter` and return structured results suitable
8
+ * for `expect(...)`-style assertions or printing alongside a non-zero
9
+ * exit code.
10
+ *
11
+ * Re-exported from `@bajustone/fortress/testing` alongside the existing
12
+ * `createTestAdapter` helper.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ import type { DatabaseAdapter } from '../adapters/database';
18
+ import type { Fortress } from '../core/fortress';
19
+ import type { RouteManifestDrift } from '../core/manifest/drift';
20
+ import type { RouteClassification, RouteManifestEntry } from '../core/manifest/route-manifest';
21
+ import type { MigrationDrift } from '../core/migrations/engine';
22
+ import type { MigrationDialect } from '../core/migrations/migrations';
23
+ import { detectRouteManifestDrift, hasRouteManifestDrift } from '../core/manifest/drift';
24
+ import { buildRouteManifest } from '../core/manifest/route-manifest';
25
+ import { detectMigrationDrift, hasMigrationDrift } from '../core/migrations/engine';
26
+
27
+ /**
28
+ * Combined result returned by every `check*` helper.
29
+ *
30
+ * `ok: true` ⇒ the check passed cleanly; `messages` is informational
31
+ * only. `ok: false` ⇒ at least one finding requires attention; the
32
+ * messages describe what.
33
+ */
34
+ export interface CheckResult {
35
+ ok: boolean;
36
+ messages: string[];
37
+ }
38
+
39
+ // ── Route manifest drift ────────────────────────────────────────────
40
+
41
+ /**
42
+ * Run the route-manifest drift detector and return a {@link CheckResult}.
43
+ * Suitable for CI: a non-empty {@link RouteManifestDrift} flips
44
+ * `ok: false` and emits one message per drift category.
45
+ */
46
+ export function checkRouteManifestDrift(
47
+ fortress: Fortress,
48
+ ): CheckResult & { drift: RouteManifestDrift } {
49
+ const drift = detectRouteManifestDrift(fortress);
50
+ const messages: string[] = [];
51
+ if (drift.mountedMissingFromManifest.length > 0)
52
+ messages.push(`Mounted routes missing from manifest: ${drift.mountedMissingFromManifest.join(', ')}`);
53
+ if (drift.manifestMissingFromMounted.length > 0)
54
+ messages.push(`Manifest routes missing from mounted set: ${drift.manifestMissingFromMounted.join(', ')}`);
55
+ if (drift.rbacPermissionMismatches.length > 0) {
56
+ messages.push(
57
+ `RBAC permission mismatches:\n ${drift.rbacPermissionMismatches
58
+ .map(m => `${m.route}: expected=${m.expected ?? '(none)'} actual=${m.actual ?? '(none)'}`)
59
+ .join('\n ')}`,
60
+ );
61
+ }
62
+ if (drift.openapiMissingFromManifest.length > 0)
63
+ messages.push(`Routes in OpenAPI but missing from manifest: ${drift.openapiMissingFromManifest.join(', ')}`);
64
+ if (drift.manifestMissingFromOpenapi.length > 0)
65
+ messages.push(`Routes in manifest but missing from OpenAPI: ${drift.manifestMissingFromOpenapi.join(', ')}`);
66
+ return { ok: !hasRouteManifestDrift(drift), drift, messages };
67
+ }
68
+
69
+ // ── Public-route allow-list ─────────────────────────────────────────
70
+
71
+ /**
72
+ * Options for {@link checkPublicRoutes}.
73
+ */
74
+ export interface PublicRouteCheckOptions {
75
+ /**
76
+ * Explicit allow-list of `'<METHOD> <path>'` entries that are *allowed*
77
+ * to be `public` or `oauth-protocol`. Anything classified as `public`
78
+ * but **not** on this list is reported as a finding so a stray
79
+ * `.security('none')` on a sensitive route fails the build.
80
+ *
81
+ * Defaults to Fortress's own intentional public surface (auth open
82
+ * routes + OAuth protocol endpoints) so consumers only need to add
83
+ * their own public routes.
84
+ */
85
+ allow?: string[];
86
+ /**
87
+ * Additional classifications considered "public-equivalent" for the
88
+ * purposes of the allow-list. Defaults to `['public', 'oauth-protocol']`
89
+ * because OAuth protocol endpoints are reachable without a Fortress
90
+ * session by design (the handler self-authenticates).
91
+ */
92
+ classifications?: RouteClassification[];
93
+ }
94
+
95
+ const DEFAULT_PUBLIC_ALLOW: readonly string[] = [
96
+ 'POST /auth/login',
97
+ 'POST /auth/register',
98
+ 'POST /auth/refresh',
99
+ 'POST /auth/logout',
100
+ 'POST /auth/2fa/verify',
101
+ 'POST /auth/magic-link/verify',
102
+ 'GET /oauth/authorize',
103
+ 'POST /oauth/token',
104
+ 'POST /oauth/introspect',
105
+ 'POST /oauth/revoke',
106
+ 'GET /oauth/userinfo',
107
+ 'GET /oauth/.well-known/openid-configuration',
108
+ 'GET /oauth/.well-known/jwks.json',
109
+ ];
110
+
111
+ /**
112
+ * Assert that no Fortress-managed route is unintentionally public.
113
+ *
114
+ * Walks the route manifest, collects every entry classified as `public`
115
+ * or `oauth-protocol` (configurable), and reports any entry not present
116
+ * in the allow-list. Defaults cover Fortress's own intentional public
117
+ * surface so consumers only need to add their own.
118
+ */
119
+ export function checkPublicRoutes(
120
+ fortress: Fortress,
121
+ options: PublicRouteCheckOptions = {},
122
+ ): CheckResult & { unexpected: RouteManifestEntry[] } {
123
+ const allow = new Set([...(options.allow ?? []), ...DEFAULT_PUBLIC_ALLOW]);
124
+ const classifications = new Set<RouteClassification>(
125
+ options.classifications ?? ['public', 'oauth-protocol'],
126
+ );
127
+
128
+ const manifest = buildRouteManifest(fortress);
129
+ const unexpected = manifest.filter(
130
+ entry => classifications.has(entry.classification)
131
+ && !allow.has(`${entry.method} ${entry.path}`),
132
+ );
133
+
134
+ const messages = unexpected.map(
135
+ entry => `Unexpected ${entry.classification} route: ${entry.method} ${entry.path} (plugin=${entry.plugin ?? 'core'})`,
136
+ );
137
+
138
+ return { ok: unexpected.length === 0, unexpected, messages };
139
+ }
140
+
141
+ // ── Migration drift ─────────────────────────────────────────────────
142
+
143
+ /**
144
+ * Run the migration drift detector and return a {@link CheckResult}.
145
+ * Reports missing version table, pending migrations, an unknown
146
+ * future version (DB ahead of the bundled catalog), missing
147
+ * Fortress-owned tables, and present-but-stale tables missing columns.
148
+ */
149
+ export async function checkMigrationDrift(
150
+ db: DatabaseAdapter,
151
+ dialect?: MigrationDialect,
152
+ ): Promise<CheckResult & { drift: MigrationDrift }> {
153
+ const drift = await detectMigrationDrift(db, dialect);
154
+ const messages: string[] = [];
155
+ if (drift.missingVersionTable)
156
+ messages.push('Schema version table is missing — run fortress.migrate()');
157
+ if (drift.pendingVersions.length > 0)
158
+ messages.push(`Pending migrations: ${drift.pendingVersions.join(', ')}`);
159
+ if (drift.unknownFutureVersion)
160
+ messages.push(`Database is at version ${drift.currentVersion} but bundled catalog stops at ${drift.latestVersion}`);
161
+ if (drift.missingTables.length > 0)
162
+ messages.push(`Missing Fortress tables: ${drift.missingTables.join(', ')}`);
163
+ if (drift.missingColumns.length > 0) {
164
+ messages.push(
165
+ `Stale Fortress tables missing columns: ${drift.missingColumns
166
+ .map(entry => `${entry.table} (${entry.columns.join(', ')})`)
167
+ .join('; ')}`,
168
+ );
169
+ }
170
+ return { ok: !hasMigrationDrift(drift), drift, messages };
171
+ }
172
+
173
+ // ── Auth smoke-test helper ──────────────────────────────────────────
174
+
175
+ /**
176
+ * Inputs to {@link smokeTestAuth}.
177
+ */
178
+ export interface AuthSmokeTestOptions {
179
+ /** Email used for the register/login round-trip. */
180
+ email?: string;
181
+ /** Password to register the smoke-test user with. */
182
+ password?: string;
183
+ /** Optional display name. */
184
+ name?: string;
185
+ }
186
+
187
+ /**
188
+ * End-to-end auth smoke test: create user → login → verify access token → refresh → logout.
189
+ * Designed to run against a fortress instance backed by
190
+ * {@link createTestAdapter} or any disposable DB. Returns a
191
+ * {@link CheckResult} so it composes with the other checks for a single
192
+ * CI gate.
193
+ *
194
+ * Calls the direct `fortress.auth.*` service methods so auth persistence,
195
+ * hashing, token verification, refresh rotation, and revocation run without
196
+ * a network roundtrip.
197
+ */
198
+ export async function smokeTestAuth(
199
+ fortress: Fortress,
200
+ options: AuthSmokeTestOptions = {},
201
+ ): Promise<CheckResult> {
202
+ const email = options.email ?? `smoke+${Date.now()}@fortress.test`;
203
+ const password = options.password ?? 'smoke-test-password-1234564!';
204
+ const name = options.name ?? 'Smoke Test';
205
+ const messages: string[] = [];
206
+ try {
207
+ await fortress.auth.createUser({ email, password, name });
208
+ const login = await fortress.auth.login(email, password);
209
+ if (login.status !== 'success' || !login.accessToken)
210
+ throw new Error(`login returned status=${login.status}`);
211
+ const claims = await fortress.auth.verifyToken(login.accessToken);
212
+ if (claims.sub !== login.user.id)
213
+ throw new Error(`token sub mismatch: ${claims.sub} vs user.id ${login.user.id}`);
214
+ if (!login.refreshToken)
215
+ throw new Error('login did not return a refresh token');
216
+ const refreshed = await fortress.auth.refresh(login.refreshToken);
217
+ if (!refreshed.accessToken)
218
+ throw new Error('refresh did not return a new access token');
219
+ await fortress.auth.logout(refreshed.refreshToken);
220
+ return { ok: true, messages };
221
+ }
222
+ catch (err) {
223
+ messages.push(`Auth smoke test failed: ${err instanceof Error ? err.message : String(err)}`);
224
+ return { ok: false, messages };
225
+ }
226
+ }
227
+
228
+ // ── Convenience aggregator ──────────────────────────────────────────
229
+
230
+ /**
231
+ * Inputs to {@link runFortressChecks}.
232
+ */
233
+ export interface RunFortressChecksOptions {
234
+ fortress: Fortress;
235
+ /** Optional DB adapter for the migration check. Defaults to `fortress.config.database`. */
236
+ db?: DatabaseAdapter;
237
+ /** Skip the migration drift check (e.g. if you bring your own migration tool). */
238
+ skipMigrations?: boolean;
239
+ /** Skip the auth smoke test (e.g. in environments without a writable DB). */
240
+ skipAuthSmokeTest?: boolean;
241
+ /** Public-route allow-list overrides. */
242
+ publicRoutes?: PublicRouteCheckOptions;
243
+ /** Smoke-test overrides. */
244
+ smokeTest?: AuthSmokeTestOptions;
245
+ }
246
+
247
+ /**
248
+ * Aggregate result returned by {@link runFortressChecks}.
249
+ */
250
+ export interface FortressChecksResult {
251
+ ok: boolean;
252
+ manifest: ReturnType<typeof checkRouteManifestDrift>;
253
+ publicRoutes: ReturnType<typeof checkPublicRoutes>;
254
+ migrations?: Awaited<ReturnType<typeof checkMigrationDrift>>;
255
+ authSmokeTest?: Awaited<ReturnType<typeof smokeTestAuth>>;
256
+ /** All non-ok messages flattened in run order. */
257
+ messages: string[];
258
+ }
259
+
260
+ /**
261
+ * Run every CI check Fortress ships in one shot. Suitable for a single
262
+ * `fortress check` step in CI:
263
+ *
264
+ * ```ts
265
+ * import { runFortressChecks } from '@bajustone/fortress/testing';
266
+ *
267
+ * const result = await runFortressChecks({ fortress });
268
+ * if (!result.ok) {
269
+ * console.error(result.messages.join('\\n'));
270
+ * process.exit(1);
271
+ * }
272
+ * ```
273
+ */
274
+ export async function runFortressChecks(
275
+ options: RunFortressChecksOptions,
276
+ ): Promise<FortressChecksResult> {
277
+ const manifest = checkRouteManifestDrift(options.fortress);
278
+ const publicRoutes = checkPublicRoutes(options.fortress, options.publicRoutes);
279
+ const migrations = options.skipMigrations
280
+ ? undefined
281
+ : await checkMigrationDrift(options.db ?? options.fortress.config.database);
282
+ const authSmokeTest = options.skipAuthSmokeTest
283
+ ? undefined
284
+ : await smokeTestAuth(options.fortress, options.smokeTest);
285
+
286
+ const messages: string[] = [];
287
+ if (!manifest.ok)
288
+ messages.push(...manifest.messages.map(m => `[manifest] ${m}`));
289
+ if (!publicRoutes.ok)
290
+ messages.push(...publicRoutes.messages.map(m => `[public-routes] ${m}`));
291
+ if (migrations && !migrations.ok)
292
+ messages.push(...migrations.messages.map(m => `[migrations] ${m}`));
293
+ if (authSmokeTest && !authSmokeTest.ok)
294
+ messages.push(...authSmokeTest.messages.map(m => `[auth-smoke] ${m}`));
295
+
296
+ return {
297
+ ok: manifest.ok && publicRoutes.ok && (migrations?.ok ?? true) && (authSmokeTest?.ok ?? true),
298
+ manifest,
299
+ publicRoutes,
300
+ ...(migrations ? { migrations } : {}),
301
+ ...(authSmokeTest ? { authSmokeTest } : {}),
302
+ messages,
303
+ };
304
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * In-memory SQLite test adapter for fortress.
3
+ *
4
+ * Spins up a fresh `bun:sqlite` database with the full fortress schema and
5
+ * returns a {@link DatabaseAdapter} ready to pass into `createFortress` from
6
+ * unit tests. Designed for fast, isolated test runs — every call creates a
7
+ * new database, so tests cannot leak state into each other.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { createTestAdapter } from '@bajustone/fortress/testing';
12
+ *
13
+ * const db = createTestAdapter();
14
+ * const fortress = createFortress({ database: db, jwt: { key: 'test' } });
15
+ * ```
16
+ *
17
+ * @module
18
+ */
19
+
20
+ import type { DatabaseAdapter } from '../adapters/database';
21
+ import { createRequire } from 'node:module';
22
+ import { resolve } from 'node:path';
23
+ import { getLatestMigrationVersion, getMigrationUpSql } from '../core/migrations/migrations';
24
+ import { createDrizzleAdapter } from '../drizzle/adapter';
25
+
26
+ export {
27
+ checkMigrationDrift,
28
+ checkPublicRoutes,
29
+ checkRouteManifestDrift,
30
+ runFortressChecks,
31
+ smokeTestAuth,
32
+ } from './checks';
33
+ export type {
34
+ AuthSmokeTestOptions,
35
+ CheckResult,
36
+ FortressChecksResult,
37
+ PublicRouteCheckOptions,
38
+ RunFortressChecksOptions,
39
+ } from './checks';
40
+
41
+ /**
42
+ * Full SQLite schema for the test adapter, derived from the bundled
43
+ * migrations so the in-memory test database and a production `migrateUp`
44
+ * can never diverge. The migrations are the SQL-first source of truth (see
45
+ * `src/core/migrations/migrations.ts`); this just concatenates their
46
+ * forward SQL.
47
+ */
48
+ const CREATE_TABLES_SQL = getMigrationUpSql('sqlite');
49
+ const STAMP_SCHEMA_VERSION_SQL = `
50
+ INSERT INTO fortress_schema_version (id, version, applied_at)
51
+ VALUES (1, ${getLatestMigrationVersion('sqlite')}, unixepoch())
52
+ ON CONFLICT(id) DO UPDATE SET
53
+ version = excluded.version,
54
+ applied_at = excluded.applied_at;
55
+ `;
56
+
57
+ const isBun = typeof (globalThis as Record<string, unknown>).Bun !== 'undefined';
58
+ const runtimeRequire = (globalThis as typeof globalThis & { require?: NodeRequire }).require
59
+ ?? createRequire(resolve(process.argv[1] ?? '__fortress_testing__.mjs'));
60
+
61
+ /**
62
+ * Create a test DatabaseAdapter using in-memory SQLite.
63
+ * Automatically detects the runtime:
64
+ * - Bun: uses bun:sqlite
65
+ * - Node/Vitest: uses better-sqlite3
66
+ *
67
+ * Usage:
68
+ * import { createTestAdapter } from '@bajustone/fortress/testing';
69
+ * const fortress = createFortress({ database: createTestAdapter(), jwt: { key: 'test' } });
70
+ */
71
+ export function createTestAdapter(): DatabaseAdapter {
72
+ if (isBun) {
73
+ return createBunAdapter();
74
+ }
75
+ return createNodeAdapter();
76
+ }
77
+
78
+ function createBunAdapter(): DatabaseAdapter {
79
+ // Dynamic import to avoid loading bun:sqlite in Node
80
+ const { Database } = runtimeRequire('bun:sqlite');
81
+ const { drizzle } = runtimeRequire('drizzle-orm/bun-sqlite');
82
+
83
+ const sqlite = new Database(':memory:');
84
+ sqlite.exec('PRAGMA journal_mode = WAL;');
85
+ sqlite.exec('PRAGMA foreign_keys = ON;');
86
+ sqlite.exec(CREATE_TABLES_SQL);
87
+ sqlite.exec(STAMP_SCHEMA_VERSION_SQL);
88
+
89
+ const db = drizzle(sqlite);
90
+ return createDrizzleAdapter(db);
91
+ }
92
+
93
+ function createNodeAdapter(): DatabaseAdapter {
94
+ // runtimeRequire works in both package formats while keeping the native
95
+ // dependency lazy and out of Bun's runtime branch.
96
+ const BetterSqlite3 = runtimeRequire('better-sqlite3');
97
+ const { drizzle } = runtimeRequire('drizzle-orm/better-sqlite3');
98
+
99
+ const sqlite = new BetterSqlite3(':memory:');
100
+ sqlite.pragma('journal_mode = WAL');
101
+ sqlite.pragma('foreign_keys = ON');
102
+ sqlite.exec(CREATE_TABLES_SQL);
103
+ sqlite.exec(STAMP_SCHEMA_VERSION_SQL);
104
+
105
+ const db = drizzle(sqlite);
106
+ return createDrizzleAdapter(db);
107
+ }