@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,261 @@
1
+ # Security Guide
2
+
3
+ ## JWT Secret Requirements
4
+
5
+ Fortress uses `jose` with HS256 by default. The secret must be at least 32 bytes.
6
+
7
+ Generate a secret:
8
+
9
+ ```bash
10
+ node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
11
+ ```
12
+
13
+ ### Secret Rotation
14
+
15
+ Pass an array to rotate secrets with zero downtime. Fortress signs with the first secret and verifies against all:
16
+
17
+ ```typescript
18
+ const fortress = createFortress({
19
+ jwt: {
20
+ key: ['new-secret-abc', 'old-secret-xyz'],
21
+ },
22
+ });
23
+ ```
24
+
25
+ 1. Deploy with `['new', 'old']`
26
+ 2. Wait for all existing access tokens to expire (default 15 min)
27
+ 3. Remove the old secret: `secret: 'new'`
28
+
29
+ ## Two-Factor Authentication
30
+
31
+ TOTP seeds are stored only as user-bound AES-256-GCM envelopes using the required application-held `twoFactor({ secretEncryptionKey })` key. Keep that key outside the database and backed up; migration 0009 removes legacy plaintext enrolments and requires affected users to re-enrol.
32
+
33
+ Two-factor continuation proofs are durably counted on the existing continuation record. Rejected TOTP and backup-code proofs survive transaction rollback, are invalidated after `maxAttempts` (default 5), and are subject to a short per-account cooldown (`failedAttemptCooldownSeconds`, default 1 second). These controls do not permanently lock an account; a new login creates a new continuation after the cooldown.
34
+
35
+ Trusted devices are explicit opt-in only. Pass `{ rememberDevice: true }` when completing setup or a two-factor challenge. Fortress returns a high-entropy `trustedDeviceToken`; pass it back in `RequestMeta.trustedDeviceToken` on later logins. The database stores only its hash and never uses User-Agent as a credential. Core is adapter-neutral and does not set plugin cookies: the host application must set the returned token in a `Secure`, `HttpOnly`, `SameSite=Lax` (or stricter), appropriately scoped cookie and forward it on subsequent requests.
36
+
37
+ ## Password Hashing
38
+
39
+ Default: Argon2id via WASM (`hash-wasm`).
40
+
41
+ | Parameter | Value |
42
+ |-----------|-------|
43
+ | Memory | 64 MB |
44
+ | Iterations | 3 |
45
+ | Parallelism | 1 |
46
+ | Output length | 32 bytes |
47
+
48
+ ### Swapping the Hasher
49
+
50
+ For better performance outside edge runtimes, swap to a native implementation:
51
+
52
+ ```typescript
53
+ import { createFortress } from '@bajustone/fortress';
54
+
55
+ const fortress = createFortress({
56
+ passwordHasher: {
57
+ hash: async (password) => Bun.password.hash(password, 'argon2id'),
58
+ verify: async (password, hash) => Bun.password.verify(password, hash),
59
+ },
60
+ });
61
+ ```
62
+
63
+ Or with `@node-rs/argon2`:
64
+
65
+ ```typescript
66
+ import { hash, verify } from '@node-rs/argon2';
67
+
68
+ const fortress = createFortress({
69
+ passwordHasher: {
70
+ hash: async (password) => hash(password),
71
+ verify: async (password, h) => verify(h, password),
72
+ },
73
+ });
74
+ ```
75
+
76
+ ## Password Policy
77
+
78
+ Configured via `passwordPolicy` in config. Defaults follow NIST 800-63B:
79
+
80
+ | Option | Default | Description |
81
+ |--------|---------|-------------|
82
+ | `minLength` | 15 | Minimum length for new passwords |
83
+ | `maxLength` | 128 | Maximum password length |
84
+ | `checkBreached` | `false` | Check against HIBP breached passwords |
85
+ | `breachedCacheTtlMs` | 86400000 | Cache TTL for HIBP results (24 hours) |
86
+ | `breachedCacheMaxEntries` | 1000 | Maximum cached HIBP ranges; `0` disables caching |
87
+ | `breachedFailureMode` | `'open'` | Accept (`'open'`) or reject (`'closed'`) password writes during HIBP outages |
88
+
89
+ ### Breach Checking (HIBP)
90
+
91
+ When `checkBreached: true`, Fortress checks passwords against the Have I Been Pwned API using k-anonymity. Only the first 5 characters of the SHA-1 hash are sent to the API -- the full password never leaves the server.
92
+
93
+ Breach checking defaults to **fail open** on network errors: if the HIBP API is unreachable, the password is accepted. Set `breachedFailureMode: 'closed'` when assurance is more important than availability. Both modes log and emit `PASSWORD_BREACH_CHECK_DEGRADED` so operators can alert on a disabled control.
94
+
95
+ ## Rate Limiting
96
+
97
+ The `rate-limit` plugin provides sliding-window rate limiting with dual-key support (per-IP and per-account).
98
+
99
+ ```typescript
100
+ import { rateLimit } from '@bajustone/fortress/plugins/rate-limit';
101
+
102
+ const fortress = createFortress({
103
+ plugins: [
104
+ rateLimit({
105
+ login: { maxPerIp: 10, maxPerAccount: 5, windowSeconds: 900 }, // 15 min window
106
+ register: { maxPerIp: 3, windowSeconds: 3600 }, // 1 hour window
107
+ }),
108
+ ],
109
+ });
110
+ ```
111
+
112
+ ### Custom Store
113
+
114
+ The default store is in-memory. For distributed deployments, provide a custom store:
115
+
116
+ ```typescript
117
+ rateLimit({
118
+ store: myRedisRateLimitStore, // implements RateLimitStore interface
119
+ })
120
+ ```
121
+
122
+ ### IPv6 Normalization
123
+
124
+ IPv6 addresses are normalized to /64 prefixes to prevent attackers from rotating through a /64 block to bypass per-IP limits.
125
+
126
+ ## Account Lockout
127
+
128
+ The `account-lockout` plugin uses progressive delays with exponential backoff instead of hard lockout.
129
+
130
+ | Option | Default | Description |
131
+ |--------|---------|-------------|
132
+ | `maxFailedAttempts` | 5 | Attempts before lockout triggers |
133
+ | `lockoutDurationSeconds` | 900 | Initial lockout (15 min) |
134
+ | `escalation` | `true` | Enable exponential backoff |
135
+ | `maxLockoutSeconds` | 3600 | Maximum lockout (1 hour) |
136
+
137
+ Lockout is tracked by login identifier (email/username), not userId. This correctly handles attempts against non-existent accounts.
138
+
139
+ ## Token Storage
140
+
141
+ ### Refresh Tokens
142
+
143
+ Store in **httpOnly, Secure, SameSite=Lax** cookies. Never store in localStorage or sessionStorage.
144
+
145
+ Why: XSS attacks can read localStorage but cannot access httpOnly cookies. A compromised script can still make requests with cookies attached, but that is mitigated by CSRF protection (see below).
146
+
147
+ ### Access Tokens
148
+
149
+ Keep in memory only (JavaScript variable). Do not persist to localStorage, sessionStorage, or cookies.
150
+
151
+ Access tokens are short-lived (default 15 min) and re-issued via the refresh token. On page reload, the client calls the refresh endpoint to get a new access token.
152
+
153
+ ## CSRF Protection
154
+
155
+ Fortress-managed routes enforce pipeline CSRF by default for unsafe methods
156
+ when the request carries a Fortress access or refresh cookie. The check
157
+ rejects `Sec-Fetch-Site: cross-site` and requires `X-Fortress-CSRF` (or your
158
+ configured header). It still applies when an `Authorization`/API-key header
159
+ is present alongside cookies; pure bearer/API-key requests with no Fortress
160
+ cookies skip it.
161
+
162
+ Fortress also provides a custom-header CSRF middleware for Hono user routes:
163
+
164
+ ```typescript
165
+ import { createCsrfMiddleware } from '@bajustone/fortress/hono';
166
+
167
+ app.use('/api/*', createCsrfMiddleware({
168
+ headerName: 'X-Fortress-CSRF', // default
169
+ skipPaths: ['/api/webhooks/*'],
170
+ safeMethods: ['GET', 'HEAD', 'OPTIONS'], // default
171
+ }));
172
+ ```
173
+
174
+ This requires API clients to send `X-Fortress-CSRF: 1` on all mutating requests (POST, PUT, PATCH, DELETE). Browsers will not attach this header on cross-origin requests unless the server explicitly allows it via CORS, which blocks CSRF.
175
+
176
+ ### Defense Layers
177
+
178
+ 1. **SameSite=Lax cookies** -- baseline protection, prevents cookies from being sent on cross-origin POST
179
+ 2. **Custom header check** -- `X-Fortress-CSRF: 1` required on mutating requests
180
+ 3. **Fetch Metadata** -- checks `Sec-Fetch-Site` header when present (modern browsers only)
181
+
182
+ ## Refresh Token Security
183
+
184
+ ### Family-Based Rotation
185
+
186
+ Every refresh token belongs to a token family. When a refresh token is used:
187
+
188
+ 1. The old token is invalidated
189
+ 2. A new token in the same family is issued
190
+
191
+ If a previously-used token is presented (reuse detection), the entire token family is invalidated. This protects against token theft: if an attacker steals a refresh token and uses it, the legitimate user's next refresh attempt triggers family-wide revocation.
192
+
193
+ Concurrency note: Fortress deliberately treats a losing concurrent refresh as reuse. If two requests submit the same refresh token at once, one rotates successfully and the loser revokes the family, including the winner's new refresh token. Coordinate refreshes client-side or add a server-side single-flight/grace mechanism if your UX requires multi-tab auto-refresh without re-login.
194
+
195
+ ### Token Fingerprinting
196
+
197
+ When `jwt.validateRefreshFingerprint` is enabled, Fortress stores a SHA-256 hash of the User-Agent with each refresh token.
198
+
199
+ | Value | Behavior |
200
+ |-------|----------|
201
+ | `true` | Fingerprint mismatch invalidates the entire token family |
202
+ | `'warn'` | Mismatch is logged but the refresh proceeds |
203
+ | `false` / unset | No fingerprint validation |
204
+
205
+ ## HTTPS Requirements
206
+
207
+ Always use HTTPS in production.
208
+
209
+ - Set the `Secure` flag on all cookies so they are only sent over HTTPS
210
+ - Enable HTTP Strict Transport Security (HSTS) at the reverse proxy or application level
211
+ - Fortress does not enforce HTTPS at the library level -- this is a deployment concern
212
+
213
+ ## Audit Logging
214
+
215
+ The `audit-log` plugin records auth events as append-only entries:
216
+
217
+ - `LOGIN_SUCCESS`, `LOGIN_FAILURE`
218
+ - `LOGOUT`
219
+ - `REGISTER`
220
+ - `TOKEN_REFRESH`, `TOKEN_REUSE`
221
+
222
+ ### Hash Chain
223
+
224
+ When enabled, each audit entry includes a SHA-256 hash of the previous entry, providing integrity checks against accidental corruption and attackers who cannot rewrite the full log plus anchor. A database-write attacker who can recompute every row and reset the in-database anchor can forge a consistent chain; use external attestation or a keyed/external anchor when that actor is in scope.
225
+
226
+ ### Compliance
227
+
228
+ | Standard | Relevant Requirement |
229
+ |----------|---------------------|
230
+ | SOC 2 | Access logging and monitoring (CC6.1, CC7.2) |
231
+ | HIPAA | Audit controls (164.312(b)) |
232
+ | PCI-DSS | Log retention of 1 year, 3 months immediately accessible (10.7) |
233
+
234
+ Configure retention in your application. Fortress writes events; your infrastructure handles retention and archival.
235
+
236
+ ## Admin Impersonation
237
+
238
+ Fortress supports admin impersonation via RFC 8693 token exchange semantics.
239
+
240
+ ```typescript
241
+ const result = await fortress.auth.impersonate(adminUserId, targetUserId, {
242
+ reason: 'Support ticket #1234',
243
+ });
244
+ ```
245
+
246
+ The resulting access token includes an `act` (actor) claim identifying the admin:
247
+
248
+ ```json
249
+ {
250
+ "sub": "target-user-id",
251
+ "act": { "sub": "admin-user-id" },
252
+ "exp": 1712345678
253
+ }
254
+ ```
255
+
256
+ Security constraints:
257
+
258
+ - Default expiry: 60 minutes
259
+ - Non-renewable: no refresh token is issued
260
+ - The caller must verify that the admin has the `fortress:impersonate` permission before calling `impersonate()`
261
+ - The impersonation reason is stored in plugin data for audit purposes
@@ -0,0 +1,161 @@
1
+ # Fortress Threat Model
2
+
3
+ Date: 2026-06-08
4
+ Scope: Fortress core auth/IAM, HTTP pipeline, adapters, and first-party plugins.
5
+
6
+ ## Security goals
7
+
8
+ - Authenticate users and non-human principals with bounded, revocable credentials.
9
+ - Authorize every non-public Fortress route by default-deny metadata (`security`, `permission`, `bearerKind`).
10
+ - Keep browser-cookie flows protected against CSRF.
11
+ - Prevent tenant/data isolation bypass across users, service accounts, tenants, and pooled DB connections.
12
+ - Preserve auditability for security-relevant state changes.
13
+ - Make route, OpenAPI, and migration drift visible in CI.
14
+
15
+ ## Trust boundaries
16
+
17
+ | Boundary | Trusted side | Untrusted side | Controls |
18
+ |---|---|---|---|
19
+ | HTTP request ingress | Fortress pipeline / adapter wrapper | Browser, API clients, reverse proxies | route manifest, CSRF, auth, RBAC, validation |
20
+ | JWT verification | Fortress `verifyToken` with pinned algorithm/issuer | Client-supplied bearer/cookie token | HS256 pinning, issuer check, reserved-claim stripping |
21
+ | Plugin principal resolution | Registered plugin code | API key/OAuth/mTLS-like credential values | first non-null resolver wins, RBAC still applies |
22
+ | Database adapter | Fortress services + configured adapter | User-controlled fields | schema validation, parameterized adapter queries, migrations |
23
+ | Tenant schema switching | Verified JWT custom claim + transaction-pinned adapter | Headers / route body tenant hints | no `X-Tenant-Code`, numeric schema ids, bound `set_config` |
24
+ | OAuth redirects | Registered clients and redirect URI list | Browser redirect parameters | exact/loopback URI matching, issuer identification, PKCE |
25
+
26
+ ## Assets
27
+
28
+ - Password hashes and login identifiers.
29
+ - Access/refresh tokens, OAuth authorization codes, OAuth access/refresh tokens, API-key hashes.
30
+ - IAM roles, permissions, bindings, groups, service accounts.
31
+ - Tenant memberships and tenant-scoped business data.
32
+ - Audit logs and webhook delivery records.
33
+ - OAuth signing keys and OIDC JWKS metadata.
34
+
35
+ ## Threats and mitigations
36
+
37
+ ### OAuth/OIDC flows
38
+
39
+ Threats:
40
+ - Authorization-code interception/replay.
41
+ - Public client without PKCE.
42
+ - Redirect URI mix-up or open redirect.
43
+ - Client grant/scope escalation.
44
+ - Non-standard token endpoint errors confusing relying parties.
45
+
46
+ Mitigations:
47
+ - PKCE required for public clients and enforced at authorization-code issuance/exchange.
48
+ - Authorization codes are single-use and exchanged atomically.
49
+ - Redirect URI matching is exact except RFC 8252 loopback dynamic-port allowance.
50
+ - Authorization responses include `iss`; discovery advertises support.
51
+ - Per-client `grantTypes`, `tokenEndpointAuthMethod`, and `allowedScopes` are enforced.
52
+ - OAuth protocol routes are explicitly marked `bearerKind: 'oauth'`; other `/oauth/*` routes use normal Fortress JWT/RBAC.
53
+
54
+ Residual risks:
55
+ - Legacy compatibility flags can weaken posture if enabled; document and monitor them.
56
+ - Client compromise remains out of scope except revocation/removal controls.
57
+
58
+ ### Refresh-token rotation/replay
59
+
60
+ Threats:
61
+ - Stolen refresh token reused after rotation.
62
+ - Concurrent refresh racing to mint multiple valid descendants.
63
+ - Timing leaks on token hash comparison.
64
+
65
+ Mitigations:
66
+ - Refresh rotation records token family and revokes family on reuse.
67
+ - SQLite transactions are serialized with `BEGIN IMMEDIATE`; PostgreSQL uses adapter transactions.
68
+ - Constant-time hash compare is used for security-critical token/code hashes.
69
+ - Refresh-cookie CSRF is detected even when only the refresh cookie remains.
70
+
71
+ Residual risks:
72
+ - Already-issued access tokens remain valid until expiry unless sessions are revoked and checked by the host flow.
73
+
74
+ ### CSRF and cookies
75
+
76
+ Threats:
77
+ - Cross-site POST using ambient auth/refresh cookies.
78
+ - Silent SvelteKit refresh on unsafe requests.
79
+ - Insecure cookie defaults in production due to missing `NODE_ENV`.
80
+
81
+ Mitigations:
82
+ - Pipeline CSRF is on by default for unsafe methods carrying access or refresh cookies.
83
+ - Rejects `Sec-Fetch-Site: cross-site`; requires custom CSRF header.
84
+ - SvelteKit silent refresh is restricted to safe methods.
85
+ - Cookies default to `Secure` and `__Host-` names unless explicitly opted out for local HTTP.
86
+
87
+ Residual risks:
88
+ - Hosts can disable CSRF or widen skip paths; review configuration and CORS policy together.
89
+
90
+ ### IAM/RBAC authorization and cache invalidation
91
+
92
+ Threats:
93
+ - Public route accidentally declares permission or protected route lacks permission.
94
+ - Stale permission cache after role/binding mutation.
95
+ - Service account inherits user/group permissions unexpectedly.
96
+
97
+ Mitigations:
98
+ - Startup rejects `security: ['none']` plus `permission` collisions.
99
+ - Fortress-managed route pipeline default-denies routes with no usable security metadata.
100
+ - Route manifest classifies every mounted route; drift helpers compare manifest/OpenAPI/RBAC.
101
+ - IAM service invalidates permission cache on permission/role/group/binding mutations.
102
+ - Service accounts have their own subject type and do not inherit group membership.
103
+
104
+ Residual risks:
105
+ - Host-owned routes must opt into `protect()` or adapter route maps; unwrapped routes are application responsibility.
106
+
107
+ ### API-key and service-account flows
108
+
109
+ Threats:
110
+ - API key mints broader keys or manages other principals.
111
+ - API key bypasses IAM by being accepted as a user session.
112
+ - Leaked key continues indefinitely.
113
+
114
+ Mitigations:
115
+ - API keys resolve through plugin principal chain to `USER` or `SERVICE_ACCOUNT` subject.
116
+ - RBAC uses subject type and optional credential scopes.
117
+ - Self-service API-key routes deny API-key credentials from minting/listing/revoking/rotating keys.
118
+ - Keys are stored hashed and can expire/revoke; admin routes require explicit permissions.
119
+
120
+ Residual risks:
121
+ - Hosts should rate-limit key usage and rotate/revoke leaked keys quickly.
122
+
123
+ ### Tenancy and data isolation
124
+
125
+ Threats:
126
+ - Header-selected tenant lets one user choose another tenant.
127
+ - Tenant code/tax ID injected into SQL identifiers.
128
+ - `search_path` set on one pooled connection but query runs on another.
129
+ - Missing tenant claim silently falls back to public/shared data.
130
+
131
+ Mitigations:
132
+ - Tenant context comes from verified `claims.customClaims.tenantId`, issued from `tenant_user` membership.
133
+ - Tenant schema names use numeric database ids (`tenant_<id>`) and validated prefix.
134
+ - Invalid tenant claims are rejected before schema switching.
135
+ - PostgreSQL `search_path` is pinned with bound `set_config('search_path', ?, true)` inside the same transaction/connection as the operation.
136
+ - Tenant business tables should live only in tenant schemas, not `public`.
137
+
138
+ Residual risks:
139
+ - JWT tenant membership is stale until access token expiry/refresh/session revocation.
140
+ - Per-tenant business-table migrations are host-owned via `onSchemaCreated` or deployment tooling.
141
+
142
+ ## Security review packet
143
+
144
+ Provide an independent reviewer with:
145
+
146
+ - This threat model.
147
+ - `docs/security.md` and `SECURITY.md`.
148
+ - Route manifest JSON from the reviewed configuration (`fortress.manifest`).
149
+ - OpenAPI spec from the same configuration.
150
+ - Migration status/drift output (`getMigrationStatus`, `detectMigrationDrift`).
151
+ - Architecture overview: `docs/architecture.md`.
152
+ - Relevant prior review/remediation docs in `docs/*review*` and `docs/*remediation*`.
153
+
154
+ ## CI checks recommended
155
+
156
+ - `bun run lint`
157
+ - `bun run typecheck`
158
+ - `bun run test`
159
+ - `fortress manifest:check` for core surface plus app-level `detectRouteManifestDrift()`.
160
+ - `fortress migrate:check` plus live `detectMigrationDrift()` against deployment DBs.
161
+ - OpenAPI diff review for newly-public endpoints.
@@ -0,0 +1,119 @@
1
+ # Examples
2
+
3
+ | Example | Shows | Run |
4
+ |---|---|---|
5
+ | [`hono-app`](./hono-app/index.ts) | Hono, plugin composition, host routes, validation, OpenAPI | `bun run dev` |
6
+ | [`express-app`](./express-app/index.ts) | Express-compatible middleware, plugins, validation | `bun run examples/express-app/index.ts` |
7
+ | [`sveltekit-app`](./sveltekit-app/README.md) | Handle hook, locals, form actions, OAuth consent | See its README |
8
+ | [`policy`](./policy/fortress.policy.json) | Policy-as-code input | `fortress policy:summary --file examples/policy/fortress.policy.json` |
9
+
10
+ ## Cookie login with Hono
11
+
12
+ ```typescript
13
+ import { Hono } from 'hono';
14
+ import { createFortress } from '@bajustone/fortress';
15
+ import { mountFortress } from '@bajustone/fortress/hono';
16
+
17
+ const fortress = createFortress({
18
+ database,
19
+ jwt: { key: process.env.FORTRESS_JWT_SECRET! },
20
+ cookies: { secure: false }, // local HTTP only
21
+ });
22
+
23
+ const app = new Hono();
24
+ mountFortress(app, fortress);
25
+ ```
26
+
27
+ Unsafe cookie-authenticated requests send the CSRF header:
28
+
29
+ ```typescript
30
+ await fetch('/auth/logout', {
31
+ method: 'POST',
32
+ headers: { 'X-Fortress-CSRF': '1' },
33
+ });
34
+ ```
35
+
36
+ See [Security](../docs/security.md#csrf-protection).
37
+
38
+ ## Bearer-only API
39
+
40
+ ```typescript
41
+ const fortress = createFortress({
42
+ database,
43
+ jwt: { key },
44
+ csrf: { enabled: false }, // only when cookies are never accepted
45
+ });
46
+
47
+ const { authMiddleware } = createHonoMiddleware(fortress);
48
+ app.use('/api/*', authMiddleware);
49
+ ```
50
+
51
+ ```http
52
+ Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
53
+ ```
54
+
55
+ ## Service account with a scoped API key
56
+
57
+ ```typescript
58
+ const account = await fortress.iam.createServiceAccount({
59
+ name: 'ci-bot',
60
+ displayName: 'CI',
61
+ });
62
+
63
+ await fortress.iam.bindRoleToServiceAccount(account.id, deployerRole.id);
64
+
65
+ const credential = await fortress.plugins['api-key'].createKey({
66
+ subject: { type: 'SERVICE_ACCOUNT', id: account.id },
67
+ name: 'deploy',
68
+ scopes: ['deploy:run'],
69
+ });
70
+ ```
71
+
72
+ ```http
73
+ Authorization: ApiKey fortress_sk_...
74
+ ```
75
+
76
+ ## Apply a policy
77
+
78
+ ```typescript
79
+ import {
80
+ applyPolicyPlan,
81
+ diffPolicy,
82
+ loadPolicy,
83
+ } from '@bajustone/fortress';
84
+
85
+ const { policy } = await loadPolicy();
86
+ const plan = await diffPolicy(policy, fortress.iam);
87
+ const result = await applyPolicyPlan(plan, fortress.iam);
88
+
89
+ if (result.errors.length)
90
+ throw new Error(JSON.stringify(result.errors));
91
+ ```
92
+
93
+ See [Policy as code](../docs/policy-as-code.md).
94
+
95
+ ## PostgreSQL schema tenancy
96
+
97
+ ```typescript
98
+ import { tenancy } from '@bajustone/fortress/plugins/tenancy';
99
+
100
+ const fortress = createFortress({
101
+ database: postgresAdapter,
102
+ jwt: { key },
103
+ plugins: [tenancy({
104
+ routes: true,
105
+ onSchemaCreated: async (schema, rawQuery) => {
106
+ await rawQuery(`CREATE TABLE "${schema}".widgets (id BIGSERIAL PRIMARY KEY)`);
107
+ },
108
+ })] as const,
109
+ });
110
+
111
+ const tenant = await fortress.plugins.tenancy.createTenant({
112
+ name: 'Acme',
113
+ taxId: 'acme',
114
+ });
115
+
116
+ await fortress.plugins.tenancy.addUserToTenant(user.id, tenant.id);
117
+ ```
118
+
119
+ See [Tenancy](../docs/plugins/tenancy.md).