@agent-native/dispatch 0.6.1 → 0.7.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/README.md +1 -1
- package/dist/actions/create-pylon-ticket.d.ts +3 -0
- package/dist/actions/create-pylon-ticket.d.ts.map +1 -0
- package/dist/actions/create-pylon-ticket.js +94 -0
- package/dist/actions/create-pylon-ticket.js.map +1 -0
- package/dist/actions/create-vault-grant.js +1 -1
- package/dist/actions/create-vault-grant.js.map +1 -1
- package/dist/actions/create-vault-secret.d.ts.map +1 -1
- package/dist/actions/create-vault-secret.js +4 -3
- package/dist/actions/create-vault-secret.js.map +1 -1
- package/dist/actions/get-vault-access-settings.d.ts +3 -0
- package/dist/actions/get-vault-access-settings.d.ts.map +1 -0
- package/dist/actions/get-vault-access-settings.js +10 -0
- package/dist/actions/get-vault-access-settings.js.map +1 -0
- 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/index.d.ts.map +1 -1
- package/dist/actions/index.js +8 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/list-integrations-catalog.js +1 -1
- package/dist/actions/list-integrations-catalog.js.map +1 -1
- package/dist/actions/list-vault-grants.js +1 -1
- package/dist/actions/list-vault-grants.js.map +1 -1
- package/dist/actions/list-workspace-apps.d.ts.map +1 -1
- package/dist/actions/list-workspace-apps.js +5 -1
- package/dist/actions/list-workspace-apps.js.map +1 -1
- package/dist/actions/set-vault-access-settings.d.ts +3 -0
- package/dist/actions/set-vault-access-settings.d.ts.map +1 -0
- package/dist/actions/set-vault-access-settings.js +13 -0
- package/dist/actions/set-vault-access-settings.js.map +1 -0
- package/dist/actions/start-workspace-app-creation.d.ts.map +1 -1
- package/dist/actions/start-workspace-app-creation.js +6 -0
- package/dist/actions/start-workspace-app-creation.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/update-workspace-app-metadata.d.ts +3 -0
- package/dist/actions/update-workspace-app-metadata.d.ts.map +1 -0
- package/dist/actions/update-workspace-app-metadata.js +30 -0
- package/dist/actions/update-workspace-app-metadata.js.map +1 -0
- package/dist/actions/view-screen.d.ts.map +1 -1
- package/dist/actions/view-screen.js +4 -2
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/components/app-keys-popover.js +16 -5
- package/dist/components/app-keys-popover.js.map +1 -1
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +38 -14
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/dispatch-shell.d.ts +4 -4
- package/dist/components/dispatch-shell.d.ts.map +1 -1
- package/dist/components/dispatch-shell.js +6 -6
- package/dist/components/dispatch-shell.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +10 -3
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/messaging-setup-panel.d.ts.map +1 -1
- package/dist/components/messaging-setup-panel.js +2 -2
- package/dist/components/messaging-setup-panel.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +41 -2
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/hooks/use-navigation-state.js +12 -5
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/lib/catch-all-target.d.ts +2 -0
- package/dist/lib/catch-all-target.d.ts.map +1 -0
- package/dist/lib/catch-all-target.js +95 -0
- package/dist/lib/catch-all-target.js.map +1 -0
- package/dist/lib/workspace-apps.d.ts +9 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/pages/$appId.d.ts +2 -2
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +17 -8
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +20 -15
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/new-app.js +1 -1
- package/dist/routes/pages/new-app.js.map +1 -1
- package/dist/routes/pages/overview.d.ts.map +1 -1
- package/dist/routes/pages/overview.js +5 -1
- package/dist/routes/pages/overview.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +23 -5
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts +13 -0
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +295 -9
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/env-config.d.ts.map +1 -1
- package/dist/server/lib/env-config.js +5 -0
- package/dist/server/lib/env-config.js.map +1 -1
- package/dist/server/lib/onboarding-steps.d.ts +12 -0
- package/dist/server/lib/onboarding-steps.d.ts.map +1 -0
- package/dist/server/lib/onboarding-steps.js +47 -0
- package/dist/server/lib/onboarding-steps.js.map +1 -0
- package/dist/server/lib/vault-store.d.ts +55 -0
- package/dist/server/lib/vault-store.d.ts.map +1 -1
- package/dist/server/lib/vault-store.js +210 -41
- package/dist/server/lib/vault-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.d.ts.map +1 -1
- package/dist/server/plugins/agent-chat.js +2 -1
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/dist/server/plugins/core-routes.d.ts.map +1 -1
- package/dist/server/plugins/core-routes.js +4 -0
- package/dist/server/plugins/core-routes.js.map +1 -1
- package/dist/server/plugins/integrations.js +2 -2
- package/dist/server/plugins/integrations.js.map +1 -1
- package/package.json +13 -11
- package/src/actions/create-pylon-ticket.ts +109 -0
- package/src/actions/create-vault-grant.ts +1 -1
- package/src/actions/create-vault-secret.ts +4 -3
- package/src/actions/get-vault-access-settings.ts +11 -0
- package/src/actions/grant-vault-secrets-to-app.ts +1 -1
- package/src/actions/index.ts +8 -0
- package/src/actions/list-integrations-catalog.ts +1 -1
- package/src/actions/list-vault-grants.ts +1 -1
- package/src/actions/list-workspace-apps.ts +5 -1
- package/src/actions/set-vault-access-settings.ts +16 -0
- package/src/actions/start-workspace-app-creation.ts +8 -0
- package/src/actions/sync-vault-to-app.ts +1 -1
- package/src/actions/update-workspace-app-metadata.ts +32 -0
- package/src/actions/view-screen.ts +4 -1
- package/src/components/app-keys-popover.tsx +23 -7
- package/src/components/create-app-popover.tsx +47 -14
- package/src/components/dispatch-shell.tsx +16 -15
- package/src/components/layout/Layout.tsx +11 -5
- package/src/components/messaging-setup-panel.tsx +54 -39
- package/src/components/workspace-app-card.tsx +102 -0
- package/src/hooks/use-navigation-state.ts +10 -4
- package/src/lib/catch-all-target.spec.ts +218 -0
- package/src/lib/catch-all-target.ts +99 -0
- package/src/lib/workspace-apps.ts +9 -0
- package/src/routes/pages/$appId.tsx +21 -8
- package/src/routes/pages/integrations.tsx +57 -18
- package/src/routes/pages/new-app.tsx +1 -1
- package/src/routes/pages/overview.tsx +11 -3
- package/src/routes/pages/vault.tsx +76 -9
- package/src/server/lib/app-creation-store.spec.ts +61 -2
- package/src/server/lib/app-creation-store.ts +386 -11
- package/src/server/lib/env-config.ts +5 -0
- package/src/server/lib/onboarding-steps.ts +49 -0
- package/src/server/lib/vault-store.spec.ts +69 -0
- package/src/server/lib/vault-store.ts +266 -49
- package/src/server/plugins/agent-chat.ts +2 -1
- package/src/server/plugins/core-routes.ts +5 -0
- package/src/server/plugins/integrations.ts +2 -2
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { type SecretScope } from "@agent-native/core/secrets";
|
|
2
|
+
import { schema } from "../../db/index.js";
|
|
3
|
+
export type VaultAccessMode = "all-apps" | "manual";
|
|
4
|
+
export interface VaultAccessSettings {
|
|
5
|
+
mode: VaultAccessMode;
|
|
6
|
+
scope: "org" | "user";
|
|
7
|
+
scopeId: string;
|
|
8
|
+
}
|
|
1
9
|
/**
|
|
2
10
|
* Caller-supplied access context for vault operations.
|
|
3
11
|
*
|
|
@@ -17,6 +25,10 @@ export interface VaultCtx {
|
|
|
17
25
|
* leaked rows across tenants when a misconfigured environment skipped auth.
|
|
18
26
|
*/
|
|
19
27
|
export declare function requireVaultCtx(): VaultCtx;
|
|
28
|
+
export declare function getVaultAccessSettings(): Promise<VaultAccessSettings>;
|
|
29
|
+
export declare function setVaultAccessSettings(input: {
|
|
30
|
+
mode: VaultAccessMode;
|
|
31
|
+
}): Promise<VaultAccessSettings>;
|
|
20
32
|
export declare function recordVaultAudit(input: {
|
|
21
33
|
action: string;
|
|
22
34
|
secretId?: string | null;
|
|
@@ -149,6 +161,12 @@ export declare function createGrant(secretId: string, appId: string, ctx?: Vault
|
|
|
149
161
|
}>;
|
|
150
162
|
export declare function grantSecretsToApp(secretIds: string[], appId: string, ctx?: VaultCtx): Promise<{
|
|
151
163
|
appId: string;
|
|
164
|
+
accessMode: "all-apps";
|
|
165
|
+
created: any[];
|
|
166
|
+
skipped: string[];
|
|
167
|
+
} | {
|
|
168
|
+
appId: string;
|
|
169
|
+
accessMode: "manual";
|
|
152
170
|
created: any[];
|
|
153
171
|
skipped: string[];
|
|
154
172
|
}>;
|
|
@@ -164,10 +182,43 @@ export declare function revokeGrant(grantId: string, ctx?: VaultCtx): Promise<{
|
|
|
164
182
|
createdAt: number;
|
|
165
183
|
updatedAt: number;
|
|
166
184
|
}>;
|
|
185
|
+
type VaultSecretRow = typeof schema.vaultSecrets.$inferSelect;
|
|
186
|
+
export declare function credentialStoreScopeForVaultCtx(ctx: VaultCtx): {
|
|
187
|
+
scope: Extract<SecretScope, "org" | "workspace">;
|
|
188
|
+
scopeId: string;
|
|
189
|
+
};
|
|
190
|
+
export declare function syncSecretsToCredentialStore(secrets: VaultSecretRow[], ctx: VaultCtx): Promise<{
|
|
191
|
+
keys: string[];
|
|
192
|
+
scope: Extract<SecretScope, "org" | "workspace">;
|
|
193
|
+
scopeId: string;
|
|
194
|
+
}>;
|
|
167
195
|
export declare function syncGrantsToApp(appId: string, ctx?: VaultCtx): Promise<{
|
|
168
196
|
appId: string;
|
|
197
|
+
accessMode: VaultAccessMode;
|
|
198
|
+
synced: number;
|
|
199
|
+
keys: any[];
|
|
200
|
+
credentialStore?: undefined;
|
|
201
|
+
envVars?: undefined;
|
|
202
|
+
} | {
|
|
203
|
+
appId: string;
|
|
204
|
+
accessMode: VaultAccessMode;
|
|
169
205
|
synced: number;
|
|
170
206
|
keys: string[];
|
|
207
|
+
credentialStore: {
|
|
208
|
+
scope: "org" | "workspace";
|
|
209
|
+
scopeId: string;
|
|
210
|
+
synced: number;
|
|
211
|
+
};
|
|
212
|
+
envVars: {
|
|
213
|
+
status: "synced";
|
|
214
|
+
keys: string[];
|
|
215
|
+
} | {
|
|
216
|
+
status: "skipped";
|
|
217
|
+
reason: string;
|
|
218
|
+
} | {
|
|
219
|
+
status: "failed";
|
|
220
|
+
reason: string;
|
|
221
|
+
};
|
|
171
222
|
}>;
|
|
172
223
|
export declare function listRequests(filter?: {
|
|
173
224
|
status?: string;
|
|
@@ -259,12 +310,16 @@ export interface AppIntegrations {
|
|
|
259
310
|
url: string;
|
|
260
311
|
color: string;
|
|
261
312
|
integrations: IntegrationEntry[];
|
|
313
|
+
vaultAccessMode: VaultAccessMode;
|
|
262
314
|
reachable: boolean;
|
|
263
315
|
}
|
|
264
316
|
export declare function listIntegrationsCatalog(): Promise<AppIntegrations[]>;
|
|
265
317
|
export declare function listVaultOverview(): Promise<{
|
|
318
|
+
accessMode: VaultAccessMode;
|
|
266
319
|
secretCount: number;
|
|
267
320
|
activeGrantCount: number;
|
|
321
|
+
manualGrantCount: number;
|
|
268
322
|
pendingRequestCount: number;
|
|
269
323
|
}>;
|
|
324
|
+
export {};
|
|
270
325
|
//# sourceMappingURL=vault-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vault-store.d.ts","sourceRoot":"","sources":["../../../src/server/lib/vault-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vault-store.d.ts","sourceRoot":"","sources":["../../../src/server/lib/vault-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { and, desc, eq, isNull, or } from "drizzle-orm";
|
|
3
3
|
import { discoverAgents } from "@agent-native/core/server/agent-discovery";
|
|
4
|
+
import { writeAppSecret } from "@agent-native/core/secrets";
|
|
5
|
+
import { getOrgSetting, getUserSetting, putOrgSetting, putUserSetting, } from "@agent-native/core/settings";
|
|
4
6
|
import { getDb, schema } from "../../db/index.js";
|
|
5
7
|
import { currentOwnerEmail, currentOrgId, recordAudit, } from "./dispatch-store.js";
|
|
8
|
+
const VAULT_ACCESS_SETTINGS_KEY = "dispatch-vault-access-settings";
|
|
6
9
|
/**
|
|
7
10
|
* Build a VaultCtx from the current request. Throws if the request is
|
|
8
11
|
* unauthenticated — the previous behavior of falling back to "local@localhost"
|
|
@@ -17,7 +20,10 @@ export function requireVaultCtx() {
|
|
|
17
20
|
}
|
|
18
21
|
/** WHERE clause that limits a vault row to the caller's ownership scope. */
|
|
19
22
|
function ctxScope(table, ctx) {
|
|
20
|
-
|
|
23
|
+
if (!ctx.orgId) {
|
|
24
|
+
return and(eq(table.ownerEmail, ctx.ownerEmail), isNull(table.orgId));
|
|
25
|
+
}
|
|
26
|
+
return or(eq(table.ownerEmail, ctx.ownerEmail), eq(table.orgId, ctx.orgId));
|
|
21
27
|
}
|
|
22
28
|
/** Build a ctx that scopes to a specific row's owner/org (used when a
|
|
23
29
|
* request approver acts on behalf of the original requester so the
|
|
@@ -34,9 +40,50 @@ function now() {
|
|
|
34
40
|
function safeJson(value) {
|
|
35
41
|
return JSON.stringify(value ?? null);
|
|
36
42
|
}
|
|
37
|
-
function
|
|
43
|
+
function scopedFilter(table) {
|
|
44
|
+
return ctxScope(table, requireVaultCtx());
|
|
45
|
+
}
|
|
46
|
+
function normalizeCredentialKey(value) {
|
|
47
|
+
return value.trim();
|
|
48
|
+
}
|
|
49
|
+
function vaultAccessScope() {
|
|
38
50
|
const orgId = currentOrgId();
|
|
39
|
-
|
|
51
|
+
if (orgId)
|
|
52
|
+
return { scope: "org", scopeId: orgId };
|
|
53
|
+
return { scope: "user", scopeId: currentOwnerEmail() };
|
|
54
|
+
}
|
|
55
|
+
function parseVaultAccessMode(value) {
|
|
56
|
+
return value === "manual" ? "manual" : "all-apps";
|
|
57
|
+
}
|
|
58
|
+
export async function getVaultAccessSettings() {
|
|
59
|
+
const scope = vaultAccessScope();
|
|
60
|
+
const raw = scope.scope === "org"
|
|
61
|
+
? await getOrgSetting(scope.scopeId, VAULT_ACCESS_SETTINGS_KEY)
|
|
62
|
+
: await getUserSetting(scope.scopeId, VAULT_ACCESS_SETTINGS_KEY);
|
|
63
|
+
return {
|
|
64
|
+
...scope,
|
|
65
|
+
mode: parseVaultAccessMode(raw?.mode),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export async function setVaultAccessSettings(input) {
|
|
69
|
+
const scope = vaultAccessScope();
|
|
70
|
+
const next = { mode: parseVaultAccessMode(input.mode) };
|
|
71
|
+
if (scope.scope === "org") {
|
|
72
|
+
await putOrgSetting(scope.scopeId, VAULT_ACCESS_SETTINGS_KEY, next);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
await putUserSetting(scope.scopeId, VAULT_ACCESS_SETTINGS_KEY, next);
|
|
76
|
+
}
|
|
77
|
+
await recordAudit({
|
|
78
|
+
action: "vault.access-settings.updated",
|
|
79
|
+
targetType: "vault-settings",
|
|
80
|
+
targetId: VAULT_ACCESS_SETTINGS_KEY,
|
|
81
|
+
summary: next.mode === "all-apps"
|
|
82
|
+
? "Set vault access to all workspace apps"
|
|
83
|
+
: "Set vault access to manual per-app grants",
|
|
84
|
+
metadata: next,
|
|
85
|
+
});
|
|
86
|
+
return getVaultAccessSettings();
|
|
40
87
|
}
|
|
41
88
|
// ─── Vault Audit ──────────────────────────────────────────────────
|
|
42
89
|
export async function recordVaultAudit(input) {
|
|
@@ -59,7 +106,7 @@ export async function listVaultAudit(limit = 50) {
|
|
|
59
106
|
return db
|
|
60
107
|
.select()
|
|
61
108
|
.from(schema.vaultAuditLog)
|
|
62
|
-
.where(
|
|
109
|
+
.where(scopedFilter(schema.vaultAuditLog))
|
|
63
110
|
.orderBy(desc(schema.vaultAuditLog.createdAt))
|
|
64
111
|
.limit(limit);
|
|
65
112
|
}
|
|
@@ -69,7 +116,7 @@ export async function listSecrets() {
|
|
|
69
116
|
return db
|
|
70
117
|
.select()
|
|
71
118
|
.from(schema.vaultSecrets)
|
|
72
|
-
.where(
|
|
119
|
+
.where(scopedFilter(schema.vaultSecrets))
|
|
73
120
|
.orderBy(desc(schema.vaultSecrets.updatedAt));
|
|
74
121
|
}
|
|
75
122
|
export async function getSecret(secretId, ctx) {
|
|
@@ -84,6 +131,41 @@ export async function getSecret(secretId, ctx) {
|
|
|
84
131
|
export async function createSecret(input, ctx = requireVaultCtx()) {
|
|
85
132
|
const db = getDb();
|
|
86
133
|
const timestamp = now();
|
|
134
|
+
const credentialKey = normalizeCredentialKey(input.credentialKey);
|
|
135
|
+
if (!credentialKey)
|
|
136
|
+
throw new Error("Credential key is required");
|
|
137
|
+
const existing = await db
|
|
138
|
+
.select()
|
|
139
|
+
.from(schema.vaultSecrets)
|
|
140
|
+
.where(and(eq(schema.vaultSecrets.credentialKey, credentialKey), ctxScope(schema.vaultSecrets, ctx)))
|
|
141
|
+
.orderBy(desc(schema.vaultSecrets.updatedAt))
|
|
142
|
+
.limit(1);
|
|
143
|
+
if (existing[0]) {
|
|
144
|
+
await db
|
|
145
|
+
.update(schema.vaultSecrets)
|
|
146
|
+
.set({
|
|
147
|
+
name: input.name,
|
|
148
|
+
credentialKey,
|
|
149
|
+
value: input.value,
|
|
150
|
+
provider: input.provider || null,
|
|
151
|
+
description: input.description || null,
|
|
152
|
+
updatedAt: timestamp,
|
|
153
|
+
})
|
|
154
|
+
.where(and(eq(schema.vaultSecrets.id, existing[0].id), ctxScope(schema.vaultSecrets, ctx)));
|
|
155
|
+
await recordVaultAudit({
|
|
156
|
+
action: "secret.updated",
|
|
157
|
+
secretId: existing[0].id,
|
|
158
|
+
summary: `Updated secret "${input.name}" (${credentialKey})`,
|
|
159
|
+
metadata: { credentialKey, provider: input.provider },
|
|
160
|
+
});
|
|
161
|
+
await recordAudit({
|
|
162
|
+
action: "vault.secret.updated",
|
|
163
|
+
targetType: "vault-secret",
|
|
164
|
+
targetId: existing[0].id,
|
|
165
|
+
summary: `Updated vault secret "${input.name}" (${credentialKey})`,
|
|
166
|
+
});
|
|
167
|
+
return getSecret(existing[0].id, ctx);
|
|
168
|
+
}
|
|
87
169
|
const secretId = id();
|
|
88
170
|
const actor = ctx.ownerEmail;
|
|
89
171
|
await db.insert(schema.vaultSecrets).values({
|
|
@@ -91,7 +173,7 @@ export async function createSecret(input, ctx = requireVaultCtx()) {
|
|
|
91
173
|
ownerEmail: actor,
|
|
92
174
|
orgId: ctx.orgId,
|
|
93
175
|
name: input.name,
|
|
94
|
-
credentialKey
|
|
176
|
+
credentialKey,
|
|
95
177
|
value: input.value,
|
|
96
178
|
provider: input.provider || null,
|
|
97
179
|
description: input.description || null,
|
|
@@ -102,14 +184,14 @@ export async function createSecret(input, ctx = requireVaultCtx()) {
|
|
|
102
184
|
await recordVaultAudit({
|
|
103
185
|
action: "secret.created",
|
|
104
186
|
secretId,
|
|
105
|
-
summary: `Created secret "${input.name}" (${
|
|
106
|
-
metadata: { credentialKey
|
|
187
|
+
summary: `Created secret "${input.name}" (${credentialKey})`,
|
|
188
|
+
metadata: { credentialKey, provider: input.provider },
|
|
107
189
|
});
|
|
108
190
|
await recordAudit({
|
|
109
191
|
action: "vault.secret.created",
|
|
110
192
|
targetType: "vault-secret",
|
|
111
193
|
targetId: secretId,
|
|
112
|
-
summary: `Created vault secret "${input.name}" (${
|
|
194
|
+
summary: `Created vault secret "${input.name}" (${credentialKey})`,
|
|
113
195
|
});
|
|
114
196
|
return getSecret(secretId, ctx);
|
|
115
197
|
}
|
|
@@ -160,7 +242,7 @@ export async function deleteSecret(secretId, ctx = requireVaultCtx()) {
|
|
|
160
242
|
// ─── Grants ──────────────────────────────────────────────────────
|
|
161
243
|
export async function listGrants(filter) {
|
|
162
244
|
const db = getDb();
|
|
163
|
-
const conditions = [
|
|
245
|
+
const conditions = [scopedFilter(schema.vaultGrants)];
|
|
164
246
|
if (filter?.secretId) {
|
|
165
247
|
conditions.push(eq(schema.vaultGrants.secretId, filter.secretId));
|
|
166
248
|
}
|
|
@@ -218,7 +300,16 @@ export async function createGrant(secretId, appId, ctx = requireVaultCtx()) {
|
|
|
218
300
|
return getGrant(grantId);
|
|
219
301
|
}
|
|
220
302
|
export async function grantSecretsToApp(secretIds, appId, ctx = requireVaultCtx()) {
|
|
303
|
+
const access = await getVaultAccessSettings();
|
|
221
304
|
const uniqueSecretIds = Array.from(new Set(secretIds));
|
|
305
|
+
if (access.mode === "all-apps") {
|
|
306
|
+
return {
|
|
307
|
+
appId,
|
|
308
|
+
accessMode: access.mode,
|
|
309
|
+
created: [],
|
|
310
|
+
skipped: uniqueSecretIds,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
222
313
|
const existingActive = (await listGrants({ appId })).filter((grant) => grant.status === "active");
|
|
223
314
|
const existingSecretIds = new Set(existingActive.map((grant) => grant.secretId));
|
|
224
315
|
const created = [];
|
|
@@ -234,7 +325,7 @@ export async function grantSecretsToApp(secretIds, appId, ctx = requireVaultCtx(
|
|
|
234
325
|
existingSecretIds.add(secretId);
|
|
235
326
|
}
|
|
236
327
|
}
|
|
237
|
-
return { appId, created, skipped };
|
|
328
|
+
return { appId, accessMode: access.mode, created, skipped };
|
|
238
329
|
}
|
|
239
330
|
export async function revokeGrant(grantId, ctx = requireVaultCtx()) {
|
|
240
331
|
const db = getDb();
|
|
@@ -261,43 +352,92 @@ export async function revokeGrant(grantId, ctx = requireVaultCtx()) {
|
|
|
261
352
|
});
|
|
262
353
|
return getGrant(grantId, ctx);
|
|
263
354
|
}
|
|
355
|
+
export function credentialStoreScopeForVaultCtx(ctx) {
|
|
356
|
+
if (ctx.orgId)
|
|
357
|
+
return { scope: "org", scopeId: ctx.orgId };
|
|
358
|
+
return { scope: "workspace", scopeId: `solo:${ctx.ownerEmail}` };
|
|
359
|
+
}
|
|
360
|
+
export async function syncSecretsToCredentialStore(secrets, ctx) {
|
|
361
|
+
const target = credentialStoreScopeForVaultCtx(ctx);
|
|
362
|
+
const syncedKeys = [];
|
|
363
|
+
for (const secret of secrets) {
|
|
364
|
+
if (!secret.credentialKey || !secret.value)
|
|
365
|
+
continue;
|
|
366
|
+
await writeAppSecret({
|
|
367
|
+
key: secret.credentialKey,
|
|
368
|
+
value: secret.value,
|
|
369
|
+
scope: target.scope,
|
|
370
|
+
scopeId: target.scopeId,
|
|
371
|
+
description: `Synced from Dispatch vault: ${secret.name}`,
|
|
372
|
+
});
|
|
373
|
+
syncedKeys.push(secret.credentialKey);
|
|
374
|
+
}
|
|
375
|
+
return { ...target, keys: syncedKeys };
|
|
376
|
+
}
|
|
264
377
|
// ─── Sync ──────────────────────────────────────────────────────
|
|
265
378
|
export async function syncGrantsToApp(appId, ctx = requireVaultCtx()) {
|
|
266
379
|
const db = getDb();
|
|
380
|
+
const access = await getVaultAccessSettings();
|
|
267
381
|
const agents = await discoverAgents("dispatch");
|
|
268
382
|
const agent = agents.find((a) => a.id === appId);
|
|
269
383
|
if (!agent)
|
|
270
384
|
throw new Error(`App "${appId}" not found in agent registry`);
|
|
271
|
-
const
|
|
272
|
-
const activeGrants =
|
|
273
|
-
|
|
274
|
-
|
|
385
|
+
const secretsToSync = [];
|
|
386
|
+
const activeGrants = access.mode === "manual"
|
|
387
|
+
? (await listGrants({ appId })).filter((g) => g.status === "active")
|
|
388
|
+
: [];
|
|
389
|
+
if (access.mode === "all-apps") {
|
|
390
|
+
const secrets = await listSecrets();
|
|
391
|
+
for (const secret of secrets) {
|
|
392
|
+
secretsToSync.push(secret);
|
|
393
|
+
}
|
|
275
394
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
395
|
+
else {
|
|
396
|
+
for (const grant of activeGrants) {
|
|
397
|
+
const secret = await getSecret(grant.secretId, ctx);
|
|
398
|
+
if (secret) {
|
|
399
|
+
secretsToSync.push(secret);
|
|
400
|
+
}
|
|
282
401
|
}
|
|
283
402
|
}
|
|
284
|
-
if (
|
|
285
|
-
return { appId, synced: 0, keys: [] };
|
|
403
|
+
if (secretsToSync.length === 0) {
|
|
404
|
+
return { appId, accessMode: access.mode, synced: 0, keys: [] };
|
|
286
405
|
}
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
406
|
+
const credentialStoreSync = await syncSecretsToCredentialStore(secretsToSync, ctx);
|
|
407
|
+
const vars = secretsToSync.map((secret) => ({
|
|
408
|
+
key: secret.credentialKey,
|
|
409
|
+
value: secret.value,
|
|
410
|
+
}));
|
|
411
|
+
let envVarSync;
|
|
412
|
+
// Best-effort push to the app's env-vars endpoint for local/dev apps that
|
|
413
|
+
// still read process.env directly. Production/shared-DB apps intentionally
|
|
414
|
+
// reject env writes; the encrypted app_secrets sync above is the canonical
|
|
415
|
+
// path for request-scoped credentials.
|
|
416
|
+
try {
|
|
417
|
+
const res = await fetch(`${agent.url}/_agent-native/env-vars`, {
|
|
418
|
+
method: "POST",
|
|
419
|
+
headers: { "Content-Type": "application/json" },
|
|
420
|
+
body: JSON.stringify({ vars }),
|
|
421
|
+
});
|
|
422
|
+
if (res.ok) {
|
|
423
|
+
const result = await res.json();
|
|
424
|
+
envVarSync = { status: "synced", keys: result.saved || [] };
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
const err = await res.text().catch(() => "Unknown error");
|
|
428
|
+
envVarSync = { status: "skipped", reason: err };
|
|
429
|
+
}
|
|
296
430
|
}
|
|
297
|
-
|
|
298
|
-
|
|
431
|
+
catch (err) {
|
|
432
|
+
envVarSync = {
|
|
433
|
+
status: "failed",
|
|
434
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const syncedKeys = credentialStoreSync.keys;
|
|
299
438
|
const timestamp = now();
|
|
300
|
-
// Update syncedAt on grants that were successfully pushed
|
|
439
|
+
// Update syncedAt on grants that were successfully pushed to the shared
|
|
440
|
+
// credential store. All-apps mode has no explicit grant rows to update.
|
|
301
441
|
for (const grant of activeGrants) {
|
|
302
442
|
const secret = await getSecret(grant.secretId, ctx);
|
|
303
443
|
if (secret && syncedKeys.includes(secret.credentialKey)) {
|
|
@@ -311,14 +451,33 @@ export async function syncGrantsToApp(appId, ctx = requireVaultCtx()) {
|
|
|
311
451
|
action: "secret.synced",
|
|
312
452
|
appId,
|
|
313
453
|
summary: `Synced ${syncedKeys.length} secret(s) to ${appId}: ${syncedKeys.join(", ")}`,
|
|
314
|
-
metadata: {
|
|
454
|
+
metadata: {
|
|
455
|
+
syncedKeys,
|
|
456
|
+
accessMode: access.mode,
|
|
457
|
+
credentialStore: {
|
|
458
|
+
scope: credentialStoreSync.scope,
|
|
459
|
+
scopeId: credentialStoreSync.scopeId,
|
|
460
|
+
},
|
|
461
|
+
envVars: envVarSync,
|
|
462
|
+
},
|
|
315
463
|
});
|
|
316
|
-
return {
|
|
464
|
+
return {
|
|
465
|
+
appId,
|
|
466
|
+
accessMode: access.mode,
|
|
467
|
+
synced: syncedKeys.length,
|
|
468
|
+
keys: syncedKeys,
|
|
469
|
+
credentialStore: {
|
|
470
|
+
scope: credentialStoreSync.scope,
|
|
471
|
+
scopeId: credentialStoreSync.scopeId,
|
|
472
|
+
synced: credentialStoreSync.keys.length,
|
|
473
|
+
},
|
|
474
|
+
envVars: envVarSync,
|
|
475
|
+
};
|
|
317
476
|
}
|
|
318
477
|
// ─── Requests ──────────────────────────────────────────────────────
|
|
319
478
|
export async function listRequests(filter) {
|
|
320
479
|
const db = getDb();
|
|
321
|
-
const conditions = [
|
|
480
|
+
const conditions = [scopedFilter(schema.vaultRequests)];
|
|
322
481
|
if (filter?.status) {
|
|
323
482
|
conditions.push(eq(schema.vaultRequests.status, filter.status));
|
|
324
483
|
}
|
|
@@ -441,6 +600,7 @@ export async function denyRequest(requestId, reason, ctx = requireVaultCtx()) {
|
|
|
441
600
|
return getRequest(requestId, ctx);
|
|
442
601
|
}
|
|
443
602
|
export async function listIntegrationsCatalog() {
|
|
603
|
+
const access = await getVaultAccessSettings();
|
|
444
604
|
const agents = await discoverAgents("dispatch");
|
|
445
605
|
const grants = await listGrants();
|
|
446
606
|
const secrets = await listSecrets();
|
|
@@ -458,6 +618,7 @@ export async function listIntegrationsCatalog() {
|
|
|
458
618
|
url: agent.url,
|
|
459
619
|
color: agent.color,
|
|
460
620
|
integrations: [],
|
|
621
|
+
vaultAccessMode: access.mode,
|
|
461
622
|
reachable: false,
|
|
462
623
|
});
|
|
463
624
|
continue;
|
|
@@ -472,7 +633,9 @@ export async function listIntegrationsCatalog() {
|
|
|
472
633
|
label: env.label,
|
|
473
634
|
required: env.required,
|
|
474
635
|
configured: env.configured,
|
|
475
|
-
vaultGranted: !!matchingSecret &&
|
|
636
|
+
vaultGranted: !!matchingSecret &&
|
|
637
|
+
(access.mode === "all-apps" ||
|
|
638
|
+
grantedSecretIds.has(matchingSecret.id)),
|
|
476
639
|
vaultSecretId: matchingSecret?.id,
|
|
477
640
|
};
|
|
478
641
|
});
|
|
@@ -482,6 +645,7 @@ export async function listIntegrationsCatalog() {
|
|
|
482
645
|
url: agent.url,
|
|
483
646
|
color: agent.color,
|
|
484
647
|
integrations,
|
|
648
|
+
vaultAccessMode: access.mode,
|
|
485
649
|
reachable: true,
|
|
486
650
|
});
|
|
487
651
|
}
|
|
@@ -492,6 +656,7 @@ export async function listIntegrationsCatalog() {
|
|
|
492
656
|
url: agent.url,
|
|
493
657
|
color: agent.color,
|
|
494
658
|
integrations: [],
|
|
659
|
+
vaultAccessMode: access.mode,
|
|
495
660
|
reachable: false,
|
|
496
661
|
});
|
|
497
662
|
}
|
|
@@ -500,14 +665,18 @@ export async function listIntegrationsCatalog() {
|
|
|
500
665
|
}
|
|
501
666
|
// ─── Vault Overview (for dashboard) ──────────────────────────────
|
|
502
667
|
export async function listVaultOverview() {
|
|
503
|
-
const [secrets, grants, requests] = await Promise.all([
|
|
668
|
+
const [secrets, grants, requests, access] = await Promise.all([
|
|
504
669
|
listSecrets(),
|
|
505
670
|
listGrants(),
|
|
506
671
|
listRequests(),
|
|
672
|
+
getVaultAccessSettings(),
|
|
507
673
|
]);
|
|
674
|
+
const manualGrantCount = grants.filter((g) => g.status === "active").length;
|
|
508
675
|
return {
|
|
676
|
+
accessMode: access.mode,
|
|
509
677
|
secretCount: secrets.length,
|
|
510
|
-
activeGrantCount:
|
|
678
|
+
activeGrantCount: access.mode === "all-apps" ? secrets.length : manualGrantCount,
|
|
679
|
+
manualGrantCount,
|
|
511
680
|
pendingRequestCount: requests.filter((r) => r.status === "pending").length,
|
|
512
681
|
};
|
|
513
682
|
}
|