@kya-os/mcp-i-core 1.3.0 → 1.3.1
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +1251 -1245
- package/dist/services/oauth-config.service.d.ts.map +1 -1
- package/dist/services/oauth-config.service.js +5 -3
- package/dist/services/oauth-config.service.js.map +1 -1
- package/dist/services/oauth-provider-registry.d.ts +11 -0
- package/dist/services/oauth-provider-registry.d.ts.map +1 -1
- package/dist/services/oauth-provider-registry.js +16 -0
- package/dist/services/oauth-provider-registry.js.map +1 -1
- package/dist/services/provider-resolver.d.ts +1 -1
- package/dist/services/provider-resolver.d.ts.map +1 -1
- package/dist/services/provider-resolver.js +14 -13
- package/dist/services/provider-resolver.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/regression/phase2-regression.test.ts +8 -6
- package/src/__tests__/services/provider-resolver-edge-cases.test.ts +168 -64
- package/src/services/__tests__/provider-resolution.integration.test.ts +9 -5
- package/src/services/__tests__/provider-resolver.test.ts +22 -26
- package/src/services/oauth-config.service.ts +6 -3
- package/src/services/oauth-provider-registry.ts +18 -0
- package/src/services/provider-resolver.ts +15 -13
- package/.turbo/turbo-test$colon$coverage.log +0 -4514
- package/src/cache/oauth-config-cache.js +0 -71
- package/src/providers/base.js +0 -38
- package/src/services/oauth-config.service.js +0 -113
- package/src/services/oauth-provider-registry.js +0 -73
- package/src/services/provider-resolver.js +0 -106
|
@@ -57,12 +57,14 @@ describe("ProviderResolver - Edge Cases", () => {
|
|
|
57
57
|
const getProviderNamesMock = vi.fn().mockReturnValue([]);
|
|
58
58
|
const loadFromAgentShieldMock = vi.fn().mockResolvedValue(undefined);
|
|
59
59
|
const hasProviderMock = vi.fn().mockReturnValue(false);
|
|
60
|
+
const getConfiguredProviderMock = vi.fn().mockReturnValue(null);
|
|
60
61
|
|
|
61
62
|
mockRegistry = {
|
|
62
63
|
hasProvider: hasProviderMock,
|
|
63
64
|
getAllProviders: vi.fn().mockReturnValue([]),
|
|
64
65
|
getProviderNames: getProviderNamesMock,
|
|
65
66
|
loadFromAgentShield: loadFromAgentShieldMock,
|
|
67
|
+
getConfiguredProvider: getConfiguredProviderMock,
|
|
66
68
|
} as any;
|
|
67
69
|
|
|
68
70
|
resolver = new ProviderResolver(mockRegistry, mockConfigService);
|
|
@@ -154,19 +156,16 @@ describe("ProviderResolver - Edge Cases", () => {
|
|
|
154
156
|
requiredScopes: [],
|
|
155
157
|
};
|
|
156
158
|
|
|
157
|
-
// Should fall through to Priority 3
|
|
158
|
-
(mockRegistry.hasProvider as any).
|
|
159
|
-
(mockRegistry.
|
|
160
|
-
{ clientId: "github_client_id" },
|
|
161
|
-
]);
|
|
162
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
159
|
+
// Should fall through to Priority 3 (configuredProvider)
|
|
160
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
161
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
163
162
|
|
|
164
163
|
const provider = await resolver.resolveProvider(
|
|
165
164
|
toolProtection,
|
|
166
165
|
"test-project"
|
|
167
166
|
);
|
|
168
167
|
|
|
169
|
-
// Should use
|
|
168
|
+
// Should use configured provider (Priority 3)
|
|
170
169
|
expect(provider).toBe("github");
|
|
171
170
|
});
|
|
172
171
|
|
|
@@ -176,19 +175,16 @@ describe("ProviderResolver - Edge Cases", () => {
|
|
|
176
175
|
requiredScopes: ["github:repo:read", "google:calendar:read"],
|
|
177
176
|
};
|
|
178
177
|
|
|
179
|
-
// Ambiguous scopes should fall through to Priority 3
|
|
180
|
-
(mockRegistry.hasProvider as any).
|
|
181
|
-
(mockRegistry.
|
|
182
|
-
{ clientId: "github_client_id" },
|
|
183
|
-
]);
|
|
184
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
178
|
+
// Ambiguous scopes should fall through to Priority 3 (configuredProvider)
|
|
179
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
180
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
185
181
|
|
|
186
182
|
const provider = await resolver.resolveProvider(
|
|
187
183
|
toolProtection,
|
|
188
184
|
"test-project"
|
|
189
185
|
);
|
|
190
186
|
|
|
191
|
-
// Should use
|
|
187
|
+
// Should use configured provider (Priority 3)
|
|
192
188
|
expect(provider).toBe("github");
|
|
193
189
|
});
|
|
194
190
|
|
|
@@ -198,19 +194,16 @@ describe("ProviderResolver - Edge Cases", () => {
|
|
|
198
194
|
requiredScopes: ["custom:unknown:scope"],
|
|
199
195
|
};
|
|
200
196
|
|
|
201
|
-
// Unknown prefix should fall through to Priority 3
|
|
202
|
-
(mockRegistry.hasProvider as any).
|
|
203
|
-
(mockRegistry.
|
|
204
|
-
{ clientId: "github_client_id" },
|
|
205
|
-
]);
|
|
206
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
197
|
+
// Unknown prefix should fall through to Priority 3 (configuredProvider)
|
|
198
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
199
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
207
200
|
|
|
208
201
|
const provider = await resolver.resolveProvider(
|
|
209
202
|
toolProtection,
|
|
210
203
|
"test-project"
|
|
211
204
|
);
|
|
212
205
|
|
|
213
|
-
// Should use
|
|
206
|
+
// Should use configured provider (Priority 3)
|
|
214
207
|
expect(provider).toBe("github");
|
|
215
208
|
});
|
|
216
209
|
|
|
@@ -277,19 +270,16 @@ describe("ProviderResolver - Edge Cases", () => {
|
|
|
277
270
|
requiredScopes: ["read", "write"], // No colons
|
|
278
271
|
};
|
|
279
272
|
|
|
280
|
-
// Should fall through to Priority 3
|
|
281
|
-
(mockRegistry.hasProvider as any).
|
|
282
|
-
(mockRegistry.
|
|
283
|
-
{ clientId: "github_client_id" },
|
|
284
|
-
]);
|
|
285
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
273
|
+
// Should fall through to Priority 3 (configuredProvider)
|
|
274
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
275
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
286
276
|
|
|
287
277
|
const provider = await resolver.resolveProvider(
|
|
288
278
|
toolProtection,
|
|
289
279
|
"test-project"
|
|
290
280
|
);
|
|
291
281
|
|
|
292
|
-
// Should use
|
|
282
|
+
// Should use configured provider (Priority 3)
|
|
293
283
|
expect(provider).toBe("github");
|
|
294
284
|
});
|
|
295
285
|
|
|
@@ -299,90 +289,102 @@ describe("ProviderResolver - Edge Cases", () => {
|
|
|
299
289
|
requiredScopes: ["github:repo:read"],
|
|
300
290
|
};
|
|
301
291
|
|
|
302
|
-
// Inferred provider exists but not in registry
|
|
303
|
-
|
|
304
|
-
(mockRegistry.hasProvider as any).mockReturnValue(false); // github not in registry
|
|
305
|
-
|
|
306
|
-
// Should fall through to Priority 3
|
|
307
|
-
(mockRegistry.getAllProviders as any).mockReturnValue([
|
|
308
|
-
{ clientId: "google_client_id" },
|
|
309
|
-
]);
|
|
292
|
+
// Inferred provider (github) exists but not in registry
|
|
293
|
+
// configuredProvider is google
|
|
310
294
|
(mockRegistry.getProviderNames as any).mockReturnValue(["google"]);
|
|
295
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "google");
|
|
296
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("google");
|
|
311
297
|
|
|
312
298
|
const provider = await resolver.resolveProvider(
|
|
313
299
|
toolProtection,
|
|
314
300
|
"test-project"
|
|
315
301
|
);
|
|
316
302
|
|
|
317
|
-
// Should use
|
|
303
|
+
// Should use configured provider (Priority 3)
|
|
318
304
|
expect(provider).toBe("google");
|
|
319
305
|
});
|
|
320
306
|
});
|
|
321
307
|
|
|
322
|
-
describe("Fallback behavior (Priority 3)", () => {
|
|
323
|
-
it("should log
|
|
308
|
+
describe("Fallback behavior (Priority 3 - configuredProvider)", () => {
|
|
309
|
+
it("should log warning when using configuredProvider fallback", async () => {
|
|
324
310
|
const toolProtection: ToolProtection = {
|
|
325
311
|
requiresDelegation: true,
|
|
326
312
|
requiredScopes: ["custom:scope"],
|
|
327
313
|
};
|
|
328
314
|
|
|
329
|
-
(mockRegistry.hasProvider as any).
|
|
330
|
-
(mockRegistry.
|
|
331
|
-
{ clientId: "github_client_id" },
|
|
332
|
-
]);
|
|
333
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
315
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
316
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
334
317
|
|
|
335
318
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
336
319
|
|
|
337
320
|
await resolver.resolveProvider(toolProtection, "test-project");
|
|
338
321
|
|
|
339
322
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
340
|
-
expect.stringContaining("
|
|
323
|
+
expect.stringContaining("project-configured provider")
|
|
341
324
|
);
|
|
342
325
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
343
|
-
expect.stringContaining("
|
|
326
|
+
expect.stringContaining("github")
|
|
344
327
|
);
|
|
345
328
|
|
|
346
329
|
consoleSpy.mockRestore();
|
|
347
330
|
});
|
|
348
331
|
|
|
349
|
-
it("should use
|
|
332
|
+
it("should use configuredProvider when oauthProvider not specified", async () => {
|
|
350
333
|
const toolProtection: ToolProtection = {
|
|
351
334
|
requiresDelegation: true,
|
|
352
335
|
requiredScopes: ["custom:scope"],
|
|
353
336
|
};
|
|
354
337
|
|
|
338
|
+
// Multiple providers available, but configuredProvider is google
|
|
339
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) =>
|
|
340
|
+
name === "github" || name === "google"
|
|
341
|
+
);
|
|
342
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("google");
|
|
343
|
+
|
|
344
|
+
const provider = await resolver.resolveProvider(
|
|
345
|
+
toolProtection,
|
|
346
|
+
"test-project"
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
// Should use configuredProvider, not first alphabetically
|
|
350
|
+
expect(provider).toBe("google");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should NOT fall back to first provider alphabetically", async () => {
|
|
354
|
+
const toolProtection: ToolProtection = {
|
|
355
|
+
requiresDelegation: true,
|
|
356
|
+
requiredScopes: ["custom:scope"],
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Multiple providers in registry, but NO configuredProvider
|
|
355
360
|
(mockRegistry.hasProvider as any).mockReturnValue(false);
|
|
361
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue(null);
|
|
356
362
|
(mockRegistry.getAllProviders as any).mockReturnValue([
|
|
357
363
|
{ clientId: "github_client_id" },
|
|
358
364
|
{ clientId: "google_client_id" },
|
|
359
365
|
]);
|
|
360
366
|
(mockRegistry.getProviderNames as any).mockReturnValue(["github", "google"]);
|
|
361
367
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
"test-project"
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
// Should use first provider
|
|
368
|
-
expect(provider).toBe("github");
|
|
368
|
+
// Should throw error, NOT pick first provider
|
|
369
|
+
await expect(
|
|
370
|
+
resolver.resolveProvider(toolProtection, "test-project")
|
|
371
|
+
).rejects.toThrow(/no provider is configured/);
|
|
369
372
|
});
|
|
370
373
|
});
|
|
371
374
|
|
|
372
375
|
describe("Error scenarios (Priority 4)", () => {
|
|
373
|
-
it("should throw error if no
|
|
376
|
+
it("should throw error if no provider is configured", async () => {
|
|
374
377
|
const toolProtection: ToolProtection = {
|
|
375
378
|
requiresDelegation: true,
|
|
376
379
|
requiredScopes: [],
|
|
377
380
|
};
|
|
378
381
|
|
|
379
382
|
(mockRegistry.hasProvider as any).mockReturnValue(false);
|
|
380
|
-
(mockRegistry.
|
|
381
|
-
(mockRegistry.getProviderNames as any).mockReturnValue([]);
|
|
383
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue(null);
|
|
382
384
|
|
|
383
385
|
await expect(
|
|
384
386
|
resolver.resolveProvider(toolProtection, "test-project")
|
|
385
|
-
).rejects.toThrow(/no provider
|
|
387
|
+
).rejects.toThrow(/no provider is configured/);
|
|
386
388
|
});
|
|
387
389
|
|
|
388
390
|
it("should include project ID in error message", async () => {
|
|
@@ -392,8 +394,7 @@ describe("ProviderResolver - Edge Cases", () => {
|
|
|
392
394
|
};
|
|
393
395
|
|
|
394
396
|
(mockRegistry.hasProvider as any).mockReturnValue(false);
|
|
395
|
-
(mockRegistry.
|
|
396
|
-
(mockRegistry.getProviderNames as any).mockReturnValue([]);
|
|
397
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue(null);
|
|
397
398
|
|
|
398
399
|
try {
|
|
399
400
|
await resolver.resolveProvider(toolProtection, "test-project-123");
|
|
@@ -409,15 +410,14 @@ describe("ProviderResolver - Edge Cases", () => {
|
|
|
409
410
|
};
|
|
410
411
|
|
|
411
412
|
(mockRegistry.hasProvider as any).mockReturnValue(false);
|
|
412
|
-
(mockRegistry.
|
|
413
|
-
(mockRegistry.getProviderNames as any).mockReturnValue([]);
|
|
413
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue(null);
|
|
414
414
|
|
|
415
415
|
try {
|
|
416
416
|
await resolver.resolveProvider(toolProtection, "test-project");
|
|
417
417
|
} catch (error) {
|
|
418
418
|
const message = (error as Error).message;
|
|
419
|
-
expect(message).toContain("
|
|
420
|
-
expect(message).toContain("
|
|
419
|
+
expect(message).toContain("AgentShield dashboard");
|
|
420
|
+
expect(message).toContain("Configure");
|
|
421
421
|
}
|
|
422
422
|
});
|
|
423
423
|
});
|
|
@@ -483,5 +483,109 @@ describe("ProviderResolver - Edge Cases", () => {
|
|
|
483
483
|
expect(provider).toBe("github");
|
|
484
484
|
});
|
|
485
485
|
});
|
|
486
|
+
|
|
487
|
+
describe("configuredProvider behavior (Priority 3)", () => {
|
|
488
|
+
it("should use configuredProvider when tool has no oauthProvider", async () => {
|
|
489
|
+
const toolProtection: ToolProtection = {
|
|
490
|
+
requiresDelegation: true,
|
|
491
|
+
requiredScopes: ["greet:execute"], // No scope inference match
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// configuredProvider is github
|
|
495
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
496
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
497
|
+
|
|
498
|
+
const provider = await resolver.resolveProvider(
|
|
499
|
+
toolProtection,
|
|
500
|
+
"test-project"
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
expect(provider).toBe("github");
|
|
504
|
+
expect(mockRegistry.getConfiguredProvider).toHaveBeenCalled();
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it("should throw when configuredProvider is null and tool needs OAuth", async () => {
|
|
508
|
+
const toolProtection: ToolProtection = {
|
|
509
|
+
requiresDelegation: true,
|
|
510
|
+
requiredScopes: ["greet:execute"],
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// No configuredProvider set
|
|
514
|
+
(mockRegistry.hasProvider as any).mockReturnValue(false);
|
|
515
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue(null);
|
|
516
|
+
|
|
517
|
+
await expect(
|
|
518
|
+
resolver.resolveProvider(toolProtection, "test-project")
|
|
519
|
+
).rejects.toThrow(/no provider is configured/);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("should NOT use unconfigured providers as fallback", async () => {
|
|
523
|
+
const toolProtection: ToolProtection = {
|
|
524
|
+
requiresDelegation: true,
|
|
525
|
+
requiredScopes: ["greet:execute"],
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// Registry has providers but configuredProvider is null
|
|
529
|
+
// This simulates AgentShield returning all providers but none configured
|
|
530
|
+
(mockRegistry.hasProvider as any).mockReturnValue(false);
|
|
531
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue(null);
|
|
532
|
+
(mockRegistry.getAllProviders as any).mockReturnValue([
|
|
533
|
+
{ clientId: "github_client_id" },
|
|
534
|
+
{ clientId: "google_client_id" },
|
|
535
|
+
{ clientId: "microsoft_client_id" },
|
|
536
|
+
]);
|
|
537
|
+
(mockRegistry.getProviderNames as any).mockReturnValue(["github", "google", "microsoft"]);
|
|
538
|
+
|
|
539
|
+
// Should NOT pick "github" alphabetically - should throw
|
|
540
|
+
await expect(
|
|
541
|
+
resolver.resolveProvider(toolProtection, "test-project")
|
|
542
|
+
).rejects.toThrow(/no provider is configured/);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it("should prefer tool-specific oauthProvider over configuredProvider", async () => {
|
|
546
|
+
const toolProtection: ToolProtection = {
|
|
547
|
+
requiresDelegation: true,
|
|
548
|
+
requiredScopes: [],
|
|
549
|
+
oauthProvider: "google", // Tool specifies google
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// configuredProvider is github, but tool wants google
|
|
553
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) =>
|
|
554
|
+
name === "github" || name === "google"
|
|
555
|
+
);
|
|
556
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
557
|
+
(mockRegistry.getProviderNames as any).mockReturnValue(["github", "google"]);
|
|
558
|
+
|
|
559
|
+
const provider = await resolver.resolveProvider(
|
|
560
|
+
toolProtection,
|
|
561
|
+
"test-project"
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
// Priority 1 (tool-specific) should win over Priority 3 (configuredProvider)
|
|
565
|
+
expect(provider).toBe("google");
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it("should prefer scope-inferred provider over configuredProvider", async () => {
|
|
569
|
+
const toolProtection: ToolProtection = {
|
|
570
|
+
requiresDelegation: true,
|
|
571
|
+
requiredScopes: ["google:calendar:read"], // Infers google
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// configuredProvider is github, but scopes infer google
|
|
575
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) =>
|
|
576
|
+
name === "github" || name === "google"
|
|
577
|
+
);
|
|
578
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
579
|
+
(mockRegistry.getProviderNames as any).mockReturnValue(["github", "google"]);
|
|
580
|
+
|
|
581
|
+
const provider = await resolver.resolveProvider(
|
|
582
|
+
toolProtection,
|
|
583
|
+
"test-project"
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
// Priority 2 (scope inference) should win over Priority 3 (configuredProvider)
|
|
587
|
+
expect(provider).toBe("google");
|
|
588
|
+
});
|
|
589
|
+
});
|
|
486
590
|
});
|
|
487
591
|
|
|
@@ -37,6 +37,8 @@ describe("Provider Resolution Integration", () => {
|
|
|
37
37
|
requiresClientSecret: false,
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
|
+
// Explicitly configured provider (Priority 3 fallback)
|
|
41
|
+
configuredProvider: "github",
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
beforeEach(() => {
|
|
@@ -95,7 +97,7 @@ describe("Provider Resolution Integration", () => {
|
|
|
95
97
|
consoleSpy.mockRestore();
|
|
96
98
|
});
|
|
97
99
|
|
|
98
|
-
it("should fall back to
|
|
100
|
+
it("should fall back to configuredProvider for Phase 1 compatibility", async () => {
|
|
99
101
|
await providerRegistry.loadFromAgentShield("test-project");
|
|
100
102
|
|
|
101
103
|
const toolProtection: ToolProtection = {
|
|
@@ -111,10 +113,10 @@ describe("Provider Resolution Integration", () => {
|
|
|
111
113
|
"test-project"
|
|
112
114
|
);
|
|
113
115
|
|
|
114
|
-
// Should use
|
|
116
|
+
// Should use configuredProvider (github)
|
|
115
117
|
expect(provider).toBe("github");
|
|
116
118
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
117
|
-
expect.stringContaining("
|
|
119
|
+
expect.stringContaining("project-configured provider")
|
|
118
120
|
);
|
|
119
121
|
|
|
120
122
|
consoleSpy.mockRestore();
|
|
@@ -157,9 +159,11 @@ describe("Provider Resolution Integration", () => {
|
|
|
157
159
|
"test-project"
|
|
158
160
|
);
|
|
159
161
|
|
|
160
|
-
// Should fall back to
|
|
162
|
+
// Should fall back to configuredProvider
|
|
161
163
|
expect(provider).toBe("github");
|
|
162
|
-
expect(consoleSpy).
|
|
164
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
165
|
+
expect.stringContaining("project-configured provider")
|
|
166
|
+
);
|
|
163
167
|
|
|
164
168
|
consoleSpy.mockRestore();
|
|
165
169
|
});
|
|
@@ -45,6 +45,7 @@ describe("ProviderResolver", () => {
|
|
|
45
45
|
getAllProviders: vi.fn().mockReturnValue([]),
|
|
46
46
|
getProviderNames: vi.fn().mockReturnValue([]),
|
|
47
47
|
loadFromAgentShield: vi.fn().mockResolvedValue(undefined),
|
|
48
|
+
getConfiguredProvider: vi.fn().mockReturnValue(null), // New method for configuredProvider
|
|
48
49
|
} as any;
|
|
49
50
|
|
|
50
51
|
resolver = new ProviderResolver(mockRegistry, mockConfigService);
|
|
@@ -121,42 +122,37 @@ describe("ProviderResolver", () => {
|
|
|
121
122
|
expect(provider).toBe("google");
|
|
122
123
|
});
|
|
123
124
|
|
|
124
|
-
it("should
|
|
125
|
+
it("should fall back to configuredProvider for ambiguous scopes", async () => {
|
|
125
126
|
const toolProtection: ToolProtection = {
|
|
126
127
|
requiresDelegation: true,
|
|
127
128
|
requiredScopes: ["github:read", "google:read"],
|
|
128
129
|
};
|
|
129
130
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
(mockRegistry.
|
|
134
|
-
{ clientId: "github_client_id" },
|
|
135
|
-
]);
|
|
136
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
131
|
+
// Ambiguous scopes - both infer different providers
|
|
132
|
+
// Should fall through to Priority 3 (configuredProvider)
|
|
133
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
134
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
137
135
|
|
|
138
136
|
const provider = await resolver.resolveProvider(
|
|
139
137
|
toolProtection,
|
|
140
138
|
"test-project"
|
|
141
139
|
);
|
|
142
140
|
|
|
143
|
-
// Falls back to
|
|
141
|
+
// Falls back to configuredProvider
|
|
144
142
|
expect(provider).toBe("github");
|
|
145
143
|
});
|
|
146
144
|
});
|
|
147
145
|
|
|
148
|
-
describe("resolveProvider - Priority 3:
|
|
149
|
-
it("should use
|
|
146
|
+
describe("resolveProvider - Priority 3: Project-configured provider", () => {
|
|
147
|
+
it("should use configuredProvider as fallback", async () => {
|
|
150
148
|
const toolProtection: ToolProtection = {
|
|
151
149
|
requiresDelegation: true,
|
|
152
150
|
requiredScopes: ["custom:scope"],
|
|
153
151
|
};
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
(mockRegistry.
|
|
157
|
-
|
|
158
|
-
]);
|
|
159
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
153
|
+
// configuredProvider is github
|
|
154
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
155
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
160
156
|
|
|
161
157
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
162
158
|
|
|
@@ -167,30 +163,28 @@ describe("ProviderResolver", () => {
|
|
|
167
163
|
|
|
168
164
|
expect(provider).toBe("github");
|
|
169
165
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
170
|
-
expect.stringContaining("
|
|
166
|
+
expect.stringContaining("project-configured provider")
|
|
171
167
|
);
|
|
172
168
|
|
|
173
169
|
consoleSpy.mockRestore();
|
|
174
170
|
});
|
|
175
171
|
|
|
176
|
-
it("should log
|
|
172
|
+
it("should log warning when using configuredProvider fallback", async () => {
|
|
177
173
|
const toolProtection: ToolProtection = {
|
|
178
174
|
requiresDelegation: true,
|
|
179
175
|
requiredScopes: [],
|
|
180
176
|
};
|
|
181
177
|
|
|
182
|
-
|
|
183
|
-
(mockRegistry.
|
|
184
|
-
|
|
185
|
-
]);
|
|
186
|
-
(mockRegistry.getProviderNames as any).mockReturnValue(["github"]);
|
|
178
|
+
// configuredProvider is github
|
|
179
|
+
(mockRegistry.hasProvider as any).mockImplementation((name: string) => name === "github");
|
|
180
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue("github");
|
|
187
181
|
|
|
188
182
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
189
183
|
|
|
190
184
|
await resolver.resolveProvider(toolProtection, "test-project");
|
|
191
185
|
|
|
192
186
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
193
|
-
expect.stringContaining("
|
|
187
|
+
expect.stringContaining("Consider explicitly setting oauthProvider")
|
|
194
188
|
);
|
|
195
189
|
|
|
196
190
|
consoleSpy.mockRestore();
|
|
@@ -198,19 +192,21 @@ describe("ProviderResolver", () => {
|
|
|
198
192
|
});
|
|
199
193
|
|
|
200
194
|
describe("resolveProvider - Priority 4: Error if no provider", () => {
|
|
201
|
-
it("should throw error if no provider
|
|
195
|
+
it("should throw error if no provider is configured", async () => {
|
|
202
196
|
const toolProtection: ToolProtection = {
|
|
203
197
|
requiresDelegation: true,
|
|
204
198
|
requiredScopes: [],
|
|
205
199
|
};
|
|
206
200
|
|
|
201
|
+
// No configuredProvider set
|
|
207
202
|
(mockRegistry.hasProvider as any).mockReturnValue(false);
|
|
203
|
+
(mockRegistry.getConfiguredProvider as any).mockReturnValue(null);
|
|
208
204
|
(mockRegistry.getAllProviders as any).mockReturnValue([]);
|
|
209
205
|
(mockRegistry.getProviderNames as any).mockReturnValue([]);
|
|
210
206
|
|
|
211
207
|
await expect(
|
|
212
208
|
resolver.resolveProvider(toolProtection, "test-project")
|
|
213
|
-
).rejects.toThrow(/no provider
|
|
209
|
+
).rejects.toThrow(/no provider is configured/);
|
|
214
210
|
});
|
|
215
211
|
});
|
|
216
212
|
});
|
|
@@ -99,6 +99,7 @@ export class OAuthConfigService {
|
|
|
99
99
|
success: boolean;
|
|
100
100
|
data?: {
|
|
101
101
|
providers?: Record<string, unknown>;
|
|
102
|
+
configuredProvider?: string | null;
|
|
102
103
|
};
|
|
103
104
|
};
|
|
104
105
|
|
|
@@ -116,11 +117,12 @@ export class OAuthConfigService {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
// Build OAuthConfig object
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
120
|
+
// Extract configuredProvider from API response - this indicates which provider
|
|
121
|
+
// the user has actually configured in AgentShield dashboard
|
|
122
|
+
const configuredProvider = result.data.configuredProvider || null;
|
|
122
123
|
const config: OAuthConfig = {
|
|
123
124
|
providers: providers as Record<string, OAuthProvider>,
|
|
125
|
+
configuredProvider,
|
|
124
126
|
};
|
|
125
127
|
|
|
126
128
|
// Cache config
|
|
@@ -130,6 +132,7 @@ export class OAuthConfigService {
|
|
|
130
132
|
projectId,
|
|
131
133
|
providerCount: Object.keys(providers).length,
|
|
132
134
|
providers: Object.keys(providers),
|
|
135
|
+
configuredProvider,
|
|
133
136
|
expiresAt: new Date(
|
|
134
137
|
Date.now() + this.config.cacheTtl
|
|
135
138
|
).toISOString(),
|
|
@@ -19,6 +19,7 @@ import type { ProviderValidator } from "./provider-validator.js";
|
|
|
19
19
|
*/
|
|
20
20
|
export class OAuthProviderRegistry {
|
|
21
21
|
private providers: Map<string, OAuthProvider> = new Map();
|
|
22
|
+
private _configuredProvider: string | null = null;
|
|
22
23
|
|
|
23
24
|
constructor(
|
|
24
25
|
private configService: OAuthConfigService,
|
|
@@ -30,6 +31,7 @@ export class OAuthProviderRegistry {
|
|
|
30
31
|
*
|
|
31
32
|
* Fetches OAuth configuration and caches providers in memory.
|
|
32
33
|
* Clears existing providers before loading new ones.
|
|
34
|
+
* Also stores the configured provider from the API response.
|
|
33
35
|
*
|
|
34
36
|
* @param projectId - Project ID to load providers for
|
|
35
37
|
*/
|
|
@@ -39,12 +41,28 @@ export class OAuthProviderRegistry {
|
|
|
39
41
|
// Clear existing providers
|
|
40
42
|
this.providers.clear();
|
|
41
43
|
|
|
44
|
+
// Store the configured provider from API response
|
|
45
|
+
// This is the provider the user has explicitly configured in AgentShield dashboard
|
|
46
|
+
this._configuredProvider = config.configuredProvider || null;
|
|
47
|
+
|
|
42
48
|
// Register all providers from config
|
|
43
49
|
for (const [name, providerConfig] of Object.entries(config.providers)) {
|
|
44
50
|
this.providers.set(name, providerConfig);
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Get the explicitly configured provider for this project
|
|
56
|
+
*
|
|
57
|
+
* Returns the provider that the user has configured in AgentShield dashboard.
|
|
58
|
+
* Used by ProviderResolver as fallback when tool doesn't specify oauthProvider.
|
|
59
|
+
*
|
|
60
|
+
* @returns Configured provider name, or null if no provider is configured
|
|
61
|
+
*/
|
|
62
|
+
getConfiguredProvider(): string | null {
|
|
63
|
+
return this._configuredProvider;
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
/**
|
|
49
67
|
* Get provider by name
|
|
50
68
|
*
|