@agent-native/dispatch 0.18.0 → 0.19.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/actions/create-workspace-resource.d.ts +13 -13
- package/dist/actions/delete-workspace-resource.d.ts +13 -13
- package/dist/actions/grant-vault-secrets-to-app.js +1 -1
- package/dist/actions/grant-vault-secrets-to-app.js.map +1 -1
- package/dist/actions/list-vault-audit.js +1 -1
- package/dist/actions/list-vault-audit.js.map +1 -1
- package/dist/actions/list-vault-secret-options.js +2 -9
- package/dist/actions/list-vault-secret-options.js.map +1 -1
- package/dist/actions/list-vault-secrets.js +1 -1
- package/dist/actions/list-vault-secrets.js.map +1 -1
- package/dist/actions/provider-api-register.d.ts +7 -7
- package/dist/actions/set-app-creation-settings.js +1 -1
- package/dist/actions/set-app-creation-settings.js.map +1 -1
- package/dist/actions/set-vault-access-settings.js +1 -1
- package/dist/actions/set-vault-access-settings.js.map +1 -1
- package/dist/actions/sync-vault-to-app.js +1 -1
- package/dist/actions/sync-vault-to-app.js.map +1 -1
- package/dist/actions/view-screen.js +4 -3
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/components/admin-navigation.d.ts +15 -0
- package/dist/components/admin-navigation.d.ts.map +1 -0
- package/dist/components/admin-navigation.js +174 -0
- package/dist/components/admin-navigation.js.map +1 -0
- package/dist/components/automation-run-history-dialog.js +1 -1
- package/dist/components/automation-run-history-dialog.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/Header.d.ts.map +1 -1
- package/dist/components/layout/Header.js +9 -0
- package/dist/components/layout/Header.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +4 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +14 -117
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/layout/workspace-apps-rail.d.ts +5 -0
- package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -0
- package/dist/components/layout/workspace-apps-rail.js +76 -0
- package/dist/components/layout/workspace-apps-rail.js.map +1 -0
- package/dist/hooks/use-navigation-state.js +39 -16
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/lib/automation-display.js +1 -1
- package/dist/lib/automation-display.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +22 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/admin._index.d.ts +5 -0
- package/dist/routes/pages/admin._index.d.ts.map +1 -0
- package/dist/routes/pages/admin._index.js +19 -0
- package/dist/routes/pages/admin._index.js.map +1 -0
- package/dist/routes/pages/admin.d.ts +5 -0
- package/dist/routes/pages/admin.d.ts.map +1 -0
- package/dist/routes/pages/admin.js +10 -0
- package/dist/routes/pages/admin.js.map +1 -0
- package/dist/routes/pages/approval.js +2 -2
- package/dist/routes/pages/approval.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +3 -2
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +2 -1
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/operations.js +3 -3
- package/dist/routes/pages/operations.js.map +1 -1
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +1 -1
- package/dist/routes/pages/settings.js.map +1 -1
- package/dist/routes/pages/team.js +1 -1
- package/dist/routes/pages/team.js.map +1 -1
- package/dist/routes/pages/transactional-email.$appId.$id.d.ts.map +1 -1
- package/dist/routes/pages/transactional-email.$appId.$id.js +5 -2
- package/dist/routes/pages/transactional-email.$appId.$id.js.map +1 -1
- package/dist/routes/pages/transactional-email.d.ts.map +1 -1
- package/dist/routes/pages/transactional-email.js +6 -2
- package/dist/routes/pages/transactional-email.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +20 -16
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/server/lib/app-creation-store.js +2 -2
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/thread-debug-store.d.ts.map +1 -1
- package/dist/server/lib/thread-debug-store.js +2 -1
- package/dist/server/lib/thread-debug-store.js.map +1 -1
- package/dist/server/lib/vault-store.d.ts +15 -0
- package/dist/server/lib/vault-store.d.ts.map +1 -1
- package/dist/server/lib/vault-store.js +110 -17
- package/dist/server/lib/vault-store.js.map +1 -1
- package/package.json +3 -3
- package/src/actions/grant-vault-secrets-to-app.ts +1 -1
- package/src/actions/list-vault-audit.ts +1 -1
- package/src/actions/list-vault-secret-options.ts +2 -9
- package/src/actions/list-vault-secrets.ts +1 -1
- package/src/actions/set-app-creation-settings.ts +1 -1
- package/src/actions/set-vault-access-settings.ts +1 -1
- package/src/actions/sync-vault-to-app.ts +1 -1
- package/src/actions/view-screen.spec.ts +1 -0
- package/src/actions/view-screen.ts +5 -3
- package/src/components/admin-navigation.tsx +278 -0
- package/src/components/automation-run-history-dialog.tsx +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/Header.tsx +8 -0
- package/src/components/layout/Layout.spec.tsx +85 -6
- package/src/components/layout/Layout.tsx +62 -206
- package/src/components/layout/workspace-apps-rail.tsx +154 -0
- package/src/hooks/use-navigation-state.spec.ts +11 -0
- package/src/hooks/use-navigation-state.ts +37 -16
- package/src/lib/automation-display.spec.ts +1 -1
- package/src/lib/automation-display.ts +1 -1
- package/src/routes/index.spec.ts +8 -0
- package/src/routes/index.ts +25 -0
- package/src/routes/pages/admin._index.tsx +39 -0
- package/src/routes/pages/admin.tsx +15 -0
- package/src/routes/pages/approval.tsx +2 -2
- package/src/routes/pages/apps.$appId.tsx +7 -2
- package/src/routes/pages/integrations.tsx +3 -2
- package/src/routes/pages/operations.tsx +3 -3
- package/src/routes/pages/settings.tsx +7 -3
- package/src/routes/pages/team.tsx +1 -1
- package/src/routes/pages/transactional-email.$appId.$id.tsx +9 -2
- package/src/routes/pages/transactional-email.tsx +6 -2
- package/src/routes/pages/vault.tsx +152 -89
- package/src/server/lib/app-creation-store.ts +2 -2
- package/src/server/lib/approval-fencing.spec.ts +69 -0
- package/src/server/lib/thread-debug-store.spec.ts +7 -3
- package/src/server/lib/thread-debug-store.ts +2 -1
- package/src/server/lib/vault-store.spec.ts +59 -0
- package/src/server/lib/vault-store.ts +123 -17
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
+
import { getDbExec } from "@agent-native/core/db";
|
|
2
3
|
import { and, desc, eq, isNull, or, sql } from "@agent-native/core/db/schema";
|
|
3
4
|
import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
|
|
4
5
|
import { deleteAppSecret, listAppSecretsForScope, writeAppSecret, } from "@agent-native/core/secrets";
|
|
@@ -27,6 +28,12 @@ function ctxScope(table, ctx) {
|
|
|
27
28
|
}
|
|
28
29
|
return or(eq(table.ownerEmail, ctx.ownerEmail), eq(table.orgId, ctx.orgId));
|
|
29
30
|
}
|
|
31
|
+
/** Scope an approval operation to the active tenant, never just the owner email. */
|
|
32
|
+
function tenantScope(table, ctx) {
|
|
33
|
+
if (ctx.orgId)
|
|
34
|
+
return eq(table.orgId, ctx.orgId);
|
|
35
|
+
return and(eq(table.ownerEmail, ctx.ownerEmail), isNull(table.orgId));
|
|
36
|
+
}
|
|
30
37
|
/** Build a ctx that scopes to a specific row's owner/org (used when a
|
|
31
38
|
* request approver acts on behalf of the original requester so the
|
|
32
39
|
* created secret lands in the request's org). */
|
|
@@ -85,6 +92,42 @@ export function isTrustedEnvVarSyncAgentUrl(agentUrl) {
|
|
|
85
92
|
function scopedFilter(table) {
|
|
86
93
|
return ctxScope(table, requireVaultCtx());
|
|
87
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Shared vault values and grants are organization administration data. Keep
|
|
97
|
+
* the member path available for request creation and safe key metadata, but
|
|
98
|
+
* fail closed before a member can read or mutate a raw secret.
|
|
99
|
+
*/
|
|
100
|
+
export async function assertCanManageVault() {
|
|
101
|
+
const orgId = currentOrgId();
|
|
102
|
+
if (!orgId)
|
|
103
|
+
return;
|
|
104
|
+
const email = currentOwnerEmail().trim().toLowerCase();
|
|
105
|
+
let role = null;
|
|
106
|
+
try {
|
|
107
|
+
const result = await getDbExec().execute({
|
|
108
|
+
sql: "SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1",
|
|
109
|
+
args: [orgId, email],
|
|
110
|
+
});
|
|
111
|
+
role = result.rows[0]?.role;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// coercion-ok: unknown membership is treated as unauthorized below.
|
|
115
|
+
// A failed membership check must not become an authorization bypass.
|
|
116
|
+
}
|
|
117
|
+
if (role !== "owner" && role !== "admin") {
|
|
118
|
+
throw new Error("Only organization owners and admins can manage the workspace vault.");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export async function canManageVault() {
|
|
122
|
+
try {
|
|
123
|
+
await assertCanManageVault();
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// coercion-ok: the UI must treat an authorization lookup failure as no access.
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
88
131
|
function normalizeCredentialKey(value) {
|
|
89
132
|
return value.trim();
|
|
90
133
|
}
|
|
@@ -108,6 +151,7 @@ export async function getVaultAccessSettings() {
|
|
|
108
151
|
};
|
|
109
152
|
}
|
|
110
153
|
export async function setVaultAccessSettings(input) {
|
|
154
|
+
await assertCanManageVault();
|
|
111
155
|
const scope = vaultAccessScope();
|
|
112
156
|
const next = { mode: parseVaultAccessMode(input.mode) };
|
|
113
157
|
if (scope.scope === "org") {
|
|
@@ -144,6 +188,7 @@ export async function recordVaultAudit(input) {
|
|
|
144
188
|
});
|
|
145
189
|
}
|
|
146
190
|
export async function listVaultAudit(limit = 50) {
|
|
191
|
+
await assertCanManageVault();
|
|
147
192
|
const db = getDb();
|
|
148
193
|
return db
|
|
149
194
|
.select()
|
|
@@ -154,6 +199,7 @@ export async function listVaultAudit(limit = 50) {
|
|
|
154
199
|
}
|
|
155
200
|
// ─── Secrets ──────────────────────────────────────────────────────
|
|
156
201
|
export async function listSecrets() {
|
|
202
|
+
await assertCanManageVault();
|
|
157
203
|
const db = getDb();
|
|
158
204
|
return db
|
|
159
205
|
.select()
|
|
@@ -161,7 +207,23 @@ export async function listSecrets() {
|
|
|
161
207
|
.where(scopedFilter(schema.vaultSecrets))
|
|
162
208
|
.orderBy(desc(schema.vaultSecrets.updatedAt));
|
|
163
209
|
}
|
|
210
|
+
/** Safe metadata for app creation and integration readiness checks. */
|
|
211
|
+
export async function listSecretOptions() {
|
|
212
|
+
const db = getDb();
|
|
213
|
+
return db
|
|
214
|
+
.select({
|
|
215
|
+
id: schema.vaultSecrets.id,
|
|
216
|
+
name: schema.vaultSecrets.name,
|
|
217
|
+
credentialKey: schema.vaultSecrets.credentialKey,
|
|
218
|
+
provider: schema.vaultSecrets.provider,
|
|
219
|
+
description: schema.vaultSecrets.description,
|
|
220
|
+
})
|
|
221
|
+
.from(schema.vaultSecrets)
|
|
222
|
+
.where(scopedFilter(schema.vaultSecrets))
|
|
223
|
+
.orderBy(desc(schema.vaultSecrets.updatedAt));
|
|
224
|
+
}
|
|
164
225
|
export async function getSecret(secretId, ctx) {
|
|
226
|
+
await assertCanManageVault();
|
|
165
227
|
const db = getDb();
|
|
166
228
|
const [row] = await db
|
|
167
229
|
.select()
|
|
@@ -171,6 +233,7 @@ export async function getSecret(secretId, ctx) {
|
|
|
171
233
|
return row ?? null;
|
|
172
234
|
}
|
|
173
235
|
export async function createSecret(input, ctx = requireVaultCtx()) {
|
|
236
|
+
await assertCanManageVault();
|
|
174
237
|
const db = getDb();
|
|
175
238
|
const timestamp = now();
|
|
176
239
|
const credentialKey = normalizeCredentialKey(input.credentialKey);
|
|
@@ -246,6 +309,7 @@ export async function createSecret(input, ctx = requireVaultCtx()) {
|
|
|
246
309
|
return created;
|
|
247
310
|
}
|
|
248
311
|
export async function updateSecret(secretId, input, ctx = requireVaultCtx()) {
|
|
312
|
+
await assertCanManageVault();
|
|
249
313
|
const db = getDb();
|
|
250
314
|
const existing = await getSecret(secretId, ctx);
|
|
251
315
|
if (!existing)
|
|
@@ -328,6 +392,7 @@ export async function updateSecret(secretId, input, ctx = requireVaultCtx()) {
|
|
|
328
392
|
return updated;
|
|
329
393
|
}
|
|
330
394
|
export async function deleteSecret(secretId, ctx = requireVaultCtx()) {
|
|
395
|
+
await assertCanManageVault();
|
|
331
396
|
const db = getDb();
|
|
332
397
|
const existing = await getSecret(secretId, ctx);
|
|
333
398
|
if (!existing)
|
|
@@ -360,6 +425,7 @@ export async function deleteSecret(secretId, ctx = requireVaultCtx()) {
|
|
|
360
425
|
}
|
|
361
426
|
// ─── Grants ──────────────────────────────────────────────────────
|
|
362
427
|
export async function listGrants(filter) {
|
|
428
|
+
await assertCanManageVault();
|
|
363
429
|
const db = getDb();
|
|
364
430
|
const conditions = [scopedFilter(schema.vaultGrants)];
|
|
365
431
|
if (filter?.secretId) {
|
|
@@ -375,6 +441,7 @@ export async function listGrants(filter) {
|
|
|
375
441
|
.orderBy(desc(schema.vaultGrants.updatedAt));
|
|
376
442
|
}
|
|
377
443
|
export async function getGrant(grantId, ctx = requireVaultCtx()) {
|
|
444
|
+
await assertCanManageVault();
|
|
378
445
|
const db = getDb();
|
|
379
446
|
const [row] = await db
|
|
380
447
|
.select()
|
|
@@ -384,6 +451,7 @@ export async function getGrant(grantId, ctx = requireVaultCtx()) {
|
|
|
384
451
|
return row ?? null;
|
|
385
452
|
}
|
|
386
453
|
export async function createGrant(secretId, appId, ctx = requireVaultCtx()) {
|
|
454
|
+
await assertCanManageVault();
|
|
387
455
|
const db = getDb();
|
|
388
456
|
const secret = await getSecret(secretId, ctx);
|
|
389
457
|
if (!secret)
|
|
@@ -453,6 +521,7 @@ export async function createGrant(secretId, appId, ctx = requireVaultCtx()) {
|
|
|
453
521
|
return getGrant(grantId);
|
|
454
522
|
}
|
|
455
523
|
export async function grantSecretsToApp(secretIds, appId, ctx = requireVaultCtx()) {
|
|
524
|
+
await assertCanManageVault();
|
|
456
525
|
const access = await getVaultAccessSettings();
|
|
457
526
|
const uniqueSecretIds = Array.from(new Set(secretIds));
|
|
458
527
|
if (access.mode === "all-apps") {
|
|
@@ -481,6 +550,7 @@ export async function grantSecretsToApp(secretIds, appId, ctx = requireVaultCtx(
|
|
|
481
550
|
return { appId, accessMode: access.mode, created, skipped };
|
|
482
551
|
}
|
|
483
552
|
export async function revokeGrant(grantId, ctx = requireVaultCtx()) {
|
|
553
|
+
await assertCanManageVault();
|
|
484
554
|
const db = getDb();
|
|
485
555
|
const grant = await getGrant(grantId, ctx);
|
|
486
556
|
if (!grant)
|
|
@@ -613,6 +683,7 @@ export async function cleanupSyncedCredentialKeysIfUnused(ctx, candidateKeys) {
|
|
|
613
683
|
}
|
|
614
684
|
// ─── Sync ──────────────────────────────────────────────────────
|
|
615
685
|
export async function syncGrantsToApp(appId, ctx = requireVaultCtx()) {
|
|
686
|
+
await assertCanManageVault();
|
|
616
687
|
const db = getDb();
|
|
617
688
|
const access = await getVaultAccessSettings();
|
|
618
689
|
const agents = await discoverAgents("dispatch");
|
|
@@ -736,7 +807,13 @@ export async function syncGrantsToApp(appId, ctx = requireVaultCtx()) {
|
|
|
736
807
|
// ─── Requests ──────────────────────────────────────────────────────
|
|
737
808
|
export async function listRequests(filter) {
|
|
738
809
|
const db = getDb();
|
|
739
|
-
const
|
|
810
|
+
const ctx = requireVaultCtx();
|
|
811
|
+
const isAdmin = await canManageVault();
|
|
812
|
+
const conditions = [
|
|
813
|
+
isAdmin
|
|
814
|
+
? ctxScope(schema.vaultRequests, ctx)
|
|
815
|
+
: eq(schema.vaultRequests.ownerEmail, ctx.ownerEmail),
|
|
816
|
+
];
|
|
740
817
|
if (filter?.status) {
|
|
741
818
|
conditions.push(eq(schema.vaultRequests.status, filter.status));
|
|
742
819
|
}
|
|
@@ -755,6 +832,15 @@ export async function getRequest(requestId, ctx = requireVaultCtx()) {
|
|
|
755
832
|
.limit(1);
|
|
756
833
|
return row ?? null;
|
|
757
834
|
}
|
|
835
|
+
async function getRequestForTenant(requestId, ctx) {
|
|
836
|
+
const db = getDb();
|
|
837
|
+
const [row] = await db
|
|
838
|
+
.select()
|
|
839
|
+
.from(schema.vaultRequests)
|
|
840
|
+
.where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx)))
|
|
841
|
+
.limit(1);
|
|
842
|
+
return row ?? null;
|
|
843
|
+
}
|
|
758
844
|
export async function createRequest(input) {
|
|
759
845
|
const db = getDb();
|
|
760
846
|
const timestamp = now();
|
|
@@ -784,8 +870,9 @@ export async function createRequest(input) {
|
|
|
784
870
|
return getRequest(requestId);
|
|
785
871
|
}
|
|
786
872
|
export async function approveRequest(requestId, secretValue, secretName, ctx = requireVaultCtx()) {
|
|
873
|
+
await assertCanManageVault();
|
|
787
874
|
const db = getDb();
|
|
788
|
-
const request = await
|
|
875
|
+
const request = await getRequestForTenant(requestId, ctx);
|
|
789
876
|
if (!request)
|
|
790
877
|
throw new Error("Request not found");
|
|
791
878
|
const timestamp = now();
|
|
@@ -804,10 +891,10 @@ export async function approveRequest(requestId, secretValue, secretName, ctx = r
|
|
|
804
891
|
status: "applying",
|
|
805
892
|
updatedAt: timestamp,
|
|
806
893
|
})
|
|
807
|
-
.where(and(eq(schema.vaultRequests.id, requestId),
|
|
894
|
+
.where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx), or(eq(schema.vaultRequests.status, "pending"), and(eq(schema.vaultRequests.status, "applying"), sql `${schema.vaultRequests.updatedAt} < ${staleApplyingBefore}`))))
|
|
808
895
|
.returning();
|
|
809
896
|
if (claimed.length === 0) {
|
|
810
|
-
const current = await
|
|
897
|
+
const current = await getRequestForTenant(requestId, ctx);
|
|
811
898
|
if (current?.status === "applying") {
|
|
812
899
|
throw new Error("Vault request is already being applied");
|
|
813
900
|
}
|
|
@@ -840,7 +927,7 @@ export async function approveRequest(requestId, secretValue, secretName, ctx = r
|
|
|
840
927
|
await db
|
|
841
928
|
.update(schema.vaultRequests)
|
|
842
929
|
.set({ status: "pending", updatedAt: now() })
|
|
843
|
-
.where(and(eq(schema.vaultRequests.id, requestId),
|
|
930
|
+
.where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx), eq(schema.vaultRequests.status, "applying")));
|
|
844
931
|
throw error;
|
|
845
932
|
}
|
|
846
933
|
await db
|
|
@@ -851,18 +938,19 @@ export async function approveRequest(requestId, secretValue, secretName, ctx = r
|
|
|
851
938
|
reviewedAt: timestamp,
|
|
852
939
|
updatedAt: now(),
|
|
853
940
|
})
|
|
854
|
-
.where(and(eq(schema.vaultRequests.id, requestId),
|
|
941
|
+
.where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx), eq(schema.vaultRequests.status, "applying")));
|
|
855
942
|
await recordVaultAudit({
|
|
856
943
|
action: "request.approved",
|
|
857
944
|
appId: claimedRequest.appId,
|
|
858
945
|
summary: `Approved ${claimedRequest.credentialKey} for ${claimedRequest.appId} (requested by ${claimedRequest.requestedBy})`,
|
|
859
946
|
metadata: { requestId, reviewer },
|
|
860
947
|
});
|
|
861
|
-
return
|
|
948
|
+
return getRequestForTenant(requestId, ctx);
|
|
862
949
|
}
|
|
863
950
|
export async function denyRequest(requestId, reason, ctx = requireVaultCtx()) {
|
|
951
|
+
await assertCanManageVault();
|
|
864
952
|
const db = getDb();
|
|
865
|
-
const request = await
|
|
953
|
+
const request = await getRequestForTenant(requestId, ctx);
|
|
866
954
|
if (!request)
|
|
867
955
|
throw new Error("Request not found");
|
|
868
956
|
const timestamp = now();
|
|
@@ -875,10 +963,10 @@ export async function denyRequest(requestId, reason, ctx = requireVaultCtx()) {
|
|
|
875
963
|
reviewedAt: timestamp,
|
|
876
964
|
updatedAt: timestamp,
|
|
877
965
|
})
|
|
878
|
-
.where(and(eq(schema.vaultRequests.id, requestId),
|
|
966
|
+
.where(and(eq(schema.vaultRequests.id, requestId), tenantScope(schema.vaultRequests, ctx), eq(schema.vaultRequests.status, "pending")))
|
|
879
967
|
.returning();
|
|
880
968
|
if (claimed.length === 0) {
|
|
881
|
-
return
|
|
969
|
+
return getRequestForTenant(requestId, ctx);
|
|
882
970
|
}
|
|
883
971
|
const claimedRequest = claimed[0];
|
|
884
972
|
await recordVaultAudit({
|
|
@@ -887,13 +975,13 @@ export async function denyRequest(requestId, reason, ctx = requireVaultCtx()) {
|
|
|
887
975
|
summary: `Denied ${claimedRequest.credentialKey} for ${claimedRequest.appId} (requested by ${claimedRequest.requestedBy})`,
|
|
888
976
|
metadata: { requestId, reviewer, reason },
|
|
889
977
|
});
|
|
890
|
-
return
|
|
978
|
+
return getRequestForTenant(requestId, ctx);
|
|
891
979
|
}
|
|
892
980
|
export async function listIntegrationsCatalog() {
|
|
893
981
|
const access = await getVaultAccessSettings();
|
|
894
982
|
const agents = await discoverAgents("dispatch");
|
|
895
|
-
const grants = await listGrants();
|
|
896
|
-
const secrets = await
|
|
983
|
+
const grants = (await canManageVault()) ? await listGrants() : [];
|
|
984
|
+
const secrets = await listSecretOptions();
|
|
897
985
|
const secretByKey = new Map(secrets.map((s) => [s.credentialKey, s]));
|
|
898
986
|
const results = [];
|
|
899
987
|
for (const agent of agents) {
|
|
@@ -955,9 +1043,10 @@ export async function listIntegrationsCatalog() {
|
|
|
955
1043
|
}
|
|
956
1044
|
// ─── Vault Overview (for dashboard) ──────────────────────────────
|
|
957
1045
|
export async function listVaultOverview() {
|
|
1046
|
+
const isAdmin = await canManageVault();
|
|
958
1047
|
const [secrets, grants, requests, access] = await Promise.all([
|
|
959
|
-
|
|
960
|
-
listGrants(),
|
|
1048
|
+
listSecretOptions(),
|
|
1049
|
+
isAdmin ? listGrants() : Promise.resolve([]),
|
|
961
1050
|
listRequests(),
|
|
962
1051
|
getVaultAccessSettings(),
|
|
963
1052
|
]);
|
|
@@ -965,8 +1054,12 @@ export async function listVaultOverview() {
|
|
|
965
1054
|
return {
|
|
966
1055
|
accessMode: access.mode,
|
|
967
1056
|
secretCount: secrets.length,
|
|
968
|
-
activeGrantCount: access.mode === "all-apps"
|
|
969
|
-
|
|
1057
|
+
activeGrantCount: access.mode === "all-apps"
|
|
1058
|
+
? secrets.length
|
|
1059
|
+
: isAdmin
|
|
1060
|
+
? manualGrantCount
|
|
1061
|
+
: 0,
|
|
1062
|
+
manualGrantCount: isAdmin ? manualGrantCount : 0,
|
|
970
1063
|
pendingRequestCount: requests.filter((r) => r.status === "pending").length,
|
|
971
1064
|
};
|
|
972
1065
|
}
|