@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,59 @@
1
+ # Drop-in GitHub Actions workflow that runs the Fortress CI checks.
2
+ #
3
+ # Save as `.github/workflows/fortress-ci.yml` in your repo. Adapt:
4
+ # - Node/Bun version if you pin a different one.
5
+ # - The path to your fortress instance (the example assumes
6
+ # `src/lib/fortress.ts` exports a configured `fortress`).
7
+ # - Install command (`bun install` / `pnpm i` / `npm ci`).
8
+ #
9
+ # What it runs:
10
+ # - `fortress manifest:check` → route-security manifest drift
11
+ # - `fortress check:public-routes` → catches `.security('none')` on routes
12
+ # that shouldn't be public (against the bundled Fortress allow-list)
13
+ # - `fortress migrate:check` → bundled migration catalog consistency
14
+ # - `bun run test` → your suite, which should include
15
+ # `runFortressChecks()` against your real fortress instance with the
16
+ # test adapter (see docs/ci.md for a sample test file).
17
+
18
+ name: fortress-ci
19
+
20
+ on:
21
+ push:
22
+ branches: [main]
23
+ pull_request:
24
+
25
+ jobs:
26
+ fortress-checks:
27
+ runs-on: ubuntu-latest
28
+ timeout-minutes: 10
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+
32
+ - uses: oven-sh/setup-bun@v2
33
+ with:
34
+ bun-version: latest
35
+
36
+ - name: Install dependencies
37
+ run: bun install --frozen-lockfile
38
+
39
+ - name: Lint
40
+ run: bun run lint
41
+
42
+ - name: Typecheck
43
+ run: bun run typecheck
44
+
45
+ - name: Fortress route-manifest drift
46
+ run: bunx fortress manifest:check
47
+
48
+ - name: Fortress public-route allow-list
49
+ run: bunx fortress check:public-routes
50
+
51
+ - name: Fortress migration catalog
52
+ run: bunx fortress migrate:check
53
+
54
+ - name: Unit tests
55
+ run: bun run test
56
+
57
+ # Optional: build artifact for verifying types compile cleanly
58
+ - name: Library build (publish dry-run)
59
+ run: bunx tsup
package/docs/ci.md ADDED
@@ -0,0 +1,109 @@
1
+ # CI checks
2
+
3
+ Fortress ships a bundle of reusable CI checks under
4
+ `@bajustone/fortress/testing` plus a `fortress check:*` CLI namespace.
5
+ The same drift detectors Fortress uses internally to keep its own
6
+ routes/migrations/RBAC mappings honest are exposed to consumer apps so
7
+ you can gate deploys on them.
8
+
9
+ ## What's covered
10
+
11
+ | Check | API | CLI | Catches |
12
+ |---|---|---|---|
13
+ | Route-manifest drift | `checkRouteManifestDrift(fortress)` | `fortress manifest:check` / `fortress check:routes` | Mounted routes not in the manifest, manifest routes not mounted, RBAC permission/OpenAPI mismatches |
14
+ | Public-route allow-list | `checkPublicRoutes(fortress, { allow })` | `fortress check:public-routes` | A new route marked `.security('none')` or `bearerKind:'oauth'` that wasn't reviewed |
15
+ | Migration drift | `checkMigrationDrift(adapter)` | `fortress migrate:check` / `fortress check:migrations` | Missing version table, pending migrations, missing Fortress tables, DB ahead of bundled catalog |
16
+ | Auth smoke test | `smokeTestAuth(fortress)` | _(run from a test file)_ | User creation/login/access-token verification/refresh/logout regression |
17
+ | Aggregator | `runFortressChecks({ fortress })` | _(run from a test file)_ | All of the above with a single ok/messages roll-up |
18
+
19
+ `runFortressChecks` is the convenience entry point — wire it into a
20
+ single vitest test or a deploy preflight script. Under Node, install
21
+ `better-sqlite3` as a development dependency for `createTestAdapter()`;
22
+ Bun uses `bun:sqlite` without an extra driver.
23
+
24
+ ## In a vitest test
25
+
26
+ ```ts
27
+ import { describe, expect, it } from 'vitest';
28
+ import { createFortress } from '@bajustone/fortress';
29
+ import { createTestAdapter, runFortressChecks } from '@bajustone/fortress/testing';
30
+ import { config } from '../src/lib/fortress-config';
31
+
32
+ describe('fortress CI checks', () => {
33
+ it('passes every Fortress drift checker', async () => {
34
+ // Build the fortress instance with your real plugin set against an
35
+ // in-memory adapter so smokeTestAuth has somewhere to write.
36
+ const fortress = createFortress({ ...config, database: createTestAdapter() });
37
+ const result = await runFortressChecks({ fortress });
38
+ if (!result.ok)
39
+ console.error(result.messages.join('\n'));
40
+ expect(result.ok).toBe(true);
41
+ });
42
+ });
43
+ ```
44
+
45
+ This single test catches:
46
+
47
+ - A new plugin route that's accidentally `.security('none')`.
48
+ - A migration that was added without bumping `fortress_schema_version`.
49
+ - A manifest entry that no longer corresponds to a mounted route.
50
+ - A regression in the auth pipeline (create user/login/token verification/refresh/logout).
51
+
52
+ ## As a CLI
53
+
54
+ ```sh
55
+ fortress manifest:check # route-security drift (also: check:routes)
56
+ fortress check:public-routes # public-route allow-list (core surface)
57
+ fortress migrate:check # migration catalog (also: check:migrations)
58
+ ```
59
+
60
+ The CLI runs against Fortress's **core auth + IAM** route surface, so it
61
+ catches drift in Fortress itself but doesn't see your plugins. For
62
+ app-level checks (plugin routes + your real config), use the API from a
63
+ vitest test as shown above.
64
+
65
+ ## GitHub Actions snippet
66
+
67
+ A drop-in workflow is committed at
68
+ [`docs/ci/github-actions.yml`](./ci/github-actions.yml). Save it as
69
+ `.github/workflows/fortress-ci.yml` in your repo. It runs:
70
+
71
+ 1. `bun run lint`
72
+ 2. `bun run typecheck`
73
+ 3. `bun run typecheck:examples`
74
+ 4. `bunx fortress manifest:check`
75
+ 5. `bunx fortress check:public-routes`
76
+ 6. `bunx fortress migrate:check`
77
+ 7. `bun run test` (which should include `runFortressChecks(...)` from
78
+ the snippet above)
79
+ 8. `bun run test:integration` for PostgreSQL-backed projects
80
+ 9. `bun run build` (verifies distributable output)
81
+
82
+ Adjust `oven-sh/setup-bun` to your preferred Node/PNPM/NPM setup; every
83
+ step is plain shell.
84
+
85
+ ## Customising the public-route allow-list
86
+
87
+ The default allow-list covers Fortress's intentional public surface
88
+ (auth open routes + OAuth protocol endpoints). Add your own public
89
+ routes via the `allow` option:
90
+
91
+ ```ts
92
+ checkPublicRoutes(fortress, {
93
+ allow: [
94
+ 'GET /health',
95
+ 'GET /robots.txt',
96
+ 'POST /webhooks/incoming',
97
+ ],
98
+ });
99
+ ```
100
+
101
+ CLI usage repeats `--allow`:
102
+
103
+ ```sh
104
+ fortress check:public-routes --allow 'GET /health' --allow 'GET /robots.txt'
105
+ ```
106
+
107
+ If you want a stricter posture (e.g. fail on `oauth-protocol` routes
108
+ that appeared without review), restrict `classifications` to just
109
+ `['public']`.
@@ -0,0 +1,115 @@
1
+ # Compatibility
2
+
3
+ The Fortress test matrix that the maintainer runs locally and via CI.
4
+ Numbers are "tested, working" — Fortress is framework-agnostic by
5
+ construction so versions outside the matrix will usually work, just
6
+ without an explicit test guarantee.
7
+
8
+ ## Runtime
9
+
10
+ | Runtime | Minimum | Tested |
11
+ |---|---|---|
12
+ | **Bun** | 1.0 | latest |
13
+ | **Node.js** | 20.19 | 20, 22, 24 |
14
+ | **Deno** | 1.40 | latest |
15
+ | **Cloudflare Workers (workerd)** | wrangler 3 | latest |
16
+ | Browsers | n/a | server-only |
17
+
18
+ Bun is the maintainer's primary runtime (test adapter prefers
19
+ `bun:sqlite`; the `bin/fortress.ts` CLI shebang targets Bun). The
20
+ package ships a Node-compatible CJS build via `tsup` so the same module
21
+ works under Node 20.19+ unchanged. Deno + workerd can import and use the runtime-neutral core because Node filesystem/path modules are dynamically loaded only when file-oriented policy/resource helpers are invoked. The Bun-targeted CLI and file helpers require a Node-compatible filesystem runtime.
22
+
23
+ ## Frameworks
24
+
25
+ | Adapter | Package | Tested versions |
26
+ |---|---|---|
27
+ | Hono | `@bajustone/fortress/hono` | `hono@^4.12` |
28
+ | Express | `@bajustone/fortress/express` | `express@^4` and `^5` (duck-typed; no `@types/express` dep) |
29
+ | SvelteKit | `@bajustone/fortress/sveltekit` | `@sveltejs/kit@^2` |
30
+
31
+ The Express adapter uses minimal duck-typed interfaces (`ExpressRequest`,
32
+ `ExpressResponse`, `ExpressMiddleware`) so consumers bring their own
33
+ Express version. The SvelteKit subpath uses the optional `@sveltejs/kit@^2`
34
+ peer directly for its strict public `Handle`/`Action` types and runtime
35
+ `redirect()`/`fail()` primitives; install that peer when using the adapter.
36
+
37
+ ## Databases
38
+
39
+ | Driver | Adapter | Tested |
40
+ |---|---|---|
41
+ | PostgreSQL via `drizzle-orm/postgres-js` | `@bajustone/fortress/drizzle` (`dialect: 'pg'`) | `postgres@^3.4` |
42
+ | PostgreSQL via `drizzle-orm/node-postgres` | same | `pg@^8` |
43
+ | SQLite via `drizzle-orm/bun-sqlite` | same (`dialect: 'sqlite'`) | bundled with Bun |
44
+ | SQLite via `drizzle-orm/better-sqlite3` | same | `better-sqlite3@^12.8` |
45
+
46
+ Drizzle minimum: `drizzle-orm@^0.45`. Earlier versions are missing
47
+ features Fortress relies on (`$dynamic()`, `getTableColumns`,
48
+ `getSetCookie` polyfill).
49
+
50
+ The `@bajustone/fortress/testing` subpath uses `bun:sqlite` under Bun and the optional `better-sqlite3@^12.8` peer under Node. Install that peer in Node test projects.
51
+
52
+ Custom adapters: implement the `DatabaseAdapter` interface in
53
+ `src/adapters/database/index.ts` to back Fortress with any datastore.
54
+ The IAM service uses a small set of generic operations
55
+ (`create`/`findOne`/`findMany`/`update`/`delete`/`count`/`transaction`,
56
+ optional `rawQuery`) so even non-SQL stores are workable.
57
+
58
+ ## Optional integrations
59
+
60
+ | Capability | Peer | Tested |
61
+ |---|---|---|
62
+ | OpenTelemetry metrics + traces | `@opentelemetry/api@^1.9` | latest with `@opentelemetry/sdk-node` |
63
+ | Argon2id password hashing (native) | `argon2@^0.31` | optional; Fortress ships a `hash-wasm` fallback |
64
+ | WebAuthn server | `@simplewebauthn/server@^13.1` | bundled dependency |
65
+ | OAuth signing (RS256 id_token) | `jose@^6.2` | bundled dependency |
66
+
67
+ ## What the CI matrix runs today
68
+
69
+ The repository workflow ([`.github/workflows/ci.yml`](../.github/workflows/ci.yml))
70
+ executes:
71
+
72
+ - **`lint`** — eslint + `tsc --noEmit` (Bun, ubuntu-latest).
73
+ - **`unit`** — the full SQLite test suite across a runtime matrix:
74
+ **Bun**, **Node 20**, and **Node 22**. Bun runs `bun:sqlite`; the Node
75
+ jobs run the same vitest suite under `better-sqlite3`, keeping the
76
+ Node-compatible build honest.
77
+ - **`integration`** — the PostgreSQL suite via testcontainers
78
+ (`bun run test:integration`), covering the `pg` dialect, the tenancy
79
+ connection-pinning / `search_path` isolation, the migration upgrade
80
+ fixture, and the framework adapters end-to-end. This is the heavier job,
81
+ so it runs on pull requests, pushes to `main`, and a **nightly cron**
82
+ rather than every branch push.
83
+ - **`jsr-check`** — `deno publish --dry-run` to guard JSR publishability.
84
+
85
+ The consumer-facing drop-in workflow
86
+ ([`docs/ci/github-actions.yml`](./ci/github-actions.yml)) is a separate
87
+ template that gates *your* deploys on Fortress's drift checkers
88
+ (`runFortressChecks`); it is not the library's own matrix.
89
+
90
+ > Deno and Cloudflare Workers remain smoke-tested locally rather than in
91
+ > the public matrix (no first-class GitHub runner for the workerd DB
92
+ > story). MySQL is not currently supported — `DrizzleDialect` is
93
+ > `'sqlite' | 'pg'`; it may be re-added once there is a real consumer and
94
+ > a CI lane to keep it honest.
95
+
96
+ ## What's intentionally out of scope
97
+
98
+ - **Bun's web runtime APIs** beyond `Request`/`Response`/`crypto`/`fetch`.
99
+ Fortress sticks to the web platform standard so any modern runtime
100
+ works.
101
+ - **ESM-only consumers without an interop layer.** The dual `esm`+`cjs`
102
+ build is shipped; if a consumer's bundler refuses CJS, use the
103
+ `import` map entry.
104
+ - **Cloudflare Workers without external DB.** Workers cannot use
105
+ `bun:sqlite` or `better-sqlite3`. Use the Postgres adapter (or a
106
+ remote DB over HTTP) when targeting workerd.
107
+
108
+ ## Reporting a compatibility issue
109
+
110
+ If a combination listed here breaks, open an issue with:
111
+
112
+ 1. Runtime + version (`bun --version`, `node --version`, `deno --version`).
113
+ 2. Driver + version (`drizzle-orm`, `postgres`, `better-sqlite3`).
114
+ 3. The minimal reproduction \u2014 ideally a failing test using
115
+ `createTestAdapter()`.
@@ -0,0 +1,305 @@
1
+ # Fortress production deployment guide
2
+
3
+ This guide covers what changes between local dev and production for a
4
+ Fortress-backed service. Treat each section as a checklist — every
5
+ production deployment should explicitly answer or skip each item.
6
+
7
+ ## 1. JWT secret
8
+
9
+ The JWT signing secret (`FortressConfig.jwt.key`) must be **at least
10
+ 32 bytes** of cryptographically random material. Anything shorter throws
11
+ at `createFortress` startup.
12
+
13
+ Generate one with the bundled CLI:
14
+
15
+ ```sh
16
+ fortress generate-secret
17
+ # 64-byte hex string (128 chars); paste into your secret manager
18
+ ```
19
+
20
+ Recommendations:
21
+
22
+ - Store the secret in your platform's secret manager (AWS Secrets
23
+ Manager, GCP Secret Manager, Vault, Doppler, Fly secrets, etc.).
24
+ Never commit it.
25
+ - Rotate the secret periodically. Fortress supports an array of secrets
26
+ on `jwt.key` — the first entry is used for **signing**, all entries
27
+ are accepted for **verification**. Add the new secret to position 0,
28
+ redeploy, wait until every issued token has expired (typically the
29
+ access-token lifetime + grace), then remove the old one.
30
+ - For multi-region deployments, replicate the same secret across regions
31
+ so tokens issued in one region verify in another.
32
+
33
+ ## 2. Cookies behind reverse proxies
34
+
35
+ Fortress defaults to `__Host-fortress_access` / `__Host-fortress_refresh`
36
+ cookies with `HttpOnly; Secure; SameSite=Lax; Path=/`. The `__Host-`
37
+ prefix is the strongest browser-enforced isolation: the cookie is bound
38
+ to the exact origin, has no `Domain` attribute, and **must** be sent
39
+ over HTTPS.
40
+
41
+ When deploying behind a reverse proxy (nginx, Caddy, ELB, Fly, Cloud
42
+ Run, fly-replay):
43
+
44
+ - **Terminate TLS at the proxy.** Set `X-Forwarded-Proto: https` so the
45
+ app sees the original scheme. Fortress only relies on this for IP /
46
+ user-agent metadata, but most frameworks (Hono, Express, SvelteKit)
47
+ use it to construct absolute URLs and cookie attributes.
48
+ - **Forward the original host.** Set `Host` to the client-facing
49
+ hostname — `__Host-` cookies are pinned to it.
50
+ - **Forward `X-Forwarded-For`.** Fortress reads `X-Forwarded-For` then
51
+ `X-Real-IP` for rate-limit keys and refresh-token metadata. Strip any
52
+ client-supplied values at the edge; only trust hops you control.
53
+ - **Do not strip cookies.** Some CDN configurations drop `Set-Cookie`
54
+ by default on cacheable responses. Whitelist
55
+ `set-cookie: __Host-fortress_*` (or your renamed prefix) on every
56
+ Fortress-served path.
57
+
58
+ To opt into a custom cookie name / scope, set
59
+ `FortressConfig.cookies` — for example a top-level apex domain:
60
+
61
+ ```ts
62
+ createFortress({
63
+ // ...
64
+ cookies: {
65
+ accessName: 'app_access',
66
+ refreshName: 'app_refresh',
67
+ domain: 'example.com',
68
+ secure: true,
69
+ sameSite: 'lax',
70
+ path: '/',
71
+ },
72
+ });
73
+ ```
74
+
75
+ Setting a `domain` removes the `__Host-` prefix automatically because
76
+ the prefix forbids cross-subdomain cookies.
77
+
78
+ ## 3. CSRF: opt-out rules and recipes
79
+
80
+ Pipeline CSRF is on by default for unsafe methods (POST/PUT/PATCH/DELETE)
81
+ on Fortress-managed routes when the request carries a Fortress auth
82
+ cookie. Bearer-only and API-key requests are immune by construction and
83
+ are skipped.
84
+
85
+ Common production tweaks:
86
+
87
+ - **Pure API backends (no browser cookies):** set `csrf: { enabled: false }`.
88
+ Every request authenticates via bearer/API-key; there is no ambient
89
+ credential to protect.
90
+ - **SPA on the same origin:** keep the default. The SPA sends a custom
91
+ header (`X-Fortress-CSRF: 1` by default — value is not validated, the
92
+ *presence* is what matters because cross-site code cannot set custom
93
+ headers without a CORS preflight you've allowed).
94
+ - **SPA on a sibling subdomain** (`app.example.com` calling
95
+ `api.example.com`): keep the default `rejectSameSite: false`; the
96
+ legitimate `Sec-Fetch-Site: same-site` traffic must pass.
97
+ - **Single-host browser app, no cross-subdomain calls:** set
98
+ `csrf: { rejectSameSite: true }` to also reject same-site requests,
99
+ closing the residual risk of a malicious subdomain.
100
+
101
+ Skip specific paths if a third party must call them without a custom
102
+ header (webhooks, OAuth `/oauth/token`, etc.):
103
+
104
+ ```ts
105
+ csrf: {
106
+ enabled: true,
107
+ skipPaths: ['/oauth/token', '/oauth/revoke', '/webhooks/incoming'],
108
+ }
109
+ ```
110
+
111
+ `/oauth/*` protocol endpoints are already classified as
112
+ `oauth-protocol` in the route manifest and self-authenticate via
113
+ bearer tokens; they don't need a CSRF skip unless you've put them
114
+ behind cookie auth somehow.
115
+
116
+ ## 4. CORS
117
+
118
+ Fortress does not ship a CORS middleware — each adapter delegates to its
119
+ host framework. Mount your framework's CORS middleware **before**
120
+ `mountFortress`, and:
121
+
122
+ - Set `Access-Control-Allow-Credentials: true` if your SPA sends
123
+ cookies.
124
+ - Set `Access-Control-Allow-Origin` to the explicit SPA origin (never
125
+ `*`) when credentials are allowed.
126
+ - Include `X-Fortress-CSRF` (and any custom header you renamed it to)
127
+ in `Access-Control-Allow-Headers`.
128
+ - Include `Authorization` and `X-API-Key` if you authenticate via
129
+ bearer/API-key from the browser.
130
+
131
+ Example (Hono):
132
+
133
+ ```ts
134
+ import { cors } from 'hono/cors';
135
+
136
+ app.use('*', cors({
137
+ origin: 'https://app.example.com',
138
+ credentials: true,
139
+ allowHeaders: ['Content-Type', 'Authorization', 'X-API-Key', 'X-Fortress-CSRF'],
140
+ exposeHeaders: ['Retry-After'],
141
+ }));
142
+ mountFortress(app, fortress);
143
+ ```
144
+
145
+ ## 5. HTTPS requirements
146
+
147
+ - The OAuth plugin **refuses to start** in production
148
+ (`NODE_ENV=production`) unless `issuerUrl` is HTTPS. Loopback URLs are
149
+ exempt for dev.
150
+ - `__Host-` cookies require HTTPS at the browser. If you do not run
151
+ HTTPS in production, set `cookies: { secure: false }` — but you are
152
+ giving up the strongest cookie isolation and exposing tokens to any
153
+ on-path attacker.
154
+ - Every external dependency Fortress speaks to (social-login providers,
155
+ webhook destinations, OIDC discovery URLs) should use HTTPS. The
156
+ social-login plugin assumes provider URLs are HTTPS; provider configs
157
+ with `http://` URLs are accepted but should be flagged in code review.
158
+
159
+ ## 6. PostgreSQL vs SQLite guarantees
160
+
161
+ | Concern | PostgreSQL | SQLite (`bun:sqlite`, `better-sqlite3`) |
162
+ |---|---|---|
163
+ | Concurrent writers | Yes, MVCC | One writer at a time (serialized in the adapter via `BEGIN IMMEDIATE`) |
164
+ | Tenancy `search_path` switching | Supported (transaction-pinned `set_config`) | Not supported — tenancy plugin is pass-through |
165
+ | Recommended for production | ✅ | Single-node deployments only (Fly Volumes, edge SQLite) |
166
+ | Migrations against a live DB | Standard | Use a small migration window; SQLite serializes writers |
167
+ | Drift checker `missingTables` | Reads `information_schema` | Reads `sqlite_master` |
168
+ | OAuth `oauth_signing_key` | Persisted; rotation safe | Persisted; rotation safe |
169
+
170
+ SQLite is perfectly fine for small to medium services that don't need
171
+ horizontal write scaling. Use Postgres when you need multi-region, true
172
+ tenancy schema isolation, or when an external tool needs to read the
173
+ schema (BI, replication).
174
+
175
+ ## 7. OAuth / OIDC RP setup
176
+
177
+ The OAuth plugin acts as an **authorization server**. To enable it:
178
+
179
+ ```ts
180
+ oauth({
181
+ issuerUrl: 'https://auth.example.com', // HTTPS mandatory in prod
182
+ loginUrl: 'https://app.example.com/signin', // your SPA login page
183
+ consentUrl: 'https://app.example.com/oauth/consent', // your SPA consent page
184
+ // Optional: per-deployment OIDC userinfo claim shaping
185
+ userinfoClaims: async (user, scope) => ({ /* ... */ }),
186
+ })
187
+ ```
188
+
189
+ Key production knobs:
190
+
191
+ - **`issuerUrl`** is signed into every id_token (`iss` claim) and is the
192
+ base of every discovery URL. Setting it wrong locks every client out
193
+ — pick once, document, treat as part of your stable public API.
194
+ - **Discovery** is served from
195
+ `${issuerUrl}/oauth/.well-known/openid-configuration`. Cache for
196
+ minutes, not hours; the JWKS rotation cadence depends on your
197
+ `oauth_signing_key` rotation policy.
198
+ - **JWKS** is served from `${issuerUrl}/oauth/.well-known/jwks.json`.
199
+ RPs typically cache it for 5–15 minutes; signing-key rotations are
200
+ picked up next cache refresh.
201
+ - **Refresh tokens** are rotated by default; reuse triggers a
202
+ family-wide revocation per RFC 9700 §2.2.2. Configure
203
+ `refreshTokenExpirySeconds` to match your session policy (0 disables
204
+ refresh entirely).
205
+ - **Public clients** (SPAs, native apps) must register with
206
+ `tokenEndpointAuthMethod: 'none'` and use PKCE. Confidential clients
207
+ default to `client_secret_basic`; opt into `'client_secret_post'` per
208
+ client if needed.
209
+ - **Per-client `allowedScopes`** narrows what each RP can request.
210
+ Leave blank for legacy clients with broad scopes; set explicitly for
211
+ any new integration.
212
+
213
+ ## 8. API-key and service-account operations
214
+
215
+ - API-key plugin endpoints are opt-in (`apiKey({ routes: true })`). In
216
+ production, decide:
217
+ - User self-service rotation: mount `routes: true` and put rate-limit
218
+ on `/api-key/keys`.
219
+ - Admin-only rotation: leave `routes: false` and rotate via the
220
+ admin plugin's `/admin/users/:userId/api-keys` endpoints (requires
221
+ `apiKey:manage` permission).
222
+ - Service accounts have no sessions and no group memberships. Provision
223
+ them via `/iam/service-accounts` (admin plugin) or
224
+ `fortress.iam.createServiceAccount(...)`. Mint API keys against them
225
+ via `/admin/service-accounts/:id/api-keys`.
226
+ - Hash storage: API keys are stored as SHA-256 of the prefix + secret
227
+ with the plaintext returned only on issuance. If a key leaks, revoke
228
+ it via `DELETE /api-key/keys/:id` (or admin route); rotation via
229
+ `POST /api-key/keys/:id/rotate` keeps the same `name` and revokes the
230
+ predecessor in a single transaction.
231
+ - Rate-limit API-key issuance separately from user logins:
232
+ `rateLimit({ apiKeyIssue: { maxPerIp: 5, maxPerUser: 5, windowSeconds: 3600 } })`.
233
+
234
+ ## 9. Migration runbook
235
+
236
+ 1. **Pre-deploy:** run `fortress migrate:check` in CI; non-zero exit
237
+ blocks the deploy. This catches "developer added a new bundled
238
+ migration but forgot to commit the SQL" before it reaches prod.
239
+ 2. **Deploy:** on application start, call
240
+ `migrateUp(adapter)` from your boot script. Both `migrateUp` and
241
+ `migrateDown` are idempotent.
242
+ 3. **Post-deploy:** in your healthcheck, call
243
+ `detectMigrationDrift(adapter)` and fail the healthcheck on
244
+ `missingTables` / `missingVersionTable`. The load balancer pulls the
245
+ instance out of rotation until drift is fixed.
246
+
247
+ For irreversible migrations (column drops, type narrowing), keep the
248
+ old code shape supported for at least one release before removing it,
249
+ and take a database snapshot immediately before applying. See
250
+ [docs/migrations/upgrade-guide.md](./migrations/upgrade-guide.md) for
251
+ the runtime API and CLI reference.
252
+
253
+ ## 10. Observability
254
+
255
+ - Pass a structured logger (`pino`, `bunyan`, framework-provided) to
256
+ `FortressConfig.logger` so security-critical events (token reuse,
257
+ account lockout, OAuth errors) are searchable.
258
+ - Wire OpenTelemetry via `@bajustone/fortress/otel`'s
259
+ `createOtelTelemetry()`. Fortress emits stable
260
+ `db.client.operation.duration`,
261
+ `fortress.auth.events.total`, IAM permission-check histograms, and
262
+ a deny-only span (`fortress.iam.permission_check.deny`).
263
+ - High-cardinality data (user IDs, emails, tenant IDs) is deliberately
264
+ kept off metric attributes — it lives on spans and structured logs.
265
+
266
+ ## 11. Backups & disaster recovery
267
+
268
+ - Back up the database before every deploy that ships a migration.
269
+ Test restores quarterly.
270
+ - The `oauth_signing_key` row is **the** RSA signing material for every
271
+ OIDC id_token. Losing it invalidates every outstanding id_token (RPs
272
+ re-authenticate). Treat it like a JWT secret — back it up with the
273
+ database, never export it.
274
+ - Audit-log rows are append-only and hash-chained when
275
+ `auditLog({ hashChain: true })` is enabled. Restoring from backup
276
+ preserves the chain; appending out-of-order rows breaks it. If you
277
+ restore, run a chain verification job before resuming writes.
278
+ - Rate-limit counter store (the in-memory default) is **not** durable.
279
+ In production with multiple instances, bring your own store
280
+ (Redis, Memcached) so limits apply across replicas.
281
+
282
+ ## 12. Checklist
283
+
284
+ Use this before promoting a release to production:
285
+
286
+ - [ ] JWT secret is ≥ 32 bytes, stored in a secret manager, rotated on a
287
+ schedule.
288
+ - [ ] HTTPS terminated end-to-end; `X-Forwarded-Proto`, `Host`, and
289
+ `X-Forwarded-For` set by the proxy.
290
+ - [ ] Cookies: `__Host-` prefix or explicit `domain` + `secure: true`.
291
+ - [ ] CSRF policy chosen explicitly (default, `rejectSameSite`, or
292
+ disabled) and documented in code review.
293
+ - [ ] CORS allow-list pins exact SPA origin and exposes the CSRF
294
+ header.
295
+ - [ ] OAuth `issuerUrl` is HTTPS in production; discovery and JWKS
296
+ endpoints reachable from RPs.
297
+ - [ ] Rate-limit plugin mounted with `login`/`register`/`refresh`
298
+ defaults; named rules cover `/api/*`.
299
+ - [ ] Audit-log plugin mounted with `hashChain: true` for any flow
300
+ that ships compliance evidence.
301
+ - [ ] `fortress migrate:check` in CI; `migrateUp` + drift check in the
302
+ app's boot/healthcheck path.
303
+ - [ ] OpenTelemetry exporter or structured logger wired so security
304
+ events leave the box.
305
+ - [ ] Backup + restore tested for the database; signing keys included.