@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,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 };
|