@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,59 @@
|
|
|
1
|
+
# Drop-in GitHub Actions workflow that runs the Fortress CI checks.
|
|
2
|
+
#
|
|
3
|
+
# Save as `.github/workflows/fortress-ci.yml` in your repo. Adapt:
|
|
4
|
+
# - Node/Bun version if you pin a different one.
|
|
5
|
+
# - The path to your fortress instance (the example assumes
|
|
6
|
+
# `src/lib/fortress.ts` exports a configured `fortress`).
|
|
7
|
+
# - Install command (`bun install` / `pnpm i` / `npm ci`).
|
|
8
|
+
#
|
|
9
|
+
# What it runs:
|
|
10
|
+
# - `fortress manifest:check` → route-security manifest drift
|
|
11
|
+
# - `fortress check:public-routes` → catches `.security('none')` on routes
|
|
12
|
+
# that shouldn't be public (against the bundled Fortress allow-list)
|
|
13
|
+
# - `fortress migrate:check` → bundled migration catalog consistency
|
|
14
|
+
# - `bun run test` → your suite, which should include
|
|
15
|
+
# `runFortressChecks()` against your real fortress instance with the
|
|
16
|
+
# test adapter (see docs/ci.md for a sample test file).
|
|
17
|
+
|
|
18
|
+
name: fortress-ci
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
push:
|
|
22
|
+
branches: [main]
|
|
23
|
+
pull_request:
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
fortress-checks:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
timeout-minutes: 10
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
|
|
32
|
+
- uses: oven-sh/setup-bun@v2
|
|
33
|
+
with:
|
|
34
|
+
bun-version: latest
|
|
35
|
+
|
|
36
|
+
- name: Install dependencies
|
|
37
|
+
run: bun install --frozen-lockfile
|
|
38
|
+
|
|
39
|
+
- name: Lint
|
|
40
|
+
run: bun run lint
|
|
41
|
+
|
|
42
|
+
- name: Typecheck
|
|
43
|
+
run: bun run typecheck
|
|
44
|
+
|
|
45
|
+
- name: Fortress route-manifest drift
|
|
46
|
+
run: bunx fortress manifest:check
|
|
47
|
+
|
|
48
|
+
- name: Fortress public-route allow-list
|
|
49
|
+
run: bunx fortress check:public-routes
|
|
50
|
+
|
|
51
|
+
- name: Fortress migration catalog
|
|
52
|
+
run: bunx fortress migrate:check
|
|
53
|
+
|
|
54
|
+
- name: Unit tests
|
|
55
|
+
run: bun run test
|
|
56
|
+
|
|
57
|
+
# Optional: build artifact for verifying types compile cleanly
|
|
58
|
+
- name: Library build (publish dry-run)
|
|
59
|
+
run: bunx tsup
|
package/docs/ci.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# CI checks
|
|
2
|
+
|
|
3
|
+
Fortress ships a bundle of reusable CI checks under
|
|
4
|
+
`@bajustone/fortress/testing` plus a `fortress check:*` CLI namespace.
|
|
5
|
+
The same drift detectors Fortress uses internally to keep its own
|
|
6
|
+
routes/migrations/RBAC mappings honest are exposed to consumer apps so
|
|
7
|
+
you can gate deploys on them.
|
|
8
|
+
|
|
9
|
+
## What's covered
|
|
10
|
+
|
|
11
|
+
| Check | API | CLI | Catches |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| Route-manifest drift | `checkRouteManifestDrift(fortress)` | `fortress manifest:check` / `fortress check:routes` | Mounted routes not in the manifest, manifest routes not mounted, RBAC permission/OpenAPI mismatches |
|
|
14
|
+
| Public-route allow-list | `checkPublicRoutes(fortress, { allow })` | `fortress check:public-routes` | A new route marked `.security('none')` or `bearerKind:'oauth'` that wasn't reviewed |
|
|
15
|
+
| Migration drift | `checkMigrationDrift(adapter)` | `fortress migrate:check` / `fortress check:migrations` | Missing version table, pending migrations, missing Fortress tables, DB ahead of bundled catalog |
|
|
16
|
+
| Auth smoke test | `smokeTestAuth(fortress)` | _(run from a test file)_ | User creation/login/access-token verification/refresh/logout regression |
|
|
17
|
+
| Aggregator | `runFortressChecks({ fortress })` | _(run from a test file)_ | All of the above with a single ok/messages roll-up |
|
|
18
|
+
|
|
19
|
+
`runFortressChecks` is the convenience entry point — wire it into a
|
|
20
|
+
single vitest test or a deploy preflight script. Under Node, install
|
|
21
|
+
`better-sqlite3` as a development dependency for `createTestAdapter()`;
|
|
22
|
+
Bun uses `bun:sqlite` without an extra driver.
|
|
23
|
+
|
|
24
|
+
## In a vitest test
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { describe, expect, it } from 'vitest';
|
|
28
|
+
import { createFortress } from '@bajustone/fortress';
|
|
29
|
+
import { createTestAdapter, runFortressChecks } from '@bajustone/fortress/testing';
|
|
30
|
+
import { config } from '../src/lib/fortress-config';
|
|
31
|
+
|
|
32
|
+
describe('fortress CI checks', () => {
|
|
33
|
+
it('passes every Fortress drift checker', async () => {
|
|
34
|
+
// Build the fortress instance with your real plugin set against an
|
|
35
|
+
// in-memory adapter so smokeTestAuth has somewhere to write.
|
|
36
|
+
const fortress = createFortress({ ...config, database: createTestAdapter() });
|
|
37
|
+
const result = await runFortressChecks({ fortress });
|
|
38
|
+
if (!result.ok)
|
|
39
|
+
console.error(result.messages.join('\n'));
|
|
40
|
+
expect(result.ok).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This single test catches:
|
|
46
|
+
|
|
47
|
+
- A new plugin route that's accidentally `.security('none')`.
|
|
48
|
+
- A migration that was added without bumping `fortress_schema_version`.
|
|
49
|
+
- A manifest entry that no longer corresponds to a mounted route.
|
|
50
|
+
- A regression in the auth pipeline (create user/login/token verification/refresh/logout).
|
|
51
|
+
|
|
52
|
+
## As a CLI
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
fortress manifest:check # route-security drift (also: check:routes)
|
|
56
|
+
fortress check:public-routes # public-route allow-list (core surface)
|
|
57
|
+
fortress migrate:check # migration catalog (also: check:migrations)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The CLI runs against Fortress's **core auth + IAM** route surface, so it
|
|
61
|
+
catches drift in Fortress itself but doesn't see your plugins. For
|
|
62
|
+
app-level checks (plugin routes + your real config), use the API from a
|
|
63
|
+
vitest test as shown above.
|
|
64
|
+
|
|
65
|
+
## GitHub Actions snippet
|
|
66
|
+
|
|
67
|
+
A drop-in workflow is committed at
|
|
68
|
+
[`docs/ci/github-actions.yml`](./ci/github-actions.yml). Save it as
|
|
69
|
+
`.github/workflows/fortress-ci.yml` in your repo. It runs:
|
|
70
|
+
|
|
71
|
+
1. `bun run lint`
|
|
72
|
+
2. `bun run typecheck`
|
|
73
|
+
3. `bun run typecheck:examples`
|
|
74
|
+
4. `bunx fortress manifest:check`
|
|
75
|
+
5. `bunx fortress check:public-routes`
|
|
76
|
+
6. `bunx fortress migrate:check`
|
|
77
|
+
7. `bun run test` (which should include `runFortressChecks(...)` from
|
|
78
|
+
the snippet above)
|
|
79
|
+
8. `bun run test:integration` for PostgreSQL-backed projects
|
|
80
|
+
9. `bun run build` (verifies distributable output)
|
|
81
|
+
|
|
82
|
+
Adjust `oven-sh/setup-bun` to your preferred Node/PNPM/NPM setup; every
|
|
83
|
+
step is plain shell.
|
|
84
|
+
|
|
85
|
+
## Customising the public-route allow-list
|
|
86
|
+
|
|
87
|
+
The default allow-list covers Fortress's intentional public surface
|
|
88
|
+
(auth open routes + OAuth protocol endpoints). Add your own public
|
|
89
|
+
routes via the `allow` option:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
checkPublicRoutes(fortress, {
|
|
93
|
+
allow: [
|
|
94
|
+
'GET /health',
|
|
95
|
+
'GET /robots.txt',
|
|
96
|
+
'POST /webhooks/incoming',
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
CLI usage repeats `--allow`:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
fortress check:public-routes --allow 'GET /health' --allow 'GET /robots.txt'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If you want a stricter posture (e.g. fail on `oauth-protocol` routes
|
|
108
|
+
that appeared without review), restrict `classifications` to just
|
|
109
|
+
`['public']`.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Compatibility
|
|
2
|
+
|
|
3
|
+
The Fortress test matrix that the maintainer runs locally and via CI.
|
|
4
|
+
Numbers are "tested, working" — Fortress is framework-agnostic by
|
|
5
|
+
construction so versions outside the matrix will usually work, just
|
|
6
|
+
without an explicit test guarantee.
|
|
7
|
+
|
|
8
|
+
## Runtime
|
|
9
|
+
|
|
10
|
+
| Runtime | Minimum | Tested |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| **Bun** | 1.0 | latest |
|
|
13
|
+
| **Node.js** | 20.19 | 20, 22, 24 |
|
|
14
|
+
| **Deno** | 1.40 | latest |
|
|
15
|
+
| **Cloudflare Workers (workerd)** | wrangler 3 | latest |
|
|
16
|
+
| Browsers | n/a | server-only |
|
|
17
|
+
|
|
18
|
+
Bun is the maintainer's primary runtime (test adapter prefers
|
|
19
|
+
`bun:sqlite`; the `bin/fortress.ts` CLI shebang targets Bun). The
|
|
20
|
+
package ships a Node-compatible CJS build via `tsup` so the same module
|
|
21
|
+
works under Node 20.19+ unchanged. Deno + workerd can import and use the runtime-neutral core because Node filesystem/path modules are dynamically loaded only when file-oriented policy/resource helpers are invoked. The Bun-targeted CLI and file helpers require a Node-compatible filesystem runtime.
|
|
22
|
+
|
|
23
|
+
## Frameworks
|
|
24
|
+
|
|
25
|
+
| Adapter | Package | Tested versions |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Hono | `@bajustone/fortress/hono` | `hono@^4.12` |
|
|
28
|
+
| Express | `@bajustone/fortress/express` | `express@^4` and `^5` (duck-typed; no `@types/express` dep) |
|
|
29
|
+
| SvelteKit | `@bajustone/fortress/sveltekit` | `@sveltejs/kit@^2` |
|
|
30
|
+
|
|
31
|
+
The Express adapter uses minimal duck-typed interfaces (`ExpressRequest`,
|
|
32
|
+
`ExpressResponse`, `ExpressMiddleware`) so consumers bring their own
|
|
33
|
+
Express version. The SvelteKit subpath uses the optional `@sveltejs/kit@^2`
|
|
34
|
+
peer directly for its strict public `Handle`/`Action` types and runtime
|
|
35
|
+
`redirect()`/`fail()` primitives; install that peer when using the adapter.
|
|
36
|
+
|
|
37
|
+
## Databases
|
|
38
|
+
|
|
39
|
+
| Driver | Adapter | Tested |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| PostgreSQL via `drizzle-orm/postgres-js` | `@bajustone/fortress/drizzle` (`dialect: 'pg'`) | `postgres@^3.4` |
|
|
42
|
+
| PostgreSQL via `drizzle-orm/node-postgres` | same | `pg@^8` |
|
|
43
|
+
| SQLite via `drizzle-orm/bun-sqlite` | same (`dialect: 'sqlite'`) | bundled with Bun |
|
|
44
|
+
| SQLite via `drizzle-orm/better-sqlite3` | same | `better-sqlite3@^12.8` |
|
|
45
|
+
|
|
46
|
+
Drizzle minimum: `drizzle-orm@^0.45`. Earlier versions are missing
|
|
47
|
+
features Fortress relies on (`$dynamic()`, `getTableColumns`,
|
|
48
|
+
`getSetCookie` polyfill).
|
|
49
|
+
|
|
50
|
+
The `@bajustone/fortress/testing` subpath uses `bun:sqlite` under Bun and the optional `better-sqlite3@^12.8` peer under Node. Install that peer in Node test projects.
|
|
51
|
+
|
|
52
|
+
Custom adapters: implement the `DatabaseAdapter` interface in
|
|
53
|
+
`src/adapters/database/index.ts` to back Fortress with any datastore.
|
|
54
|
+
The IAM service uses a small set of generic operations
|
|
55
|
+
(`create`/`findOne`/`findMany`/`update`/`delete`/`count`/`transaction`,
|
|
56
|
+
optional `rawQuery`) so even non-SQL stores are workable.
|
|
57
|
+
|
|
58
|
+
## Optional integrations
|
|
59
|
+
|
|
60
|
+
| Capability | Peer | Tested |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| OpenTelemetry metrics + traces | `@opentelemetry/api@^1.9` | latest with `@opentelemetry/sdk-node` |
|
|
63
|
+
| Argon2id password hashing (native) | `argon2@^0.31` | optional; Fortress ships a `hash-wasm` fallback |
|
|
64
|
+
| WebAuthn server | `@simplewebauthn/server@^13.1` | bundled dependency |
|
|
65
|
+
| OAuth signing (RS256 id_token) | `jose@^6.2` | bundled dependency |
|
|
66
|
+
|
|
67
|
+
## What the CI matrix runs today
|
|
68
|
+
|
|
69
|
+
The repository workflow ([`.github/workflows/ci.yml`](../.github/workflows/ci.yml))
|
|
70
|
+
executes:
|
|
71
|
+
|
|
72
|
+
- **`lint`** — eslint + `tsc --noEmit` (Bun, ubuntu-latest).
|
|
73
|
+
- **`unit`** — the full SQLite test suite across a runtime matrix:
|
|
74
|
+
**Bun**, **Node 20**, and **Node 22**. Bun runs `bun:sqlite`; the Node
|
|
75
|
+
jobs run the same vitest suite under `better-sqlite3`, keeping the
|
|
76
|
+
Node-compatible build honest.
|
|
77
|
+
- **`integration`** — the PostgreSQL suite via testcontainers
|
|
78
|
+
(`bun run test:integration`), covering the `pg` dialect, the tenancy
|
|
79
|
+
connection-pinning / `search_path` isolation, the migration upgrade
|
|
80
|
+
fixture, and the framework adapters end-to-end. This is the heavier job,
|
|
81
|
+
so it runs on pull requests, pushes to `main`, and a **nightly cron**
|
|
82
|
+
rather than every branch push.
|
|
83
|
+
- **`jsr-check`** — `deno publish --dry-run` to guard JSR publishability.
|
|
84
|
+
|
|
85
|
+
The consumer-facing drop-in workflow
|
|
86
|
+
([`docs/ci/github-actions.yml`](./ci/github-actions.yml)) is a separate
|
|
87
|
+
template that gates *your* deploys on Fortress's drift checkers
|
|
88
|
+
(`runFortressChecks`); it is not the library's own matrix.
|
|
89
|
+
|
|
90
|
+
> Deno and Cloudflare Workers remain smoke-tested locally rather than in
|
|
91
|
+
> the public matrix (no first-class GitHub runner for the workerd DB
|
|
92
|
+
> story). MySQL is not currently supported — `DrizzleDialect` is
|
|
93
|
+
> `'sqlite' | 'pg'`; it may be re-added once there is a real consumer and
|
|
94
|
+
> a CI lane to keep it honest.
|
|
95
|
+
|
|
96
|
+
## What's intentionally out of scope
|
|
97
|
+
|
|
98
|
+
- **Bun's web runtime APIs** beyond `Request`/`Response`/`crypto`/`fetch`.
|
|
99
|
+
Fortress sticks to the web platform standard so any modern runtime
|
|
100
|
+
works.
|
|
101
|
+
- **ESM-only consumers without an interop layer.** The dual `esm`+`cjs`
|
|
102
|
+
build is shipped; if a consumer's bundler refuses CJS, use the
|
|
103
|
+
`import` map entry.
|
|
104
|
+
- **Cloudflare Workers without external DB.** Workers cannot use
|
|
105
|
+
`bun:sqlite` or `better-sqlite3`. Use the Postgres adapter (or a
|
|
106
|
+
remote DB over HTTP) when targeting workerd.
|
|
107
|
+
|
|
108
|
+
## Reporting a compatibility issue
|
|
109
|
+
|
|
110
|
+
If a combination listed here breaks, open an issue with:
|
|
111
|
+
|
|
112
|
+
1. Runtime + version (`bun --version`, `node --version`, `deno --version`).
|
|
113
|
+
2. Driver + version (`drizzle-orm`, `postgres`, `better-sqlite3`).
|
|
114
|
+
3. The minimal reproduction \u2014 ideally a failing test using
|
|
115
|
+
`createTestAdapter()`.
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Fortress production deployment guide
|
|
2
|
+
|
|
3
|
+
This guide covers what changes between local dev and production for a
|
|
4
|
+
Fortress-backed service. Treat each section as a checklist — every
|
|
5
|
+
production deployment should explicitly answer or skip each item.
|
|
6
|
+
|
|
7
|
+
## 1. JWT secret
|
|
8
|
+
|
|
9
|
+
The JWT signing secret (`FortressConfig.jwt.key`) must be **at least
|
|
10
|
+
32 bytes** of cryptographically random material. Anything shorter throws
|
|
11
|
+
at `createFortress` startup.
|
|
12
|
+
|
|
13
|
+
Generate one with the bundled CLI:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
fortress generate-secret
|
|
17
|
+
# 64-byte hex string (128 chars); paste into your secret manager
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Recommendations:
|
|
21
|
+
|
|
22
|
+
- Store the secret in your platform's secret manager (AWS Secrets
|
|
23
|
+
Manager, GCP Secret Manager, Vault, Doppler, Fly secrets, etc.).
|
|
24
|
+
Never commit it.
|
|
25
|
+
- Rotate the secret periodically. Fortress supports an array of secrets
|
|
26
|
+
on `jwt.key` — the first entry is used for **signing**, all entries
|
|
27
|
+
are accepted for **verification**. Add the new secret to position 0,
|
|
28
|
+
redeploy, wait until every issued token has expired (typically the
|
|
29
|
+
access-token lifetime + grace), then remove the old one.
|
|
30
|
+
- For multi-region deployments, replicate the same secret across regions
|
|
31
|
+
so tokens issued in one region verify in another.
|
|
32
|
+
|
|
33
|
+
## 2. Cookies behind reverse proxies
|
|
34
|
+
|
|
35
|
+
Fortress defaults to `__Host-fortress_access` / `__Host-fortress_refresh`
|
|
36
|
+
cookies with `HttpOnly; Secure; SameSite=Lax; Path=/`. The `__Host-`
|
|
37
|
+
prefix is the strongest browser-enforced isolation: the cookie is bound
|
|
38
|
+
to the exact origin, has no `Domain` attribute, and **must** be sent
|
|
39
|
+
over HTTPS.
|
|
40
|
+
|
|
41
|
+
When deploying behind a reverse proxy (nginx, Caddy, ELB, Fly, Cloud
|
|
42
|
+
Run, fly-replay):
|
|
43
|
+
|
|
44
|
+
- **Terminate TLS at the proxy.** Set `X-Forwarded-Proto: https` so the
|
|
45
|
+
app sees the original scheme. Fortress only relies on this for IP /
|
|
46
|
+
user-agent metadata, but most frameworks (Hono, Express, SvelteKit)
|
|
47
|
+
use it to construct absolute URLs and cookie attributes.
|
|
48
|
+
- **Forward the original host.** Set `Host` to the client-facing
|
|
49
|
+
hostname — `__Host-` cookies are pinned to it.
|
|
50
|
+
- **Forward `X-Forwarded-For`.** Fortress reads `X-Forwarded-For` then
|
|
51
|
+
`X-Real-IP` for rate-limit keys and refresh-token metadata. Strip any
|
|
52
|
+
client-supplied values at the edge; only trust hops you control.
|
|
53
|
+
- **Do not strip cookies.** Some CDN configurations drop `Set-Cookie`
|
|
54
|
+
by default on cacheable responses. Whitelist
|
|
55
|
+
`set-cookie: __Host-fortress_*` (or your renamed prefix) on every
|
|
56
|
+
Fortress-served path.
|
|
57
|
+
|
|
58
|
+
To opt into a custom cookie name / scope, set
|
|
59
|
+
`FortressConfig.cookies` — for example a top-level apex domain:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
createFortress({
|
|
63
|
+
// ...
|
|
64
|
+
cookies: {
|
|
65
|
+
accessName: 'app_access',
|
|
66
|
+
refreshName: 'app_refresh',
|
|
67
|
+
domain: 'example.com',
|
|
68
|
+
secure: true,
|
|
69
|
+
sameSite: 'lax',
|
|
70
|
+
path: '/',
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Setting a `domain` removes the `__Host-` prefix automatically because
|
|
76
|
+
the prefix forbids cross-subdomain cookies.
|
|
77
|
+
|
|
78
|
+
## 3. CSRF: opt-out rules and recipes
|
|
79
|
+
|
|
80
|
+
Pipeline CSRF is on by default for unsafe methods (POST/PUT/PATCH/DELETE)
|
|
81
|
+
on Fortress-managed routes when the request carries a Fortress auth
|
|
82
|
+
cookie. Bearer-only and API-key requests are immune by construction and
|
|
83
|
+
are skipped.
|
|
84
|
+
|
|
85
|
+
Common production tweaks:
|
|
86
|
+
|
|
87
|
+
- **Pure API backends (no browser cookies):** set `csrf: { enabled: false }`.
|
|
88
|
+
Every request authenticates via bearer/API-key; there is no ambient
|
|
89
|
+
credential to protect.
|
|
90
|
+
- **SPA on the same origin:** keep the default. The SPA sends a custom
|
|
91
|
+
header (`X-Fortress-CSRF: 1` by default — value is not validated, the
|
|
92
|
+
*presence* is what matters because cross-site code cannot set custom
|
|
93
|
+
headers without a CORS preflight you've allowed).
|
|
94
|
+
- **SPA on a sibling subdomain** (`app.example.com` calling
|
|
95
|
+
`api.example.com`): keep the default `rejectSameSite: false`; the
|
|
96
|
+
legitimate `Sec-Fetch-Site: same-site` traffic must pass.
|
|
97
|
+
- **Single-host browser app, no cross-subdomain calls:** set
|
|
98
|
+
`csrf: { rejectSameSite: true }` to also reject same-site requests,
|
|
99
|
+
closing the residual risk of a malicious subdomain.
|
|
100
|
+
|
|
101
|
+
Skip specific paths if a third party must call them without a custom
|
|
102
|
+
header (webhooks, OAuth `/oauth/token`, etc.):
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
csrf: {
|
|
106
|
+
enabled: true,
|
|
107
|
+
skipPaths: ['/oauth/token', '/oauth/revoke', '/webhooks/incoming'],
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`/oauth/*` protocol endpoints are already classified as
|
|
112
|
+
`oauth-protocol` in the route manifest and self-authenticate via
|
|
113
|
+
bearer tokens; they don't need a CSRF skip unless you've put them
|
|
114
|
+
behind cookie auth somehow.
|
|
115
|
+
|
|
116
|
+
## 4. CORS
|
|
117
|
+
|
|
118
|
+
Fortress does not ship a CORS middleware — each adapter delegates to its
|
|
119
|
+
host framework. Mount your framework's CORS middleware **before**
|
|
120
|
+
`mountFortress`, and:
|
|
121
|
+
|
|
122
|
+
- Set `Access-Control-Allow-Credentials: true` if your SPA sends
|
|
123
|
+
cookies.
|
|
124
|
+
- Set `Access-Control-Allow-Origin` to the explicit SPA origin (never
|
|
125
|
+
`*`) when credentials are allowed.
|
|
126
|
+
- Include `X-Fortress-CSRF` (and any custom header you renamed it to)
|
|
127
|
+
in `Access-Control-Allow-Headers`.
|
|
128
|
+
- Include `Authorization` and `X-API-Key` if you authenticate via
|
|
129
|
+
bearer/API-key from the browser.
|
|
130
|
+
|
|
131
|
+
Example (Hono):
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { cors } from 'hono/cors';
|
|
135
|
+
|
|
136
|
+
app.use('*', cors({
|
|
137
|
+
origin: 'https://app.example.com',
|
|
138
|
+
credentials: true,
|
|
139
|
+
allowHeaders: ['Content-Type', 'Authorization', 'X-API-Key', 'X-Fortress-CSRF'],
|
|
140
|
+
exposeHeaders: ['Retry-After'],
|
|
141
|
+
}));
|
|
142
|
+
mountFortress(app, fortress);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 5. HTTPS requirements
|
|
146
|
+
|
|
147
|
+
- The OAuth plugin **refuses to start** in production
|
|
148
|
+
(`NODE_ENV=production`) unless `issuerUrl` is HTTPS. Loopback URLs are
|
|
149
|
+
exempt for dev.
|
|
150
|
+
- `__Host-` cookies require HTTPS at the browser. If you do not run
|
|
151
|
+
HTTPS in production, set `cookies: { secure: false }` — but you are
|
|
152
|
+
giving up the strongest cookie isolation and exposing tokens to any
|
|
153
|
+
on-path attacker.
|
|
154
|
+
- Every external dependency Fortress speaks to (social-login providers,
|
|
155
|
+
webhook destinations, OIDC discovery URLs) should use HTTPS. The
|
|
156
|
+
social-login plugin assumes provider URLs are HTTPS; provider configs
|
|
157
|
+
with `http://` URLs are accepted but should be flagged in code review.
|
|
158
|
+
|
|
159
|
+
## 6. PostgreSQL vs SQLite guarantees
|
|
160
|
+
|
|
161
|
+
| Concern | PostgreSQL | SQLite (`bun:sqlite`, `better-sqlite3`) |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| Concurrent writers | Yes, MVCC | One writer at a time (serialized in the adapter via `BEGIN IMMEDIATE`) |
|
|
164
|
+
| Tenancy `search_path` switching | Supported (transaction-pinned `set_config`) | Not supported — tenancy plugin is pass-through |
|
|
165
|
+
| Recommended for production | ✅ | Single-node deployments only (Fly Volumes, edge SQLite) |
|
|
166
|
+
| Migrations against a live DB | Standard | Use a small migration window; SQLite serializes writers |
|
|
167
|
+
| Drift checker `missingTables` | Reads `information_schema` | Reads `sqlite_master` |
|
|
168
|
+
| OAuth `oauth_signing_key` | Persisted; rotation safe | Persisted; rotation safe |
|
|
169
|
+
|
|
170
|
+
SQLite is perfectly fine for small to medium services that don't need
|
|
171
|
+
horizontal write scaling. Use Postgres when you need multi-region, true
|
|
172
|
+
tenancy schema isolation, or when an external tool needs to read the
|
|
173
|
+
schema (BI, replication).
|
|
174
|
+
|
|
175
|
+
## 7. OAuth / OIDC RP setup
|
|
176
|
+
|
|
177
|
+
The OAuth plugin acts as an **authorization server**. To enable it:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
oauth({
|
|
181
|
+
issuerUrl: 'https://auth.example.com', // HTTPS mandatory in prod
|
|
182
|
+
loginUrl: 'https://app.example.com/signin', // your SPA login page
|
|
183
|
+
consentUrl: 'https://app.example.com/oauth/consent', // your SPA consent page
|
|
184
|
+
// Optional: per-deployment OIDC userinfo claim shaping
|
|
185
|
+
userinfoClaims: async (user, scope) => ({ /* ... */ }),
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Key production knobs:
|
|
190
|
+
|
|
191
|
+
- **`issuerUrl`** is signed into every id_token (`iss` claim) and is the
|
|
192
|
+
base of every discovery URL. Setting it wrong locks every client out
|
|
193
|
+
— pick once, document, treat as part of your stable public API.
|
|
194
|
+
- **Discovery** is served from
|
|
195
|
+
`${issuerUrl}/oauth/.well-known/openid-configuration`. Cache for
|
|
196
|
+
minutes, not hours; the JWKS rotation cadence depends on your
|
|
197
|
+
`oauth_signing_key` rotation policy.
|
|
198
|
+
- **JWKS** is served from `${issuerUrl}/oauth/.well-known/jwks.json`.
|
|
199
|
+
RPs typically cache it for 5–15 minutes; signing-key rotations are
|
|
200
|
+
picked up next cache refresh.
|
|
201
|
+
- **Refresh tokens** are rotated by default; reuse triggers a
|
|
202
|
+
family-wide revocation per RFC 9700 §2.2.2. Configure
|
|
203
|
+
`refreshTokenExpirySeconds` to match your session policy (0 disables
|
|
204
|
+
refresh entirely).
|
|
205
|
+
- **Public clients** (SPAs, native apps) must register with
|
|
206
|
+
`tokenEndpointAuthMethod: 'none'` and use PKCE. Confidential clients
|
|
207
|
+
default to `client_secret_basic`; opt into `'client_secret_post'` per
|
|
208
|
+
client if needed.
|
|
209
|
+
- **Per-client `allowedScopes`** narrows what each RP can request.
|
|
210
|
+
Leave blank for legacy clients with broad scopes; set explicitly for
|
|
211
|
+
any new integration.
|
|
212
|
+
|
|
213
|
+
## 8. API-key and service-account operations
|
|
214
|
+
|
|
215
|
+
- API-key plugin endpoints are opt-in (`apiKey({ routes: true })`). In
|
|
216
|
+
production, decide:
|
|
217
|
+
- User self-service rotation: mount `routes: true` and put rate-limit
|
|
218
|
+
on `/api-key/keys`.
|
|
219
|
+
- Admin-only rotation: leave `routes: false` and rotate via the
|
|
220
|
+
admin plugin's `/admin/users/:userId/api-keys` endpoints (requires
|
|
221
|
+
`apiKey:manage` permission).
|
|
222
|
+
- Service accounts have no sessions and no group memberships. Provision
|
|
223
|
+
them via `/iam/service-accounts` (admin plugin) or
|
|
224
|
+
`fortress.iam.createServiceAccount(...)`. Mint API keys against them
|
|
225
|
+
via `/admin/service-accounts/:id/api-keys`.
|
|
226
|
+
- Hash storage: API keys are stored as SHA-256 of the prefix + secret
|
|
227
|
+
with the plaintext returned only on issuance. If a key leaks, revoke
|
|
228
|
+
it via `DELETE /api-key/keys/:id` (or admin route); rotation via
|
|
229
|
+
`POST /api-key/keys/:id/rotate` keeps the same `name` and revokes the
|
|
230
|
+
predecessor in a single transaction.
|
|
231
|
+
- Rate-limit API-key issuance separately from user logins:
|
|
232
|
+
`rateLimit({ apiKeyIssue: { maxPerIp: 5, maxPerUser: 5, windowSeconds: 3600 } })`.
|
|
233
|
+
|
|
234
|
+
## 9. Migration runbook
|
|
235
|
+
|
|
236
|
+
1. **Pre-deploy:** run `fortress migrate:check` in CI; non-zero exit
|
|
237
|
+
blocks the deploy. This catches "developer added a new bundled
|
|
238
|
+
migration but forgot to commit the SQL" before it reaches prod.
|
|
239
|
+
2. **Deploy:** on application start, call
|
|
240
|
+
`migrateUp(adapter)` from your boot script. Both `migrateUp` and
|
|
241
|
+
`migrateDown` are idempotent.
|
|
242
|
+
3. **Post-deploy:** in your healthcheck, call
|
|
243
|
+
`detectMigrationDrift(adapter)` and fail the healthcheck on
|
|
244
|
+
`missingTables` / `missingVersionTable`. The load balancer pulls the
|
|
245
|
+
instance out of rotation until drift is fixed.
|
|
246
|
+
|
|
247
|
+
For irreversible migrations (column drops, type narrowing), keep the
|
|
248
|
+
old code shape supported for at least one release before removing it,
|
|
249
|
+
and take a database snapshot immediately before applying. See
|
|
250
|
+
[docs/migrations/upgrade-guide.md](./migrations/upgrade-guide.md) for
|
|
251
|
+
the runtime API and CLI reference.
|
|
252
|
+
|
|
253
|
+
## 10. Observability
|
|
254
|
+
|
|
255
|
+
- Pass a structured logger (`pino`, `bunyan`, framework-provided) to
|
|
256
|
+
`FortressConfig.logger` so security-critical events (token reuse,
|
|
257
|
+
account lockout, OAuth errors) are searchable.
|
|
258
|
+
- Wire OpenTelemetry via `@bajustone/fortress/otel`'s
|
|
259
|
+
`createOtelTelemetry()`. Fortress emits stable
|
|
260
|
+
`db.client.operation.duration`,
|
|
261
|
+
`fortress.auth.events.total`, IAM permission-check histograms, and
|
|
262
|
+
a deny-only span (`fortress.iam.permission_check.deny`).
|
|
263
|
+
- High-cardinality data (user IDs, emails, tenant IDs) is deliberately
|
|
264
|
+
kept off metric attributes — it lives on spans and structured logs.
|
|
265
|
+
|
|
266
|
+
## 11. Backups & disaster recovery
|
|
267
|
+
|
|
268
|
+
- Back up the database before every deploy that ships a migration.
|
|
269
|
+
Test restores quarterly.
|
|
270
|
+
- The `oauth_signing_key` row is **the** RSA signing material for every
|
|
271
|
+
OIDC id_token. Losing it invalidates every outstanding id_token (RPs
|
|
272
|
+
re-authenticate). Treat it like a JWT secret — back it up with the
|
|
273
|
+
database, never export it.
|
|
274
|
+
- Audit-log rows are append-only and hash-chained when
|
|
275
|
+
`auditLog({ hashChain: true })` is enabled. Restoring from backup
|
|
276
|
+
preserves the chain; appending out-of-order rows breaks it. If you
|
|
277
|
+
restore, run a chain verification job before resuming writes.
|
|
278
|
+
- Rate-limit counter store (the in-memory default) is **not** durable.
|
|
279
|
+
In production with multiple instances, bring your own store
|
|
280
|
+
(Redis, Memcached) so limits apply across replicas.
|
|
281
|
+
|
|
282
|
+
## 12. Checklist
|
|
283
|
+
|
|
284
|
+
Use this before promoting a release to production:
|
|
285
|
+
|
|
286
|
+
- [ ] JWT secret is ≥ 32 bytes, stored in a secret manager, rotated on a
|
|
287
|
+
schedule.
|
|
288
|
+
- [ ] HTTPS terminated end-to-end; `X-Forwarded-Proto`, `Host`, and
|
|
289
|
+
`X-Forwarded-For` set by the proxy.
|
|
290
|
+
- [ ] Cookies: `__Host-` prefix or explicit `domain` + `secure: true`.
|
|
291
|
+
- [ ] CSRF policy chosen explicitly (default, `rejectSameSite`, or
|
|
292
|
+
disabled) and documented in code review.
|
|
293
|
+
- [ ] CORS allow-list pins exact SPA origin and exposes the CSRF
|
|
294
|
+
header.
|
|
295
|
+
- [ ] OAuth `issuerUrl` is HTTPS in production; discovery and JWKS
|
|
296
|
+
endpoints reachable from RPs.
|
|
297
|
+
- [ ] Rate-limit plugin mounted with `login`/`register`/`refresh`
|
|
298
|
+
defaults; named rules cover `/api/*`.
|
|
299
|
+
- [ ] Audit-log plugin mounted with `hashChain: true` for any flow
|
|
300
|
+
that ships compliance evidence.
|
|
301
|
+
- [ ] `fortress migrate:check` in CI; `migrateUp` + drift check in the
|
|
302
|
+
app's boot/healthcheck path.
|
|
303
|
+
- [ ] OpenTelemetry exporter or structured logger wired so security
|
|
304
|
+
events leave the box.
|
|
305
|
+
- [ ] Backup + restore tested for the database; signing keys included.
|