@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,210 @@
1
+ import type { DatabaseAdapter } from '../adapters/database';
2
+ import type { ScopeRule } from '../adapters/database/types';
3
+ import type { AuthService } from './auth/auth-service';
4
+ import type { FortressConfig } from './config';
5
+ import type { EndpointDefinition } from './endpoint';
6
+ import type { PluginRequestContext } from './http/plugin-middleware';
7
+ import type { IamService } from './iam/iam-service';
8
+ import type { FortressLogger } from './observability/logger';
9
+ import type {
10
+ AuthSuccess,
11
+ AuthTokenPair,
12
+ CreateUserInput,
13
+ FortressUser,
14
+ PendingReason,
15
+ RequestMeta,
16
+ Subject,
17
+ TokenClaims,
18
+ } from './types';
19
+
20
+ export interface FortressPlugin {
21
+ /** Unique plugin identifier */
22
+ name: string;
23
+
24
+ /** DB models this plugin needs */
25
+ models?: ModelDefinition[];
26
+
27
+ /** Hooks into auth lifecycle (executed in plugin registration order) */
28
+ hooks?: PluginHooks;
29
+
30
+ /** Extra methods exposed on fortress.plugins.<name> */
31
+ // eslint-disable-next-line ts/no-unsafe-function-type -- plugin methods are dynamically typed
32
+ methods?: (ctx: PluginContext) => Record<string, Function>;
33
+
34
+ /**
35
+ * HTTP routes this plugin adds, keyed by handler name.
36
+ *
37
+ * A keyed record (not an array) so each entry's full
38
+ * `EndpointDefinition<TBody, TQuery, TParams, TResponses>` type is
39
+ * preserved for the typed `fortress.call.*` proxy. The dispatcher looks
40
+ * plugin route handlers up by name (`fortress.plugins[pluginName][handlerName]`),
41
+ * so the keyed shape is already the natural fit at dispatch time — the
42
+ * key just needs to match the `EndpointDefinition.handler` string.
43
+ */
44
+ routes?: Record<string, EndpointDefinition>;
45
+
46
+ /** Middleware to inject into the request pipeline */
47
+ middleware?: MiddlewareDefinition[];
48
+
49
+ /** Wrap the DatabaseAdapter per-request */
50
+ wrapAdapter?: (
51
+ adapter: DatabaseAdapter,
52
+ requestContext: Record<string, unknown>,
53
+ ) => DatabaseAdapter;
54
+
55
+ /** Extend JWT token claims */
56
+ enrichTokenClaims?: (
57
+ userId: string,
58
+ ctx: PluginContext,
59
+ ) => Promise<Record<string, unknown>>;
60
+
61
+ /** Scope data access by user context (row-level data isolation) */
62
+ scopeRules?: (
63
+ userId: string,
64
+ model: string,
65
+ ctx: PluginContext,
66
+ ) => Promise<ScopeRule | null>;
67
+
68
+ /**
69
+ * Resolve a request principal from a non-JWT credential — API key,
70
+ * OAuth bearer, mTLS client cert, signed JWT assertion, etc.
71
+ *
72
+ * Called by `fortress.handleRequest` **before** the JWT fallback. Plugins
73
+ * are tried in registration order; the first to return a non-null result
74
+ * wins, and its subject (plus optional claims) become the request
75
+ * principal for downstream RBAC. Returning `null` means "defer" — the
76
+ * next plugin is tried, and if none resolve, the core JWT path runs.
77
+ *
78
+ * This is the extension point for any future credential mechanism. The
79
+ * api-key plugin implements it to turn `Authorization: ApiKey <key>` /
80
+ * `X-API-Key: <key>` headers into a `USER` or `SERVICE_ACCOUNT`
81
+ * principal.
82
+ */
83
+ resolvePrincipal?: (
84
+ request: Request,
85
+ ctx: PluginContext,
86
+ ) => Promise<{ subject: Subject; claims?: TokenClaims; scopes?: string[] | null } | null>;
87
+ }
88
+
89
+ // --- Hooks ---
90
+
91
+ export interface PluginHooks {
92
+ beforeLogin?: (ctx: HookContext & { email: string }) => Promise<HookResult | void>;
93
+ beforeRegister?: (ctx: HookContext & { data: CreateUserInput }) => Promise<HookResult | void>;
94
+ beforeTokenRefresh?: (ctx: HookContext & { token: string }) => Promise<HookResult | void>;
95
+ beforeLogout?: (ctx: HookContext & { token: string }) => Promise<void>;
96
+ onLoginFailure?: (ctx: HookContext & { identifier: string; error: Error }) => Promise<void>;
97
+
98
+ /** Runs after primary credentials succeed but before any token is issued. */
99
+ postAuthGate?: PostAuthGateProvider;
100
+
101
+ afterLogin?: (ctx: AfterHookContext, result: AuthSuccess) => Promise<AuthSuccess>;
102
+ afterRegister?: (ctx: AfterHookContext, user: FortressUser) => Promise<void>;
103
+ afterTokenRefresh?: (ctx: AfterHookContext, result: AuthTokenPair) => Promise<AuthTokenPair>;
104
+ }
105
+
106
+ export interface HookContext {
107
+ db: DatabaseAdapter;
108
+ config: FortressConfig;
109
+ meta?: RequestMeta;
110
+ }
111
+
112
+ export interface AfterHookContext extends HookContext {
113
+ responseHeaders: Headers;
114
+ /** Normalized login identifier used for this auth flow, when applicable. */
115
+ identifier?: string;
116
+ }
117
+
118
+ export interface HookResult {
119
+ stop: true;
120
+ response: Record<string, unknown>;
121
+ }
122
+
123
+ export interface PostAuthGateContext extends HookContext {
124
+ user: FortressUser;
125
+ /** Reasons already satisfied by the continuation currently being completed. */
126
+ completedReasons: readonly PendingReason[];
127
+ }
128
+
129
+ export interface PostAuthGateDecision {
130
+ pluginData?: Record<string, unknown>;
131
+ }
132
+
133
+ export interface PostAuthGateVerificationContext extends HookContext {
134
+ user: FortressUser;
135
+ continuation: {
136
+ id: string;
137
+ reason: PendingReason;
138
+ expiresAt: Date;
139
+ };
140
+ }
141
+
142
+ export interface PostAuthGateProvider {
143
+ reason: PendingReason;
144
+ /** Durable failed-proof policy stored on each continuation. */
145
+ maxAttempts?: number;
146
+ cooldownSeconds?: number;
147
+ evaluate: (ctx: PostAuthGateContext) => Promise<PostAuthGateDecision | void>;
148
+ /** Return optional plugin data to include in the final AuthResult. */
149
+ verify: (ctx: PostAuthGateVerificationContext, completion: unknown) => Promise<Record<string, unknown> | void>;
150
+ }
151
+
152
+ // --- Supporting Types ---
153
+
154
+ export type ModelConstraint
155
+ = | { type: 'unique'; fields: string[] }
156
+ | { type: 'index'; fields: string[]; name?: string };
157
+
158
+ export interface ModelDefinition {
159
+ name: string;
160
+ fields: Record<string, FieldDefinition>;
161
+ constraints?: ModelConstraint[];
162
+ }
163
+
164
+ export interface FieldDefinition {
165
+ type: 'string' | 'number' | 'boolean' | 'date';
166
+ required?: boolean;
167
+ unique?: boolean;
168
+ references?: { model: string; field: string };
169
+ }
170
+
171
+ export interface PluginContext {
172
+ db: DatabaseAdapter;
173
+ config: FortressConfig;
174
+ /** Auth service reference. Optional at init time; available at runtime (enrichTokenClaims, scopeRules). */
175
+ auth?: AuthService;
176
+ /** IAM service reference. Optional at init time; available at runtime. */
177
+ iam?: IamService;
178
+ /** Resolved logger (silent no-op if `config.logger` is unset). */
179
+ logger?: FortressLogger;
180
+ }
181
+
182
+ /**
183
+ * Second argument passed to plugin HTTP route handlers by the dispatcher.
184
+ * Carries the verified caller identity and the raw Request so handlers can
185
+ * make authorization decisions, stamp audit entries, or read headers/cookies
186
+ * without trusting client-supplied body fields.
187
+ *
188
+ * `subject` / `claims` are populated whenever the endpoint's `meta.security`
189
+ * declared bearer auth (the dispatcher resolves principals first). `userId`
190
+ * is a convenience — it's present iff `subject?.type === 'USER'`, matching
191
+ * the pre-SERVICE_ACCOUNT shape. For public endpoints all three are
192
+ * `undefined`.
193
+ */
194
+ export interface PluginRouteContext {
195
+ /** Resolved request principal — USER or SERVICE_ACCOUNT. */
196
+ subject?: Subject;
197
+ /** Convenience alias for `subject?.id` when the subject is a USER. */
198
+ userId?: string;
199
+ claims?: TokenClaims;
200
+ /** Credential-level narrowing scopes from principal resolution, if any. */
201
+ scopes?: string[] | null;
202
+ meta?: RequestMeta;
203
+ request: Request;
204
+ }
205
+
206
+ export interface MiddlewareDefinition {
207
+ path: string;
208
+ position: 'before-auth' | 'after-auth' | 'after-rbac';
209
+ handler: (ctx: PluginContext, request: PluginRequestContext, next: () => Promise<void>) => Promise<void>;
210
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Execute a {@link PolicyPlan} against an {@link IamService}.
3
+ *
4
+ * Ops are applied in dependency-safe order (resources before roles before
5
+ * service accounts) so a single `applyPolicyPlan` call can bring a fresh
6
+ * database to the declared state in one shot.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import type { IamService } from '../iam/iam-service';
12
+ import type { PolicyOp, PolicyPlan } from './types';
13
+ import { Errors } from '../errors';
14
+
15
+ /** Result of {@link applyPolicyPlan}. */
16
+ export interface ApplyPolicyResult {
17
+ applied: PolicyOp[];
18
+ skipped: PolicyOp[];
19
+ errors: { op: PolicyOp; message: string }[];
20
+ }
21
+
22
+ const OP_ORDER: Record<PolicyOp['kind'], number> = {
23
+ 'create-resource': 0,
24
+ 'add-resource-action': 1,
25
+ 'create-role': 2,
26
+ 'update-role-description': 3,
27
+ 'add-role-permission': 4,
28
+ 'remove-role-permission': 5,
29
+ 'create-group': 6,
30
+ 'update-group-description': 7,
31
+ 'create-service-account': 8,
32
+ 'update-service-account': 9,
33
+ 'bind-service-account-role': 10,
34
+ 'unbind-service-account-role': 11,
35
+ 'delete-role': 12,
36
+ 'delete-group': 13,
37
+ 'delete-service-account': 14,
38
+ };
39
+
40
+ /**
41
+ * Apply every op in the plan, sorted by op kind so dependencies are
42
+ * satisfied (resources before roles, roles before service-account
43
+ * bindings). Returns a structured result so the caller can log success
44
+ * vs. failure per op.
45
+ */
46
+ export async function applyPolicyPlan(
47
+ plan: PolicyPlan,
48
+ iam: IamService,
49
+ ): Promise<ApplyPolicyResult> {
50
+ const ops = [...plan.ops].sort((a, b) => OP_ORDER[a.kind] - OP_ORDER[b.kind]);
51
+ const applied: PolicyOp[] = [];
52
+ const skipped: PolicyOp[] = [];
53
+ const errors: { op: PolicyOp; message: string }[] = [];
54
+
55
+ // Caches to avoid repeated lookups across ops in the same apply pass.
56
+ const roleIdByName = new Map<string, string>();
57
+ const groupIdByName = new Map<string, string>();
58
+ const saIdByName = new Map<string, string>();
59
+
60
+ const resolveRoleId = async (name: string): Promise<string> => {
61
+ if (roleIdByName.has(name))
62
+ return roleIdByName.get(name)!;
63
+ const roles = await iam.getRoles();
64
+ for (const role of roles) roleIdByName.set(role.name, role.id);
65
+ const id = roleIdByName.get(name);
66
+ if (id == null)
67
+ throw Errors.notFound(`Role '${name}' not found`);
68
+ return id;
69
+ };
70
+
71
+ const resolveGroupId = async (name: string): Promise<string> => {
72
+ if (groupIdByName.has(name))
73
+ return groupIdByName.get(name)!;
74
+ const { groups } = await iam.listGroups({ limit: 10_000 });
75
+ for (const group of groups) groupIdByName.set(group.name, group.id);
76
+ const id = groupIdByName.get(name);
77
+ if (id == null)
78
+ throw Errors.notFound(`Group '${name}' not found`);
79
+ return id;
80
+ };
81
+
82
+ const resolveSaId = async (name: string): Promise<string> => {
83
+ if (saIdByName.has(name))
84
+ return saIdByName.get(name)!;
85
+ const { serviceAccounts } = await iam.listServiceAccounts({ limit: 10_000 });
86
+ for (const sa of serviceAccounts) saIdByName.set(sa.name, sa.id);
87
+ const id = saIdByName.get(name);
88
+ if (id == null)
89
+ throw Errors.notFound(`Service account '${name}' not found`);
90
+ return id;
91
+ };
92
+
93
+ for (const op of ops) {
94
+ try {
95
+ switch (op.kind) {
96
+ case 'create-resource':
97
+ case 'add-resource-action': {
98
+ // Both go through pushResources via getResources merge: get current
99
+ // resources, layer the change, push.
100
+ const current = await iam.getResources();
101
+ const next = { resources: { ...current.resources } };
102
+ if (op.kind === 'create-resource') {
103
+ next.resources[op.resource.name] = {
104
+ actions: [...op.resource.actions],
105
+ description: op.resource.description,
106
+ };
107
+ }
108
+ else {
109
+ const existing = next.resources[op.resource] ?? { actions: [] };
110
+ next.resources[op.resource] = {
111
+ ...existing,
112
+ actions: existing.actions.includes(op.action)
113
+ ? existing.actions
114
+ : [...existing.actions, op.action],
115
+ };
116
+ }
117
+ await iam.pushResources(next);
118
+ applied.push(op);
119
+ break;
120
+ }
121
+ case 'create-role': {
122
+ const role = await iam.createRole(op.role.name, op.role.permissions.map(perm => ({
123
+ resource: perm.resource,
124
+ action: perm.action,
125
+ effect: perm.effect,
126
+ })), op.role.description);
127
+ roleIdByName.set(op.role.name, role.id);
128
+ applied.push(op);
129
+ break;
130
+ }
131
+ case 'update-role-description': {
132
+ const roleId = await resolveRoleId(op.role);
133
+ await iam.updateRole(roleId, { description: op.to ?? null });
134
+ applied.push(op);
135
+ break;
136
+ }
137
+ case 'add-role-permission': {
138
+ const roleId = await resolveRoleId(op.role);
139
+ await iam.addPermissionToRole(roleId, {
140
+ resource: op.permission.resource,
141
+ action: op.permission.action,
142
+ effect: op.permission.effect,
143
+ });
144
+ applied.push(op);
145
+ break;
146
+ }
147
+ case 'remove-role-permission': {
148
+ const roleId = await resolveRoleId(op.role);
149
+ await iam.removePermissionFromRole(roleId, {
150
+ resource: op.permission.resource,
151
+ action: op.permission.action,
152
+ effect: op.permission.effect,
153
+ });
154
+ applied.push(op);
155
+ break;
156
+ }
157
+ case 'delete-role': {
158
+ const roleId = await resolveRoleId(op.role);
159
+ await iam.deleteRole(roleId);
160
+ roleIdByName.delete(op.role);
161
+ applied.push(op);
162
+ break;
163
+ }
164
+ case 'create-group': {
165
+ const group = await iam.createGroup(op.group.name, op.group.description);
166
+ groupIdByName.set(op.group.name, group.id);
167
+ applied.push(op);
168
+ break;
169
+ }
170
+ case 'update-group-description': {
171
+ const groupId = await resolveGroupId(op.group);
172
+ await iam.updateGroup(groupId, { description: op.to });
173
+ applied.push(op);
174
+ break;
175
+ }
176
+ case 'delete-group': {
177
+ const groupId = await resolveGroupId(op.group);
178
+ await iam.deleteGroup(groupId);
179
+ groupIdByName.delete(op.group);
180
+ applied.push(op);
181
+ break;
182
+ }
183
+ case 'create-service-account': {
184
+ const sa = await iam.createServiceAccount({
185
+ name: op.serviceAccount.name,
186
+ displayName: op.serviceAccount.displayName,
187
+ description: op.serviceAccount.description,
188
+ });
189
+ // `isActive` defaults to true at create-time; only emit an update
190
+ // when the policy explicitly disables the account.
191
+ if (op.serviceAccount.isActive === false) {
192
+ await iam.updateServiceAccount(sa.id, { isActive: false });
193
+ }
194
+ saIdByName.set(op.serviceAccount.name, sa.id);
195
+ applied.push(op);
196
+ break;
197
+ }
198
+ case 'update-service-account': {
199
+ const saId = await resolveSaId(op.serviceAccount);
200
+ await iam.updateServiceAccount(saId, op.changes as {
201
+ displayName?: string | null;
202
+ description?: string | null;
203
+ isActive?: boolean;
204
+ });
205
+ applied.push(op);
206
+ break;
207
+ }
208
+ case 'bind-service-account-role': {
209
+ const saId = await resolveSaId(op.serviceAccount);
210
+ const roleId = await resolveRoleId(op.role);
211
+ await iam.bindRoleToServiceAccount(saId, roleId);
212
+ applied.push(op);
213
+ break;
214
+ }
215
+ case 'unbind-service-account-role': {
216
+ const saId = await resolveSaId(op.serviceAccount);
217
+ const roleId = await resolveRoleId(op.role);
218
+ await iam.unbindRoleFromServiceAccount(saId, roleId, null);
219
+ applied.push(op);
220
+ break;
221
+ }
222
+ case 'delete-service-account': {
223
+ const saId = await resolveSaId(op.serviceAccount);
224
+ await iam.deleteServiceAccount(saId);
225
+ saIdByName.delete(op.serviceAccount);
226
+ applied.push(op);
227
+ break;
228
+ }
229
+ }
230
+ }
231
+ catch (err) {
232
+ errors.push({ op, message: err instanceof Error ? err.message : String(err) });
233
+ }
234
+ }
235
+
236
+ return { applied, skipped, errors };
237
+ }
238
+
239
+ /**
240
+ * Compatibility helper that applies only the resource operations in a plan.
241
+ * The file-path argument is retained for source compatibility but no file is
242
+ * written; resource updates are applied directly through the IAM service.
243
+ */
244
+ export async function applyResourceOps(
245
+ plan: PolicyPlan,
246
+ iam: IamService,
247
+ _syncResourceFile: string,
248
+ ): Promise<void> {
249
+ const resourceOps = plan.ops.filter(op => op.kind === 'create-resource' || op.kind === 'add-resource-action');
250
+ if (resourceOps.length === 0)
251
+ return;
252
+ const current = await iam.getResources();
253
+ const next = { resources: { ...current.resources } };
254
+ for (const op of resourceOps) {
255
+ if (op.kind === 'create-resource') {
256
+ next.resources[op.resource.name] = {
257
+ actions: [...op.resource.actions],
258
+ description: op.resource.description,
259
+ };
260
+ }
261
+ else if (op.kind === 'add-resource-action') {
262
+ const existing = next.resources[op.resource] ?? { actions: [] };
263
+ next.resources[op.resource] = {
264
+ ...existing,
265
+ actions: existing.actions.includes(op.action)
266
+ ? existing.actions
267
+ : [...existing.actions, op.action],
268
+ };
269
+ }
270
+ }
271
+ await iam.pushResources(next);
272
+ }