@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,246 @@
|
|
|
1
|
+
// src/core/auth/refresh-token.ts
|
|
2
|
+
async function generateRefreshToken() {
|
|
3
|
+
const bytes = new Uint8Array(32);
|
|
4
|
+
crypto.getRandomValues(bytes);
|
|
5
|
+
const raw = base64UrlEncode(bytes);
|
|
6
|
+
const hash = await hashToken(raw);
|
|
7
|
+
return { raw, hash };
|
|
8
|
+
}
|
|
9
|
+
async function hashToken(raw) {
|
|
10
|
+
const data = new TextEncoder().encode(raw);
|
|
11
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
12
|
+
return hexEncode(new Uint8Array(hashBuffer));
|
|
13
|
+
}
|
|
14
|
+
function base64UrlEncode(bytes) {
|
|
15
|
+
const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join("");
|
|
16
|
+
return btoa(binString).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
17
|
+
}
|
|
18
|
+
function hexEncode(bytes) {
|
|
19
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/core/errors.ts
|
|
23
|
+
var FortressError = class extends Error {
|
|
24
|
+
code;
|
|
25
|
+
statusCode;
|
|
26
|
+
retryAfter;
|
|
27
|
+
details;
|
|
28
|
+
/** RFC 6749 §5.2 / §4.1.2.1 machine code. Set by `Errors.oauth()`. */
|
|
29
|
+
oauthError;
|
|
30
|
+
/** Human-readable description for the OAuth `error_description` field. */
|
|
31
|
+
oauthDescription;
|
|
32
|
+
/** Optional URI for the OAuth `error_uri` field (§5.2). */
|
|
33
|
+
oauthErrorUri;
|
|
34
|
+
constructor(code, message, statusCode, options) {
|
|
35
|
+
super(message, { cause: options?.cause });
|
|
36
|
+
this.code = code;
|
|
37
|
+
this.statusCode = statusCode;
|
|
38
|
+
this.retryAfter = options?.retryAfter;
|
|
39
|
+
this.details = options?.details;
|
|
40
|
+
this.oauthError = options?.oauthError;
|
|
41
|
+
this.oauthDescription = options?.oauthDescription;
|
|
42
|
+
this.oauthErrorUri = options?.oauthErrorUri;
|
|
43
|
+
}
|
|
44
|
+
toJSON() {
|
|
45
|
+
return {
|
|
46
|
+
code: this.code,
|
|
47
|
+
message: this.message,
|
|
48
|
+
statusCode: this.statusCode,
|
|
49
|
+
...this.details !== void 0 && { details: this.details }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var Errors = {
|
|
54
|
+
unauthorized: (message = "Unauthorized") => new FortressError("UNAUTHORIZED", message, 401),
|
|
55
|
+
tokenReuse: () => new FortressError("TOKEN_REUSE", "Token reuse detected", 401),
|
|
56
|
+
sessionIdleTimeout: () => new FortressError("SESSION_IDLE_TIMEOUT", "Session idle timeout exceeded", 401),
|
|
57
|
+
sessionAbsoluteTimeout: () => new FortressError("SESSION_ABSOLUTE_TIMEOUT", "Session absolute timeout exceeded", 401),
|
|
58
|
+
forbidden: (message = "Forbidden") => new FortressError("FORBIDDEN", message, 403),
|
|
59
|
+
badRequest: (message = "Bad request") => new FortressError("BAD_REQUEST", message, 400),
|
|
60
|
+
notFound: (message = "Not found") => new FortressError("NOT_FOUND", message, 404),
|
|
61
|
+
conflict: (message = "Conflict", options) => new FortressError("CONFLICT", message, 409, options),
|
|
62
|
+
unprocessable: (message = "Unprocessable entity", options) => new FortressError("UNPROCESSABLE_ENTITY", message, 422, options),
|
|
63
|
+
rateLimited: (retryAfter) => new FortressError("RATE_LIMITED", "Too many requests", 429, { retryAfter }),
|
|
64
|
+
database: (message = "Database error", cause) => new FortressError("DATABASE_ERROR", message, 500, { cause }),
|
|
65
|
+
serviceUnavailable: (message = "Service unavailable", options) => new FortressError("SERVICE_UNAVAILABLE", message, 503, options),
|
|
66
|
+
validationError: (issues) => new FortressError("VALIDATION_ERROR", "Validation failed", 422, { details: issues }),
|
|
67
|
+
/**
|
|
68
|
+
* RFC 6749 §5.2 / §4.1.2.1 OAuth error.
|
|
69
|
+
*
|
|
70
|
+
* The HTTP error mapper detects `oauthError` and emits the OAuth-spec
|
|
71
|
+
* JSON body `{ error, error_description, error_uri? }` instead of the
|
|
72
|
+
* default fortress `{ code, message }` shape, so strict OAuth clients
|
|
73
|
+
* (Moodle, openid-client, Spring Security, etc.) can switch behaviour
|
|
74
|
+
* on the machine-readable `error` field.
|
|
75
|
+
*
|
|
76
|
+
* Status is inferred from the OAuth code per the spec table:
|
|
77
|
+
* - `invalid_client` → 401
|
|
78
|
+
* - everything else → 400
|
|
79
|
+
*/
|
|
80
|
+
oauth: (error, description, options) => {
|
|
81
|
+
const status = options?.status ?? (error === "invalid_client" ? 401 : 400);
|
|
82
|
+
const code = status === 401 ? "UNAUTHORIZED" : "BAD_REQUEST";
|
|
83
|
+
return new FortressError(code, description ?? error, status, {
|
|
84
|
+
oauthError: error,
|
|
85
|
+
oauthDescription: description,
|
|
86
|
+
oauthErrorUri: options?.errorUri,
|
|
87
|
+
cause: options?.cause
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
/**
|
|
91
|
+
* Reconstruct a {@link FortressError} from a JSON error body emitted by
|
|
92
|
+
* `errorToResponse`. Used by the in-process `fortress.call.*` client to
|
|
93
|
+
* convert non-2xx responses into typed throws that mirror what a direct
|
|
94
|
+
* service-method call would produce.
|
|
95
|
+
*
|
|
96
|
+
* Maps by HTTP status when the body doesn't carry a recognizable
|
|
97
|
+
* `{ code, message }` payload, so network errors and opaque upstream
|
|
98
|
+
* failures still become structured `FortressError`s.
|
|
99
|
+
*/
|
|
100
|
+
fromHttpResponse: (status, body) => {
|
|
101
|
+
const payload = body && typeof body === "object" ? body : {};
|
|
102
|
+
const message = typeof payload.message === "string" ? payload.message : `HTTP ${status}`;
|
|
103
|
+
const code = typeof payload.code === "string" && isFortressErrorCode(payload.code) ? payload.code : statusToErrorCode(status);
|
|
104
|
+
return new FortressError(code, message, status, { details: payload.details });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
function isFortressErrorCode(code) {
|
|
108
|
+
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";
|
|
109
|
+
}
|
|
110
|
+
function statusToErrorCode(status) {
|
|
111
|
+
if (status === 401)
|
|
112
|
+
return "UNAUTHORIZED";
|
|
113
|
+
if (status === 403)
|
|
114
|
+
return "FORBIDDEN";
|
|
115
|
+
if (status === 404)
|
|
116
|
+
return "NOT_FOUND";
|
|
117
|
+
if (status === 409)
|
|
118
|
+
return "CONFLICT";
|
|
119
|
+
if (status === 422)
|
|
120
|
+
return "UNPROCESSABLE_ENTITY";
|
|
121
|
+
if (status === 429)
|
|
122
|
+
return "RATE_LIMITED";
|
|
123
|
+
if (status === 503)
|
|
124
|
+
return "SERVICE_UNAVAILABLE";
|
|
125
|
+
if (status >= 500)
|
|
126
|
+
return "DATABASE_ERROR";
|
|
127
|
+
return "BAD_REQUEST";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/plugins/email-verification/index.ts
|
|
131
|
+
function emailVerification(config = {}) {
|
|
132
|
+
const tokenExpirySeconds = config.tokenExpirySeconds ?? 86400;
|
|
133
|
+
const requireVerification = config.requireVerification ?? true;
|
|
134
|
+
async function consumeVerificationToken(db, rawToken, expectedUserId) {
|
|
135
|
+
const hash = await hashToken(rawToken);
|
|
136
|
+
const usedAt = /* @__PURE__ */ new Date();
|
|
137
|
+
const record = await db.update({
|
|
138
|
+
model: "email_verification_token",
|
|
139
|
+
where: [
|
|
140
|
+
{ field: "token", operator: "=", value: hash },
|
|
141
|
+
{ field: "usedAt", operator: "isNull", value: null },
|
|
142
|
+
{ field: "expiresAt", operator: "gt", value: usedAt },
|
|
143
|
+
...expectedUserId ? [{ field: "userId", operator: "=", value: expectedUserId }] : []
|
|
144
|
+
],
|
|
145
|
+
data: { usedAt }
|
|
146
|
+
});
|
|
147
|
+
if (!record)
|
|
148
|
+
throw Errors.notFound("Invalid or expired verification token");
|
|
149
|
+
await db.update({
|
|
150
|
+
model: "user",
|
|
151
|
+
where: [{ field: "id", operator: "=", value: record.userId }],
|
|
152
|
+
data: { email: record.email, emailVerified: true }
|
|
153
|
+
});
|
|
154
|
+
return record;
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
name: "email-verification",
|
|
158
|
+
models: [{
|
|
159
|
+
name: "email_verification_token",
|
|
160
|
+
fields: {
|
|
161
|
+
id: { type: "number", required: true },
|
|
162
|
+
userId: { type: "number", required: true, references: { model: "user", field: "id" } },
|
|
163
|
+
token: { type: "string", required: true },
|
|
164
|
+
email: { type: "string", required: true },
|
|
165
|
+
expiresAt: { type: "date", required: true },
|
|
166
|
+
usedAt: { type: "date" },
|
|
167
|
+
createdAt: { type: "date", required: true }
|
|
168
|
+
}
|
|
169
|
+
}],
|
|
170
|
+
hooks: {
|
|
171
|
+
postAuthGate: {
|
|
172
|
+
reason: "email-verification",
|
|
173
|
+
async evaluate(ctx) {
|
|
174
|
+
if (!requireVerification || ctx.user.emailVerified)
|
|
175
|
+
return;
|
|
176
|
+
const tokens = await ctx.db.findMany({
|
|
177
|
+
model: "email_verification_token",
|
|
178
|
+
where: [{ field: "userId", operator: "=", value: ctx.user.id }]
|
|
179
|
+
});
|
|
180
|
+
return tokens.some((token) => token.usedAt !== null) ? void 0 : { pluginData: { requiresEmailVerification: true } };
|
|
181
|
+
},
|
|
182
|
+
async verify(ctx, completion) {
|
|
183
|
+
if (typeof completion !== "string")
|
|
184
|
+
throw Errors.unauthorized("Invalid verification token");
|
|
185
|
+
await consumeVerificationToken(ctx.db, completion, ctx.user.id);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
async afterRegister(ctx, user) {
|
|
189
|
+
const { raw, hash } = await generateRefreshToken();
|
|
190
|
+
const expiresAt = new Date(Date.now() + tokenExpirySeconds * 1e3);
|
|
191
|
+
await ctx.db.create({
|
|
192
|
+
model: "email_verification_token",
|
|
193
|
+
data: {
|
|
194
|
+
userId: user.id,
|
|
195
|
+
token: hash,
|
|
196
|
+
email: user.email,
|
|
197
|
+
expiresAt,
|
|
198
|
+
usedAt: null
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
if (config.onSendVerification) {
|
|
202
|
+
await config.onSendVerification(user.email, raw, user.id);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
methods: (ctx) => ({
|
|
207
|
+
async sendVerification(userId, email) {
|
|
208
|
+
const user = await ctx.db.findOne({
|
|
209
|
+
model: "user",
|
|
210
|
+
where: [{ field: "id", operator: "=", value: userId }]
|
|
211
|
+
});
|
|
212
|
+
if (!user)
|
|
213
|
+
throw Errors.notFound("User not found");
|
|
214
|
+
const targetEmail = email ?? user.email;
|
|
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,
|
|
221
|
+
token: hash,
|
|
222
|
+
email: targetEmail,
|
|
223
|
+
expiresAt,
|
|
224
|
+
usedAt: null
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
if (config.onSendVerification) {
|
|
228
|
+
await config.onSendVerification(targetEmail, raw, userId);
|
|
229
|
+
}
|
|
230
|
+
return { sent: true };
|
|
231
|
+
},
|
|
232
|
+
async verify(rawToken) {
|
|
233
|
+
const record = await ctx.db.transaction((tx) => consumeVerificationToken(tx, rawToken));
|
|
234
|
+
return { userId: record.userId, email: record.email };
|
|
235
|
+
},
|
|
236
|
+
async completeVerification(continuationToken, verificationToken, meta) {
|
|
237
|
+
if (!ctx.auth)
|
|
238
|
+
throw Errors.badRequest("Auth service is unavailable");
|
|
239
|
+
return ctx.auth.completePendingAuth(continuationToken, verificationToken, meta);
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
export {
|
|
245
|
+
emailVerification
|
|
246
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
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/magic-link/index.ts
|
|
21
|
+
var magic_link_exports = {};
|
|
22
|
+
__export(magic_link_exports, {
|
|
23
|
+
magicLink: () => magicLink
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(magic_link_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/magic-link/index.ts
|
|
157
|
+
function magicLink(config = {}) {
|
|
158
|
+
const tokenExpirySeconds = config.tokenExpirySeconds ?? 600;
|
|
159
|
+
return {
|
|
160
|
+
name: "magic-link",
|
|
161
|
+
models: [{
|
|
162
|
+
name: "magic_link_token",
|
|
163
|
+
fields: {
|
|
164
|
+
id: { type: "number", required: true },
|
|
165
|
+
email: { type: "string", required: true },
|
|
166
|
+
token: { type: "string", required: true },
|
|
167
|
+
expiresAt: { type: "date", required: true },
|
|
168
|
+
usedAt: { type: "date" },
|
|
169
|
+
createdAt: { type: "date", required: true }
|
|
170
|
+
}
|
|
171
|
+
}],
|
|
172
|
+
methods: (ctx) => ({
|
|
173
|
+
async sendMagicLink(email) {
|
|
174
|
+
const { raw, hash } = await generateRefreshToken();
|
|
175
|
+
const expiresAt = new Date(Date.now() + tokenExpirySeconds * 1e3);
|
|
176
|
+
await ctx.db.create({
|
|
177
|
+
model: "magic_link_token",
|
|
178
|
+
data: {
|
|
179
|
+
email,
|
|
180
|
+
token: hash,
|
|
181
|
+
expiresAt,
|
|
182
|
+
usedAt: null
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
if (config.onSendMagicLink) {
|
|
186
|
+
await config.onSendMagicLink(email, raw);
|
|
187
|
+
}
|
|
188
|
+
return { sent: true };
|
|
189
|
+
},
|
|
190
|
+
async verify(rawToken, meta) {
|
|
191
|
+
const hash = await hashToken(rawToken);
|
|
192
|
+
const usedAt = /* @__PURE__ */ new Date();
|
|
193
|
+
const record = await ctx.db.transaction((tx) => tx.update({
|
|
194
|
+
model: "magic_link_token",
|
|
195
|
+
where: [
|
|
196
|
+
{ field: "token", operator: "=", value: hash },
|
|
197
|
+
{ field: "usedAt", operator: "isNull", value: null },
|
|
198
|
+
{ field: "expiresAt", operator: "gt", value: usedAt }
|
|
199
|
+
],
|
|
200
|
+
data: { usedAt }
|
|
201
|
+
}));
|
|
202
|
+
if (!record)
|
|
203
|
+
throw Errors.notFound("Invalid or expired magic link token");
|
|
204
|
+
let user = await ctx.db.findOne({
|
|
205
|
+
model: "user",
|
|
206
|
+
where: [{ field: "email", operator: "=", value: record.email }]
|
|
207
|
+
});
|
|
208
|
+
if (!user) {
|
|
209
|
+
user = await ctx.auth.createUser({
|
|
210
|
+
email: record.email,
|
|
211
|
+
name: record.email.split("@")[0]
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (!user.emailVerified) {
|
|
215
|
+
await ctx.db.update({
|
|
216
|
+
model: "user",
|
|
217
|
+
where: [{ field: "id", operator: "=", value: user.id }],
|
|
218
|
+
data: { emailVerified: true }
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (!ctx.auth)
|
|
222
|
+
throw Errors.badRequest("Auth service is unavailable");
|
|
223
|
+
return ctx.auth.completePluginAuth(user.id, "magic-link", meta);
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
229
|
+
0 && (module.exports = {
|
|
230
|
+
magicLink
|
|
231
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
* Passwordless magic-link plugin for fortress.
|
|
9
|
+
*
|
|
10
|
+
* Issues short-lived hashed tokens delivered out-of-band (typically by email)
|
|
11
|
+
* and exchanges a valid token for a fortress access/refresh token pair.
|
|
12
|
+
* Useful as a passwordless sign-in flow or as a recovery mechanism.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface MagicLinkConfig {
|
|
18
|
+
/** Token expiry in seconds. Default: 600 (10 minutes). */
|
|
19
|
+
tokenExpirySeconds?: number;
|
|
20
|
+
/** Called when a magic link token is created -- send the link to the user */
|
|
21
|
+
onSendMagicLink?: (email: string, token: string) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
interface MagicLinkMethods {
|
|
24
|
+
sendMagicLink: (email: string) => Promise<{
|
|
25
|
+
sent: true;
|
|
26
|
+
}>;
|
|
27
|
+
verify: (rawToken: string, meta?: RequestMeta) => Promise<AuthResult>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Magic link plugin factory. Returns a {@link FortressPlugin} that issues
|
|
31
|
+
* short-lived hashed tokens for passwordless sign-in. Tokens are typically
|
|
32
|
+
* delivered out-of-band (email) and exchanged for fortress access/refresh
|
|
33
|
+
* tokens via the verify endpoint.
|
|
34
|
+
*/
|
|
35
|
+
declare function magicLink(config?: MagicLinkConfig): FortressPlugin & {
|
|
36
|
+
readonly name: 'magic-link';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { type MagicLinkConfig, type MagicLinkMethods, magicLink };
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
* Passwordless magic-link plugin for fortress.
|
|
9
|
+
*
|
|
10
|
+
* Issues short-lived hashed tokens delivered out-of-band (typically by email)
|
|
11
|
+
* and exchanges a valid token for a fortress access/refresh token pair.
|
|
12
|
+
* Useful as a passwordless sign-in flow or as a recovery mechanism.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface MagicLinkConfig {
|
|
18
|
+
/** Token expiry in seconds. Default: 600 (10 minutes). */
|
|
19
|
+
tokenExpirySeconds?: number;
|
|
20
|
+
/** Called when a magic link token is created -- send the link to the user */
|
|
21
|
+
onSendMagicLink?: (email: string, token: string) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
interface MagicLinkMethods {
|
|
24
|
+
sendMagicLink: (email: string) => Promise<{
|
|
25
|
+
sent: true;
|
|
26
|
+
}>;
|
|
27
|
+
verify: (rawToken: string, meta?: RequestMeta) => Promise<AuthResult>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Magic link plugin factory. Returns a {@link FortressPlugin} that issues
|
|
31
|
+
* short-lived hashed tokens for passwordless sign-in. Tokens are typically
|
|
32
|
+
* delivered out-of-band (email) and exchanged for fortress access/refresh
|
|
33
|
+
* tokens via the verify endpoint.
|
|
34
|
+
*/
|
|
35
|
+
declare function magicLink(config?: MagicLinkConfig): FortressPlugin & {
|
|
36
|
+
readonly name: 'magic-link';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { type MagicLinkConfig, type MagicLinkMethods, magicLink };
|