@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,49 @@
|
|
|
1
|
+
import { e as PluginContext, F as FortressPlugin } from '../config-GtlVt6ZD.cjs';
|
|
2
|
+
import '../index-CxEkfCA0.cjs';
|
|
3
|
+
import '../jwt.cjs';
|
|
4
|
+
import '../types-et0R8tsC.cjs';
|
|
5
|
+
import '../auth-service-BcyQ2PuZ.cjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Row-level data isolation plugin for fortress.
|
|
9
|
+
*
|
|
10
|
+
* Lets you scope read/write access to a database row by per-user assignments,
|
|
11
|
+
* for example "user can only see rows where `org_id` equals their assigned
|
|
12
|
+
* org". Works with any database adapter via the core `scopeRules` capability.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface DataIsolationScope {
|
|
18
|
+
/** Scope name for identification and bypass control */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Column name that holds the scoping value in the target tables */
|
|
21
|
+
field: string;
|
|
22
|
+
/** Which models (tables) this scope applies to. Use ['*'] for all models. */
|
|
23
|
+
models: string[];
|
|
24
|
+
/** Resolve the current user's value for this scope */
|
|
25
|
+
resolveValue: (userId: string, ctx: PluginContext) => Promise<unknown>;
|
|
26
|
+
}
|
|
27
|
+
interface DataIsolationConfig {
|
|
28
|
+
scopes: DataIsolationScope[];
|
|
29
|
+
/**
|
|
30
|
+
* Behavior when an applicable scope resolves to `null` or `undefined`.
|
|
31
|
+
* Defaults to `'deny'`. `'skip'` is an unsafe legacy compatibility mode
|
|
32
|
+
* that removes that scope's row isolation for the operation.
|
|
33
|
+
*/
|
|
34
|
+
unresolvedScope?: 'deny' | 'skip';
|
|
35
|
+
}
|
|
36
|
+
interface DataIsolationMethods {
|
|
37
|
+
withoutScope: <T>(scopeName: string, fn: () => Promise<T>) => Promise<T>;
|
|
38
|
+
unscoped: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Data isolation plugin factory. Returns a {@link FortressPlugin} that scopes
|
|
42
|
+
* read and write access to a database row by per-user scope assignments,
|
|
43
|
+
* enforcing isolation through the core `scopeRules` capability.
|
|
44
|
+
*/
|
|
45
|
+
declare function dataIsolation(config: DataIsolationConfig): FortressPlugin & {
|
|
46
|
+
readonly name: 'data-isolation';
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { type DataIsolationConfig, type DataIsolationMethods, type DataIsolationScope, dataIsolation };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { e as PluginContext, F as FortressPlugin } from '../config-B2366s_G.js';
|
|
2
|
+
import '../index-CxEkfCA0.js';
|
|
3
|
+
import '../jwt.js';
|
|
4
|
+
import '../types-et0R8tsC.js';
|
|
5
|
+
import '../auth-service-BkzZkWfH.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Row-level data isolation plugin for fortress.
|
|
9
|
+
*
|
|
10
|
+
* Lets you scope read/write access to a database row by per-user assignments,
|
|
11
|
+
* for example "user can only see rows where `org_id` equals their assigned
|
|
12
|
+
* org". Works with any database adapter via the core `scopeRules` capability.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface DataIsolationScope {
|
|
18
|
+
/** Scope name for identification and bypass control */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Column name that holds the scoping value in the target tables */
|
|
21
|
+
field: string;
|
|
22
|
+
/** Which models (tables) this scope applies to. Use ['*'] for all models. */
|
|
23
|
+
models: string[];
|
|
24
|
+
/** Resolve the current user's value for this scope */
|
|
25
|
+
resolveValue: (userId: string, ctx: PluginContext) => Promise<unknown>;
|
|
26
|
+
}
|
|
27
|
+
interface DataIsolationConfig {
|
|
28
|
+
scopes: DataIsolationScope[];
|
|
29
|
+
/**
|
|
30
|
+
* Behavior when an applicable scope resolves to `null` or `undefined`.
|
|
31
|
+
* Defaults to `'deny'`. `'skip'` is an unsafe legacy compatibility mode
|
|
32
|
+
* that removes that scope's row isolation for the operation.
|
|
33
|
+
*/
|
|
34
|
+
unresolvedScope?: 'deny' | 'skip';
|
|
35
|
+
}
|
|
36
|
+
interface DataIsolationMethods {
|
|
37
|
+
withoutScope: <T>(scopeName: string, fn: () => Promise<T>) => Promise<T>;
|
|
38
|
+
unscoped: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Data isolation plugin factory. Returns a {@link FortressPlugin} that scopes
|
|
42
|
+
* read and write access to a database row by per-user scope assignments,
|
|
43
|
+
* enforcing isolation through the core `scopeRules` capability.
|
|
44
|
+
*/
|
|
45
|
+
declare function dataIsolation(config: DataIsolationConfig): FortressPlugin & {
|
|
46
|
+
readonly name: 'data-isolation';
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { type DataIsolationConfig, type DataIsolationMethods, type DataIsolationScope, dataIsolation };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// src/plugins/data-isolation/index.ts
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
|
|
4
|
+
// src/core/errors.ts
|
|
5
|
+
var FortressError = class extends Error {
|
|
6
|
+
code;
|
|
7
|
+
statusCode;
|
|
8
|
+
retryAfter;
|
|
9
|
+
details;
|
|
10
|
+
/** RFC 6749 §5.2 / §4.1.2.1 machine code. Set by `Errors.oauth()`. */
|
|
11
|
+
oauthError;
|
|
12
|
+
/** Human-readable description for the OAuth `error_description` field. */
|
|
13
|
+
oauthDescription;
|
|
14
|
+
/** Optional URI for the OAuth `error_uri` field (§5.2). */
|
|
15
|
+
oauthErrorUri;
|
|
16
|
+
constructor(code, message, statusCode, options) {
|
|
17
|
+
super(message, { cause: options?.cause });
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.statusCode = statusCode;
|
|
20
|
+
this.retryAfter = options?.retryAfter;
|
|
21
|
+
this.details = options?.details;
|
|
22
|
+
this.oauthError = options?.oauthError;
|
|
23
|
+
this.oauthDescription = options?.oauthDescription;
|
|
24
|
+
this.oauthErrorUri = options?.oauthErrorUri;
|
|
25
|
+
}
|
|
26
|
+
toJSON() {
|
|
27
|
+
return {
|
|
28
|
+
code: this.code,
|
|
29
|
+
message: this.message,
|
|
30
|
+
statusCode: this.statusCode,
|
|
31
|
+
...this.details !== void 0 && { details: this.details }
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var Errors = {
|
|
36
|
+
unauthorized: (message = "Unauthorized") => new FortressError("UNAUTHORIZED", message, 401),
|
|
37
|
+
tokenReuse: () => new FortressError("TOKEN_REUSE", "Token reuse detected", 401),
|
|
38
|
+
sessionIdleTimeout: () => new FortressError("SESSION_IDLE_TIMEOUT", "Session idle timeout exceeded", 401),
|
|
39
|
+
sessionAbsoluteTimeout: () => new FortressError("SESSION_ABSOLUTE_TIMEOUT", "Session absolute timeout exceeded", 401),
|
|
40
|
+
forbidden: (message = "Forbidden") => new FortressError("FORBIDDEN", message, 403),
|
|
41
|
+
badRequest: (message = "Bad request") => new FortressError("BAD_REQUEST", message, 400),
|
|
42
|
+
notFound: (message = "Not found") => new FortressError("NOT_FOUND", message, 404),
|
|
43
|
+
conflict: (message = "Conflict", options) => new FortressError("CONFLICT", message, 409, options),
|
|
44
|
+
unprocessable: (message = "Unprocessable entity", options) => new FortressError("UNPROCESSABLE_ENTITY", message, 422, options),
|
|
45
|
+
rateLimited: (retryAfter) => new FortressError("RATE_LIMITED", "Too many requests", 429, { retryAfter }),
|
|
46
|
+
database: (message = "Database error", cause) => new FortressError("DATABASE_ERROR", message, 500, { cause }),
|
|
47
|
+
serviceUnavailable: (message = "Service unavailable", options) => new FortressError("SERVICE_UNAVAILABLE", message, 503, options),
|
|
48
|
+
validationError: (issues) => new FortressError("VALIDATION_ERROR", "Validation failed", 422, { details: issues }),
|
|
49
|
+
/**
|
|
50
|
+
* RFC 6749 §5.2 / §4.1.2.1 OAuth error.
|
|
51
|
+
*
|
|
52
|
+
* The HTTP error mapper detects `oauthError` and emits the OAuth-spec
|
|
53
|
+
* JSON body `{ error, error_description, error_uri? }` instead of the
|
|
54
|
+
* default fortress `{ code, message }` shape, so strict OAuth clients
|
|
55
|
+
* (Moodle, openid-client, Spring Security, etc.) can switch behaviour
|
|
56
|
+
* on the machine-readable `error` field.
|
|
57
|
+
*
|
|
58
|
+
* Status is inferred from the OAuth code per the spec table:
|
|
59
|
+
* - `invalid_client` → 401
|
|
60
|
+
* - everything else → 400
|
|
61
|
+
*/
|
|
62
|
+
oauth: (error, description, options) => {
|
|
63
|
+
const status = options?.status ?? (error === "invalid_client" ? 401 : 400);
|
|
64
|
+
const code = status === 401 ? "UNAUTHORIZED" : "BAD_REQUEST";
|
|
65
|
+
return new FortressError(code, description ?? error, status, {
|
|
66
|
+
oauthError: error,
|
|
67
|
+
oauthDescription: description,
|
|
68
|
+
oauthErrorUri: options?.errorUri,
|
|
69
|
+
cause: options?.cause
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Reconstruct a {@link FortressError} from a JSON error body emitted by
|
|
74
|
+
* `errorToResponse`. Used by the in-process `fortress.call.*` client to
|
|
75
|
+
* convert non-2xx responses into typed throws that mirror what a direct
|
|
76
|
+
* service-method call would produce.
|
|
77
|
+
*
|
|
78
|
+
* Maps by HTTP status when the body doesn't carry a recognizable
|
|
79
|
+
* `{ code, message }` payload, so network errors and opaque upstream
|
|
80
|
+
* failures still become structured `FortressError`s.
|
|
81
|
+
*/
|
|
82
|
+
fromHttpResponse: (status, body) => {
|
|
83
|
+
const payload = body && typeof body === "object" ? body : {};
|
|
84
|
+
const message = typeof payload.message === "string" ? payload.message : `HTTP ${status}`;
|
|
85
|
+
const code = typeof payload.code === "string" && isFortressErrorCode(payload.code) ? payload.code : statusToErrorCode(status);
|
|
86
|
+
return new FortressError(code, message, status, { details: payload.details });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function isFortressErrorCode(code) {
|
|
90
|
+
return code === "UNAUTHORIZED" || code === "TOKEN_REUSE" || code === "SESSION_IDLE_TIMEOUT" || code === "SESSION_ABSOLUTE_TIMEOUT" || code === "FORBIDDEN" || code === "BAD_REQUEST" || code === "NOT_FOUND" || code === "CONFLICT" || code === "UNPROCESSABLE_ENTITY" || code === "RATE_LIMITED" || code === "DATABASE_ERROR" || code === "VALIDATION_ERROR" || code === "SERVICE_UNAVAILABLE";
|
|
91
|
+
}
|
|
92
|
+
function statusToErrorCode(status) {
|
|
93
|
+
if (status === 401)
|
|
94
|
+
return "UNAUTHORIZED";
|
|
95
|
+
if (status === 403)
|
|
96
|
+
return "FORBIDDEN";
|
|
97
|
+
if (status === 404)
|
|
98
|
+
return "NOT_FOUND";
|
|
99
|
+
if (status === 409)
|
|
100
|
+
return "CONFLICT";
|
|
101
|
+
if (status === 422)
|
|
102
|
+
return "UNPROCESSABLE_ENTITY";
|
|
103
|
+
if (status === 429)
|
|
104
|
+
return "RATE_LIMITED";
|
|
105
|
+
if (status === 503)
|
|
106
|
+
return "SERVICE_UNAVAILABLE";
|
|
107
|
+
if (status >= 500)
|
|
108
|
+
return "DATABASE_ERROR";
|
|
109
|
+
return "BAD_REQUEST";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/plugins/data-isolation/index.ts
|
|
113
|
+
var bypassStore = new AsyncLocalStorage();
|
|
114
|
+
function currentBypassState() {
|
|
115
|
+
return bypassStore.getStore();
|
|
116
|
+
}
|
|
117
|
+
function dataIsolation(config) {
|
|
118
|
+
return {
|
|
119
|
+
name: "data-isolation",
|
|
120
|
+
models: [{
|
|
121
|
+
name: "user_scope_assignment",
|
|
122
|
+
fields: {
|
|
123
|
+
id: { type: "number", required: true },
|
|
124
|
+
userId: { type: "number", required: true, references: { model: "user", field: "id" } },
|
|
125
|
+
scopeName: { type: "string", required: true },
|
|
126
|
+
scopeValue: { type: "string", required: true },
|
|
127
|
+
createdAt: { type: "date", required: true }
|
|
128
|
+
}
|
|
129
|
+
}],
|
|
130
|
+
async scopeRules(userId, model, ctx) {
|
|
131
|
+
const state = currentBypassState();
|
|
132
|
+
if (state?.all)
|
|
133
|
+
return null;
|
|
134
|
+
const filters = [];
|
|
135
|
+
const defaults = {};
|
|
136
|
+
for (const scope of config.scopes) {
|
|
137
|
+
if (state?.scopes.has(scope.name))
|
|
138
|
+
continue;
|
|
139
|
+
const applies = scope.models.includes("*") || scope.models.includes(model);
|
|
140
|
+
if (!applies)
|
|
141
|
+
continue;
|
|
142
|
+
const value = await scope.resolveValue(userId, ctx);
|
|
143
|
+
if (value === void 0 || value === null) {
|
|
144
|
+
if (config.unresolvedScope === "skip")
|
|
145
|
+
continue;
|
|
146
|
+
throw Errors.forbidden(`Data isolation scope '${scope.name}' could not be resolved`);
|
|
147
|
+
}
|
|
148
|
+
filters.push({ field: scope.field, operator: "=", value });
|
|
149
|
+
defaults[scope.field] = value;
|
|
150
|
+
}
|
|
151
|
+
return filters.length > 0 ? { filters, defaults } : null;
|
|
152
|
+
},
|
|
153
|
+
methods: () => ({
|
|
154
|
+
/**
|
|
155
|
+
* Execute a callback with a specific scope bypassed.
|
|
156
|
+
* Queries within the callback will not have the named scope filter applied.
|
|
157
|
+
*
|
|
158
|
+
* The bypass is async-context-local: it applies only to async work
|
|
159
|
+
* spawned inside `fn` and never leaks across concurrent requests.
|
|
160
|
+
*/
|
|
161
|
+
async withoutScope(scopeName, fn) {
|
|
162
|
+
const existing = currentBypassState();
|
|
163
|
+
const nextScopes = new Set(existing?.scopes ?? []);
|
|
164
|
+
nextScopes.add(scopeName);
|
|
165
|
+
const next = { all: existing?.all ?? false, scopes: nextScopes };
|
|
166
|
+
return bypassStore.run(next, fn);
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Execute a callback with all scopes bypassed.
|
|
170
|
+
* Use with caution \u2014 no row-level isolation is applied.
|
|
171
|
+
*
|
|
172
|
+
* The bypass is async-context-local. Calls outside the callback (and
|
|
173
|
+
* concurrent requests running on different async contexts) are
|
|
174
|
+
* unaffected.
|
|
175
|
+
*/
|
|
176
|
+
async unscoped(fn) {
|
|
177
|
+
const existing = currentBypassState();
|
|
178
|
+
const next = { all: true, scopes: new Set(existing?.scopes ?? []) };
|
|
179
|
+
return bypassStore.run(next, fn);
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export {
|
|
185
|
+
dataIsolation
|
|
186
|
+
};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/plugins/email-verification/index.ts
|
|
21
|
+
var email_verification_exports = {};
|
|
22
|
+
__export(email_verification_exports, {
|
|
23
|
+
emailVerification: () => emailVerification
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(email_verification_exports);
|
|
26
|
+
|
|
27
|
+
// src/core/auth/refresh-token.ts
|
|
28
|
+
async function generateRefreshToken() {
|
|
29
|
+
const bytes = new Uint8Array(32);
|
|
30
|
+
crypto.getRandomValues(bytes);
|
|
31
|
+
const raw = base64UrlEncode(bytes);
|
|
32
|
+
const hash = await hashToken(raw);
|
|
33
|
+
return { raw, hash };
|
|
34
|
+
}
|
|
35
|
+
async function hashToken(raw) {
|
|
36
|
+
const data = new TextEncoder().encode(raw);
|
|
37
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
38
|
+
return hexEncode(new Uint8Array(hashBuffer));
|
|
39
|
+
}
|
|
40
|
+
function base64UrlEncode(bytes) {
|
|
41
|
+
const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join("");
|
|
42
|
+
return btoa(binString).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
43
|
+
}
|
|
44
|
+
function hexEncode(bytes) {
|
|
45
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/core/errors.ts
|
|
49
|
+
var FortressError = class extends Error {
|
|
50
|
+
code;
|
|
51
|
+
statusCode;
|
|
52
|
+
retryAfter;
|
|
53
|
+
details;
|
|
54
|
+
/** RFC 6749 §5.2 / §4.1.2.1 machine code. Set by `Errors.oauth()`. */
|
|
55
|
+
oauthError;
|
|
56
|
+
/** Human-readable description for the OAuth `error_description` field. */
|
|
57
|
+
oauthDescription;
|
|
58
|
+
/** Optional URI for the OAuth `error_uri` field (§5.2). */
|
|
59
|
+
oauthErrorUri;
|
|
60
|
+
constructor(code, message, statusCode, options) {
|
|
61
|
+
super(message, { cause: options?.cause });
|
|
62
|
+
this.code = code;
|
|
63
|
+
this.statusCode = statusCode;
|
|
64
|
+
this.retryAfter = options?.retryAfter;
|
|
65
|
+
this.details = options?.details;
|
|
66
|
+
this.oauthError = options?.oauthError;
|
|
67
|
+
this.oauthDescription = options?.oauthDescription;
|
|
68
|
+
this.oauthErrorUri = options?.oauthErrorUri;
|
|
69
|
+
}
|
|
70
|
+
toJSON() {
|
|
71
|
+
return {
|
|
72
|
+
code: this.code,
|
|
73
|
+
message: this.message,
|
|
74
|
+
statusCode: this.statusCode,
|
|
75
|
+
...this.details !== void 0 && { details: this.details }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var Errors = {
|
|
80
|
+
unauthorized: (message = "Unauthorized") => new FortressError("UNAUTHORIZED", message, 401),
|
|
81
|
+
tokenReuse: () => new FortressError("TOKEN_REUSE", "Token reuse detected", 401),
|
|
82
|
+
sessionIdleTimeout: () => new FortressError("SESSION_IDLE_TIMEOUT", "Session idle timeout exceeded", 401),
|
|
83
|
+
sessionAbsoluteTimeout: () => new FortressError("SESSION_ABSOLUTE_TIMEOUT", "Session absolute timeout exceeded", 401),
|
|
84
|
+
forbidden: (message = "Forbidden") => new FortressError("FORBIDDEN", message, 403),
|
|
85
|
+
badRequest: (message = "Bad request") => new FortressError("BAD_REQUEST", message, 400),
|
|
86
|
+
notFound: (message = "Not found") => new FortressError("NOT_FOUND", message, 404),
|
|
87
|
+
conflict: (message = "Conflict", options) => new FortressError("CONFLICT", message, 409, options),
|
|
88
|
+
unprocessable: (message = "Unprocessable entity", options) => new FortressError("UNPROCESSABLE_ENTITY", message, 422, options),
|
|
89
|
+
rateLimited: (retryAfter) => new FortressError("RATE_LIMITED", "Too many requests", 429, { retryAfter }),
|
|
90
|
+
database: (message = "Database error", cause) => new FortressError("DATABASE_ERROR", message, 500, { cause }),
|
|
91
|
+
serviceUnavailable: (message = "Service unavailable", options) => new FortressError("SERVICE_UNAVAILABLE", message, 503, options),
|
|
92
|
+
validationError: (issues) => new FortressError("VALIDATION_ERROR", "Validation failed", 422, { details: issues }),
|
|
93
|
+
/**
|
|
94
|
+
* RFC 6749 §5.2 / §4.1.2.1 OAuth error.
|
|
95
|
+
*
|
|
96
|
+
* The HTTP error mapper detects `oauthError` and emits the OAuth-spec
|
|
97
|
+
* JSON body `{ error, error_description, error_uri? }` instead of the
|
|
98
|
+
* default fortress `{ code, message }` shape, so strict OAuth clients
|
|
99
|
+
* (Moodle, openid-client, Spring Security, etc.) can switch behaviour
|
|
100
|
+
* on the machine-readable `error` field.
|
|
101
|
+
*
|
|
102
|
+
* Status is inferred from the OAuth code per the spec table:
|
|
103
|
+
* - `invalid_client` → 401
|
|
104
|
+
* - everything else → 400
|
|
105
|
+
*/
|
|
106
|
+
oauth: (error, description, options) => {
|
|
107
|
+
const status = options?.status ?? (error === "invalid_client" ? 401 : 400);
|
|
108
|
+
const code = status === 401 ? "UNAUTHORIZED" : "BAD_REQUEST";
|
|
109
|
+
return new FortressError(code, description ?? error, status, {
|
|
110
|
+
oauthError: error,
|
|
111
|
+
oauthDescription: description,
|
|
112
|
+
oauthErrorUri: options?.errorUri,
|
|
113
|
+
cause: options?.cause
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
/**
|
|
117
|
+
* Reconstruct a {@link FortressError} from a JSON error body emitted by
|
|
118
|
+
* `errorToResponse`. Used by the in-process `fortress.call.*` client to
|
|
119
|
+
* convert non-2xx responses into typed throws that mirror what a direct
|
|
120
|
+
* service-method call would produce.
|
|
121
|
+
*
|
|
122
|
+
* Maps by HTTP status when the body doesn't carry a recognizable
|
|
123
|
+
* `{ code, message }` payload, so network errors and opaque upstream
|
|
124
|
+
* failures still become structured `FortressError`s.
|
|
125
|
+
*/
|
|
126
|
+
fromHttpResponse: (status, body) => {
|
|
127
|
+
const payload = body && typeof body === "object" ? body : {};
|
|
128
|
+
const message = typeof payload.message === "string" ? payload.message : `HTTP ${status}`;
|
|
129
|
+
const code = typeof payload.code === "string" && isFortressErrorCode(payload.code) ? payload.code : statusToErrorCode(status);
|
|
130
|
+
return new FortressError(code, message, status, { details: payload.details });
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
function isFortressErrorCode(code) {
|
|
134
|
+
return code === "UNAUTHORIZED" || code === "TOKEN_REUSE" || code === "SESSION_IDLE_TIMEOUT" || code === "SESSION_ABSOLUTE_TIMEOUT" || code === "FORBIDDEN" || code === "BAD_REQUEST" || code === "NOT_FOUND" || code === "CONFLICT" || code === "UNPROCESSABLE_ENTITY" || code === "RATE_LIMITED" || code === "DATABASE_ERROR" || code === "VALIDATION_ERROR" || code === "SERVICE_UNAVAILABLE";
|
|
135
|
+
}
|
|
136
|
+
function statusToErrorCode(status) {
|
|
137
|
+
if (status === 401)
|
|
138
|
+
return "UNAUTHORIZED";
|
|
139
|
+
if (status === 403)
|
|
140
|
+
return "FORBIDDEN";
|
|
141
|
+
if (status === 404)
|
|
142
|
+
return "NOT_FOUND";
|
|
143
|
+
if (status === 409)
|
|
144
|
+
return "CONFLICT";
|
|
145
|
+
if (status === 422)
|
|
146
|
+
return "UNPROCESSABLE_ENTITY";
|
|
147
|
+
if (status === 429)
|
|
148
|
+
return "RATE_LIMITED";
|
|
149
|
+
if (status === 503)
|
|
150
|
+
return "SERVICE_UNAVAILABLE";
|
|
151
|
+
if (status >= 500)
|
|
152
|
+
return "DATABASE_ERROR";
|
|
153
|
+
return "BAD_REQUEST";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/plugins/email-verification/index.ts
|
|
157
|
+
function emailVerification(config = {}) {
|
|
158
|
+
const tokenExpirySeconds = config.tokenExpirySeconds ?? 86400;
|
|
159
|
+
const requireVerification = config.requireVerification ?? true;
|
|
160
|
+
async function consumeVerificationToken(db, rawToken, expectedUserId) {
|
|
161
|
+
const hash = await hashToken(rawToken);
|
|
162
|
+
const usedAt = /* @__PURE__ */ new Date();
|
|
163
|
+
const record = await db.update({
|
|
164
|
+
model: "email_verification_token",
|
|
165
|
+
where: [
|
|
166
|
+
{ field: "token", operator: "=", value: hash },
|
|
167
|
+
{ field: "usedAt", operator: "isNull", value: null },
|
|
168
|
+
{ field: "expiresAt", operator: "gt", value: usedAt },
|
|
169
|
+
...expectedUserId ? [{ field: "userId", operator: "=", value: expectedUserId }] : []
|
|
170
|
+
],
|
|
171
|
+
data: { usedAt }
|
|
172
|
+
});
|
|
173
|
+
if (!record)
|
|
174
|
+
throw Errors.notFound("Invalid or expired verification token");
|
|
175
|
+
await db.update({
|
|
176
|
+
model: "user",
|
|
177
|
+
where: [{ field: "id", operator: "=", value: record.userId }],
|
|
178
|
+
data: { email: record.email, emailVerified: true }
|
|
179
|
+
});
|
|
180
|
+
return record;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
name: "email-verification",
|
|
184
|
+
models: [{
|
|
185
|
+
name: "email_verification_token",
|
|
186
|
+
fields: {
|
|
187
|
+
id: { type: "number", required: true },
|
|
188
|
+
userId: { type: "number", required: true, references: { model: "user", field: "id" } },
|
|
189
|
+
token: { type: "string", required: true },
|
|
190
|
+
email: { type: "string", required: true },
|
|
191
|
+
expiresAt: { type: "date", required: true },
|
|
192
|
+
usedAt: { type: "date" },
|
|
193
|
+
createdAt: { type: "date", required: true }
|
|
194
|
+
}
|
|
195
|
+
}],
|
|
196
|
+
hooks: {
|
|
197
|
+
postAuthGate: {
|
|
198
|
+
reason: "email-verification",
|
|
199
|
+
async evaluate(ctx) {
|
|
200
|
+
if (!requireVerification || ctx.user.emailVerified)
|
|
201
|
+
return;
|
|
202
|
+
const tokens = await ctx.db.findMany({
|
|
203
|
+
model: "email_verification_token",
|
|
204
|
+
where: [{ field: "userId", operator: "=", value: ctx.user.id }]
|
|
205
|
+
});
|
|
206
|
+
return tokens.some((token) => token.usedAt !== null) ? void 0 : { pluginData: { requiresEmailVerification: true } };
|
|
207
|
+
},
|
|
208
|
+
async verify(ctx, completion) {
|
|
209
|
+
if (typeof completion !== "string")
|
|
210
|
+
throw Errors.unauthorized("Invalid verification token");
|
|
211
|
+
await consumeVerificationToken(ctx.db, completion, ctx.user.id);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
async afterRegister(ctx, user) {
|
|
215
|
+
const { raw, hash } = await generateRefreshToken();
|
|
216
|
+
const expiresAt = new Date(Date.now() + tokenExpirySeconds * 1e3);
|
|
217
|
+
await ctx.db.create({
|
|
218
|
+
model: "email_verification_token",
|
|
219
|
+
data: {
|
|
220
|
+
userId: user.id,
|
|
221
|
+
token: hash,
|
|
222
|
+
email: user.email,
|
|
223
|
+
expiresAt,
|
|
224
|
+
usedAt: null
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
if (config.onSendVerification) {
|
|
228
|
+
await config.onSendVerification(user.email, raw, user.id);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
methods: (ctx) => ({
|
|
233
|
+
async sendVerification(userId, email) {
|
|
234
|
+
const user = await ctx.db.findOne({
|
|
235
|
+
model: "user",
|
|
236
|
+
where: [{ field: "id", operator: "=", value: userId }]
|
|
237
|
+
});
|
|
238
|
+
if (!user)
|
|
239
|
+
throw Errors.notFound("User not found");
|
|
240
|
+
const targetEmail = email ?? user.email;
|
|
241
|
+
const { raw, hash } = await generateRefreshToken();
|
|
242
|
+
const expiresAt = new Date(Date.now() + tokenExpirySeconds * 1e3);
|
|
243
|
+
await ctx.db.create({
|
|
244
|
+
model: "email_verification_token",
|
|
245
|
+
data: {
|
|
246
|
+
userId,
|
|
247
|
+
token: hash,
|
|
248
|
+
email: targetEmail,
|
|
249
|
+
expiresAt,
|
|
250
|
+
usedAt: null
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
if (config.onSendVerification) {
|
|
254
|
+
await config.onSendVerification(targetEmail, raw, userId);
|
|
255
|
+
}
|
|
256
|
+
return { sent: true };
|
|
257
|
+
},
|
|
258
|
+
async verify(rawToken) {
|
|
259
|
+
const record = await ctx.db.transaction((tx) => consumeVerificationToken(tx, rawToken));
|
|
260
|
+
return { userId: record.userId, email: record.email };
|
|
261
|
+
},
|
|
262
|
+
async completeVerification(continuationToken, verificationToken, meta) {
|
|
263
|
+
if (!ctx.auth)
|
|
264
|
+
throw Errors.badRequest("Auth service is unavailable");
|
|
265
|
+
return ctx.auth.completePendingAuth(continuationToken, verificationToken, meta);
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
271
|
+
0 && (module.exports = {
|
|
272
|
+
emailVerification
|
|
273
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { F as FortressPlugin } from '../config-GtlVt6ZD.cjs';
|
|
2
|
+
import { R as RequestMeta, A as AuthResult } from '../types-et0R8tsC.cjs';
|
|
3
|
+
import '../index-CxEkfCA0.cjs';
|
|
4
|
+
import '../jwt.cjs';
|
|
5
|
+
import '../auth-service-BcyQ2PuZ.cjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Email verification plugin for fortress.
|
|
9
|
+
*
|
|
10
|
+
* Issues hashed, time-limited verification tokens, exposes
|
|
11
|
+
* `requestVerification` and `verifyEmail` methods on the fortress instance,
|
|
12
|
+
* and adds the corresponding HTTP routes when mounted via a framework adapter.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface EmailVerificationConfig {
|
|
18
|
+
/** Token expiry in seconds (default: 86400 = 24h) */
|
|
19
|
+
tokenExpirySeconds?: number;
|
|
20
|
+
/** Block login for unverified users (default: true) */
|
|
21
|
+
requireVerification?: boolean;
|
|
22
|
+
/** Called when a verification token is created */
|
|
23
|
+
onSendVerification?: (email: string, token: string, userId: string) => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
interface EmailVerificationMethods {
|
|
26
|
+
sendVerification: (userId: string, email?: string) => Promise<{
|
|
27
|
+
sent: true;
|
|
28
|
+
}>;
|
|
29
|
+
verify: (rawToken: string) => Promise<{
|
|
30
|
+
userId: string;
|
|
31
|
+
email: string;
|
|
32
|
+
}>;
|
|
33
|
+
completeVerification: (continuationToken: string, verificationToken: string, meta?: RequestMeta) => Promise<AuthResult>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Email verification plugin factory. Returns a {@link FortressPlugin} that
|
|
37
|
+
* issues hashed verification tokens, exposes `requestVerification` /
|
|
38
|
+
* `verifyEmail` methods, and (when mounted) the corresponding HTTP routes.
|
|
39
|
+
*/
|
|
40
|
+
declare function emailVerification(config?: EmailVerificationConfig): FortressPlugin & {
|
|
41
|
+
readonly name: 'email-verification';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export { type EmailVerificationConfig, type EmailVerificationMethods, emailVerification };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { F as FortressPlugin } from '../config-B2366s_G.js';
|
|
2
|
+
import { R as RequestMeta, A as AuthResult } from '../types-et0R8tsC.js';
|
|
3
|
+
import '../index-CxEkfCA0.js';
|
|
4
|
+
import '../jwt.js';
|
|
5
|
+
import '../auth-service-BkzZkWfH.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Email verification plugin for fortress.
|
|
9
|
+
*
|
|
10
|
+
* Issues hashed, time-limited verification tokens, exposes
|
|
11
|
+
* `requestVerification` and `verifyEmail` methods on the fortress instance,
|
|
12
|
+
* and adds the corresponding HTTP routes when mounted via a framework adapter.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface EmailVerificationConfig {
|
|
18
|
+
/** Token expiry in seconds (default: 86400 = 24h) */
|
|
19
|
+
tokenExpirySeconds?: number;
|
|
20
|
+
/** Block login for unverified users (default: true) */
|
|
21
|
+
requireVerification?: boolean;
|
|
22
|
+
/** Called when a verification token is created */
|
|
23
|
+
onSendVerification?: (email: string, token: string, userId: string) => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
interface EmailVerificationMethods {
|
|
26
|
+
sendVerification: (userId: string, email?: string) => Promise<{
|
|
27
|
+
sent: true;
|
|
28
|
+
}>;
|
|
29
|
+
verify: (rawToken: string) => Promise<{
|
|
30
|
+
userId: string;
|
|
31
|
+
email: string;
|
|
32
|
+
}>;
|
|
33
|
+
completeVerification: (continuationToken: string, verificationToken: string, meta?: RequestMeta) => Promise<AuthResult>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Email verification plugin factory. Returns a {@link FortressPlugin} that
|
|
37
|
+
* issues hashed verification tokens, exposes `requestVerification` /
|
|
38
|
+
* `verifyEmail` methods, and (when mounted) the corresponding HTTP routes.
|
|
39
|
+
*/
|
|
40
|
+
declare function emailVerification(config?: EmailVerificationConfig): FortressPlugin & {
|
|
41
|
+
readonly name: 'email-verification';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export { type EmailVerificationConfig, type EmailVerificationMethods, emailVerification };
|