@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,210 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../adapters/database';
|
|
2
|
+
import type { ScopeRule } from '../adapters/database/types';
|
|
3
|
+
import type { AuthService } from './auth/auth-service';
|
|
4
|
+
import type { FortressConfig } from './config';
|
|
5
|
+
import type { EndpointDefinition } from './endpoint';
|
|
6
|
+
import type { PluginRequestContext } from './http/plugin-middleware';
|
|
7
|
+
import type { IamService } from './iam/iam-service';
|
|
8
|
+
import type { FortressLogger } from './observability/logger';
|
|
9
|
+
import type {
|
|
10
|
+
AuthSuccess,
|
|
11
|
+
AuthTokenPair,
|
|
12
|
+
CreateUserInput,
|
|
13
|
+
FortressUser,
|
|
14
|
+
PendingReason,
|
|
15
|
+
RequestMeta,
|
|
16
|
+
Subject,
|
|
17
|
+
TokenClaims,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
export interface FortressPlugin {
|
|
21
|
+
/** Unique plugin identifier */
|
|
22
|
+
name: string;
|
|
23
|
+
|
|
24
|
+
/** DB models this plugin needs */
|
|
25
|
+
models?: ModelDefinition[];
|
|
26
|
+
|
|
27
|
+
/** Hooks into auth lifecycle (executed in plugin registration order) */
|
|
28
|
+
hooks?: PluginHooks;
|
|
29
|
+
|
|
30
|
+
/** Extra methods exposed on fortress.plugins.<name> */
|
|
31
|
+
// eslint-disable-next-line ts/no-unsafe-function-type -- plugin methods are dynamically typed
|
|
32
|
+
methods?: (ctx: PluginContext) => Record<string, Function>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* HTTP routes this plugin adds, keyed by handler name.
|
|
36
|
+
*
|
|
37
|
+
* A keyed record (not an array) so each entry's full
|
|
38
|
+
* `EndpointDefinition<TBody, TQuery, TParams, TResponses>` type is
|
|
39
|
+
* preserved for the typed `fortress.call.*` proxy. The dispatcher looks
|
|
40
|
+
* plugin route handlers up by name (`fortress.plugins[pluginName][handlerName]`),
|
|
41
|
+
* so the keyed shape is already the natural fit at dispatch time — the
|
|
42
|
+
* key just needs to match the `EndpointDefinition.handler` string.
|
|
43
|
+
*/
|
|
44
|
+
routes?: Record<string, EndpointDefinition>;
|
|
45
|
+
|
|
46
|
+
/** Middleware to inject into the request pipeline */
|
|
47
|
+
middleware?: MiddlewareDefinition[];
|
|
48
|
+
|
|
49
|
+
/** Wrap the DatabaseAdapter per-request */
|
|
50
|
+
wrapAdapter?: (
|
|
51
|
+
adapter: DatabaseAdapter,
|
|
52
|
+
requestContext: Record<string, unknown>,
|
|
53
|
+
) => DatabaseAdapter;
|
|
54
|
+
|
|
55
|
+
/** Extend JWT token claims */
|
|
56
|
+
enrichTokenClaims?: (
|
|
57
|
+
userId: string,
|
|
58
|
+
ctx: PluginContext,
|
|
59
|
+
) => Promise<Record<string, unknown>>;
|
|
60
|
+
|
|
61
|
+
/** Scope data access by user context (row-level data isolation) */
|
|
62
|
+
scopeRules?: (
|
|
63
|
+
userId: string,
|
|
64
|
+
model: string,
|
|
65
|
+
ctx: PluginContext,
|
|
66
|
+
) => Promise<ScopeRule | null>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolve a request principal from a non-JWT credential — API key,
|
|
70
|
+
* OAuth bearer, mTLS client cert, signed JWT assertion, etc.
|
|
71
|
+
*
|
|
72
|
+
* Called by `fortress.handleRequest` **before** the JWT fallback. Plugins
|
|
73
|
+
* are tried in registration order; the first to return a non-null result
|
|
74
|
+
* wins, and its subject (plus optional claims) become the request
|
|
75
|
+
* principal for downstream RBAC. Returning `null` means "defer" — the
|
|
76
|
+
* next plugin is tried, and if none resolve, the core JWT path runs.
|
|
77
|
+
*
|
|
78
|
+
* This is the extension point for any future credential mechanism. The
|
|
79
|
+
* api-key plugin implements it to turn `Authorization: ApiKey <key>` /
|
|
80
|
+
* `X-API-Key: <key>` headers into a `USER` or `SERVICE_ACCOUNT`
|
|
81
|
+
* principal.
|
|
82
|
+
*/
|
|
83
|
+
resolvePrincipal?: (
|
|
84
|
+
request: Request,
|
|
85
|
+
ctx: PluginContext,
|
|
86
|
+
) => Promise<{ subject: Subject; claims?: TokenClaims; scopes?: string[] | null } | null>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Hooks ---
|
|
90
|
+
|
|
91
|
+
export interface PluginHooks {
|
|
92
|
+
beforeLogin?: (ctx: HookContext & { email: string }) => Promise<HookResult | void>;
|
|
93
|
+
beforeRegister?: (ctx: HookContext & { data: CreateUserInput }) => Promise<HookResult | void>;
|
|
94
|
+
beforeTokenRefresh?: (ctx: HookContext & { token: string }) => Promise<HookResult | void>;
|
|
95
|
+
beforeLogout?: (ctx: HookContext & { token: string }) => Promise<void>;
|
|
96
|
+
onLoginFailure?: (ctx: HookContext & { identifier: string; error: Error }) => Promise<void>;
|
|
97
|
+
|
|
98
|
+
/** Runs after primary credentials succeed but before any token is issued. */
|
|
99
|
+
postAuthGate?: PostAuthGateProvider;
|
|
100
|
+
|
|
101
|
+
afterLogin?: (ctx: AfterHookContext, result: AuthSuccess) => Promise<AuthSuccess>;
|
|
102
|
+
afterRegister?: (ctx: AfterHookContext, user: FortressUser) => Promise<void>;
|
|
103
|
+
afterTokenRefresh?: (ctx: AfterHookContext, result: AuthTokenPair) => Promise<AuthTokenPair>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface HookContext {
|
|
107
|
+
db: DatabaseAdapter;
|
|
108
|
+
config: FortressConfig;
|
|
109
|
+
meta?: RequestMeta;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface AfterHookContext extends HookContext {
|
|
113
|
+
responseHeaders: Headers;
|
|
114
|
+
/** Normalized login identifier used for this auth flow, when applicable. */
|
|
115
|
+
identifier?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface HookResult {
|
|
119
|
+
stop: true;
|
|
120
|
+
response: Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface PostAuthGateContext extends HookContext {
|
|
124
|
+
user: FortressUser;
|
|
125
|
+
/** Reasons already satisfied by the continuation currently being completed. */
|
|
126
|
+
completedReasons: readonly PendingReason[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface PostAuthGateDecision {
|
|
130
|
+
pluginData?: Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface PostAuthGateVerificationContext extends HookContext {
|
|
134
|
+
user: FortressUser;
|
|
135
|
+
continuation: {
|
|
136
|
+
id: string;
|
|
137
|
+
reason: PendingReason;
|
|
138
|
+
expiresAt: Date;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface PostAuthGateProvider {
|
|
143
|
+
reason: PendingReason;
|
|
144
|
+
/** Durable failed-proof policy stored on each continuation. */
|
|
145
|
+
maxAttempts?: number;
|
|
146
|
+
cooldownSeconds?: number;
|
|
147
|
+
evaluate: (ctx: PostAuthGateContext) => Promise<PostAuthGateDecision | void>;
|
|
148
|
+
/** Return optional plugin data to include in the final AuthResult. */
|
|
149
|
+
verify: (ctx: PostAuthGateVerificationContext, completion: unknown) => Promise<Record<string, unknown> | void>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// --- Supporting Types ---
|
|
153
|
+
|
|
154
|
+
export type ModelConstraint
|
|
155
|
+
= | { type: 'unique'; fields: string[] }
|
|
156
|
+
| { type: 'index'; fields: string[]; name?: string };
|
|
157
|
+
|
|
158
|
+
export interface ModelDefinition {
|
|
159
|
+
name: string;
|
|
160
|
+
fields: Record<string, FieldDefinition>;
|
|
161
|
+
constraints?: ModelConstraint[];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface FieldDefinition {
|
|
165
|
+
type: 'string' | 'number' | 'boolean' | 'date';
|
|
166
|
+
required?: boolean;
|
|
167
|
+
unique?: boolean;
|
|
168
|
+
references?: { model: string; field: string };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface PluginContext {
|
|
172
|
+
db: DatabaseAdapter;
|
|
173
|
+
config: FortressConfig;
|
|
174
|
+
/** Auth service reference. Optional at init time; available at runtime (enrichTokenClaims, scopeRules). */
|
|
175
|
+
auth?: AuthService;
|
|
176
|
+
/** IAM service reference. Optional at init time; available at runtime. */
|
|
177
|
+
iam?: IamService;
|
|
178
|
+
/** Resolved logger (silent no-op if `config.logger` is unset). */
|
|
179
|
+
logger?: FortressLogger;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Second argument passed to plugin HTTP route handlers by the dispatcher.
|
|
184
|
+
* Carries the verified caller identity and the raw Request so handlers can
|
|
185
|
+
* make authorization decisions, stamp audit entries, or read headers/cookies
|
|
186
|
+
* without trusting client-supplied body fields.
|
|
187
|
+
*
|
|
188
|
+
* `subject` / `claims` are populated whenever the endpoint's `meta.security`
|
|
189
|
+
* declared bearer auth (the dispatcher resolves principals first). `userId`
|
|
190
|
+
* is a convenience — it's present iff `subject?.type === 'USER'`, matching
|
|
191
|
+
* the pre-SERVICE_ACCOUNT shape. For public endpoints all three are
|
|
192
|
+
* `undefined`.
|
|
193
|
+
*/
|
|
194
|
+
export interface PluginRouteContext {
|
|
195
|
+
/** Resolved request principal — USER or SERVICE_ACCOUNT. */
|
|
196
|
+
subject?: Subject;
|
|
197
|
+
/** Convenience alias for `subject?.id` when the subject is a USER. */
|
|
198
|
+
userId?: string;
|
|
199
|
+
claims?: TokenClaims;
|
|
200
|
+
/** Credential-level narrowing scopes from principal resolution, if any. */
|
|
201
|
+
scopes?: string[] | null;
|
|
202
|
+
meta?: RequestMeta;
|
|
203
|
+
request: Request;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface MiddlewareDefinition {
|
|
207
|
+
path: string;
|
|
208
|
+
position: 'before-auth' | 'after-auth' | 'after-rbac';
|
|
209
|
+
handler: (ctx: PluginContext, request: PluginRequestContext, next: () => Promise<void>) => Promise<void>;
|
|
210
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute a {@link PolicyPlan} against an {@link IamService}.
|
|
3
|
+
*
|
|
4
|
+
* Ops are applied in dependency-safe order (resources before roles before
|
|
5
|
+
* service accounts) so a single `applyPolicyPlan` call can bring a fresh
|
|
6
|
+
* database to the declared state in one shot.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { IamService } from '../iam/iam-service';
|
|
12
|
+
import type { PolicyOp, PolicyPlan } from './types';
|
|
13
|
+
import { Errors } from '../errors';
|
|
14
|
+
|
|
15
|
+
/** Result of {@link applyPolicyPlan}. */
|
|
16
|
+
export interface ApplyPolicyResult {
|
|
17
|
+
applied: PolicyOp[];
|
|
18
|
+
skipped: PolicyOp[];
|
|
19
|
+
errors: { op: PolicyOp; message: string }[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const OP_ORDER: Record<PolicyOp['kind'], number> = {
|
|
23
|
+
'create-resource': 0,
|
|
24
|
+
'add-resource-action': 1,
|
|
25
|
+
'create-role': 2,
|
|
26
|
+
'update-role-description': 3,
|
|
27
|
+
'add-role-permission': 4,
|
|
28
|
+
'remove-role-permission': 5,
|
|
29
|
+
'create-group': 6,
|
|
30
|
+
'update-group-description': 7,
|
|
31
|
+
'create-service-account': 8,
|
|
32
|
+
'update-service-account': 9,
|
|
33
|
+
'bind-service-account-role': 10,
|
|
34
|
+
'unbind-service-account-role': 11,
|
|
35
|
+
'delete-role': 12,
|
|
36
|
+
'delete-group': 13,
|
|
37
|
+
'delete-service-account': 14,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Apply every op in the plan, sorted by op kind so dependencies are
|
|
42
|
+
* satisfied (resources before roles, roles before service-account
|
|
43
|
+
* bindings). Returns a structured result so the caller can log success
|
|
44
|
+
* vs. failure per op.
|
|
45
|
+
*/
|
|
46
|
+
export async function applyPolicyPlan(
|
|
47
|
+
plan: PolicyPlan,
|
|
48
|
+
iam: IamService,
|
|
49
|
+
): Promise<ApplyPolicyResult> {
|
|
50
|
+
const ops = [...plan.ops].sort((a, b) => OP_ORDER[a.kind] - OP_ORDER[b.kind]);
|
|
51
|
+
const applied: PolicyOp[] = [];
|
|
52
|
+
const skipped: PolicyOp[] = [];
|
|
53
|
+
const errors: { op: PolicyOp; message: string }[] = [];
|
|
54
|
+
|
|
55
|
+
// Caches to avoid repeated lookups across ops in the same apply pass.
|
|
56
|
+
const roleIdByName = new Map<string, string>();
|
|
57
|
+
const groupIdByName = new Map<string, string>();
|
|
58
|
+
const saIdByName = new Map<string, string>();
|
|
59
|
+
|
|
60
|
+
const resolveRoleId = async (name: string): Promise<string> => {
|
|
61
|
+
if (roleIdByName.has(name))
|
|
62
|
+
return roleIdByName.get(name)!;
|
|
63
|
+
const roles = await iam.getRoles();
|
|
64
|
+
for (const role of roles) roleIdByName.set(role.name, role.id);
|
|
65
|
+
const id = roleIdByName.get(name);
|
|
66
|
+
if (id == null)
|
|
67
|
+
throw Errors.notFound(`Role '${name}' not found`);
|
|
68
|
+
return id;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const resolveGroupId = async (name: string): Promise<string> => {
|
|
72
|
+
if (groupIdByName.has(name))
|
|
73
|
+
return groupIdByName.get(name)!;
|
|
74
|
+
const { groups } = await iam.listGroups({ limit: 10_000 });
|
|
75
|
+
for (const group of groups) groupIdByName.set(group.name, group.id);
|
|
76
|
+
const id = groupIdByName.get(name);
|
|
77
|
+
if (id == null)
|
|
78
|
+
throw Errors.notFound(`Group '${name}' not found`);
|
|
79
|
+
return id;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const resolveSaId = async (name: string): Promise<string> => {
|
|
83
|
+
if (saIdByName.has(name))
|
|
84
|
+
return saIdByName.get(name)!;
|
|
85
|
+
const { serviceAccounts } = await iam.listServiceAccounts({ limit: 10_000 });
|
|
86
|
+
for (const sa of serviceAccounts) saIdByName.set(sa.name, sa.id);
|
|
87
|
+
const id = saIdByName.get(name);
|
|
88
|
+
if (id == null)
|
|
89
|
+
throw Errors.notFound(`Service account '${name}' not found`);
|
|
90
|
+
return id;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
for (const op of ops) {
|
|
94
|
+
try {
|
|
95
|
+
switch (op.kind) {
|
|
96
|
+
case 'create-resource':
|
|
97
|
+
case 'add-resource-action': {
|
|
98
|
+
// Both go through pushResources via getResources merge: get current
|
|
99
|
+
// resources, layer the change, push.
|
|
100
|
+
const current = await iam.getResources();
|
|
101
|
+
const next = { resources: { ...current.resources } };
|
|
102
|
+
if (op.kind === 'create-resource') {
|
|
103
|
+
next.resources[op.resource.name] = {
|
|
104
|
+
actions: [...op.resource.actions],
|
|
105
|
+
description: op.resource.description,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const existing = next.resources[op.resource] ?? { actions: [] };
|
|
110
|
+
next.resources[op.resource] = {
|
|
111
|
+
...existing,
|
|
112
|
+
actions: existing.actions.includes(op.action)
|
|
113
|
+
? existing.actions
|
|
114
|
+
: [...existing.actions, op.action],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
await iam.pushResources(next);
|
|
118
|
+
applied.push(op);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'create-role': {
|
|
122
|
+
const role = await iam.createRole(op.role.name, op.role.permissions.map(perm => ({
|
|
123
|
+
resource: perm.resource,
|
|
124
|
+
action: perm.action,
|
|
125
|
+
effect: perm.effect,
|
|
126
|
+
})), op.role.description);
|
|
127
|
+
roleIdByName.set(op.role.name, role.id);
|
|
128
|
+
applied.push(op);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case 'update-role-description': {
|
|
132
|
+
const roleId = await resolveRoleId(op.role);
|
|
133
|
+
await iam.updateRole(roleId, { description: op.to ?? null });
|
|
134
|
+
applied.push(op);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case 'add-role-permission': {
|
|
138
|
+
const roleId = await resolveRoleId(op.role);
|
|
139
|
+
await iam.addPermissionToRole(roleId, {
|
|
140
|
+
resource: op.permission.resource,
|
|
141
|
+
action: op.permission.action,
|
|
142
|
+
effect: op.permission.effect,
|
|
143
|
+
});
|
|
144
|
+
applied.push(op);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case 'remove-role-permission': {
|
|
148
|
+
const roleId = await resolveRoleId(op.role);
|
|
149
|
+
await iam.removePermissionFromRole(roleId, {
|
|
150
|
+
resource: op.permission.resource,
|
|
151
|
+
action: op.permission.action,
|
|
152
|
+
effect: op.permission.effect,
|
|
153
|
+
});
|
|
154
|
+
applied.push(op);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'delete-role': {
|
|
158
|
+
const roleId = await resolveRoleId(op.role);
|
|
159
|
+
await iam.deleteRole(roleId);
|
|
160
|
+
roleIdByName.delete(op.role);
|
|
161
|
+
applied.push(op);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case 'create-group': {
|
|
165
|
+
const group = await iam.createGroup(op.group.name, op.group.description);
|
|
166
|
+
groupIdByName.set(op.group.name, group.id);
|
|
167
|
+
applied.push(op);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'update-group-description': {
|
|
171
|
+
const groupId = await resolveGroupId(op.group);
|
|
172
|
+
await iam.updateGroup(groupId, { description: op.to });
|
|
173
|
+
applied.push(op);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'delete-group': {
|
|
177
|
+
const groupId = await resolveGroupId(op.group);
|
|
178
|
+
await iam.deleteGroup(groupId);
|
|
179
|
+
groupIdByName.delete(op.group);
|
|
180
|
+
applied.push(op);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'create-service-account': {
|
|
184
|
+
const sa = await iam.createServiceAccount({
|
|
185
|
+
name: op.serviceAccount.name,
|
|
186
|
+
displayName: op.serviceAccount.displayName,
|
|
187
|
+
description: op.serviceAccount.description,
|
|
188
|
+
});
|
|
189
|
+
// `isActive` defaults to true at create-time; only emit an update
|
|
190
|
+
// when the policy explicitly disables the account.
|
|
191
|
+
if (op.serviceAccount.isActive === false) {
|
|
192
|
+
await iam.updateServiceAccount(sa.id, { isActive: false });
|
|
193
|
+
}
|
|
194
|
+
saIdByName.set(op.serviceAccount.name, sa.id);
|
|
195
|
+
applied.push(op);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case 'update-service-account': {
|
|
199
|
+
const saId = await resolveSaId(op.serviceAccount);
|
|
200
|
+
await iam.updateServiceAccount(saId, op.changes as {
|
|
201
|
+
displayName?: string | null;
|
|
202
|
+
description?: string | null;
|
|
203
|
+
isActive?: boolean;
|
|
204
|
+
});
|
|
205
|
+
applied.push(op);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 'bind-service-account-role': {
|
|
209
|
+
const saId = await resolveSaId(op.serviceAccount);
|
|
210
|
+
const roleId = await resolveRoleId(op.role);
|
|
211
|
+
await iam.bindRoleToServiceAccount(saId, roleId);
|
|
212
|
+
applied.push(op);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case 'unbind-service-account-role': {
|
|
216
|
+
const saId = await resolveSaId(op.serviceAccount);
|
|
217
|
+
const roleId = await resolveRoleId(op.role);
|
|
218
|
+
await iam.unbindRoleFromServiceAccount(saId, roleId, null);
|
|
219
|
+
applied.push(op);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case 'delete-service-account': {
|
|
223
|
+
const saId = await resolveSaId(op.serviceAccount);
|
|
224
|
+
await iam.deleteServiceAccount(saId);
|
|
225
|
+
saIdByName.delete(op.serviceAccount);
|
|
226
|
+
applied.push(op);
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
errors.push({ op, message: err instanceof Error ? err.message : String(err) });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { applied, skipped, errors };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Compatibility helper that applies only the resource operations in a plan.
|
|
241
|
+
* The file-path argument is retained for source compatibility but no file is
|
|
242
|
+
* written; resource updates are applied directly through the IAM service.
|
|
243
|
+
*/
|
|
244
|
+
export async function applyResourceOps(
|
|
245
|
+
plan: PolicyPlan,
|
|
246
|
+
iam: IamService,
|
|
247
|
+
_syncResourceFile: string,
|
|
248
|
+
): Promise<void> {
|
|
249
|
+
const resourceOps = plan.ops.filter(op => op.kind === 'create-resource' || op.kind === 'add-resource-action');
|
|
250
|
+
if (resourceOps.length === 0)
|
|
251
|
+
return;
|
|
252
|
+
const current = await iam.getResources();
|
|
253
|
+
const next = { resources: { ...current.resources } };
|
|
254
|
+
for (const op of resourceOps) {
|
|
255
|
+
if (op.kind === 'create-resource') {
|
|
256
|
+
next.resources[op.resource.name] = {
|
|
257
|
+
actions: [...op.resource.actions],
|
|
258
|
+
description: op.resource.description,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
else if (op.kind === 'add-resource-action') {
|
|
262
|
+
const existing = next.resources[op.resource] ?? { actions: [] };
|
|
263
|
+
next.resources[op.resource] = {
|
|
264
|
+
...existing,
|
|
265
|
+
actions: existing.actions.includes(op.action)
|
|
266
|
+
? existing.actions
|
|
267
|
+
: [...existing.actions, op.action],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
await iam.pushResources(next);
|
|
272
|
+
}
|