@kya-os/mcp-i-core 1.3.7-canary.0 → 1.3.7-canary.clientinfo.20251126041014
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 +4 -0
- package/.turbo/turbo-test$colon$coverage.log +4239 -0
- package/.turbo/turbo-test.log +2973 -0
- package/COMPLIANCE_IMPROVEMENT_REPORT.md +483 -0
- package/Composer 3.md +615 -0
- package/GPT-5.md +1169 -0
- package/OPUS-plan.md +352 -0
- package/PHASE_3_AND_4.1_SUMMARY.md +585 -0
- package/PHASE_3_SUMMARY.md +317 -0
- package/PHASE_4.1.3_SUMMARY.md +428 -0
- package/PHASE_4.1_COMPLETE.md +525 -0
- package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +1240 -0
- package/SCHEMA_COMPLIANCE_REPORT.md +275 -0
- package/TEST_PLAN.md +571 -0
- package/coverage/coverage-final.json +57 -0
- package/dist/__tests__/utils/mock-providers.d.ts +1 -2
- package/dist/__tests__/utils/mock-providers.d.ts.map +1 -1
- package/dist/__tests__/utils/mock-providers.js.map +1 -1
- package/dist/cache/oauth-config-cache.d.ts +69 -0
- package/dist/cache/oauth-config-cache.d.ts.map +1 -0
- package/dist/cache/oauth-config-cache.js +76 -0
- package/dist/cache/oauth-config-cache.js.map +1 -0
- package/dist/identity/idp-token-resolver.d.ts +53 -0
- package/dist/identity/idp-token-resolver.d.ts.map +1 -0
- package/dist/identity/idp-token-resolver.js +108 -0
- package/dist/identity/idp-token-resolver.js.map +1 -0
- package/dist/identity/idp-token-storage.interface.d.ts +42 -0
- package/dist/identity/idp-token-storage.interface.d.ts.map +1 -0
- package/dist/identity/idp-token-storage.interface.js +12 -0
- package/dist/identity/idp-token-storage.interface.js.map +1 -0
- package/dist/identity/user-did-manager.d.ts +39 -1
- package/dist/identity/user-did-manager.d.ts.map +1 -1
- package/dist/identity/user-did-manager.js +69 -3
- package/dist/identity/user-did-manager.js.map +1 -1
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/audit-logger.d.ts +37 -0
- package/dist/runtime/audit-logger.d.ts.map +1 -0
- package/dist/runtime/audit-logger.js +9 -0
- package/dist/runtime/audit-logger.js.map +1 -0
- package/dist/runtime/base.d.ts +19 -2
- package/dist/runtime/base.d.ts.map +1 -1
- package/dist/runtime/base.js +227 -11
- package/dist/runtime/base.js.map +1 -1
- package/dist/services/access-control.service.d.ts.map +1 -1
- package/dist/services/access-control.service.js +199 -15
- package/dist/services/access-control.service.js.map +1 -1
- package/dist/services/authorization/authorization-registry.d.ts +29 -0
- package/dist/services/authorization/authorization-registry.d.ts.map +1 -0
- package/dist/services/authorization/authorization-registry.js +57 -0
- package/dist/services/authorization/authorization-registry.js.map +1 -0
- package/dist/services/authorization/types.d.ts +53 -0
- package/dist/services/authorization/types.d.ts.map +1 -0
- package/dist/services/authorization/types.js +10 -0
- package/dist/services/authorization/types.js.map +1 -0
- package/dist/services/batch-delegation.service.d.ts +53 -0
- package/dist/services/batch-delegation.service.d.ts.map +1 -0
- package/dist/services/batch-delegation.service.js +95 -0
- package/dist/services/batch-delegation.service.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +4 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/oauth-config.service.d.ts +53 -0
- package/dist/services/oauth-config.service.d.ts.map +1 -0
- package/dist/services/oauth-config.service.js +141 -0
- package/dist/services/oauth-config.service.js.map +1 -0
- package/dist/services/oauth-provider-registry.d.ts +88 -0
- package/dist/services/oauth-provider-registry.d.ts.map +1 -0
- package/dist/services/oauth-provider-registry.js +128 -0
- package/dist/services/oauth-provider-registry.js.map +1 -0
- package/dist/services/oauth-service.d.ts +77 -0
- package/dist/services/oauth-service.d.ts.map +1 -0
- package/dist/services/oauth-service.js +373 -0
- package/dist/services/oauth-service.js.map +1 -0
- package/dist/services/oauth-token-retrieval.service.d.ts +49 -0
- package/dist/services/oauth-token-retrieval.service.d.ts.map +1 -0
- package/dist/services/oauth-token-retrieval.service.js +150 -0
- package/dist/services/oauth-token-retrieval.service.js.map +1 -0
- package/dist/services/provider-resolver.d.ts +48 -0
- package/dist/services/provider-resolver.d.ts.map +1 -0
- package/dist/services/provider-resolver.js +121 -0
- package/dist/services/provider-resolver.js.map +1 -0
- package/dist/services/provider-validator.d.ts +55 -0
- package/dist/services/provider-validator.d.ts.map +1 -0
- package/dist/services/provider-validator.js +135 -0
- package/dist/services/provider-validator.js.map +1 -0
- package/dist/services/session-registration.service.d.ts +80 -0
- package/dist/services/session-registration.service.d.ts.map +1 -0
- package/dist/services/session-registration.service.js +228 -0
- package/dist/services/session-registration.service.js.map +1 -0
- package/dist/services/tool-context-builder.d.ts +57 -0
- package/dist/services/tool-context-builder.d.ts.map +1 -0
- package/dist/services/tool-context-builder.js +125 -0
- package/dist/services/tool-context-builder.js.map +1 -0
- package/dist/services/tool-protection.service.d.ts +27 -0
- package/dist/services/tool-protection.service.d.ts.map +1 -1
- package/dist/services/tool-protection.service.js +194 -4
- package/dist/services/tool-protection.service.js.map +1 -1
- package/dist/types/oauth-required-error.d.ts +40 -0
- package/dist/types/oauth-required-error.d.ts.map +1 -0
- package/dist/types/oauth-required-error.js +40 -0
- package/dist/types/oauth-required-error.js.map +1 -0
- package/dist/utils/did-helpers.d.ts +33 -0
- package/dist/utils/did-helpers.d.ts.map +1 -1
- package/dist/utils/did-helpers.js +40 -0
- package/dist/utils/did-helpers.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/docs/API_REFERENCE.md +1362 -0
- package/docs/COMPLIANCE_MATRIX.md +691 -0
- package/docs/STATUSLIST2021_GUIDE.md +696 -0
- package/docs/W3C_VC_DELEGATION_GUIDE.md +710 -0
- package/package.json +23 -54
- package/scripts/audit-compliance.ts +724 -0
- package/src/__tests__/cache/tool-protection-cache.test.ts +640 -0
- package/src/__tests__/config/provider-runtime-config.test.ts +309 -0
- package/src/__tests__/delegation-e2e.test.ts +690 -0
- package/src/__tests__/identity/user-did-manager.test.ts +213 -0
- package/src/__tests__/index.test.ts +56 -0
- package/src/__tests__/integration/full-flow.test.ts +776 -0
- package/src/__tests__/integration.test.ts +281 -0
- package/src/__tests__/providers/base.test.ts +173 -0
- package/src/__tests__/providers/memory.test.ts +319 -0
- package/src/__tests__/regression/phase2-regression.test.ts +429 -0
- package/src/__tests__/runtime/audit-logger.test.ts +154 -0
- package/src/__tests__/runtime/base-extensions.test.ts +593 -0
- package/src/__tests__/runtime/base.test.ts +869 -0
- package/src/__tests__/runtime/delegation-flow.test.ts +164 -0
- package/src/__tests__/runtime/proof-client-did.test.ts +375 -0
- package/src/__tests__/runtime/route-interception.test.ts +686 -0
- package/src/__tests__/runtime/tool-protection-enforcement.test.ts +908 -0
- package/src/__tests__/services/agentshield-integration.test.ts +784 -0
- package/src/__tests__/services/cache-busting.test.ts +125 -0
- package/src/__tests__/services/oauth-service-pkce.test.ts +556 -0
- package/src/__tests__/services/provider-resolver-edge-cases.test.ts +591 -0
- package/src/__tests__/services/tool-protection-oauth-provider.test.ts +480 -0
- package/src/__tests__/services/tool-protection.service.test.ts +1366 -0
- package/src/__tests__/utils/mock-providers.ts +340 -0
- package/src/cache/oauth-config-cache.d.ts +69 -0
- package/src/cache/oauth-config-cache.d.ts.map +1 -0
- package/src/cache/oauth-config-cache.js.map +1 -0
- package/src/cache/oauth-config-cache.ts +123 -0
- package/src/cache/tool-protection-cache.ts +171 -0
- package/src/compliance/EXAMPLE.md +412 -0
- package/src/compliance/__tests__/schema-verifier.test.ts +797 -0
- package/src/compliance/index.ts +8 -0
- package/src/compliance/schema-registry.ts +460 -0
- package/src/compliance/schema-verifier.ts +708 -0
- package/src/config/__tests__/remote-config.spec.ts +268 -0
- package/src/config/remote-config.ts +174 -0
- package/src/config.ts +309 -0
- package/src/delegation/__tests__/audience-validator.test.ts +112 -0
- package/src/delegation/__tests__/bitstring.test.ts +346 -0
- package/src/delegation/__tests__/cascading-revocation.test.ts +628 -0
- package/src/delegation/__tests__/delegation-graph.test.ts +584 -0
- package/src/delegation/__tests__/utils.test.ts +152 -0
- package/src/delegation/__tests__/vc-issuer.test.ts +442 -0
- package/src/delegation/__tests__/vc-verifier.test.ts +922 -0
- package/src/delegation/audience-validator.ts +52 -0
- package/src/delegation/bitstring.ts +278 -0
- package/src/delegation/cascading-revocation.ts +370 -0
- package/src/delegation/delegation-graph.ts +299 -0
- package/src/delegation/index.ts +14 -0
- package/src/delegation/statuslist-manager.ts +353 -0
- package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
- package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
- package/src/delegation/storage/index.ts +9 -0
- package/src/delegation/storage/memory-graph-storage.ts +178 -0
- package/src/delegation/storage/memory-statuslist-storage.ts +77 -0
- package/src/delegation/utils.ts +42 -0
- package/src/delegation/vc-issuer.ts +232 -0
- package/src/delegation/vc-verifier.ts +568 -0
- package/src/identity/idp-token-resolver.ts +147 -0
- package/src/identity/idp-token-storage.interface.ts +59 -0
- package/src/identity/user-did-manager.ts +370 -0
- package/src/index.ts +271 -0
- package/src/providers/base.d.ts +91 -0
- package/src/providers/base.d.ts.map +1 -0
- package/src/providers/base.js.map +1 -0
- package/src/providers/base.ts +96 -0
- package/src/providers/memory.ts +142 -0
- package/src/runtime/audit-logger.ts +39 -0
- package/src/runtime/base.ts +1329 -0
- package/src/services/__tests__/access-control.integration.test.ts +443 -0
- package/src/services/__tests__/access-control.proof-response-validation.test.ts +578 -0
- package/src/services/__tests__/access-control.service.test.ts +970 -0
- package/src/services/__tests__/batch-delegation.service.test.ts +351 -0
- package/src/services/__tests__/crypto.service.test.ts +531 -0
- package/src/services/__tests__/oauth-provider-registry.test.ts +142 -0
- package/src/services/__tests__/proof-verifier.integration.test.ts +485 -0
- package/src/services/__tests__/proof-verifier.test.ts +489 -0
- package/src/services/__tests__/provider-resolution.integration.test.ts +202 -0
- package/src/services/__tests__/provider-resolver.test.ts +213 -0
- package/src/services/__tests__/storage.service.test.ts +358 -0
- package/src/services/access-control.service.ts +990 -0
- package/src/services/authorization/authorization-registry.ts +66 -0
- package/src/services/authorization/types.ts +71 -0
- package/src/services/batch-delegation.service.ts +137 -0
- package/src/services/crypto.service.ts +302 -0
- package/src/services/errors.ts +76 -0
- package/src/services/index.ts +18 -0
- package/src/services/oauth-config.service.d.ts +53 -0
- package/src/services/oauth-config.service.d.ts.map +1 -0
- package/src/services/oauth-config.service.js.map +1 -0
- package/src/services/oauth-config.service.ts +192 -0
- package/src/services/oauth-provider-registry.d.ts +57 -0
- package/src/services/oauth-provider-registry.d.ts.map +1 -0
- package/src/services/oauth-provider-registry.js.map +1 -0
- package/src/services/oauth-provider-registry.ts +141 -0
- package/src/services/oauth-service.ts +544 -0
- package/src/services/oauth-token-retrieval.service.ts +245 -0
- package/src/services/proof-verifier.ts +478 -0
- package/src/services/provider-resolver.d.ts +48 -0
- package/src/services/provider-resolver.d.ts.map +1 -0
- package/src/services/provider-resolver.js.map +1 -0
- package/src/services/provider-resolver.ts +146 -0
- package/src/services/provider-validator.ts +170 -0
- package/src/services/session-registration.service.ts +317 -0
- package/src/services/storage.service.ts +566 -0
- package/src/services/tool-context-builder.ts +172 -0
- package/src/services/tool-protection.service.ts +982 -0
- package/src/types/oauth-required-error.ts +63 -0
- package/src/types/tool-protection.ts +155 -0
- package/src/utils/__tests__/did-helpers.test.ts +101 -0
- package/src/utils/base64.ts +148 -0
- package/src/utils/cors.ts +83 -0
- package/src/utils/did-helpers.ts +150 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/storage-keys.ts +278 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +56 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ProofVerifier
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive security test coverage for proof verification service.
|
|
5
|
+
* Tests nonce replay protection, timestamp skew validation, canonical payload reconstruction,
|
|
6
|
+
* and various security attack scenarios.
|
|
7
|
+
*
|
|
8
|
+
* Test Coverage Requirements: 100% - All security-critical code paths
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
12
|
+
import { ProofVerifier } from '../proof-verifier.js';
|
|
13
|
+
import { CryptoService, type Ed25519JWK } from '../crypto.service.js';
|
|
14
|
+
import type {
|
|
15
|
+
CryptoProvider,
|
|
16
|
+
ClockProvider,
|
|
17
|
+
NonceCacheProvider,
|
|
18
|
+
FetchProvider,
|
|
19
|
+
} from '../../providers/base.js';
|
|
20
|
+
import type { DetachedProof } from '@kya-os/contracts/proof';
|
|
21
|
+
import {
|
|
22
|
+
ProofVerificationError,
|
|
23
|
+
PROOF_VERIFICATION_ERROR_CODES,
|
|
24
|
+
} from '../errors.js';
|
|
25
|
+
|
|
26
|
+
describe('ProofVerifier Security', () => {
|
|
27
|
+
let proofVerifier: ProofVerifier;
|
|
28
|
+
let mockCryptoProvider: CryptoProvider;
|
|
29
|
+
let mockClockProvider: ClockProvider;
|
|
30
|
+
let mockNonceCache: NonceCacheProvider;
|
|
31
|
+
let mockFetchProvider: FetchProvider;
|
|
32
|
+
let cryptoService: CryptoService;
|
|
33
|
+
|
|
34
|
+
const validJwk: Ed25519JWK = {
|
|
35
|
+
kty: 'OKP',
|
|
36
|
+
crv: 'Ed25519',
|
|
37
|
+
x: 'VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ',
|
|
38
|
+
kid: 'did:key:z123#key-1',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const createValidProof = (): DetachedProof => {
|
|
42
|
+
const header = { alg: 'EdDSA', typ: 'JWT' };
|
|
43
|
+
// Create a proper JSON payload that matches the meta structure
|
|
44
|
+
const payload = {
|
|
45
|
+
aud: 'test-audience',
|
|
46
|
+
sub: 'did:key:z123',
|
|
47
|
+
iss: 'did:key:z123',
|
|
48
|
+
nonce: 'nonce123',
|
|
49
|
+
ts: Math.floor(Date.now() / 1000),
|
|
50
|
+
sessionId: 'session123',
|
|
51
|
+
requestHash: 'sha256:' + 'a'.repeat(64),
|
|
52
|
+
responseHash: 'sha256:' + 'b'.repeat(64),
|
|
53
|
+
};
|
|
54
|
+
// Use btoa for base64 encoding (available in test environment via polyfill)
|
|
55
|
+
const headerB64 = btoa(JSON.stringify(header))
|
|
56
|
+
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
57
|
+
const payloadB64 = btoa(JSON.stringify(payload))
|
|
58
|
+
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
59
|
+
const signatureB64 = btoa('signature')
|
|
60
|
+
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
61
|
+
const jws = `${headerB64}.${payloadB64}.${signatureB64}`;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
jws,
|
|
65
|
+
meta: {
|
|
66
|
+
did: 'did:key:z123',
|
|
67
|
+
kid: 'did:key:z123#key-1',
|
|
68
|
+
ts: Math.floor(Date.now() / 1000),
|
|
69
|
+
nonce: 'nonce123',
|
|
70
|
+
audience: 'test-audience',
|
|
71
|
+
sessionId: 'session123',
|
|
72
|
+
requestHash: 'sha256:' + 'a'.repeat(64),
|
|
73
|
+
responseHash: 'sha256:' + 'b'.repeat(64),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
mockCryptoProvider = {
|
|
80
|
+
sign: vi.fn(),
|
|
81
|
+
verify: vi.fn().mockResolvedValue(true),
|
|
82
|
+
generateKeyPair: vi.fn(),
|
|
83
|
+
hash: vi.fn(),
|
|
84
|
+
randomBytes: vi.fn(),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
cryptoService = new CryptoService(mockCryptoProvider);
|
|
88
|
+
|
|
89
|
+
mockClockProvider = {
|
|
90
|
+
now: vi.fn().mockReturnValue(Date.now()), // Return milliseconds
|
|
91
|
+
isWithinSkew: vi.fn().mockReturnValue(true),
|
|
92
|
+
hasExpired: vi.fn(),
|
|
93
|
+
calculateExpiry: vi.fn((ttlSeconds: number) => Date.now() + (ttlSeconds * 1000)), // Return milliseconds
|
|
94
|
+
format: vi.fn(),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
mockNonceCache = {
|
|
98
|
+
has: vi.fn().mockResolvedValue(false),
|
|
99
|
+
add: vi.fn().mockResolvedValue(undefined),
|
|
100
|
+
cleanup: vi.fn().mockResolvedValue(undefined),
|
|
101
|
+
destroy: vi.fn().mockResolvedValue(undefined),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
mockFetchProvider = {
|
|
105
|
+
resolveDID: vi.fn().mockResolvedValue({
|
|
106
|
+
verificationMethod: [{
|
|
107
|
+
id: 'did:key:z123#key-1',
|
|
108
|
+
publicKeyJwk: validJwk,
|
|
109
|
+
}],
|
|
110
|
+
}),
|
|
111
|
+
fetchStatusList: vi.fn(),
|
|
112
|
+
fetchDelegationChain: vi.fn(),
|
|
113
|
+
fetch: vi.fn(),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
proofVerifier = new ProofVerifier({
|
|
117
|
+
cryptoProvider: mockCryptoProvider,
|
|
118
|
+
clockProvider: mockClockProvider,
|
|
119
|
+
nonceCacheProvider: mockNonceCache,
|
|
120
|
+
fetchProvider: mockFetchProvider,
|
|
121
|
+
timestampSkewSeconds: 120,
|
|
122
|
+
nonceTtlSeconds: 300,
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('Nonce Replay Protection', () => {
|
|
127
|
+
it('should prevent nonce replay attacks', async () => {
|
|
128
|
+
const proof = createValidProof();
|
|
129
|
+
|
|
130
|
+
// First verification should succeed
|
|
131
|
+
const result1 = await proofVerifier.verifyProof(proof, validJwk);
|
|
132
|
+
expect(result1.valid).toBe(true);
|
|
133
|
+
expect(mockNonceCache.has).toHaveBeenCalledWith('nonce123', 'did:key:z123');
|
|
134
|
+
expect(mockNonceCache.add).toHaveBeenCalled();
|
|
135
|
+
|
|
136
|
+
// Reset mock to simulate second attempt
|
|
137
|
+
mockNonceCache.has = vi.fn().mockResolvedValue(true);
|
|
138
|
+
|
|
139
|
+
// Second verification with same nonce should fail
|
|
140
|
+
const result2 = await proofVerifier.verifyProof(proof, validJwk);
|
|
141
|
+
expect(result2.valid).toBe(false);
|
|
142
|
+
expect(result2.reason).toContain('replay');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should add nonce to cache after successful verification', async () => {
|
|
146
|
+
const proof = createValidProof();
|
|
147
|
+
|
|
148
|
+
await proofVerifier.verifyProof(proof, validJwk);
|
|
149
|
+
|
|
150
|
+
expect(mockNonceCache.add).toHaveBeenCalledWith(
|
|
151
|
+
'nonce123',
|
|
152
|
+
expect.any(Number),
|
|
153
|
+
'did:key:z123'
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('Timestamp Skew Validation', () => {
|
|
159
|
+
it('should enforce timestamp skew limits', async () => {
|
|
160
|
+
const proof = createValidProof();
|
|
161
|
+
const currentTime = Date.now(); // milliseconds
|
|
162
|
+
|
|
163
|
+
// Set clock to 5 minutes in the future
|
|
164
|
+
mockClockProvider.now = vi.fn().mockReturnValue(currentTime);
|
|
165
|
+
mockClockProvider.isWithinSkew = vi.fn().mockReturnValue(false);
|
|
166
|
+
|
|
167
|
+
const result = await proofVerifier.verifyProof(proof, validJwk);
|
|
168
|
+
|
|
169
|
+
expect(result.valid).toBe(false);
|
|
170
|
+
expect(result.reason).toContain('skew');
|
|
171
|
+
// isWithinSkew is called with timestamp in milliseconds (converted from seconds)
|
|
172
|
+
expect(mockClockProvider.isWithinSkew).toHaveBeenCalledWith(
|
|
173
|
+
proof.meta.ts * 1000, // Convert seconds to milliseconds
|
|
174
|
+
120
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should accept timestamps within skew window', async () => {
|
|
179
|
+
const proof = createValidProof();
|
|
180
|
+
mockClockProvider.isWithinSkew = vi.fn().mockReturnValue(true);
|
|
181
|
+
|
|
182
|
+
const result = await proofVerifier.verifyProof(proof, validJwk);
|
|
183
|
+
|
|
184
|
+
expect(result.valid).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should use custom timestamp skew seconds', async () => {
|
|
188
|
+
const customProofVerifier = new ProofVerifier({
|
|
189
|
+
cryptoProvider: mockCryptoProvider,
|
|
190
|
+
clockProvider: mockClockProvider,
|
|
191
|
+
nonceCacheProvider: mockNonceCache,
|
|
192
|
+
fetchProvider: mockFetchProvider,
|
|
193
|
+
timestampSkewSeconds: 300, // 5 minutes
|
|
194
|
+
nonceTtlSeconds: 300,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const proof = createValidProof();
|
|
198
|
+
mockClockProvider.isWithinSkew = vi.fn().mockReturnValue(false);
|
|
199
|
+
|
|
200
|
+
await customProofVerifier.verifyProof(proof, validJwk);
|
|
201
|
+
|
|
202
|
+
// isWithinSkew is called with timestamp in milliseconds (converted from seconds)
|
|
203
|
+
expect(mockClockProvider.isWithinSkew).toHaveBeenCalledWith(
|
|
204
|
+
proof.meta.ts * 1000, // Convert seconds to milliseconds
|
|
205
|
+
300
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('Canonical Payload Reconstruction', () => {
|
|
211
|
+
it('should reconstruct canonical payload from meta', async () => {
|
|
212
|
+
const proof = createValidProof();
|
|
213
|
+
|
|
214
|
+
await proofVerifier.verifyProof(proof, validJwk);
|
|
215
|
+
|
|
216
|
+
// Verify that verifyJWS was called with detached payload
|
|
217
|
+
expect(mockCryptoProvider.verify).toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should validate canonical payload ordering determinism', () => {
|
|
221
|
+
const meta1 = {
|
|
222
|
+
z: 1,
|
|
223
|
+
a: 2,
|
|
224
|
+
m: 3,
|
|
225
|
+
did: 'did:test',
|
|
226
|
+
kid: 'kid',
|
|
227
|
+
ts: 123,
|
|
228
|
+
nonce: 'nonce',
|
|
229
|
+
audience: 'aud',
|
|
230
|
+
sessionId: 'session',
|
|
231
|
+
requestHash: 'sha256:' + 'a'.repeat(64),
|
|
232
|
+
responseHash: 'sha256:' + 'b'.repeat(64),
|
|
233
|
+
};
|
|
234
|
+
const meta2 = {
|
|
235
|
+
a: 2,
|
|
236
|
+
m: 3,
|
|
237
|
+
z: 1,
|
|
238
|
+
did: 'did:test',
|
|
239
|
+
kid: 'kid',
|
|
240
|
+
ts: 123,
|
|
241
|
+
nonce: 'nonce',
|
|
242
|
+
audience: 'aud',
|
|
243
|
+
sessionId: 'session',
|
|
244
|
+
requestHash: 'sha256:' + 'a'.repeat(64),
|
|
245
|
+
responseHash: 'sha256:' + 'b'.repeat(64),
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const canonical1 = proofVerifier.buildCanonicalPayload(meta1);
|
|
249
|
+
const canonical2 = proofVerifier.buildCanonicalPayload(meta2);
|
|
250
|
+
|
|
251
|
+
// Should be identical despite different key order
|
|
252
|
+
expect(canonical1).toBe(canonical2);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should handle detached JWS reconstruction', async () => {
|
|
256
|
+
const header = { alg: 'EdDSA' };
|
|
257
|
+
const headerB64 = btoa(JSON.stringify(header))
|
|
258
|
+
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
259
|
+
const signatureB64 = btoa('signature')
|
|
260
|
+
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
261
|
+
const detachedJws = `${headerB64}..${signatureB64}`;
|
|
262
|
+
|
|
263
|
+
const proof: DetachedProof = {
|
|
264
|
+
jws: detachedJws,
|
|
265
|
+
meta: createValidProof().meta,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const result = await proofVerifier.verifyProof(proof, validJwk);
|
|
269
|
+
|
|
270
|
+
// Should call verifyJWS with detached payload
|
|
271
|
+
expect(mockCryptoProvider.verify).toHaveBeenCalled();
|
|
272
|
+
expect(result.valid).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('Proof Structure Validation', () => {
|
|
277
|
+
it('should reject invalid proof structure', async () => {
|
|
278
|
+
const invalidProof = {
|
|
279
|
+
jws: 'invalid',
|
|
280
|
+
meta: {
|
|
281
|
+
// Missing required fields
|
|
282
|
+
did: 'did:test',
|
|
283
|
+
},
|
|
284
|
+
} as any;
|
|
285
|
+
|
|
286
|
+
const result = await proofVerifier.verifyProof(invalidProof, validJwk);
|
|
287
|
+
|
|
288
|
+
expect(result.valid).toBe(false);
|
|
289
|
+
expect(result.reason).toContain('Invalid proof structure');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should reject proof with missing required meta fields', async () => {
|
|
293
|
+
const invalidProof: DetachedProof = {
|
|
294
|
+
jws: 'header.payload.signature',
|
|
295
|
+
meta: {
|
|
296
|
+
did: 'did:test',
|
|
297
|
+
kid: 'kid',
|
|
298
|
+
ts: 123,
|
|
299
|
+
nonce: 'nonce',
|
|
300
|
+
audience: 'aud',
|
|
301
|
+
sessionId: 'session',
|
|
302
|
+
// Missing requestHash and responseHash
|
|
303
|
+
requestHash: '' as any,
|
|
304
|
+
responseHash: '' as any,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const result = await proofVerifier.verifyProof(invalidProof, validJwk);
|
|
309
|
+
|
|
310
|
+
expect(result.valid).toBe(false);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('Signature Verification', () => {
|
|
315
|
+
it('should reject proof with invalid signature', async () => {
|
|
316
|
+
const proof = createValidProof();
|
|
317
|
+
mockCryptoProvider.verify = vi.fn().mockResolvedValue(false);
|
|
318
|
+
|
|
319
|
+
const result = await proofVerifier.verifyProof(proof, validJwk);
|
|
320
|
+
|
|
321
|
+
expect(result.valid).toBe(false);
|
|
322
|
+
expect(result.reason).toContain('Invalid JWS signature');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should handle signature verification errors gracefully', async () => {
|
|
326
|
+
const proof = createValidProof();
|
|
327
|
+
mockCryptoProvider.verify = vi.fn().mockRejectedValue(
|
|
328
|
+
new Error('Crypto error')
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const result = await proofVerifier.verifyProof(proof, validJwk);
|
|
332
|
+
|
|
333
|
+
expect(result.valid).toBe(false);
|
|
334
|
+
expect(result.reason).toBeDefined();
|
|
335
|
+
// Should not throw, should return error result
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('verifyProofDetached', () => {
|
|
340
|
+
it('should verify proof with string canonical payload', async () => {
|
|
341
|
+
const proof = createValidProof();
|
|
342
|
+
const canonicalPayload = proofVerifier.buildCanonicalPayload(proof.meta);
|
|
343
|
+
|
|
344
|
+
const result = await proofVerifier.verifyProofDetached(
|
|
345
|
+
proof,
|
|
346
|
+
canonicalPayload,
|
|
347
|
+
validJwk
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
expect(result.valid).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should verify proof with Uint8Array canonical payload', async () => {
|
|
354
|
+
const proof = createValidProof();
|
|
355
|
+
const canonicalPayload = proofVerifier.buildCanonicalPayload(proof.meta);
|
|
356
|
+
const canonicalPayloadBytes = new TextEncoder().encode(canonicalPayload);
|
|
357
|
+
|
|
358
|
+
const result = await proofVerifier.verifyProofDetached(
|
|
359
|
+
proof,
|
|
360
|
+
canonicalPayloadBytes,
|
|
361
|
+
validJwk
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
expect(result.valid).toBe(true);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should prevent nonce replay in verifyProofDetached', async () => {
|
|
368
|
+
const proof = createValidProof();
|
|
369
|
+
const canonicalPayload = proofVerifier.buildCanonicalPayload(proof.meta);
|
|
370
|
+
|
|
371
|
+
// First verification
|
|
372
|
+
const result1 = await proofVerifier.verifyProofDetached(
|
|
373
|
+
proof,
|
|
374
|
+
canonicalPayload,
|
|
375
|
+
validJwk
|
|
376
|
+
);
|
|
377
|
+
expect(result1.valid).toBe(true);
|
|
378
|
+
|
|
379
|
+
// Second verification should fail
|
|
380
|
+
mockNonceCache.has = vi.fn().mockResolvedValue(true);
|
|
381
|
+
const result2 = await proofVerifier.verifyProofDetached(
|
|
382
|
+
proof,
|
|
383
|
+
canonicalPayload,
|
|
384
|
+
validJwk
|
|
385
|
+
);
|
|
386
|
+
expect(result2.valid).toBe(false);
|
|
387
|
+
expect(result2.reason).toContain('replay');
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe('Error Handling', () => {
|
|
392
|
+
it('should never throw on verification errors', async () => {
|
|
393
|
+
const proof = createValidProof();
|
|
394
|
+
|
|
395
|
+
// Simulate various error conditions
|
|
396
|
+
mockNonceCache.has = vi.fn().mockRejectedValue(new Error('Cache error'));
|
|
397
|
+
|
|
398
|
+
const result = await proofVerifier.verifyProof(proof, validJwk);
|
|
399
|
+
|
|
400
|
+
// Should return error result, not throw
|
|
401
|
+
expect(result.valid).toBe(false);
|
|
402
|
+
expect(result.reason).toBeDefined();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should handle clock provider errors gracefully', async () => {
|
|
406
|
+
const proof = createValidProof();
|
|
407
|
+
mockClockProvider.isWithinSkew = vi.fn().mockImplementation(() => {
|
|
408
|
+
throw new Error('Clock error');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const result = await proofVerifier.verifyProof(proof, validJwk);
|
|
412
|
+
|
|
413
|
+
expect(result.valid).toBe(false);
|
|
414
|
+
expect(result.reason).toBeDefined();
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe('fetchPublicKeyFromDID', () => {
|
|
419
|
+
it('should fetch public key from DID document', async () => {
|
|
420
|
+
const jwk = await proofVerifier.fetchPublicKeyFromDID('did:key:z123', 'key-1');
|
|
421
|
+
|
|
422
|
+
expect(jwk).toEqual(validJwk);
|
|
423
|
+
expect(mockFetchProvider.resolveDID).toHaveBeenCalledWith('did:key:z123');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should throw ProofVerificationError if DID document not found', async () => {
|
|
427
|
+
mockFetchProvider.resolveDID = vi.fn().mockResolvedValue(null);
|
|
428
|
+
|
|
429
|
+
await expect(
|
|
430
|
+
proofVerifier.fetchPublicKeyFromDID('did:key:z123')
|
|
431
|
+
).rejects.toThrow(ProofVerificationError);
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
await proofVerifier.fetchPublicKeyFromDID('did:key:z123');
|
|
435
|
+
} catch (error) {
|
|
436
|
+
expect(error).toBeInstanceOf(ProofVerificationError);
|
|
437
|
+
expect((error as ProofVerificationError).code).toBe(
|
|
438
|
+
PROOF_VERIFICATION_ERROR_CODES.DID_DOCUMENT_NOT_FOUND
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('should throw ProofVerificationError if verification method not found', async () => {
|
|
444
|
+
mockFetchProvider.resolveDID = vi.fn().mockResolvedValue({
|
|
445
|
+
verificationMethod: [],
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
await expect(
|
|
449
|
+
proofVerifier.fetchPublicKeyFromDID('did:key:z123', 'key-1')
|
|
450
|
+
).rejects.toThrow(ProofVerificationError);
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
await proofVerifier.fetchPublicKeyFromDID('did:key:z123', 'key-1');
|
|
454
|
+
} catch (error) {
|
|
455
|
+
expect(error).toBeInstanceOf(ProofVerificationError);
|
|
456
|
+
expect((error as ProofVerificationError).code).toBe(
|
|
457
|
+
PROOF_VERIFICATION_ERROR_CODES.VERIFICATION_METHOD_NOT_FOUND
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('should throw ProofVerificationError if JWK is not Ed25519', async () => {
|
|
463
|
+
mockFetchProvider.resolveDID = vi.fn().mockResolvedValue({
|
|
464
|
+
verificationMethod: [{
|
|
465
|
+
id: 'did:key:z123#key-1',
|
|
466
|
+
publicKeyJwk: {
|
|
467
|
+
kty: 'RSA',
|
|
468
|
+
crv: 'RS256',
|
|
469
|
+
n: 'invalid',
|
|
470
|
+
},
|
|
471
|
+
}],
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
await expect(
|
|
475
|
+
proofVerifier.fetchPublicKeyFromDID('did:key:z123')
|
|
476
|
+
).rejects.toThrow(ProofVerificationError);
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
await proofVerifier.fetchPublicKeyFromDID('did:key:z123');
|
|
480
|
+
} catch (error) {
|
|
481
|
+
expect(error).toBeInstanceOf(ProofVerificationError);
|
|
482
|
+
expect((error as ProofVerificationError).code).toBe(
|
|
483
|
+
PROOF_VERIFICATION_ERROR_CODES.INVALID_JWK_FORMAT
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Resolution Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests end-to-end provider resolution flow and backward compatibility.
|
|
5
|
+
*
|
|
6
|
+
* @package @kya-os/mcp-i-core
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
10
|
+
import { OAuthProviderRegistry } from "../oauth-provider-registry.js";
|
|
11
|
+
import { ProviderResolver } from "../provider-resolver.js";
|
|
12
|
+
import { OAuthConfigService } from "../oauth-config.service.js";
|
|
13
|
+
import { ToolProtectionService } from "../tool-protection.service.js";
|
|
14
|
+
import type { ToolProtection } from "@kya-os/contracts/tool-protection";
|
|
15
|
+
import type { OAuthConfig } from "@kya-os/contracts/config";
|
|
16
|
+
|
|
17
|
+
describe("Provider Resolution Integration", () => {
|
|
18
|
+
let mockConfigService: OAuthConfigService;
|
|
19
|
+
let mockToolProtectionService: ToolProtectionService;
|
|
20
|
+
let providerRegistry: OAuthProviderRegistry;
|
|
21
|
+
let providerResolver: ProviderResolver;
|
|
22
|
+
|
|
23
|
+
const mockOAuthConfig: OAuthConfig = {
|
|
24
|
+
providers: {
|
|
25
|
+
github: {
|
|
26
|
+
clientId: "github_client_id",
|
|
27
|
+
authorizationUrl: "https://github.com/login/oauth/authorize",
|
|
28
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
29
|
+
supportsPKCE: true,
|
|
30
|
+
requiresClientSecret: false,
|
|
31
|
+
},
|
|
32
|
+
google: {
|
|
33
|
+
clientId: "google_client_id",
|
|
34
|
+
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
35
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
36
|
+
supportsPKCE: true,
|
|
37
|
+
requiresClientSecret: false,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
// Explicitly configured provider (Priority 3 fallback)
|
|
41
|
+
configuredProvider: "github",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
mockConfigService = {
|
|
46
|
+
getOAuthConfig: vi.fn().mockResolvedValue(mockOAuthConfig),
|
|
47
|
+
} as any;
|
|
48
|
+
|
|
49
|
+
mockToolProtectionService = {
|
|
50
|
+
checkToolProtection: vi.fn(),
|
|
51
|
+
getProjectId: vi.fn().mockReturnValue("test-project"),
|
|
52
|
+
} as any;
|
|
53
|
+
|
|
54
|
+
providerRegistry = new OAuthProviderRegistry(mockConfigService);
|
|
55
|
+
providerResolver = new ProviderResolver(providerRegistry, mockConfigService);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("End-to-end provider resolution", () => {
|
|
59
|
+
it("should resolve provider for tool with oauthProvider field", async () => {
|
|
60
|
+
await providerRegistry.loadFromAgentShield("test-project");
|
|
61
|
+
|
|
62
|
+
const toolProtection: ToolProtection = {
|
|
63
|
+
requiresDelegation: true,
|
|
64
|
+
requiredScopes: ["repo:read"],
|
|
65
|
+
oauthProvider: "github",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const provider = await providerResolver.resolveProvider(
|
|
69
|
+
toolProtection,
|
|
70
|
+
"test-project"
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(provider).toBe("github");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should resolve provider via scope inference when oauthProvider not specified", async () => {
|
|
77
|
+
await providerRegistry.loadFromAgentShield("test-project");
|
|
78
|
+
|
|
79
|
+
const toolProtection: ToolProtection = {
|
|
80
|
+
requiresDelegation: true,
|
|
81
|
+
requiredScopes: ["github:repo:read"],
|
|
82
|
+
// No oauthProvider field (Phase 1 compatibility)
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
86
|
+
|
|
87
|
+
const provider = await providerResolver.resolveProvider(
|
|
88
|
+
toolProtection,
|
|
89
|
+
"test-project"
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(provider).toBe("github");
|
|
93
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
94
|
+
expect.stringContaining("Inferred provider")
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
consoleSpy.mockRestore();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should fall back to configuredProvider for Phase 1 compatibility", async () => {
|
|
101
|
+
await providerRegistry.loadFromAgentShield("test-project");
|
|
102
|
+
|
|
103
|
+
const toolProtection: ToolProtection = {
|
|
104
|
+
requiresDelegation: true,
|
|
105
|
+
requiredScopes: ["custom:scope"], // No recognizable prefix
|
|
106
|
+
// No oauthProvider field
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
110
|
+
|
|
111
|
+
const provider = await providerResolver.resolveProvider(
|
|
112
|
+
toolProtection,
|
|
113
|
+
"test-project"
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Should use configuredProvider (github)
|
|
117
|
+
expect(provider).toBe("github");
|
|
118
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
119
|
+
expect.stringContaining("project-configured provider")
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
consoleSpy.mockRestore();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("Backward compatibility", () => {
|
|
127
|
+
it("should work with Phase 1 tools (no oauthProvider field)", async () => {
|
|
128
|
+
await providerRegistry.loadFromAgentShield("test-project");
|
|
129
|
+
|
|
130
|
+
const toolProtection: ToolProtection = {
|
|
131
|
+
requiresDelegation: true,
|
|
132
|
+
requiredScopes: ["repo:read"],
|
|
133
|
+
// No oauthProvider - Phase 1 style
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Should not throw - uses fallback resolution
|
|
137
|
+
const provider = await providerResolver.resolveProvider(
|
|
138
|
+
toolProtection,
|
|
139
|
+
"test-project"
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(provider).toBeDefined();
|
|
143
|
+
expect(typeof provider).toBe("string");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should handle tools with empty requiredScopes", async () => {
|
|
147
|
+
await providerRegistry.loadFromAgentShield("test-project");
|
|
148
|
+
|
|
149
|
+
const toolProtection: ToolProtection = {
|
|
150
|
+
requiresDelegation: true,
|
|
151
|
+
requiredScopes: [],
|
|
152
|
+
// No oauthProvider
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
156
|
+
|
|
157
|
+
const provider = await providerResolver.resolveProvider(
|
|
158
|
+
toolProtection,
|
|
159
|
+
"test-project"
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Should fall back to configuredProvider
|
|
163
|
+
expect(provider).toBe("github");
|
|
164
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
165
|
+
expect.stringContaining("project-configured provider")
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
consoleSpy.mockRestore();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("Multi-provider scenarios", () => {
|
|
173
|
+
it("should resolve different providers for different tools", async () => {
|
|
174
|
+
await providerRegistry.loadFromAgentShield("test-project");
|
|
175
|
+
|
|
176
|
+
const githubTool: ToolProtection = {
|
|
177
|
+
requiresDelegation: true,
|
|
178
|
+
requiredScopes: ["repo:read"],
|
|
179
|
+
oauthProvider: "github",
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const googleTool: ToolProtection = {
|
|
183
|
+
requiresDelegation: true,
|
|
184
|
+
requiredScopes: ["calendar:read"],
|
|
185
|
+
oauthProvider: "google",
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const githubProvider = await providerResolver.resolveProvider(
|
|
189
|
+
githubTool,
|
|
190
|
+
"test-project"
|
|
191
|
+
);
|
|
192
|
+
const googleProvider = await providerResolver.resolveProvider(
|
|
193
|
+
googleTool,
|
|
194
|
+
"test-project"
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(githubProvider).toBe("github");
|
|
198
|
+
expect(googleProvider).toBe("google");
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|