@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,56 @@
|
|
|
1
|
+
const CHANGELOG_HEADER = '# Changelog';
|
|
2
|
+
const UNRELEASED_HEADING = '## [Unreleased]';
|
|
3
|
+
const SECTION_HEADING = /^### /;
|
|
4
|
+
|
|
5
|
+
function parseGeneratedSections(value: string): Array<{ heading: string; body: string[] }> {
|
|
6
|
+
const lines = value.trim().split('\n');
|
|
7
|
+
const sections: Array<{ heading: string; body: string[] }> = [];
|
|
8
|
+
for (const line of lines) {
|
|
9
|
+
if (SECTION_HEADING.test(line))
|
|
10
|
+
sections.push({ heading: line, body: [] });
|
|
11
|
+
else if (sections.length > 0 && line.length > 0)
|
|
12
|
+
sections.at(-1)!.body.push(line);
|
|
13
|
+
}
|
|
14
|
+
return sections;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Promote the first, top-level Unreleased section without detaching its notes. */
|
|
18
|
+
export function promoteUnreleased(
|
|
19
|
+
changelog: string,
|
|
20
|
+
version: string,
|
|
21
|
+
date: string,
|
|
22
|
+
generatedSection = '',
|
|
23
|
+
): string {
|
|
24
|
+
const lines = changelog.split('\n');
|
|
25
|
+
if (lines[0] !== CHANGELOG_HEADER)
|
|
26
|
+
throw new Error('CHANGELOG.md must start with # Changelog');
|
|
27
|
+
|
|
28
|
+
const releaseStart = lines.findIndex((line, index) => index > 0 && line.startsWith('## '));
|
|
29
|
+
if (releaseStart < 0 || lines[releaseStart] !== UNRELEASED_HEADING)
|
|
30
|
+
throw new Error('CHANGELOG.md must have [Unreleased] as its first release section');
|
|
31
|
+
|
|
32
|
+
lines[releaseStart] = `## [${version}] - ${date}`;
|
|
33
|
+
let releaseEnd = lines.findIndex((line, index) => index > releaseStart && line.startsWith('## '));
|
|
34
|
+
if (releaseEnd < 0)
|
|
35
|
+
releaseEnd = lines.length;
|
|
36
|
+
|
|
37
|
+
for (const generated of parseGeneratedSections(generatedSection)) {
|
|
38
|
+
const existingHeading = lines.findIndex(
|
|
39
|
+
(line, index) => index > releaseStart && index < releaseEnd && line === generated.heading,
|
|
40
|
+
);
|
|
41
|
+
if (existingHeading >= 0) {
|
|
42
|
+
let insertion = lines.findIndex(
|
|
43
|
+
(line, index) => index > existingHeading && index < releaseEnd && SECTION_HEADING.test(line),
|
|
44
|
+
);
|
|
45
|
+
if (insertion < 0)
|
|
46
|
+
insertion = releaseEnd;
|
|
47
|
+
lines.splice(insertion, 0, ...generated.body, '');
|
|
48
|
+
releaseEnd += generated.body.length + 1;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
lines.splice(releaseStart + 1, 0, '', generated.heading, ...generated.body);
|
|
52
|
+
releaseEnd += generated.body.length + 2;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry adapter for Fortress.
|
|
3
|
+
*
|
|
4
|
+
* This is the **only file in the repository that imports `@opentelemetry/api`**.
|
|
5
|
+
* Core Fortress code depends on the internal {@link TelemetryProvider}
|
|
6
|
+
* interface in `src/core/observability/types.ts`; this sub-path exists so
|
|
7
|
+
* runtimes that never opt in (Cloudflare Workers, Deno without OTel, etc.)
|
|
8
|
+
* never resolve `@opentelemetry/api` at all.
|
|
9
|
+
*
|
|
10
|
+
* The import is dynamic (`await import(...)`) as a belt-and-suspenders
|
|
11
|
+
* guarantee: even if a bundler or package resolver attempts to evaluate
|
|
12
|
+
* this module, `@opentelemetry/api` is not referenced until
|
|
13
|
+
* {@link createOtelTelemetry} is actually called.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { createFortress } from '@bajustone/fortress';
|
|
18
|
+
* import { createOtelTelemetry } from '@bajustone/fortress/otel';
|
|
19
|
+
* import pino from 'pino';
|
|
20
|
+
*
|
|
21
|
+
* const observability = await createOtelTelemetry({ name: 'my-app-auth' });
|
|
22
|
+
* const fortress = createFortress({
|
|
23
|
+
* // ...config
|
|
24
|
+
* logger: pino(),
|
|
25
|
+
* observability,
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* The returned provider satisfies Fortress's internal
|
|
30
|
+
* {@link TelemetryProvider} interface and can be wired into any
|
|
31
|
+
* `FortressConfig.observability` slot. Because Fortress's metric catalog
|
|
32
|
+
* uses the stable OTel semantic convention for database operations
|
|
33
|
+
* (`db.client.operation.duration`), Grafana/Prometheus dashboards that
|
|
34
|
+
* know about that name will automatically pick up Fortress's DB queries.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import type { Span, TelemetryProvider } from '../core/observability/types';
|
|
38
|
+
|
|
39
|
+
export type { AuthEvent, AuthEventListener } from '../core/auth/auth-service';
|
|
40
|
+
export type {
|
|
41
|
+
IamEvent,
|
|
42
|
+
IamEventListener,
|
|
43
|
+
PermissionCheckEvent,
|
|
44
|
+
PermissionCheckListener,
|
|
45
|
+
} from '../core/iam/iam-service';
|
|
46
|
+
export type { Unsubscribe } from '../core/observability/listener-list';
|
|
47
|
+
export type { FortressLogger } from '../core/observability/logger';
|
|
48
|
+
export type {
|
|
49
|
+
Attributes,
|
|
50
|
+
AttributeValue,
|
|
51
|
+
Counter,
|
|
52
|
+
Histogram,
|
|
53
|
+
Meter,
|
|
54
|
+
Span,
|
|
55
|
+
TelemetryProvider,
|
|
56
|
+
Tracer,
|
|
57
|
+
} from '../core/observability/types';
|
|
58
|
+
|
|
59
|
+
export interface OtelTelemetryOptions {
|
|
60
|
+
/** Instrumentation scope name. Defaults to `'fortress'`. */
|
|
61
|
+
name?: string;
|
|
62
|
+
/** Instrumentation scope version. Defaults to `'0.0.x'`. */
|
|
63
|
+
version?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create a Fortress {@link TelemetryProvider} backed by OpenTelemetry's
|
|
68
|
+
* global tracer and meter providers.
|
|
69
|
+
*
|
|
70
|
+
* If `@opentelemetry/api` is not installed, this function throws with a
|
|
71
|
+
* clear error message pointing at the missing peer dependency. The
|
|
72
|
+
* dynamic import ensures the failure mode is a controlled throw at call
|
|
73
|
+
* time — not a module-load crash.
|
|
74
|
+
*
|
|
75
|
+
* When no OTel SDK is registered globally, the returned provider's spans
|
|
76
|
+
* and metric instruments will resolve to OTel's own no-op implementations
|
|
77
|
+
* and impose no measurable overhead.
|
|
78
|
+
*/
|
|
79
|
+
export async function createOtelTelemetry(
|
|
80
|
+
options: OtelTelemetryOptions = {},
|
|
81
|
+
): Promise<TelemetryProvider> {
|
|
82
|
+
let api: typeof import('@opentelemetry/api');
|
|
83
|
+
try {
|
|
84
|
+
api = await import('@opentelemetry/api');
|
|
85
|
+
}
|
|
86
|
+
catch (cause) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
'@opentelemetry/api is not installed. Add it to your dependencies to use createOtelTelemetry().',
|
|
89
|
+
{ cause },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const name = options.name ?? 'fortress';
|
|
94
|
+
const version = options.version ?? '0.0.x';
|
|
95
|
+
const otelTracer = api.trace.getTracer(name, version);
|
|
96
|
+
const otelMeter = api.metrics.getMeter(name, version);
|
|
97
|
+
const { SpanStatusCode } = api;
|
|
98
|
+
const wrapSpan = (span: import('@opentelemetry/api').Span): Span => ({
|
|
99
|
+
setAttribute: (k, v): void => {
|
|
100
|
+
span.setAttribute(k, v);
|
|
101
|
+
},
|
|
102
|
+
recordException: (err): void => {
|
|
103
|
+
span.recordException(err as Error);
|
|
104
|
+
},
|
|
105
|
+
setStatus: (s): void => {
|
|
106
|
+
span.setStatus({
|
|
107
|
+
code: s.code === 'ok' ? SpanStatusCode.OK : SpanStatusCode.ERROR,
|
|
108
|
+
message: s.message,
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
end: (): void => {
|
|
112
|
+
span.end();
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
tracer: {
|
|
118
|
+
startSpan(spanName, attrs): Span {
|
|
119
|
+
return wrapSpan(otelTracer.startSpan(spanName, { attributes: attrs }));
|
|
120
|
+
},
|
|
121
|
+
startActiveSpan<T>(
|
|
122
|
+
spanName: string,
|
|
123
|
+
attrs: import('../core/observability/types').Attributes | undefined,
|
|
124
|
+
callback: (span: Span) => T | Promise<T>,
|
|
125
|
+
): Promise<T> {
|
|
126
|
+
return otelTracer.startActiveSpan(
|
|
127
|
+
spanName,
|
|
128
|
+
{ attributes: attrs },
|
|
129
|
+
async span => callback(wrapSpan(span)),
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
meter: {
|
|
134
|
+
createCounter(metricName, opts): import('../core/observability/types').Counter {
|
|
135
|
+
const c = otelMeter.createCounter(metricName, opts);
|
|
136
|
+
return {
|
|
137
|
+
add: (v, a): void => {
|
|
138
|
+
c.add(v, a);
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
createHistogram(metricName, opts): import('../core/observability/types').Histogram {
|
|
143
|
+
const h = otelMeter.createHistogram(metricName, {
|
|
144
|
+
description: opts?.description,
|
|
145
|
+
unit: opts?.unit,
|
|
146
|
+
advice: opts?.boundaries
|
|
147
|
+
? { explicitBucketBoundaries: opts.boundaries }
|
|
148
|
+
: undefined,
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
record: (v, a): void => {
|
|
152
|
+
h.record(v, a);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthEvent,
|
|
3
|
+
AuthEventListener,
|
|
4
|
+
FortressLogger,
|
|
5
|
+
IamEvent,
|
|
6
|
+
IamEventListener,
|
|
7
|
+
PermissionCheckEvent,
|
|
8
|
+
PermissionCheckListener,
|
|
9
|
+
TelemetryProvider,
|
|
10
|
+
Unsubscribe,
|
|
11
|
+
} from '../index';
|
|
12
|
+
import type {
|
|
13
|
+
AuthEvent as OtelAuthEvent,
|
|
14
|
+
AuthEventListener as OtelAuthEventListener,
|
|
15
|
+
FortressLogger as OtelFortressLogger,
|
|
16
|
+
IamEvent as OtelIamEvent,
|
|
17
|
+
IamEventListener as OtelIamEventListener,
|
|
18
|
+
PermissionCheckEvent as OtelPermissionCheckEvent,
|
|
19
|
+
PermissionCheckListener as OtelPermissionCheckListener,
|
|
20
|
+
TelemetryProvider as OtelTelemetryProvider,
|
|
21
|
+
Unsubscribe as OtelUnsubscribe,
|
|
22
|
+
} from './index';
|
|
23
|
+
import { expectTypeOf, it } from 'vitest';
|
|
24
|
+
|
|
25
|
+
it('exports public observability contracts from root and /otel', () => {
|
|
26
|
+
expectTypeOf<OtelFortressLogger>().toEqualTypeOf<FortressLogger>();
|
|
27
|
+
expectTypeOf<OtelTelemetryProvider>().toEqualTypeOf<TelemetryProvider>();
|
|
28
|
+
expectTypeOf<OtelAuthEvent>().toEqualTypeOf<AuthEvent>();
|
|
29
|
+
expectTypeOf<OtelAuthEventListener>().toEqualTypeOf<AuthEventListener>();
|
|
30
|
+
expectTypeOf<OtelIamEvent>().toEqualTypeOf<IamEvent>();
|
|
31
|
+
expectTypeOf<OtelIamEventListener>().toEqualTypeOf<IamEventListener>();
|
|
32
|
+
expectTypeOf<OtelPermissionCheckEvent>().toEqualTypeOf<PermissionCheckEvent>();
|
|
33
|
+
expectTypeOf<OtelPermissionCheckListener>().toEqualTypeOf<PermissionCheckListener>();
|
|
34
|
+
expectTypeOf<OtelUnsubscribe>().toEqualTypeOf<Unsubscribe>();
|
|
35
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { MetricData, ResourceMetrics } from '@opentelemetry/sdk-metrics';
|
|
2
|
+
import type { Fortress } from '../core/fortress';
|
|
3
|
+
import { context, metrics, trace } from '@opentelemetry/api';
|
|
4
|
+
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
|
|
5
|
+
import {
|
|
6
|
+
AggregationTemporality,
|
|
7
|
+
InMemoryMetricExporter,
|
|
8
|
+
MeterProvider,
|
|
9
|
+
PeriodicExportingMetricReader,
|
|
10
|
+
} from '@opentelemetry/sdk-metrics';
|
|
11
|
+
import {
|
|
12
|
+
BasicTracerProvider,
|
|
13
|
+
InMemorySpanExporter,
|
|
14
|
+
SimpleSpanProcessor,
|
|
15
|
+
} from '@opentelemetry/sdk-trace-base';
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
17
|
+
import { createFortress } from '../core/fortress';
|
|
18
|
+
import { assertSuccess } from '../core/types';
|
|
19
|
+
import { createTestAdapter } from '../testing';
|
|
20
|
+
import { createOtelTelemetry } from './index';
|
|
21
|
+
|
|
22
|
+
const SECRET = 'otel-integration-test-secret-32!!';
|
|
23
|
+
|
|
24
|
+
let fortress: Fortress;
|
|
25
|
+
let exporter: InMemoryMetricExporter;
|
|
26
|
+
let reader: PeriodicExportingMetricReader;
|
|
27
|
+
let provider: MeterProvider;
|
|
28
|
+
let traceProvider: BasicTracerProvider;
|
|
29
|
+
let spanExporter: InMemorySpanExporter;
|
|
30
|
+
let contextManager: AsyncLocalStorageContextManager;
|
|
31
|
+
|
|
32
|
+
async function collectMetrics(): Promise<MetricData[]> {
|
|
33
|
+
await reader.forceFlush();
|
|
34
|
+
const snapshots: ResourceMetrics[] = exporter.getMetrics();
|
|
35
|
+
const all: MetricData[] = [];
|
|
36
|
+
for (const snapshot of snapshots) {
|
|
37
|
+
for (const scope of snapshot.scopeMetrics) {
|
|
38
|
+
all.push(...scope.metrics);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return all;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findMetric(list: MetricData[], name: string): MetricData | undefined {
|
|
45
|
+
return list.find(m => m.descriptor.name === name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('createOtelTelemetry', () => {
|
|
49
|
+
beforeEach(async () => {
|
|
50
|
+
exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
|
|
51
|
+
reader = new PeriodicExportingMetricReader({
|
|
52
|
+
exporter,
|
|
53
|
+
exportIntervalMillis: 60_000,
|
|
54
|
+
});
|
|
55
|
+
provider = new MeterProvider({ readers: [reader] });
|
|
56
|
+
metrics.setGlobalMeterProvider(provider);
|
|
57
|
+
spanExporter = new InMemorySpanExporter();
|
|
58
|
+
traceProvider = new BasicTracerProvider({
|
|
59
|
+
spanProcessors: [new SimpleSpanProcessor(spanExporter)],
|
|
60
|
+
});
|
|
61
|
+
trace.setGlobalTracerProvider(traceProvider);
|
|
62
|
+
contextManager = new AsyncLocalStorageContextManager().enable();
|
|
63
|
+
context.setGlobalContextManager(contextManager);
|
|
64
|
+
|
|
65
|
+
const telemetry = await createOtelTelemetry({ name: 'fortress-test' });
|
|
66
|
+
fortress = createFortress({
|
|
67
|
+
jwt: { key: SECRET },
|
|
68
|
+
database: createTestAdapter(),
|
|
69
|
+
rbac: { cache: { ttlSeconds: 30, maxEntries: 100 } },
|
|
70
|
+
observability: telemetry,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await fortress.auth.createUser({
|
|
74
|
+
email: 'otel@example.com',
|
|
75
|
+
name: 'OTel User',
|
|
76
|
+
password: 'password-123456',
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterEach(async () => {
|
|
81
|
+
await provider.shutdown();
|
|
82
|
+
await traceProvider.shutdown();
|
|
83
|
+
metrics.disable();
|
|
84
|
+
trace.disable();
|
|
85
|
+
context.disable();
|
|
86
|
+
contextManager.disable();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('emits fortress.auth.events.total on successful login', async () => {
|
|
90
|
+
await fortress.auth.login('otel@example.com', 'password-123456');
|
|
91
|
+
const all = await collectMetrics();
|
|
92
|
+
|
|
93
|
+
const authEvents = findMetric(all, 'fortress.auth.events.total');
|
|
94
|
+
expect(authEvents).toBeDefined();
|
|
95
|
+
// There should be at least one data point with LOGIN_SUCCESS + password.
|
|
96
|
+
const points = authEvents!.dataPoints;
|
|
97
|
+
const success = points.find((p) => {
|
|
98
|
+
const attrs = p.attributes as Record<string, string>;
|
|
99
|
+
return attrs.event === 'LOGIN_SUCCESS' && attrs.outcome === 'success';
|
|
100
|
+
});
|
|
101
|
+
expect(success).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('emits fortress.auth.events.total with outcome=failure on bad credentials', async () => {
|
|
105
|
+
await expect(
|
|
106
|
+
fortress.auth.login('otel@example.com', 'wrong'),
|
|
107
|
+
).rejects.toThrow();
|
|
108
|
+
const all = await collectMetrics();
|
|
109
|
+
|
|
110
|
+
const authEvents = findMetric(all, 'fortress.auth.events.total');
|
|
111
|
+
expect(authEvents).toBeDefined();
|
|
112
|
+
const failure = authEvents!.dataPoints.find((p) => {
|
|
113
|
+
const attrs = p.attributes as Record<string, string>;
|
|
114
|
+
return attrs.event === 'LOGIN_FAILURE' && attrs.outcome === 'failure';
|
|
115
|
+
});
|
|
116
|
+
expect(failure).toBeDefined();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('emits fortress.iam.permission_check.duration histogram + cache hit/miss counters', async () => {
|
|
120
|
+
// Set up permissions.
|
|
121
|
+
const user = await fortress.auth.createUser({
|
|
122
|
+
email: 'perm-otel@example.com',
|
|
123
|
+
name: 'Perm User',
|
|
124
|
+
password: 'password-123456',
|
|
125
|
+
});
|
|
126
|
+
const role = await fortress.iam.createRole('reader', [
|
|
127
|
+
{ resource: 'post', action: 'read' },
|
|
128
|
+
]);
|
|
129
|
+
await fortress.iam.bindRoleToUser(user.id, role.id);
|
|
130
|
+
|
|
131
|
+
// Two checks: miss + hit.
|
|
132
|
+
await fortress.iam.checkPermission({ type: 'USER', id: user.id }, 'post', 'read');
|
|
133
|
+
await fortress.iam.checkPermission({ type: 'USER', id: user.id }, 'post', 'read');
|
|
134
|
+
|
|
135
|
+
const all = await collectMetrics();
|
|
136
|
+
|
|
137
|
+
const duration = findMetric(all, 'fortress.iam.permission_check.duration');
|
|
138
|
+
expect(duration).toBeDefined();
|
|
139
|
+
expect(duration!.dataPoints.length).toBeGreaterThan(0);
|
|
140
|
+
|
|
141
|
+
const hits = findMetric(all, 'fortress.iam.permission_check.cache.hits');
|
|
142
|
+
const misses = findMetric(all, 'fortress.iam.permission_check.cache.misses');
|
|
143
|
+
expect(hits).toBeDefined();
|
|
144
|
+
expect(misses).toBeDefined();
|
|
145
|
+
|
|
146
|
+
// Sum the counters — there must be at least one hit and one miss.
|
|
147
|
+
function sumCounter(m: MetricData | undefined): number {
|
|
148
|
+
if (!m)
|
|
149
|
+
return 0;
|
|
150
|
+
return m.dataPoints.reduce((acc, p) => acc + Number(p.value), 0);
|
|
151
|
+
}
|
|
152
|
+
expect(sumCounter(misses)).toBeGreaterThanOrEqual(1);
|
|
153
|
+
expect(sumCounter(hits)).toBeGreaterThanOrEqual(1);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('emits the standard db.client.operation.duration histogram for Fortress DB calls', async () => {
|
|
157
|
+
// Any DB operation will do — the register in beforeEach is enough.
|
|
158
|
+
await fortress.auth.login('otel@example.com', 'password-123456');
|
|
159
|
+
const all = await collectMetrics();
|
|
160
|
+
|
|
161
|
+
const dbDuration = findMetric(all, 'db.client.operation.duration');
|
|
162
|
+
expect(dbDuration).toBeDefined();
|
|
163
|
+
expect(dbDuration!.dataPoints.length).toBeGreaterThan(0);
|
|
164
|
+
|
|
165
|
+
// At least one data point should carry the standard semantic-convention
|
|
166
|
+
// attributes: db.system.name and db.operation.name.
|
|
167
|
+
const hasDbAttrs = dbDuration!.dataPoints.some((p) => {
|
|
168
|
+
const attrs = p.attributes as Record<string, string>;
|
|
169
|
+
return 'db.system.name' in attrs && 'db.operation.name' in attrs;
|
|
170
|
+
});
|
|
171
|
+
expect(hasDbAttrs).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('parents nested database spans under the active request span', async () => {
|
|
175
|
+
const response = await fortress.handleRequest(new Request('http://localhost/auth/login', {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: { 'content-type': 'application/json' },
|
|
178
|
+
body: JSON.stringify({ identifier: 'otel@example.com', password: 'password-123456' }),
|
|
179
|
+
}));
|
|
180
|
+
expect(response.status).toBe(200);
|
|
181
|
+
await traceProvider.forceFlush();
|
|
182
|
+
|
|
183
|
+
const spans = spanExporter.getFinishedSpans();
|
|
184
|
+
const requestSpan = spans.find(span => span.name === 'fortress.handleRequest');
|
|
185
|
+
const databaseSpans = spans.filter(span => span.attributes['db.operation.name'] !== undefined);
|
|
186
|
+
expect(requestSpan).toBeDefined();
|
|
187
|
+
expect(databaseSpans.length).toBeGreaterThan(0);
|
|
188
|
+
expect(databaseSpans.some(
|
|
189
|
+
span => span.parentSpanContext?.spanId === requestSpan?.spanContext().spanId,
|
|
190
|
+
)).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('emits fortress.auth.token_verify.duration on verifyToken calls', async () => {
|
|
194
|
+
const login = await fortress.auth.login('otel@example.com', 'password-123456');
|
|
195
|
+
assertSuccess(login);
|
|
196
|
+
expect(login.accessToken).toBeTruthy();
|
|
197
|
+
// Successful verify
|
|
198
|
+
await fortress.auth.verifyToken(login.accessToken as string);
|
|
199
|
+
// Failed verify
|
|
200
|
+
await expect(fortress.auth.verifyToken('not.a.jwt')).rejects.toThrow();
|
|
201
|
+
|
|
202
|
+
const all = await collectMetrics();
|
|
203
|
+
const verify = findMetric(all, 'fortress.auth.token_verify.duration');
|
|
204
|
+
expect(verify).toBeDefined();
|
|
205
|
+
|
|
206
|
+
const ok = verify!.dataPoints.find((p) => {
|
|
207
|
+
const attrs = p.attributes as Record<string, string>;
|
|
208
|
+
return attrs.result === 'ok';
|
|
209
|
+
});
|
|
210
|
+
const invalid = verify!.dataPoints.find((p) => {
|
|
211
|
+
const attrs = p.attributes as Record<string, string>;
|
|
212
|
+
return attrs.result === 'invalid';
|
|
213
|
+
});
|
|
214
|
+
expect(ok).toBeDefined();
|
|
215
|
+
expect(invalid).toBeDefined();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('createOtelTelemetry returns a zero-overhead provider when no SDK is registered', async () => {
|
|
219
|
+
// Shut down the SDK so there's no global provider.
|
|
220
|
+
await provider.shutdown();
|
|
221
|
+
metrics.disable();
|
|
222
|
+
|
|
223
|
+
const fresh = await createOtelTelemetry({ name: 'fortress-no-sdk' });
|
|
224
|
+
// Methods should exist and be callable without throwing.
|
|
225
|
+
const counter = fresh.meter.createCounter('foo');
|
|
226
|
+
expect(() => counter.add(1, { attr: 'x' })).not.toThrow();
|
|
227
|
+
const histogram = fresh.meter.createHistogram('bar');
|
|
228
|
+
expect(() => histogram.record(0.5)).not.toThrow();
|
|
229
|
+
const span = fresh.tracer.startSpan('baz');
|
|
230
|
+
expect(() => {
|
|
231
|
+
span.setAttribute('k', 'v');
|
|
232
|
+
span.end();
|
|
233
|
+
}).not.toThrow();
|
|
234
|
+
});
|
|
235
|
+
});
|