@penclipai/server 2026.704.0 → 2026.714.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/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/process/execute.d.ts.map +1 -1
- package/dist/adapters/process/execute.js +1 -0
- package/dist/adapters/process/execute.js.map +1 -1
- package/dist/adapters/registry.d.ts.map +1 -1
- package/dist/adapters/registry.js +79 -51
- package/dist/adapters/registry.js.map +1 -1
- package/dist/adapters/utils.d.ts +5 -0
- package/dist/adapters/utils.d.ts.map +1 -1
- package/dist/adapters/utils.js.map +1 -1
- package/dist/agent-auth-jwt.d.ts +5 -1
- package/dist/agent-auth-jwt.d.ts.map +1 -1
- package/dist/agent-auth-jwt.js +72 -20
- package/dist/agent-auth-jwt.js.map +1 -1
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +10 -0
- package/dist/app.js.map +1 -1
- package/dist/built-ins/agents/reflection-coach/AGENTS.md +47 -0
- package/dist/built-ins/agents/reflection-coach/routines/recent-agent-reflection.md +77 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +208 -116
- package/dist/index.js.map +1 -1
- package/dist/middleware/api-compression.d.ts +8 -0
- package/dist/middleware/api-compression.d.ts.map +1 -0
- package/dist/middleware/api-compression.js +207 -0
- package/dist/middleware/api-compression.js.map +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +130 -3
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +27 -0
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/onboarding-assets/default/AGENTS.md +1 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +11 -0
- package/dist/redaction.js.map +1 -1
- package/dist/routes/access.d.ts.map +1 -1
- package/dist/routes/access.js +4 -3
- package/dist/routes/access.js.map +1 -1
- package/dist/routes/adapters.d.ts.map +1 -1
- package/dist/routes/adapters.js +2 -0
- package/dist/routes/adapters.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +222 -87
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/attention.d.ts +3 -0
- package/dist/routes/attention.d.ts.map +1 -0
- package/dist/routes/attention.js +24 -0
- package/dist/routes/attention.js.map +1 -0
- package/dist/routes/authz.d.ts.map +1 -1
- package/dist/routes/authz.js +29 -1
- package/dist/routes/authz.js.map +1 -1
- package/dist/routes/board-chat.d.ts.map +1 -1
- package/dist/routes/board-chat.js +4 -1
- package/dist/routes/board-chat.js.map +1 -1
- package/dist/routes/built-in-agents.d.ts +3 -0
- package/dist/routes/built-in-agents.d.ts.map +1 -0
- package/dist/routes/built-in-agents.js +267 -0
- package/dist/routes/built-in-agents.js.map +1 -0
- package/dist/routes/cases.d.ts +46 -0
- package/dist/routes/cases.d.ts.map +1 -0
- package/dist/routes/cases.js +1395 -0
- package/dist/routes/cases.js.map +1 -0
- package/dist/routes/companies.d.ts.map +1 -1
- package/dist/routes/companies.js +4 -1
- package/dist/routes/companies.js.map +1 -1
- package/dist/routes/company-skills.d.ts.map +1 -1
- package/dist/routes/company-skills.js +453 -33
- package/dist/routes/company-skills.js.map +1 -1
- package/dist/routes/environments.d.ts.map +1 -1
- package/dist/routes/environments.js +71 -8
- package/dist/routes/environments.js.map +1 -1
- package/dist/routes/execution-workspaces.d.ts.map +1 -1
- package/dist/routes/execution-workspaces.js +89 -3
- package/dist/routes/execution-workspaces.js.map +1 -1
- package/dist/routes/health.d.ts +2 -0
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +30 -0
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/inbox-dismissals.d.ts.map +1 -1
- package/dist/routes/inbox-dismissals.js +80 -19
- package/dist/routes/inbox-dismissals.js.map +1 -1
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/issues.d.ts +25 -0
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +1455 -46
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/openapi.d.ts.map +1 -1
- package/dist/routes/openapi.js +448 -11
- package/dist/routes/openapi.js.map +1 -1
- package/dist/routes/pipelines.js +2 -2
- package/dist/routes/pipelines.js.map +1 -1
- package/dist/routes/resource-memberships.d.ts.map +1 -1
- package/dist/routes/resource-memberships.js +13 -5
- package/dist/routes/resource-memberships.js.map +1 -1
- package/dist/routes/secrets.d.ts.map +1 -1
- package/dist/routes/secrets.js +261 -1
- package/dist/routes/secrets.js.map +1 -1
- package/dist/routes/sidebar-badges.d.ts.map +1 -1
- package/dist/routes/sidebar-badges.js +18 -2
- package/dist/routes/sidebar-badges.js.map +1 -1
- package/dist/routes/user-profiles.d.ts.map +1 -1
- package/dist/routes/user-profiles.js +5 -4
- package/dist/routes/user-profiles.js.map +1 -1
- package/dist/server-info.d.ts +2 -0
- package/dist/server-info.d.ts.map +1 -1
- package/dist/server-info.js +20 -5
- package/dist/server-info.js.map +1 -1
- package/dist/services/access.d.ts +4 -4
- package/dist/services/access.d.ts.map +1 -1
- package/dist/services/activity.d.ts +3 -3
- package/dist/services/activity.d.ts.map +1 -1
- package/dist/services/activity.js +5 -4
- package/dist/services/activity.js.map +1 -1
- package/dist/services/adapter-registry-bootstrap.reconcile.test.js +1 -0
- package/dist/services/adapter-registry-bootstrap.reconcile.test.js.map +1 -1
- package/dist/services/agent-secret-bindings.d.ts +15 -0
- package/dist/services/agent-secret-bindings.d.ts.map +1 -1
- package/dist/services/agent-secret-bindings.js +43 -0
- package/dist/services/agent-secret-bindings.js.map +1 -1
- package/dist/services/agents.d.ts +194 -133
- package/dist/services/agents.d.ts.map +1 -1
- package/dist/services/agents.js +107 -4
- package/dist/services/agents.js.map +1 -1
- package/dist/services/approvals.d.ts +10 -10
- package/dist/services/approvals.d.ts.map +1 -1
- package/dist/services/approvals.js +9 -1
- package/dist/services/approvals.js.map +1 -1
- package/dist/services/assets.d.ts +5 -5
- package/dist/services/attention.d.ts +11 -0
- package/dist/services/attention.d.ts.map +1 -0
- package/dist/services/attention.js +1024 -0
- package/dist/services/attention.js.map +1 -0
- package/dist/services/authorization.d.ts +15 -2
- package/dist/services/authorization.d.ts.map +1 -1
- package/dist/services/authorization.js +324 -8
- package/dist/services/authorization.js.map +1 -1
- package/dist/services/board-auth.d.ts +2 -2
- package/dist/services/budgets.d.ts.map +1 -1
- package/dist/services/budgets.js +8 -7
- package/dist/services/budgets.js.map +1 -1
- package/dist/services/built-in-agent-metadata.d.ts +9 -0
- package/dist/services/built-in-agent-metadata.d.ts.map +1 -0
- package/dist/services/built-in-agent-metadata.js +39 -0
- package/dist/services/built-in-agent-metadata.js.map +1 -0
- package/dist/services/built-in-agents.d.ts +394 -0
- package/dist/services/built-in-agents.d.ts.map +1 -0
- package/dist/services/built-in-agents.js +1426 -0
- package/dist/services/built-in-agents.js.map +1 -0
- package/dist/services/change-consent-gate.d.ts +18 -0
- package/dist/services/change-consent-gate.d.ts.map +1 -0
- package/dist/services/change-consent-gate.js +170 -0
- package/dist/services/change-consent-gate.js.map +1 -0
- package/dist/services/companies.d.ts +14 -8
- package/dist/services/companies.d.ts.map +1 -1
- package/dist/services/companies.js +6 -1
- package/dist/services/companies.js.map +1 -1
- package/dist/services/company-artifacts.d.ts +4 -1
- package/dist/services/company-artifacts.d.ts.map +1 -1
- package/dist/services/company-artifacts.js +9 -1
- package/dist/services/company-artifacts.js.map +1 -1
- package/dist/services/company-member-roles.d.ts.map +1 -1
- package/dist/services/company-member-roles.js +2 -0
- package/dist/services/company-member-roles.js.map +1 -1
- package/dist/services/company-portability.d.ts +1 -1
- package/dist/services/company-portability.d.ts.map +1 -1
- package/dist/services/company-portability.js +113 -8
- package/dist/services/company-portability.js.map +1 -1
- package/dist/services/company-search.d.ts.map +1 -1
- package/dist/services/company-search.js +843 -294
- package/dist/services/company-search.js.map +1 -1
- package/dist/services/company-skills.d.ts +64 -3
- package/dist/services/company-skills.d.ts.map +1 -1
- package/dist/services/company-skills.js +1205 -20
- package/dist/services/company-skills.js.map +1 -1
- package/dist/services/costs.d.ts +9 -8
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +9 -2
- package/dist/services/costs.js.map +1 -1
- package/dist/services/dashboard.d.ts +2 -0
- package/dist/services/dashboard.d.ts.map +1 -1
- package/dist/services/dashboard.js +60 -17
- package/dist/services/dashboard.js.map +1 -1
- package/dist/services/database-backup-health.d.ts +34 -0
- package/dist/services/database-backup-health.d.ts.map +1 -0
- package/dist/services/database-backup-health.js +104 -0
- package/dist/services/database-backup-health.js.map +1 -0
- package/dist/services/document-annotations.d.ts +169 -1
- package/dist/services/document-annotations.d.ts.map +1 -1
- package/dist/services/document-annotations.js +272 -1
- package/dist/services/document-annotations.js.map +1 -1
- package/dist/services/documents.d.ts +368 -0
- package/dist/services/documents.d.ts.map +1 -1
- package/dist/services/documents.js +2 -2
- package/dist/services/documents.js.map +1 -1
- package/dist/services/environment-config.d.ts.map +1 -1
- package/dist/services/environment-config.js +6 -0
- package/dist/services/environment-config.js.map +1 -1
- package/dist/services/environment-custom-image-runtime.d.ts +28 -0
- package/dist/services/environment-custom-image-runtime.d.ts.map +1 -1
- package/dist/services/environment-custom-image-runtime.js +106 -7
- package/dist/services/environment-custom-image-runtime.js.map +1 -1
- package/dist/services/environment-custom-images.d.ts +31 -1
- package/dist/services/environment-custom-images.d.ts.map +1 -1
- package/dist/services/environment-custom-images.js +110 -4
- package/dist/services/environment-custom-images.js.map +1 -1
- package/dist/services/environment-execution-target.d.ts.map +1 -1
- package/dist/services/environment-execution-target.js +1 -3
- package/dist/services/environment-execution-target.js.map +1 -1
- package/dist/services/environment-probe.d.ts +1 -0
- package/dist/services/environment-probe.d.ts.map +1 -1
- package/dist/services/environment-probe.js +99 -5
- package/dist/services/environment-probe.js.map +1 -1
- package/dist/services/environments.d.ts +3 -1
- package/dist/services/environments.d.ts.map +1 -1
- package/dist/services/environments.js +98 -3
- package/dist/services/environments.js.map +1 -1
- package/dist/services/execution-workspace-policy.d.ts +29 -3
- package/dist/services/execution-workspace-policy.d.ts.map +1 -1
- package/dist/services/execution-workspace-policy.js +44 -1
- package/dist/services/execution-workspace-policy.js.map +1 -1
- package/dist/services/execution-workspaces.d.ts +64 -1
- package/dist/services/execution-workspaces.d.ts.map +1 -1
- package/dist/services/execution-workspaces.js +634 -3
- package/dist/services/execution-workspaces.js.map +1 -1
- package/dist/services/external-objects.d.ts +26 -26
- package/dist/services/feedback.d.ts +2 -2
- package/dist/services/finance.d.ts +5 -5
- package/dist/services/goals.d.ts +8 -8
- package/dist/services/heartbeat-stop-metadata.d.ts +2 -2
- package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -1
- package/dist/services/heartbeat-stop-metadata.js +4 -0
- package/dist/services/heartbeat-stop-metadata.js.map +1 -1
- package/dist/services/heartbeat-stop-metadata.test.js +9 -0
- package/dist/services/heartbeat-stop-metadata.test.js.map +1 -1
- package/dist/services/heartbeat.d.ts +133 -28
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +1963 -239
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/inbox-dismissals.d.ts +26 -0
- package/dist/services/inbox-dismissals.d.ts.map +1 -1
- package/dist/services/inbox-dismissals.js +33 -18
- package/dist/services/inbox-dismissals.js.map +1 -1
- package/dist/services/index.d.ts +3 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-settings.d.ts +25 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +103 -5
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/issue-continuation-summary.js +1 -1
- package/dist/services/issue-continuation-summary.js.map +1 -1
- package/dist/services/issue-recovery-actions.d.ts +3 -0
- package/dist/services/issue-recovery-actions.d.ts.map +1 -1
- package/dist/services/issue-recovery-actions.js +9 -0
- package/dist/services/issue-recovery-actions.js.map +1 -1
- package/dist/services/issue-rewake-throttle.d.ts +92 -0
- package/dist/services/issue-rewake-throttle.d.ts.map +1 -0
- package/dist/services/issue-rewake-throttle.js +139 -0
- package/dist/services/issue-rewake-throttle.js.map +1 -0
- package/dist/services/issue-thread-interactions.d.ts +8 -1
- package/dist/services/issue-thread-interactions.d.ts.map +1 -1
- package/dist/services/issue-thread-interactions.js +231 -16
- package/dist/services/issue-thread-interactions.js.map +1 -1
- package/dist/services/issue-visibility.d.ts +4 -0
- package/dist/services/issue-visibility.d.ts.map +1 -0
- package/dist/services/issue-visibility.js +9 -0
- package/dist/services/issue-visibility.js.map +1 -0
- package/dist/services/issues.d.ts +225 -105
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +540 -18
- package/dist/services/issues.js.map +1 -1
- package/dist/services/local-service-supervisor.d.ts +3 -0
- package/dist/services/local-service-supervisor.d.ts.map +1 -1
- package/dist/services/local-service-supervisor.js +51 -0
- package/dist/services/local-service-supervisor.js.map +1 -1
- package/dist/services/pipeline-case-outputs.d.ts.map +1 -1
- package/dist/services/pipeline-case-outputs.js +2 -1
- package/dist/services/pipeline-case-outputs.js.map +1 -1
- package/dist/services/pipelines-aggregation.d.ts.map +1 -1
- package/dist/services/pipelines-aggregation.js +3 -2
- package/dist/services/pipelines-aggregation.js.map +1 -1
- package/dist/services/pipelines.d.ts +165 -165
- package/dist/services/pipelines.d.ts.map +1 -1
- package/dist/services/pipelines.js +6 -4
- package/dist/services/pipelines.js.map +1 -1
- package/dist/services/plugin-database.d.ts +2 -2
- package/dist/services/plugin-environment-driver.d.ts +1 -1
- package/dist/services/plugin-host-services.d.ts.map +1 -1
- package/dist/services/plugin-host-services.js +3 -1
- package/dist/services/plugin-host-services.js.map +1 -1
- package/dist/services/plugin-managed-routines.d.ts +1 -0
- package/dist/services/plugin-managed-routines.d.ts.map +1 -1
- package/dist/services/plugin-registry.d.ts +21 -21
- package/dist/services/productivity-review.d.ts +1 -0
- package/dist/services/productivity-review.d.ts.map +1 -1
- package/dist/services/productivity-review.js +6 -5
- package/dist/services/productivity-review.js.map +1 -1
- package/dist/services/projects.d.ts +9 -9
- package/dist/services/recovery/service.d.ts +82 -54
- package/dist/services/recovery/service.d.ts.map +1 -1
- package/dist/services/recovery/service.js +200 -35
- package/dist/services/recovery/service.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.d.ts +1 -0
- package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -1
- package/dist/services/recovery/successful-run-handoff.js +2 -0
- package/dist/services/recovery/successful-run-handoff.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.test.js +20 -0
- package/dist/services/recovery/successful-run-handoff.test.js.map +1 -1
- package/dist/services/resource-memberships.d.ts +13 -10
- package/dist/services/resource-memberships.d.ts.map +1 -1
- package/dist/services/resource-memberships.js +89 -20
- package/dist/services/resource-memberships.js.map +1 -1
- package/dist/services/responsible-user-denial-run-outcomes.d.ts +60 -0
- package/dist/services/responsible-user-denial-run-outcomes.d.ts.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.js +56 -0
- package/dist/services/responsible-user-denial-run-outcomes.js.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.d.ts +2 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.d.ts.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.js +75 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.js.map +1 -0
- package/dist/services/routines.d.ts +30 -8
- package/dist/services/routines.d.ts.map +1 -1
- package/dist/services/routines.js +241 -18
- package/dist/services/routines.js.map +1 -1
- package/dist/services/run-liveness.d.ts.map +1 -1
- package/dist/services/run-liveness.js +21 -0
- package/dist/services/run-liveness.js.map +1 -1
- package/dist/services/run-log-store.d.ts +1 -0
- package/dist/services/run-log-store.d.ts.map +1 -1
- package/dist/services/run-log-store.js +4 -0
- package/dist/services/run-log-store.js.map +1 -1
- package/dist/services/run-scratch.d.ts +42 -0
- package/dist/services/run-scratch.d.ts.map +1 -0
- package/dist/services/run-scratch.js +111 -0
- package/dist/services/run-scratch.js.map +1 -0
- package/dist/services/run-scratch.test.d.ts +2 -0
- package/dist/services/run-scratch.test.d.ts.map +1 -0
- package/dist/services/run-scratch.test.js +98 -0
- package/dist/services/run-scratch.test.js.map +1 -0
- package/dist/services/secrets.d.ts +1261 -64
- package/dist/services/secrets.d.ts.map +1 -1
- package/dist/services/secrets.js +1089 -96
- package/dist/services/secrets.js.map +1 -1
- package/dist/services/task-watchdogs.d.ts +1 -0
- package/dist/services/task-watchdogs.d.ts.map +1 -1
- package/dist/services/task-watchdogs.js +78 -9
- package/dist/services/task-watchdogs.js.map +1 -1
- package/dist/services/work-timeline.d.ts.map +1 -1
- package/dist/services/work-timeline.js +34 -2
- package/dist/services/work-timeline.js.map +1 -1
- package/dist/services/workspace-runtime-read-model.d.ts +30 -29
- package/dist/services/workspace-runtime-read-model.d.ts.map +1 -1
- package/dist/services/workspace-runtime-read-model.js.map +1 -1
- package/dist/services/workspace-runtime.d.ts +59 -13
- package/dist/services/workspace-runtime.d.ts.map +1 -1
- package/dist/services/workspace-runtime.js +1121 -102
- package/dist/services/workspace-runtime.js.map +1 -1
- package/dist/skills-catalog/catalog/bundled/paperclip-operations/reflection-coach/SKILL.md +202 -0
- package/dist/skills-catalog/catalog/bundled/product/paperclip-capsules/SKILL.md +1 -1
- package/dist/skills-catalog/catalog/bundled/product/wireframe/SKILL.md +1 -1
- package/dist/skills-catalog/catalog/optional/content/release-announcement/SKILL.md +90 -0
- package/dist/skills-catalog/catalog/optional/finance/ramp/SKILL.md +98 -0
- package/dist/skills-catalog/generated/catalog.json +84 -12
- package/dist/ui-branding.d.ts +7 -0
- package/dist/ui-branding.d.ts.map +1 -1
- package/dist/ui-branding.js +8 -2
- package/dist/ui-branding.js.map +1 -1
- package/dist/version.d.ts +14 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +114 -3
- package/dist/version.js.map +1 -1
- package/package.json +19 -20
- package/skills/paperclip/SKILL.md +45 -10
- package/skills/paperclip/references/api-reference.md +246 -1
- package/skills/paperclip/references/cases.md +295 -0
- package/skills/paperclip/references/company-skills.md +1 -1
- package/skills/paperclip-board/SKILL.md +3 -4
- package/skills/paperclip-converting-plans-to-tasks/SKILL.md +3 -7
- package/skills/para-memory-files/SKILL.md +3 -7
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BsFz2IiG.js → abnfDiagram-VRR7QNED-Dtgfy1PK.js} +1 -1
- package/ui-dist/assets/{arc-HZhAwbOs.js → arc-BGdvNvs8.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-BteMjuOn.js → architectureDiagram-ZJ3FMSHR-CK19SQDx.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-Iq66uZjf.js → blockDiagram-677ZJIJ3-Kh4N3XG4.js} +1 -1
- package/ui-dist/assets/{browser-ponyfill-BBsuyZJt.js → browser-ponyfill-B3hXtEFW.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-_nlglj7I.js → c4Diagram-LMCZKHZV-CBHpGrgU.js} +1 -1
- package/ui-dist/assets/channel-4fjB8e8N.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-Cvz7WwSQ.js → chunk-2Q5K7J3B-BdHbqi7k.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-LLubniCS.js → chunk-32BRIVSS-DxtzZzpt.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-BOst5hcr.js → chunk-5VM5RSS4-DMKxBbgu.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-D5WNShVm.js → chunk-EX3LRPZG-BaIOPi4v.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-CA07x4av.js → chunk-JWPE2WC7-DGo3MPmr.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-Clfi83rK.js → chunk-MOJQB5TN-BEhQg0uD.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-BhZ6p7YJ.js → chunk-RYQCIY6F-HPJ6zRpJ.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-CdexJGoV.js → chunk-V7JOEXUC-Bl_6cAJD.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-8vjmBhr1.js → chunk-VR4S4FIN-KMjgGCsw.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-CqmCaplj.js → chunk-XXDRQBXY-oWGAIKjr.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CTXxO5l9.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CTXxO5l9.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-CY-zAygY.js → cose-bilkent-JH36ORCC-Ckqse1pH.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-BQSQ1HKr.js → cynefin-VYW2F7L2-DfZ8Hzqi.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-NJCuFrZL.js → cynefinDiagram-TSTJHNR4-BkDQo9oi.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-DAUPIc8g.js → dagre-VKFMJZFB-ClghXQ6E.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-DvMwJlCi.js → diagram-FQU43EPY-D5a0CwpV.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-DPY0Jz_y.js → diagram-G47NLZAW-hVjYR_xl.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-BpopeEhp.js → diagram-NH7WQ7WH-BlDPgD5Z.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-Qpu05z9X.js → diagram-OA4YK3LP-D0iZKIS3.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-DHnvw9XU.js → diagram-WEI45ONY-m6hrfD0J.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-D61S54_7.js → ebnfDiagram-CCIWWBDH-D4SQYLod.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-pplAN_x2.js → erDiagram-Q63AITRT-Byzb6-I5.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-BaBcbyDq.js → flowDiagram-23GEKE2U-CaTUkFO6.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-BOJSII2b.js → ganttDiagram-NO4QXBWP-DmdMQ3ES.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-aYAaO4a2.js → gitGraphDiagram-IHSO6WYX-Bt-1g2CS.js} +1 -1
- package/ui-dist/assets/{index-CZxYajxF.js → index-2Dk9yO0W.js} +1 -1
- package/ui-dist/assets/{index-B8H0SHw1.js → index-8jLpgFNU.js} +1 -1
- package/ui-dist/assets/{index-3EKATXgc.js → index-AP_iimxs.js} +1 -1
- package/ui-dist/assets/{index-C2JgdjPa.js → index-BNJasQY9.js} +1 -1
- package/ui-dist/assets/{index-BaiskB3o.js → index-BO-aNmyX.js} +1 -1
- package/ui-dist/assets/{index-B5YenxAQ.js → index-BPCzCn1Y.js} +1 -1
- package/ui-dist/assets/{index-Da2kqT1M.js → index-BSLnLTb2.js} +1 -1
- package/ui-dist/assets/{index-By0oeEvz.js → index-CCZ2qgHN.js} +1 -1
- package/ui-dist/assets/{index-bUFCSEgv.js → index-CLZXR1k9.js} +1 -1
- package/ui-dist/assets/{index-CJlw4l9L.js → index-CTNfrE1s.js} +1 -1
- package/ui-dist/assets/{index-TmpY6Xh_.js → index-ChNd25s7.js} +1 -1
- package/ui-dist/assets/{index-BRgdHUNR.js → index-CjHiMOEO.js} +1 -1
- package/ui-dist/assets/{index-DrL6pHSJ.js → index-CmeJQpXZ.js} +1 -1
- package/ui-dist/assets/index-CyuYtn7U.css +1 -0
- package/ui-dist/assets/{index-CKKytnOd.js → index-CzZjM8eh.js} +1 -1
- package/ui-dist/assets/{index-Dceamza_.js → index-D-6T0Aft.js} +1 -1
- package/ui-dist/assets/{index-CjDqB1SX.js → index-DG_7V1pG.js} +1 -1
- package/ui-dist/assets/index-DIKcgxCt.js +676 -0
- package/ui-dist/assets/{index-BKVeJ3Jh.js → index-DiUerHmi.js} +1 -1
- package/ui-dist/assets/{index-C-nKjFZh.js → index-NZ7v5Fc2.js} +1 -1
- package/ui-dist/assets/{index-CvPPuNs1.js → index-Py70EvBg.js} +1 -1
- package/ui-dist/assets/{index-LHkZdu0h.js → index-R3aYn0qM.js} +1 -1
- package/ui-dist/assets/{index-BT8b79Bd.js → index-YXpm7gW-.js} +1 -1
- package/ui-dist/assets/{index-CemenVOf.js → index-n4q3gzki.js} +1 -1
- package/ui-dist/assets/{index-D3ODiUup.js → index-vlZ3GoYY.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-C7_90qGq.js → infoDiagram-FWYZ7A6U-jdi5ciVg.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-T-nPPdTt.js → ishikawaDiagram-FXEZZL3T-CMz5VvYp.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-C3hqQN0R.js → journeyDiagram-5HDEW3XC-DK6dk2R-.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-CPoD9DUm.js → kanban-definition-HUTT4EX6-DX5cZL-D.js} +1 -1
- package/ui-dist/assets/{linear-D-S8yr3Z.js → linear-CI96ABKp.js} +1 -1
- package/ui-dist/assets/{mermaid.core-DhvH8shK.js → mermaid.core-DnsEVfbd.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-CxCIKrKo.js → mindmap-definition-LN4V7U3C-BdDCY7hf.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-D4G-PC6N.js → pegDiagram-2B236MQR-DuY4TF-f.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-N4X8UvqY.js → pieDiagram-ENE6RG2P-bgFw7dm3.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CZohoDln.js → quadrantDiagram-ABIIQ3AL-BCZVWyWS.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-fMGQVhwF.js → railroadDiagram-RFXS5EU6-D9V5_Cgc.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-CK0IuUmk.js → requirementDiagram-TGXJPOKE-CItsya2r.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-pSfb3CqG.js → sankeyDiagram-HTMAVEWB-Cz9IHsZO.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CbhkqIeq.js → sequenceDiagram-DBY2YBRQ-hMSqTiwK.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-B6vqqPx-.js → sizeCapture-X5ZJPWSS-0g9M5wMf.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-DIJf9h5C.js → stateDiagram-2N3HPSRC-F8_YhgWl.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-fZImUBtq.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-DIX_LV8O.js → swimlanes-5IMT3BWC-CCZqPbpE.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-9C1UT51R.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-CD-Pxb_g.js → timeline-definition-FHXFAJF6-BeUIr18x.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-BAZsWL0R.js → vennDiagram-L72KCM5P-D3d5h_G4.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-CnNyfwX5.js → wardleyDiagram-EHGQE667-Bzt-As9A.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-j9YgsD3Y.js → xychartDiagram-FW5EYKEG-C2mly0xf.js} +1 -1
- package/ui-dist/fonts/InterVariable-Italic.woff2 +0 -0
- package/ui-dist/fonts/InterVariable.woff2 +0 -0
- package/ui-dist/fonts/NOTICE.md +17 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/locales/en/common.json +1142 -5
- package/ui-dist/locales/zh-CN/common.json +1140 -3
- package/ui-dist/assets/channel-CsiI2LYw.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CBn1Jqo8.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CBn1Jqo8.js +0 -1
- package/ui-dist/assets/index-BsL8K1vL.css +0 -1
- package/ui-dist/assets/index-D__fTsb2.js +0 -643
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DpwLD7YL.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Cklauutg.js +0 -8
package/dist/services/secrets.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { and, desc, eq, inArray, like, ne, notInArray, or, sql } from "drizzle-orm";
|
|
3
|
-
import { agents, companySecretBindings, companySecretProviderConfigs, companySecrets, companySecretVersions, environments, heartbeatRuns, issues, projects, routines, secretAccessEvents, } from "@penclipai/db";
|
|
3
|
+
import { agents, companySecretBindings, companySecretProviderConfigs, companySecrets, companySecretVersions, companyMemberships, environments, heartbeatRuns, issues, projects, routines, secretAccessEvents, userSecretDeclarations, userSecretDefinitions, } from "@penclipai/db";
|
|
4
4
|
import { createSecretProviderConfigSchema, deriveProjectUrlKey, envBindingSchema, isUuidLike, normalizeAgentUrlKey, secretProviderConfigPayloadSchema, secretProviderConfigDiscoveryPreviewSchema, updateSecretProviderConfigSchema, } from "@penclipai/shared";
|
|
5
5
|
import { conflict, forbidden, HttpError, notFound, unprocessable } from "../errors.js";
|
|
6
6
|
import { logger } from "../middleware/logger.js";
|
|
7
7
|
import { checkSecretProviders, getSecretProvider, listSecretProviders, } from "../secrets/provider-registry.js";
|
|
8
8
|
import { isSecretProviderClientError } from "../secrets/types.js";
|
|
9
|
-
import { authorizationService } from "./authorization.js";
|
|
9
|
+
import { authorizationDeniedDetails, authorizationService } from "./authorization.js";
|
|
10
10
|
import { findActiveServerAdapter } from "../adapters/index.js";
|
|
11
11
|
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
12
12
|
const SENSITIVE_ENV_KEY_RE = /(api[-_]?key|access[-_]?token|auth(?:_?token)?|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)/i;
|
|
@@ -18,6 +18,21 @@ const COMING_SOON_SECRET_PROVIDERS = new Set([
|
|
|
18
18
|
const FALLBACK_ADAPTER_SCHEMA_SECRET_FIELDS = {
|
|
19
19
|
hermes_gateway: ["apiKey"],
|
|
20
20
|
};
|
|
21
|
+
const USER_SECRET_DEFINITION_KEY_UNIQUE_CONSTRAINT = "user_secret_definitions_company_key_uq";
|
|
22
|
+
const USER_SECRET_VALUE_UNIQUE_CONSTRAINT = "company_secrets_user_definition_owner_uq";
|
|
23
|
+
function isUniqueConstraintViolation(error, constraintName) {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
let current = error;
|
|
26
|
+
while (typeof current === "object" && current !== null && !seen.has(current)) {
|
|
27
|
+
seen.add(current);
|
|
28
|
+
const maybe = current;
|
|
29
|
+
const constraint = maybe.constraint ?? maybe.constraint_name;
|
|
30
|
+
if (maybe.code === "23505" && constraint === constraintName)
|
|
31
|
+
return true;
|
|
32
|
+
current = maybe.cause;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
21
36
|
function remoteProviderHttpError(error, context) {
|
|
22
37
|
if (isSecretProviderClientError(error)) {
|
|
23
38
|
logger.warn({
|
|
@@ -42,10 +57,110 @@ function remoteProviderHttpError(error, context) {
|
|
|
42
57
|
}, "remote secret provider request failed");
|
|
43
58
|
return new HttpError(502, "Remote secret provider request failed.", safeRemoteProviderErrorDetails(null, context));
|
|
44
59
|
}
|
|
60
|
+
function remoteProviderWriteHttpError(error, context) {
|
|
61
|
+
return remoteProviderHttpError(error, {
|
|
62
|
+
companyId: context.companyId,
|
|
63
|
+
provider: context.provider,
|
|
64
|
+
providerConfigId: context.providerConfig?.id ?? context.providerConfigId ?? "deployment-default",
|
|
65
|
+
operation: context.operation,
|
|
66
|
+
providerConfig: context.providerConfig?.config ?? null,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function throwProviderWriteOrReservedRowRollbackError(input) {
|
|
70
|
+
const providerError = remoteProviderWriteHttpError(input.error, input);
|
|
71
|
+
try {
|
|
72
|
+
await input.rollbackReservedRow();
|
|
73
|
+
}
|
|
74
|
+
catch (rollbackError) {
|
|
75
|
+
const providerConfigId = input.providerConfig?.id ?? input.providerConfigId ?? "deployment-default";
|
|
76
|
+
logger.warn({
|
|
77
|
+
err: rollbackError,
|
|
78
|
+
providerErr: providerError,
|
|
79
|
+
companyId: input.companyId,
|
|
80
|
+
provider: input.provider,
|
|
81
|
+
providerConfigId,
|
|
82
|
+
operation: input.operation,
|
|
83
|
+
}, "remote secret provider write failed and reserved secret rollback failed");
|
|
84
|
+
throw new HttpError(500, "Secret create failed and Paperclip could not roll back the local secret reservation.", {
|
|
85
|
+
code: "secret_create_rollback_failed",
|
|
86
|
+
provider: input.provider,
|
|
87
|
+
operation: input.operation,
|
|
88
|
+
providerConfigId,
|
|
89
|
+
providerError: {
|
|
90
|
+
status: providerError.status,
|
|
91
|
+
message: providerError.message,
|
|
92
|
+
details: providerError.details ?? null,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
throw providerError;
|
|
97
|
+
}
|
|
98
|
+
function providerConfigIdentifier(input) {
|
|
99
|
+
return input.providerConfig?.id ?? input.providerConfigId ?? "deployment-default";
|
|
100
|
+
}
|
|
101
|
+
async function deleteLocalSecretCreateReservationOrThrow(input) {
|
|
102
|
+
try {
|
|
103
|
+
await input.db.delete(companySecretVersions).where(eq(companySecretVersions.secretId, input.secretId));
|
|
104
|
+
await input.db.delete(companySecrets).where(eq(companySecrets.id, input.secretId));
|
|
105
|
+
}
|
|
106
|
+
catch (rollbackError) {
|
|
107
|
+
const providerConfigId = providerConfigIdentifier(input);
|
|
108
|
+
logger.warn({
|
|
109
|
+
err: rollbackError,
|
|
110
|
+
companyId: input.companyId,
|
|
111
|
+
provider: input.provider,
|
|
112
|
+
providerConfigId,
|
|
113
|
+
operation: input.operation,
|
|
114
|
+
}, "secret create failed and local reserved secret rollback failed");
|
|
115
|
+
throw new HttpError(500, "Secret create failed and Paperclip could not roll back the local secret reservation.", {
|
|
116
|
+
code: "secret_create_rollback_failed",
|
|
117
|
+
provider: input.provider,
|
|
118
|
+
operation: input.operation,
|
|
119
|
+
providerConfigId,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function throwProviderCleanupFailedAfterCreateRollback(input) {
|
|
124
|
+
const providerConfigId = providerConfigIdentifier(input);
|
|
125
|
+
throw new HttpError(500, "Secret create failed and Paperclip could not clean up the remote provider secret.", {
|
|
126
|
+
code: "secret_create_provider_cleanup_failed",
|
|
127
|
+
provider: input.provider,
|
|
128
|
+
operation: input.operation,
|
|
129
|
+
providerConfigId,
|
|
130
|
+
localCleanupHandle: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
45
133
|
function safeRemoteProviderErrorDetails(error, context) {
|
|
46
134
|
if (context.provider !== "aws_secrets_manager" ||
|
|
47
135
|
context.operation !== "secret_provider_config.discovery.preview") {
|
|
48
|
-
|
|
136
|
+
if (context.provider !== "aws_secrets_manager") {
|
|
137
|
+
return { code: error?.code ?? "provider_error" };
|
|
138
|
+
}
|
|
139
|
+
const details = {
|
|
140
|
+
code: error?.code ?? "provider_error",
|
|
141
|
+
provider: context.provider,
|
|
142
|
+
operation: context.operation,
|
|
143
|
+
providerConfigId: context.providerConfigId,
|
|
144
|
+
};
|
|
145
|
+
const region = safeString(context.providerConfig?.region);
|
|
146
|
+
if (region)
|
|
147
|
+
details.region = region;
|
|
148
|
+
details.credentialPath = "Paperclip server runtime/provider credential path";
|
|
149
|
+
if (error?.code === "access_denied") {
|
|
150
|
+
if (context.operation === "secret.create") {
|
|
151
|
+
details.requiredCapability = "secretsmanager:CreateSecret";
|
|
152
|
+
details.actionableMessage =
|
|
153
|
+
"AWS managed secret creation needs secretsmanager:CreateSecret in the selected region for this provider vault. If the vault config uses a KMS key, the runtime credentials also need KMS write permissions for that key.";
|
|
154
|
+
details.safeAlternative =
|
|
155
|
+
"If the secret already exists in AWS, link it as an external reference instead of creating a Paperclip-managed value.";
|
|
156
|
+
}
|
|
157
|
+
else if (context.operation === "secret.rotate") {
|
|
158
|
+
details.requiredCapability = "secretsmanager:PutSecretValue";
|
|
159
|
+
details.actionableMessage =
|
|
160
|
+
"AWS managed secret rotation needs secretsmanager:PutSecretValue for the selected provider vault and managed secret path.";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return details;
|
|
49
164
|
}
|
|
50
165
|
const details = {
|
|
51
166
|
code: error?.code ?? "provider_error",
|
|
@@ -145,6 +260,15 @@ function canonicalizeBinding(binding) {
|
|
|
145
260
|
if (binding.type === "plain") {
|
|
146
261
|
return { type: "plain", value: String(binding.value) };
|
|
147
262
|
}
|
|
263
|
+
if (binding.type === "user_secret_ref") {
|
|
264
|
+
return {
|
|
265
|
+
type: "user_secret_ref",
|
|
266
|
+
key: binding.key,
|
|
267
|
+
version: binding.version ?? "latest",
|
|
268
|
+
required: binding.required ?? true,
|
|
269
|
+
allowMissingOverride: binding.allowMissingOverride ?? false,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
148
272
|
return {
|
|
149
273
|
type: "secret_ref",
|
|
150
274
|
secretId: binding.secretId,
|
|
@@ -170,6 +294,18 @@ function secretResolutionErrorCode(error) {
|
|
|
170
294
|
}
|
|
171
295
|
if (error.message === "Secret is not active")
|
|
172
296
|
return "secret_inactive";
|
|
297
|
+
if (error.message === "User secret value is not configured")
|
|
298
|
+
return "user_secret_missing";
|
|
299
|
+
if (error.message === "Responsible user is required for user secret resolution") {
|
|
300
|
+
return "responsible_user_missing";
|
|
301
|
+
}
|
|
302
|
+
if (error.message === "User secret definition not found")
|
|
303
|
+
return "user_secret_definition_missing";
|
|
304
|
+
if (error.message === "User secret definition is not active")
|
|
305
|
+
return "user_secret_definition_inactive";
|
|
306
|
+
if (error.message === "User-scoped secrets must be resolved through user secret declarations") {
|
|
307
|
+
return "secret_scope_invalid";
|
|
308
|
+
}
|
|
173
309
|
if (error.message === "Secret version not found")
|
|
174
310
|
return "version_missing";
|
|
175
311
|
if (error.message === "Secret version is not active")
|
|
@@ -183,6 +319,22 @@ function secretResolutionErrorCode(error) {
|
|
|
183
319
|
}
|
|
184
320
|
return "provider_error";
|
|
185
321
|
}
|
|
322
|
+
function missingUserSecretDefinitionRuntimeBinding(entry, context, definition, errorCode) {
|
|
323
|
+
return {
|
|
324
|
+
consumerType: context.consumerType,
|
|
325
|
+
consumerId: context.consumerId,
|
|
326
|
+
configPath: entry.configPath,
|
|
327
|
+
envKey: entry.key,
|
|
328
|
+
bindingType: "user_secret_ref",
|
|
329
|
+
secretId: null,
|
|
330
|
+
secretName: null,
|
|
331
|
+
userSecretDefinitionId: definition?.id ?? null,
|
|
332
|
+
userSecretDefinitionKey: definition?.key ?? entry.binding.key,
|
|
333
|
+
userSecretDefinitionName: definition?.name ?? null,
|
|
334
|
+
responsibleUserId: context.responsibleUserId ?? null,
|
|
335
|
+
errorCode,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
186
338
|
function assertSelectableProviderConfig(config, companyId, provider) {
|
|
187
339
|
if (config.companyId !== companyId)
|
|
188
340
|
throw unprocessable("Provider vault must belong to same company");
|
|
@@ -208,9 +360,54 @@ export function secretService(db) {
|
|
|
208
360
|
return db
|
|
209
361
|
.select()
|
|
210
362
|
.from(companySecrets)
|
|
211
|
-
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.name, name), ne(companySecrets.status, "deleted")))
|
|
363
|
+
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "company"), eq(companySecrets.name, name), ne(companySecrets.status, "deleted")))
|
|
364
|
+
.then((rows) => rows[0] ?? null);
|
|
365
|
+
}
|
|
366
|
+
async function getUserSecretDefinitionById(companyId, definitionId, source = db) {
|
|
367
|
+
return source
|
|
368
|
+
.select()
|
|
369
|
+
.from(userSecretDefinitions)
|
|
370
|
+
.where(and(eq(userSecretDefinitions.companyId, companyId), eq(userSecretDefinitions.id, definitionId)))
|
|
371
|
+
.then((rows) => rows[0] ?? null);
|
|
372
|
+
}
|
|
373
|
+
async function getUserSecretDefinitionByKey(companyId, key, source = db) {
|
|
374
|
+
return source
|
|
375
|
+
.select()
|
|
376
|
+
.from(userSecretDefinitions)
|
|
377
|
+
.where(and(eq(userSecretDefinitions.companyId, companyId), eq(userSecretDefinitions.key, key), ne(userSecretDefinitions.status, "deleted")))
|
|
378
|
+
.then((rows) => rows[0] ?? null);
|
|
379
|
+
}
|
|
380
|
+
async function resolveUserSecretDefinition(companyId, input, source = db) {
|
|
381
|
+
const definition = input.definitionId
|
|
382
|
+
? await getUserSecretDefinitionById(companyId, input.definitionId, source)
|
|
383
|
+
: input.definitionKey
|
|
384
|
+
? await getUserSecretDefinitionByKey(companyId, input.definitionKey, source)
|
|
385
|
+
: null;
|
|
386
|
+
if (!definition || definition.deletedAt || definition.status === "deleted") {
|
|
387
|
+
throw notFound("User secret definition not found");
|
|
388
|
+
}
|
|
389
|
+
if (definition.companyId !== companyId) {
|
|
390
|
+
throw unprocessable("User secret definition must belong to same company");
|
|
391
|
+
}
|
|
392
|
+
return definition;
|
|
393
|
+
}
|
|
394
|
+
async function getUserSecretValue(input) {
|
|
395
|
+
return db
|
|
396
|
+
.select()
|
|
397
|
+
.from(companySecrets)
|
|
398
|
+
.where(and(eq(companySecrets.companyId, input.companyId), eq(companySecrets.scope, "user"), eq(companySecrets.ownerUserId, input.ownerUserId), eq(companySecrets.userSecretDefinitionId, input.definitionId), ne(companySecrets.status, "deleted")))
|
|
212
399
|
.then((rows) => rows[0] ?? null);
|
|
213
400
|
}
|
|
401
|
+
async function getUserSecretValueById(companyId, ownerUserId, secretId) {
|
|
402
|
+
const secret = await getById(secretId);
|
|
403
|
+
if (!secret || secret.status === "deleted" || secret.scope !== "user") {
|
|
404
|
+
throw notFound("User secret value not found");
|
|
405
|
+
}
|
|
406
|
+
if (secret.companyId !== companyId || secret.ownerUserId !== ownerUserId) {
|
|
407
|
+
throw notFound("User secret value not found");
|
|
408
|
+
}
|
|
409
|
+
return secret;
|
|
410
|
+
}
|
|
214
411
|
async function getSecretVersion(secretId, version) {
|
|
215
412
|
return db
|
|
216
413
|
.select()
|
|
@@ -253,8 +450,14 @@ export function secretService(db) {
|
|
|
253
450
|
await db.insert(secretAccessEvents).values({
|
|
254
451
|
companyId: input.companyId,
|
|
255
452
|
secretId: input.secretId,
|
|
453
|
+
userSecretDefinitionId: input.userSecretDefinitionId ?? null,
|
|
454
|
+
secretScope: input.secretScope ?? "company",
|
|
256
455
|
version: input.version,
|
|
257
456
|
provider: input.provider,
|
|
457
|
+
responsibleUserId: input.context.responsibleUserId ?? null,
|
|
458
|
+
credentialOwnerUserId: input.credentialOwnerUserId ?? null,
|
|
459
|
+
credentialSubjectType: input.credentialSubjectType ?? null,
|
|
460
|
+
credentialSubjectId: input.credentialSubjectId ?? null,
|
|
258
461
|
actorType: input.context.actorType ?? "system",
|
|
259
462
|
actorId: input.context.actorId ?? null,
|
|
260
463
|
consumerType: input.context.consumerType,
|
|
@@ -275,6 +478,8 @@ export function secretService(db) {
|
|
|
275
478
|
throw notFound("Secret not found");
|
|
276
479
|
if (secret.companyId !== companyId)
|
|
277
480
|
throw unprocessable("Secret must belong to same company");
|
|
481
|
+
if (secret.scope !== "company")
|
|
482
|
+
throw unprocessable("Secret references require company-scoped secrets");
|
|
278
483
|
return secret;
|
|
279
484
|
}
|
|
280
485
|
async function getProviderConfigById(id) {
|
|
@@ -377,6 +582,11 @@ export function secretService(db) {
|
|
|
377
582
|
throw notFound("Secret not found");
|
|
378
583
|
if (secret.companyId !== companyId)
|
|
379
584
|
throw unprocessable("Secret must belong to same company");
|
|
585
|
+
if (secret.scope !== "company" && !options?.allowUserSecretScope) {
|
|
586
|
+
throw unprocessable("User-scoped secrets must be resolved through user secret declarations", {
|
|
587
|
+
code: "secret_scope_invalid",
|
|
588
|
+
});
|
|
589
|
+
}
|
|
380
590
|
const resolvedVersion = version === "latest" ? secret.latestVersion : version;
|
|
381
591
|
const providerId = secret.provider;
|
|
382
592
|
const configPath = accessContext?.configPath ?? null;
|
|
@@ -421,9 +631,14 @@ export function secretService(db) {
|
|
|
421
631
|
recordAccessEvent({
|
|
422
632
|
companyId,
|
|
423
633
|
secretId: secret.id,
|
|
634
|
+
userSecretDefinitionId: secret.userSecretDefinitionId ?? null,
|
|
635
|
+
secretScope: secret.scope,
|
|
424
636
|
version: resolvedVersion,
|
|
425
637
|
provider: providerId,
|
|
426
638
|
context: accessContext,
|
|
639
|
+
credentialOwnerUserId: secret.ownerUserId ?? null,
|
|
640
|
+
credentialSubjectType: secret.scope === "user" ? "user" : null,
|
|
641
|
+
credentialSubjectId: secret.ownerUserId ?? null,
|
|
427
642
|
outcome: "success",
|
|
428
643
|
}).catch(() => undefined),
|
|
429
644
|
]);
|
|
@@ -447,9 +662,14 @@ export function secretService(db) {
|
|
|
447
662
|
await recordAccessEvent({
|
|
448
663
|
companyId,
|
|
449
664
|
secretId: secret.id,
|
|
665
|
+
userSecretDefinitionId: secret.userSecretDefinitionId ?? null,
|
|
666
|
+
secretScope: secret.scope,
|
|
450
667
|
version: resolvedVersion,
|
|
451
668
|
provider: providerId,
|
|
452
669
|
context: accessContext,
|
|
670
|
+
credentialOwnerUserId: secret.ownerUserId ?? null,
|
|
671
|
+
credentialSubjectType: secret.scope === "user" ? "user" : null,
|
|
672
|
+
credentialSubjectId: secret.ownerUserId ?? null,
|
|
453
673
|
outcome: "failure",
|
|
454
674
|
errorCode,
|
|
455
675
|
}).catch(() => undefined);
|
|
@@ -494,7 +714,7 @@ export function secretService(db) {
|
|
|
494
714
|
resource: { type: "company", companyId },
|
|
495
715
|
});
|
|
496
716
|
if (!decision.allowed) {
|
|
497
|
-
throw forbidden(decision.explanation);
|
|
717
|
+
throw forbidden(decision.explanation, authorizationDeniedDetails(decision));
|
|
498
718
|
}
|
|
499
719
|
return (await resolveSecretValueInternal(companyId, secretId, version, {
|
|
500
720
|
accessContext: context,
|
|
@@ -524,6 +744,10 @@ export function secretService(db) {
|
|
|
524
744
|
normalized[key] = binding;
|
|
525
745
|
continue;
|
|
526
746
|
}
|
|
747
|
+
if (binding.type === "user_secret_ref") {
|
|
748
|
+
normalized[key] = binding;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
527
751
|
await assertSecretInCompany(companyId, binding.secretId);
|
|
528
752
|
normalized[key] = {
|
|
529
753
|
type: "secret_ref",
|
|
@@ -594,6 +818,9 @@ export function secretService(db) {
|
|
|
594
818
|
version: binding.version,
|
|
595
819
|
};
|
|
596
820
|
}
|
|
821
|
+
if (binding.type === "user_secret_ref") {
|
|
822
|
+
throw unprocessable(`${input.key} must be a string, plain binding, or company secret reference`);
|
|
823
|
+
}
|
|
597
824
|
const value = binding.value.trim();
|
|
598
825
|
if (!value)
|
|
599
826
|
return undefined;
|
|
@@ -625,7 +852,7 @@ export function secretService(db) {
|
|
|
625
852
|
const duplicateKey = await db
|
|
626
853
|
.select()
|
|
627
854
|
.from(companySecrets)
|
|
628
|
-
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.key, key), ne(companySecrets.status, "deleted")))
|
|
855
|
+
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "company"), eq(companySecrets.key, key), ne(companySecrets.status, "deleted")))
|
|
629
856
|
.then((rows) => rows[0] ?? null);
|
|
630
857
|
if (duplicateKey)
|
|
631
858
|
throw conflict(`Secret key already exists: ${key}`);
|
|
@@ -946,6 +1173,238 @@ export function secretService(db) {
|
|
|
946
1173
|
assertSelectableProviderConfig(providerConfig, companyId, provider);
|
|
947
1174
|
return { providerConfig, provider, runtimeConfig: toProviderVaultRuntimeConfig(providerConfig) };
|
|
948
1175
|
}
|
|
1176
|
+
async function createUserSecretValueInternal(companyId, ownerUserId, input, actor) {
|
|
1177
|
+
const definition = await resolveUserSecretDefinition(companyId, input);
|
|
1178
|
+
if (definition.status !== "active") {
|
|
1179
|
+
throw unprocessable("User secret definition is not active");
|
|
1180
|
+
}
|
|
1181
|
+
const existing = await getUserSecretValue({
|
|
1182
|
+
companyId,
|
|
1183
|
+
ownerUserId,
|
|
1184
|
+
definitionId: definition.id,
|
|
1185
|
+
});
|
|
1186
|
+
if (existing)
|
|
1187
|
+
throw conflict("User secret value already exists");
|
|
1188
|
+
const providerId = definition.provider;
|
|
1189
|
+
const managedMode = definition.managedMode;
|
|
1190
|
+
if (managedMode === "external_reference" && !input.externalRef?.trim()) {
|
|
1191
|
+
throw unprocessable("External reference user secrets require externalRef");
|
|
1192
|
+
}
|
|
1193
|
+
if (managedMode === "paperclip_managed" && input.externalRef?.trim()) {
|
|
1194
|
+
throw unprocessable("Managed user secrets cannot override externalRef");
|
|
1195
|
+
}
|
|
1196
|
+
if (managedMode === "paperclip_managed" && !input.value?.trim()) {
|
|
1197
|
+
throw unprocessable("Managed user secrets require value");
|
|
1198
|
+
}
|
|
1199
|
+
const providerConfigId = input.providerConfigId === undefined ? definition.providerConfigId : input.providerConfigId;
|
|
1200
|
+
const provider = getSecretProvider(providerId);
|
|
1201
|
+
const providerConfig = await getSelectableRuntimeProviderConfig({
|
|
1202
|
+
companyId,
|
|
1203
|
+
provider: providerId,
|
|
1204
|
+
providerConfigId,
|
|
1205
|
+
});
|
|
1206
|
+
const idSuffix = randomUUID();
|
|
1207
|
+
const key = normalizeSecretKey(`user.${definition.key}.${idSuffix}`);
|
|
1208
|
+
const name = `${definition.name} (${ownerUserId})`;
|
|
1209
|
+
const providerWriteContext = {
|
|
1210
|
+
companyId,
|
|
1211
|
+
secretKey: key,
|
|
1212
|
+
secretName: definition.name,
|
|
1213
|
+
version: 1,
|
|
1214
|
+
};
|
|
1215
|
+
let reservedSecret;
|
|
1216
|
+
try {
|
|
1217
|
+
reservedSecret = await db
|
|
1218
|
+
.insert(companySecrets)
|
|
1219
|
+
.values({
|
|
1220
|
+
companyId,
|
|
1221
|
+
scope: "user",
|
|
1222
|
+
ownerUserId,
|
|
1223
|
+
userSecretDefinitionId: definition.id,
|
|
1224
|
+
key,
|
|
1225
|
+
name,
|
|
1226
|
+
provider: providerId,
|
|
1227
|
+
providerConfigId: providerConfigId ?? null,
|
|
1228
|
+
status: "archived",
|
|
1229
|
+
managedMode,
|
|
1230
|
+
externalRef: null,
|
|
1231
|
+
providerMetadata: definition.providerMetadata ?? null,
|
|
1232
|
+
latestVersion: 0,
|
|
1233
|
+
description: definition.description ?? null,
|
|
1234
|
+
createdByAgentId: actor?.agentId ?? null,
|
|
1235
|
+
createdByUserId: actor?.userId ?? null,
|
|
1236
|
+
})
|
|
1237
|
+
.returning()
|
|
1238
|
+
.then((rows) => rows[0]);
|
|
1239
|
+
}
|
|
1240
|
+
catch (error) {
|
|
1241
|
+
if (isUniqueConstraintViolation(error, USER_SECRET_VALUE_UNIQUE_CONSTRAINT)) {
|
|
1242
|
+
throw conflict("User secret value already exists");
|
|
1243
|
+
}
|
|
1244
|
+
throw error;
|
|
1245
|
+
}
|
|
1246
|
+
let prepared;
|
|
1247
|
+
try {
|
|
1248
|
+
prepared =
|
|
1249
|
+
managedMode === "external_reference"
|
|
1250
|
+
? await provider.linkExternalSecret({
|
|
1251
|
+
externalRef: input.externalRef ?? "",
|
|
1252
|
+
providerVersionRef: input.providerVersionRef ?? null,
|
|
1253
|
+
providerConfig,
|
|
1254
|
+
context: providerWriteContext,
|
|
1255
|
+
})
|
|
1256
|
+
: await provider.createSecret({
|
|
1257
|
+
value: input.value ?? "",
|
|
1258
|
+
externalRef: null,
|
|
1259
|
+
providerConfig,
|
|
1260
|
+
context: providerWriteContext,
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
catch (error) {
|
|
1264
|
+
throw await throwProviderWriteOrReservedRowRollbackError({
|
|
1265
|
+
error,
|
|
1266
|
+
rollbackReservedRow: () => db.delete(companySecrets).where(eq(companySecrets.id, reservedSecret.id)),
|
|
1267
|
+
companyId,
|
|
1268
|
+
provider: provider.id,
|
|
1269
|
+
providerConfigId,
|
|
1270
|
+
providerConfig,
|
|
1271
|
+
operation: "secret.create",
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
try {
|
|
1275
|
+
return await db.transaction(async (tx) => {
|
|
1276
|
+
await tx.insert(companySecretVersions).values({
|
|
1277
|
+
secretId: reservedSecret.id,
|
|
1278
|
+
version: 1,
|
|
1279
|
+
material: prepared.material,
|
|
1280
|
+
valueSha256: prepared.valueSha256,
|
|
1281
|
+
fingerprintSha256: prepared.fingerprintSha256 ?? prepared.valueSha256,
|
|
1282
|
+
providerVersionRef: prepared.providerVersionRef ?? null,
|
|
1283
|
+
status: "current",
|
|
1284
|
+
createdByAgentId: actor?.agentId ?? null,
|
|
1285
|
+
createdByUserId: actor?.userId ?? null,
|
|
1286
|
+
});
|
|
1287
|
+
const secret = await tx
|
|
1288
|
+
.update(companySecrets)
|
|
1289
|
+
.set({
|
|
1290
|
+
status: "active",
|
|
1291
|
+
externalRef: prepared.externalRef,
|
|
1292
|
+
latestVersion: 1,
|
|
1293
|
+
lastRotatedAt: new Date(),
|
|
1294
|
+
updatedAt: new Date(),
|
|
1295
|
+
})
|
|
1296
|
+
.where(eq(companySecrets.id, reservedSecret.id))
|
|
1297
|
+
.returning()
|
|
1298
|
+
.then((rows) => rows[0]);
|
|
1299
|
+
if (!secret)
|
|
1300
|
+
throw notFound("User secret value not found");
|
|
1301
|
+
return secret;
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
catch (error) {
|
|
1305
|
+
if (managedMode === "paperclip_managed") {
|
|
1306
|
+
const cleaned = await cleanupPreparedProviderWrite({
|
|
1307
|
+
provider,
|
|
1308
|
+
prepared,
|
|
1309
|
+
providerConfig,
|
|
1310
|
+
context: providerWriteContext,
|
|
1311
|
+
mode: "delete",
|
|
1312
|
+
operation: "user_secret_value.create_rollback",
|
|
1313
|
+
});
|
|
1314
|
+
if (!cleaned) {
|
|
1315
|
+
throwProviderCleanupFailedAfterCreateRollback({
|
|
1316
|
+
companyId,
|
|
1317
|
+
provider: provider.id,
|
|
1318
|
+
providerConfigId,
|
|
1319
|
+
providerConfig,
|
|
1320
|
+
operation: "user_secret_value.create_rollback",
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
await deleteLocalSecretCreateReservationOrThrow({
|
|
1325
|
+
db,
|
|
1326
|
+
secretId: reservedSecret.id,
|
|
1327
|
+
companyId,
|
|
1328
|
+
provider: provider.id,
|
|
1329
|
+
providerConfigId,
|
|
1330
|
+
providerConfig,
|
|
1331
|
+
operation: "user_secret_value.create_rollback",
|
|
1332
|
+
});
|
|
1333
|
+
throw error;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
async function removeSecretInternal(secretId) {
|
|
1337
|
+
const secret = await getById(secretId);
|
|
1338
|
+
if (!secret)
|
|
1339
|
+
return null;
|
|
1340
|
+
const versionRow = await getSecretVersion(secret.id, secret.latestVersion);
|
|
1341
|
+
const providerId = secret.provider;
|
|
1342
|
+
const provider = getSecretProvider(providerId);
|
|
1343
|
+
if (secret.status !== "deleted") {
|
|
1344
|
+
await db
|
|
1345
|
+
.update(companySecrets)
|
|
1346
|
+
.set({
|
|
1347
|
+
key: `${secret.key}__deleted__${secret.id}`,
|
|
1348
|
+
name: `${secret.name}__deleted__${secret.id}`,
|
|
1349
|
+
status: "deleted",
|
|
1350
|
+
deletedAt: secret.deletedAt ?? new Date(),
|
|
1351
|
+
updatedAt: new Date(),
|
|
1352
|
+
})
|
|
1353
|
+
.where(eq(companySecrets.id, secretId));
|
|
1354
|
+
}
|
|
1355
|
+
const providerConfig = secret.providerConfigId
|
|
1356
|
+
? await getProviderConfigById(secret.providerConfigId)
|
|
1357
|
+
: null;
|
|
1358
|
+
const providerRuntimeConfig = providerConfig && providerConfig.status !== "disabled" && providerConfig.status !== "coming_soon"
|
|
1359
|
+
? toProviderVaultRuntimeConfig(providerConfig)
|
|
1360
|
+
: null;
|
|
1361
|
+
if (!secret.providerConfigId || providerRuntimeConfig) {
|
|
1362
|
+
try {
|
|
1363
|
+
await provider.deleteOrArchive({
|
|
1364
|
+
material: versionRow?.material,
|
|
1365
|
+
externalRef: secret.externalRef,
|
|
1366
|
+
providerConfig: providerRuntimeConfig,
|
|
1367
|
+
context: {
|
|
1368
|
+
companyId: secret.companyId,
|
|
1369
|
+
secretKey: secret.key,
|
|
1370
|
+
secretName: secret.name,
|
|
1371
|
+
version: secret.latestVersion,
|
|
1372
|
+
},
|
|
1373
|
+
mode: "delete",
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
catch (error) {
|
|
1377
|
+
if (!isSecretProviderClientError(error) || error.code !== "not_found") {
|
|
1378
|
+
throw error;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
await db.delete(companySecrets).where(eq(companySecrets.id, secretId));
|
|
1383
|
+
return secret;
|
|
1384
|
+
}
|
|
1385
|
+
async function removeUserSecretDefinitionInternal(companyId, definitionId, actor) {
|
|
1386
|
+
const existing = await resolveUserSecretDefinition(companyId, { definitionId });
|
|
1387
|
+
const values = await db
|
|
1388
|
+
.select({ id: companySecrets.id })
|
|
1389
|
+
.from(companySecrets)
|
|
1390
|
+
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "user"), eq(companySecrets.userSecretDefinitionId, definitionId)));
|
|
1391
|
+
for (const value of values) {
|
|
1392
|
+
await removeSecretInternal(value.id);
|
|
1393
|
+
}
|
|
1394
|
+
return db
|
|
1395
|
+
.update(userSecretDefinitions)
|
|
1396
|
+
.set({
|
|
1397
|
+
key: `${existing.key}__deleted__${existing.id}`,
|
|
1398
|
+
status: "deleted",
|
|
1399
|
+
deletedAt: existing.deletedAt ?? new Date(),
|
|
1400
|
+
updatedByAgentId: actor?.agentId ?? null,
|
|
1401
|
+
updatedByUserId: actor?.userId ?? null,
|
|
1402
|
+
updatedAt: new Date(),
|
|
1403
|
+
})
|
|
1404
|
+
.where(and(eq(userSecretDefinitions.companyId, companyId), eq(userSecretDefinitions.id, definitionId)))
|
|
1405
|
+
.returning()
|
|
1406
|
+
.then((rows) => rows[0] ?? null);
|
|
1407
|
+
}
|
|
949
1408
|
return {
|
|
950
1409
|
listProviders: () => listSecretProviders(),
|
|
951
1410
|
checkProviders: () => checkSecretProviders(),
|
|
@@ -1159,7 +1618,7 @@ export function secretService(db) {
|
|
|
1159
1618
|
db
|
|
1160
1619
|
.select()
|
|
1161
1620
|
.from(companySecrets)
|
|
1162
|
-
.where(and(eq(companySecrets.companyId, companyId), ne(companySecrets.status, "deleted")))
|
|
1621
|
+
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "company"), ne(companySecrets.status, "deleted")))
|
|
1163
1622
|
.orderBy(desc(companySecrets.createdAt)),
|
|
1164
1623
|
db
|
|
1165
1624
|
.select({
|
|
@@ -1201,6 +1660,270 @@ export function secretService(db) {
|
|
|
1201
1660
|
.from(secretAccessEvents)
|
|
1202
1661
|
.where(and(eq(secretAccessEvents.companyId, companyId), eq(secretAccessEvents.secretId, secretId)))
|
|
1203
1662
|
.orderBy(desc(secretAccessEvents.createdAt)),
|
|
1663
|
+
listUserSecretDefinitions: (companyId) => db
|
|
1664
|
+
.select()
|
|
1665
|
+
.from(userSecretDefinitions)
|
|
1666
|
+
.where(and(eq(userSecretDefinitions.companyId, companyId), ne(userSecretDefinitions.status, "deleted")))
|
|
1667
|
+
.orderBy(desc(userSecretDefinitions.createdAt)),
|
|
1668
|
+
getUserSecretDefinitionById: (companyId, definitionId) => getUserSecretDefinitionById(companyId, definitionId),
|
|
1669
|
+
createUserSecretDefinition: async (companyId, input, actor) => {
|
|
1670
|
+
const key = input.key.trim();
|
|
1671
|
+
const duplicate = await getUserSecretDefinitionByKey(companyId, key);
|
|
1672
|
+
if (duplicate)
|
|
1673
|
+
throw conflict(`User secret definition already exists: ${key}`);
|
|
1674
|
+
await assertProviderConfigForSecret(companyId, input.provider, input.providerConfigId);
|
|
1675
|
+
try {
|
|
1676
|
+
return await db
|
|
1677
|
+
.insert(userSecretDefinitions)
|
|
1678
|
+
.values({
|
|
1679
|
+
companyId,
|
|
1680
|
+
key,
|
|
1681
|
+
name: input.name.trim(),
|
|
1682
|
+
description: input.description ?? null,
|
|
1683
|
+
status: input.status ?? "active",
|
|
1684
|
+
provider: input.provider,
|
|
1685
|
+
providerConfigId: input.providerConfigId ?? null,
|
|
1686
|
+
managedMode: input.managedMode ?? "paperclip_managed",
|
|
1687
|
+
providerMetadata: input.providerMetadata ?? null,
|
|
1688
|
+
usageGuidance: input.usageGuidance ?? null,
|
|
1689
|
+
createdByAgentId: actor?.agentId ?? null,
|
|
1690
|
+
createdByUserId: actor?.userId ?? null,
|
|
1691
|
+
updatedByAgentId: actor?.agentId ?? null,
|
|
1692
|
+
updatedByUserId: actor?.userId ?? null,
|
|
1693
|
+
})
|
|
1694
|
+
.returning()
|
|
1695
|
+
.then((rows) => rows[0]);
|
|
1696
|
+
}
|
|
1697
|
+
catch (error) {
|
|
1698
|
+
if (isUniqueConstraintViolation(error, USER_SECRET_DEFINITION_KEY_UNIQUE_CONSTRAINT)) {
|
|
1699
|
+
throw conflict(`User secret definition already exists: ${key}`);
|
|
1700
|
+
}
|
|
1701
|
+
throw error;
|
|
1702
|
+
}
|
|
1703
|
+
},
|
|
1704
|
+
updateUserSecretDefinition: async (companyId, definitionId, patch, actor) => {
|
|
1705
|
+
const existing = await resolveUserSecretDefinition(companyId, { definitionId });
|
|
1706
|
+
if (patch.status === "deleted") {
|
|
1707
|
+
return removeUserSecretDefinitionInternal(companyId, existing.id, actor);
|
|
1708
|
+
}
|
|
1709
|
+
const nextKey = patch.key?.trim() ?? existing.key;
|
|
1710
|
+
if (nextKey !== existing.key) {
|
|
1711
|
+
const duplicate = await getUserSecretDefinitionByKey(companyId, nextKey);
|
|
1712
|
+
if (duplicate && duplicate.id !== existing.id) {
|
|
1713
|
+
throw conflict(`User secret definition already exists: ${nextKey}`);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
if (patch.providerConfigId !== undefined) {
|
|
1717
|
+
await assertProviderConfigForSecret(companyId, existing.provider, patch.providerConfigId);
|
|
1718
|
+
}
|
|
1719
|
+
return db
|
|
1720
|
+
.update(userSecretDefinitions)
|
|
1721
|
+
.set({
|
|
1722
|
+
key: nextKey,
|
|
1723
|
+
name: patch.name?.trim() ?? existing.name,
|
|
1724
|
+
description: patch.description === undefined ? existing.description : patch.description,
|
|
1725
|
+
status: patch.status ?? existing.status,
|
|
1726
|
+
providerConfigId: patch.providerConfigId === undefined ? existing.providerConfigId : patch.providerConfigId,
|
|
1727
|
+
providerMetadata: patch.providerMetadata === undefined ? existing.providerMetadata : patch.providerMetadata,
|
|
1728
|
+
usageGuidance: patch.usageGuidance === undefined ? existing.usageGuidance : patch.usageGuidance,
|
|
1729
|
+
updatedByAgentId: actor?.agentId ?? null,
|
|
1730
|
+
updatedByUserId: actor?.userId ?? null,
|
|
1731
|
+
deletedAt: existing.deletedAt,
|
|
1732
|
+
updatedAt: new Date(),
|
|
1733
|
+
})
|
|
1734
|
+
.where(and(eq(userSecretDefinitions.companyId, companyId), eq(userSecretDefinitions.id, definitionId)))
|
|
1735
|
+
.returning()
|
|
1736
|
+
.then((rows) => rows[0] ?? null);
|
|
1737
|
+
},
|
|
1738
|
+
removeUserSecretDefinition: async (companyId, definitionId, actor) => removeUserSecretDefinitionInternal(companyId, definitionId, actor),
|
|
1739
|
+
getUserSecretDefinitionCoverage: async (companyId, definitionId) => {
|
|
1740
|
+
await resolveUserSecretDefinition(companyId, { definitionId });
|
|
1741
|
+
const [members, values] = await Promise.all([
|
|
1742
|
+
db
|
|
1743
|
+
.select({ principalId: companyMemberships.principalId })
|
|
1744
|
+
.from(companyMemberships)
|
|
1745
|
+
.where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.principalType, "user"), eq(companyMemberships.status, "active"))),
|
|
1746
|
+
db
|
|
1747
|
+
.select({ status: companySecrets.status, ownerUserId: companySecrets.ownerUserId })
|
|
1748
|
+
.from(companySecrets)
|
|
1749
|
+
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "user"), eq(companySecrets.userSecretDefinitionId, definitionId), ne(companySecrets.status, "deleted"))),
|
|
1750
|
+
]);
|
|
1751
|
+
const memberIds = new Set(members.map((member) => member.principalId));
|
|
1752
|
+
const configuredCount = values.filter((value) => value.status === "active" && value.ownerUserId && memberIds.has(value.ownerUserId)).length;
|
|
1753
|
+
const inactiveCount = values.filter((value) => value.status !== "active" && value.ownerUserId && memberIds.has(value.ownerUserId)).length;
|
|
1754
|
+
return {
|
|
1755
|
+
definitionId,
|
|
1756
|
+
configuredCount,
|
|
1757
|
+
inactiveCount,
|
|
1758
|
+
missingCount: Math.max(0, memberIds.size - configuredCount - inactiveCount),
|
|
1759
|
+
};
|
|
1760
|
+
},
|
|
1761
|
+
listCurrentUserSecretValues: async (companyId, ownerUserId) => {
|
|
1762
|
+
const definitions = await db
|
|
1763
|
+
.select()
|
|
1764
|
+
.from(userSecretDefinitions)
|
|
1765
|
+
.where(and(eq(userSecretDefinitions.companyId, companyId), ne(userSecretDefinitions.status, "deleted")))
|
|
1766
|
+
.orderBy(desc(userSecretDefinitions.createdAt));
|
|
1767
|
+
const values = await db
|
|
1768
|
+
.select()
|
|
1769
|
+
.from(companySecrets)
|
|
1770
|
+
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "user"), eq(companySecrets.ownerUserId, ownerUserId), ne(companySecrets.status, "deleted")));
|
|
1771
|
+
const valuesByDefinitionId = new Map(values.map((value) => [value.userSecretDefinitionId, value]));
|
|
1772
|
+
return definitions.map((definition) => ({
|
|
1773
|
+
definition,
|
|
1774
|
+
secret: valuesByDefinitionId.get(definition.id) ?? null,
|
|
1775
|
+
}));
|
|
1776
|
+
},
|
|
1777
|
+
createCurrentUserSecretValue: createUserSecretValueInternal,
|
|
1778
|
+
rotateCurrentUserSecretValue: async (companyId, ownerUserId, secretId, input, actor) => {
|
|
1779
|
+
const secret = await getUserSecretValueById(companyId, ownerUserId, secretId);
|
|
1780
|
+
return await (async () => {
|
|
1781
|
+
await resolveUserSecretDefinition(companyId, { definitionId: secret.userSecretDefinitionId });
|
|
1782
|
+
return (await secretService(db).rotate(secret.id, input, actor));
|
|
1783
|
+
})();
|
|
1784
|
+
},
|
|
1785
|
+
updateCurrentUserSecretValue: async (companyId, ownerUserId, secretId, patch, actor) => {
|
|
1786
|
+
const secret = await getUserSecretValueById(companyId, ownerUserId, secretId);
|
|
1787
|
+
if (patch.value != null ||
|
|
1788
|
+
patch.externalRef != null ||
|
|
1789
|
+
patch.providerVersionRef != null ||
|
|
1790
|
+
patch.providerConfigId != null) {
|
|
1791
|
+
return await secretService(db).rotateCurrentUserSecretValue(companyId, ownerUserId, secret.id, patch, actor);
|
|
1792
|
+
}
|
|
1793
|
+
if (patch.status === "deleted") {
|
|
1794
|
+
return await secretService(db).removeCurrentUserSecretValue(companyId, ownerUserId, secret.id);
|
|
1795
|
+
}
|
|
1796
|
+
return db
|
|
1797
|
+
.update(companySecrets)
|
|
1798
|
+
.set({
|
|
1799
|
+
status: patch.status ?? secret.status,
|
|
1800
|
+
updatedAt: new Date(),
|
|
1801
|
+
})
|
|
1802
|
+
.where(eq(companySecrets.id, secret.id))
|
|
1803
|
+
.returning()
|
|
1804
|
+
.then((rows) => rows[0] ?? null);
|
|
1805
|
+
},
|
|
1806
|
+
removeCurrentUserSecretValue: async (companyId, ownerUserId, secretId) => {
|
|
1807
|
+
const secret = await getUserSecretValueById(companyId, ownerUserId, secretId);
|
|
1808
|
+
return await secretService(db).remove(secret.id);
|
|
1809
|
+
},
|
|
1810
|
+
syncUserSecretDeclarationsForTarget: async (companyId, target, refs, options) => {
|
|
1811
|
+
const targetDb = options?.db ?? db;
|
|
1812
|
+
const normalizedRefs = [];
|
|
1813
|
+
for (const ref of refs) {
|
|
1814
|
+
const definition = await resolveUserSecretDefinition(companyId, { definitionKey: ref.definitionKey }, targetDb);
|
|
1815
|
+
normalizedRefs.push({
|
|
1816
|
+
definitionId: definition.id,
|
|
1817
|
+
configPath: ref.configPath,
|
|
1818
|
+
envKey: ref.envKey,
|
|
1819
|
+
versionSelector: ref.versionSelector ?? "latest",
|
|
1820
|
+
required: ref.required ?? true,
|
|
1821
|
+
allowMissingOverride: ref.allowMissingOverride ?? false,
|
|
1822
|
+
label: ref.label ?? null,
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
const pathPrefix = target.pathPrefix ?? "env";
|
|
1826
|
+
const writeDeclarations = async (executor) => {
|
|
1827
|
+
if (options?.replaceAll) {
|
|
1828
|
+
await executor
|
|
1829
|
+
.delete(userSecretDeclarations)
|
|
1830
|
+
.where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.targetType, target.targetType), eq(userSecretDeclarations.targetId, target.targetId)));
|
|
1831
|
+
}
|
|
1832
|
+
else {
|
|
1833
|
+
await executor
|
|
1834
|
+
.delete(userSecretDeclarations)
|
|
1835
|
+
.where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.targetType, target.targetType), eq(userSecretDeclarations.targetId, target.targetId), like(userSecretDeclarations.configPath, `${pathPrefix}.%`)));
|
|
1836
|
+
}
|
|
1837
|
+
if (normalizedRefs.length === 0)
|
|
1838
|
+
return;
|
|
1839
|
+
await executor.insert(userSecretDeclarations).values(normalizedRefs.map((ref) => ({
|
|
1840
|
+
companyId,
|
|
1841
|
+
userSecretDefinitionId: ref.definitionId,
|
|
1842
|
+
targetType: target.targetType,
|
|
1843
|
+
targetId: target.targetId,
|
|
1844
|
+
configPath: ref.configPath,
|
|
1845
|
+
envKey: ref.envKey,
|
|
1846
|
+
versionSelector: String(ref.versionSelector),
|
|
1847
|
+
required: ref.required,
|
|
1848
|
+
allowMissingOverride: ref.allowMissingOverride,
|
|
1849
|
+
label: ref.label,
|
|
1850
|
+
})));
|
|
1851
|
+
};
|
|
1852
|
+
if (options?.db) {
|
|
1853
|
+
await writeDeclarations(targetDb);
|
|
1854
|
+
}
|
|
1855
|
+
else {
|
|
1856
|
+
await db.transaction(async (tx) => writeDeclarations(tx));
|
|
1857
|
+
}
|
|
1858
|
+
return normalizedRefs;
|
|
1859
|
+
},
|
|
1860
|
+
resolveUserSecretValue: async (companyId, input, context) => {
|
|
1861
|
+
const responsibleUserId = input.responsibleUserId ?? context?.responsibleUserId ?? null;
|
|
1862
|
+
const optionalBinding = input.allowMissingOverride || input.required === false;
|
|
1863
|
+
let definition;
|
|
1864
|
+
try {
|
|
1865
|
+
definition = await resolveUserSecretDefinition(companyId, input);
|
|
1866
|
+
}
|
|
1867
|
+
catch (error) {
|
|
1868
|
+
if (optionalBinding && error instanceof HttpError && error.status === 404)
|
|
1869
|
+
return null;
|
|
1870
|
+
throw error;
|
|
1871
|
+
}
|
|
1872
|
+
if (definition.status !== "active") {
|
|
1873
|
+
if (optionalBinding)
|
|
1874
|
+
return null;
|
|
1875
|
+
throw unprocessable("User secret definition is not active");
|
|
1876
|
+
}
|
|
1877
|
+
if (!responsibleUserId?.trim()) {
|
|
1878
|
+
if (optionalBinding)
|
|
1879
|
+
return null;
|
|
1880
|
+
throw unprocessable("Responsible user is required for user secret resolution", {
|
|
1881
|
+
code: "responsible_user_missing",
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
let declaration = null;
|
|
1885
|
+
if (context?.configPath) {
|
|
1886
|
+
declaration = await db
|
|
1887
|
+
.select()
|
|
1888
|
+
.from(userSecretDeclarations)
|
|
1889
|
+
.where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.userSecretDefinitionId, definition.id), eq(userSecretDeclarations.targetType, context.consumerType), eq(userSecretDeclarations.targetId, context.consumerId), eq(userSecretDeclarations.configPath, context.configPath)))
|
|
1890
|
+
.then((rows) => rows[0] ?? null);
|
|
1891
|
+
if (!declaration) {
|
|
1892
|
+
if (optionalBinding)
|
|
1893
|
+
return null;
|
|
1894
|
+
throw unprocessable(`User secret is not declared for ${context.consumerType}:${context.consumerId} at ${context.configPath}`, { code: "binding_missing" });
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
if (Array.isArray(context?.allowedBindingIds) &&
|
|
1898
|
+
(!declaration || !context.allowedBindingIds.includes(declaration.id))) {
|
|
1899
|
+
throw unprocessable("User secret declaration is outside the active low-trust boundary", { code: "binding_not_allowed" });
|
|
1900
|
+
}
|
|
1901
|
+
const secret = await getUserSecretValue({
|
|
1902
|
+
companyId,
|
|
1903
|
+
ownerUserId: responsibleUserId,
|
|
1904
|
+
definitionId: definition.id,
|
|
1905
|
+
});
|
|
1906
|
+
if (!secret) {
|
|
1907
|
+
if (optionalBinding)
|
|
1908
|
+
return null;
|
|
1909
|
+
throw unprocessable("User secret value is not configured", {
|
|
1910
|
+
code: "user_secret_missing",
|
|
1911
|
+
definitionId: definition.id,
|
|
1912
|
+
responsibleUserId,
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
const resolution = await resolveSecretValueInternal(companyId, secret.id, input.version ?? "latest", {
|
|
1916
|
+
accessContext: context ? { ...context, responsibleUserId } : undefined,
|
|
1917
|
+
allowUserSecretScope: true,
|
|
1918
|
+
});
|
|
1919
|
+
return {
|
|
1920
|
+
...resolution,
|
|
1921
|
+
manifestEntry: {
|
|
1922
|
+
...resolution.manifestEntry,
|
|
1923
|
+
bindingId: declaration?.id ?? resolution.manifestEntry.bindingId ?? null,
|
|
1924
|
+
},
|
|
1925
|
+
};
|
|
1926
|
+
},
|
|
1204
1927
|
previewRemoteImport: async (companyId, input) => {
|
|
1205
1928
|
const { providerConfig, provider: providerId, runtimeConfig } = await getRemoteImportProviderConfig(companyId, input.providerConfigId);
|
|
1206
1929
|
const provider = getSecretProvider(providerId);
|
|
@@ -1478,7 +2201,7 @@ export function secretService(db) {
|
|
|
1478
2201
|
const duplicateKey = await db
|
|
1479
2202
|
.select()
|
|
1480
2203
|
.from(companySecrets)
|
|
1481
|
-
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.key, key), ne(companySecrets.status, "deleted")))
|
|
2204
|
+
.where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "company"), eq(companySecrets.key, key), ne(companySecrets.status, "deleted")))
|
|
1482
2205
|
.then((rows) => rows[0] ?? null);
|
|
1483
2206
|
if (duplicateKey)
|
|
1484
2207
|
throw conflict(`Secret key already exists: ${key}`);
|
|
@@ -1541,8 +2264,15 @@ export function secretService(db) {
|
|
|
1541
2264
|
});
|
|
1542
2265
|
}
|
|
1543
2266
|
catch (error) {
|
|
1544
|
-
await
|
|
1545
|
-
|
|
2267
|
+
throw await throwProviderWriteOrReservedRowRollbackError({
|
|
2268
|
+
error,
|
|
2269
|
+
rollbackReservedRow: () => db.delete(companySecrets).where(eq(companySecrets.id, reservedSecret.id)),
|
|
2270
|
+
companyId,
|
|
2271
|
+
provider: provider.id,
|
|
2272
|
+
providerConfigId: input.providerConfigId ?? null,
|
|
2273
|
+
providerConfig,
|
|
2274
|
+
operation: "secret.create",
|
|
2275
|
+
});
|
|
1546
2276
|
}
|
|
1547
2277
|
try {
|
|
1548
2278
|
await db
|
|
@@ -1575,15 +2305,25 @@ export function secretService(db) {
|
|
|
1575
2305
|
mode: "delete",
|
|
1576
2306
|
operation: "create.prepare_rollback",
|
|
1577
2307
|
});
|
|
1578
|
-
if (cleaned) {
|
|
1579
|
-
|
|
1580
|
-
|
|
2308
|
+
if (!cleaned) {
|
|
2309
|
+
throwProviderCleanupFailedAfterCreateRollback({
|
|
2310
|
+
companyId,
|
|
2311
|
+
provider: provider.id,
|
|
2312
|
+
providerConfigId: input.providerConfigId ?? null,
|
|
2313
|
+
providerConfig,
|
|
2314
|
+
operation: "create.prepare_rollback",
|
|
2315
|
+
});
|
|
1581
2316
|
}
|
|
1582
2317
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
2318
|
+
await deleteLocalSecretCreateReservationOrThrow({
|
|
2319
|
+
db,
|
|
2320
|
+
secretId: reservedSecret.id,
|
|
2321
|
+
companyId,
|
|
2322
|
+
provider: provider.id,
|
|
2323
|
+
providerConfigId: input.providerConfigId ?? null,
|
|
2324
|
+
providerConfig,
|
|
2325
|
+
operation: "create.prepare_rollback",
|
|
2326
|
+
});
|
|
1587
2327
|
throw error;
|
|
1588
2328
|
}
|
|
1589
2329
|
try {
|
|
@@ -1619,15 +2359,25 @@ export function secretService(db) {
|
|
|
1619
2359
|
mode: "delete",
|
|
1620
2360
|
operation: "create.rollback",
|
|
1621
2361
|
});
|
|
1622
|
-
if (cleaned) {
|
|
1623
|
-
|
|
1624
|
-
|
|
2362
|
+
if (!cleaned) {
|
|
2363
|
+
throwProviderCleanupFailedAfterCreateRollback({
|
|
2364
|
+
companyId,
|
|
2365
|
+
provider: provider.id,
|
|
2366
|
+
providerConfigId: input.providerConfigId ?? null,
|
|
2367
|
+
providerConfig,
|
|
2368
|
+
operation: "create.rollback",
|
|
2369
|
+
});
|
|
1625
2370
|
}
|
|
1626
2371
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
2372
|
+
await deleteLocalSecretCreateReservationOrThrow({
|
|
2373
|
+
db,
|
|
2374
|
+
secretId: reservedSecret.id,
|
|
2375
|
+
companyId,
|
|
2376
|
+
provider: provider.id,
|
|
2377
|
+
providerConfigId: input.providerConfigId ?? null,
|
|
2378
|
+
providerConfig,
|
|
2379
|
+
operation: "create.rollback",
|
|
2380
|
+
});
|
|
1631
2381
|
throw error;
|
|
1632
2382
|
}
|
|
1633
2383
|
},
|
|
@@ -1661,19 +2411,32 @@ export function secretService(db) {
|
|
|
1661
2411
|
secretName: secret.name,
|
|
1662
2412
|
version: nextVersion,
|
|
1663
2413
|
};
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
2414
|
+
let prepared;
|
|
2415
|
+
try {
|
|
2416
|
+
prepared =
|
|
2417
|
+
secret.managedMode === "external_reference"
|
|
2418
|
+
? await provider.linkExternalSecret({
|
|
2419
|
+
externalRef: input.externalRef ?? secret.externalRef ?? "",
|
|
2420
|
+
providerVersionRef: input.providerVersionRef ?? null,
|
|
2421
|
+
providerConfig,
|
|
2422
|
+
context: providerWriteContext,
|
|
2423
|
+
})
|
|
2424
|
+
: await provider.createVersion({
|
|
2425
|
+
value: input.value ?? "",
|
|
2426
|
+
externalRef: secret.externalRef ?? null,
|
|
2427
|
+
providerConfig,
|
|
2428
|
+
context: providerWriteContext,
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
catch (error) {
|
|
2432
|
+
throw remoteProviderWriteHttpError(error, {
|
|
2433
|
+
companyId: secret.companyId,
|
|
2434
|
+
provider: provider.id,
|
|
2435
|
+
providerConfigId,
|
|
1674
2436
|
providerConfig,
|
|
1675
|
-
|
|
2437
|
+
operation: "secret.rotate",
|
|
1676
2438
|
});
|
|
2439
|
+
}
|
|
1677
2440
|
try {
|
|
1678
2441
|
await db.insert(companySecretVersions).values({
|
|
1679
2442
|
secretId: secret.id,
|
|
@@ -1766,7 +2529,7 @@ export function secretService(db) {
|
|
|
1766
2529
|
const duplicateKey = await db
|
|
1767
2530
|
.select()
|
|
1768
2531
|
.from(companySecrets)
|
|
1769
|
-
.where(and(eq(companySecrets.companyId, secret.companyId), eq(companySecrets.key, nextKey), ne(companySecrets.status, "deleted")))
|
|
2532
|
+
.where(and(eq(companySecrets.companyId, secret.companyId), eq(companySecrets.scope, "company"), eq(companySecrets.key, nextKey), ne(companySecrets.status, "deleted")))
|
|
1770
2533
|
.then((rows) => rows[0] ?? null);
|
|
1771
2534
|
if (duplicateKey && duplicateKey.id !== secret.id) {
|
|
1772
2535
|
throw conflict(`Secret key already exists: ${nextKey}`);
|
|
@@ -1892,6 +2655,7 @@ export function secretService(db) {
|
|
|
1892
2655
|
syncEnvBindingsForTarget: async (companyId, target, envValue, options) => {
|
|
1893
2656
|
const record = asRecord(envValue) ?? {};
|
|
1894
2657
|
const refs = [];
|
|
2658
|
+
const userRefs = [];
|
|
1895
2659
|
const pathPrefix = target.pathPrefix ?? "env";
|
|
1896
2660
|
const bindingDb = options?.db ?? db;
|
|
1897
2661
|
for (const [key, rawBinding] of Object.entries(record)) {
|
|
@@ -1899,6 +2663,18 @@ export function secretService(db) {
|
|
|
1899
2663
|
if (!parsed.success)
|
|
1900
2664
|
continue;
|
|
1901
2665
|
const binding = canonicalizeBinding(parsed.data);
|
|
2666
|
+
if (binding.type === "user_secret_ref") {
|
|
2667
|
+
await resolveUserSecretDefinition(companyId, { definitionKey: binding.key }, bindingDb);
|
|
2668
|
+
userRefs.push({
|
|
2669
|
+
definitionKey: binding.key,
|
|
2670
|
+
configPath: `${pathPrefix}.${key}`,
|
|
2671
|
+
envKey: key,
|
|
2672
|
+
versionSelector: binding.version,
|
|
2673
|
+
required: binding.required,
|
|
2674
|
+
allowMissingOverride: binding.allowMissingOverride,
|
|
2675
|
+
});
|
|
2676
|
+
continue;
|
|
2677
|
+
}
|
|
1902
2678
|
if (binding.type !== "secret_ref")
|
|
1903
2679
|
continue;
|
|
1904
2680
|
await assertSecretInCompany(companyId, binding.secretId, bindingDb);
|
|
@@ -1924,63 +2700,42 @@ export function secretService(db) {
|
|
|
1924
2700
|
required: true,
|
|
1925
2701
|
})));
|
|
1926
2702
|
};
|
|
2703
|
+
const writeUserDeclarations = async (targetDb) => {
|
|
2704
|
+
await targetDb
|
|
2705
|
+
.delete(userSecretDeclarations)
|
|
2706
|
+
.where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.targetType, target.targetType), eq(userSecretDeclarations.targetId, target.targetId), like(userSecretDeclarations.configPath, `${pathPrefix}.%`)));
|
|
2707
|
+
if (userRefs.length === 0)
|
|
2708
|
+
return;
|
|
2709
|
+
const definitions = new Map();
|
|
2710
|
+
for (const ref of userRefs) {
|
|
2711
|
+
const definition = await resolveUserSecretDefinition(companyId, { definitionKey: ref.definitionKey }, targetDb);
|
|
2712
|
+
definitions.set(ref.definitionKey, definition.id);
|
|
2713
|
+
}
|
|
2714
|
+
await targetDb.insert(userSecretDeclarations).values(userRefs.map((ref) => ({
|
|
2715
|
+
companyId,
|
|
2716
|
+
userSecretDefinitionId: definitions.get(ref.definitionKey),
|
|
2717
|
+
targetType: target.targetType,
|
|
2718
|
+
targetId: target.targetId,
|
|
2719
|
+
configPath: ref.configPath,
|
|
2720
|
+
envKey: ref.envKey,
|
|
2721
|
+
versionSelector: String(ref.versionSelector),
|
|
2722
|
+
required: ref.required,
|
|
2723
|
+
allowMissingOverride: ref.allowMissingOverride,
|
|
2724
|
+
})));
|
|
2725
|
+
};
|
|
1927
2726
|
if (options?.db) {
|
|
1928
2727
|
await writeBindings(options.db);
|
|
2728
|
+
await writeUserDeclarations(options.db);
|
|
1929
2729
|
}
|
|
1930
2730
|
else {
|
|
1931
|
-
await db.transaction(async (tx) =>
|
|
2731
|
+
await db.transaction(async (tx) => {
|
|
2732
|
+
await writeBindings(tx);
|
|
2733
|
+
await writeUserDeclarations(tx);
|
|
2734
|
+
});
|
|
1932
2735
|
}
|
|
1933
2736
|
return refs;
|
|
1934
2737
|
},
|
|
1935
|
-
remove:
|
|
1936
|
-
const secret = await getById(secretId);
|
|
1937
|
-
if (!secret)
|
|
1938
|
-
return null;
|
|
1939
|
-
const versionRow = await getSecretVersion(secret.id, secret.latestVersion);
|
|
1940
|
-
const providerId = secret.provider;
|
|
1941
|
-
const provider = getSecretProvider(providerId);
|
|
1942
|
-
if (secret.status !== "deleted") {
|
|
1943
|
-
await db
|
|
1944
|
-
.update(companySecrets)
|
|
1945
|
-
.set({
|
|
1946
|
-
key: `${secret.key}__deleted__${secret.id}`,
|
|
1947
|
-
name: `${secret.name}__deleted__${secret.id}`,
|
|
1948
|
-
status: "deleted",
|
|
1949
|
-
deletedAt: secret.deletedAt ?? new Date(),
|
|
1950
|
-
updatedAt: new Date(),
|
|
1951
|
-
})
|
|
1952
|
-
.where(eq(companySecrets.id, secretId));
|
|
1953
|
-
}
|
|
1954
|
-
const providerConfig = secret.providerConfigId
|
|
1955
|
-
? await getProviderConfigById(secret.providerConfigId)
|
|
1956
|
-
: null;
|
|
1957
|
-
const providerRuntimeConfig = providerConfig && providerConfig.status !== "disabled" && providerConfig.status !== "coming_soon"
|
|
1958
|
-
? toProviderVaultRuntimeConfig(providerConfig)
|
|
1959
|
-
: null;
|
|
1960
|
-
if (!secret.providerConfigId || providerRuntimeConfig) {
|
|
1961
|
-
try {
|
|
1962
|
-
await provider.deleteOrArchive({
|
|
1963
|
-
material: versionRow?.material,
|
|
1964
|
-
externalRef: secret.externalRef,
|
|
1965
|
-
providerConfig: providerRuntimeConfig,
|
|
1966
|
-
context: {
|
|
1967
|
-
companyId: secret.companyId,
|
|
1968
|
-
secretKey: secret.key,
|
|
1969
|
-
secretName: secret.name,
|
|
1970
|
-
version: secret.latestVersion,
|
|
1971
|
-
},
|
|
1972
|
-
mode: "delete",
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
1975
|
-
catch (error) {
|
|
1976
|
-
if (!isSecretProviderClientError(error) || error.code !== "not_found") {
|
|
1977
|
-
throw error;
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
await db.delete(companySecrets).where(eq(companySecrets.id, secretId));
|
|
1982
|
-
return secret;
|
|
1983
|
-
},
|
|
2738
|
+
remove: removeSecretInternal,
|
|
1984
2739
|
normalizeAdapterConfigForPersistence: async (companyId, adapterConfig, opts) => normalizeAdapterConfigForPersistenceInternal(companyId, adapterConfig, opts),
|
|
1985
2740
|
normalizeEnvBindingsForPersistence: async (companyId, envValue, opts) => normalizeEnvConfig(companyId, envValue, opts),
|
|
1986
2741
|
normalizeHireApprovalPayloadForPersistence: async (companyId, payload, opts) => {
|
|
@@ -2010,7 +2765,7 @@ export function secretService(db) {
|
|
|
2010
2765
|
if (binding.type === "plain") {
|
|
2011
2766
|
resolved[key] = binding.value;
|
|
2012
2767
|
}
|
|
2013
|
-
else {
|
|
2768
|
+
else if (binding.type === "secret_ref") {
|
|
2014
2769
|
const secretResolution = await resolveSecretValueInternal(companyId, binding.secretId, binding.version, context
|
|
2015
2770
|
? {
|
|
2016
2771
|
bindingContext: { ...context, configPath: `env.${key}` },
|
|
@@ -2021,6 +2776,25 @@ export function secretService(db) {
|
|
|
2021
2776
|
manifest.push(secretResolution.manifestEntry);
|
|
2022
2777
|
secretKeys.add(key);
|
|
2023
2778
|
}
|
|
2779
|
+
else {
|
|
2780
|
+
const secretResolution = await secretService(db).resolveUserSecretValue(companyId, {
|
|
2781
|
+
definitionKey: binding.key,
|
|
2782
|
+
version: binding.version,
|
|
2783
|
+
required: binding.required,
|
|
2784
|
+
allowMissingOverride: binding.allowMissingOverride,
|
|
2785
|
+
}, context
|
|
2786
|
+
? {
|
|
2787
|
+
...context,
|
|
2788
|
+
configPath: `env.${key}`,
|
|
2789
|
+
responsibleUserId: context.responsibleUserId ?? null,
|
|
2790
|
+
}
|
|
2791
|
+
: undefined);
|
|
2792
|
+
if (secretResolution) {
|
|
2793
|
+
resolved[key] = secretResolution.value;
|
|
2794
|
+
manifest.push(secretResolution.manifestEntry);
|
|
2795
|
+
secretKeys.add(key);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2024
2798
|
}
|
|
2025
2799
|
return { env: resolved, secretKeys, manifest };
|
|
2026
2800
|
},
|
|
@@ -2043,7 +2817,20 @@ export function secretService(db) {
|
|
|
2043
2817
|
return [];
|
|
2044
2818
|
return [{ key, configPath: `env.${key}`, secretId: binding.secretId }];
|
|
2045
2819
|
});
|
|
2046
|
-
|
|
2820
|
+
const userSecretRefs = Object.entries(record).flatMap(([key, rawBinding]) => {
|
|
2821
|
+
if (!ENV_KEY_RE.test(key))
|
|
2822
|
+
return [];
|
|
2823
|
+
const parsed = envBindingSchema.safeParse(rawBinding);
|
|
2824
|
+
if (!parsed.success)
|
|
2825
|
+
return [];
|
|
2826
|
+
const binding = canonicalizeBinding(parsed.data);
|
|
2827
|
+
if (binding.type !== "user_secret_ref")
|
|
2828
|
+
return [];
|
|
2829
|
+
if (!binding.required || binding.allowMissingOverride)
|
|
2830
|
+
return [];
|
|
2831
|
+
return [{ key, configPath: `env.${key}`, binding }];
|
|
2832
|
+
});
|
|
2833
|
+
if (secretRefs.length === 0 && userSecretRefs.length === 0)
|
|
2047
2834
|
return [];
|
|
2048
2835
|
const bindingChecks = await Promise.all(secretRefs.map(async (entry) => ({
|
|
2049
2836
|
entry,
|
|
@@ -2058,21 +2845,96 @@ export function secretService(db) {
|
|
|
2058
2845
|
const missingEntries = bindingChecks
|
|
2059
2846
|
.filter((check) => !check.found)
|
|
2060
2847
|
.map((check) => check.entry);
|
|
2061
|
-
if (missingEntries.length === 0)
|
|
2062
|
-
return [];
|
|
2063
2848
|
const secretRows = await Promise.all([...new Set(missingEntries.map((entry) => entry.secretId))].map(async (secretId) => [
|
|
2064
2849
|
secretId,
|
|
2065
2850
|
await getById(secretId).catch(() => null),
|
|
2066
2851
|
]));
|
|
2067
2852
|
const secretsById = new Map(secretRows);
|
|
2068
|
-
|
|
2853
|
+
const missingSecretBindings = missingEntries.map((entry) => ({
|
|
2069
2854
|
consumerType: context.consumerType,
|
|
2070
2855
|
consumerId: context.consumerId,
|
|
2071
2856
|
configPath: entry.configPath,
|
|
2072
2857
|
envKey: entry.key,
|
|
2858
|
+
bindingType: "secret_ref",
|
|
2073
2859
|
secretId: entry.secretId,
|
|
2074
2860
|
secretName: secretsById.get(entry.secretId)?.name ?? null,
|
|
2075
2861
|
}));
|
|
2862
|
+
const missingUserSecretBindings = [];
|
|
2863
|
+
for (const entry of userSecretRefs) {
|
|
2864
|
+
let definition = null;
|
|
2865
|
+
try {
|
|
2866
|
+
definition = await resolveUserSecretDefinition(companyId, { definitionKey: entry.binding.key });
|
|
2867
|
+
}
|
|
2868
|
+
catch {
|
|
2869
|
+
missingUserSecretBindings.push(missingUserSecretDefinitionRuntimeBinding(entry, context, null, "user_secret_definition_missing"));
|
|
2870
|
+
continue;
|
|
2871
|
+
}
|
|
2872
|
+
if (definition.status !== "active") {
|
|
2873
|
+
missingUserSecretBindings.push(missingUserSecretDefinitionRuntimeBinding(entry, context, definition, "user_secret_definition_inactive"));
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
const declaration = await db
|
|
2877
|
+
.select()
|
|
2878
|
+
.from(userSecretDeclarations)
|
|
2879
|
+
.where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.userSecretDefinitionId, definition.id), eq(userSecretDeclarations.targetType, context.consumerType), eq(userSecretDeclarations.targetId, context.consumerId), eq(userSecretDeclarations.configPath, entry.configPath)))
|
|
2880
|
+
.then((rows) => rows[0] ?? null);
|
|
2881
|
+
if (!declaration) {
|
|
2882
|
+
missingUserSecretBindings.push({
|
|
2883
|
+
consumerType: context.consumerType,
|
|
2884
|
+
consumerId: context.consumerId,
|
|
2885
|
+
configPath: entry.configPath,
|
|
2886
|
+
envKey: entry.key,
|
|
2887
|
+
bindingType: "user_secret_ref",
|
|
2888
|
+
secretId: null,
|
|
2889
|
+
secretName: null,
|
|
2890
|
+
userSecretDefinitionId: definition.id,
|
|
2891
|
+
userSecretDefinitionKey: definition.key,
|
|
2892
|
+
userSecretDefinitionName: definition.name,
|
|
2893
|
+
responsibleUserId: context.responsibleUserId ?? null,
|
|
2894
|
+
errorCode: "binding_missing",
|
|
2895
|
+
});
|
|
2896
|
+
continue;
|
|
2897
|
+
}
|
|
2898
|
+
if (!context.responsibleUserId?.trim()) {
|
|
2899
|
+
missingUserSecretBindings.push({
|
|
2900
|
+
consumerType: context.consumerType,
|
|
2901
|
+
consumerId: context.consumerId,
|
|
2902
|
+
configPath: entry.configPath,
|
|
2903
|
+
envKey: entry.key,
|
|
2904
|
+
bindingType: "user_secret_ref",
|
|
2905
|
+
secretId: null,
|
|
2906
|
+
secretName: null,
|
|
2907
|
+
userSecretDefinitionId: definition.id,
|
|
2908
|
+
userSecretDefinitionKey: definition.key,
|
|
2909
|
+
userSecretDefinitionName: definition.name,
|
|
2910
|
+
responsibleUserId: null,
|
|
2911
|
+
errorCode: "responsible_user_missing",
|
|
2912
|
+
});
|
|
2913
|
+
continue;
|
|
2914
|
+
}
|
|
2915
|
+
const secret = await getUserSecretValue({
|
|
2916
|
+
companyId,
|
|
2917
|
+
ownerUserId: context.responsibleUserId,
|
|
2918
|
+
definitionId: definition.id,
|
|
2919
|
+
});
|
|
2920
|
+
if (!secret || secret.status !== "active") {
|
|
2921
|
+
missingUserSecretBindings.push({
|
|
2922
|
+
consumerType: context.consumerType,
|
|
2923
|
+
consumerId: context.consumerId,
|
|
2924
|
+
configPath: entry.configPath,
|
|
2925
|
+
envKey: entry.key,
|
|
2926
|
+
bindingType: "user_secret_ref",
|
|
2927
|
+
secretId: secret?.id ?? null,
|
|
2928
|
+
secretName: null,
|
|
2929
|
+
userSecretDefinitionId: definition.id,
|
|
2930
|
+
userSecretDefinitionKey: definition.key,
|
|
2931
|
+
userSecretDefinitionName: definition.name,
|
|
2932
|
+
responsibleUserId: context.responsibleUserId,
|
|
2933
|
+
errorCode: secret ? "secret_inactive" : "user_secret_missing",
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
return [...missingSecretBindings, ...missingUserSecretBindings];
|
|
2076
2938
|
},
|
|
2077
2939
|
collectMissingAdapterConfigRuntimeBindings: async (companyId, adapterConfig, adapterType, context) => {
|
|
2078
2940
|
const secretFieldKeys = await listAdapterSchemaSecretFieldKeys(adapterType);
|
|
@@ -2085,7 +2947,18 @@ export function secretService(db) {
|
|
|
2085
2947
|
return [];
|
|
2086
2948
|
return [{ key, configPath: key, secretId: binding.secretId }];
|
|
2087
2949
|
});
|
|
2088
|
-
|
|
2950
|
+
const userSecretRefs = secretFieldKeys.flatMap((key) => {
|
|
2951
|
+
const parsed = envBindingSchema.safeParse(adapterConfig[key]);
|
|
2952
|
+
if (!parsed.success)
|
|
2953
|
+
return [];
|
|
2954
|
+
const binding = canonicalizeBinding(parsed.data);
|
|
2955
|
+
if (binding.type !== "user_secret_ref")
|
|
2956
|
+
return [];
|
|
2957
|
+
if (!binding.required || binding.allowMissingOverride)
|
|
2958
|
+
return [];
|
|
2959
|
+
return [{ key, configPath: key, binding }];
|
|
2960
|
+
});
|
|
2961
|
+
if (secretRefs.length === 0 && userSecretRefs.length === 0)
|
|
2089
2962
|
return [];
|
|
2090
2963
|
const bindingChecks = await Promise.all(secretRefs.map(async (entry) => ({
|
|
2091
2964
|
entry,
|
|
@@ -2100,21 +2973,96 @@ export function secretService(db) {
|
|
|
2100
2973
|
const missingEntries = bindingChecks
|
|
2101
2974
|
.filter((check) => !check.found)
|
|
2102
2975
|
.map((check) => check.entry);
|
|
2103
|
-
if (missingEntries.length === 0)
|
|
2104
|
-
return [];
|
|
2105
2976
|
const secretRows = await Promise.all([...new Set(missingEntries.map((entry) => entry.secretId))].map(async (secretId) => [
|
|
2106
2977
|
secretId,
|
|
2107
2978
|
await getById(secretId).catch(() => null),
|
|
2108
2979
|
]));
|
|
2109
2980
|
const secretsById = new Map(secretRows);
|
|
2110
|
-
|
|
2981
|
+
const missingSecretBindings = missingEntries.map((entry) => ({
|
|
2111
2982
|
consumerType: context.consumerType,
|
|
2112
2983
|
consumerId: context.consumerId,
|
|
2113
2984
|
configPath: entry.configPath,
|
|
2114
2985
|
envKey: entry.key,
|
|
2986
|
+
bindingType: "secret_ref",
|
|
2115
2987
|
secretId: entry.secretId,
|
|
2116
2988
|
secretName: secretsById.get(entry.secretId)?.name ?? null,
|
|
2117
2989
|
}));
|
|
2990
|
+
const missingUserSecretBindings = [];
|
|
2991
|
+
for (const entry of userSecretRefs) {
|
|
2992
|
+
let definition = null;
|
|
2993
|
+
try {
|
|
2994
|
+
definition = await resolveUserSecretDefinition(companyId, { definitionKey: entry.binding.key });
|
|
2995
|
+
}
|
|
2996
|
+
catch {
|
|
2997
|
+
missingUserSecretBindings.push(missingUserSecretDefinitionRuntimeBinding(entry, context, null, "user_secret_definition_missing"));
|
|
2998
|
+
continue;
|
|
2999
|
+
}
|
|
3000
|
+
if (definition.status !== "active") {
|
|
3001
|
+
missingUserSecretBindings.push(missingUserSecretDefinitionRuntimeBinding(entry, context, definition, "user_secret_definition_inactive"));
|
|
3002
|
+
continue;
|
|
3003
|
+
}
|
|
3004
|
+
const declaration = await db
|
|
3005
|
+
.select()
|
|
3006
|
+
.from(userSecretDeclarations)
|
|
3007
|
+
.where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.userSecretDefinitionId, definition.id), eq(userSecretDeclarations.targetType, context.consumerType), eq(userSecretDeclarations.targetId, context.consumerId), eq(userSecretDeclarations.configPath, entry.configPath)))
|
|
3008
|
+
.then((rows) => rows[0] ?? null);
|
|
3009
|
+
if (!declaration) {
|
|
3010
|
+
missingUserSecretBindings.push({
|
|
3011
|
+
consumerType: context.consumerType,
|
|
3012
|
+
consumerId: context.consumerId,
|
|
3013
|
+
configPath: entry.configPath,
|
|
3014
|
+
envKey: entry.key,
|
|
3015
|
+
bindingType: "user_secret_ref",
|
|
3016
|
+
secretId: null,
|
|
3017
|
+
secretName: null,
|
|
3018
|
+
userSecretDefinitionId: definition.id,
|
|
3019
|
+
userSecretDefinitionKey: definition.key,
|
|
3020
|
+
userSecretDefinitionName: definition.name,
|
|
3021
|
+
responsibleUserId: context.responsibleUserId ?? null,
|
|
3022
|
+
errorCode: "binding_missing",
|
|
3023
|
+
});
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
3026
|
+
if (!context.responsibleUserId?.trim()) {
|
|
3027
|
+
missingUserSecretBindings.push({
|
|
3028
|
+
consumerType: context.consumerType,
|
|
3029
|
+
consumerId: context.consumerId,
|
|
3030
|
+
configPath: entry.configPath,
|
|
3031
|
+
envKey: entry.key,
|
|
3032
|
+
bindingType: "user_secret_ref",
|
|
3033
|
+
secretId: null,
|
|
3034
|
+
secretName: null,
|
|
3035
|
+
userSecretDefinitionId: definition.id,
|
|
3036
|
+
userSecretDefinitionKey: definition.key,
|
|
3037
|
+
userSecretDefinitionName: definition.name,
|
|
3038
|
+
responsibleUserId: null,
|
|
3039
|
+
errorCode: "responsible_user_missing",
|
|
3040
|
+
});
|
|
3041
|
+
continue;
|
|
3042
|
+
}
|
|
3043
|
+
const secret = await getUserSecretValue({
|
|
3044
|
+
companyId,
|
|
3045
|
+
ownerUserId: context.responsibleUserId,
|
|
3046
|
+
definitionId: definition.id,
|
|
3047
|
+
});
|
|
3048
|
+
if (!secret || secret.status !== "active") {
|
|
3049
|
+
missingUserSecretBindings.push({
|
|
3050
|
+
consumerType: context.consumerType,
|
|
3051
|
+
consumerId: context.consumerId,
|
|
3052
|
+
configPath: entry.configPath,
|
|
3053
|
+
envKey: entry.key,
|
|
3054
|
+
bindingType: "user_secret_ref",
|
|
3055
|
+
secretId: secret?.id ?? null,
|
|
3056
|
+
secretName: null,
|
|
3057
|
+
userSecretDefinitionId: definition.id,
|
|
3058
|
+
userSecretDefinitionKey: definition.key,
|
|
3059
|
+
userSecretDefinitionName: definition.name,
|
|
3060
|
+
responsibleUserId: context.responsibleUserId,
|
|
3061
|
+
errorCode: secret ? "secret_inactive" : "user_secret_missing",
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
return [...missingSecretBindings, ...missingUserSecretBindings];
|
|
2118
3066
|
},
|
|
2119
3067
|
resolveAdapterConfigForRuntime: async (companyId, adapterConfig, context, opts) => {
|
|
2120
3068
|
const resolved = { ...adapterConfig };
|
|
@@ -2139,7 +3087,7 @@ export function secretService(db) {
|
|
|
2139
3087
|
if (binding.type === "plain") {
|
|
2140
3088
|
env[key] = binding.value;
|
|
2141
3089
|
}
|
|
2142
|
-
else {
|
|
3090
|
+
else if (binding.type === "secret_ref") {
|
|
2143
3091
|
const secretResolution = await resolveSecretValueInternal(companyId, binding.secretId, binding.version, context
|
|
2144
3092
|
? {
|
|
2145
3093
|
bindingContext: { ...context, configPath: `env.${key}` },
|
|
@@ -2150,6 +3098,27 @@ export function secretService(db) {
|
|
|
2150
3098
|
manifest.push(secretResolution.manifestEntry);
|
|
2151
3099
|
secretKeys.add(key);
|
|
2152
3100
|
}
|
|
3101
|
+
else {
|
|
3102
|
+
if (opts?.skipUserSecrets)
|
|
3103
|
+
continue;
|
|
3104
|
+
const secretResolution = await secretService(db).resolveUserSecretValue(companyId, {
|
|
3105
|
+
definitionKey: binding.key,
|
|
3106
|
+
version: binding.version,
|
|
3107
|
+
required: binding.required,
|
|
3108
|
+
allowMissingOverride: binding.allowMissingOverride,
|
|
3109
|
+
}, context
|
|
3110
|
+
? {
|
|
3111
|
+
...context,
|
|
3112
|
+
configPath: `env.${key}`,
|
|
3113
|
+
responsibleUserId: context.responsibleUserId ?? null,
|
|
3114
|
+
}
|
|
3115
|
+
: undefined);
|
|
3116
|
+
if (secretResolution) {
|
|
3117
|
+
env[key] = secretResolution.value;
|
|
3118
|
+
manifest.push(secretResolution.manifestEntry);
|
|
3119
|
+
secretKeys.add(key);
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
2153
3122
|
}
|
|
2154
3123
|
resolved.env = env;
|
|
2155
3124
|
}
|
|
@@ -2162,6 +3131,30 @@ export function secretService(db) {
|
|
|
2162
3131
|
const binding = canonicalizeBinding(parsed.data);
|
|
2163
3132
|
if (binding.type === "plain")
|
|
2164
3133
|
continue;
|
|
3134
|
+
if (binding.type === "user_secret_ref") {
|
|
3135
|
+
if (opts?.skipUserSecrets) {
|
|
3136
|
+
delete resolved[key];
|
|
3137
|
+
continue;
|
|
3138
|
+
}
|
|
3139
|
+
const secretResolution = await secretService(db).resolveUserSecretValue(companyId, {
|
|
3140
|
+
definitionKey: binding.key,
|
|
3141
|
+
version: binding.version,
|
|
3142
|
+
required: binding.required,
|
|
3143
|
+
allowMissingOverride: binding.allowMissingOverride,
|
|
3144
|
+
}, context
|
|
3145
|
+
? {
|
|
3146
|
+
...context,
|
|
3147
|
+
configPath: key,
|
|
3148
|
+
responsibleUserId: context.responsibleUserId ?? null,
|
|
3149
|
+
}
|
|
3150
|
+
: undefined);
|
|
3151
|
+
if (secretResolution) {
|
|
3152
|
+
resolved[key] = secretResolution.value;
|
|
3153
|
+
manifest.push(secretResolution.manifestEntry);
|
|
3154
|
+
secretKeys.add(key);
|
|
3155
|
+
}
|
|
3156
|
+
continue;
|
|
3157
|
+
}
|
|
2165
3158
|
const secretResolution = await resolveSecretValueInternal(companyId, binding.secretId, binding.version, context
|
|
2166
3159
|
? {
|
|
2167
3160
|
bindingContext: { ...context, configPath: key },
|