@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,354 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/plugins/rate-limit/index.ts
21
+ var rate_limit_exports = {};
22
+ __export(rate_limit_exports, {
23
+ normalizeAccountIdentifier: () => normalizeAccountIdentifier,
24
+ normalizeIp: () => normalizeIp,
25
+ rateLimit: () => rateLimit
26
+ });
27
+ module.exports = __toCommonJS(rate_limit_exports);
28
+
29
+ // src/core/errors.ts
30
+ var FortressError = class extends Error {
31
+ code;
32
+ statusCode;
33
+ retryAfter;
34
+ details;
35
+ /** RFC 6749 §5.2 / §4.1.2.1 machine code. Set by `Errors.oauth()`. */
36
+ oauthError;
37
+ /** Human-readable description for the OAuth `error_description` field. */
38
+ oauthDescription;
39
+ /** Optional URI for the OAuth `error_uri` field (§5.2). */
40
+ oauthErrorUri;
41
+ constructor(code, message, statusCode, options) {
42
+ super(message, { cause: options?.cause });
43
+ this.code = code;
44
+ this.statusCode = statusCode;
45
+ this.retryAfter = options?.retryAfter;
46
+ this.details = options?.details;
47
+ this.oauthError = options?.oauthError;
48
+ this.oauthDescription = options?.oauthDescription;
49
+ this.oauthErrorUri = options?.oauthErrorUri;
50
+ }
51
+ toJSON() {
52
+ return {
53
+ code: this.code,
54
+ message: this.message,
55
+ statusCode: this.statusCode,
56
+ ...this.details !== void 0 && { details: this.details }
57
+ };
58
+ }
59
+ };
60
+ var Errors = {
61
+ unauthorized: (message = "Unauthorized") => new FortressError("UNAUTHORIZED", message, 401),
62
+ tokenReuse: () => new FortressError("TOKEN_REUSE", "Token reuse detected", 401),
63
+ sessionIdleTimeout: () => new FortressError("SESSION_IDLE_TIMEOUT", "Session idle timeout exceeded", 401),
64
+ sessionAbsoluteTimeout: () => new FortressError("SESSION_ABSOLUTE_TIMEOUT", "Session absolute timeout exceeded", 401),
65
+ forbidden: (message = "Forbidden") => new FortressError("FORBIDDEN", message, 403),
66
+ badRequest: (message = "Bad request") => new FortressError("BAD_REQUEST", message, 400),
67
+ notFound: (message = "Not found") => new FortressError("NOT_FOUND", message, 404),
68
+ conflict: (message = "Conflict", options) => new FortressError("CONFLICT", message, 409, options),
69
+ unprocessable: (message = "Unprocessable entity", options) => new FortressError("UNPROCESSABLE_ENTITY", message, 422, options),
70
+ rateLimited: (retryAfter) => new FortressError("RATE_LIMITED", "Too many requests", 429, { retryAfter }),
71
+ database: (message = "Database error", cause) => new FortressError("DATABASE_ERROR", message, 500, { cause }),
72
+ serviceUnavailable: (message = "Service unavailable", options) => new FortressError("SERVICE_UNAVAILABLE", message, 503, options),
73
+ validationError: (issues) => new FortressError("VALIDATION_ERROR", "Validation failed", 422, { details: issues }),
74
+ /**
75
+ * RFC 6749 §5.2 / §4.1.2.1 OAuth error.
76
+ *
77
+ * The HTTP error mapper detects `oauthError` and emits the OAuth-spec
78
+ * JSON body `{ error, error_description, error_uri? }` instead of the
79
+ * default fortress `{ code, message }` shape, so strict OAuth clients
80
+ * (Moodle, openid-client, Spring Security, etc.) can switch behaviour
81
+ * on the machine-readable `error` field.
82
+ *
83
+ * Status is inferred from the OAuth code per the spec table:
84
+ * - `invalid_client` → 401
85
+ * - everything else → 400
86
+ */
87
+ oauth: (error, description, options) => {
88
+ const status = options?.status ?? (error === "invalid_client" ? 401 : 400);
89
+ const code = status === 401 ? "UNAUTHORIZED" : "BAD_REQUEST";
90
+ return new FortressError(code, description ?? error, status, {
91
+ oauthError: error,
92
+ oauthDescription: description,
93
+ oauthErrorUri: options?.errorUri,
94
+ cause: options?.cause
95
+ });
96
+ },
97
+ /**
98
+ * Reconstruct a {@link FortressError} from a JSON error body emitted by
99
+ * `errorToResponse`. Used by the in-process `fortress.call.*` client to
100
+ * convert non-2xx responses into typed throws that mirror what a direct
101
+ * service-method call would produce.
102
+ *
103
+ * Maps by HTTP status when the body doesn't carry a recognizable
104
+ * `{ code, message }` payload, so network errors and opaque upstream
105
+ * failures still become structured `FortressError`s.
106
+ */
107
+ fromHttpResponse: (status, body) => {
108
+ const payload = body && typeof body === "object" ? body : {};
109
+ const message = typeof payload.message === "string" ? payload.message : `HTTP ${status}`;
110
+ const code = typeof payload.code === "string" && isFortressErrorCode(payload.code) ? payload.code : statusToErrorCode(status);
111
+ return new FortressError(code, message, status, { details: payload.details });
112
+ }
113
+ };
114
+ function isFortressErrorCode(code) {
115
+ return code === "UNAUTHORIZED" || code === "TOKEN_REUSE" || code === "SESSION_IDLE_TIMEOUT" || code === "SESSION_ABSOLUTE_TIMEOUT" || code === "FORBIDDEN" || code === "BAD_REQUEST" || code === "NOT_FOUND" || code === "CONFLICT" || code === "UNPROCESSABLE_ENTITY" || code === "RATE_LIMITED" || code === "DATABASE_ERROR" || code === "VALIDATION_ERROR" || code === "SERVICE_UNAVAILABLE";
116
+ }
117
+ function statusToErrorCode(status) {
118
+ if (status === 401)
119
+ return "UNAUTHORIZED";
120
+ if (status === 403)
121
+ return "FORBIDDEN";
122
+ if (status === 404)
123
+ return "NOT_FOUND";
124
+ if (status === 409)
125
+ return "CONFLICT";
126
+ if (status === 422)
127
+ return "UNPROCESSABLE_ENTITY";
128
+ if (status === 429)
129
+ return "RATE_LIMITED";
130
+ if (status === 503)
131
+ return "SERVICE_UNAVAILABLE";
132
+ if (status >= 500)
133
+ return "DATABASE_ERROR";
134
+ return "BAD_REQUEST";
135
+ }
136
+
137
+ // src/plugins/rate-limit/memory-store.ts
138
+ function createMemoryStore() {
139
+ const store = /* @__PURE__ */ new Map();
140
+ const cleanupInterval = setInterval(() => {
141
+ const now = Date.now();
142
+ for (const [key, record] of store) {
143
+ const timestamps = record.timestamps.filter((timestamp) => now - timestamp < record.maxWindowMs);
144
+ if (timestamps.length === 0)
145
+ store.delete(key);
146
+ else
147
+ store.set(key, { ...record, timestamps });
148
+ }
149
+ }, 6e4);
150
+ if (typeof cleanupInterval === "object" && "unref" in cleanupInterval)
151
+ cleanupInterval.unref();
152
+ return {
153
+ async increment(key, windowMs) {
154
+ const now = Date.now();
155
+ const existing = store.get(key);
156
+ const maxWindowMs = Math.max(existing?.maxWindowMs ?? 0, windowMs);
157
+ const retained = (existing?.timestamps ?? []).filter((timestamp) => timestamp > now - maxWindowMs);
158
+ retained.push(now);
159
+ store.set(key, { timestamps: retained, maxWindowMs, lastWindowMs: windowMs });
160
+ const active = retained.filter((timestamp) => timestamp > now - windowMs);
161
+ return { count: active.length, resetAt: active[0] + windowMs };
162
+ },
163
+ async get(key) {
164
+ const record = store.get(key);
165
+ if (!record)
166
+ return null;
167
+ const now = Date.now();
168
+ const active = record.timestamps.filter((timestamp) => timestamp > now - record.lastWindowMs);
169
+ if (active.length === 0)
170
+ return null;
171
+ return {
172
+ count: active.length,
173
+ resetAt: active[0] + record.lastWindowMs
174
+ };
175
+ }
176
+ };
177
+ }
178
+
179
+ // src/plugins/rate-limit/index.ts
180
+ var DEFAULT_LOGIN = { maxPerIp: 10, maxPerAccount: 5, windowSeconds: 900 };
181
+ var DEFAULT_REGISTER = { maxPerIp: 3, windowSeconds: 3600 };
182
+ var DEFAULT_REFRESH = { maxPerIp: 60, maxPerUser: 60, windowSeconds: 60 };
183
+ var DEFAULT_OAUTH_TOKEN = { maxPerIp: 60, windowSeconds: 60 };
184
+ var DEFAULT_API_KEY_ISSUE = { maxPerIp: 10, maxPerUser: 10, windowSeconds: 3600 };
185
+ function normalizeIp(ip) {
186
+ if (!ip)
187
+ return "unknown";
188
+ if (ip.startsWith("::ffff:"))
189
+ return ip.slice(7);
190
+ if (ip.includes(":"))
191
+ return ip.split(":").slice(0, 4).join(":");
192
+ return ip;
193
+ }
194
+ function normalizeAccountIdentifier(identifier) {
195
+ return identifier.trim().normalize("NFC").toLowerCase();
196
+ }
197
+ function ipFromRequest(request) {
198
+ const xff = request.headers.get("x-forwarded-for");
199
+ if (xff)
200
+ return xff.split(",")[0].trim();
201
+ return request.headers.get("x-real-ip") ?? "";
202
+ }
203
+ async function runRule(store, ruleName, rule, keys) {
204
+ const windowMs = rule.windowSeconds * 1e3;
205
+ const prefix = rule.keyPrefix ?? ruleName;
206
+ if (rule.maxPerIp != null) {
207
+ const ip = normalizeIp(keys.ip);
208
+ const res = await store.increment(`${prefix}:ip:${ip}`, windowMs);
209
+ if (res.count > rule.maxPerIp) {
210
+ const retryAfter = Math.max(Math.ceil((res.resetAt - Date.now()) / 1e3), 1);
211
+ throw Errors.rateLimited(retryAfter);
212
+ }
213
+ }
214
+ if (rule.maxPerUser != null && keys.userId != null) {
215
+ const res = await store.increment(`${prefix}:user:${keys.userId}`, windowMs);
216
+ if (res.count > rule.maxPerUser) {
217
+ const retryAfter = Math.max(Math.ceil((res.resetAt - Date.now()) / 1e3), 1);
218
+ throw Errors.rateLimited(retryAfter);
219
+ }
220
+ }
221
+ }
222
+ function isDisabled(block) {
223
+ return !!block && "disabled" in block && block.disabled === true;
224
+ }
225
+ function rateLimit(config = {}) {
226
+ const store = config.store ?? createMemoryStore();
227
+ const loginCfg = isDisabled(config.login) ? void 0 : { ...DEFAULT_LOGIN, ...config.login };
228
+ const registerCfg = isDisabled(config.register) ? void 0 : { ...DEFAULT_REGISTER, ...config.register };
229
+ const refreshCfg = config.refresh ? { ...DEFAULT_REFRESH, ...config.refresh } : void 0;
230
+ const oauthTokenCfg = config.oauthToken ? { ...DEFAULT_OAUTH_TOKEN, ...config.oauthToken } : void 0;
231
+ const apiKeyIssueCfg = config.apiKeyIssue ? { ...DEFAULT_API_KEY_ISSUE, ...config.apiKeyIssue } : void 0;
232
+ const rules = { ...config.rules };
233
+ if (loginCfg) {
234
+ rules.login = { maxPerIp: loginCfg.maxPerIp, windowSeconds: loginCfg.windowSeconds };
235
+ if (loginCfg.maxPerAccount != null) {
236
+ rules["login:account"] = {
237
+ maxPerUser: loginCfg.maxPerAccount,
238
+ windowSeconds: loginCfg.windowSeconds,
239
+ keyPrefix: "login:account"
240
+ };
241
+ }
242
+ }
243
+ if (registerCfg)
244
+ rules.register = { maxPerIp: registerCfg.maxPerIp, windowSeconds: registerCfg.windowSeconds };
245
+ if (refreshCfg) {
246
+ rules.refresh = {
247
+ maxPerIp: refreshCfg.maxPerIp,
248
+ maxPerUser: refreshCfg.maxPerUser,
249
+ windowSeconds: refreshCfg.windowSeconds
250
+ };
251
+ }
252
+ if (oauthTokenCfg)
253
+ rules.oauthToken = { maxPerIp: oauthTokenCfg.maxPerIp, windowSeconds: oauthTokenCfg.windowSeconds };
254
+ if (apiKeyIssueCfg) {
255
+ rules.apiKeyIssue = {
256
+ maxPerIp: apiKeyIssueCfg.maxPerIp,
257
+ maxPerUser: apiKeyIssueCfg.maxPerUser,
258
+ windowSeconds: apiKeyIssueCfg.windowSeconds
259
+ };
260
+ }
261
+ async function check(ruleName, keys) {
262
+ const rule = rules[ruleName];
263
+ if (!rule)
264
+ throw new Error(`rate-limit: unknown rule '${ruleName}' \u2014 declare it in config.rules or add the matching endpoint config block`);
265
+ await runRule(store, ruleName, rule, keys);
266
+ }
267
+ const middleware = [];
268
+ const bindBuiltin = (ruleName, match, methodFilter, position) => {
269
+ middleware.push({
270
+ path: match,
271
+ position,
272
+ handler: async (_ctx, request, next) => {
273
+ const req = request.request;
274
+ if (!(req instanceof Request))
275
+ throw Errors.badRequest("PluginRequestContext.request is required");
276
+ if (methodFilter && !methodFilter.includes(req.method)) {
277
+ await next();
278
+ return;
279
+ }
280
+ const { fortressUserId: userId } = request;
281
+ await check(ruleName, { ip: ipFromRequest(req), userId });
282
+ await next();
283
+ }
284
+ });
285
+ };
286
+ if (oauthTokenCfg)
287
+ bindBuiltin("oauthToken", "/oauth/token", ["POST"], "before-auth");
288
+ if (apiKeyIssueCfg)
289
+ bindBuiltin("apiKeyIssue", "/api-key/keys", ["POST"], "after-auth");
290
+ for (const binding of config.paths ?? []) {
291
+ const inlineRule = typeof binding.rule === "object" ? binding.rule : null;
292
+ const ruleName = typeof binding.rule === "string" ? binding.rule : `path:${binding.match}`;
293
+ if (inlineRule)
294
+ rules[ruleName] = inlineRule;
295
+ else if (!rules[ruleName])
296
+ throw new Error(`rate-limit: unknown rule reference '${binding.rule}' in paths config`);
297
+ const position = binding.position ?? "before-auth";
298
+ const methodFilter = binding.methods?.map((m) => m.toUpperCase());
299
+ middleware.push({
300
+ path: binding.match,
301
+ position,
302
+ handler: async (_ctx, request, next) => {
303
+ const req = request.request;
304
+ if (!(req instanceof Request))
305
+ throw Errors.badRequest("PluginRequestContext.request is required");
306
+ if (methodFilter && !methodFilter.includes(req.method)) {
307
+ await next();
308
+ return;
309
+ }
310
+ const { fortressUserId: userId } = request;
311
+ await check(ruleName, { ip: ipFromRequest(req), userId });
312
+ await next();
313
+ }
314
+ });
315
+ }
316
+ return {
317
+ name: "rate-limit",
318
+ ...middleware.length > 0 ? { middleware } : {},
319
+ hooks: {
320
+ async beforeLogin(ctx) {
321
+ if (!loginCfg)
322
+ return;
323
+ const ip = ctx.meta?.ipAddress;
324
+ const email = ctx.email;
325
+ if (loginCfg.maxPerIp != null)
326
+ await check("login", { ip });
327
+ if (loginCfg.maxPerAccount != null)
328
+ await check("login:account", { userId: normalizeAccountIdentifier(email) });
329
+ },
330
+ async beforeRegister(ctx) {
331
+ if (!registerCfg)
332
+ return;
333
+ await check("register", { ip: ctx.meta?.ipAddress });
334
+ },
335
+ async beforeTokenRefresh(ctx) {
336
+ if (!refreshCfg)
337
+ return;
338
+ const ip = ctx.meta?.ipAddress;
339
+ await check("refresh", { ip });
340
+ }
341
+ },
342
+ methods: (_ctx) => ({
343
+ check,
344
+ /** Read-only view of the resolved rule registry (debug / introspection). */
345
+ listRules: () => Object.keys(rules)
346
+ })
347
+ };
348
+ }
349
+ // Annotate the CommonJS export names for ESM import in node:
350
+ 0 && (module.exports = {
351
+ normalizeAccountIdentifier,
352
+ normalizeIp,
353
+ rateLimit
354
+ });
@@ -0,0 +1,140 @@
1
+ import { F as FortressPlugin } from '../config-GtlVt6ZD.cjs';
2
+ import '../index-CxEkfCA0.cjs';
3
+ import '../jwt.cjs';
4
+ import '../types-et0R8tsC.cjs';
5
+ import '../auth-service-BcyQ2PuZ.cjs';
6
+
7
+ interface RateLimitStore {
8
+ /** Increment the counter for a key within a sliding window. Returns current count and reset time. */
9
+ increment: (key: string, windowMs: number) => Promise<{
10
+ count: number;
11
+ resetAt: number;
12
+ }>;
13
+ /** Get the current counter for the most recently used window. */
14
+ get: (key: string) => Promise<{
15
+ count: number;
16
+ resetAt: number;
17
+ } | null>;
18
+ }
19
+
20
+ /**
21
+ * Sliding-window rate limiting plugin for fortress.
22
+ *
23
+ * Protects built-in Fortress auth endpoints (`login`, `register`, `refresh`,
24
+ * plus OAuth token issuance and API-key creation when those plugins are
25
+ * mounted) and exposes a `check()` method + per-framework middleware
26
+ * wrappers so consumers can rate-limit any of their own routes.
27
+ *
28
+ * Ships with an in-memory store; bring-your-own store via the
29
+ * {@link RateLimitStore} interface for distributed deployments (Redis, etc).
30
+ *
31
+ * @module
32
+ */
33
+
34
+ /**
35
+ * A single rate-limit rule. At least one of `maxPerIp` / `maxPerUser` must be
36
+ * set; if neither is set the rule is a no-op. Keys are checked independently
37
+ * and the first to exceed short-circuits with a `rateLimited` error.
38
+ */
39
+ interface RateLimitRule {
40
+ /** Max requests per client IP in the window. */
41
+ maxPerIp?: number;
42
+ /** Max requests per authenticated user in the window. Ignored if the check has no userId. */
43
+ maxPerUser?: number;
44
+ /** Sliding window length in seconds. */
45
+ windowSeconds: number;
46
+ /** Optional key-namespace override. Defaults to the rule name. */
47
+ keyPrefix?: string;
48
+ }
49
+ /** Rate-limit config block for login (per-account adds an email-scoped key). */
50
+ interface LoginRateLimit {
51
+ maxPerIp?: number;
52
+ maxPerAccount?: number;
53
+ windowSeconds?: number;
54
+ }
55
+ interface SimpleRateLimit {
56
+ maxPerIp?: number;
57
+ maxPerUser?: number;
58
+ windowSeconds?: number;
59
+ }
60
+ /** Sentinel passed to the `login` / `register` blocks to opt out of the always-on defaults. */
61
+ interface DisabledBlock {
62
+ disabled: true;
63
+ }
64
+ interface RateLimitConfig {
65
+ /**
66
+ * Limit for `POST /auth/login`. **Always on with defaults** when the plugin
67
+ * is registered — pass `{ disabled: true }` to turn it off. This is a gate
68
+ * plugin; leaving the default protects against silent loss of auth DoS
69
+ * protection on upgrade.
70
+ */
71
+ login?: LoginRateLimit | DisabledBlock;
72
+ /**
73
+ * Limit for `POST /auth/register`. Same default-on / explicit-disable
74
+ * behavior as `login` — both are security-critical gates.
75
+ */
76
+ register?: {
77
+ maxPerIp?: number;
78
+ windowSeconds?: number;
79
+ } | DisabledBlock;
80
+ /** Limit for `POST /auth/refresh` (runs in `beforeTokenRefresh` hook). */
81
+ refresh?: SimpleRateLimit;
82
+ /** Limit for OAuth `POST /oauth/token` (IP-scoped; client_id lives in the form body). */
83
+ oauthToken?: {
84
+ maxPerIp?: number;
85
+ windowSeconds?: number;
86
+ };
87
+ /** Limit for API-key issuance `POST /api-key/keys` (requires authenticated user). */
88
+ apiKeyIssue?: SimpleRateLimit;
89
+ /**
90
+ * Named rules referenced by `methods.check(name, keys)` and the
91
+ * per-framework middleware wrappers. Use these for your own app routes
92
+ * or for Fortress endpoints that don't have a dedicated config block
93
+ * (magic-link, 2FA verify, email verification — these are methods-only,
94
+ * so wire the limiter in your own handler).
95
+ */
96
+ rules?: Record<string, RateLimitRule>;
97
+ /**
98
+ * Extra path-based bindings for any route served by `fortress.handleRequest`.
99
+ * Each entry binds a named rule (or inline rule) to a path glob.
100
+ */
101
+ paths?: PathBinding[];
102
+ /** Custom store for rate-limit counters. Default: in-memory sliding window. */
103
+ store?: RateLimitStore;
104
+ }
105
+ /**
106
+ * Declarative rate-limit binding for a Fortress-dispatched path. Only fires
107
+ * for routes that flow through `fortress.handleRequest`.
108
+ *
109
+ * **When to use `paths` vs the framework wrappers**:
110
+ * - Framework wrappers (`honoRateLimit` / `expressRateLimit` /
111
+ * `svelteKitRateLimit`) — use when you have a middleware layer around your
112
+ * routes. Mount on any path, Fortress-owned or user-owned.
113
+ * - `paths` — use in serverless / framework-less deployments that call
114
+ * `fortress.handleRequest` directly, or when you prefer declarative config
115
+ * co-located with the rest of the rate-limit setup.
116
+ *
117
+ * Both target the same store. **Don't stack both on the same path** — each
118
+ * match increments the counter, so double-wrapping halves the effective
119
+ * limit.
120
+ */
121
+ interface PathBinding {
122
+ /** Path glob (supports `*` and `:param`). */
123
+ match: string;
124
+ /** HTTP methods to restrict to (uppercase). Omit to match all methods. */
125
+ methods?: string[];
126
+ /** Pipeline phase. `after-auth` required when keying per user. Default: `before-auth`. */
127
+ position?: 'before-auth' | 'after-auth';
128
+ /** Named rule key in `config.rules`, or an inline rule. */
129
+ rule: string | RateLimitRule;
130
+ }
131
+ /**
132
+ * Normalize an IPv6 address to its /64 prefix to prevent bypass via address rotation.
133
+ * IPv4 addresses are returned as-is.
134
+ */
135
+ declare function normalizeIp(ip: string | undefined | null): string;
136
+ /** Canonical key for account-scoped login limits. */
137
+ declare function normalizeAccountIdentifier(identifier: string): string;
138
+ declare function rateLimit(config?: RateLimitConfig): FortressPlugin;
139
+
140
+ export { type DisabledBlock, type LoginRateLimit, type PathBinding, type RateLimitConfig, type RateLimitRule, type RateLimitStore, type SimpleRateLimit, normalizeAccountIdentifier, normalizeIp, rateLimit };
@@ -0,0 +1,140 @@
1
+ import { F as FortressPlugin } from '../config-B2366s_G.js';
2
+ import '../index-CxEkfCA0.js';
3
+ import '../jwt.js';
4
+ import '../types-et0R8tsC.js';
5
+ import '../auth-service-BkzZkWfH.js';
6
+
7
+ interface RateLimitStore {
8
+ /** Increment the counter for a key within a sliding window. Returns current count and reset time. */
9
+ increment: (key: string, windowMs: number) => Promise<{
10
+ count: number;
11
+ resetAt: number;
12
+ }>;
13
+ /** Get the current counter for the most recently used window. */
14
+ get: (key: string) => Promise<{
15
+ count: number;
16
+ resetAt: number;
17
+ } | null>;
18
+ }
19
+
20
+ /**
21
+ * Sliding-window rate limiting plugin for fortress.
22
+ *
23
+ * Protects built-in Fortress auth endpoints (`login`, `register`, `refresh`,
24
+ * plus OAuth token issuance and API-key creation when those plugins are
25
+ * mounted) and exposes a `check()` method + per-framework middleware
26
+ * wrappers so consumers can rate-limit any of their own routes.
27
+ *
28
+ * Ships with an in-memory store; bring-your-own store via the
29
+ * {@link RateLimitStore} interface for distributed deployments (Redis, etc).
30
+ *
31
+ * @module
32
+ */
33
+
34
+ /**
35
+ * A single rate-limit rule. At least one of `maxPerIp` / `maxPerUser` must be
36
+ * set; if neither is set the rule is a no-op. Keys are checked independently
37
+ * and the first to exceed short-circuits with a `rateLimited` error.
38
+ */
39
+ interface RateLimitRule {
40
+ /** Max requests per client IP in the window. */
41
+ maxPerIp?: number;
42
+ /** Max requests per authenticated user in the window. Ignored if the check has no userId. */
43
+ maxPerUser?: number;
44
+ /** Sliding window length in seconds. */
45
+ windowSeconds: number;
46
+ /** Optional key-namespace override. Defaults to the rule name. */
47
+ keyPrefix?: string;
48
+ }
49
+ /** Rate-limit config block for login (per-account adds an email-scoped key). */
50
+ interface LoginRateLimit {
51
+ maxPerIp?: number;
52
+ maxPerAccount?: number;
53
+ windowSeconds?: number;
54
+ }
55
+ interface SimpleRateLimit {
56
+ maxPerIp?: number;
57
+ maxPerUser?: number;
58
+ windowSeconds?: number;
59
+ }
60
+ /** Sentinel passed to the `login` / `register` blocks to opt out of the always-on defaults. */
61
+ interface DisabledBlock {
62
+ disabled: true;
63
+ }
64
+ interface RateLimitConfig {
65
+ /**
66
+ * Limit for `POST /auth/login`. **Always on with defaults** when the plugin
67
+ * is registered — pass `{ disabled: true }` to turn it off. This is a gate
68
+ * plugin; leaving the default protects against silent loss of auth DoS
69
+ * protection on upgrade.
70
+ */
71
+ login?: LoginRateLimit | DisabledBlock;
72
+ /**
73
+ * Limit for `POST /auth/register`. Same default-on / explicit-disable
74
+ * behavior as `login` — both are security-critical gates.
75
+ */
76
+ register?: {
77
+ maxPerIp?: number;
78
+ windowSeconds?: number;
79
+ } | DisabledBlock;
80
+ /** Limit for `POST /auth/refresh` (runs in `beforeTokenRefresh` hook). */
81
+ refresh?: SimpleRateLimit;
82
+ /** Limit for OAuth `POST /oauth/token` (IP-scoped; client_id lives in the form body). */
83
+ oauthToken?: {
84
+ maxPerIp?: number;
85
+ windowSeconds?: number;
86
+ };
87
+ /** Limit for API-key issuance `POST /api-key/keys` (requires authenticated user). */
88
+ apiKeyIssue?: SimpleRateLimit;
89
+ /**
90
+ * Named rules referenced by `methods.check(name, keys)` and the
91
+ * per-framework middleware wrappers. Use these for your own app routes
92
+ * or for Fortress endpoints that don't have a dedicated config block
93
+ * (magic-link, 2FA verify, email verification — these are methods-only,
94
+ * so wire the limiter in your own handler).
95
+ */
96
+ rules?: Record<string, RateLimitRule>;
97
+ /**
98
+ * Extra path-based bindings for any route served by `fortress.handleRequest`.
99
+ * Each entry binds a named rule (or inline rule) to a path glob.
100
+ */
101
+ paths?: PathBinding[];
102
+ /** Custom store for rate-limit counters. Default: in-memory sliding window. */
103
+ store?: RateLimitStore;
104
+ }
105
+ /**
106
+ * Declarative rate-limit binding for a Fortress-dispatched path. Only fires
107
+ * for routes that flow through `fortress.handleRequest`.
108
+ *
109
+ * **When to use `paths` vs the framework wrappers**:
110
+ * - Framework wrappers (`honoRateLimit` / `expressRateLimit` /
111
+ * `svelteKitRateLimit`) — use when you have a middleware layer around your
112
+ * routes. Mount on any path, Fortress-owned or user-owned.
113
+ * - `paths` — use in serverless / framework-less deployments that call
114
+ * `fortress.handleRequest` directly, or when you prefer declarative config
115
+ * co-located with the rest of the rate-limit setup.
116
+ *
117
+ * Both target the same store. **Don't stack both on the same path** — each
118
+ * match increments the counter, so double-wrapping halves the effective
119
+ * limit.
120
+ */
121
+ interface PathBinding {
122
+ /** Path glob (supports `*` and `:param`). */
123
+ match: string;
124
+ /** HTTP methods to restrict to (uppercase). Omit to match all methods. */
125
+ methods?: string[];
126
+ /** Pipeline phase. `after-auth` required when keying per user. Default: `before-auth`. */
127
+ position?: 'before-auth' | 'after-auth';
128
+ /** Named rule key in `config.rules`, or an inline rule. */
129
+ rule: string | RateLimitRule;
130
+ }
131
+ /**
132
+ * Normalize an IPv6 address to its /64 prefix to prevent bypass via address rotation.
133
+ * IPv4 addresses are returned as-is.
134
+ */
135
+ declare function normalizeIp(ip: string | undefined | null): string;
136
+ /** Canonical key for account-scoped login limits. */
137
+ declare function normalizeAccountIdentifier(identifier: string): string;
138
+ declare function rateLimit(config?: RateLimitConfig): FortressPlugin;
139
+
140
+ export { type DisabledBlock, type LoginRateLimit, type PathBinding, type RateLimitConfig, type RateLimitRule, type RateLimitStore, type SimpleRateLimit, normalizeAccountIdentifier, normalizeIp, rateLimit };