@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.
- package/CHANGELOG.md +1011 -0
- package/LICENSE +21 -0
- package/README.md +760 -0
- package/SECURITY.md +197 -0
- package/bin/fortress.ts +667 -0
- package/dist/auth-service-BcyQ2PuZ.d.cts +307 -0
- package/dist/auth-service-BkzZkWfH.d.ts +307 -0
- package/dist/config-B2366s_G.d.ts +665 -0
- package/dist/config-GtlVt6ZD.d.cts +665 -0
- package/dist/convert-routes-BLCQT57f.d.ts +72 -0
- package/dist/convert-routes-BdMvP2_X.d.cts +72 -0
- package/dist/crypto.cjs +61 -0
- package/dist/crypto.d.cts +36 -0
- package/dist/crypto.d.ts +36 -0
- package/dist/crypto.js +35 -0
- package/dist/drift-BT1wtMDJ.d.cts +22 -0
- package/dist/drift-k9LiYpRP.d.ts +22 -0
- package/dist/drizzle/pg.cjs +481 -0
- package/dist/drizzle/pg.d.cts +15 -0
- package/dist/drizzle/pg.d.ts +15 -0
- package/dist/drizzle/pg.js +454 -0
- package/dist/drizzle.cjs +1442 -0
- package/dist/drizzle.d.cts +85 -0
- package/dist/drizzle.d.ts +85 -0
- package/dist/drizzle.js +1411 -0
- package/dist/express.cjs +1357 -0
- package/dist/express.d.cts +333 -0
- package/dist/express.d.ts +333 -0
- package/dist/express.js +1314 -0
- package/dist/fetcher.cjs +77 -0
- package/dist/fetcher.d.cts +7 -0
- package/dist/fetcher.d.ts +7 -0
- package/dist/fetcher.js +41 -0
- package/dist/fortress-BmSvFfqK.d.cts +1089 -0
- package/dist/fortress-uA-_cheR.d.ts +1089 -0
- package/dist/hono.cjs +1530 -0
- package/dist/hono.d.cts +514 -0
- package/dist/hono.d.ts +514 -0
- package/dist/hono.js +1483 -0
- package/dist/index-CxEkfCA0.d.cts +77 -0
- package/dist/index-CxEkfCA0.d.ts +77 -0
- package/dist/index-D054pcb-.d.cts +146 -0
- package/dist/index-jAMbbUAY.d.ts +146 -0
- package/dist/index.cjs +9046 -0
- package/dist/index.d.cts +717 -0
- package/dist/index.d.ts +717 -0
- package/dist/index.js +8935 -0
- package/dist/jwt.cjs +255 -0
- package/dist/jwt.d.cts +90 -0
- package/dist/jwt.d.ts +90 -0
- package/dist/jwt.js +227 -0
- package/dist/otel.cjs +108 -0
- package/dist/otel.d.cts +62 -0
- package/dist/otel.d.ts +62 -0
- package/dist/otel.js +73 -0
- package/dist/plugins/account-lockout.cjs +322 -0
- package/dist/plugins/account-lockout.d.cts +42 -0
- package/dist/plugins/account-lockout.d.ts +42 -0
- package/dist/plugins/account-lockout.js +295 -0
- package/dist/plugins/admin.cjs +2378 -0
- package/dist/plugins/admin.d.cts +72 -0
- package/dist/plugins/admin.d.ts +72 -0
- package/dist/plugins/admin.js +2351 -0
- package/dist/plugins/api-key.cjs +792 -0
- package/dist/plugins/api-key.d.cts +121 -0
- package/dist/plugins/api-key.d.ts +121 -0
- package/dist/plugins/api-key.js +765 -0
- package/dist/plugins/audit-log.cjs +493 -0
- package/dist/plugins/audit-log.d.cts +86 -0
- package/dist/plugins/audit-log.d.ts +86 -0
- package/dist/plugins/audit-log.js +468 -0
- package/dist/plugins/data-isolation.cjs +211 -0
- package/dist/plugins/data-isolation.d.cts +49 -0
- package/dist/plugins/data-isolation.d.ts +49 -0
- package/dist/plugins/data-isolation.js +186 -0
- package/dist/plugins/email-verification.cjs +273 -0
- package/dist/plugins/email-verification.d.cts +44 -0
- package/dist/plugins/email-verification.d.ts +44 -0
- package/dist/plugins/email-verification.js +246 -0
- package/dist/plugins/magic-link.cjs +231 -0
- package/dist/plugins/magic-link.d.cts +39 -0
- package/dist/plugins/magic-link.d.ts +39 -0
- package/dist/plugins/magic-link.js +204 -0
- package/dist/plugins/oauth.cjs +1696 -0
- package/dist/plugins/oauth.d.cts +406 -0
- package/dist/plugins/oauth.d.ts +406 -0
- package/dist/plugins/oauth.js +1669 -0
- package/dist/plugins/openapi.cjs +1185 -0
- package/dist/plugins/openapi.d.cts +6 -0
- package/dist/plugins/openapi.d.ts +6 -0
- package/dist/plugins/openapi.js +1158 -0
- package/dist/plugins/rate-limit/express.cjs +55 -0
- package/dist/plugins/rate-limit/express.d.cts +64 -0
- package/dist/plugins/rate-limit/express.d.ts +64 -0
- package/dist/plugins/rate-limit/express.js +30 -0
- package/dist/plugins/rate-limit/hono.cjs +51 -0
- package/dist/plugins/rate-limit/hono.d.cts +59 -0
- package/dist/plugins/rate-limit/hono.d.ts +59 -0
- package/dist/plugins/rate-limit/hono.js +26 -0
- package/dist/plugins/rate-limit/sveltekit.cjs +49 -0
- package/dist/plugins/rate-limit/sveltekit.d.cts +59 -0
- package/dist/plugins/rate-limit/sveltekit.d.ts +59 -0
- package/dist/plugins/rate-limit/sveltekit.js +24 -0
- package/dist/plugins/rate-limit.cjs +354 -0
- package/dist/plugins/rate-limit.d.cts +140 -0
- package/dist/plugins/rate-limit.d.ts +140 -0
- package/dist/plugins/rate-limit.js +325 -0
- package/dist/plugins/social-login.cjs +905 -0
- package/dist/plugins/social-login.d.cts +114 -0
- package/dist/plugins/social-login.d.ts +114 -0
- package/dist/plugins/social-login.js +880 -0
- package/dist/plugins/tenancy.cjs +780 -0
- package/dist/plugins/tenancy.d.cts +108 -0
- package/dist/plugins/tenancy.d.ts +108 -0
- package/dist/plugins/tenancy.js +753 -0
- package/dist/plugins/two-factor.cjs +555 -0
- package/dist/plugins/two-factor.d.cts +67 -0
- package/dist/plugins/two-factor.d.ts +67 -0
- package/dist/plugins/two-factor.js +526 -0
- package/dist/plugins/webauthn.cjs +786 -0
- package/dist/plugins/webauthn.d.cts +72 -0
- package/dist/plugins/webauthn.d.ts +72 -0
- package/dist/plugins/webauthn.js +766 -0
- package/dist/plugins/webhook.cjs +819 -0
- package/dist/plugins/webhook.d.cts +254 -0
- package/dist/plugins/webhook.d.ts +254 -0
- package/dist/plugins/webhook.js +789 -0
- package/dist/protect-D1v_0upK.d.cts +123 -0
- package/dist/protect-DsxV_-dJ.d.ts +123 -0
- package/dist/sveltekit.cjs +1258 -0
- package/dist/sveltekit.d.cts +420 -0
- package/dist/sveltekit.d.ts +420 -0
- package/dist/sveltekit.js +1207 -0
- package/dist/testing.cjs +4294 -0
- package/dist/testing.d.cts +197 -0
- package/dist/testing.d.ts +197 -0
- package/dist/testing.js +4264 -0
- package/dist/types-et0R8tsC.d.cts +217 -0
- package/dist/types-et0R8tsC.d.ts +217 -0
- package/docs/adapter-typed-helpers.md +192 -0
- package/docs/admin-recipes.md +227 -0
- package/docs/architecture.md +434 -0
- package/docs/ci/github-actions.yml +59 -0
- package/docs/ci.md +109 -0
- package/docs/compatibility.md +115 -0
- package/docs/deployment.md +305 -0
- package/docs/hardening.md +118 -0
- package/docs/host-owned-routes.md +154 -0
- package/docs/migrations/0001-schema-version.md +54 -0
- package/docs/migrations/0002-initial-schema.md +84 -0
- package/docs/migrations/upgrade-guide.md +160 -0
- package/docs/observability.md +394 -0
- package/docs/plugins/account-lockout.md +131 -0
- package/docs/plugins/admin.md +402 -0
- package/docs/plugins/api-key.md +327 -0
- package/docs/plugins/audit-log.md +261 -0
- package/docs/plugins/data-isolation.md +186 -0
- package/docs/plugins/email-verification.md +121 -0
- package/docs/plugins/magic-link.md +123 -0
- package/docs/plugins/oauth.md +544 -0
- package/docs/plugins/openapi.md +250 -0
- package/docs/plugins/rate-limit.md +223 -0
- package/docs/plugins/social-login.md +337 -0
- package/docs/plugins/tenancy.md +122 -0
- package/docs/plugins/two-factor.md +191 -0
- package/docs/plugins/webauthn.md +188 -0
- package/docs/plugins/webhook.md +242 -0
- package/docs/policy-as-code.md +156 -0
- package/docs/route-manifest.md +46 -0
- package/docs/security.md +261 -0
- package/docs/threat-model.md +161 -0
- package/examples/README.md +119 -0
- package/examples/express-app/index.ts +747 -0
- package/examples/hono-app/docker-compose.yml +14 -0
- package/examples/hono-app/index.ts +1009 -0
- package/examples/policy/fortress.policy.json +47 -0
- package/examples/sveltekit-app/README.md +125 -0
- package/examples/sveltekit-app/src/app.d.ts +16 -0
- package/examples/sveltekit-app/src/hooks.server.ts +23 -0
- package/examples/sveltekit-app/src/lib/server/fortress.ts +81 -0
- package/examples/sveltekit-app/src/routes/api/echo/[id]/+server.ts +32 -0
- package/examples/sveltekit-app/src/routes/api/fortress/[...path]/+server.ts +15 -0
- package/examples/sveltekit-app/src/routes/dashboard/+page.server.ts +20 -0
- package/examples/sveltekit-app/src/routes/login/+page.server.ts +48 -0
- package/examples/sveltekit-app/src/routes/oauth/consent/+page.server.ts +69 -0
- package/examples/sveltekit-app/src/routes/oauth/consent/+page.svelte +83 -0
- package/migrations/pg/0001_schema_version.down.sql +2 -0
- package/migrations/pg/0001_schema_version.sql +8 -0
- package/migrations/pg/0002_initial_schema.down.sql +36 -0
- package/migrations/pg/0002_initial_schema.sql +376 -0
- package/migrations/pg/0003_auth_continuation.down.sql +6 -0
- package/migrations/pg/0003_auth_continuation.sql +30 -0
- package/migrations/pg/0004_tenant_default_unique.down.sql +1 -0
- package/migrations/pg/0004_tenant_default_unique.sql +14 -0
- package/migrations/pg/0005_hot_indexes_timestamptz.down.sql +71 -0
- package/migrations/pg/0005_hot_indexes_timestamptz.sql +71 -0
- package/migrations/pg/0006_canonical_email.down.sql +3 -0
- package/migrations/pg/0006_canonical_email.sql +8 -0
- package/migrations/pg/0007_audit_chain_anchor.down.sql +1 -0
- package/migrations/pg/0007_audit_chain_anchor.sql +8 -0
- package/migrations/pg/0008_two_factor_hardening.down.sql +8 -0
- package/migrations/pg/0008_two_factor_hardening.sql +8 -0
- package/migrations/pg/0009_encrypt_totp_secrets.down.sql +2 -0
- package/migrations/pg/0009_encrypt_totp_secrets.sql +5 -0
- package/migrations/pg/0010_bigint_append_only_ids.down.sql +2 -0
- package/migrations/pg/0010_bigint_append_only_ids.sql +23 -0
- package/migrations/sqlite/0001_schema_version.down.sql +2 -0
- package/migrations/sqlite/0001_schema_version.sql +8 -0
- package/migrations/sqlite/0002_initial_schema.down.sql +36 -0
- package/migrations/sqlite/0002_initial_schema.sql +376 -0
- package/migrations/sqlite/0003_auth_continuation.down.sql +27 -0
- package/migrations/sqlite/0003_auth_continuation.sql +45 -0
- package/migrations/sqlite/0004_tenant_default_unique.down.sql +1 -0
- package/migrations/sqlite/0004_tenant_default_unique.sql +13 -0
- package/migrations/sqlite/0005_hot_indexes_timestamptz.down.sql +10 -0
- package/migrations/sqlite/0005_hot_indexes_timestamptz.sql +10 -0
- package/migrations/sqlite/0006_canonical_email.down.sql +3 -0
- package/migrations/sqlite/0006_canonical_email.sql +8 -0
- package/migrations/sqlite/0007_audit_chain_anchor.down.sql +1 -0
- package/migrations/sqlite/0007_audit_chain_anchor.sql +8 -0
- package/migrations/sqlite/0008_two_factor_hardening.down.sql +14 -0
- package/migrations/sqlite/0008_two_factor_hardening.sql +7 -0
- package/migrations/sqlite/0009_encrypt_totp_secrets.down.sql +2 -0
- package/migrations/sqlite/0009_encrypt_totp_secrets.sql +5 -0
- package/migrations/sqlite/0010_bigint_append_only_ids.down.sql +1 -0
- package/migrations/sqlite/0010_bigint_append_only_ids.sql +2 -0
- package/package.json +398 -0
- package/src/adapters/database/index.ts +63 -0
- package/src/adapters/database/types.ts +22 -0
- package/src/changelog-script.test.ts +21 -0
- package/src/cli.test.ts +79 -0
- package/src/core/auth/auth-admin.test.ts +247 -0
- package/src/core/auth/auth-endpoints.ts +558 -0
- package/src/core/auth/auth-observer.test.ts +191 -0
- package/src/core/auth/auth-service.ts +1464 -0
- package/src/core/auth/email.test.ts +17 -0
- package/src/core/auth/email.ts +11 -0
- package/src/core/auth/hooks.test.ts +251 -0
- package/src/core/auth/impersonation.test.ts +179 -0
- package/src/core/auth/jwt.test.ts +184 -0
- package/src/core/auth/jwt.ts +239 -0
- package/src/core/auth/login-timing.test.ts +77 -0
- package/src/core/auth/password-policy.test.ts +253 -0
- package/src/core/auth/password-policy.ts +220 -0
- package/src/core/auth/password.test.ts +42 -0
- package/src/core/auth/password.ts +62 -0
- package/src/core/auth/post-auth-gate.test.ts +258 -0
- package/src/core/auth/post-auth-gate.ts +228 -0
- package/src/core/auth/refresh-token.test.ts +86 -0
- package/src/core/auth/refresh-token.ts +105 -0
- package/src/core/auth/timing-safe.ts +23 -0
- package/src/core/config.ts +212 -0
- package/src/core/endpoint.ts +149 -0
- package/src/core/errors.ts +199 -0
- package/src/core/fetcher-interop.test.ts +106 -0
- package/src/core/fortress.test.ts +354 -0
- package/src/core/fortress.ts +550 -0
- package/src/core/http/call.test.ts +148 -0
- package/src/core/http/call.ts +149 -0
- package/src/core/http/cookie-serialize.test.ts +198 -0
- package/src/core/http/cookie-serialize.ts +107 -0
- package/src/core/http/csrf.test.ts +140 -0
- package/src/core/http/csrf.ts +173 -0
- package/src/core/http/dispatch.ts +652 -0
- package/src/core/http/error-response.test.ts +69 -0
- package/src/core/http/error-response.ts +93 -0
- package/src/core/http/fortress-rbac.test.ts +43 -0
- package/src/core/http/fortress-rbac.ts +127 -0
- package/src/core/http/handle-request.test.ts +441 -0
- package/src/core/http/handle-request.ts +354 -0
- package/src/core/http/match.test.ts +122 -0
- package/src/core/http/match.ts +126 -0
- package/src/core/http/outbound.test.ts +34 -0
- package/src/core/http/outbound.ts +105 -0
- package/src/core/http/plugin-middleware.ts +67 -0
- package/src/core/http/principal.test.ts +345 -0
- package/src/core/http/principal.ts +86 -0
- package/src/core/http/protect.test.ts +220 -0
- package/src/core/http/protect.ts +394 -0
- package/src/core/http/token-extraction.test.ts +59 -0
- package/src/core/http/token-extraction.ts +53 -0
- package/src/core/iam/explain.test.ts +177 -0
- package/src/core/iam/explain.ts +302 -0
- package/src/core/iam/iam-endpoints.ts +779 -0
- package/src/core/iam/iam-service.test.ts +829 -0
- package/src/core/iam/iam-service.ts +1137 -0
- package/src/core/iam/permission-cache.test.ts +115 -0
- package/src/core/iam/permission-cache.ts +71 -0
- package/src/core/iam/permission-check-observer.test.ts +90 -0
- package/src/core/iam/permission-evaluator.test.ts +291 -0
- package/src/core/iam/permission-evaluator.ts +198 -0
- package/src/core/iam/permission-sync.test.ts +166 -0
- package/src/core/iam/permission-sync.ts +192 -0
- package/src/core/iam/resource-sync.test.ts +70 -0
- package/src/core/iam/resource-sync.ts +182 -0
- package/src/core/iam/service-account-http.test.ts +529 -0
- package/src/core/iam/service-account.test.ts +282 -0
- package/src/core/internal-adapter.test.ts +389 -0
- package/src/core/internal-adapter.ts +435 -0
- package/src/core/json-schema.ts +50 -0
- package/src/core/manifest/__snapshots__/route-manifest.test.ts.snap +192 -0
- package/src/core/manifest/drift.ts +103 -0
- package/src/core/manifest/route-manifest.test.ts +142 -0
- package/src/core/manifest/route-manifest.ts +154 -0
- package/src/core/migrate.test.ts +72 -0
- package/src/core/migrations/engine.test.ts +308 -0
- package/src/core/migrations/engine.ts +548 -0
- package/src/core/migrations/migrations.ts +1771 -0
- package/src/core/migrations/pg-migration.integration-test.ts +353 -0
- package/src/core/migrations/upgrade-fixture.test.ts +378 -0
- package/src/core/observability/db-instrumentation.ts +205 -0
- package/src/core/observability/listener-list.test.ts +124 -0
- package/src/core/observability/listener-list.ts +73 -0
- package/src/core/observability/logger.test.ts +43 -0
- package/src/core/observability/logger.ts +49 -0
- package/src/core/observability/spans.test.ts +193 -0
- package/src/core/observability/types.ts +80 -0
- package/src/core/openapi-drift.test.ts +41 -0
- package/src/core/openapi.ts +47 -0
- package/src/core/plugin-methods-map.ts +75 -0
- package/src/core/plugin-runner.test.ts +233 -0
- package/src/core/plugin-runner.ts +276 -0
- package/src/core/plugin.ts +210 -0
- package/src/core/policy/apply.ts +272 -0
- package/src/core/policy/diff.ts +288 -0
- package/src/core/policy/loader.test.ts +63 -0
- package/src/core/policy/loader.ts +214 -0
- package/src/core/policy/policy.test.ts +232 -0
- package/src/core/policy/types.ts +105 -0
- package/src/core/schema-builder.test.ts +660 -0
- package/src/core/schema-builder.ts +881 -0
- package/src/core/standard-schema.ts +56 -0
- package/src/core/types.ts +258 -0
- package/src/core/validation.test.ts +195 -0
- package/src/core/validation.ts +192 -0
- package/src/drizzle/adapter.test.ts +425 -0
- package/src/drizzle/adapter.ts +474 -0
- package/src/drizzle/index.ts +31 -0
- package/src/drizzle/pg/adapter.integration-test.ts +456 -0
- package/src/drizzle/pg/index.ts +13 -0
- package/src/drizzle/pg/pg.integration-test.ts +1539 -0
- package/src/drizzle/pg/schema.ts +539 -0
- package/src/drizzle/pg-error-map.test.ts +218 -0
- package/src/drizzle/pg-error-map.ts +151 -0
- package/src/drizzle/schema.ts +544 -0
- package/src/drizzle/sqlite-serialization.test.ts +106 -0
- package/src/express/express-api-key-user-routes.test.ts +241 -0
- package/src/express/express.test.ts +442 -0
- package/src/express/handle.ts +191 -0
- package/src/express/index.ts +62 -0
- package/src/express/middleware.ts +551 -0
- package/src/express/protect.ts +154 -0
- package/src/express/validated.test.ts +86 -0
- package/src/express/validated.ts +99 -0
- package/src/fetcher/index.ts +81 -0
- package/src/hono/convert-routes.test.ts +220 -0
- package/src/hono/convert-routes.ts +196 -0
- package/src/hono/converters.test.ts +50 -0
- package/src/hono/converters.ts +43 -0
- package/src/hono/handle.ts +79 -0
- package/src/hono/helpers.ts +2 -0
- package/src/hono/hono-api-key-user-routes.test.ts +225 -0
- package/src/hono/hono-handlers.test.ts +861 -0
- package/src/hono/hono.test.ts +454 -0
- package/src/hono/index.ts +101 -0
- package/src/hono/middleware/auth.ts +236 -0
- package/src/hono/middleware/auth.types.test.ts +94 -0
- package/src/hono/middleware/csrf.test.ts +127 -0
- package/src/hono/middleware/csrf.ts +68 -0
- package/src/hono/middleware/error-handler.ts +12 -0
- package/src/hono/middleware/plugin-middleware.ts +35 -0
- package/src/hono/middleware/rbac.ts +131 -0
- package/src/hono/middleware/security-headers.test.ts +88 -0
- package/src/hono/middleware/security-headers.ts +68 -0
- package/src/hono/openapi.ts +237 -0
- package/src/hono/protect.ts +87 -0
- package/src/hono/validated.test.ts +123 -0
- package/src/hono/validated.ts +62 -0
- package/src/index.ts +309 -0
- package/src/integration.test.ts +718 -0
- package/src/internal/changelog.ts +56 -0
- package/src/otel/index.ts +158 -0
- package/src/otel/otel-types.test.ts +35 -0
- package/src/otel/otel.test.ts +235 -0
- package/src/plugins/account-lockout/account-lockout.test.ts +367 -0
- package/src/plugins/account-lockout/index.ts +267 -0
- package/src/plugins/admin/admin-api-key.test.ts +438 -0
- package/src/plugins/admin/index.ts +1612 -0
- package/src/plugins/api-key/api-key.test.ts +684 -0
- package/src/plugins/api-key/core.ts +336 -0
- package/src/plugins/api-key/index.ts +315 -0
- package/src/plugins/audit-log/audit-log.test.ts +749 -0
- package/src/plugins/audit-log/index.ts +680 -0
- package/src/plugins/data-isolation/data-isolation.test.ts +197 -0
- package/src/plugins/data-isolation/index.ts +157 -0
- package/src/plugins/email-verification/email-verification.test.ts +199 -0
- package/src/plugins/email-verification/index.ts +190 -0
- package/src/plugins/magic-link/index.ts +129 -0
- package/src/plugins/magic-link/magic-link.test.ts +156 -0
- package/src/plugins/oauth/id-token.ts +86 -0
- package/src/plugins/oauth/index.ts +2145 -0
- package/src/plugins/oauth/jwks.test.ts +32 -0
- package/src/plugins/oauth/jwks.ts +182 -0
- package/src/plugins/oauth/oauth.test.ts +2220 -0
- package/src/plugins/oauth/pkce.ts +58 -0
- package/src/plugins/openapi/index.ts +298 -0
- package/src/plugins/openapi/openapi.test.ts +425 -0
- package/src/plugins/openapi/spec-builder.ts +287 -0
- package/src/plugins/rate-limit/express.test.ts +89 -0
- package/src/plugins/rate-limit/express.ts +86 -0
- package/src/plugins/rate-limit/hono.test.ts +60 -0
- package/src/plugins/rate-limit/hono.ts +74 -0
- package/src/plugins/rate-limit/index.ts +383 -0
- package/src/plugins/rate-limit/memory-store.ts +61 -0
- package/src/plugins/rate-limit/rate-limit.test.ts +756 -0
- package/src/plugins/rate-limit/sveltekit.test.ts +58 -0
- package/src/plugins/rate-limit/sveltekit.ts +66 -0
- package/src/plugins/social-login/index.ts +715 -0
- package/src/plugins/social-login/providers/apple.ts +34 -0
- package/src/plugins/social-login/providers/discord.ts +26 -0
- package/src/plugins/social-login/providers/github.ts +54 -0
- package/src/plugins/social-login/providers/google.ts +23 -0
- package/src/plugins/social-login/providers/index.ts +22 -0
- package/src/plugins/social-login/providers/microsoft.ts +35 -0
- package/src/plugins/social-login/providers/oidc.ts +37 -0
- package/src/plugins/social-login/providers/providers.test.ts +211 -0
- package/src/plugins/social-login/social-login.test.ts +675 -0
- package/src/plugins/social-login/types.ts +80 -0
- package/src/plugins/tenancy/index.ts +544 -0
- package/src/plugins/tenancy/tenancy.test.ts +323 -0
- package/src/plugins/two-factor/index.ts +569 -0
- package/src/plugins/two-factor/two-factor.test.ts +235 -0
- package/src/plugins/webauthn/index.ts +554 -0
- package/src/plugins/webauthn/webauthn.test.ts +472 -0
- package/src/plugins/webhook/builtin-events.ts +29 -0
- package/src/plugins/webhook/delivery.test.ts +51 -0
- package/src/plugins/webhook/delivery.ts +154 -0
- package/src/plugins/webhook/hooks.ts +89 -0
- package/src/plugins/webhook/index.ts +445 -0
- package/src/plugins/webhook/queue/database.ts +67 -0
- package/src/plugins/webhook/queue/in-memory.test.ts +48 -0
- package/src/plugins/webhook/queue/in-memory.ts +71 -0
- package/src/plugins/webhook/queue/types.ts +51 -0
- package/src/plugins/webhook/registry.test.ts +48 -0
- package/src/plugins/webhook/registry.ts +64 -0
- package/src/plugins/webhook/signing.ts +45 -0
- package/src/plugins/webhook/ssrf.ts +198 -0
- package/src/plugins/webhook/types.ts +138 -0
- package/src/plugins/webhook/webhook.test.ts +324 -0
- package/src/sveltekit/actions.ts +233 -0
- package/src/sveltekit/catch-all.ts +43 -0
- package/src/sveltekit/cookies.ts +136 -0
- package/src/sveltekit/handle.ts +356 -0
- package/src/sveltekit/helpers.ts +69 -0
- package/src/sveltekit/index.ts +74 -0
- package/src/sveltekit/protect.ts +80 -0
- package/src/sveltekit/sveltekit-types.test.ts +31 -0
- package/src/sveltekit/sveltekit.test.ts +799 -0
- package/src/sveltekit/types.ts +134 -0
- package/src/sveltekit/validated.test.ts +117 -0
- package/src/sveltekit/validated.ts +78 -0
- package/src/testing/adapter-conformance.test.ts +408 -0
- package/src/testing/checks.test.ts +124 -0
- package/src/testing/checks.ts +304 -0
- package/src/testing/index.ts +107 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# OpenAPI Plugin
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `openapi` plugin generates an OpenAPI 3.1 specification from all Fortress endpoints (auth, IAM, plugins, and top-level host `routes`) and serves a Scalar interactive API reference UI. It automatically discovers routes from your Fortress configuration, so the spec stays in sync without manual upkeep.
|
|
6
|
+
|
|
7
|
+
If you only need a spec object for a build script or client code generator, use the standalone `toOpenAPI()` helper instead of installing the plugin. This keeps codegen paths env/DB-free:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { toOpenAPI } from '@bajustone/fortress';
|
|
11
|
+
import { appEndpointList } from './routes/v1/endpoints';
|
|
12
|
+
|
|
13
|
+
const spec = toOpenAPI(appEndpointList, {
|
|
14
|
+
title: 'My API',
|
|
15
|
+
version: '1.0.0',
|
|
16
|
+
servers: [{ url: 'http://localhost:3001', description: 'Local' }],
|
|
17
|
+
tags: [{ name: 'Schools' }],
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If you already have a configured instance, `fortress.toOpenAPI()` emits a spec from `fortress.endpoints` by default.
|
|
22
|
+
|
|
23
|
+
Use the `additionalEndpoints` option with the `convertRoutes` utility to merge routes that are not already registered as Fortress endpoint definitions.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Import the `openapi` factory and pass it in the `plugins` array when creating a Fortress instance:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createFortress } from '@bajustone/fortress';
|
|
31
|
+
import { openapi } from '@bajustone/fortress/plugins/openapi';
|
|
32
|
+
|
|
33
|
+
const fortress = createFortress({
|
|
34
|
+
jwt: { key: 'your-secret-at-least-32-bytes!!' },
|
|
35
|
+
database: adapter,
|
|
36
|
+
plugins: [
|
|
37
|
+
openapi({
|
|
38
|
+
title: 'My API',
|
|
39
|
+
version: '1.0.0',
|
|
40
|
+
description: 'Auth and IAM powered by Fortress',
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Once registered, methods are available at `fortress.plugins['openapi']` with full type safety.
|
|
47
|
+
|
|
48
|
+
This gives you two routes out of the box:
|
|
49
|
+
|
|
50
|
+
- `GET /openapi.json` -- the OpenAPI 3.1 JSON spec
|
|
51
|
+
- `GET /openapi` -- Scalar interactive UI
|
|
52
|
+
|
|
53
|
+
Both routes are public (no authentication required).
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
All fields on `OpenAPIConfig` are optional:
|
|
58
|
+
|
|
59
|
+
| Option | Type | Default | Description |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| `title` | `string` | `'Fortress Auth API'` | API title displayed in the spec and UI. |
|
|
62
|
+
| `version` | `string` | `'1.0.0'` | API version. |
|
|
63
|
+
| `description` | `string` | -- | API description included in the spec's `info` block. |
|
|
64
|
+
| `servers` | `Array<{ url: string; description?: string }>` | -- | Server URL(s) for the spec. |
|
|
65
|
+
| `tags` | `Array<{ name: string; description?: string }>` | -- | Top-level OpenAPI tags. |
|
|
66
|
+
| `operationId` | `'methodPath' \| 'handler' \| ((endpoint) => string \| undefined)` | `'methodPath'` | Operation ID strategy. |
|
|
67
|
+
| `specPath` | `string` | `'/openapi.json'` | Path to serve the JSON spec. |
|
|
68
|
+
| `uiPath` | `string` | `'/openapi'` | Path to serve the Scalar UI. |
|
|
69
|
+
| `disableUI` | `boolean` | `false` | When `true`, only the JSON spec route is registered. |
|
|
70
|
+
| `includeCoreAuth` | `boolean` | `true` | Include core auth endpoints (login, register, refresh, etc.) in the spec. |
|
|
71
|
+
| `includeCoreIam` | `boolean` | `true` | Include core IAM endpoints (roles, groups, permissions, etc.) in the spec. |
|
|
72
|
+
| `additionalSchemas` | `ComponentSchemas` | -- | Extra component schemas to merge into `components.schemas`. |
|
|
73
|
+
| `additionalEndpoints` | `EndpointDefinition[]` | -- | Extra endpoint definitions to include in the spec, typically produced by `convertRoutes`. |
|
|
74
|
+
|
|
75
|
+
## Usage
|
|
76
|
+
|
|
77
|
+
### Basic setup
|
|
78
|
+
|
|
79
|
+
With no options, the plugin includes all core auth endpoints, all core IAM endpoints, and every registered plugin's routes:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
openapi()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Customizing the spec paths
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
openapi({
|
|
89
|
+
specPath: '/api/docs/spec.json',
|
|
90
|
+
uiPath: '/api/docs',
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Disabling the Scalar UI
|
|
95
|
+
|
|
96
|
+
If you only need the JSON spec (for example, to feed into a code generator), disable the UI:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
openapi({
|
|
100
|
+
disableUI: true,
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Excluding core endpoints
|
|
105
|
+
|
|
106
|
+
To generate a spec that only contains plugin and application routes:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
openapi({
|
|
110
|
+
includeCoreAuth: false,
|
|
111
|
+
includeCoreIam: false,
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Adding server URLs
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
openapi({
|
|
119
|
+
servers: [
|
|
120
|
+
{ url: 'https://api.example.com', description: 'Production' },
|
|
121
|
+
{ url: 'http://localhost:3000', description: 'Local development' },
|
|
122
|
+
],
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Merging Application Routes
|
|
127
|
+
|
|
128
|
+
The most powerful feature of the OpenAPI plugin is the ability to merge your application's own routes into the same spec as Fortress endpoints. This produces a single unified API reference.
|
|
129
|
+
|
|
130
|
+
### Zod-free frozen converters
|
|
131
|
+
|
|
132
|
+
`@bajustone/fortress/hono` exports three supported converter contracts:
|
|
133
|
+
|
|
134
|
+
- `identitySchemaConverter` — returns raw JSON Schema unchanged for tooling that consumes JSON Schema directly.
|
|
135
|
+
- `fetcherSchemaConverter` — compiles JSON Schema to a validating Standard Schema V1 using the pinned `@bajustone/fetcher` runtime; use with `hono-openapi` / `@hono/standard-validator` without Zod.
|
|
136
|
+
- `toJSONSchemaConverter` — extracts JSON Schema from Fortress or fetcher builder schemas when importing app routes with `convertRoutes`.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import {
|
|
140
|
+
convertRoutes,
|
|
141
|
+
fetcherSchemaConverter,
|
|
142
|
+
mountFortressOpenAPI,
|
|
143
|
+
toJSONSchemaConverter,
|
|
144
|
+
} from '@bajustone/fortress/hono';
|
|
145
|
+
|
|
146
|
+
mountFortressOpenAPI(app, fortress, {
|
|
147
|
+
createRoute, // from your Hono OpenAPI integration
|
|
148
|
+
schemaConverter: fetcherSchemaConverter,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const appEndpoints = convertRoutes(appRoutes, {
|
|
152
|
+
prefix: '/api/v1',
|
|
153
|
+
schemaConverter: toJSONSchemaConverter,
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
These export names and signatures are part of the frozen Hono/OpenAPI public surface.
|
|
158
|
+
|
|
159
|
+
### Using `convertRoutes`
|
|
160
|
+
|
|
161
|
+
The `convertRoutes` utility (exported from `@bajustone/fortress/hono` or `@bajustone/fortress/express`) converts `createRoute`-style route objects into Fortress `EndpointDefinition[]`. It is schema-library agnostic -- you provide a converter function that turns your schema objects into JSON Schema.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { openapi } from '@bajustone/fortress/plugins/openapi';
|
|
165
|
+
import { convertRoutes } from '@bajustone/fortress/hono'; // or /express
|
|
166
|
+
import { z } from 'zod';
|
|
167
|
+
|
|
168
|
+
// Your application routes defined with createRoute
|
|
169
|
+
import { loginRoute, listUsersRoute } from './modules/auth/routes';
|
|
170
|
+
import { listSchoolsRoute } from './modules/sdms/routes';
|
|
171
|
+
|
|
172
|
+
openapi({
|
|
173
|
+
title: 'My API',
|
|
174
|
+
version: '1.0.0',
|
|
175
|
+
description: 'Unified spec: fortress + app endpoints',
|
|
176
|
+
additionalEndpoints: convertRoutes(
|
|
177
|
+
[loginRoute, listUsersRoute, listSchoolsRoute],
|
|
178
|
+
{ prefix: '/api/v1', schemaConverter: z.toJSONSchema },
|
|
179
|
+
),
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### `convertRoutes` options
|
|
184
|
+
|
|
185
|
+
| Option | Type | Required | Description |
|
|
186
|
+
|---|---|---|---|
|
|
187
|
+
| `schemaConverter` | `(schema: unknown) => JSONSchema` | Yes | Converts your schema objects to JSON Schema. For Zod v4 use `z.toJSONSchema`. Works with any schema library. |
|
|
188
|
+
| `prefix` | `string` | No | Path prefix to prepend to every route (e.g., `'/api/v1'`). |
|
|
189
|
+
|
|
190
|
+
### Supported route shape
|
|
191
|
+
|
|
192
|
+
`convertRoutes` accepts any object matching the `ExternalRoute` interface, which is compatible with the shape returned by `@hono/zod-openapi`'s `createRoute`:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
interface ExternalRoute {
|
|
196
|
+
method: string;
|
|
197
|
+
path: string;
|
|
198
|
+
tags?: string[];
|
|
199
|
+
summary?: string;
|
|
200
|
+
description?: string;
|
|
201
|
+
deprecated?: boolean;
|
|
202
|
+
security?: Array<Record<string, string[]>>;
|
|
203
|
+
request?: {
|
|
204
|
+
body?: { content: { 'application/json': { schema: unknown } } };
|
|
205
|
+
params?: unknown;
|
|
206
|
+
query?: unknown;
|
|
207
|
+
};
|
|
208
|
+
responses: Record<number | string, {
|
|
209
|
+
description: string;
|
|
210
|
+
content?: { 'application/json': { schema: unknown } };
|
|
211
|
+
}>;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Adding raw `EndpointDefinition` objects
|
|
216
|
+
|
|
217
|
+
If you prefer to skip `convertRoutes`, you can pass `EndpointDefinition[]` directly:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import type { EndpointDefinition } from '@bajustone/fortress';
|
|
221
|
+
|
|
222
|
+
const myEndpoint: EndpointDefinition = {
|
|
223
|
+
method: 'GET',
|
|
224
|
+
path: '/api/v1/health',
|
|
225
|
+
handler: 'healthCheck',
|
|
226
|
+
meta: { summary: 'Health check', tags: ['System'], security: ['none'] },
|
|
227
|
+
responses: { 200: { description: 'OK' } },
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
openapi({
|
|
231
|
+
additionalEndpoints: [myEndpoint],
|
|
232
|
+
})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## API Reference
|
|
236
|
+
|
|
237
|
+
| Method | Signature | Returns |
|
|
238
|
+
|---|---|---|
|
|
239
|
+
| `generateSpec` | `()` | `OpenAPISpec` -- full OpenAPI 3.1 spec object (cached after first call) |
|
|
240
|
+
| `getSpec` | `()` | `Record<string, unknown>` -- the spec as a plain object (used by the JSON route) |
|
|
241
|
+
| `getUI` | `()` | `string` -- Scalar HTML page (used by the UI route) |
|
|
242
|
+
|
|
243
|
+
## How It Works
|
|
244
|
+
|
|
245
|
+
1. **Endpoint discovery** -- On the first call to `generateSpec`, the plugin collects endpoints from three sources: core auth endpoints, core IAM endpoints, and all registered plugins (excluding itself). Consumer-provided `additionalEndpoints` are appended last.
|
|
246
|
+
2. **Schema merging** -- Component schemas from core auth, core IAM, and `additionalSchemas` are merged into the spec's `components.schemas`.
|
|
247
|
+
3. **Spec building** -- The collected endpoints and schemas are passed to the internal `buildOpenAPISpec` function, which produces a valid OpenAPI 3.1.0 document. Path parameters using `:param` syntax are converted to `{param}` format. Operation IDs are auto-generated from the HTTP method and path.
|
|
248
|
+
4. **Security scheme detection** -- Security schemes (Bearer JWT, Basic, API Key) are only included in the spec when at least one endpoint references them, keeping the output clean.
|
|
249
|
+
5. **Caching** -- The generated spec is cached after the first call. The spec is regenerated only when a new Fortress instance is created.
|
|
250
|
+
6. **Scalar UI** -- The UI route serves a lightweight HTML page that loads the Scalar API reference library from a CDN and points it at the JSON spec path.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Rate Limit Plugin
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `rate-limit` plugin adds sliding-window rate limiting to Fortress. It covers:
|
|
6
|
+
|
|
7
|
+
- **Built-in auth endpoints**: `/auth/login`, `/auth/register`, `/auth/refresh`, plus OAuth token issuance (`/oauth/token`) and API-key creation (`/api-key/keys`) when those plugins are mounted.
|
|
8
|
+
- **User-owned routes**: exposes `fortress.plugins['rate-limit'].check(ruleName, keys)` and per-framework middleware wrappers so any route in your app can be rate-limited against a shared, named rule.
|
|
9
|
+
|
|
10
|
+
Attempts are tracked by IP address, authenticated user (when keyed that way), or both. When a limit is exceeded, a `FortressError` with code `RATE_LIMITED` and a `retryAfter` value (in seconds) is thrown — adapters translate this into a 429 with a `Retry-After` header automatically.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createFortress } from '@bajustone/fortress';
|
|
16
|
+
import { rateLimit } from '@bajustone/fortress/plugins/rate-limit';
|
|
17
|
+
|
|
18
|
+
const fortress = createFortress({
|
|
19
|
+
jwt: { key: 'your-secret-at-least-32-bytes!!' },
|
|
20
|
+
database: adapter,
|
|
21
|
+
plugins: [
|
|
22
|
+
rateLimit({
|
|
23
|
+
// Built-in Fortress endpoint blocks — each is opt-in; omitting a block
|
|
24
|
+
// disables rate-limiting for that endpoint.
|
|
25
|
+
login: { maxPerIp: 10, maxPerAccount: 5, windowSeconds: 900 },
|
|
26
|
+
register: { maxPerIp: 3, windowSeconds: 3600 },
|
|
27
|
+
refresh: { maxPerIp: 60, windowSeconds: 60 },
|
|
28
|
+
oauthToken: { maxPerIp: 60, windowSeconds: 60 },
|
|
29
|
+
apiKeyIssue: { maxPerIp: 10, maxPerUser: 10, windowSeconds: 3600 },
|
|
30
|
+
|
|
31
|
+
// Named rules for your own routes + programmatic check() calls.
|
|
32
|
+
rules: {
|
|
33
|
+
api: { maxPerIp: 200, maxPerUser: 1000, windowSeconds: 60 },
|
|
34
|
+
strict: { maxPerIp: 5, windowSeconds: 60 },
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
### Endpoint blocks
|
|
44
|
+
|
|
45
|
+
| Block | Default state | Applied at | Default values |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| `login` | **Always on** | `beforeLogin` hook | `{ maxPerIp: 10, maxPerAccount: 5, windowSeconds: 900 }` |
|
|
48
|
+
| `register` | **Always on** | `beforeRegister` hook | `{ maxPerIp: 3, windowSeconds: 3600 }` |
|
|
49
|
+
| `refresh` | Opt-in | `beforeTokenRefresh` hook | `{ maxPerIp: 60, maxPerUser: 60, windowSeconds: 60 }` |
|
|
50
|
+
| `oauthToken` | Opt-in | Path middleware on `POST /oauth/token` (before-auth) | `{ maxPerIp: 60, windowSeconds: 60 }` |
|
|
51
|
+
| `apiKeyIssue` | Opt-in | Path middleware on `POST /api-key/keys` (after-auth) | `{ maxPerIp: 10, maxPerUser: 10, windowSeconds: 3600 }` |
|
|
52
|
+
|
|
53
|
+
**Gate blocks (`login`, `register`) default on.** They're the first line of defense against credential stuffing and mass registration; leaving them opt-in would let a version bump silently remove DoS protection. To turn one off explicitly:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
rateLimit({
|
|
57
|
+
login: { disabled: true },
|
|
58
|
+
register: { disabled: true },
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Other endpoints are opt-in** — present the block to enable it (with defaults merged), omit to disable.
|
|
63
|
+
|
|
64
|
+
### Rule shape (`rules` + `paths`)
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
interface RateLimitRule {
|
|
68
|
+
maxPerIp?: number; // per-client-IP limit
|
|
69
|
+
maxPerUser?: number; // per-authenticated-user limit (no-op if no userId)
|
|
70
|
+
windowSeconds: number;
|
|
71
|
+
keyPrefix?: string; // defaults to rule name
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
A rule with both `maxPerIp` and `maxPerUser` evaluates each key independently — whichever fills up first short-circuits with `RATE_LIMITED`.
|
|
76
|
+
|
|
77
|
+
### Path bindings (advanced)
|
|
78
|
+
|
|
79
|
+
Bind a named rule (or inline rule) to any Fortress-handled path glob. Matching uses `:param` and `*` wildcards.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
rateLimit({
|
|
83
|
+
rules: { webhooks: { maxPerIp: 20, windowSeconds: 60 } },
|
|
84
|
+
paths: [
|
|
85
|
+
{ match: '/webhooks/*', methods: ['POST'], rule: 'webhooks' },
|
|
86
|
+
{ match: '/internal/*', position: 'after-auth',
|
|
87
|
+
rule: { maxPerIp: 1000, maxPerUser: 100, windowSeconds: 60 } },
|
|
88
|
+
],
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`position: 'before-auth'` (default) matches any request against the IP. `after-auth` runs after principal resolution so `maxPerUser` can apply. Path bindings only fire for routes dispatched through `fortress.handleRequest`.
|
|
93
|
+
|
|
94
|
+
### `paths` vs the per-framework wrapper
|
|
95
|
+
|
|
96
|
+
The config-driven `paths` list and the per-framework wrappers (`honoRateLimit` / `expressRateLimit` / `svelteKitRateLimit`) both apply a named rule to a route. Pick by deployment shape:
|
|
97
|
+
|
|
98
|
+
| Use this… | …when |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `honoRateLimit(fortress, 'api')` / `expressRateLimit(...)` / `svelteKitRateLimit(...)` | You have a framework middleware layer and want to limit routes the framework serves (either your own or Fortress-mounted). |
|
|
101
|
+
| `paths: [{ match: '/foo/*', rule: 'api' }]` | Serverless / framework-less deployments that call `fortress.handleRequest` directly, or when you prefer declarative config co-located with the rest of the rate-limit setup. |
|
|
102
|
+
|
|
103
|
+
Both target the same store. **Don't stack both on the same path** — each match increments the counter, so double-wrapping halves the effective limit.
|
|
104
|
+
|
|
105
|
+
## Rate-limiting your own routes
|
|
106
|
+
|
|
107
|
+
The plugin exposes a `check(ruleName, keys)` method and ready-made middleware wrappers for Hono, Express, and SvelteKit.
|
|
108
|
+
|
|
109
|
+
### Programmatic API
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// Works from any context — route handlers, jobs, WebSocket messages, etc.
|
|
113
|
+
await fortress.plugins['rate-limit'].check('api', {
|
|
114
|
+
ip: request.ip,
|
|
115
|
+
userId: session.userId,
|
|
116
|
+
});
|
|
117
|
+
// throws FortressError(RATE_LIMITED) when the limit is exceeded.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Hono
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { honoRateLimit } from '@bajustone/fortress/plugins/rate-limit/hono';
|
|
124
|
+
|
|
125
|
+
app.use('/api/*', honoRateLimit(fortress, 'api'));
|
|
126
|
+
// Extracts IP from X-Forwarded-For / X-Real-IP and fortressUserId from the
|
|
127
|
+
// Hono context (populated by the fortress auth middleware). Pair with the
|
|
128
|
+
// fortress Hono errorHandler so 429 responses carry Retry-After.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Express
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { expressRateLimit } from '@bajustone/fortress/plugins/rate-limit/express';
|
|
135
|
+
|
|
136
|
+
app.use('/api', expressRateLimit(fortress, 'api'));
|
|
137
|
+
// Uses req.ip / forwarding headers and req.fortressUserId set by the
|
|
138
|
+
// fortress Express auth middleware. Errors flow through next(err) into the
|
|
139
|
+
// standard fortress error handler.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### SvelteKit
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// hooks.server.ts
|
|
146
|
+
import { svelteKitRateLimit } from '@bajustone/fortress/plugins/rate-limit/sveltekit';
|
|
147
|
+
|
|
148
|
+
export const handle = async ({ event, resolve }) => {
|
|
149
|
+
if (event.url.pathname.startsWith('/api/')) {
|
|
150
|
+
await svelteKitRateLimit(fortress, 'api', event);
|
|
151
|
+
}
|
|
152
|
+
return resolve(event);
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Methods-only plugins (magic-link, 2FA, email-verification)
|
|
157
|
+
|
|
158
|
+
These plugins expose methods rather than HTTP routes — you wire them into your own endpoints. Rate-limit them the same way you'd rate-limit any app route: call `check()` (or the per-framework wrapper) in your handler before invoking the plugin method.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
app.post('/auth/magic-link/send', async (c) => {
|
|
162
|
+
await fortress.plugins['rate-limit'].check('strict', {
|
|
163
|
+
ip: c.req.header('x-forwarded-for'),
|
|
164
|
+
});
|
|
165
|
+
const { email } = await c.req.json();
|
|
166
|
+
return c.json(await fortress.plugins['magic-link'].sendMagicLink(email));
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## How It Works
|
|
171
|
+
|
|
172
|
+
### IPv6 normalization
|
|
173
|
+
|
|
174
|
+
IPv6 addresses are normalized to their `/64` prefix to prevent bypass via address rotation within an allocated block. IPv4-mapped IPv6 addresses (`::ffff:1.2.3.4`) are stripped to the IPv4 form. IPv4 addresses are used as-is. Missing IPs fall through as `unknown` — still counted against the `unknown` bucket so smuggling via a stripped header doesn't get a free pass.
|
|
175
|
+
|
|
176
|
+
### Sliding window algorithm
|
|
177
|
+
|
|
178
|
+
The default in-memory store records a timestamp for each request. On each check, timestamps older than the window are pruned, and the remaining count is compared to the limit. A background cleanup runs every 60 seconds to prune stale entries.
|
|
179
|
+
|
|
180
|
+
### Key format
|
|
181
|
+
|
|
182
|
+
Keys are namespaced by rule name:
|
|
183
|
+
- `<rule>:ip:<normalized-ip>` for per-IP counters
|
|
184
|
+
- `<rule>:user:<userId>` for per-user counters
|
|
185
|
+
|
|
186
|
+
Set `keyPrefix` on a rule to override the namespace (useful when sharing a store with other tooling).
|
|
187
|
+
|
|
188
|
+
## Custom Store
|
|
189
|
+
|
|
190
|
+
Provide a custom `RateLimitStore` for distributed deployments (e.g., Redis):
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
interface RateLimitStore {
|
|
194
|
+
increment: (key: string, windowMs: number) => Promise<{ count: number; resetAt: number }>;
|
|
195
|
+
get: (key: string) => Promise<{ count: number; resetAt: number } | null>;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
| Method | Description |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `increment(key, windowMs)` | Increment the counter for `key` within a sliding window of `windowMs` milliseconds. Returns the current count and the timestamp (ms) when the oldest entry in the window expires. |
|
|
202
|
+
| `get(key)` | Return the current counter for `key`, or `null` if no record exists. |
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
rateLimit({
|
|
206
|
+
login: { maxPerIp: 10, maxPerAccount: 5, windowSeconds: 900 },
|
|
207
|
+
store: myRedisStore, // implements RateLimitStore
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Example: handling the error
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { FortressError } from '@bajustone/fortress';
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
await fortress.auth.login('alice@example.com', 'wrong-password');
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err instanceof FortressError && err.code === 'RATE_LIMITED') {
|
|
220
|
+
console.log(`Try again in ${err.retryAfter} seconds`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|