@bevel-software/platform-core-backend 0.22.0 → 0.24.0
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/dist/core/core-ports.d.ts +11 -1
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts +3 -3
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +46 -13
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +12 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +137 -24
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +46 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +99 -13
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +16 -12
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +28 -13
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +42 -12
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +67 -10
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +214 -35
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +17 -16
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +7 -3
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +8 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +8 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +38 -6
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +39 -6
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/index.d.ts +2 -1
- package/dist/modules/agent-instructions/index.d.ts.map +1 -1
- package/dist/modules/agent-instructions/index.js +2 -1
- package/dist/modules/agent-instructions/index.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +115 -0
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -0
- package/dist/modules/agent-instructions/shared-file-rules.js +272 -0
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +4 -2
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +58 -7
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +44 -1
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +57 -16
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +1 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +61 -5
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +1 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +18 -7
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +8 -0
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +70 -2
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +197 -18
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +7 -2
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts +20 -2
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +66 -35
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/database/connection.d.ts +16 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +117 -0
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +337 -120
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +116 -58
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts +8 -0
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +271 -1
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
- package/dist/modules/kb-fs/branch-name.js +12 -2
- package/dist/modules/kb-fs/branch-name.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +11 -16
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +79 -0
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
- package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
- package/dist/modules/kb-sync/sync-auth.js +1 -1
- package/dist/modules/kb-sync/sync-auth.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +8 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +38 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-records.store.js +7 -4
- package/dist/modules/plugins/join-request-records.store.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +8 -3
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts +52 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +207 -19
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +90 -18
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.contract.js +4 -1
- package/dist/modules/skills/skills.contract.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +98 -9
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +239 -37
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +7 -0
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +71 -7
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +12 -4
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +25 -4
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +42 -31
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +80 -0
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -0
- package/dist/modules/tool-registry/description-length.js +108 -0
- package/dist/modules/tool-registry/description-length.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +24 -0
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +30 -0
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +94 -0
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +193 -3
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +60 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +7 -3
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
- package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
- package/dist/modules/workflow/workflow-hooks.js +16 -1
- package/dist/modules/workflow/workflow-hooks.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +60 -6
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +115 -5
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
- package/dist/modules/workspace/agent-access.gate.js +123 -0
- package/dist/modules/workspace/agent-access.gate.js.map +1 -0
- package/dist/modules/workspace/agent-upload.routes.d.ts +77 -0
- package/dist/modules/workspace/agent-upload.routes.d.ts.map +1 -0
- package/dist/modules/workspace/agent-upload.routes.js +210 -0
- package/dist/modules/workspace/agent-upload.routes.js.map +1 -0
- package/dist/modules/workspace/agent-upload.store.d.ts +284 -0
- package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -0
- package/dist/modules/workspace/agent-upload.store.js +553 -0
- package/dist/modules/workspace/agent-upload.store.js.map +1 -0
- package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
- package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
- package/dist/modules/workspace/routine-write-policy.js +5 -6
- package/dist/modules/workspace/routine-write-policy.js.map +1 -1
- package/dist/modules/workspace/session-sink.d.ts +5 -5
- package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
- package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
- package/dist/modules/workspace/set-aside-clone.js +92 -0
- package/dist/modules/workspace/set-aside-clone.js.map +1 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +3 -3
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +40 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +46 -4
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/upload-limits.d.ts +13 -0
- package/dist/modules/workspace/upload-limits.d.ts.map +1 -0
- package/dist/modules/workspace/upload-limits.js +13 -0
- package/dist/modules/workspace/upload-limits.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +93 -4
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +128 -5
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +316 -58
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +13 -11
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +791 -198
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +0 -6
- package/dist/modules/workspace/write-denial.d.ts.map +1 -1
- package/dist/modules/workspace/write-denial.js +0 -6
- package/dist/modules/workspace/write-denial.js.map +1 -1
- package/dist/modules/workspace/zip-entry-rules.d.ts +114 -0
- package/dist/modules/workspace/zip-entry-rules.d.ts.map +1 -0
- package/dist/modules/workspace/zip-entry-rules.js +154 -0
- package/dist/modules/workspace/zip-entry-rules.js.map +1 -0
- package/dist/modules/write-access/write-access.d.ts +60 -0
- package/dist/modules/write-access/write-access.d.ts.map +1 -0
- package/dist/modules/write-access/write-access.js +129 -0
- package/dist/modules/write-access/write-access.js.map +1 -0
- package/dist/shared/column-crypto.d.ts +194 -0
- package/dist/shared/column-crypto.d.ts.map +1 -0
- package/dist/shared/column-crypto.js +144 -0
- package/dist/shared/column-crypto.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +25 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +28 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +20 -0
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js +26 -0
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/token-crypto.d.ts.map +1 -1
- package/dist/shared/token-crypto.js +25 -1
- package/dist/shared/token-crypto.js.map +1 -1
- package/dist/tenancy/static-tenant-source.d.ts +0 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +1 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/dist/tenancy/tenant-secrets.d.ts +5 -1
- package/dist/tenancy/tenant-secrets.d.ts.map +1 -1
- package/dist/tenancy/tenant-secrets.js +4 -0
- package/dist/tenancy/tenant-secrets.js.map +1 -1
- package/kb-template/AGENTS.md +2 -0
- package/migrations/0014_change_request_closed_reason.sql +1 -0
- package/migrations/0015_account_deactivation.sql +3 -0
- package/migrations/0016_pii_encryption.sql +20 -0
- package/migrations/meta/0014_snapshot.json +2265 -0
- package/migrations/meta/0015_snapshot.json +2271 -0
- package/migrations/meta/0016_snapshot.json +2327 -0
- package/migrations/meta/_journal.json +21 -0
- package/package.json +3 -3
- package/src/__tests__/kb-layout-config.test.ts +0 -2
- package/src/__tests__/retired-settings.test.ts +96 -0
- package/src/core/__tests__/gated-boot.test.ts +132 -0
- package/src/core/__tests__/lifecycle.test.ts +241 -5
- package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
- package/src/core/core-ports.ts +11 -1
- package/src/core/create-core-server.ts +52 -16
- package/src/core/create-core-services.ts +156 -24
- package/src/core/lifecycle.ts +117 -13
- package/src/core-config.ts +31 -14
- package/src/index.ts +30 -1
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
- package/src/modules/access/__tests__/users-db-double.ts +21 -12
- package/src/modules/access/access-control.interface.ts +43 -12
- package/src/modules/access/access-control.service.ts +234 -36
- package/src/modules/access/access.routes.ts +17 -16
- package/src/modules/access/directory-sync-bot.ts +7 -3
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +11 -5
- package/src/modules/agent-instructions/__tests__/compose.test.ts +19 -10
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +238 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -2
- package/src/modules/agent-instructions/compose.ts +50 -7
- package/src/modules/agent-instructions/index.ts +12 -0
- package/src/modules/agent-instructions/shared-file-rules.ts +314 -0
- package/src/modules/audit/agent-audit.service.ts +4 -2
- package/src/modules/auth/__tests__/account-deactivation.test.ts +253 -0
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +6 -2
- package/src/modules/auth/__tests__/account.routes.test.ts +87 -6
- package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
- package/src/modules/auth/__tests__/auth.service.test.ts +4 -2
- package/src/modules/auth/account-admission.ts +78 -9
- package/src/modules/auth/account-erasure.service.ts +69 -18
- package/src/modules/auth/account.routes.ts +63 -7
- package/src/modules/auth/auth.middleware.ts +19 -8
- package/src/modules/auth/auth.routes.ts +8 -0
- package/src/modules/auth/auth.service.ts +206 -23
- package/src/modules/auth/oidc-auth-provider.ts +7 -2
- package/src/modules/code-mode/__tests__/chain-runtime.e2e.test.ts +335 -0
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +46 -2
- package/src/modules/code-mode/code-mode.tool.ts +80 -34
- package/src/modules/database/__tests__/connection.test.ts +12 -0
- package/src/modules/database/__tests__/pii-backfill.pg.test.ts +561 -0
- package/src/modules/database/connection.ts +117 -0
- package/src/modules/database/core-schema.ts +116 -58
- package/src/modules/database/migrate.ts +353 -1
- package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
- package/src/modules/kb-fs/branch-name.ts +14 -1
- package/src/modules/kb-fs/repo-path.ts +85 -0
- package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
- package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
- package/src/modules/kb-sync/sync-auth.ts +2 -2
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +4 -3
- package/src/modules/mcp/__tests__/mcp.service.test.ts +115 -13
- package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
- package/src/modules/mcp/mcp.service.ts +46 -7
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
- package/src/modules/plugins/join-request-records.store.ts +7 -4
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
- package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
- package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
- package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
- package/src/modules/settings/deployment-settings.service.ts +8 -3
- package/src/modules/settings/setup.routes.ts +263 -19
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
- package/src/modules/skills/skills.contract.ts +91 -18
- package/src/modules/skills/skills.service.ts +278 -41
- package/src/modules/skills/skills.tools.ts +80 -8
- package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
- package/src/modules/tool-auth/external-api-key.service.ts +12 -4
- package/src/modules/tool-auth/internal-token.service.ts +3 -3
- package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
- package/src/modules/tool-helpers/tool-handler.ts +24 -4
- package/src/modules/tool-helpers/tool.contract.ts +3 -2
- package/src/modules/tool-helpers/validate-token.ts +1 -1
- package/src/modules/tool-manuals/tool-manuals.tools.ts +44 -31
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +378 -0
- package/src/modules/tool-registry/description-length.ts +111 -0
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
- package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
- package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
- package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
- package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
- package/src/modules/workflow/file-lock.service.ts +31 -0
- package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
- package/src/modules/workflow/git/__tests__/git.service.prFetchFailure.test.ts +170 -0
- package/src/modules/workflow/git/git.service.ts +215 -1
- package/src/modules/workflow/pending-commits.service.ts +64 -2
- package/src/modules/workflow/recovery-bot.ts +7 -3
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +4 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +25 -3
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
- package/src/modules/workflow/workflow-hooks.ts +64 -26
- package/src/modules/workflow/workflow.service.ts +125 -5
- package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
- package/src/modules/workspace/__tests__/agent-uploads.test.ts +1604 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +394 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
- package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
- package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
- package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +156 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +402 -0
- package/src/modules/workspace/__tests__/workspace.service.test.ts +77 -9
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +41 -39
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +766 -56
- package/src/modules/workspace/agent-access.gate.ts +164 -0
- package/src/modules/workspace/agent-upload.routes.ts +214 -0
- package/src/modules/workspace/agent-upload.store.ts +668 -0
- package/src/modules/workspace/routine-write-policy.ts +5 -6
- package/src/modules/workspace/session-sink.ts +5 -5
- package/src/modules/workspace/set-aside-clone.ts +96 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +9 -9
- package/src/modules/workspace/startup/steps/seed-tree.ts +3 -6
- package/src/modules/workspace/startup/steps/template-source.ts +53 -5
- package/src/modules/workspace/upload-limits.ts +12 -0
- package/src/modules/workspace/workspace.routes.ts +96 -5
- package/src/modules/workspace/workspace.service.ts +319 -61
- package/src/modules/workspace/workspace.tools.ts +894 -214
- package/src/modules/workspace/write-denial.ts +0 -8
- package/src/modules/workspace/zip-entry-rules.ts +173 -0
- package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
- package/src/modules/write-access/write-access.ts +153 -0
- package/src/shared/__tests__/column-crypto.test.ts +217 -0
- package/src/shared/column-crypto.ts +218 -0
- package/src/shared/domain-errors.ts +31 -0
- package/src/shared/git.contract.ts +27 -0
- package/src/shared/token-crypto.ts +28 -1
- package/src/tenancy/__tests__/static-tenant-source.test.ts +4 -1
- package/src/tenancy/static-tenant-source.ts +1 -3
- package/src/tenancy/tenant-secrets.ts +5 -1
- package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
- package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.policy.js +0 -62
- package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
- package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
- package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.service.js +0 -147
- package/dist/modules/workflow/session-ontology.service.js.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.js +0 -161
- package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
- package/dist/shared/kb-layout.d.ts +0 -39
- package/dist/shared/kb-layout.d.ts.map +0 -1
- package/dist/shared/kb-layout.js +0 -103
- package/dist/shared/kb-layout.js.map +0 -1
- package/dist/shared/kb-layout.test.d.ts +0 -2
- package/dist/shared/kb-layout.test.d.ts.map +0 -1
- package/dist/shared/kb-layout.test.js +0 -75
- package/dist/shared/kb-layout.test.js.map +0 -1
- package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
- package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
- package/src/modules/workflow/session-ontology.policy.ts +0 -70
- package/src/modules/workflow/session-ontology.service.ts +0 -183
- package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
- package/src/modules/workspace/session-ontology.gate.ts +0 -191
- package/src/shared/kb-layout.test.ts +0 -98
- package/src/shared/kb-layout.ts +0 -102
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Request, Response, NextFunction } from 'express';
|
|
2
2
|
import type { AuthService } from './auth.service.js';
|
|
3
|
+
import { AccountDeactivatedError, AuthBackendError } from './account-admission.js';
|
|
3
4
|
|
|
4
5
|
// Augment Express Request so userId/userEmail are available after auth middleware
|
|
5
6
|
declare global {
|
|
@@ -46,8 +47,8 @@ function readAuthCookie(req: Request): string | null {
|
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
export function createAuthMiddleware(authService: AuthService) {
|
|
50
|
-
return (req: Request, res: Response, next: NextFunction) => {
|
|
50
|
+
export function createAuthMiddleware(authService: Pick<AuthService, 'resolveSession'>) {
|
|
51
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
51
52
|
// Bearer is the primary path — every JSON API call in the frontend
|
|
52
53
|
// attaches it via `authFetch`. Cookie is the fallback for transports
|
|
53
54
|
// that can't carry headers (EventSource, image tags), set at login by
|
|
@@ -66,13 +67,23 @@ export function createAuthMiddleware(authService: AuthService) {
|
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
let session: { userId: string; email: string };
|
|
69
71
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
session = await authService.resolveSession(token);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// The account could not be looked up: not the caller's doing, and a
|
|
75
|
+
// 401 here would sign every valid session out during an outage.
|
|
76
|
+
if (err instanceof AuthBackendError) {
|
|
77
|
+
res.status(500).json({ error: 'Authentication backend unavailable' });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// A 401 otherwise, so the app drops the session and shows the login
|
|
81
|
+
// page; a switched-off account is told why there.
|
|
82
|
+
res.status(401).json({ error: err instanceof AccountDeactivatedError ? err.message : 'Invalid or expired token' });
|
|
83
|
+
return;
|
|
76
84
|
}
|
|
85
|
+
req.userId = session.userId;
|
|
86
|
+
req.userEmail = session.email;
|
|
87
|
+
next();
|
|
77
88
|
};
|
|
78
89
|
}
|
|
@@ -5,6 +5,7 @@ const log = logger('auth');
|
|
|
5
5
|
import type { AuthService } from './auth.service.js';
|
|
6
6
|
import { AUTH_COOKIE_NAME } from './auth.middleware.js'; // also imports Express Request augmentation
|
|
7
7
|
import { FixedWindowRateLimiter } from './rate-limit.js';
|
|
8
|
+
import { AccountDeactivatedError } from './account-admission.js';
|
|
8
9
|
import { canonicalEmail } from '../../shared/email-identity.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -113,6 +114,13 @@ export function createAuthRoutes(
|
|
|
113
114
|
});
|
|
114
115
|
res.json(result);
|
|
115
116
|
} catch (error) {
|
|
117
|
+
// The password was right and an admin switched the account off: said
|
|
118
|
+
// as such (only ever after the password is proven, see
|
|
119
|
+
// `loginWithPassword`), so the person knows whom to ask.
|
|
120
|
+
if (error instanceof AccountDeactivatedError) {
|
|
121
|
+
res.status(403).json({ error: error.message });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
116
124
|
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
117
125
|
log.error('Login error:', { detail: msg });
|
|
118
126
|
res.status(401).json({ error: 'Authentication failed' });
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import jwt from 'jsonwebtoken';
|
|
2
|
-
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { and, eq, isNotNull, isNull } from 'drizzle-orm';
|
|
3
3
|
import type { Database } from '../database/connection.js';
|
|
4
4
|
import { users } from '../database/schema.js';
|
|
5
5
|
import type { AuthUser } from '@bevel-software/platform-shared';
|
|
6
6
|
import { canonicalEmail, hashEmail } from '../../shared/email-identity.js';
|
|
7
7
|
import {
|
|
8
8
|
AccountAdmissionRefusedError,
|
|
9
|
+
AccountChangeRefusedError,
|
|
10
|
+
AccountDeactivatedError,
|
|
11
|
+
AuthBackendError,
|
|
9
12
|
admitEveryone,
|
|
10
13
|
type AccountProvisionReason,
|
|
11
14
|
type IAccountAdmission,
|
|
@@ -16,9 +19,22 @@ import {
|
|
|
16
19
|
timingSafeStringEqual,
|
|
17
20
|
MIN_PASSWORD_LENGTH,
|
|
18
21
|
} from './password-hash.js';
|
|
22
|
+
import { RECOVERY_BOT_EMAIL } from '../workflow/recovery-bot.js';
|
|
23
|
+
import { DIRECTORY_SYNC_BOT_EMAIL } from '../access/directory-sync-bot.js';
|
|
19
24
|
|
|
20
25
|
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
21
26
|
|
|
27
|
+
/**
|
|
28
|
+
* How long {@link AuthService.isActive} trusts what it last read. Every
|
|
29
|
+
* request carrying a credential asks, so the answer is kept briefly rather
|
|
30
|
+
* than read each time; switching an account off or on in THIS process
|
|
31
|
+
* forgets it at once, and another replica sees the change within this long.
|
|
32
|
+
*/
|
|
33
|
+
const ACTIVE_CACHE_MS = 30_000;
|
|
34
|
+
|
|
35
|
+
/** The accounts core runs its own work as. Nobody signs in with them, and nobody switches them off. */
|
|
36
|
+
const SYSTEM_ACCOUNT_EMAILS: readonly string[] = [RECOVERY_BOT_EMAIL, DIRECTORY_SYNC_BOT_EMAIL];
|
|
37
|
+
|
|
22
38
|
/**
|
|
23
39
|
* What every caller is told when it tries to give the deployment admin a
|
|
24
40
|
* stored password — the Account page's own change and an admin's "Set
|
|
@@ -125,11 +141,28 @@ export class AuthService {
|
|
|
125
141
|
const [existing] = await this.db
|
|
126
142
|
.select({ id: users.id })
|
|
127
143
|
.from(users)
|
|
128
|
-
.where(eq(users.
|
|
144
|
+
.where(eq(users.emailBidx, normalizedEmail))
|
|
129
145
|
.limit(1);
|
|
130
146
|
if (existing) return;
|
|
131
147
|
const verdict = await this.admission.canProvision(normalizedEmail, reason);
|
|
132
|
-
if (
|
|
148
|
+
if (verdict.ok) return;
|
|
149
|
+
// A first sign-in the port would rather keep than turn away: the person
|
|
150
|
+
// is put on file, switched off, for an admin to switch on — and is still
|
|
151
|
+
// refused now, with the port's words. Only for single sign-on, the one
|
|
152
|
+
// provisioning nobody asked for (see `AccountAdmissionVerdict`).
|
|
153
|
+
if (verdict.waitForAdmin && reason === 'sso') {
|
|
154
|
+
await this.db
|
|
155
|
+
.insert(users)
|
|
156
|
+
.values({
|
|
157
|
+
email: normalizedEmail,
|
|
158
|
+
emailBidx: normalizedEmail,
|
|
159
|
+
name: normalizedEmail.split('@')[0] || normalizedEmail,
|
|
160
|
+
deactivatedAt: new Date(),
|
|
161
|
+
})
|
|
162
|
+
.onConflictDoNothing({ target: users.emailBidx });
|
|
163
|
+
throw new AccountAdmissionRefusedError(verdict.message, { waitingForAdmin: true });
|
|
164
|
+
}
|
|
165
|
+
throw new AccountAdmissionRefusedError(verdict.message);
|
|
133
166
|
}
|
|
134
167
|
|
|
135
168
|
/**
|
|
@@ -170,11 +203,12 @@ export class AuthService {
|
|
|
170
203
|
// address's — one round trip short — and repeated timings would then
|
|
171
204
|
// disclose which email the deployment configured as `ADMIN_EMAIL`. The
|
|
172
205
|
// decoy hash already buys that uniformity for the scrypt half; this keeps
|
|
173
|
-
// the database half uniform too.
|
|
206
|
+
// the database half uniform too. Through the blind index: `email` is
|
|
207
|
+
// randomized ciphertext.
|
|
174
208
|
const [user] = await this.db
|
|
175
209
|
.select()
|
|
176
210
|
.from(users)
|
|
177
|
-
.where(eq(users.
|
|
211
|
+
.where(eq(users.emailBidx, normalizedEmail))
|
|
178
212
|
.limit(1);
|
|
179
213
|
|
|
180
214
|
if (this.isEnvAdminEmail(normalizedEmail)) {
|
|
@@ -200,6 +234,9 @@ export class AuthService {
|
|
|
200
234
|
await verifyPassword(provided, await decoyHash());
|
|
201
235
|
throw new Error('Invalid credentials');
|
|
202
236
|
}
|
|
237
|
+
// Never refused for being switched off: the environment password is
|
|
238
|
+
// the deployment's way back in, and {@link deactivate} refuses this
|
|
239
|
+
// account for that reason.
|
|
203
240
|
const defaultName = normalizedEmail.split('@')[0] || normalizedEmail;
|
|
204
241
|
await this.assertAdmitted(normalizedEmail, 'bootstrap');
|
|
205
242
|
const admin = await this.upsertUserByEmail(normalizedEmail, defaultName);
|
|
@@ -215,6 +252,9 @@ export class AuthService {
|
|
|
215
252
|
if (!user?.passwordHash || !matches) {
|
|
216
253
|
throw new Error('Invalid credentials');
|
|
217
254
|
}
|
|
255
|
+
// Only now, with the password proven: telling a stranger that an address
|
|
256
|
+
// is switched off would tell them it has an account.
|
|
257
|
+
if (user.deactivatedAt) throw new AccountDeactivatedError();
|
|
218
258
|
return { token: this.signToken(user.id, user.email), user: this.toClientUser(user) };
|
|
219
259
|
}
|
|
220
260
|
|
|
@@ -246,6 +286,7 @@ export class AuthService {
|
|
|
246
286
|
await this.assertAdmitted(normalizedEmail, 'sso');
|
|
247
287
|
const displayName = (name ?? '').trim() || normalizedEmail.split('@')[0] || normalizedEmail;
|
|
248
288
|
const user = await this.upsertUserByEmail(normalizedEmail, displayName);
|
|
289
|
+
if (user.deactivatedAt) throw new AccountDeactivatedError();
|
|
249
290
|
return { token: this.signToken(user.id, user.email), user: this.toClientUser(user) };
|
|
250
291
|
}
|
|
251
292
|
|
|
@@ -255,7 +296,13 @@ export class AuthService {
|
|
|
255
296
|
* account (e.g. one that first arrived via SSO, or a reset for a locked-out
|
|
256
297
|
* user) is deliberate admin behavior, not an error.
|
|
257
298
|
*
|
|
258
|
-
*
|
|
299
|
+
* WITHOUT a password the account is made for single sign-on: the person
|
|
300
|
+
* signs in through the deployment's provider and finds their account
|
|
301
|
+
* waiting, already holding its place (a seat, on a host that sells them).
|
|
302
|
+
* Password sign-in refuses it until a password is set. For an address that
|
|
303
|
+
* already has an account it changes nothing but an explicitly given name.
|
|
304
|
+
*
|
|
305
|
+
* WITH a password, the deployment admin is the one target this refuses, for the reason
|
|
259
306
|
* {@link changePassword} refuses it: that account's password is the
|
|
260
307
|
* environment's, so a stored hash would not replace it but ADD a second
|
|
261
308
|
* credential — one that keeps signing in after `ADMIN_PASSWORD` is rotated,
|
|
@@ -266,12 +313,26 @@ export class AuthService {
|
|
|
266
313
|
async createAccount(
|
|
267
314
|
email: string,
|
|
268
315
|
name: string | undefined,
|
|
269
|
-
password
|
|
316
|
+
password?: string,
|
|
270
317
|
): Promise<AuthUser> {
|
|
271
318
|
const normalizedEmail = canonicalEmail(email ?? '');
|
|
272
319
|
if (!EMAIL_REGEX.test(normalizedEmail)) {
|
|
273
320
|
throw new Error('Invalid email');
|
|
274
321
|
}
|
|
322
|
+
const suppliedName = (name ?? '').trim();
|
|
323
|
+
const displayName = suppliedName || normalizedEmail.split('@')[0] || normalizedEmail;
|
|
324
|
+
if (!password) {
|
|
325
|
+
await this.assertAdmitted(normalizedEmail, 'admin-create');
|
|
326
|
+
const [created] = await this.db
|
|
327
|
+
.insert(users)
|
|
328
|
+
.values({ email: normalizedEmail, emailBidx: normalizedEmail, name: displayName })
|
|
329
|
+
.onConflictDoUpdate({
|
|
330
|
+
target: users.emailBidx,
|
|
331
|
+
set: suppliedName ? { name: suppliedName, updatedAt: new Date() } : { updatedAt: new Date() },
|
|
332
|
+
})
|
|
333
|
+
.returning();
|
|
334
|
+
return this.toClientUser(created);
|
|
335
|
+
}
|
|
275
336
|
// Before the policy check, so the refusal names the real reason rather
|
|
276
337
|
// than sending the admin off to pick a longer password first.
|
|
277
338
|
if (this.isEnvAdminEmail(normalizedEmail)) {
|
|
@@ -279,8 +340,6 @@ export class AuthService {
|
|
|
279
340
|
}
|
|
280
341
|
this.assertPasswordPolicy(password);
|
|
281
342
|
await this.assertAdmitted(normalizedEmail, 'admin-create');
|
|
282
|
-
const suppliedName = (name ?? '').trim();
|
|
283
|
-
const displayName = suppliedName || normalizedEmail.split('@')[0] || normalizedEmail;
|
|
284
343
|
const passwordHash = await hashPassword(password);
|
|
285
344
|
// One atomic upsert. On conflict (re-provisioning an existing account) an
|
|
286
345
|
// EXPLICITLY supplied name is persisted too; a blank name keeps the
|
|
@@ -288,9 +347,9 @@ export class AuthService {
|
|
|
288
347
|
// fallback. `returning()` yields the authoritative row either way.
|
|
289
348
|
const [row] = await this.db
|
|
290
349
|
.insert(users)
|
|
291
|
-
.values({ email: normalizedEmail, name: displayName, passwordHash })
|
|
350
|
+
.values({ email: normalizedEmail, emailBidx: normalizedEmail, name: displayName, passwordHash })
|
|
292
351
|
.onConflictDoUpdate({
|
|
293
|
-
target: users.
|
|
352
|
+
target: users.emailBidx,
|
|
294
353
|
set: suppliedName
|
|
295
354
|
? { passwordHash, name: suppliedName, updatedAt: new Date() }
|
|
296
355
|
: { passwordHash, updatedAt: new Date() },
|
|
@@ -402,18 +461,28 @@ export class AuthService {
|
|
|
402
461
|
name: string;
|
|
403
462
|
hasPassword: boolean;
|
|
404
463
|
isEnvAdmin: boolean;
|
|
464
|
+
/** When an admin switched the account off; null while it is on. */
|
|
465
|
+
deactivatedAt: Date | null;
|
|
466
|
+
/** One of the accounts the platform runs its own work as: never switched off, nobody signs in with it. */
|
|
467
|
+
isSystem: boolean;
|
|
405
468
|
createdAt: Date;
|
|
406
469
|
}>
|
|
407
470
|
> {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
471
|
+
// Sorted in-process: `email` is ciphertext in the database, so ORDER BY
|
|
472
|
+
// would sort by IV noise. The table is one row per team member.
|
|
473
|
+
const rows = await this.db.select().from(users);
|
|
474
|
+
return rows
|
|
475
|
+
.map((row) => ({
|
|
476
|
+
id: row.id,
|
|
477
|
+
email: row.email,
|
|
478
|
+
name: row.name,
|
|
479
|
+
hasPassword: row.passwordHash != null,
|
|
480
|
+
isEnvAdmin: this.reportsAsEnvAdmin(row.email),
|
|
481
|
+
deactivatedAt: row.deactivatedAt,
|
|
482
|
+
isSystem: SYSTEM_ACCOUNT_EMAILS.includes(row.email),
|
|
483
|
+
createdAt: row.createdAt,
|
|
484
|
+
}))
|
|
485
|
+
.sort((a, b) => a.email.localeCompare(b.email));
|
|
417
486
|
}
|
|
418
487
|
|
|
419
488
|
private assertPasswordPolicy(password: string): void {
|
|
@@ -443,18 +512,24 @@ export class AuthService {
|
|
|
443
512
|
await this.assertAdmitted(normalizedEmail, 'embed');
|
|
444
513
|
const displayName = (name ?? '').trim() || normalizedEmail.split('@')[0] || normalizedEmail;
|
|
445
514
|
const user = await this.upsertUserByEmail(normalizedEmail, displayName);
|
|
515
|
+
if (user.deactivatedAt) throw new AccountDeactivatedError();
|
|
446
516
|
return { id: user.id, email: user.email, name: user.name };
|
|
447
517
|
}
|
|
448
518
|
|
|
449
519
|
private async upsertUserByEmail(email: string, name: string) {
|
|
450
520
|
const [user] = await this.db
|
|
451
521
|
.insert(users)
|
|
452
|
-
.values({ email, name })
|
|
453
|
-
.onConflictDoUpdate({ target: users.
|
|
522
|
+
.values({ email, emailBidx: email, name })
|
|
523
|
+
.onConflictDoUpdate({ target: users.emailBidx, set: { updatedAt: new Date() } })
|
|
454
524
|
.returning();
|
|
455
525
|
return user;
|
|
456
526
|
}
|
|
457
527
|
|
|
528
|
+
/**
|
|
529
|
+
* The signature and expiry of a session token, and nothing else — whether
|
|
530
|
+
* its account is still on is {@link resolveSession}'s question. Every
|
|
531
|
+
* place a session is ACCEPTED goes through that one.
|
|
532
|
+
*/
|
|
458
533
|
verifyToken(token: string): { userId: string; email: string } {
|
|
459
534
|
const decoded = jwt.verify(token, this.config.jwtSecret) as {
|
|
460
535
|
userId: string;
|
|
@@ -463,6 +538,114 @@ export class AuthService {
|
|
|
463
538
|
return { userId: decoded.userId, email: decoded.email };
|
|
464
539
|
}
|
|
465
540
|
|
|
541
|
+
/**
|
|
542
|
+
* A session token that may still be used: signed by us, not expired, and
|
|
543
|
+
* its account still on. A token outlives a deactivation by up to seven
|
|
544
|
+
* days, so the signature alone no longer says the bearer may enter.
|
|
545
|
+
* Throws for an invalid token, {@link AccountDeactivatedError} for one
|
|
546
|
+
* whose account was switched off.
|
|
547
|
+
*/
|
|
548
|
+
async resolveSession(token: string): Promise<{ userId: string; email: string }> {
|
|
549
|
+
const claim = this.verifyToken(token);
|
|
550
|
+
let active: boolean;
|
|
551
|
+
try {
|
|
552
|
+
active = await this.isActive(claim.userId);
|
|
553
|
+
} catch (err) {
|
|
554
|
+
// The token is fine; the database is not. Said apart from a refusal, so
|
|
555
|
+
// an outage is a 500 and not a sign-out (see `AuthBackendError`).
|
|
556
|
+
throw new AuthBackendError(err);
|
|
557
|
+
}
|
|
558
|
+
if (!active) throw new AccountDeactivatedError();
|
|
559
|
+
return claim;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private readonly activeCache = new Map<string, { active: boolean; at: number }>();
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Whether the account behind a credential is on: it exists and no admin
|
|
566
|
+
* switched it off. Asked for every credential presented that does not
|
|
567
|
+
* already read the account (a session token, an internal token), so it is
|
|
568
|
+
* cached for {@link ACTIVE_CACHE_MS}; switching an account off or on in
|
|
569
|
+
* this process forgets its entry at once.
|
|
570
|
+
*
|
|
571
|
+
* A missing row is not on either — an erased account's tokens die with it.
|
|
572
|
+
*
|
|
573
|
+
* The deployment admin's account is always on, whatever its row says: its
|
|
574
|
+
* environment password signs it in regardless (see `loginWithPassword`),
|
|
575
|
+
* and a session that sign-in mints must then be usable too, or the way
|
|
576
|
+
* back into a deployment would lead nowhere. {@link deactivate} refuses
|
|
577
|
+
* the account, but a host may write the column itself.
|
|
578
|
+
*/
|
|
579
|
+
async isActive(userId: string): Promise<boolean> {
|
|
580
|
+
const now = Date.now();
|
|
581
|
+
const hit = this.activeCache.get(userId);
|
|
582
|
+
if (hit && now - hit.at < ACTIVE_CACHE_MS) return hit.active;
|
|
583
|
+
const [row] = await this.db
|
|
584
|
+
.select({ email: users.email, deactivatedAt: users.deactivatedAt })
|
|
585
|
+
.from(users)
|
|
586
|
+
.where(eq(users.id, userId))
|
|
587
|
+
.limit(1);
|
|
588
|
+
const active = row !== undefined && (!row.deactivatedAt || this.isEnvAdminEmail(row.email ?? ''));
|
|
589
|
+
this.activeCache.set(userId, { active, at: now });
|
|
590
|
+
return active;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Switch an account off. Its row, history and place in roles and groups
|
|
595
|
+
* stay; every credential it holds stops being honoured (see
|
|
596
|
+
* {@link isActive}). Idempotent: an account already off keeps the moment
|
|
597
|
+
* it was first switched off.
|
|
598
|
+
*
|
|
599
|
+
* The deployment admin is refused: the environment password is the way
|
|
600
|
+
* back into a deployment whose every other admin is gone, and switching
|
|
601
|
+
* it off would close that door from inside. So are the machine accounts
|
|
602
|
+
* core runs its own work as. Refusing an admin's own account is the
|
|
603
|
+
* route's concern, since it knows who is asking.
|
|
604
|
+
*
|
|
605
|
+
* Returns false when there is no such account.
|
|
606
|
+
*/
|
|
607
|
+
async deactivate(userId: string): Promise<boolean> {
|
|
608
|
+
const [row] = await this.db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
609
|
+
if (!row) return false;
|
|
610
|
+
if (this.isEnvAdminEmail(row.email)) {
|
|
611
|
+
throw new AccountChangeRefusedError('The deployment admin cannot be switched off: its password in the environment is the way back in.');
|
|
612
|
+
}
|
|
613
|
+
if (SYSTEM_ACCOUNT_EMAILS.includes(row.email)) {
|
|
614
|
+
throw new AccountChangeRefusedError('This account belongs to the platform itself and cannot be switched off.');
|
|
615
|
+
}
|
|
616
|
+
await this.db
|
|
617
|
+
.update(users)
|
|
618
|
+
.set({ deactivatedAt: new Date(), updatedAt: new Date() })
|
|
619
|
+
.where(and(eq(users.id, userId), isNull(users.deactivatedAt)));
|
|
620
|
+
this.activeCache.delete(userId);
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Switch a deactivated account back on, if the deployment has room for it:
|
|
626
|
+
* the admission port is asked, as for a new account, because an account
|
|
627
|
+
* that is on takes the same place. A refusal throws
|
|
628
|
+
* {@link AccountAdmissionRefusedError} with the port's words. An account
|
|
629
|
+
* already on is not asked about.
|
|
630
|
+
*
|
|
631
|
+
* Returns false when there is no such account.
|
|
632
|
+
*/
|
|
633
|
+
async reactivate(userId: string): Promise<boolean> {
|
|
634
|
+
const [row] = await this.db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
635
|
+
if (!row) return false;
|
|
636
|
+
if (!row.deactivatedAt) return true;
|
|
637
|
+
if (this.admission !== admitEveryone) {
|
|
638
|
+
const verdict = await this.admission.canProvision(row.email, 'reactivate');
|
|
639
|
+
if (!verdict.ok) throw new AccountAdmissionRefusedError(verdict.message);
|
|
640
|
+
}
|
|
641
|
+
await this.db
|
|
642
|
+
.update(users)
|
|
643
|
+
.set({ deactivatedAt: null, updatedAt: new Date() })
|
|
644
|
+
.where(and(eq(users.id, userId), isNotNull(users.deactivatedAt)));
|
|
645
|
+
this.activeCache.delete(userId);
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
|
|
466
649
|
/**
|
|
467
650
|
* Resolve a set of `authorId` hashes back to the app users that produced
|
|
468
651
|
* them. Used by the PR list to display the human who triggered each PR
|
|
@@ -513,7 +696,7 @@ export class AuthService {
|
|
|
513
696
|
const [user] = await this.db
|
|
514
697
|
.select()
|
|
515
698
|
.from(users)
|
|
516
|
-
.where(eq(users.
|
|
699
|
+
.where(eq(users.emailBidx, canonicalEmail(email ?? '')))
|
|
517
700
|
.limit(1);
|
|
518
701
|
|
|
519
702
|
if (!user) return null;
|
|
@@ -7,7 +7,7 @@ import type { AuthProviderPlugin } from './auth.routes.js';
|
|
|
7
7
|
import { AUTH_COOKIE_MAX_AGE_S } from './auth.routes.js';
|
|
8
8
|
import { AUTH_COOKIE_NAME } from './auth.middleware.js';
|
|
9
9
|
import type { AuthService } from './auth.service.js';
|
|
10
|
-
import { AccountAdmissionRefusedError } from './account-admission.js';
|
|
10
|
+
import { AccountAdmissionRefusedError, AccountDeactivatedError } from './account-admission.js';
|
|
11
11
|
import { normalizeIssuerUrl } from '../settings/oidc-check.js';
|
|
12
12
|
|
|
13
13
|
// Short-lived CSRF state + PKCE verifier for the OAuth round-trip: set before
|
|
@@ -325,7 +325,12 @@ export class OidcAuthProvider implements AuthProviderPlugin {
|
|
|
325
325
|
// screen can say so instead of "try again".
|
|
326
326
|
if (error instanceof AccountAdmissionRefusedError) {
|
|
327
327
|
log.warn('OIDC sign-in refused by the account admission port:', { detail: error.message });
|
|
328
|
-
fail('admission');
|
|
328
|
+
fail(error.waitingForAdmin ? 'waiting' : 'admission');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (error instanceof AccountDeactivatedError) {
|
|
332
|
+
log.warn('OIDC sign-in refused: the account is switched off');
|
|
333
|
+
fail('deactivated');
|
|
329
334
|
return;
|
|
330
335
|
}
|
|
331
336
|
log.error('OIDC callback error:', { err: error });
|