@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,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Example Fortress policy-as-code document. Place at the root of your repo as fortress.policy.json (optionally fortress.policy.<env>.json).",
|
|
3
|
+
"resources": [
|
|
4
|
+
{
|
|
5
|
+
"name": "article",
|
|
6
|
+
"actions": ["create", "read", "update", "delete", "publish"],
|
|
7
|
+
"description": "Blog articles"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "audit-log",
|
|
11
|
+
"actions": ["read", "export"],
|
|
12
|
+
"description": "Audit-log entries"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"roles": [
|
|
16
|
+
{
|
|
17
|
+
"name": "editor",
|
|
18
|
+
"description": "Can author and publish articles",
|
|
19
|
+
"permissions": [
|
|
20
|
+
{ "resource": "article", "action": "create" },
|
|
21
|
+
{ "resource": "article", "action": "read" },
|
|
22
|
+
{ "resource": "article", "action": "update" },
|
|
23
|
+
{ "resource": "article", "action": "publish" }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "compliance-reviewer",
|
|
28
|
+
"description": "Read-only access to audit logs",
|
|
29
|
+
"permissions": [
|
|
30
|
+
{ "resource": "audit-log", "action": "read" }
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"groups": [
|
|
35
|
+
{ "name": "engineering", "description": "Engineering team" },
|
|
36
|
+
{ "name": "compliance", "description": "Compliance / risk team" }
|
|
37
|
+
],
|
|
38
|
+
"serviceAccounts": [
|
|
39
|
+
{
|
|
40
|
+
"name": "ci-bot",
|
|
41
|
+
"displayName": "CI Bot",
|
|
42
|
+
"description": "GitHub Actions service account",
|
|
43
|
+
"isActive": true,
|
|
44
|
+
"roles": ["editor"]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Fortress + SvelteKit Example
|
|
2
|
+
|
|
3
|
+
A reference for wiring `@bajustone/fortress` into a SvelteKit project.
|
|
4
|
+
|
|
5
|
+
This directory holds the **Fortress-relevant** files. Drop them into a real
|
|
6
|
+
SvelteKit project (`npm create svelte@latest`) and they work as-is once you
|
|
7
|
+
adjust the imports to use SvelteKit's `$lib` / `$app` aliases.
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `src/lib/server/fortress.ts` | Singleton Fortress instance (server-only). |
|
|
14
|
+
| `src/hooks.server.ts` | Handle hook — primary integration point. |
|
|
15
|
+
| `src/app.d.ts` | Augments `App.Locals` so `event.locals.fortress` is typed. |
|
|
16
|
+
| `src/routes/login/+page.server.ts` | Form-action login. Honors `?flow=<id>` to land back on consent. |
|
|
17
|
+
| `src/routes/dashboard/+page.server.ts` | Protected route reading `getUserId`. |
|
|
18
|
+
| `src/routes/oauth/consent/+page.server.ts` | OAuth consent flow — fetches flow metadata, posts approve/deny. |
|
|
19
|
+
| `src/routes/oauth/consent/+page.svelte` | Branded consent UI rendered in your app, not Fortress. |
|
|
20
|
+
| `src/routes/api/fortress/[...path]/+server.ts` | **Optional** catch-all escape hatch. |
|
|
21
|
+
|
|
22
|
+
## OAuth consent flow (Pattern B)
|
|
23
|
+
|
|
24
|
+
`src/lib/server/fortress.ts` registers the `oauth` plugin with
|
|
25
|
+
`enableAuthorizeEndpoint`/`enableConsentApi` on, and seeds a demo user
|
|
26
|
+
(`alice@example.com` / `hunter2`) plus a demo OAuth client at startup.
|
|
27
|
+
The seeded `client_id` / `client_secret` are logged to the server console.
|
|
28
|
+
|
|
29
|
+
To try the flow end-to-end:
|
|
30
|
+
|
|
31
|
+
1. Start the example (`npm run dev`).
|
|
32
|
+
2. Copy the seeded `client_id` from the server log.
|
|
33
|
+
3. Hit `http://localhost:5173/api/oauth/authorize?client_id=<id>&redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Foauth%2Fcallback-demo&response_type=code&state=xyz&scope=read:posts`.
|
|
34
|
+
4. Fortress 302s to `/login?flow=<id>` (no session). Log in as alice.
|
|
35
|
+
5. The login action redirects to `/oauth/consent?flow=<id>`. The page
|
|
36
|
+
loads flow metadata, you click Allow, and the action redirects the
|
|
37
|
+
browser to the OAuth client's `redirect_uri` with `?code=...&state=xyz`.
|
|
38
|
+
6. The client exchanges the code for an access token via
|
|
39
|
+
`POST /api/oauth/token` (Basic auth with `client_id:client_secret`).
|
|
40
|
+
|
|
41
|
+
No HTML ever leaves Fortress — the consent screen is `+page.svelte`,
|
|
42
|
+
styled however you like.
|
|
43
|
+
|
|
44
|
+
## Two ways to mount Fortress
|
|
45
|
+
|
|
46
|
+
### 1. Handle hook (recommended)
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// src/hooks.server.ts
|
|
50
|
+
import { sequence } from '@sveltejs/kit/hooks';
|
|
51
|
+
import { createSvelteKitHandle } from '@bajustone/fortress/sveltekit';
|
|
52
|
+
import { fortress } from '$lib/server/fortress';
|
|
53
|
+
|
|
54
|
+
export const handle = sequence(
|
|
55
|
+
createSvelteKitHandle(fortress, { basePath: '/api' }),
|
|
56
|
+
);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This single hook:
|
|
60
|
+
|
|
61
|
+
- Intercepts `/api/auth/*`, `/api/iam/*`, plugin paths and delegates to
|
|
62
|
+
`fortress.handleRequest`. The Response (with `Set-Cookie`) is returned
|
|
63
|
+
directly, **bypassing SvelteKit's `csrf.checkOrigin`**.
|
|
64
|
+
- For user routes, populates `event.locals.fortress` from the access cookie
|
|
65
|
+
(or `Authorization: Bearer` fallback). Auto-refreshes when expired.
|
|
66
|
+
- Runs plugin middleware at `before-auth` / `after-auth` / `after-rbac`.
|
|
67
|
+
- Auto-skips during `vite build` prerender via `building`.
|
|
68
|
+
|
|
69
|
+
Zero route files. No `+server.ts` for `/auth/login` etc.
|
|
70
|
+
|
|
71
|
+
### 2. Catch-all `+server.ts` (escape hatch)
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// src/routes/api/fortress/[...path]/+server.ts
|
|
75
|
+
import { toSvelteKitHandler } from '@bajustone/fortress/sveltekit';
|
|
76
|
+
import { fortress } from '$lib/server/fortress';
|
|
77
|
+
|
|
78
|
+
export const { GET, POST, PUT, DELETE, PATCH } = toSvelteKitHandler(fortress);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use this when you want Fortress routes colocated under your normal routes
|
|
82
|
+
tree. Works alongside the handle hook — there is no conflict because the
|
|
83
|
+
handle hook intercepts paths the file handler would have served anyway.
|
|
84
|
+
|
|
85
|
+
## Cookie defaults
|
|
86
|
+
|
|
87
|
+
In production (`NODE_ENV === 'production'`):
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
Set-Cookie: __Host-fortress_access=...; HttpOnly; Secure; SameSite=Lax; Path=/
|
|
91
|
+
Set-Cookie: __Host-fortress_refresh=...; HttpOnly; Secure; SameSite=Lax; Path=/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
In development:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Set-Cookie: fortress_access=...; HttpOnly; SameSite=Lax; Path=/
|
|
98
|
+
Set-Cookie: fortress_refresh=...; HttpOnly; SameSite=Lax; Path=/
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
(`__Host-` prefix requires `Secure`, which breaks localhost over HTTP.)
|
|
102
|
+
|
|
103
|
+
Override via `FortressConfig.cookies` if you need different names, a
|
|
104
|
+
`Domain`, or `SameSite=strict`.
|
|
105
|
+
|
|
106
|
+
## CSRF posture
|
|
107
|
+
|
|
108
|
+
- **Fortress-managed routes** (`/api/auth/*`, etc.) are intercepted *before*
|
|
109
|
+
`resolve()` runs, so SvelteKit's built-in `csrf.checkOrigin` does NOT
|
|
110
|
+
apply. They rely on `SameSite=Lax` cookies and (for OAuth) PKCE.
|
|
111
|
+
- **Form actions** (`?/login`) DO go through `resolve()` and ARE subject to
|
|
112
|
+
SvelteKit's CSRF check. This is good — the check is the right default
|
|
113
|
+
for form-based logins.
|
|
114
|
+
|
|
115
|
+
## Auto-refresh during SSR
|
|
116
|
+
|
|
117
|
+
When a user opens a protected page with an expired access cookie but a
|
|
118
|
+
valid refresh cookie, the handle hook silently refreshes both tokens and
|
|
119
|
+
sets the new cookies before `resolve(event)`. The render proceeds as if
|
|
120
|
+
the access token was never expired.
|
|
121
|
+
|
|
122
|
+
**Caveat**: opening N tabs simultaneously triggers N parallel refresh
|
|
123
|
+
attempts. Fortress's refresh-token family rotation will succeed for one and
|
|
124
|
+
fail (with `TOKEN_REUSE`) for the rest. This is a known JWT+rotation
|
|
125
|
+
trade-off — out of scope for this adapter.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvelteKit `app.d.ts` — augments `App.Locals` so server load functions
|
|
3
|
+
* see `event.locals.fortress` as a typed object instead of `unknown`.
|
|
4
|
+
*
|
|
5
|
+
* In a real project this lives at `src/app.d.ts`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { FortressLocals } from '../../../src/sveltekit';
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
namespace App {
|
|
12
|
+
interface Locals extends FortressLocals {}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvelteKit `hooks.server.ts` — wires Fortress into the request pipeline.
|
|
3
|
+
*
|
|
4
|
+
* In a real project this is `src/hooks.server.ts` and uses `sequence` from
|
|
5
|
+
* `@sveltejs/kit/hooks` to compose multiple handles. This example imports
|
|
6
|
+
* via relative paths so it type-checks against the workspace fortress
|
|
7
|
+
* source — adapt to `$lib/server/fortress` and `@sveltejs/kit/hooks` in
|
|
8
|
+
* your own project.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createSvelteKitHandle } from '../../../src/sveltekit';
|
|
12
|
+
import { fortress } from './lib/server/fortress';
|
|
13
|
+
|
|
14
|
+
// In a real SvelteKit project:
|
|
15
|
+
// import { sequence } from '@sveltejs/kit/hooks';
|
|
16
|
+
// export const handle = sequence(createSvelteKitHandle(fortress, { basePath: '/api' }));
|
|
17
|
+
export const handle = createSvelteKitHandle(fortress, {
|
|
18
|
+
basePath: '/api',
|
|
19
|
+
routeMap: {
|
|
20
|
+
'GET /api/users': { resource: 'user', action: 'list' },
|
|
21
|
+
},
|
|
22
|
+
skipPaths: ['/api/health'],
|
|
23
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-only Fortress instance.
|
|
3
|
+
*
|
|
4
|
+
* In a real SvelteKit project this lives at `src/lib/server/fortress.ts`
|
|
5
|
+
* and is imported as `$lib/server/fortress`. The `lib/server/*` convention
|
|
6
|
+
* guarantees the file never ends up in client bundles.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createFortress } from '../../../../../src';
|
|
10
|
+
import { oauth } from '../../../../../src/plugins/oauth';
|
|
11
|
+
import { createTestAdapter } from '../../../../../src/testing';
|
|
12
|
+
|
|
13
|
+
export const fortress = createFortress({
|
|
14
|
+
jwt: {
|
|
15
|
+
key: process.env.JWT_SECRET ?? 'dev-secret-minimum-32-bytes-long!!',
|
|
16
|
+
accessTokenExpirySeconds: 900,
|
|
17
|
+
refreshTokenExpirySeconds: 60 * 60 * 24 * 7,
|
|
18
|
+
},
|
|
19
|
+
// In a real project, swap this for createDrizzleAdapter(...).
|
|
20
|
+
database: createTestAdapter(),
|
|
21
|
+
plugins: [
|
|
22
|
+
oauth({
|
|
23
|
+
issuerUrl: 'http://localhost:5173',
|
|
24
|
+
// Pattern B (SPA-friendly) consent flow. The consent UI lives in
|
|
25
|
+
// SvelteKit; Fortress just owns the state machine.
|
|
26
|
+
enableAuthorizeEndpoint: true,
|
|
27
|
+
enableConsentApi: true,
|
|
28
|
+
// Where Fortress 302s the browser to. These are SvelteKit page paths
|
|
29
|
+
// — host-relative because the API and the web app share an origin
|
|
30
|
+
// in this example. For a cross-origin setup, use absolute URLs.
|
|
31
|
+
loginUrl: '/login',
|
|
32
|
+
consentUrl: '/oauth/consent',
|
|
33
|
+
scopePermissionMap: {
|
|
34
|
+
'read:posts': { resource: 'post', action: 'read' },
|
|
35
|
+
'write:posts': { resource: 'post', action: 'create' },
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Seed a demo user + OAuth client on startup so the example actually works
|
|
43
|
+
* end-to-end against the in-memory adapter (which loses state on reload).
|
|
44
|
+
*
|
|
45
|
+
* Don't do this in production — register clients via your admin tooling and
|
|
46
|
+
* persist them in your real database.
|
|
47
|
+
*/
|
|
48
|
+
export const demoSeedPromise: Promise<{ clientId: string; clientSecret: string }> = (async () => {
|
|
49
|
+
// Demo user: alice@example.com / hunter2.
|
|
50
|
+
// The in-memory adapter is wiped on each process boot, so we always
|
|
51
|
+
// try to create — swallow conflicts if the module is re-evaluated.
|
|
52
|
+
await fortress.auth
|
|
53
|
+
.createUser({
|
|
54
|
+
email: 'alice@example.com',
|
|
55
|
+
name: 'Alice',
|
|
56
|
+
password: 'hunter2',
|
|
57
|
+
})
|
|
58
|
+
.catch(() => undefined);
|
|
59
|
+
|
|
60
|
+
// Demo OAuth client. clientId/clientSecret are returned only here, so we
|
|
61
|
+
// log them so the README/CLI can use them.
|
|
62
|
+
const client = await fortress.plugins.oauth.createClient({
|
|
63
|
+
name: 'Example OAuth Client',
|
|
64
|
+
redirectUris: ['http://localhost:5173/oauth/callback-demo'],
|
|
65
|
+
grantTypes: ['authorization_code'],
|
|
66
|
+
});
|
|
67
|
+
if (!client.clientSecret)
|
|
68
|
+
throw new Error('Demo OAuth client must be confidential');
|
|
69
|
+
|
|
70
|
+
console.warn(
|
|
71
|
+
`\n[fortress example] OAuth client seeded:\n client_id=${client.clientId}\n client_secret=${client.clientSecret}\n redirect_uri=http://localhost:5173/oauth/callback-demo\n Try: http://localhost:5173/api/oauth/authorize?client_id=${client.clientId}&redirect_uri=${encodeURIComponent('http://localhost:5173/oauth/callback-demo')}&response_type=code&state=xyz&scope=read:posts\n`,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return { clientId: client.clientId, clientSecret: client.clientSecret };
|
|
75
|
+
})();
|
|
76
|
+
|
|
77
|
+
// Surface seed errors loudly during dev startup instead of silently leaving
|
|
78
|
+
// the promise rejected (which would break the first OAuth flow attempt).
|
|
79
|
+
demoSeedPromise.catch((err: unknown) => {
|
|
80
|
+
console.error('[fortress example] demo seed failed:', err);
|
|
81
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demonstrates the SvelteKit `vBody`/`vParam`/`vQuery` helpers.
|
|
3
|
+
*
|
|
4
|
+
* - GET /api/echo/42?greeting=hi → 200 { id: '42', greeting: 'hi' }
|
|
5
|
+
* - GET /api/echo/42 → 422 VALIDATION_ERROR (missing greeting)
|
|
6
|
+
* - POST /api/echo/42 { name } → 200 { id: '42', name }
|
|
7
|
+
* - POST /api/echo/42 {} → 422 VALIDATION_ERROR (missing name)
|
|
8
|
+
*
|
|
9
|
+
* Validation failures throw `FortressError('VALIDATION_ERROR', 422)` — the
|
|
10
|
+
* same shape every fortress-managed endpoint produces. SvelteKit's default
|
|
11
|
+
* error handling reports the thrown `FortressError` to the client.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { json } from '@sveltejs/kit';
|
|
15
|
+
import { obj, str } from '../../../../../../../src';
|
|
16
|
+
import { vBody, vParam, vQuery } from '../../../../../../../src/sveltekit';
|
|
17
|
+
|
|
18
|
+
const Param = obj({ id: str('Echo ID') }, 'id');
|
|
19
|
+
const Query = obj({ greeting: str('Greeting') }, 'greeting');
|
|
20
|
+
const Body = obj({ name: str('Name') }, 'name');
|
|
21
|
+
|
|
22
|
+
export async function GET(event: any): Promise<Response> {
|
|
23
|
+
const { id } = await vParam(event, Param);
|
|
24
|
+
const { greeting } = await vQuery(event, Query);
|
|
25
|
+
return json({ id, greeting });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function POST(event: any): Promise<Response> {
|
|
29
|
+
const { id } = await vParam(event, Param);
|
|
30
|
+
const { name } = await vBody(event, Body);
|
|
31
|
+
return json({ id, name });
|
|
32
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional catch-all `+server.ts` mounting. Useful when you want Fortress
|
|
3
|
+
* routes colocated under your normal routes tree instead of intercepted in
|
|
4
|
+
* `hooks.server.ts`. The handle hook approach in `hooks.server.ts` already
|
|
5
|
+
* handles these paths — this file is here to demonstrate the escape hatch.
|
|
6
|
+
*
|
|
7
|
+
* Drop this file (and only this file) into a real SvelteKit project at
|
|
8
|
+
* `src/routes/api/fortress/[...path]/+server.ts` to expose all Fortress
|
|
9
|
+
* routes under `/api/fortress/...` without touching `hooks.server.ts`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { toSvelteKitHandler } from '../../../../../../../src/sveltekit';
|
|
13
|
+
import { fortress } from '../../../../lib/server/fortress';
|
|
14
|
+
|
|
15
|
+
export const { GET, POST, PUT, DELETE, PATCH } = toSvelteKitHandler(fortress);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protected route. Reads the authenticated user via `getUserId`.
|
|
3
|
+
* If the auth handle hook didn't populate `event.locals.fortress.userId`
|
|
4
|
+
* (no token, expired beyond refresh, etc.), `getUserId` throws and the
|
|
5
|
+
* caller redirects to `/login`.
|
|
6
|
+
*
|
|
7
|
+
* In a real project the load function uses SvelteKit's `error()` /
|
|
8
|
+
* `redirect()` from `@sveltejs/kit` instead of throwing fortress errors
|
|
9
|
+
* directly. This example throws to keep the dependencies minimal.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { SvelteKitRequestEvent } from '../../../../../src/sveltekit';
|
|
13
|
+
import { getUserId } from '../../../../../src/sveltekit';
|
|
14
|
+
import { fortress } from '../../lib/server/fortress';
|
|
15
|
+
|
|
16
|
+
export async function load(event: SvelteKitRequestEvent): Promise<{ user: { id: string; email: string } }> {
|
|
17
|
+
const userId = getUserId(event as never);
|
|
18
|
+
const user = await fortress.auth.me(userId);
|
|
19
|
+
return { user };
|
|
20
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<form method="POST">` login.
|
|
3
|
+
*
|
|
4
|
+
* Reuses `fortressActions.login` for the actual auth work, then layers a
|
|
5
|
+
* dynamic redirect on top so the same page can also be the landing pad
|
|
6
|
+
* for the OAuth consent flow:
|
|
7
|
+
*
|
|
8
|
+
* - regular login → /dashboard
|
|
9
|
+
* - OAuth consent flow (`?flow=<id>`) → /oauth/consent?flow=<id>
|
|
10
|
+
*
|
|
11
|
+
* The corresponding `+page.svelte`:
|
|
12
|
+
*
|
|
13
|
+
* ```svelte
|
|
14
|
+
* <script>
|
|
15
|
+
* import { enhance } from '$app/forms';
|
|
16
|
+
* export let form;
|
|
17
|
+
* </script>
|
|
18
|
+
*
|
|
19
|
+
* <form method="POST" use:enhance>
|
|
20
|
+
* <input name="identifier" required />
|
|
21
|
+
* <input name="password" type="password" required />
|
|
22
|
+
* {#if form?.error}<p class="error">{form.error}</p>{/if}
|
|
23
|
+
* <button>Log in</button>
|
|
24
|
+
* </form>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { Actions } from '@sveltejs/kit';
|
|
29
|
+
import { redirect } from '@sveltejs/kit';
|
|
30
|
+
import { fortressActions } from '../../../../../src/sveltekit';
|
|
31
|
+
import { fortress } from '../../lib/server/fortress';
|
|
32
|
+
|
|
33
|
+
const baseLogin = fortressActions.login(fortress); // no static redirectTo
|
|
34
|
+
|
|
35
|
+
export const actions: Actions = {
|
|
36
|
+
default: async (event) => {
|
|
37
|
+
const result = await baseLogin(event);
|
|
38
|
+
// Failed: bubble the form-fail object back so the page can render the error.
|
|
39
|
+
if (result && 'error' in result)
|
|
40
|
+
return result;
|
|
41
|
+
|
|
42
|
+
// Success: pick destination based on whether we're mid-OAuth.
|
|
43
|
+
const flow = event.url.searchParams.get('flow');
|
|
44
|
+
if (flow)
|
|
45
|
+
throw redirect(303, `/oauth/consent?flow=${encodeURIComponent(flow)}`);
|
|
46
|
+
throw redirect(303, '/dashboard');
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth consent page (Pattern B).
|
|
3
|
+
*
|
|
4
|
+
* The browser arrives here with `?flow=<id>` after `GET /api/oauth/authorize`
|
|
5
|
+
* detected an authenticated session and 302'd to the configured `consentUrl`.
|
|
6
|
+
*
|
|
7
|
+
* Server load fetches the flow metadata from Fortress (cookie-authed —
|
|
8
|
+
* `event.fetch` automatically forwards cookies to the same origin). The
|
|
9
|
+
* page then renders the client name + scopes and offers Allow / Deny.
|
|
10
|
+
*
|
|
11
|
+
* Both actions POST to the corresponding Fortress endpoint and `throw
|
|
12
|
+
* redirect` to the URL Fortress hands back (the OAuth client's
|
|
13
|
+
* `redirect_uri` with `?code=...&state=...` or `?error=access_denied&state=...`).
|
|
14
|
+
*
|
|
15
|
+
* For a cross-origin setup, swap `event.fetch('/api/...')` for an absolute
|
|
16
|
+
* URL and either (a) move the load to a `+page.ts` (browser-side) so the
|
|
17
|
+
* fetch goes out with `credentials: 'include'`, or (b) give the SvelteKit
|
|
18
|
+
* server its own session and proxy.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { Actions, RequestEvent } from '@sveltejs/kit';
|
|
22
|
+
import { error, redirect } from '@sveltejs/kit';
|
|
23
|
+
|
|
24
|
+
interface FlowMetadata {
|
|
25
|
+
flowId: number;
|
|
26
|
+
client: { clientId: string; name: string };
|
|
27
|
+
redirectUri: string;
|
|
28
|
+
scopes: string[];
|
|
29
|
+
state: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function load(event: RequestEvent): Promise<{ flow: FlowMetadata }> {
|
|
33
|
+
const flow = event.url.searchParams.get('flow');
|
|
34
|
+
if (!flow)
|
|
35
|
+
throw error(400, 'Missing flow id');
|
|
36
|
+
|
|
37
|
+
const res = await event.fetch(`/api/oauth/flows/${encodeURIComponent(flow)}`);
|
|
38
|
+
if (res.status === 401)
|
|
39
|
+
throw redirect(303, `/login?flow=${encodeURIComponent(flow)}`);
|
|
40
|
+
if (!res.ok)
|
|
41
|
+
throw error(res.status, await res.text());
|
|
42
|
+
|
|
43
|
+
const meta = (await res.json()) as FlowMetadata;
|
|
44
|
+
return { flow: meta };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function postFlowAction(
|
|
48
|
+
event: RequestEvent,
|
|
49
|
+
decision: 'approve' | 'deny',
|
|
50
|
+
): Promise<never> {
|
|
51
|
+
const flow = event.url.searchParams.get('flow');
|
|
52
|
+
if (!flow)
|
|
53
|
+
throw error(400, 'Missing flow id');
|
|
54
|
+
|
|
55
|
+
const res = await event.fetch(
|
|
56
|
+
`/api/oauth/flows/${encodeURIComponent(flow)}/${decision}`,
|
|
57
|
+
{ method: 'POST' },
|
|
58
|
+
);
|
|
59
|
+
if (!res.ok)
|
|
60
|
+
throw error(res.status, await res.text());
|
|
61
|
+
|
|
62
|
+
const { redirectUrl } = (await res.json()) as { redirectUrl: string };
|
|
63
|
+
throw redirect(303, redirectUrl);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const actions: Actions = {
|
|
67
|
+
approve: event => postFlowAction(event, 'approve'),
|
|
68
|
+
deny: event => postFlowAction(event, 'deny'),
|
|
69
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
OAuth consent page. Renders the client name, redirect URI, and requested
|
|
3
|
+
scopes from the data the `+page.server.ts` load returned, then offers
|
|
4
|
+
Allow / Deny form actions.
|
|
5
|
+
|
|
6
|
+
Two separate <form>s so each button submits to its own action. SvelteKit's
|
|
7
|
+
`?/approve` / `?/deny` syntax in `formaction` keeps this single-file.
|
|
8
|
+
-->
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { enhance } from '$app/forms';
|
|
11
|
+
|
|
12
|
+
let { data } = $props<{
|
|
13
|
+
data: {
|
|
14
|
+
flow: {
|
|
15
|
+
flowId: number;
|
|
16
|
+
client: { clientId: string; name: string };
|
|
17
|
+
redirectUri: string;
|
|
18
|
+
scopes: string[];
|
|
19
|
+
state: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}>();
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<main class="consent">
|
|
26
|
+
<h1>Authorize {data.flow.client.name}</h1>
|
|
27
|
+
<p>
|
|
28
|
+
<strong>{data.flow.client.name}</strong> wants to access your account.
|
|
29
|
+
It will be able to:
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
{#if data.flow.scopes.length > 0}
|
|
33
|
+
<ul>
|
|
34
|
+
{#each data.flow.scopes as scope}
|
|
35
|
+
<li><code>{scope}</code></li>
|
|
36
|
+
{/each}
|
|
37
|
+
</ul>
|
|
38
|
+
{:else}
|
|
39
|
+
<p><em>No specific permissions requested.</em></p>
|
|
40
|
+
{/if}
|
|
41
|
+
|
|
42
|
+
<p class="redirect">
|
|
43
|
+
You will be redirected to:
|
|
44
|
+
<code>{data.flow.redirectUri}</code>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
<form method="POST" use:enhance class="actions">
|
|
48
|
+
<button type="submit" formaction="?/deny" class="btn-secondary">
|
|
49
|
+
Deny
|
|
50
|
+
</button>
|
|
51
|
+
<button type="submit" formaction="?/approve" class="btn-primary">
|
|
52
|
+
Allow
|
|
53
|
+
</button>
|
|
54
|
+
</form>
|
|
55
|
+
</main>
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
.consent {
|
|
59
|
+
max-width: 28rem;
|
|
60
|
+
margin: 4rem auto;
|
|
61
|
+
padding: 2rem;
|
|
62
|
+
border: 1px solid #e2e8f0;
|
|
63
|
+
border-radius: 0.75rem;
|
|
64
|
+
font-family: system-ui, sans-serif;
|
|
65
|
+
}
|
|
66
|
+
h1 { margin-top: 0; }
|
|
67
|
+
ul { margin: 0.5rem 0 1.5rem; padding-left: 1.5rem; }
|
|
68
|
+
.redirect { color: #475569; font-size: 0.9rem; }
|
|
69
|
+
.actions {
|
|
70
|
+
display: flex;
|
|
71
|
+
gap: 0.75rem;
|
|
72
|
+
justify-content: flex-end;
|
|
73
|
+
margin-top: 1.5rem;
|
|
74
|
+
}
|
|
75
|
+
.btn-primary, .btn-secondary {
|
|
76
|
+
padding: 0.5rem 1rem;
|
|
77
|
+
border-radius: 0.375rem;
|
|
78
|
+
font-weight: 500;
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
}
|
|
81
|
+
.btn-primary { background: #2563eb; color: white; border: none; }
|
|
82
|
+
.btn-secondary{ background: white; color: #334155; border: 1px solid #cbd5e1; }
|
|
83
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Fortress migration 0001: schema version tracking table (PostgreSQL)
|
|
2
|
+
-- The version row is written by the migration runner after apply; this
|
|
3
|
+
-- migration only creates the checkpoint table.
|
|
4
|
+
CREATE TABLE IF NOT EXISTS fortress_schema_version (
|
|
5
|
+
id integer PRIMARY KEY DEFAULT 1 CHECK (id = 1),
|
|
6
|
+
version integer NOT NULL,
|
|
7
|
+
applied_at timestamp NOT NULL DEFAULT now()
|
|
8
|
+
);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- Fortress migration 0002: initial Fortress schema (PostgreSQL) — rollback
|
|
2
|
+
|
|
3
|
+
DROP TABLE IF EXISTS fortress_webauthn_challenge CASCADE;
|
|
4
|
+
DROP TABLE IF EXISTS fortress_webauthn_credential CASCADE;
|
|
5
|
+
DROP TABLE IF EXISTS fortress_webhook_delivery CASCADE;
|
|
6
|
+
DROP TABLE IF EXISTS fortress_webhook_endpoint CASCADE;
|
|
7
|
+
DROP TABLE IF EXISTS fortress_audit_log CASCADE;
|
|
8
|
+
DROP TABLE IF EXISTS fortress_account_lockout CASCADE;
|
|
9
|
+
DROP TABLE IF EXISTS fortress_user_scope_assignment CASCADE;
|
|
10
|
+
DROP TABLE IF EXISTS fortress_oauth_signing_key CASCADE;
|
|
11
|
+
DROP TABLE IF EXISTS fortress_oauth_pending_flow CASCADE;
|
|
12
|
+
DROP TABLE IF EXISTS fortress_oauth_refresh_token CASCADE;
|
|
13
|
+
DROP TABLE IF EXISTS fortress_oauth_access_token CASCADE;
|
|
14
|
+
DROP TABLE IF EXISTS fortress_oauth_authorization_code CASCADE;
|
|
15
|
+
DROP TABLE IF EXISTS fortress_oauth_client CASCADE;
|
|
16
|
+
DROP TABLE IF EXISTS fortress_tenant_user CASCADE;
|
|
17
|
+
DROP TABLE IF EXISTS fortress_tenant CASCADE;
|
|
18
|
+
DROP TABLE IF EXISTS fortress_social_account CASCADE;
|
|
19
|
+
DROP TABLE IF EXISTS fortress_trusted_device CASCADE;
|
|
20
|
+
DROP TABLE IF EXISTS fortress_backup_code CASCADE;
|
|
21
|
+
DROP TABLE IF EXISTS fortress_two_factor_secret CASCADE;
|
|
22
|
+
DROP TABLE IF EXISTS fortress_api_key CASCADE;
|
|
23
|
+
DROP TABLE IF EXISTS fortress_magic_link_token CASCADE;
|
|
24
|
+
DROP TABLE IF EXISTS fortress_email_verification_token CASCADE;
|
|
25
|
+
DROP TABLE IF EXISTS fortress_direct_permission_binding CASCADE;
|
|
26
|
+
DROP TABLE IF EXISTS fortress_role_binding CASCADE;
|
|
27
|
+
DROP TABLE IF EXISTS fortress_role_permission CASCADE;
|
|
28
|
+
DROP TABLE IF EXISTS fortress_role CASCADE;
|
|
29
|
+
DROP TABLE IF EXISTS fortress_permission CASCADE;
|
|
30
|
+
DROP TABLE IF EXISTS fortress_resource CASCADE;
|
|
31
|
+
DROP TABLE IF EXISTS fortress_service_account CASCADE;
|
|
32
|
+
DROP TABLE IF EXISTS fortress_group_user CASCADE;
|
|
33
|
+
DROP TABLE IF EXISTS fortress_group CASCADE;
|
|
34
|
+
DROP TABLE IF EXISTS fortress_refresh_token CASCADE;
|
|
35
|
+
DROP TABLE IF EXISTS fortress_login_identifier CASCADE;
|
|
36
|
+
DROP TABLE IF EXISTS fortress_user CASCADE;
|