@vellumai/credential-executor 0.5.11 → 0.5.12
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/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/command-executor.test.ts +2 -2
- package/src/__tests__/http-executor.test.ts +4 -4
- package/src/__tests__/local-materializers.test.ts +32 -32
- package/src/__tests__/managed-materializers.test.ts +1 -1
|
@@ -71,19 +71,19 @@ describe("handles", () => {
|
|
|
71
71
|
|
|
72
72
|
describe("localOAuthHandle", () => {
|
|
73
73
|
test("constructs the expected format", () => {
|
|
74
|
-
expect(localOAuthHandle("
|
|
75
|
-
"local_oauth:
|
|
74
|
+
expect(localOAuthHandle("google", "conn-123")).toBe(
|
|
75
|
+
"local_oauth:google/conn-123",
|
|
76
76
|
);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
test("roundtrips
|
|
80
|
-
const raw = localOAuthHandle("
|
|
79
|
+
test("roundtrips through parseHandle", () => {
|
|
80
|
+
const raw = localOAuthHandle("slack", "conn-abc");
|
|
81
81
|
const result = parseHandle(raw);
|
|
82
82
|
expect(result.ok).toBe(true);
|
|
83
83
|
if (!result.ok) return;
|
|
84
84
|
expect(result.handle.type).toBe(HandleType.LocalOAuth);
|
|
85
85
|
if (result.handle.type !== HandleType.LocalOAuth) return;
|
|
86
|
-
expect(result.handle.providerKey).toBe("
|
|
86
|
+
expect(result.handle.providerKey).toBe("slack");
|
|
87
87
|
expect(result.handle.connectionId).toBe("conn-abc");
|
|
88
88
|
});
|
|
89
89
|
});
|
|
@@ -133,7 +133,7 @@ describe("handles", () => {
|
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
test("rejects local_oauth with no slash", () => {
|
|
136
|
-
const result = parseHandle("local_oauth:
|
|
136
|
+
const result = parseHandle("local_oauth:google");
|
|
137
137
|
expect(result.ok).toBe(false);
|
|
138
138
|
});
|
|
139
139
|
|
|
@@ -150,7 +150,7 @@ describe("handles", () => {
|
|
|
150
150
|
).not.toThrow();
|
|
151
151
|
expect(() =>
|
|
152
152
|
CredentialHandleSchema.parse(
|
|
153
|
-
"local_oauth:
|
|
153
|
+
"local_oauth:google/conn-1",
|
|
154
154
|
),
|
|
155
155
|
).not.toThrow();
|
|
156
156
|
expect(() =>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 2. **Local OAuth** — references a locally persisted OAuth connection.
|
|
15
15
|
* Format: `local_oauth:<providerKey>/<connectionId>` where providerKey
|
|
16
|
-
*
|
|
16
|
+
* is the bare provider name (e.g. `google`).
|
|
17
17
|
*
|
|
18
18
|
* 3. **Managed OAuth** — references an OAuth connection managed by the
|
|
19
19
|
* platform. Format: `platform_oauth:<connectionId>` where connectionId
|
|
@@ -50,7 +50,7 @@ export interface LocalStaticHandle {
|
|
|
50
50
|
|
|
51
51
|
export interface LocalOAuthHandle {
|
|
52
52
|
type: typeof HandleType.LocalOAuth;
|
|
53
|
-
/** Provider key (e.g. "
|
|
53
|
+
/** Provider key (e.g. "google", "slack"). */
|
|
54
54
|
providerKey: string;
|
|
55
55
|
/** Connection identifier. */
|
|
56
56
|
connectionId: string;
|
|
@@ -146,8 +146,9 @@ export function parseHandle(raw: string): ParseHandleResult {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
case HandleType.LocalOAuth: {
|
|
149
|
-
// providerKey
|
|
150
|
-
//
|
|
149
|
+
// providerKey is typically a bare name (e.g. "google"), but legacy handles
|
|
150
|
+
// may contain a colon (e.g. "integration:google"), so we split on the
|
|
151
|
+
// *last* "/" to separate providerKey from connectionId.
|
|
151
152
|
const lastSlashIdx = rest.lastIndexOf("/");
|
|
152
153
|
if (
|
|
153
154
|
lastSlashIdx === -1 ||
|
|
@@ -98,7 +98,7 @@ export interface SecureKeyBackend {
|
|
|
98
98
|
export interface OAuthConnectionRecord {
|
|
99
99
|
/** Unique identifier for this connection. */
|
|
100
100
|
id: string;
|
|
101
|
-
/** Provider key (e.g. "
|
|
101
|
+
/** Provider key (e.g. "google", "slack"). */
|
|
102
102
|
providerKey: string;
|
|
103
103
|
/** Account identifier (e.g. email address). */
|
|
104
104
|
accountInfo: string | null;
|
package/package.json
CHANGED
|
@@ -1482,7 +1482,7 @@ describe("executeAuthenticatedCommand — integration: local OAuth", () => {
|
|
|
1482
1482
|
});
|
|
1483
1483
|
addCommandGrant(
|
|
1484
1484
|
deps.persistentStore,
|
|
1485
|
-
"local_oauth:
|
|
1485
|
+
"local_oauth:google/conn-123",
|
|
1486
1486
|
digest,
|
|
1487
1487
|
"list",
|
|
1488
1488
|
);
|
|
@@ -1490,7 +1490,7 @@ describe("executeAuthenticatedCommand — integration: local OAuth", () => {
|
|
|
1490
1490
|
const request: ExecuteCommandRequest = {
|
|
1491
1491
|
bundleDigest: digest,
|
|
1492
1492
|
profileName: "list",
|
|
1493
|
-
credentialHandle: "local_oauth:
|
|
1493
|
+
credentialHandle: "local_oauth:google/conn-123",
|
|
1494
1494
|
argv: ["list", "--format", "json"],
|
|
1495
1495
|
workspaceDir: testWorkspaceDir,
|
|
1496
1496
|
purpose: "OAuth pipeline test",
|
|
@@ -102,7 +102,7 @@ function buildOAuthConnection(
|
|
|
102
102
|
): OAuthConnectionRecord {
|
|
103
103
|
return {
|
|
104
104
|
id: overrides.id ?? "conn-uuid-1",
|
|
105
|
-
providerKey: overrides.providerKey ?? "
|
|
105
|
+
providerKey: overrides.providerKey ?? "google",
|
|
106
106
|
accountInfo: overrides.accountInfo ?? "user@example.com",
|
|
107
107
|
grantedScopes: overrides.grantedScopes ?? ["openid", "email"],
|
|
108
108
|
accessTokenPath: overrides.accessTokenPath ??
|
|
@@ -434,7 +434,7 @@ describe("HTTP executor: local OAuth", () => {
|
|
|
434
434
|
beforeEach(() => {
|
|
435
435
|
connection = buildOAuthConnection({
|
|
436
436
|
id: "conn-google-1",
|
|
437
|
-
providerKey: "
|
|
437
|
+
providerKey: "google",
|
|
438
438
|
expiresAt: Date.now() + 3600000, // valid for 1 hour
|
|
439
439
|
});
|
|
440
440
|
|
|
@@ -451,7 +451,7 @@ describe("HTTP executor: local OAuth", () => {
|
|
|
451
451
|
});
|
|
452
452
|
|
|
453
453
|
test("successful OAuth request with matching grant", async () => {
|
|
454
|
-
const handle = localOAuthHandle("
|
|
454
|
+
const handle = localOAuthHandle("google", "conn-google-1");
|
|
455
455
|
|
|
456
456
|
fixture.persistentStore.add({
|
|
457
457
|
id: "grant-google-calendar",
|
|
@@ -492,7 +492,7 @@ describe("HTTP executor: local OAuth", () => {
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
test("OAuth token scrubbed from response body", async () => {
|
|
495
|
-
const handle = localOAuthHandle("
|
|
495
|
+
const handle = localOAuthHandle("google", "conn-google-1");
|
|
496
496
|
|
|
497
497
|
fixture.persistentStore.add({
|
|
498
498
|
id: "grant-google-debug",
|
|
@@ -98,7 +98,7 @@ function buildOAuthConnection(
|
|
|
98
98
|
): OAuthConnectionRecord {
|
|
99
99
|
return {
|
|
100
100
|
id: overrides.id ?? "conn-uuid-1",
|
|
101
|
-
providerKey: overrides.providerKey ?? "
|
|
101
|
+
providerKey: overrides.providerKey ?? "google",
|
|
102
102
|
accountInfo: overrides.accountInfo ?? "user@example.com",
|
|
103
103
|
grantedScopes: overrides.grantedScopes ?? ["openid", "email"],
|
|
104
104
|
accessTokenPath: overrides.accessTokenPath ??
|
|
@@ -227,10 +227,10 @@ describe("local OAuth subject resolution", () => {
|
|
|
227
227
|
test("resolves a valid OAuth handle to an OAuth subject", () => {
|
|
228
228
|
const conn = buildOAuthConnection({
|
|
229
229
|
id: "conn-abc",
|
|
230
|
-
providerKey: "
|
|
230
|
+
providerKey: "google",
|
|
231
231
|
});
|
|
232
232
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
233
|
-
const handle = localOAuthHandle("
|
|
233
|
+
const handle = localOAuthHandle("google", "conn-abc");
|
|
234
234
|
|
|
235
235
|
const result = resolveLocalSubject(handle, deps);
|
|
236
236
|
|
|
@@ -239,12 +239,12 @@ describe("local OAuth subject resolution", () => {
|
|
|
239
239
|
expect(result.subject.type).toBe(HandleType.LocalOAuth);
|
|
240
240
|
if (result.subject.type !== HandleType.LocalOAuth) return;
|
|
241
241
|
expect(result.subject.connection.id).toBe("conn-abc");
|
|
242
|
-
expect(result.subject.connection.providerKey).toBe("
|
|
242
|
+
expect(result.subject.connection.providerKey).toBe("google");
|
|
243
243
|
});
|
|
244
244
|
|
|
245
245
|
test("fails when the connection does not exist", () => {
|
|
246
246
|
const deps = createResolverDeps({ oauthConnections: [] });
|
|
247
|
-
const handle = localOAuthHandle("
|
|
247
|
+
const handle = localOAuthHandle("google", "missing-conn");
|
|
248
248
|
|
|
249
249
|
const result = resolveLocalSubject(handle, deps);
|
|
250
250
|
|
|
@@ -257,19 +257,19 @@ describe("local OAuth subject resolution", () => {
|
|
|
257
257
|
test("fails when provider key in handle does not match connection", () => {
|
|
258
258
|
const conn = buildOAuthConnection({
|
|
259
259
|
id: "conn-xyz",
|
|
260
|
-
providerKey: "
|
|
260
|
+
providerKey: "slack",
|
|
261
261
|
});
|
|
262
262
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
263
263
|
// Handle says google but connection is slack
|
|
264
|
-
const handle = localOAuthHandle("
|
|
264
|
+
const handle = localOAuthHandle("google", "conn-xyz");
|
|
265
265
|
|
|
266
266
|
const result = resolveLocalSubject(handle, deps);
|
|
267
267
|
|
|
268
268
|
expect(result.ok).toBe(false);
|
|
269
269
|
if (result.ok) return;
|
|
270
270
|
expect(result.error).toMatch(/providerKey/);
|
|
271
|
-
expect(result.error).toMatch(/
|
|
272
|
-
expect(result.error).toMatch(/
|
|
271
|
+
expect(result.error).toMatch(/slack/);
|
|
272
|
+
expect(result.error).toMatch(/google/);
|
|
273
273
|
});
|
|
274
274
|
});
|
|
275
275
|
|
|
@@ -378,13 +378,13 @@ describe("OAuth token materialisation", () => {
|
|
|
378
378
|
test("materialises a valid non-expired access token", async () => {
|
|
379
379
|
const conn = buildOAuthConnection({
|
|
380
380
|
id: "conn-1",
|
|
381
|
-
providerKey: "
|
|
381
|
+
providerKey: "google",
|
|
382
382
|
// Token expires in the future (1 hour from now)
|
|
383
383
|
expiresAt: Date.now() + 60 * 60 * 1000,
|
|
384
384
|
hasRefreshToken: true,
|
|
385
385
|
});
|
|
386
386
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
387
|
-
const handle = localOAuthHandle("
|
|
387
|
+
const handle = localOAuthHandle("google", "conn-1");
|
|
388
388
|
|
|
389
389
|
const resolved = resolveLocalSubject(handle, deps);
|
|
390
390
|
expect(resolved.ok).toBe(true);
|
|
@@ -408,11 +408,11 @@ describe("OAuth token materialisation", () => {
|
|
|
408
408
|
test("fails when no access token is stored (disconnected connection)", async () => {
|
|
409
409
|
const conn = buildOAuthConnection({
|
|
410
410
|
id: "conn-disconnected",
|
|
411
|
-
providerKey: "
|
|
411
|
+
providerKey: "slack",
|
|
412
412
|
hasRefreshToken: false,
|
|
413
413
|
});
|
|
414
414
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
415
|
-
const handle = localOAuthHandle("
|
|
415
|
+
const handle = localOAuthHandle("slack", "conn-disconnected");
|
|
416
416
|
|
|
417
417
|
const resolved = resolveLocalSubject(handle, deps);
|
|
418
418
|
expect(resolved.ok).toBe(true);
|
|
@@ -435,14 +435,14 @@ describe("OAuth token materialisation", () => {
|
|
|
435
435
|
test("fails when token is expired and hasRefreshToken is false", async () => {
|
|
436
436
|
const conn = buildOAuthConnection({
|
|
437
437
|
id: "conn-expired-no-refresh",
|
|
438
|
-
providerKey: "
|
|
438
|
+
providerKey: "google",
|
|
439
439
|
// Token expired 10 minutes ago
|
|
440
440
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
441
441
|
hasRefreshToken: false,
|
|
442
442
|
});
|
|
443
443
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
444
444
|
const handle = localOAuthHandle(
|
|
445
|
-
"
|
|
445
|
+
"google",
|
|
446
446
|
"conn-expired-no-refresh",
|
|
447
447
|
);
|
|
448
448
|
|
|
@@ -469,12 +469,12 @@ describe("OAuth token materialisation", () => {
|
|
|
469
469
|
test("materialises a token with null expiresAt (no expiry info)", async () => {
|
|
470
470
|
const conn = buildOAuthConnection({
|
|
471
471
|
id: "conn-noexpiry",
|
|
472
|
-
providerKey: "
|
|
472
|
+
providerKey: "github",
|
|
473
473
|
expiresAt: null,
|
|
474
474
|
hasRefreshToken: false,
|
|
475
475
|
});
|
|
476
476
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
477
|
-
const handle = localOAuthHandle("
|
|
477
|
+
const handle = localOAuthHandle("github", "conn-noexpiry");
|
|
478
478
|
|
|
479
479
|
const resolved = resolveLocalSubject(handle, deps);
|
|
480
480
|
expect(resolved.ok).toBe(true);
|
|
@@ -504,13 +504,13 @@ describe("OAuth refresh-on-expiry", () => {
|
|
|
504
504
|
test("refreshes an expired token and returns the new access token", async () => {
|
|
505
505
|
const conn = buildOAuthConnection({
|
|
506
506
|
id: "conn-expired",
|
|
507
|
-
providerKey: "
|
|
507
|
+
providerKey: "google",
|
|
508
508
|
// Token expired 10 minutes ago
|
|
509
509
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
510
510
|
hasRefreshToken: true,
|
|
511
511
|
});
|
|
512
512
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
513
|
-
const handle = localOAuthHandle("
|
|
513
|
+
const handle = localOAuthHandle("google", "conn-expired");
|
|
514
514
|
|
|
515
515
|
const resolved = resolveLocalSubject(handle, deps);
|
|
516
516
|
expect(resolved.ok).toBe(true);
|
|
@@ -545,12 +545,12 @@ describe("OAuth refresh-on-expiry", () => {
|
|
|
545
545
|
test("fails when token is expired but no refresh function is configured", async () => {
|
|
546
546
|
const conn = buildOAuthConnection({
|
|
547
547
|
id: "conn-no-refresh-fn",
|
|
548
|
-
providerKey: "
|
|
548
|
+
providerKey: "google",
|
|
549
549
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
550
550
|
hasRefreshToken: true,
|
|
551
551
|
});
|
|
552
552
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
553
|
-
const handle = localOAuthHandle("
|
|
553
|
+
const handle = localOAuthHandle("google", "conn-no-refresh-fn");
|
|
554
554
|
|
|
555
555
|
const resolved = resolveLocalSubject(handle, deps);
|
|
556
556
|
expect(resolved.ok).toBe(true);
|
|
@@ -577,13 +577,13 @@ describe("OAuth refresh-on-expiry", () => {
|
|
|
577
577
|
test("fails when token is expired and no refresh token is stored", async () => {
|
|
578
578
|
const conn = buildOAuthConnection({
|
|
579
579
|
id: "conn-no-stored-refresh",
|
|
580
|
-
providerKey: "
|
|
580
|
+
providerKey: "google",
|
|
581
581
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
582
582
|
hasRefreshToken: true,
|
|
583
583
|
});
|
|
584
584
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
585
585
|
const handle = localOAuthHandle(
|
|
586
|
-
"
|
|
586
|
+
"google",
|
|
587
587
|
"conn-no-stored-refresh",
|
|
588
588
|
);
|
|
589
589
|
|
|
@@ -617,13 +617,13 @@ describe("OAuth refresh-on-expiry", () => {
|
|
|
617
617
|
test("fails when refresh function returns a failure result", async () => {
|
|
618
618
|
const conn = buildOAuthConnection({
|
|
619
619
|
id: "conn-refresh-fail",
|
|
620
|
-
providerKey: "
|
|
620
|
+
providerKey: "google",
|
|
621
621
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
622
622
|
hasRefreshToken: true,
|
|
623
623
|
});
|
|
624
624
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
625
625
|
const handle = localOAuthHandle(
|
|
626
|
-
"
|
|
626
|
+
"google",
|
|
627
627
|
"conn-refresh-fail",
|
|
628
628
|
);
|
|
629
629
|
|
|
@@ -662,12 +662,12 @@ describe("refresh circuit breaker", () => {
|
|
|
662
662
|
test("trips after repeated refresh failures and returns error", async () => {
|
|
663
663
|
const conn = buildOAuthConnection({
|
|
664
664
|
id: "conn-breaker",
|
|
665
|
-
providerKey: "
|
|
665
|
+
providerKey: "google",
|
|
666
666
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
667
667
|
hasRefreshToken: true,
|
|
668
668
|
});
|
|
669
669
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
670
|
-
const handle = localOAuthHandle("
|
|
670
|
+
const handle = localOAuthHandle("google", "conn-breaker");
|
|
671
671
|
|
|
672
672
|
const resolved = resolveLocalSubject(handle, deps);
|
|
673
673
|
expect(resolved.ok).toBe(true);
|
|
@@ -724,7 +724,7 @@ describe("deterministic fail-closed behaviour", () => {
|
|
|
724
724
|
|
|
725
725
|
// Missing OAuth connection
|
|
726
726
|
const r3 = resolveLocalSubject(
|
|
727
|
-
localOAuthHandle("
|
|
727
|
+
localOAuthHandle("x", "missing-conn"),
|
|
728
728
|
deps,
|
|
729
729
|
);
|
|
730
730
|
expect(r3.ok).toBe(false);
|
|
@@ -765,10 +765,10 @@ describe("deterministic fail-closed behaviour", () => {
|
|
|
765
765
|
test("OAuth disconnection detected before any refresh attempt", async () => {
|
|
766
766
|
const conn = buildOAuthConnection({
|
|
767
767
|
id: "conn-disco",
|
|
768
|
-
providerKey: "
|
|
768
|
+
providerKey: "slack",
|
|
769
769
|
});
|
|
770
770
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
771
|
-
const handle = localOAuthHandle("
|
|
771
|
+
const handle = localOAuthHandle("slack", "conn-disco");
|
|
772
772
|
|
|
773
773
|
const resolved = resolveLocalSubject(handle, deps);
|
|
774
774
|
expect(resolved.ok).toBe(true);
|
|
@@ -831,12 +831,12 @@ describe("end-to-end local materialisation", () => {
|
|
|
831
831
|
test("full pipeline: resolve OAuth handle -> materialise token", async () => {
|
|
832
832
|
const conn = buildOAuthConnection({
|
|
833
833
|
id: "conn-e2e",
|
|
834
|
-
providerKey: "
|
|
834
|
+
providerKey: "linear",
|
|
835
835
|
expiresAt: Date.now() + 3600 * 1000,
|
|
836
836
|
hasRefreshToken: true,
|
|
837
837
|
});
|
|
838
838
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
839
|
-
const handle = localOAuthHandle("
|
|
839
|
+
const handle = localOAuthHandle("linear", "conn-e2e");
|
|
840
840
|
|
|
841
841
|
// Step 1: Resolve
|
|
842
842
|
const resolved = resolveLocalSubject(handle, deps);
|
|
@@ -264,7 +264,7 @@ describe("resolveManagedSubject", () => {
|
|
|
264
264
|
|
|
265
265
|
test("rejects a local_oauth handle", async () => {
|
|
266
266
|
const result = await resolveManagedSubject(
|
|
267
|
-
"local_oauth:
|
|
267
|
+
"local_oauth:google/conn_local1",
|
|
268
268
|
{
|
|
269
269
|
platformBaseUrl: TEST_PLATFORM_URL,
|
|
270
270
|
assistantApiKey: TEST_API_KEY,
|