@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
package/dist/otel.cjs
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/otel/index.ts
|
|
31
|
+
var otel_exports = {};
|
|
32
|
+
__export(otel_exports, {
|
|
33
|
+
createOtelTelemetry: () => createOtelTelemetry
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(otel_exports);
|
|
36
|
+
async function createOtelTelemetry(options = {}) {
|
|
37
|
+
let api;
|
|
38
|
+
try {
|
|
39
|
+
api = await import("@opentelemetry/api");
|
|
40
|
+
} catch (cause) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"@opentelemetry/api is not installed. Add it to your dependencies to use createOtelTelemetry().",
|
|
43
|
+
{ cause }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const name = options.name ?? "fortress";
|
|
47
|
+
const version = options.version ?? "0.0.x";
|
|
48
|
+
const otelTracer = api.trace.getTracer(name, version);
|
|
49
|
+
const otelMeter = api.metrics.getMeter(name, version);
|
|
50
|
+
const { SpanStatusCode } = api;
|
|
51
|
+
const wrapSpan = (span) => ({
|
|
52
|
+
setAttribute: (k, v) => {
|
|
53
|
+
span.setAttribute(k, v);
|
|
54
|
+
},
|
|
55
|
+
recordException: (err) => {
|
|
56
|
+
span.recordException(err);
|
|
57
|
+
},
|
|
58
|
+
setStatus: (s) => {
|
|
59
|
+
span.setStatus({
|
|
60
|
+
code: s.code === "ok" ? SpanStatusCode.OK : SpanStatusCode.ERROR,
|
|
61
|
+
message: s.message
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
end: () => {
|
|
65
|
+
span.end();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
tracer: {
|
|
70
|
+
startSpan(spanName, attrs) {
|
|
71
|
+
return wrapSpan(otelTracer.startSpan(spanName, { attributes: attrs }));
|
|
72
|
+
},
|
|
73
|
+
startActiveSpan(spanName, attrs, callback) {
|
|
74
|
+
return otelTracer.startActiveSpan(
|
|
75
|
+
spanName,
|
|
76
|
+
{ attributes: attrs },
|
|
77
|
+
async (span) => callback(wrapSpan(span))
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
meter: {
|
|
82
|
+
createCounter(metricName, opts) {
|
|
83
|
+
const c = otelMeter.createCounter(metricName, opts);
|
|
84
|
+
return {
|
|
85
|
+
add: (v, a) => {
|
|
86
|
+
c.add(v, a);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
createHistogram(metricName, opts) {
|
|
91
|
+
const h = otelMeter.createHistogram(metricName, {
|
|
92
|
+
description: opts?.description,
|
|
93
|
+
unit: opts?.unit,
|
|
94
|
+
advice: opts?.boundaries ? { explicitBucketBoundaries: opts.boundaries } : void 0
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
record: (v, a) => {
|
|
98
|
+
h.record(v, a);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
106
|
+
0 && (module.exports = {
|
|
107
|
+
createOtelTelemetry
|
|
108
|
+
});
|
package/dist/otel.d.cts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { T as TelemetryProvider } from './auth-service-BcyQ2PuZ.cjs';
|
|
2
|
+
export { A as AttributeValue, a as Attributes, b as AuthEvent, c as AuthEventListener, C as Counter, F as FortressLogger, H as Histogram, I as IamEvent, d as IamEventListener, M as Meter, P as PermissionCheckEvent, e as PermissionCheckListener, S as Span, f as Tracer, U as Unsubscribe } from './auth-service-BcyQ2PuZ.cjs';
|
|
3
|
+
import './types-et0R8tsC.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OpenTelemetry adapter for Fortress.
|
|
7
|
+
*
|
|
8
|
+
* This is the **only file in the repository that imports `@opentelemetry/api`**.
|
|
9
|
+
* Core Fortress code depends on the internal {@link TelemetryProvider}
|
|
10
|
+
* interface in `src/core/observability/types.ts`; this sub-path exists so
|
|
11
|
+
* runtimes that never opt in (Cloudflare Workers, Deno without OTel, etc.)
|
|
12
|
+
* never resolve `@opentelemetry/api` at all.
|
|
13
|
+
*
|
|
14
|
+
* The import is dynamic (`await import(...)`) as a belt-and-suspenders
|
|
15
|
+
* guarantee: even if a bundler or package resolver attempts to evaluate
|
|
16
|
+
* this module, `@opentelemetry/api` is not referenced until
|
|
17
|
+
* {@link createOtelTelemetry} is actually called.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createFortress } from '@bajustone/fortress';
|
|
22
|
+
* import { createOtelTelemetry } from '@bajustone/fortress/otel';
|
|
23
|
+
* import pino from 'pino';
|
|
24
|
+
*
|
|
25
|
+
* const observability = await createOtelTelemetry({ name: 'my-app-auth' });
|
|
26
|
+
* const fortress = createFortress({
|
|
27
|
+
* // ...config
|
|
28
|
+
* logger: pino(),
|
|
29
|
+
* observability,
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* The returned provider satisfies Fortress's internal
|
|
34
|
+
* {@link TelemetryProvider} interface and can be wired into any
|
|
35
|
+
* `FortressConfig.observability` slot. Because Fortress's metric catalog
|
|
36
|
+
* uses the stable OTel semantic convention for database operations
|
|
37
|
+
* (`db.client.operation.duration`), Grafana/Prometheus dashboards that
|
|
38
|
+
* know about that name will automatically pick up Fortress's DB queries.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
interface OtelTelemetryOptions {
|
|
42
|
+
/** Instrumentation scope name. Defaults to `'fortress'`. */
|
|
43
|
+
name?: string;
|
|
44
|
+
/** Instrumentation scope version. Defaults to `'0.0.x'`. */
|
|
45
|
+
version?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a Fortress {@link TelemetryProvider} backed by OpenTelemetry's
|
|
49
|
+
* global tracer and meter providers.
|
|
50
|
+
*
|
|
51
|
+
* If `@opentelemetry/api` is not installed, this function throws with a
|
|
52
|
+
* clear error message pointing at the missing peer dependency. The
|
|
53
|
+
* dynamic import ensures the failure mode is a controlled throw at call
|
|
54
|
+
* time — not a module-load crash.
|
|
55
|
+
*
|
|
56
|
+
* When no OTel SDK is registered globally, the returned provider's spans
|
|
57
|
+
* and metric instruments will resolve to OTel's own no-op implementations
|
|
58
|
+
* and impose no measurable overhead.
|
|
59
|
+
*/
|
|
60
|
+
declare function createOtelTelemetry(options?: OtelTelemetryOptions): Promise<TelemetryProvider>;
|
|
61
|
+
|
|
62
|
+
export { type OtelTelemetryOptions, TelemetryProvider, createOtelTelemetry };
|
package/dist/otel.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { T as TelemetryProvider } from './auth-service-BkzZkWfH.js';
|
|
2
|
+
export { A as AttributeValue, a as Attributes, b as AuthEvent, c as AuthEventListener, C as Counter, F as FortressLogger, H as Histogram, I as IamEvent, d as IamEventListener, M as Meter, P as PermissionCheckEvent, e as PermissionCheckListener, S as Span, f as Tracer, U as Unsubscribe } from './auth-service-BkzZkWfH.js';
|
|
3
|
+
import './types-et0R8tsC.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OpenTelemetry adapter for Fortress.
|
|
7
|
+
*
|
|
8
|
+
* This is the **only file in the repository that imports `@opentelemetry/api`**.
|
|
9
|
+
* Core Fortress code depends on the internal {@link TelemetryProvider}
|
|
10
|
+
* interface in `src/core/observability/types.ts`; this sub-path exists so
|
|
11
|
+
* runtimes that never opt in (Cloudflare Workers, Deno without OTel, etc.)
|
|
12
|
+
* never resolve `@opentelemetry/api` at all.
|
|
13
|
+
*
|
|
14
|
+
* The import is dynamic (`await import(...)`) as a belt-and-suspenders
|
|
15
|
+
* guarantee: even if a bundler or package resolver attempts to evaluate
|
|
16
|
+
* this module, `@opentelemetry/api` is not referenced until
|
|
17
|
+
* {@link createOtelTelemetry} is actually called.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createFortress } from '@bajustone/fortress';
|
|
22
|
+
* import { createOtelTelemetry } from '@bajustone/fortress/otel';
|
|
23
|
+
* import pino from 'pino';
|
|
24
|
+
*
|
|
25
|
+
* const observability = await createOtelTelemetry({ name: 'my-app-auth' });
|
|
26
|
+
* const fortress = createFortress({
|
|
27
|
+
* // ...config
|
|
28
|
+
* logger: pino(),
|
|
29
|
+
* observability,
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* The returned provider satisfies Fortress's internal
|
|
34
|
+
* {@link TelemetryProvider} interface and can be wired into any
|
|
35
|
+
* `FortressConfig.observability` slot. Because Fortress's metric catalog
|
|
36
|
+
* uses the stable OTel semantic convention for database operations
|
|
37
|
+
* (`db.client.operation.duration`), Grafana/Prometheus dashboards that
|
|
38
|
+
* know about that name will automatically pick up Fortress's DB queries.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
interface OtelTelemetryOptions {
|
|
42
|
+
/** Instrumentation scope name. Defaults to `'fortress'`. */
|
|
43
|
+
name?: string;
|
|
44
|
+
/** Instrumentation scope version. Defaults to `'0.0.x'`. */
|
|
45
|
+
version?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a Fortress {@link TelemetryProvider} backed by OpenTelemetry's
|
|
49
|
+
* global tracer and meter providers.
|
|
50
|
+
*
|
|
51
|
+
* If `@opentelemetry/api` is not installed, this function throws with a
|
|
52
|
+
* clear error message pointing at the missing peer dependency. The
|
|
53
|
+
* dynamic import ensures the failure mode is a controlled throw at call
|
|
54
|
+
* time — not a module-load crash.
|
|
55
|
+
*
|
|
56
|
+
* When no OTel SDK is registered globally, the returned provider's spans
|
|
57
|
+
* and metric instruments will resolve to OTel's own no-op implementations
|
|
58
|
+
* and impose no measurable overhead.
|
|
59
|
+
*/
|
|
60
|
+
declare function createOtelTelemetry(options?: OtelTelemetryOptions): Promise<TelemetryProvider>;
|
|
61
|
+
|
|
62
|
+
export { type OtelTelemetryOptions, TelemetryProvider, createOtelTelemetry };
|
package/dist/otel.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// src/otel/index.ts
|
|
2
|
+
async function createOtelTelemetry(options = {}) {
|
|
3
|
+
let api;
|
|
4
|
+
try {
|
|
5
|
+
api = await import("@opentelemetry/api");
|
|
6
|
+
} catch (cause) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"@opentelemetry/api is not installed. Add it to your dependencies to use createOtelTelemetry().",
|
|
9
|
+
{ cause }
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
const name = options.name ?? "fortress";
|
|
13
|
+
const version = options.version ?? "0.0.x";
|
|
14
|
+
const otelTracer = api.trace.getTracer(name, version);
|
|
15
|
+
const otelMeter = api.metrics.getMeter(name, version);
|
|
16
|
+
const { SpanStatusCode } = api;
|
|
17
|
+
const wrapSpan = (span) => ({
|
|
18
|
+
setAttribute: (k, v) => {
|
|
19
|
+
span.setAttribute(k, v);
|
|
20
|
+
},
|
|
21
|
+
recordException: (err) => {
|
|
22
|
+
span.recordException(err);
|
|
23
|
+
},
|
|
24
|
+
setStatus: (s) => {
|
|
25
|
+
span.setStatus({
|
|
26
|
+
code: s.code === "ok" ? SpanStatusCode.OK : SpanStatusCode.ERROR,
|
|
27
|
+
message: s.message
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
end: () => {
|
|
31
|
+
span.end();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
tracer: {
|
|
36
|
+
startSpan(spanName, attrs) {
|
|
37
|
+
return wrapSpan(otelTracer.startSpan(spanName, { attributes: attrs }));
|
|
38
|
+
},
|
|
39
|
+
startActiveSpan(spanName, attrs, callback) {
|
|
40
|
+
return otelTracer.startActiveSpan(
|
|
41
|
+
spanName,
|
|
42
|
+
{ attributes: attrs },
|
|
43
|
+
async (span) => callback(wrapSpan(span))
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
meter: {
|
|
48
|
+
createCounter(metricName, opts) {
|
|
49
|
+
const c = otelMeter.createCounter(metricName, opts);
|
|
50
|
+
return {
|
|
51
|
+
add: (v, a) => {
|
|
52
|
+
c.add(v, a);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
createHistogram(metricName, opts) {
|
|
57
|
+
const h = otelMeter.createHistogram(metricName, {
|
|
58
|
+
description: opts?.description,
|
|
59
|
+
unit: opts?.unit,
|
|
60
|
+
advice: opts?.boundaries ? { explicitBucketBoundaries: opts.boundaries } : void 0
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
record: (v, a) => {
|
|
64
|
+
h.record(v, a);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
createOtelTelemetry
|
|
73
|
+
};
|
|
@@ -0,0 +1,322 @@
|
|
|
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/account-lockout/index.ts
|
|
21
|
+
var account_lockout_exports = {};
|
|
22
|
+
__export(account_lockout_exports, {
|
|
23
|
+
accountLockout: () => accountLockout
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(account_lockout_exports);
|
|
26
|
+
|
|
27
|
+
// src/core/errors.ts
|
|
28
|
+
var FortressError = class extends Error {
|
|
29
|
+
code;
|
|
30
|
+
statusCode;
|
|
31
|
+
retryAfter;
|
|
32
|
+
details;
|
|
33
|
+
/** RFC 6749 §5.2 / §4.1.2.1 machine code. Set by `Errors.oauth()`. */
|
|
34
|
+
oauthError;
|
|
35
|
+
/** Human-readable description for the OAuth `error_description` field. */
|
|
36
|
+
oauthDescription;
|
|
37
|
+
/** Optional URI for the OAuth `error_uri` field (§5.2). */
|
|
38
|
+
oauthErrorUri;
|
|
39
|
+
constructor(code, message, statusCode, options) {
|
|
40
|
+
super(message, { cause: options?.cause });
|
|
41
|
+
this.code = code;
|
|
42
|
+
this.statusCode = statusCode;
|
|
43
|
+
this.retryAfter = options?.retryAfter;
|
|
44
|
+
this.details = options?.details;
|
|
45
|
+
this.oauthError = options?.oauthError;
|
|
46
|
+
this.oauthDescription = options?.oauthDescription;
|
|
47
|
+
this.oauthErrorUri = options?.oauthErrorUri;
|
|
48
|
+
}
|
|
49
|
+
toJSON() {
|
|
50
|
+
return {
|
|
51
|
+
code: this.code,
|
|
52
|
+
message: this.message,
|
|
53
|
+
statusCode: this.statusCode,
|
|
54
|
+
...this.details !== void 0 && { details: this.details }
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var Errors = {
|
|
59
|
+
unauthorized: (message = "Unauthorized") => new FortressError("UNAUTHORIZED", message, 401),
|
|
60
|
+
tokenReuse: () => new FortressError("TOKEN_REUSE", "Token reuse detected", 401),
|
|
61
|
+
sessionIdleTimeout: () => new FortressError("SESSION_IDLE_TIMEOUT", "Session idle timeout exceeded", 401),
|
|
62
|
+
sessionAbsoluteTimeout: () => new FortressError("SESSION_ABSOLUTE_TIMEOUT", "Session absolute timeout exceeded", 401),
|
|
63
|
+
forbidden: (message = "Forbidden") => new FortressError("FORBIDDEN", message, 403),
|
|
64
|
+
badRequest: (message = "Bad request") => new FortressError("BAD_REQUEST", message, 400),
|
|
65
|
+
notFound: (message = "Not found") => new FortressError("NOT_FOUND", message, 404),
|
|
66
|
+
conflict: (message = "Conflict", options) => new FortressError("CONFLICT", message, 409, options),
|
|
67
|
+
unprocessable: (message = "Unprocessable entity", options) => new FortressError("UNPROCESSABLE_ENTITY", message, 422, options),
|
|
68
|
+
rateLimited: (retryAfter) => new FortressError("RATE_LIMITED", "Too many requests", 429, { retryAfter }),
|
|
69
|
+
database: (message = "Database error", cause) => new FortressError("DATABASE_ERROR", message, 500, { cause }),
|
|
70
|
+
serviceUnavailable: (message = "Service unavailable", options) => new FortressError("SERVICE_UNAVAILABLE", message, 503, options),
|
|
71
|
+
validationError: (issues) => new FortressError("VALIDATION_ERROR", "Validation failed", 422, { details: issues }),
|
|
72
|
+
/**
|
|
73
|
+
* RFC 6749 §5.2 / §4.1.2.1 OAuth error.
|
|
74
|
+
*
|
|
75
|
+
* The HTTP error mapper detects `oauthError` and emits the OAuth-spec
|
|
76
|
+
* JSON body `{ error, error_description, error_uri? }` instead of the
|
|
77
|
+
* default fortress `{ code, message }` shape, so strict OAuth clients
|
|
78
|
+
* (Moodle, openid-client, Spring Security, etc.) can switch behaviour
|
|
79
|
+
* on the machine-readable `error` field.
|
|
80
|
+
*
|
|
81
|
+
* Status is inferred from the OAuth code per the spec table:
|
|
82
|
+
* - `invalid_client` → 401
|
|
83
|
+
* - everything else → 400
|
|
84
|
+
*/
|
|
85
|
+
oauth: (error, description, options) => {
|
|
86
|
+
const status = options?.status ?? (error === "invalid_client" ? 401 : 400);
|
|
87
|
+
const code = status === 401 ? "UNAUTHORIZED" : "BAD_REQUEST";
|
|
88
|
+
return new FortressError(code, description ?? error, status, {
|
|
89
|
+
oauthError: error,
|
|
90
|
+
oauthDescription: description,
|
|
91
|
+
oauthErrorUri: options?.errorUri,
|
|
92
|
+
cause: options?.cause
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* Reconstruct a {@link FortressError} from a JSON error body emitted by
|
|
97
|
+
* `errorToResponse`. Used by the in-process `fortress.call.*` client to
|
|
98
|
+
* convert non-2xx responses into typed throws that mirror what a direct
|
|
99
|
+
* service-method call would produce.
|
|
100
|
+
*
|
|
101
|
+
* Maps by HTTP status when the body doesn't carry a recognizable
|
|
102
|
+
* `{ code, message }` payload, so network errors and opaque upstream
|
|
103
|
+
* failures still become structured `FortressError`s.
|
|
104
|
+
*/
|
|
105
|
+
fromHttpResponse: (status, body) => {
|
|
106
|
+
const payload = body && typeof body === "object" ? body : {};
|
|
107
|
+
const message = typeof payload.message === "string" ? payload.message : `HTTP ${status}`;
|
|
108
|
+
const code = typeof payload.code === "string" && isFortressErrorCode(payload.code) ? payload.code : statusToErrorCode(status);
|
|
109
|
+
return new FortressError(code, message, status, { details: payload.details });
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
function isFortressErrorCode(code) {
|
|
113
|
+
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";
|
|
114
|
+
}
|
|
115
|
+
function statusToErrorCode(status) {
|
|
116
|
+
if (status === 401)
|
|
117
|
+
return "UNAUTHORIZED";
|
|
118
|
+
if (status === 403)
|
|
119
|
+
return "FORBIDDEN";
|
|
120
|
+
if (status === 404)
|
|
121
|
+
return "NOT_FOUND";
|
|
122
|
+
if (status === 409)
|
|
123
|
+
return "CONFLICT";
|
|
124
|
+
if (status === 422)
|
|
125
|
+
return "UNPROCESSABLE_ENTITY";
|
|
126
|
+
if (status === 429)
|
|
127
|
+
return "RATE_LIMITED";
|
|
128
|
+
if (status === 503)
|
|
129
|
+
return "SERVICE_UNAVAILABLE";
|
|
130
|
+
if (status >= 500)
|
|
131
|
+
return "DATABASE_ERROR";
|
|
132
|
+
return "BAD_REQUEST";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/plugins/account-lockout/index.ts
|
|
136
|
+
function accountLockout(config = {}) {
|
|
137
|
+
const maxFailedAttempts = config.maxFailedAttempts ?? 5;
|
|
138
|
+
const lockoutDurationSeconds = config.lockoutDurationSeconds ?? 900;
|
|
139
|
+
const escalation = config.escalation ?? true;
|
|
140
|
+
const maxLockoutSeconds = config.maxLockoutSeconds ?? 3600;
|
|
141
|
+
function normalizeIdentifier(identifier) {
|
|
142
|
+
return identifier.trim().normalize("NFC").toLowerCase();
|
|
143
|
+
}
|
|
144
|
+
function calculateLockoutDuration(lockoutCount) {
|
|
145
|
+
if (!escalation) {
|
|
146
|
+
return lockoutDurationSeconds;
|
|
147
|
+
}
|
|
148
|
+
const duration = lockoutDurationSeconds * 2 ** lockoutCount;
|
|
149
|
+
return Math.min(duration, maxLockoutSeconds);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
name: "account-lockout",
|
|
153
|
+
models: [{
|
|
154
|
+
name: "account_lockout",
|
|
155
|
+
fields: {
|
|
156
|
+
id: { type: "number", required: true },
|
|
157
|
+
identifier: { type: "string", required: true, unique: true },
|
|
158
|
+
failedAttempts: { type: "number", required: true },
|
|
159
|
+
lastFailedAt: { type: "date" },
|
|
160
|
+
lockedUntil: { type: "date" },
|
|
161
|
+
lockoutCount: { type: "number", required: true },
|
|
162
|
+
createdAt: { type: "date", required: true }
|
|
163
|
+
}
|
|
164
|
+
}],
|
|
165
|
+
hooks: {
|
|
166
|
+
async beforeLogin(ctx) {
|
|
167
|
+
const identifier = normalizeIdentifier(ctx.email);
|
|
168
|
+
const record = await ctx.db.findOne({
|
|
169
|
+
model: "account_lockout",
|
|
170
|
+
where: [{ field: "identifier", operator: "=", value: identifier }]
|
|
171
|
+
});
|
|
172
|
+
if (!record) {
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
const now = /* @__PURE__ */ new Date();
|
|
176
|
+
if (record.lockedUntil && record.lockedUntil > now) {
|
|
177
|
+
throw Errors.unauthorized("Account temporarily locked. Try again later.");
|
|
178
|
+
}
|
|
179
|
+
if (record.lockedUntil && record.lockedUntil <= now) {
|
|
180
|
+
await ctx.db.update({
|
|
181
|
+
model: "account_lockout",
|
|
182
|
+
where: [{ field: "id", operator: "=", value: record.id }],
|
|
183
|
+
data: { failedAttempts: 0, lockedUntil: null, lastFailedAt: null }
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return void 0;
|
|
187
|
+
},
|
|
188
|
+
async onLoginFailure(ctx) {
|
|
189
|
+
if (ctx.error.message.includes("temporarily locked"))
|
|
190
|
+
return;
|
|
191
|
+
const identifier = normalizeIdentifier(ctx.identifier);
|
|
192
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
193
|
+
const now = /* @__PURE__ */ new Date();
|
|
194
|
+
let record = await ctx.db.findOne({
|
|
195
|
+
model: "account_lockout",
|
|
196
|
+
where: [{ field: "identifier", operator: "=", value: identifier }]
|
|
197
|
+
});
|
|
198
|
+
if (!record) {
|
|
199
|
+
const newFailedAttempts2 = 1;
|
|
200
|
+
const updateData2 = {
|
|
201
|
+
identifier,
|
|
202
|
+
failedAttempts: newFailedAttempts2,
|
|
203
|
+
lastFailedAt: now,
|
|
204
|
+
lockedUntil: null,
|
|
205
|
+
lockoutCount: 0
|
|
206
|
+
};
|
|
207
|
+
if (newFailedAttempts2 >= maxFailedAttempts) {
|
|
208
|
+
updateData2.lockedUntil = new Date(now.getTime() + calculateLockoutDuration(0) * 1e3);
|
|
209
|
+
updateData2.lockoutCount = 1;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
await ctx.db.create({ model: "account_lockout", data: updateData2 });
|
|
213
|
+
return;
|
|
214
|
+
} catch {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const expired = record.lockedUntil !== null && record.lockedUntil <= now;
|
|
219
|
+
const observedFailedAttempts = record.failedAttempts;
|
|
220
|
+
const observedLockoutCount = record.lockoutCount;
|
|
221
|
+
if (expired) {
|
|
222
|
+
record = { ...record, failedAttempts: 0, lockedUntil: null, lastFailedAt: null };
|
|
223
|
+
}
|
|
224
|
+
const newFailedAttempts = record.failedAttempts + 1;
|
|
225
|
+
const updateData = {
|
|
226
|
+
failedAttempts: newFailedAttempts,
|
|
227
|
+
lastFailedAt: now,
|
|
228
|
+
lockedUntil: expired ? null : record.lockedUntil
|
|
229
|
+
};
|
|
230
|
+
if (newFailedAttempts >= maxFailedAttempts) {
|
|
231
|
+
const duration = calculateLockoutDuration(record.lockoutCount);
|
|
232
|
+
updateData.lockedUntil = new Date(now.getTime() + duration * 1e3);
|
|
233
|
+
updateData.lockoutCount = record.lockoutCount + 1;
|
|
234
|
+
} else if (expired) {
|
|
235
|
+
updateData.lockoutCount = record.lockoutCount;
|
|
236
|
+
}
|
|
237
|
+
const updated = await ctx.db.update({
|
|
238
|
+
model: "account_lockout",
|
|
239
|
+
where: [
|
|
240
|
+
{ field: "id", operator: "=", value: record.id },
|
|
241
|
+
{ field: "failedAttempts", operator: "=", value: observedFailedAttempts },
|
|
242
|
+
{ field: "lockoutCount", operator: "=", value: observedLockoutCount }
|
|
243
|
+
],
|
|
244
|
+
data: updateData
|
|
245
|
+
});
|
|
246
|
+
if (updated)
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
async afterLogin(ctx, result) {
|
|
251
|
+
const identifier = normalizeIdentifier(ctx.identifier ?? result.user.email);
|
|
252
|
+
const record = await ctx.db.findOne({
|
|
253
|
+
model: "account_lockout",
|
|
254
|
+
where: [{ field: "identifier", operator: "=", value: identifier }]
|
|
255
|
+
});
|
|
256
|
+
if (record && record.failedAttempts > 0) {
|
|
257
|
+
await ctx.db.update({
|
|
258
|
+
model: "account_lockout",
|
|
259
|
+
where: [{ field: "id", operator: "=", value: record.id }],
|
|
260
|
+
data: {
|
|
261
|
+
failedAttempts: 0,
|
|
262
|
+
lockedUntil: null
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
methods: (ctx) => ({
|
|
270
|
+
async getLockoutStatus(identifier) {
|
|
271
|
+
const normalized = normalizeIdentifier(identifier);
|
|
272
|
+
const record = await ctx.db.findOne({
|
|
273
|
+
model: "account_lockout",
|
|
274
|
+
where: [{ field: "identifier", operator: "=", value: normalized }]
|
|
275
|
+
});
|
|
276
|
+
if (!record) {
|
|
277
|
+
return {
|
|
278
|
+
identifier: normalized,
|
|
279
|
+
failedAttempts: 0,
|
|
280
|
+
lockoutCount: 0,
|
|
281
|
+
lockedUntil: null,
|
|
282
|
+
lastFailedAt: null,
|
|
283
|
+
isLocked: false
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const isLocked = record.lockedUntil !== null && record.lockedUntil > /* @__PURE__ */ new Date();
|
|
287
|
+
return {
|
|
288
|
+
identifier: record.identifier,
|
|
289
|
+
failedAttempts: record.failedAttempts,
|
|
290
|
+
lockoutCount: record.lockoutCount,
|
|
291
|
+
lockedUntil: record.lockedUntil,
|
|
292
|
+
lastFailedAt: record.lastFailedAt,
|
|
293
|
+
isLocked
|
|
294
|
+
};
|
|
295
|
+
},
|
|
296
|
+
async resetLockout(identifier) {
|
|
297
|
+
const normalized = normalizeIdentifier(identifier);
|
|
298
|
+
const record = await ctx.db.findOne({
|
|
299
|
+
model: "account_lockout",
|
|
300
|
+
where: [{ field: "identifier", operator: "=", value: normalized }]
|
|
301
|
+
});
|
|
302
|
+
if (!record) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
await ctx.db.update({
|
|
306
|
+
model: "account_lockout",
|
|
307
|
+
where: [{ field: "id", operator: "=", value: record.id }],
|
|
308
|
+
data: {
|
|
309
|
+
failedAttempts: 0,
|
|
310
|
+
lockedUntil: null,
|
|
311
|
+
lockoutCount: 0,
|
|
312
|
+
lastFailedAt: null
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
320
|
+
0 && (module.exports = {
|
|
321
|
+
accountLockout
|
|
322
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { 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
|
+
* Account lockout plugin for fortress.
|
|
9
|
+
*
|
|
10
|
+
* Tracks failed sign-in attempts per identifier and applies a progressive
|
|
11
|
+
* lockout (each successive lockout extends the cooldown). Hooks into the
|
|
12
|
+
* sign-in flow to reject attempts during a lockout window.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface AccountLockoutConfig {
|
|
18
|
+
/** Max failed attempts before lockout. Default: 5. */
|
|
19
|
+
maxFailedAttempts?: number;
|
|
20
|
+
/** Initial lockout duration in seconds. Default: 900 (15 minutes). */
|
|
21
|
+
lockoutDurationSeconds?: number;
|
|
22
|
+
/** Double lockout duration on repeated lockouts. Default: true. */
|
|
23
|
+
escalation?: boolean;
|
|
24
|
+
/** Maximum lockout duration in seconds. Default: 3600 (1 hour). */
|
|
25
|
+
maxLockoutSeconds?: number;
|
|
26
|
+
}
|
|
27
|
+
interface LockoutStatus {
|
|
28
|
+
identifier: string;
|
|
29
|
+
failedAttempts: number;
|
|
30
|
+
lockoutCount: number;
|
|
31
|
+
lockedUntil: Date | null;
|
|
32
|
+
lastFailedAt: Date | null;
|
|
33
|
+
isLocked: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Account lockout plugin factory. Returns a {@link FortressPlugin} that
|
|
37
|
+
* tracks failed sign-in attempts per identifier and applies a progressive
|
|
38
|
+
* lockout (each successive lockout extends the cooldown window).
|
|
39
|
+
*/
|
|
40
|
+
declare function accountLockout(config?: AccountLockoutConfig): FortressPlugin;
|
|
41
|
+
|
|
42
|
+
export { type AccountLockoutConfig, type LockoutStatus, accountLockout };
|