@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,186 @@
|
|
|
1
|
+
# Data Isolation Plugin
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `data-isolation` plugin adds row-level data isolation to Fortress. It works with any database by automatically injecting WHERE filters on reads and default values on creates based on configurable scopes.
|
|
6
|
+
|
|
7
|
+
Unlike the `tenancy` plugin (which uses PostgreSQL schema-per-tenant isolation), this plugin operates at the row level and supports any database backend.
|
|
8
|
+
|
|
9
|
+
Runtime note: scoped bypass helpers (`withoutScope()` and `unscoped()`) use
|
|
10
|
+
`node:async_hooks` / `AsyncLocalStorage` to keep bypass state local to the
|
|
11
|
+
current async request. Use this plugin in Node/Bun-compatible runtimes that
|
|
12
|
+
provide `node:async_hooks`; edge/browser-like runtimes may need a custom
|
|
13
|
+
request-context wrapper or should avoid those bypass helpers.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Import the `dataIsolation` factory and pass it in the `plugins` array when creating a Fortress instance:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createFortress } from '@bajustone/fortress';
|
|
21
|
+
import { dataIsolation } from '@bajustone/fortress/plugins/data-isolation';
|
|
22
|
+
|
|
23
|
+
const fortress = createFortress({
|
|
24
|
+
jwt: { key: 'your-secret-at-least-32-bytes!!' },
|
|
25
|
+
database: adapter,
|
|
26
|
+
plugins: [
|
|
27
|
+
dataIsolation({
|
|
28
|
+
scopes: [
|
|
29
|
+
{
|
|
30
|
+
name: 'organization',
|
|
31
|
+
field: 'orgId',
|
|
32
|
+
models: ['post', 'comment'],
|
|
33
|
+
resolveValue: async (userId, ctx) => {
|
|
34
|
+
const user = await ctx.db.findOne({
|
|
35
|
+
model: 'user',
|
|
36
|
+
where: [{ field: 'id', operator: '=', value: userId }],
|
|
37
|
+
});
|
|
38
|
+
return user?.orgId;
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Once registered, methods are available at `fortress.plugins['data-isolation']` with full type safety.
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
The `DataIsolationConfig` requires a `scopes` array:
|
|
52
|
+
|
|
53
|
+
| Option | Type | Required | Description |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| `scopes` | `DataIsolationScope[]` | Yes | Array of scope definitions that control row-level filtering. |
|
|
56
|
+
| `unresolvedScope` | `'deny' \| 'skip'` | No | Behavior for a nullish applicable scope. Defaults to secure `'deny'`. `'skip'` is unsafe legacy compatibility mode. |
|
|
57
|
+
|
|
58
|
+
Each `DataIsolationScope` has the following fields:
|
|
59
|
+
|
|
60
|
+
| Field | Type | Description |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `name` | `string` | Scope name for identification and bypass control. |
|
|
63
|
+
| `field` | `string` | Column name that holds the scoping value in the target tables. |
|
|
64
|
+
| `models` | `string[]` | Which models (tables) this scope applies to. Use `['*']` for all models. |
|
|
65
|
+
| `resolveValue` | `(userId: string, ctx: PluginContext) => Promise<unknown>` | Async function that returns the current user's value for this scope. |
|
|
66
|
+
|
|
67
|
+
## How It Works
|
|
68
|
+
|
|
69
|
+
The plugin implements `scopeRules`, which the database adapter calls on every query:
|
|
70
|
+
|
|
71
|
+
- **Reads** (`findOne`, `findMany`, `count`) -- A WHERE filter is added: `WHERE {field} = {resolvedValue}`.
|
|
72
|
+
- **Creates** (`create`) -- The scoping field is set as a default value: `data.{field} = {resolvedValue}`.
|
|
73
|
+
|
|
74
|
+
If an applicable `resolveValue` returns `null` or `undefined`, the plugin fails closed by default with a `FORBIDDEN` error. Scope resolution happens before a scoped adapter is returned, so both reads and creates are denied rather than becoming unscoped or accepting caller-controlled scope fields.
|
|
75
|
+
|
|
76
|
+
For migration only, `unresolvedScope: 'skip'` restores the unsafe legacy behavior of omitting an unresolved filter. This can expose every row and permit cross-scope creates; do not use it as an authorization bypass. Use the explicit async-context-local `withoutScope()` or `unscoped()` helpers for reviewed administrative operations.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// Unsafe legacy compatibility only — remove after scope assignments are complete.
|
|
80
|
+
dataIsolation({
|
|
81
|
+
unresolvedScope: 'skip',
|
|
82
|
+
scopes: [/* ... */],
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Example behavior
|
|
87
|
+
|
|
88
|
+
With a scope `{ name: 'organization', field: 'orgId', models: ['post'] }` and a user whose `orgId` is `'acme'`:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// This query:
|
|
92
|
+
db.findMany({ model: 'post' });
|
|
93
|
+
// Becomes: SELECT * FROM post WHERE orgId = 'acme'
|
|
94
|
+
|
|
95
|
+
// This create:
|
|
96
|
+
db.create({ model: 'post', data: { title: 'Hello' } });
|
|
97
|
+
// Becomes: INSERT INTO post (title, orgId) VALUES ('Hello', 'acme')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API Reference
|
|
101
|
+
|
|
102
|
+
| Method | Signature | Returns |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `withoutScope` | `<T>(scopeName: string, fn: () => Promise<T>)` | `Promise<T>` |
|
|
105
|
+
| `unscoped` | `<T>(fn: () => Promise<T>)` | `Promise<T>` |
|
|
106
|
+
|
|
107
|
+
### withoutScope
|
|
108
|
+
|
|
109
|
+
Temporarily bypasses a specific named scope. Queries within the callback run without that scope's filter:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
await fortress.plugins['data-isolation'].withoutScope('organization', async () => {
|
|
113
|
+
// Queries here run without the 'organization' filter
|
|
114
|
+
const allPosts = await db.findMany({ model: 'post' });
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### unscoped
|
|
119
|
+
|
|
120
|
+
Temporarily bypasses all scopes. Use with caution -- no row-level isolation is applied within the callback:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
await fortress.plugins['data-isolation'].unscoped(async () => {
|
|
124
|
+
// No scoping at all
|
|
125
|
+
const everything = await db.findMany({ model: 'post' });
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Using Scoped DB in Middleware
|
|
130
|
+
|
|
131
|
+
In Hono and Express, use the `getScopedDb` helper to get a database adapter with isolation applied:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
app.get('/api/posts', async (c) => {
|
|
135
|
+
const scopedDb = await getScopedDb(c, 'post');
|
|
136
|
+
const posts = await scopedDb.findMany({ model: 'post' });
|
|
137
|
+
// Only returns posts the current user has access to
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Example
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { createFortress } from '@bajustone/fortress';
|
|
145
|
+
import { dataIsolation } from '@bajustone/fortress/plugins/data-isolation';
|
|
146
|
+
|
|
147
|
+
const fortress = createFortress({
|
|
148
|
+
jwt: { key: 'your-secret-at-least-32-bytes!!' },
|
|
149
|
+
database: adapter,
|
|
150
|
+
plugins: [
|
|
151
|
+
dataIsolation({
|
|
152
|
+
scopes: [
|
|
153
|
+
{
|
|
154
|
+
name: 'organization',
|
|
155
|
+
field: 'orgId',
|
|
156
|
+
models: ['post', 'comment', 'invoice'],
|
|
157
|
+
resolveValue: async (userId, ctx) => {
|
|
158
|
+
const user = await ctx.db.findOne({
|
|
159
|
+
model: 'user',
|
|
160
|
+
where: [{ field: 'id', operator: '=', value: userId }],
|
|
161
|
+
});
|
|
162
|
+
return user?.orgId;
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'department',
|
|
167
|
+
field: 'deptId',
|
|
168
|
+
models: ['ticket'],
|
|
169
|
+
resolveValue: async (userId, ctx) => {
|
|
170
|
+
const user = await ctx.db.findOne({
|
|
171
|
+
model: 'user',
|
|
172
|
+
where: [{ field: 'id', operator: '=', value: userId }],
|
|
173
|
+
});
|
|
174
|
+
return user?.deptId;
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
}),
|
|
179
|
+
],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Admin operation that needs cross-org visibility:
|
|
183
|
+
await fortress.plugins['data-isolation'].withoutScope('organization', async () => {
|
|
184
|
+
const allInvoices = await db.findMany({ model: 'invoice' });
|
|
185
|
+
});
|
|
186
|
+
```
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Email verification
|
|
2
|
+
|
|
3
|
+
Register the plugin and deliver raw verification tokens through your mail provider:
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { emailVerification } from '@bajustone/fortress/plugins/email-verification';
|
|
7
|
+
|
|
8
|
+
const fortress = createFortress({
|
|
9
|
+
database,
|
|
10
|
+
jwt: { key },
|
|
11
|
+
plugins: [emailVerification({
|
|
12
|
+
tokenExpirySeconds: 24 * 60 * 60,
|
|
13
|
+
requireVerification: true,
|
|
14
|
+
onSendVerification: async (email, token) => {
|
|
15
|
+
const url = `https://app.example.com/verify?token=${encodeURIComponent(token)}`;
|
|
16
|
+
await mailer.send({ to: email, text: url });
|
|
17
|
+
},
|
|
18
|
+
})] as const,
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Option | Default | Use |
|
|
23
|
+
|---|---:|---|
|
|
24
|
+
| `tokenExpirySeconds` | `86400` | Token lifetime |
|
|
25
|
+
| `requireVerification` | `true` | Hold login before session issuance |
|
|
26
|
+
| `onSendVerification` | — | Deliver the raw token |
|
|
27
|
+
|
|
28
|
+
Fortress stores only the SHA-256 token hash.
|
|
29
|
+
|
|
30
|
+
## Registration
|
|
31
|
+
|
|
32
|
+
Creating a user creates and sends a token:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
await fortress.auth.createUser({
|
|
36
|
+
email: 'alice@example.com',
|
|
37
|
+
name: 'Alice',
|
|
38
|
+
password: 'correct-horse-battery-staple',
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`afterRegister` is a committed side-effect hook. If token creation or delivery throws, Fortress logs the failure and keeps the created user. Retry delivery with `sendVerification`:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
await fortress.plugins['email-verification'].sendVerification(userId);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Send a changed-address token without changing the account immediately:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
await fortress.plugins['email-verification'].sendVerification(
|
|
52
|
+
userId,
|
|
53
|
+
'new-address@example.com',
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The new address is adopted only after that token is verified.
|
|
58
|
+
|
|
59
|
+
## Complete a pending login
|
|
60
|
+
|
|
61
|
+
With `requireVerification: true`, correct primary credentials return `pending` instead of issuing tokens:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
const login = await fortress.auth.login(email, password);
|
|
65
|
+
|
|
66
|
+
if (login.status === 'pending' && login.pending.reason === 'email-verification') {
|
|
67
|
+
const result = await fortress.plugins['email-verification'].completeVerification(
|
|
68
|
+
login.pending.continuationToken,
|
|
69
|
+
tokenFromEmail,
|
|
70
|
+
{ ipAddress, userAgent },
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (result.status === 'success') {
|
|
74
|
+
result.accessToken;
|
|
75
|
+
result.refreshToken;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`completeVerification` consumes both the single-use continuation and the email token, then reruns any remaining post-auth gates before issuing a session.
|
|
81
|
+
|
|
82
|
+
## Verify outside login
|
|
83
|
+
|
|
84
|
+
Use `verify` for a standalone verification page:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const verified = await fortress.plugins['email-verification'].verify(tokenFromUrl);
|
|
88
|
+
// { userId, email }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A token is accepted only when it:
|
|
92
|
+
|
|
93
|
+
- matches a stored hash;
|
|
94
|
+
- has not been used;
|
|
95
|
+
- has not expired;
|
|
96
|
+
- belongs to the pending user when completing login.
|
|
97
|
+
|
|
98
|
+
Consumption and the user update run transactionally. Invalid, used, expired, or mismatched tokens throw `NOT_FOUND` without revealing which check failed.
|
|
99
|
+
|
|
100
|
+
## Methods
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface EmailVerificationMethods {
|
|
104
|
+
sendVerification(
|
|
105
|
+
userId: string,
|
|
106
|
+
email?: string,
|
|
107
|
+
): Promise<{ sent: true }>;
|
|
108
|
+
|
|
109
|
+
verify(
|
|
110
|
+
rawToken: string,
|
|
111
|
+
): Promise<{ userId: string; email: string }>;
|
|
112
|
+
|
|
113
|
+
completeVerification(
|
|
114
|
+
continuationToken: string,
|
|
115
|
+
verificationToken: string,
|
|
116
|
+
meta?: RequestMeta,
|
|
117
|
+
): Promise<AuthResult>;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The plugin owns no HTTP routes. Use its methods in host routes. The core auth pipeline owns pending-auth continuation behavior.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Magic Link Plugin
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `magic-link` plugin adds passwordless authentication via one-time email tokens to Fortress. Users receive a time-limited link that logs them in without a password. If no account exists for the given email, one is created automatically (JIT provisioning).
|
|
6
|
+
|
|
7
|
+
Tokens are generated using cryptographically secure random bytes and hashed with SHA-256 before storage. Each token is single-use.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Import the `magicLink` factory and pass it in the `plugins` array when creating a Fortress instance:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createFortress } from '@bajustone/fortress';
|
|
15
|
+
import { magicLink } from '@bajustone/fortress/plugins/magic-link';
|
|
16
|
+
|
|
17
|
+
const fortress = createFortress({
|
|
18
|
+
jwt: { key: 'your-secret-at-least-32-bytes!!' },
|
|
19
|
+
database: adapter,
|
|
20
|
+
plugins: [
|
|
21
|
+
magicLink({
|
|
22
|
+
tokenExpirySeconds: 600,
|
|
23
|
+
onSendMagicLink: async (email, token) => {
|
|
24
|
+
await sendEmail(email, `Login: https://myapp.com/auth/magic?token=${token}`);
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Once registered, methods are available at `fortress.plugins['magic-link']` with full type safety.
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
All fields on `MagicLinkConfig` are optional:
|
|
36
|
+
|
|
37
|
+
| Option | Type | Default | Description |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| `tokenExpirySeconds` | `number` | `600` (10 minutes) | How long a magic link token remains valid, in seconds. |
|
|
40
|
+
| `onSendMagicLink` | `(email: string, token: string) => Promise<void>` | `undefined` | Callback invoked when a magic link token is created. Use this to send the email containing the link. |
|
|
41
|
+
|
|
42
|
+
## API Reference
|
|
43
|
+
|
|
44
|
+
| Method | Signature | Returns |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `sendMagicLink` | `(email: string)` | `Promise<{ sent: true }>` |
|
|
47
|
+
| `verify` | `(rawToken: string, meta?: RequestMeta)` | `Promise<AuthResult>` |
|
|
48
|
+
|
|
49
|
+
### sendMagicLink
|
|
50
|
+
|
|
51
|
+
Generates a magic link token and delivers it via the `onSendMagicLink` callback:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
await fortress.plugins['magic-link'].sendMagicLink('alice@example.com');
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The user does not need to exist in the database. If they do not exist, the account is created when the link is verified.
|
|
58
|
+
|
|
59
|
+
### verify
|
|
60
|
+
|
|
61
|
+
Atomically consumes a magic-link token and returns the unified auth result:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const result = await fortress.plugins['magic-link'].verify(rawToken);
|
|
65
|
+
|
|
66
|
+
if (result.status === 'success') {
|
|
67
|
+
console.log(result.user.id);
|
|
68
|
+
console.log(result.user.email);
|
|
69
|
+
console.log(result.accessToken, result.refreshToken);
|
|
70
|
+
}
|
|
71
|
+
else if (result.status === 'pending') {
|
|
72
|
+
// Another configured post-auth gate must complete first.
|
|
73
|
+
console.log(result.pending.reason, result.pending.continuationToken);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The equivalent HTTP endpoint is `POST /auth/magic-link/verify` with `{ token }`.
|
|
78
|
+
|
|
79
|
+
This method:
|
|
80
|
+
1. Hashes the raw token and looks it up in the database.
|
|
81
|
+
2. Checks that the token has not been used and has not expired.
|
|
82
|
+
3. Marks the token as used (`usedAt` timestamp).
|
|
83
|
+
4. Finds the user by email, or creates a new account if none exists (JIT provisioning).
|
|
84
|
+
5. Runs every configured post-auth gate and issues an access/refresh session only when they all pass.
|
|
85
|
+
|
|
86
|
+
Throws:
|
|
87
|
+
- `NotFound` -- Invalid or unknown token.
|
|
88
|
+
- `BadRequest` -- Token already used.
|
|
89
|
+
- `BadRequest` -- Token expired.
|
|
90
|
+
|
|
91
|
+
## Example
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { createFortress } from '@bajustone/fortress';
|
|
95
|
+
import { magicLink } from '@bajustone/fortress/plugins/magic-link';
|
|
96
|
+
|
|
97
|
+
const fortress = createFortress({
|
|
98
|
+
jwt: { key: 'your-secret-at-least-32-bytes!!' },
|
|
99
|
+
database: adapter,
|
|
100
|
+
plugins: [
|
|
101
|
+
magicLink({
|
|
102
|
+
tokenExpirySeconds: 300, // 5 minutes
|
|
103
|
+
onSendMagicLink: async (email, token) => {
|
|
104
|
+
await mailer.send({
|
|
105
|
+
to: email,
|
|
106
|
+
subject: 'Your login link',
|
|
107
|
+
html: `<a href="https://myapp.com/auth/magic?token=${token}">Click to sign in</a>`,
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Step 1: User requests a magic link
|
|
115
|
+
await fortress.plugins['magic-link'].sendMagicLink('alice@example.com');
|
|
116
|
+
|
|
117
|
+
// Step 2: User clicks the link -- your handler calls verify
|
|
118
|
+
const result = await fortress.plugins['magic-link'].verify(tokenFromUrl);
|
|
119
|
+
|
|
120
|
+
if (result.status === 'success') {
|
|
121
|
+
// The user is authenticated with result.accessToken/result.refreshToken.
|
|
122
|
+
}
|
|
123
|
+
```
|