@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,140 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
/**
|
|
3
|
+
* Pipeline CSRF check (H5) regression tests.
|
|
4
|
+
*
|
|
5
|
+
* Cover the matrix:
|
|
6
|
+
* - Safe methods always pass.
|
|
7
|
+
* - Bearer / API-key flows skip even when unsafe.
|
|
8
|
+
* - Cookie-authenticated POST with no CSRF header → 403.
|
|
9
|
+
* - Cookie-authenticated POST with valid CSRF header → passes.
|
|
10
|
+
* - `Sec-Fetch-Site: cross-site` → 403 regardless of header.
|
|
11
|
+
* - Opt-out via `csrf: { enabled: false }` disables the check.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createTestAdapter } from '../../testing';
|
|
15
|
+
import { createFortress } from '../fortress';
|
|
16
|
+
import { assertSuccess } from '../types';
|
|
17
|
+
|
|
18
|
+
interface LoginResult { accessToken: string; refreshToken: string }
|
|
19
|
+
|
|
20
|
+
async function setup(csrf?: { enabled?: boolean }): Promise<{ fortress: Awaited<ReturnType<typeof createFortress>>; login: LoginResult }> {
|
|
21
|
+
const fortress = createFortress({
|
|
22
|
+
database: createTestAdapter(),
|
|
23
|
+
jwt: { key: 'x'.repeat(32) },
|
|
24
|
+
cookies: { secure: false },
|
|
25
|
+
csrf,
|
|
26
|
+
});
|
|
27
|
+
await fortress.auth.createUser({ email: 'csrf@test.com', name: 'C', password: 'pass-test-12345' });
|
|
28
|
+
const login = await fortress.auth.login('csrf@test.com', 'pass-test-12345');
|
|
29
|
+
assertSuccess(login);
|
|
30
|
+
return { fortress, login: { accessToken: login.accessToken!, refreshToken: login.refreshToken! } };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('pipeline CSRF check (H5)', () => {
|
|
34
|
+
let fortress: Awaited<ReturnType<typeof createFortress>>;
|
|
35
|
+
let login: LoginResult;
|
|
36
|
+
|
|
37
|
+
beforeEach(async () => {
|
|
38
|
+
({ fortress, login } = await setup());
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('safe methods pass without CSRF header', async () => {
|
|
42
|
+
const res = await fortress.handleRequest(new Request('http://localhost/auth/me', {
|
|
43
|
+
headers: { cookie: `${fortress.cookies.accessName}=${login.accessToken}` },
|
|
44
|
+
}));
|
|
45
|
+
expect(res.status).toBe(200);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('bearer POST passes without CSRF header', async () => {
|
|
49
|
+
const res = await fortress.handleRequest(new Request('http://localhost/auth/logout', {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'content-type': 'application/json',
|
|
53
|
+
'authorization': `Bearer ${login.accessToken}`,
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({ refreshToken: login.refreshToken }),
|
|
56
|
+
}));
|
|
57
|
+
// Logout uses security:['none'] but cookie absence + bearer header
|
|
58
|
+
// means no cookie auth ambient credential — CSRF skipped.
|
|
59
|
+
expect(res.status).toBe(200);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('rejects cross-site pre-authentication state changes without a cookie', async () => {
|
|
63
|
+
const res = await fortress.handleRequest(new Request('http://localhost/auth/login', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'content-type': 'application/json',
|
|
67
|
+
'sec-fetch-site': 'cross-site',
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({ identifier: 'csrf@example.com', password: 'password-123456' }),
|
|
70
|
+
}));
|
|
71
|
+
expect(res.status).toBe(403);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('cookie POST without CSRF header is rejected (403)', async () => {
|
|
75
|
+
const res = await fortress.handleRequest(new Request('http://localhost/auth/logout', {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
'content-type': 'application/json',
|
|
79
|
+
'cookie': `${fortress.cookies.accessName}=${login.accessToken}`,
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({ refreshToken: login.refreshToken }),
|
|
82
|
+
}));
|
|
83
|
+
expect(res.status).toBe(403);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('cookie POST with custom CSRF header passes', async () => {
|
|
87
|
+
const res = await fortress.handleRequest(new Request('http://localhost/auth/logout', {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: {
|
|
90
|
+
'content-type': 'application/json',
|
|
91
|
+
'cookie': `${fortress.cookies.accessName}=${login.accessToken}`,
|
|
92
|
+
'x-fortress-csrf': '1',
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify({ refreshToken: login.refreshToken }),
|
|
95
|
+
}));
|
|
96
|
+
expect(res.status).toBe(200);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('sec-fetch-site: cross-site is rejected even with the header', async () => {
|
|
100
|
+
const res = await fortress.handleRequest(new Request('http://localhost/auth/logout', {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: {
|
|
103
|
+
'content-type': 'application/json',
|
|
104
|
+
'cookie': `${fortress.cookies.accessName}=${login.accessToken}`,
|
|
105
|
+
'x-fortress-csrf': '1',
|
|
106
|
+
'sec-fetch-site': 'cross-site',
|
|
107
|
+
},
|
|
108
|
+
body: JSON.stringify({ refreshToken: login.refreshToken }),
|
|
109
|
+
}));
|
|
110
|
+
expect(res.status).toBe(403);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('refresh-cookie-only request (expired access) is still CSRF-checked', async () => {
|
|
114
|
+
// A session whose access cookie has expired still carries the refresh
|
|
115
|
+
// cookie. Such a request has ambient credentials and must not slip the
|
|
116
|
+
// check by virtue of the access cookie being absent.
|
|
117
|
+
const res = await fortress.handleRequest(new Request('http://localhost/auth/logout', {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: {
|
|
120
|
+
'content-type': 'application/json',
|
|
121
|
+
'cookie': `${fortress.cookies.refreshName}=${login.refreshToken}`,
|
|
122
|
+
},
|
|
123
|
+
body: JSON.stringify({ refreshToken: login.refreshToken }),
|
|
124
|
+
}));
|
|
125
|
+
expect(res.status).toBe(403);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('opting out via csrf.enabled=false disables the check', async () => {
|
|
129
|
+
const optedOut = await setup({ enabled: false });
|
|
130
|
+
const res = await optedOut.fortress.handleRequest(new Request('http://localhost/auth/logout', {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'content-type': 'application/json',
|
|
134
|
+
'cookie': `${optedOut.fortress.cookies.accessName}=${optedOut.login.accessToken}`,
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify({ refreshToken: optedOut.login.refreshToken }),
|
|
137
|
+
}));
|
|
138
|
+
expect(res.status).toBe(200);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline-level CSRF protection.
|
|
3
|
+
*
|
|
4
|
+
* Activated by default for unsafe HTTP methods (`POST/PUT/PATCH/DELETE`)
|
|
5
|
+
* when the request authenticated via a Fortress auth cookie. Bearer-only
|
|
6
|
+
* and API-key requests are CSRF-immune by construction (no ambient
|
|
7
|
+
* credential the browser would auto-attach) and are skipped.
|
|
8
|
+
*
|
|
9
|
+
* The check is intentionally narrow:
|
|
10
|
+
*
|
|
11
|
+
* 1. Reject when `Sec-Fetch-Site: cross-site`. Modern browsers send this
|
|
12
|
+
* header on every fetch — its presence + value is a reliable signal.
|
|
13
|
+
* 2. Reject when the required custom header (`X-Fortress-CSRF` by default)
|
|
14
|
+
* is absent. Custom-header CSRF leverages the browser preflight rule:
|
|
15
|
+
* a cross-origin request can't set the header without an explicit
|
|
16
|
+
* server-side CORS allowance.
|
|
17
|
+
*
|
|
18
|
+
* Closes H5 — before this, `mountFortress` / `createSvelteKitHandle`
|
|
19
|
+
* dispatched straight to `handleRequest` with no CSRF check. This check now
|
|
20
|
+
* runs inside `handleRequest`, so every Fortress-managed route is covered
|
|
21
|
+
* regardless of adapter (Hono, Express, SvelteKit). The SvelteKit adapter's
|
|
22
|
+
* user-route silent token refresh — the one remaining Fortress state-change
|
|
23
|
+
* outside this pipeline — is independently gated to safe HTTP methods.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { ResolvedCookieConfig } from '../config';
|
|
27
|
+
import { Errors } from '../errors';
|
|
28
|
+
import { parseCookieHeader } from './cookie-serialize';
|
|
29
|
+
|
|
30
|
+
/** Resolved CSRF configuration. */
|
|
31
|
+
export interface ResolvedCsrfConfig {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
headerName: string;
|
|
34
|
+
skipPaths: string[];
|
|
35
|
+
rejectSameSite: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** User-facing CSRF configuration (all fields optional). */
|
|
39
|
+
export interface CsrfConfig {
|
|
40
|
+
/**
|
|
41
|
+
* Set to `false` to disable the pipeline CSRF check entirely. Useful for
|
|
42
|
+
* pure bearer-only API deployments where no Fortress route is reachable
|
|
43
|
+
* with ambient credentials. Default: `true`.
|
|
44
|
+
*/
|
|
45
|
+
enabled?: boolean;
|
|
46
|
+
/** Required header name. Default: `'X-Fortress-CSRF'`. */
|
|
47
|
+
headerName?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Exact-path allow-list of routes the check should skip. Matched at
|
|
50
|
+
* segment boundaries (i.e. `'/foo'` matches `/foo` and `/foo/bar` but
|
|
51
|
+
* not `/foobar`).
|
|
52
|
+
*/
|
|
53
|
+
skipPaths?: string[];
|
|
54
|
+
/**
|
|
55
|
+
* Additionally reject `Sec-Fetch-Site: same-site` requests. Single-host
|
|
56
|
+
* deployments (web app + Fortress on the same origin) can opt in to the
|
|
57
|
+
* stricter posture; multi-host (`api.example.com` + `app.example.com`)
|
|
58
|
+
* deployments leave this `false` so the legit app traffic passes.
|
|
59
|
+
* Default: `false`.
|
|
60
|
+
*/
|
|
61
|
+
rejectSameSite?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Resolve user-facing CSRF config against defaults. */
|
|
65
|
+
export function resolveCsrfConfig(config?: CsrfConfig): ResolvedCsrfConfig {
|
|
66
|
+
return {
|
|
67
|
+
enabled: config?.enabled ?? true,
|
|
68
|
+
headerName: config?.headerName ?? 'X-Fortress-CSRF',
|
|
69
|
+
skipPaths: config?.skipPaths ?? [],
|
|
70
|
+
rejectSameSite: config?.rejectSameSite ?? false,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Test whether `path` falls under any of the configured skip paths,
|
|
78
|
+
* matched at segment boundaries (so `/foo` doesn't accidentally skip
|
|
79
|
+
* `/foobar`).
|
|
80
|
+
*/
|
|
81
|
+
function matchesSkipPath(path: string, skipPaths: string[]): boolean {
|
|
82
|
+
for (const skip of skipPaths) {
|
|
83
|
+
if (path === skip)
|
|
84
|
+
return true;
|
|
85
|
+
if (path.startsWith(`${skip}/`))
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Detect whether the request authenticated through a Fortress auth
|
|
93
|
+
* cookie. We treat cookie-based auth as "browser ambient credentials
|
|
94
|
+
* exist" — the only condition under which a cross-site request can use
|
|
95
|
+
* the user's session.
|
|
96
|
+
*/
|
|
97
|
+
function isCookieAuthenticated(
|
|
98
|
+
request: Request,
|
|
99
|
+
cookies: ResolvedCookieConfig,
|
|
100
|
+
): boolean {
|
|
101
|
+
const jar = parseCookieHeader(request.headers.get('cookie'));
|
|
102
|
+
// Either cookie counts as ambient credentials. A session whose access
|
|
103
|
+
// cookie has expired still carries a valid refresh cookie, and adapters
|
|
104
|
+
// can silently rotate it — so a refresh-only request is just as
|
|
105
|
+
// CSRF-exposed as an access-cookie request.
|
|
106
|
+
const hasCookie = (name: string): boolean =>
|
|
107
|
+
typeof jar[name] === 'string' && jar[name].length > 0;
|
|
108
|
+
return hasCookie(cookies.accessName) || hasCookie(cookies.refreshName);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Enforce CSRF on unsafe, cookie-authenticated requests. Throws
|
|
113
|
+
* {@link FortressError} (403) when the request is rejected. Returns
|
|
114
|
+
* silently when the request is safe or the check is bypassed.
|
|
115
|
+
*/
|
|
116
|
+
export function enforceCsrf(
|
|
117
|
+
request: Request,
|
|
118
|
+
pathname: string,
|
|
119
|
+
csrf: ResolvedCsrfConfig,
|
|
120
|
+
cookies: ResolvedCookieConfig,
|
|
121
|
+
): void {
|
|
122
|
+
if (!csrf.enabled)
|
|
123
|
+
return;
|
|
124
|
+
|
|
125
|
+
// GET/HEAD/OPTIONS are safe-by-method per RFC 9110.
|
|
126
|
+
if (SAFE_METHODS.has(request.method.toUpperCase()))
|
|
127
|
+
return;
|
|
128
|
+
|
|
129
|
+
if (matchesSkipPath(pathname, csrf.skipPaths))
|
|
130
|
+
return;
|
|
131
|
+
|
|
132
|
+
const fetchSite = request.headers.get('sec-fetch-site');
|
|
133
|
+
const cookieAuthenticated = isCookieAuthenticated(request, cookies);
|
|
134
|
+
|
|
135
|
+
// Pre-authentication endpoints still need login-CSRF protection: a hostile
|
|
136
|
+
// site can otherwise submit its own credentials and bind the victim's
|
|
137
|
+
// browser to the attacker's account. Browser cross-site signals are checked
|
|
138
|
+
// even before an auth cookie exists; non-browser bearer/API-key clients do
|
|
139
|
+
// not send these ambient-browser headers and remain unaffected.
|
|
140
|
+
if (!cookieAuthenticated) {
|
|
141
|
+
// Explicit credentials are not ambient browser authority and therefore
|
|
142
|
+
// are not vulnerable to CSRF, even when sent cross-origin intentionally.
|
|
143
|
+
if (request.headers.has('authorization') || request.headers.has('x-api-key'))
|
|
144
|
+
return;
|
|
145
|
+
if (fetchSite === 'cross-site')
|
|
146
|
+
throw Errors.forbidden('CSRF: cross-site request rejected');
|
|
147
|
+
const origin = request.headers.get('origin');
|
|
148
|
+
if (origin) {
|
|
149
|
+
try {
|
|
150
|
+
if (new URL(origin).origin !== new URL(request.url).origin)
|
|
151
|
+
throw Errors.forbidden('CSRF: cross-origin request rejected');
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error instanceof Error && 'code' in error)
|
|
155
|
+
throw error;
|
|
156
|
+
throw Errors.forbidden('CSRF: invalid Origin header');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Cookie-authenticated browser requests additionally require the custom
|
|
163
|
+
// header, which forces a CORS preflight for cross-origin JavaScript.
|
|
164
|
+
|
|
165
|
+
if (fetchSite === 'cross-site')
|
|
166
|
+
throw Errors.forbidden('CSRF: cross-site request rejected');
|
|
167
|
+
if (csrf.rejectSameSite && fetchSite === 'same-site')
|
|
168
|
+
throw Errors.forbidden('CSRF: same-site request rejected');
|
|
169
|
+
|
|
170
|
+
const tokenHeader = request.headers.get(csrf.headerName);
|
|
171
|
+
if (!tokenHeader)
|
|
172
|
+
throw Errors.forbidden(`CSRF: missing ${csrf.headerName} header`);
|
|
173
|
+
}
|