@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,69 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Errors, FortressError } from '../errors';
|
|
3
|
+
import { errorToResponse, withCookies } from './error-response';
|
|
4
|
+
|
|
5
|
+
describe('errorToResponse', () => {
|
|
6
|
+
it('maps a FortressError to its declared status', async () => {
|
|
7
|
+
const res = errorToResponse(Errors.unauthorized('nope'));
|
|
8
|
+
expect(res.status).toBe(401);
|
|
9
|
+
expect(res.headers.get('content-type')).toBe('application/json');
|
|
10
|
+
const body = await res.json() as { code: string; message: string };
|
|
11
|
+
expect(body.code).toBe('UNAUTHORIZED');
|
|
12
|
+
expect(body.message).toBe('nope');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('attaches Retry-After for RATE_LIMITED', () => {
|
|
16
|
+
const res = errorToResponse(Errors.rateLimited(42));
|
|
17
|
+
expect(res.status).toBe(429);
|
|
18
|
+
expect(res.headers.get('retry-after')).toBe('42');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('omits Retry-After for non-rate-limited errors', () => {
|
|
22
|
+
const res = errorToResponse(Errors.forbidden());
|
|
23
|
+
expect(res.headers.get('retry-after')).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns 500 INTERNAL_ERROR for unknown errors and does not leak the message', async () => {
|
|
27
|
+
const res = errorToResponse(new Error('something private'));
|
|
28
|
+
expect(res.status).toBe(500);
|
|
29
|
+
const body = await res.json() as { code: string; message: string };
|
|
30
|
+
expect(body.code).toBe('INTERNAL_ERROR');
|
|
31
|
+
expect(body.message).toBe('Internal server error');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('handles validation errors (issue array)', async () => {
|
|
35
|
+
const err = Errors.validationError([{ message: 'required', path: ['email'] }]);
|
|
36
|
+
expect(err).toBeInstanceOf(FortressError);
|
|
37
|
+
const res = errorToResponse(err);
|
|
38
|
+
expect(res.status).toBe(422);
|
|
39
|
+
const body = await res.json() as { code: string; details: unknown };
|
|
40
|
+
expect(body.code).toBe('VALIDATION_ERROR');
|
|
41
|
+
expect(body.details).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('withCookies', () => {
|
|
46
|
+
it('returns the same response when no cookies given', () => {
|
|
47
|
+
const r = new Response('hi', { status: 200 });
|
|
48
|
+
expect(withCookies(r, [])).toBe(r);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('appends multiple Set-Cookie headers (not overwriting)', () => {
|
|
52
|
+
const r = new Response('{}', {
|
|
53
|
+
status: 200,
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
});
|
|
56
|
+
const out = withCookies(r, ['a=1; Path=/', 'b=2; Path=/']);
|
|
57
|
+
const setCookies = out.headers.getSetCookie();
|
|
58
|
+
expect(setCookies).toContain('a=1; Path=/');
|
|
59
|
+
expect(setCookies).toContain('b=2; Path=/');
|
|
60
|
+
expect(out.headers.get('content-type')).toBe('application/json');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('preserves status and statusText', () => {
|
|
64
|
+
const r = new Response('x', { status: 201, statusText: 'Created' });
|
|
65
|
+
const out = withCookies(r, ['a=1']);
|
|
66
|
+
expect(out.status).toBe(201);
|
|
67
|
+
expect(out.statusText).toBe('Created');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map any thrown error to a web-standard `Response`.
|
|
3
|
+
*
|
|
4
|
+
* Knows about {@link FortressError} (typed status + retry-after handling)
|
|
5
|
+
* and falls back to a sanitized 500 for everything else. Used by
|
|
6
|
+
* `fortress.handleRequest` and re-exportable by adapters.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { FortressLogger } from '../observability/logger';
|
|
10
|
+
import { FortressError } from '../errors';
|
|
11
|
+
|
|
12
|
+
/** JSON shape of an internal/unknown error response. */
|
|
13
|
+
interface InternalErrorBody {
|
|
14
|
+
code: 'INTERNAL_ERROR';
|
|
15
|
+
message: 'Internal server error';
|
|
16
|
+
statusCode: 500;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Convert any caught error into a JSON `Response`. {@link FortressError}
|
|
21
|
+
* instances are mapped to their declared status code, with `Retry-After`
|
|
22
|
+
* set for `RATE_LIMITED`. Unknown errors return a sanitized 500 with no
|
|
23
|
+
* stack trace; the original error is routed to the caller-supplied
|
|
24
|
+
* {@link FortressLogger} at `error` level.
|
|
25
|
+
*/
|
|
26
|
+
export function errorToResponse(err: unknown, logger?: FortressLogger): Response {
|
|
27
|
+
if (err instanceof FortressError) {
|
|
28
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
29
|
+
if (err.code === 'RATE_LIMITED' && err.retryAfter !== undefined) {
|
|
30
|
+
headers['Retry-After'] = String(err.retryAfter);
|
|
31
|
+
}
|
|
32
|
+
// RFC 6749 §5.2: token-endpoint failures MUST use the
|
|
33
|
+
// `{ error, error_description, error_uri? }` shape with a
|
|
34
|
+
// machine-readable `error` code. Detect it via FortressError.oauthError
|
|
35
|
+
// and emit the spec body in place of the default fortress envelope.
|
|
36
|
+
if (err.oauthError) {
|
|
37
|
+
const body: Record<string, unknown> = { error: err.oauthError };
|
|
38
|
+
if (err.oauthDescription)
|
|
39
|
+
body.error_description = err.oauthDescription;
|
|
40
|
+
if (err.oauthErrorUri)
|
|
41
|
+
body.error_uri = err.oauthErrorUri;
|
|
42
|
+
// For 401s on the token endpoint the spec also recommends a
|
|
43
|
+
// `WWW-Authenticate: Basic realm="..."` header when the client used
|
|
44
|
+
// HTTP Basic; clients ignore it in practice and we don't track the
|
|
45
|
+
// auth scheme here, so omit it.
|
|
46
|
+
return new Response(JSON.stringify(body), {
|
|
47
|
+
status: err.statusCode,
|
|
48
|
+
headers,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return new Response(JSON.stringify(err.toJSON()), {
|
|
52
|
+
status: err.statusCode,
|
|
53
|
+
headers,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
logger?.error(
|
|
58
|
+
{ err, message: err instanceof Error ? err.message : 'Unknown error' },
|
|
59
|
+
'unhandled error in fortress.handleRequest',
|
|
60
|
+
);
|
|
61
|
+
const body: InternalErrorBody = {
|
|
62
|
+
code: 'INTERNAL_ERROR',
|
|
63
|
+
message: 'Internal server error',
|
|
64
|
+
statusCode: 500,
|
|
65
|
+
};
|
|
66
|
+
return new Response(JSON.stringify(body), {
|
|
67
|
+
status: 500,
|
|
68
|
+
headers: { 'Content-Type': 'application/json' },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Append `Set-Cookie` headers to an existing `Response`. The web-standard
|
|
74
|
+
* `Headers.set` only keeps the last `set-cookie`, so multiple cookies must
|
|
75
|
+
* use `append`. This helper centralizes that quirk for adapters.
|
|
76
|
+
*
|
|
77
|
+
* Returns a *new* `Response` with the cookies merged in. The original is
|
|
78
|
+
* not mutated (its body is teed via `.clone()` semantics through the
|
|
79
|
+
* constructor).
|
|
80
|
+
*/
|
|
81
|
+
export function withCookies(response: Response, setCookies: string[]): Response {
|
|
82
|
+
if (setCookies.length === 0)
|
|
83
|
+
return response;
|
|
84
|
+
const headers = new Headers(response.headers);
|
|
85
|
+
for (const cookie of setCookies) {
|
|
86
|
+
headers.append('set-cookie', cookie);
|
|
87
|
+
}
|
|
88
|
+
return new Response(response.body, {
|
|
89
|
+
status: response.status,
|
|
90
|
+
statusText: response.statusText,
|
|
91
|
+
headers,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { EndpointDefinition } from '../endpoint';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { enforceFortressPermission } from './fortress-rbac';
|
|
4
|
+
|
|
5
|
+
function endpoint(security: NonNullable<EndpointDefinition['meta']>['security']): EndpointDefinition {
|
|
6
|
+
return {
|
|
7
|
+
method: 'GET',
|
|
8
|
+
path: '/billing',
|
|
9
|
+
handler: 'billing',
|
|
10
|
+
input: {},
|
|
11
|
+
responses: {},
|
|
12
|
+
meta: {
|
|
13
|
+
summary: 'Billing',
|
|
14
|
+
security,
|
|
15
|
+
permission: { resource: 'billing', action: 'read' },
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('enforceFortressPermission', () => {
|
|
21
|
+
it('enforces permission before basic transport metadata', async () => {
|
|
22
|
+
const checkPermission = vi.fn(async () => false);
|
|
23
|
+
await expect(enforceFortressPermission(
|
|
24
|
+
endpoint(['basic']),
|
|
25
|
+
{ type: 'USER', id: '1' },
|
|
26
|
+
{ checkPermission },
|
|
27
|
+
)).rejects.toMatchObject({ code: 'FORBIDDEN' });
|
|
28
|
+
expect(checkPermission).toHaveBeenCalledWith(
|
|
29
|
+
{ type: 'USER', id: '1' },
|
|
30
|
+
'billing',
|
|
31
|
+
'read',
|
|
32
|
+
undefined,
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('requires a subject for api-key permission routes', async () => {
|
|
37
|
+
await expect(enforceFortressPermission(
|
|
38
|
+
endpoint(['apiKey']),
|
|
39
|
+
undefined,
|
|
40
|
+
{ checkPermission: async () => true },
|
|
41
|
+
)).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default-deny RBAC for fortress-managed routes.
|
|
3
|
+
*
|
|
4
|
+
* Run by `fortress.handleRequest` *after* an endpoint has been matched. Uses
|
|
5
|
+
* the endpoint's `meta.security` and `meta.permission` declarations to
|
|
6
|
+
* decide whether the caller is allowed through:
|
|
7
|
+
*
|
|
8
|
+
* - `security: 'none'` or `security: 'basic'` → public, allow.
|
|
9
|
+
* - `meta.permission` set → require auth + IAM `checkPermission`.
|
|
10
|
+
* - `security: 'bearer'` (no permission) → require auth, no IAM check.
|
|
11
|
+
* - Anything else → deny (security-first default).
|
|
12
|
+
*
|
|
13
|
+
* Adapter-side route maps (Hono/Express user-route protection) live in
|
|
14
|
+
* each adapter — this module only handles fortress's own routes.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { EndpointDefinition } from '../endpoint';
|
|
18
|
+
import type { FortressPlugin } from '../plugin';
|
|
19
|
+
import type { Subject } from '../types';
|
|
20
|
+
import { Errors } from '../errors';
|
|
21
|
+
|
|
22
|
+
const FORTRESS_CORE_PREFIXES = ['/iam/'];
|
|
23
|
+
const FORTRESS_AUTH_PROTECTED = ['/auth/impersonate', '/auth/users'];
|
|
24
|
+
const PLUGIN_PREFIX_REGEX = /^(\/[^/]+\/)/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compute the unique top-level path prefixes (e.g. `/oauth/`) used by all
|
|
28
|
+
* registered plugin routes. Adapters call this once at startup to feed
|
|
29
|
+
* {@link isFortressPath}.
|
|
30
|
+
*/
|
|
31
|
+
export function getPluginPathPrefixes(plugins: readonly FortressPlugin[]): string[] {
|
|
32
|
+
const prefixes = new Set<string>();
|
|
33
|
+
for (const plugin of plugins) {
|
|
34
|
+
if (!plugin.routes)
|
|
35
|
+
continue;
|
|
36
|
+
for (const route of Object.values(plugin.routes)) {
|
|
37
|
+
const match = PLUGIN_PREFIX_REGEX.exec(route.path);
|
|
38
|
+
if (match)
|
|
39
|
+
prefixes.add(match[1]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return [...prefixes];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check whether a path is owned by fortress (core IAM, sensitive auth, or
|
|
47
|
+
* any plugin-registered prefix). Fortress-owned paths are subject to
|
|
48
|
+
* default-deny when no permission mapping resolves.
|
|
49
|
+
*/
|
|
50
|
+
export function isFortressPath(
|
|
51
|
+
path: string,
|
|
52
|
+
pluginPathPrefixes: readonly string[],
|
|
53
|
+
): boolean {
|
|
54
|
+
if (FORTRESS_CORE_PREFIXES.some(p => path.startsWith(p)))
|
|
55
|
+
return true;
|
|
56
|
+
if (FORTRESS_AUTH_PROTECTED.some(p => path === p || path.startsWith(`${p}/`)))
|
|
57
|
+
return true;
|
|
58
|
+
if (pluginPathPrefixes.some(p => path.startsWith(p)))
|
|
59
|
+
return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Per-call dependencies for {@link enforceFortressPermission}.
|
|
65
|
+
*/
|
|
66
|
+
export interface PermissionEnforcement {
|
|
67
|
+
/** IAM check, typically `fortress.iam.checkPermission`. */
|
|
68
|
+
checkPermission: (
|
|
69
|
+
subject: Subject,
|
|
70
|
+
resource: string,
|
|
71
|
+
action: string,
|
|
72
|
+
scopes?: string[] | null,
|
|
73
|
+
) => Promise<boolean>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Enforce fortress's default-deny policy for an already-matched endpoint.
|
|
78
|
+
*
|
|
79
|
+
* - Public endpoints (`security: 'none'` / `'basic'`) pass through.
|
|
80
|
+
* - Endpoints with `meta.permission` require an authenticated subject and a
|
|
81
|
+
* passing `checkPermission` call — otherwise throws `UNAUTHORIZED` /
|
|
82
|
+
* `FORBIDDEN`.
|
|
83
|
+
* - Bearer-only endpoints require an authenticated subject but skip IAM.
|
|
84
|
+
* - Endpoints with no security metadata are denied.
|
|
85
|
+
*
|
|
86
|
+
* The caller (typically `fortress.handleRequest`) is responsible for first
|
|
87
|
+
* resolving the request principal (either via a plugin's `resolvePrincipal`
|
|
88
|
+
* or the JWT fallback) and supplying the resulting `subject`.
|
|
89
|
+
*/
|
|
90
|
+
export async function enforceFortressPermission(
|
|
91
|
+
endpoint: EndpointDefinition,
|
|
92
|
+
subject: Subject | undefined,
|
|
93
|
+
enforcement: PermissionEnforcement,
|
|
94
|
+
scopes?: string[] | null,
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const security = endpoint.meta?.security;
|
|
97
|
+
|
|
98
|
+
// Explicit IAM permission requirements always win over transport metadata.
|
|
99
|
+
// A route cannot become public merely because it also accepts Basic auth.
|
|
100
|
+
if (endpoint.meta?.permission) {
|
|
101
|
+
if (!subject)
|
|
102
|
+
throw Errors.unauthorized('Not authenticated');
|
|
103
|
+
const allowed = await enforcement.checkPermission(
|
|
104
|
+
subject,
|
|
105
|
+
endpoint.meta.permission.resource,
|
|
106
|
+
endpoint.meta.permission.action,
|
|
107
|
+
scopes,
|
|
108
|
+
);
|
|
109
|
+
if (!allowed)
|
|
110
|
+
throw Errors.forbidden('Insufficient permissions');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Public/self-authenticated routes without an IAM requirement.
|
|
115
|
+
if (security?.includes('none') || security?.includes('basic'))
|
|
116
|
+
return;
|
|
117
|
+
|
|
118
|
+
// Bearer-only routes — auth required, no IAM check
|
|
119
|
+
if (security?.includes('bearer')) {
|
|
120
|
+
if (!subject)
|
|
121
|
+
throw Errors.unauthorized('Not authenticated');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// No security metadata at all → default deny
|
|
126
|
+
throw Errors.forbidden('No permission mapping for this route');
|
|
127
|
+
}
|