@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
package/docs/security.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Security Guide
|
|
2
|
+
|
|
3
|
+
## JWT Secret Requirements
|
|
4
|
+
|
|
5
|
+
Fortress uses `jose` with HS256 by default. The secret must be at least 32 bytes.
|
|
6
|
+
|
|
7
|
+
Generate a secret:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Secret Rotation
|
|
14
|
+
|
|
15
|
+
Pass an array to rotate secrets with zero downtime. Fortress signs with the first secret and verifies against all:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
const fortress = createFortress({
|
|
19
|
+
jwt: {
|
|
20
|
+
key: ['new-secret-abc', 'old-secret-xyz'],
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
1. Deploy with `['new', 'old']`
|
|
26
|
+
2. Wait for all existing access tokens to expire (default 15 min)
|
|
27
|
+
3. Remove the old secret: `secret: 'new'`
|
|
28
|
+
|
|
29
|
+
## Two-Factor Authentication
|
|
30
|
+
|
|
31
|
+
TOTP seeds are stored only as user-bound AES-256-GCM envelopes using the required application-held `twoFactor({ secretEncryptionKey })` key. Keep that key outside the database and backed up; migration 0009 removes legacy plaintext enrolments and requires affected users to re-enrol.
|
|
32
|
+
|
|
33
|
+
Two-factor continuation proofs are durably counted on the existing continuation record. Rejected TOTP and backup-code proofs survive transaction rollback, are invalidated after `maxAttempts` (default 5), and are subject to a short per-account cooldown (`failedAttemptCooldownSeconds`, default 1 second). These controls do not permanently lock an account; a new login creates a new continuation after the cooldown.
|
|
34
|
+
|
|
35
|
+
Trusted devices are explicit opt-in only. Pass `{ rememberDevice: true }` when completing setup or a two-factor challenge. Fortress returns a high-entropy `trustedDeviceToken`; pass it back in `RequestMeta.trustedDeviceToken` on later logins. The database stores only its hash and never uses User-Agent as a credential. Core is adapter-neutral and does not set plugin cookies: the host application must set the returned token in a `Secure`, `HttpOnly`, `SameSite=Lax` (or stricter), appropriately scoped cookie and forward it on subsequent requests.
|
|
36
|
+
|
|
37
|
+
## Password Hashing
|
|
38
|
+
|
|
39
|
+
Default: Argon2id via WASM (`hash-wasm`).
|
|
40
|
+
|
|
41
|
+
| Parameter | Value |
|
|
42
|
+
|-----------|-------|
|
|
43
|
+
| Memory | 64 MB |
|
|
44
|
+
| Iterations | 3 |
|
|
45
|
+
| Parallelism | 1 |
|
|
46
|
+
| Output length | 32 bytes |
|
|
47
|
+
|
|
48
|
+
### Swapping the Hasher
|
|
49
|
+
|
|
50
|
+
For better performance outside edge runtimes, swap to a native implementation:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { createFortress } from '@bajustone/fortress';
|
|
54
|
+
|
|
55
|
+
const fortress = createFortress({
|
|
56
|
+
passwordHasher: {
|
|
57
|
+
hash: async (password) => Bun.password.hash(password, 'argon2id'),
|
|
58
|
+
verify: async (password, hash) => Bun.password.verify(password, hash),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or with `@node-rs/argon2`:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { hash, verify } from '@node-rs/argon2';
|
|
67
|
+
|
|
68
|
+
const fortress = createFortress({
|
|
69
|
+
passwordHasher: {
|
|
70
|
+
hash: async (password) => hash(password),
|
|
71
|
+
verify: async (password, h) => verify(h, password),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Password Policy
|
|
77
|
+
|
|
78
|
+
Configured via `passwordPolicy` in config. Defaults follow NIST 800-63B:
|
|
79
|
+
|
|
80
|
+
| Option | Default | Description |
|
|
81
|
+
|--------|---------|-------------|
|
|
82
|
+
| `minLength` | 15 | Minimum length for new passwords |
|
|
83
|
+
| `maxLength` | 128 | Maximum password length |
|
|
84
|
+
| `checkBreached` | `false` | Check against HIBP breached passwords |
|
|
85
|
+
| `breachedCacheTtlMs` | 86400000 | Cache TTL for HIBP results (24 hours) |
|
|
86
|
+
| `breachedCacheMaxEntries` | 1000 | Maximum cached HIBP ranges; `0` disables caching |
|
|
87
|
+
| `breachedFailureMode` | `'open'` | Accept (`'open'`) or reject (`'closed'`) password writes during HIBP outages |
|
|
88
|
+
|
|
89
|
+
### Breach Checking (HIBP)
|
|
90
|
+
|
|
91
|
+
When `checkBreached: true`, Fortress checks passwords against the Have I Been Pwned API using k-anonymity. Only the first 5 characters of the SHA-1 hash are sent to the API -- the full password never leaves the server.
|
|
92
|
+
|
|
93
|
+
Breach checking defaults to **fail open** on network errors: if the HIBP API is unreachable, the password is accepted. Set `breachedFailureMode: 'closed'` when assurance is more important than availability. Both modes log and emit `PASSWORD_BREACH_CHECK_DEGRADED` so operators can alert on a disabled control.
|
|
94
|
+
|
|
95
|
+
## Rate Limiting
|
|
96
|
+
|
|
97
|
+
The `rate-limit` plugin provides sliding-window rate limiting with dual-key support (per-IP and per-account).
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { rateLimit } from '@bajustone/fortress/plugins/rate-limit';
|
|
101
|
+
|
|
102
|
+
const fortress = createFortress({
|
|
103
|
+
plugins: [
|
|
104
|
+
rateLimit({
|
|
105
|
+
login: { maxPerIp: 10, maxPerAccount: 5, windowSeconds: 900 }, // 15 min window
|
|
106
|
+
register: { maxPerIp: 3, windowSeconds: 3600 }, // 1 hour window
|
|
107
|
+
}),
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Custom Store
|
|
113
|
+
|
|
114
|
+
The default store is in-memory. For distributed deployments, provide a custom store:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
rateLimit({
|
|
118
|
+
store: myRedisRateLimitStore, // implements RateLimitStore interface
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### IPv6 Normalization
|
|
123
|
+
|
|
124
|
+
IPv6 addresses are normalized to /64 prefixes to prevent attackers from rotating through a /64 block to bypass per-IP limits.
|
|
125
|
+
|
|
126
|
+
## Account Lockout
|
|
127
|
+
|
|
128
|
+
The `account-lockout` plugin uses progressive delays with exponential backoff instead of hard lockout.
|
|
129
|
+
|
|
130
|
+
| Option | Default | Description |
|
|
131
|
+
|--------|---------|-------------|
|
|
132
|
+
| `maxFailedAttempts` | 5 | Attempts before lockout triggers |
|
|
133
|
+
| `lockoutDurationSeconds` | 900 | Initial lockout (15 min) |
|
|
134
|
+
| `escalation` | `true` | Enable exponential backoff |
|
|
135
|
+
| `maxLockoutSeconds` | 3600 | Maximum lockout (1 hour) |
|
|
136
|
+
|
|
137
|
+
Lockout is tracked by login identifier (email/username), not userId. This correctly handles attempts against non-existent accounts.
|
|
138
|
+
|
|
139
|
+
## Token Storage
|
|
140
|
+
|
|
141
|
+
### Refresh Tokens
|
|
142
|
+
|
|
143
|
+
Store in **httpOnly, Secure, SameSite=Lax** cookies. Never store in localStorage or sessionStorage.
|
|
144
|
+
|
|
145
|
+
Why: XSS attacks can read localStorage but cannot access httpOnly cookies. A compromised script can still make requests with cookies attached, but that is mitigated by CSRF protection (see below).
|
|
146
|
+
|
|
147
|
+
### Access Tokens
|
|
148
|
+
|
|
149
|
+
Keep in memory only (JavaScript variable). Do not persist to localStorage, sessionStorage, or cookies.
|
|
150
|
+
|
|
151
|
+
Access tokens are short-lived (default 15 min) and re-issued via the refresh token. On page reload, the client calls the refresh endpoint to get a new access token.
|
|
152
|
+
|
|
153
|
+
## CSRF Protection
|
|
154
|
+
|
|
155
|
+
Fortress-managed routes enforce pipeline CSRF by default for unsafe methods
|
|
156
|
+
when the request carries a Fortress access or refresh cookie. The check
|
|
157
|
+
rejects `Sec-Fetch-Site: cross-site` and requires `X-Fortress-CSRF` (or your
|
|
158
|
+
configured header). It still applies when an `Authorization`/API-key header
|
|
159
|
+
is present alongside cookies; pure bearer/API-key requests with no Fortress
|
|
160
|
+
cookies skip it.
|
|
161
|
+
|
|
162
|
+
Fortress also provides a custom-header CSRF middleware for Hono user routes:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { createCsrfMiddleware } from '@bajustone/fortress/hono';
|
|
166
|
+
|
|
167
|
+
app.use('/api/*', createCsrfMiddleware({
|
|
168
|
+
headerName: 'X-Fortress-CSRF', // default
|
|
169
|
+
skipPaths: ['/api/webhooks/*'],
|
|
170
|
+
safeMethods: ['GET', 'HEAD', 'OPTIONS'], // default
|
|
171
|
+
}));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
This requires API clients to send `X-Fortress-CSRF: 1` on all mutating requests (POST, PUT, PATCH, DELETE). Browsers will not attach this header on cross-origin requests unless the server explicitly allows it via CORS, which blocks CSRF.
|
|
175
|
+
|
|
176
|
+
### Defense Layers
|
|
177
|
+
|
|
178
|
+
1. **SameSite=Lax cookies** -- baseline protection, prevents cookies from being sent on cross-origin POST
|
|
179
|
+
2. **Custom header check** -- `X-Fortress-CSRF: 1` required on mutating requests
|
|
180
|
+
3. **Fetch Metadata** -- checks `Sec-Fetch-Site` header when present (modern browsers only)
|
|
181
|
+
|
|
182
|
+
## Refresh Token Security
|
|
183
|
+
|
|
184
|
+
### Family-Based Rotation
|
|
185
|
+
|
|
186
|
+
Every refresh token belongs to a token family. When a refresh token is used:
|
|
187
|
+
|
|
188
|
+
1. The old token is invalidated
|
|
189
|
+
2. A new token in the same family is issued
|
|
190
|
+
|
|
191
|
+
If a previously-used token is presented (reuse detection), the entire token family is invalidated. This protects against token theft: if an attacker steals a refresh token and uses it, the legitimate user's next refresh attempt triggers family-wide revocation.
|
|
192
|
+
|
|
193
|
+
Concurrency note: Fortress deliberately treats a losing concurrent refresh as reuse. If two requests submit the same refresh token at once, one rotates successfully and the loser revokes the family, including the winner's new refresh token. Coordinate refreshes client-side or add a server-side single-flight/grace mechanism if your UX requires multi-tab auto-refresh without re-login.
|
|
194
|
+
|
|
195
|
+
### Token Fingerprinting
|
|
196
|
+
|
|
197
|
+
When `jwt.validateRefreshFingerprint` is enabled, Fortress stores a SHA-256 hash of the User-Agent with each refresh token.
|
|
198
|
+
|
|
199
|
+
| Value | Behavior |
|
|
200
|
+
|-------|----------|
|
|
201
|
+
| `true` | Fingerprint mismatch invalidates the entire token family |
|
|
202
|
+
| `'warn'` | Mismatch is logged but the refresh proceeds |
|
|
203
|
+
| `false` / unset | No fingerprint validation |
|
|
204
|
+
|
|
205
|
+
## HTTPS Requirements
|
|
206
|
+
|
|
207
|
+
Always use HTTPS in production.
|
|
208
|
+
|
|
209
|
+
- Set the `Secure` flag on all cookies so they are only sent over HTTPS
|
|
210
|
+
- Enable HTTP Strict Transport Security (HSTS) at the reverse proxy or application level
|
|
211
|
+
- Fortress does not enforce HTTPS at the library level -- this is a deployment concern
|
|
212
|
+
|
|
213
|
+
## Audit Logging
|
|
214
|
+
|
|
215
|
+
The `audit-log` plugin records auth events as append-only entries:
|
|
216
|
+
|
|
217
|
+
- `LOGIN_SUCCESS`, `LOGIN_FAILURE`
|
|
218
|
+
- `LOGOUT`
|
|
219
|
+
- `REGISTER`
|
|
220
|
+
- `TOKEN_REFRESH`, `TOKEN_REUSE`
|
|
221
|
+
|
|
222
|
+
### Hash Chain
|
|
223
|
+
|
|
224
|
+
When enabled, each audit entry includes a SHA-256 hash of the previous entry, providing integrity checks against accidental corruption and attackers who cannot rewrite the full log plus anchor. A database-write attacker who can recompute every row and reset the in-database anchor can forge a consistent chain; use external attestation or a keyed/external anchor when that actor is in scope.
|
|
225
|
+
|
|
226
|
+
### Compliance
|
|
227
|
+
|
|
228
|
+
| Standard | Relevant Requirement |
|
|
229
|
+
|----------|---------------------|
|
|
230
|
+
| SOC 2 | Access logging and monitoring (CC6.1, CC7.2) |
|
|
231
|
+
| HIPAA | Audit controls (164.312(b)) |
|
|
232
|
+
| PCI-DSS | Log retention of 1 year, 3 months immediately accessible (10.7) |
|
|
233
|
+
|
|
234
|
+
Configure retention in your application. Fortress writes events; your infrastructure handles retention and archival.
|
|
235
|
+
|
|
236
|
+
## Admin Impersonation
|
|
237
|
+
|
|
238
|
+
Fortress supports admin impersonation via RFC 8693 token exchange semantics.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const result = await fortress.auth.impersonate(adminUserId, targetUserId, {
|
|
242
|
+
reason: 'Support ticket #1234',
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The resulting access token includes an `act` (actor) claim identifying the admin:
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"sub": "target-user-id",
|
|
251
|
+
"act": { "sub": "admin-user-id" },
|
|
252
|
+
"exp": 1712345678
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Security constraints:
|
|
257
|
+
|
|
258
|
+
- Default expiry: 60 minutes
|
|
259
|
+
- Non-renewable: no refresh token is issued
|
|
260
|
+
- The caller must verify that the admin has the `fortress:impersonate` permission before calling `impersonate()`
|
|
261
|
+
- The impersonation reason is stored in plugin data for audit purposes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Fortress Threat Model
|
|
2
|
+
|
|
3
|
+
Date: 2026-06-08
|
|
4
|
+
Scope: Fortress core auth/IAM, HTTP pipeline, adapters, and first-party plugins.
|
|
5
|
+
|
|
6
|
+
## Security goals
|
|
7
|
+
|
|
8
|
+
- Authenticate users and non-human principals with bounded, revocable credentials.
|
|
9
|
+
- Authorize every non-public Fortress route by default-deny metadata (`security`, `permission`, `bearerKind`).
|
|
10
|
+
- Keep browser-cookie flows protected against CSRF.
|
|
11
|
+
- Prevent tenant/data isolation bypass across users, service accounts, tenants, and pooled DB connections.
|
|
12
|
+
- Preserve auditability for security-relevant state changes.
|
|
13
|
+
- Make route, OpenAPI, and migration drift visible in CI.
|
|
14
|
+
|
|
15
|
+
## Trust boundaries
|
|
16
|
+
|
|
17
|
+
| Boundary | Trusted side | Untrusted side | Controls |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| HTTP request ingress | Fortress pipeline / adapter wrapper | Browser, API clients, reverse proxies | route manifest, CSRF, auth, RBAC, validation |
|
|
20
|
+
| JWT verification | Fortress `verifyToken` with pinned algorithm/issuer | Client-supplied bearer/cookie token | HS256 pinning, issuer check, reserved-claim stripping |
|
|
21
|
+
| Plugin principal resolution | Registered plugin code | API key/OAuth/mTLS-like credential values | first non-null resolver wins, RBAC still applies |
|
|
22
|
+
| Database adapter | Fortress services + configured adapter | User-controlled fields | schema validation, parameterized adapter queries, migrations |
|
|
23
|
+
| Tenant schema switching | Verified JWT custom claim + transaction-pinned adapter | Headers / route body tenant hints | no `X-Tenant-Code`, numeric schema ids, bound `set_config` |
|
|
24
|
+
| OAuth redirects | Registered clients and redirect URI list | Browser redirect parameters | exact/loopback URI matching, issuer identification, PKCE |
|
|
25
|
+
|
|
26
|
+
## Assets
|
|
27
|
+
|
|
28
|
+
- Password hashes and login identifiers.
|
|
29
|
+
- Access/refresh tokens, OAuth authorization codes, OAuth access/refresh tokens, API-key hashes.
|
|
30
|
+
- IAM roles, permissions, bindings, groups, service accounts.
|
|
31
|
+
- Tenant memberships and tenant-scoped business data.
|
|
32
|
+
- Audit logs and webhook delivery records.
|
|
33
|
+
- OAuth signing keys and OIDC JWKS metadata.
|
|
34
|
+
|
|
35
|
+
## Threats and mitigations
|
|
36
|
+
|
|
37
|
+
### OAuth/OIDC flows
|
|
38
|
+
|
|
39
|
+
Threats:
|
|
40
|
+
- Authorization-code interception/replay.
|
|
41
|
+
- Public client without PKCE.
|
|
42
|
+
- Redirect URI mix-up or open redirect.
|
|
43
|
+
- Client grant/scope escalation.
|
|
44
|
+
- Non-standard token endpoint errors confusing relying parties.
|
|
45
|
+
|
|
46
|
+
Mitigations:
|
|
47
|
+
- PKCE required for public clients and enforced at authorization-code issuance/exchange.
|
|
48
|
+
- Authorization codes are single-use and exchanged atomically.
|
|
49
|
+
- Redirect URI matching is exact except RFC 8252 loopback dynamic-port allowance.
|
|
50
|
+
- Authorization responses include `iss`; discovery advertises support.
|
|
51
|
+
- Per-client `grantTypes`, `tokenEndpointAuthMethod`, and `allowedScopes` are enforced.
|
|
52
|
+
- OAuth protocol routes are explicitly marked `bearerKind: 'oauth'`; other `/oauth/*` routes use normal Fortress JWT/RBAC.
|
|
53
|
+
|
|
54
|
+
Residual risks:
|
|
55
|
+
- Legacy compatibility flags can weaken posture if enabled; document and monitor them.
|
|
56
|
+
- Client compromise remains out of scope except revocation/removal controls.
|
|
57
|
+
|
|
58
|
+
### Refresh-token rotation/replay
|
|
59
|
+
|
|
60
|
+
Threats:
|
|
61
|
+
- Stolen refresh token reused after rotation.
|
|
62
|
+
- Concurrent refresh racing to mint multiple valid descendants.
|
|
63
|
+
- Timing leaks on token hash comparison.
|
|
64
|
+
|
|
65
|
+
Mitigations:
|
|
66
|
+
- Refresh rotation records token family and revokes family on reuse.
|
|
67
|
+
- SQLite transactions are serialized with `BEGIN IMMEDIATE`; PostgreSQL uses adapter transactions.
|
|
68
|
+
- Constant-time hash compare is used for security-critical token/code hashes.
|
|
69
|
+
- Refresh-cookie CSRF is detected even when only the refresh cookie remains.
|
|
70
|
+
|
|
71
|
+
Residual risks:
|
|
72
|
+
- Already-issued access tokens remain valid until expiry unless sessions are revoked and checked by the host flow.
|
|
73
|
+
|
|
74
|
+
### CSRF and cookies
|
|
75
|
+
|
|
76
|
+
Threats:
|
|
77
|
+
- Cross-site POST using ambient auth/refresh cookies.
|
|
78
|
+
- Silent SvelteKit refresh on unsafe requests.
|
|
79
|
+
- Insecure cookie defaults in production due to missing `NODE_ENV`.
|
|
80
|
+
|
|
81
|
+
Mitigations:
|
|
82
|
+
- Pipeline CSRF is on by default for unsafe methods carrying access or refresh cookies.
|
|
83
|
+
- Rejects `Sec-Fetch-Site: cross-site`; requires custom CSRF header.
|
|
84
|
+
- SvelteKit silent refresh is restricted to safe methods.
|
|
85
|
+
- Cookies default to `Secure` and `__Host-` names unless explicitly opted out for local HTTP.
|
|
86
|
+
|
|
87
|
+
Residual risks:
|
|
88
|
+
- Hosts can disable CSRF or widen skip paths; review configuration and CORS policy together.
|
|
89
|
+
|
|
90
|
+
### IAM/RBAC authorization and cache invalidation
|
|
91
|
+
|
|
92
|
+
Threats:
|
|
93
|
+
- Public route accidentally declares permission or protected route lacks permission.
|
|
94
|
+
- Stale permission cache after role/binding mutation.
|
|
95
|
+
- Service account inherits user/group permissions unexpectedly.
|
|
96
|
+
|
|
97
|
+
Mitigations:
|
|
98
|
+
- Startup rejects `security: ['none']` plus `permission` collisions.
|
|
99
|
+
- Fortress-managed route pipeline default-denies routes with no usable security metadata.
|
|
100
|
+
- Route manifest classifies every mounted route; drift helpers compare manifest/OpenAPI/RBAC.
|
|
101
|
+
- IAM service invalidates permission cache on permission/role/group/binding mutations.
|
|
102
|
+
- Service accounts have their own subject type and do not inherit group membership.
|
|
103
|
+
|
|
104
|
+
Residual risks:
|
|
105
|
+
- Host-owned routes must opt into `protect()` or adapter route maps; unwrapped routes are application responsibility.
|
|
106
|
+
|
|
107
|
+
### API-key and service-account flows
|
|
108
|
+
|
|
109
|
+
Threats:
|
|
110
|
+
- API key mints broader keys or manages other principals.
|
|
111
|
+
- API key bypasses IAM by being accepted as a user session.
|
|
112
|
+
- Leaked key continues indefinitely.
|
|
113
|
+
|
|
114
|
+
Mitigations:
|
|
115
|
+
- API keys resolve through plugin principal chain to `USER` or `SERVICE_ACCOUNT` subject.
|
|
116
|
+
- RBAC uses subject type and optional credential scopes.
|
|
117
|
+
- Self-service API-key routes deny API-key credentials from minting/listing/revoking/rotating keys.
|
|
118
|
+
- Keys are stored hashed and can expire/revoke; admin routes require explicit permissions.
|
|
119
|
+
|
|
120
|
+
Residual risks:
|
|
121
|
+
- Hosts should rate-limit key usage and rotate/revoke leaked keys quickly.
|
|
122
|
+
|
|
123
|
+
### Tenancy and data isolation
|
|
124
|
+
|
|
125
|
+
Threats:
|
|
126
|
+
- Header-selected tenant lets one user choose another tenant.
|
|
127
|
+
- Tenant code/tax ID injected into SQL identifiers.
|
|
128
|
+
- `search_path` set on one pooled connection but query runs on another.
|
|
129
|
+
- Missing tenant claim silently falls back to public/shared data.
|
|
130
|
+
|
|
131
|
+
Mitigations:
|
|
132
|
+
- Tenant context comes from verified `claims.customClaims.tenantId`, issued from `tenant_user` membership.
|
|
133
|
+
- Tenant schema names use numeric database ids (`tenant_<id>`) and validated prefix.
|
|
134
|
+
- Invalid tenant claims are rejected before schema switching.
|
|
135
|
+
- PostgreSQL `search_path` is pinned with bound `set_config('search_path', ?, true)` inside the same transaction/connection as the operation.
|
|
136
|
+
- Tenant business tables should live only in tenant schemas, not `public`.
|
|
137
|
+
|
|
138
|
+
Residual risks:
|
|
139
|
+
- JWT tenant membership is stale until access token expiry/refresh/session revocation.
|
|
140
|
+
- Per-tenant business-table migrations are host-owned via `onSchemaCreated` or deployment tooling.
|
|
141
|
+
|
|
142
|
+
## Security review packet
|
|
143
|
+
|
|
144
|
+
Provide an independent reviewer with:
|
|
145
|
+
|
|
146
|
+
- This threat model.
|
|
147
|
+
- `docs/security.md` and `SECURITY.md`.
|
|
148
|
+
- Route manifest JSON from the reviewed configuration (`fortress.manifest`).
|
|
149
|
+
- OpenAPI spec from the same configuration.
|
|
150
|
+
- Migration status/drift output (`getMigrationStatus`, `detectMigrationDrift`).
|
|
151
|
+
- Architecture overview: `docs/architecture.md`.
|
|
152
|
+
- Relevant prior review/remediation docs in `docs/*review*` and `docs/*remediation*`.
|
|
153
|
+
|
|
154
|
+
## CI checks recommended
|
|
155
|
+
|
|
156
|
+
- `bun run lint`
|
|
157
|
+
- `bun run typecheck`
|
|
158
|
+
- `bun run test`
|
|
159
|
+
- `fortress manifest:check` for core surface plus app-level `detectRouteManifestDrift()`.
|
|
160
|
+
- `fortress migrate:check` plus live `detectMigrationDrift()` against deployment DBs.
|
|
161
|
+
- OpenAPI diff review for newly-public endpoints.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
| Example | Shows | Run |
|
|
4
|
+
|---|---|---|
|
|
5
|
+
| [`hono-app`](./hono-app/index.ts) | Hono, plugin composition, host routes, validation, OpenAPI | `bun run dev` |
|
|
6
|
+
| [`express-app`](./express-app/index.ts) | Express-compatible middleware, plugins, validation | `bun run examples/express-app/index.ts` |
|
|
7
|
+
| [`sveltekit-app`](./sveltekit-app/README.md) | Handle hook, locals, form actions, OAuth consent | See its README |
|
|
8
|
+
| [`policy`](./policy/fortress.policy.json) | Policy-as-code input | `fortress policy:summary --file examples/policy/fortress.policy.json` |
|
|
9
|
+
|
|
10
|
+
## Cookie login with Hono
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { Hono } from 'hono';
|
|
14
|
+
import { createFortress } from '@bajustone/fortress';
|
|
15
|
+
import { mountFortress } from '@bajustone/fortress/hono';
|
|
16
|
+
|
|
17
|
+
const fortress = createFortress({
|
|
18
|
+
database,
|
|
19
|
+
jwt: { key: process.env.FORTRESS_JWT_SECRET! },
|
|
20
|
+
cookies: { secure: false }, // local HTTP only
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const app = new Hono();
|
|
24
|
+
mountFortress(app, fortress);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Unsafe cookie-authenticated requests send the CSRF header:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
await fetch('/auth/logout', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: { 'X-Fortress-CSRF': '1' },
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
See [Security](../docs/security.md#csrf-protection).
|
|
37
|
+
|
|
38
|
+
## Bearer-only API
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const fortress = createFortress({
|
|
42
|
+
database,
|
|
43
|
+
jwt: { key },
|
|
44
|
+
csrf: { enabled: false }, // only when cookies are never accepted
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const { authMiddleware } = createHonoMiddleware(fortress);
|
|
48
|
+
app.use('/api/*', authMiddleware);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```http
|
|
52
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Service account with a scoped API key
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const account = await fortress.iam.createServiceAccount({
|
|
59
|
+
name: 'ci-bot',
|
|
60
|
+
displayName: 'CI',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await fortress.iam.bindRoleToServiceAccount(account.id, deployerRole.id);
|
|
64
|
+
|
|
65
|
+
const credential = await fortress.plugins['api-key'].createKey({
|
|
66
|
+
subject: { type: 'SERVICE_ACCOUNT', id: account.id },
|
|
67
|
+
name: 'deploy',
|
|
68
|
+
scopes: ['deploy:run'],
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```http
|
|
73
|
+
Authorization: ApiKey fortress_sk_...
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Apply a policy
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import {
|
|
80
|
+
applyPolicyPlan,
|
|
81
|
+
diffPolicy,
|
|
82
|
+
loadPolicy,
|
|
83
|
+
} from '@bajustone/fortress';
|
|
84
|
+
|
|
85
|
+
const { policy } = await loadPolicy();
|
|
86
|
+
const plan = await diffPolicy(policy, fortress.iam);
|
|
87
|
+
const result = await applyPolicyPlan(plan, fortress.iam);
|
|
88
|
+
|
|
89
|
+
if (result.errors.length)
|
|
90
|
+
throw new Error(JSON.stringify(result.errors));
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
See [Policy as code](../docs/policy-as-code.md).
|
|
94
|
+
|
|
95
|
+
## PostgreSQL schema tenancy
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { tenancy } from '@bajustone/fortress/plugins/tenancy';
|
|
99
|
+
|
|
100
|
+
const fortress = createFortress({
|
|
101
|
+
database: postgresAdapter,
|
|
102
|
+
jwt: { key },
|
|
103
|
+
plugins: [tenancy({
|
|
104
|
+
routes: true,
|
|
105
|
+
onSchemaCreated: async (schema, rawQuery) => {
|
|
106
|
+
await rawQuery(`CREATE TABLE "${schema}".widgets (id BIGSERIAL PRIMARY KEY)`);
|
|
107
|
+
},
|
|
108
|
+
})] as const,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const tenant = await fortress.plugins.tenancy.createTenant({
|
|
112
|
+
name: 'Acme',
|
|
113
|
+
taxId: 'acme',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await fortress.plugins.tenancy.addUserToTenant(user.id, tenant.id);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
See [Tenancy](../docs/plugins/tenancy.md).
|