@kya-os/mcp-i-core 1.3.13 → 1.3.15

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.
Files changed (255) hide show
  1. package/dist/config/remote-config.js +9 -12
  2. package/dist/runtime/base.d.ts +2 -1
  3. package/dist/runtime/base.js +34 -6
  4. package/dist/services/access-control.service.js +5 -0
  5. package/dist/services/tool-protection.service.js +17 -8
  6. package/package.json +2 -2
  7. package/.turbo/turbo-build.log +0 -4
  8. package/.turbo/turbo-test$colon$coverage.log +0 -4586
  9. package/.turbo/turbo-test.log +0 -4631
  10. package/COMPLIANCE_IMPROVEMENT_REPORT.md +0 -483
  11. package/Composer 3.md +0 -615
  12. package/GPT-5.md +0 -1169
  13. package/OPUS-plan.md +0 -352
  14. package/PHASE_3_AND_4.1_SUMMARY.md +0 -585
  15. package/PHASE_3_SUMMARY.md +0 -317
  16. package/PHASE_4.1.3_SUMMARY.md +0 -428
  17. package/PHASE_4.1_COMPLETE.md +0 -525
  18. package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +0 -1240
  19. package/SCHEMA_COMPLIANCE_REPORT.md +0 -275
  20. package/TEST_PLAN.md +0 -571
  21. package/coverage/coverage-final.json +0 -60
  22. package/dist/cache/oauth-config-cache.d.ts.map +0 -1
  23. package/dist/cache/oauth-config-cache.js.map +0 -1
  24. package/dist/cache/tool-protection-cache.d.ts.map +0 -1
  25. package/dist/cache/tool-protection-cache.js.map +0 -1
  26. package/dist/compliance/index.d.ts.map +0 -1
  27. package/dist/compliance/index.js.map +0 -1
  28. package/dist/compliance/schema-registry.d.ts.map +0 -1
  29. package/dist/compliance/schema-registry.js.map +0 -1
  30. package/dist/compliance/schema-verifier.d.ts.map +0 -1
  31. package/dist/compliance/schema-verifier.js.map +0 -1
  32. package/dist/config/remote-config.d.ts.map +0 -1
  33. package/dist/config/remote-config.js.map +0 -1
  34. package/dist/config.d.ts.map +0 -1
  35. package/dist/config.js.map +0 -1
  36. package/dist/delegation/audience-validator.d.ts.map +0 -1
  37. package/dist/delegation/audience-validator.js.map +0 -1
  38. package/dist/delegation/bitstring.d.ts.map +0 -1
  39. package/dist/delegation/bitstring.js.map +0 -1
  40. package/dist/delegation/cascading-revocation.d.ts.map +0 -1
  41. package/dist/delegation/cascading-revocation.js.map +0 -1
  42. package/dist/delegation/delegation-graph.d.ts.map +0 -1
  43. package/dist/delegation/delegation-graph.js.map +0 -1
  44. package/dist/delegation/did-key-resolver.d.ts.map +0 -1
  45. package/dist/delegation/did-key-resolver.js.map +0 -1
  46. package/dist/delegation/index.d.ts.map +0 -1
  47. package/dist/delegation/index.js.map +0 -1
  48. package/dist/delegation/statuslist-manager.d.ts.map +0 -1
  49. package/dist/delegation/statuslist-manager.js.map +0 -1
  50. package/dist/delegation/storage/index.d.ts.map +0 -1
  51. package/dist/delegation/storage/index.js.map +0 -1
  52. package/dist/delegation/storage/memory-graph-storage.d.ts.map +0 -1
  53. package/dist/delegation/storage/memory-graph-storage.js.map +0 -1
  54. package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +0 -1
  55. package/dist/delegation/storage/memory-statuslist-storage.js.map +0 -1
  56. package/dist/delegation/utils.d.ts.map +0 -1
  57. package/dist/delegation/utils.js.map +0 -1
  58. package/dist/delegation/vc-issuer.d.ts.map +0 -1
  59. package/dist/delegation/vc-issuer.js.map +0 -1
  60. package/dist/delegation/vc-verifier.d.ts.map +0 -1
  61. package/dist/delegation/vc-verifier.js.map +0 -1
  62. package/dist/identity/idp-token-resolver.d.ts.map +0 -1
  63. package/dist/identity/idp-token-resolver.js.map +0 -1
  64. package/dist/identity/idp-token-storage.interface.d.ts.map +0 -1
  65. package/dist/identity/idp-token-storage.interface.js.map +0 -1
  66. package/dist/identity/user-did-manager.d.ts.map +0 -1
  67. package/dist/identity/user-did-manager.js.map +0 -1
  68. package/dist/index.d.ts.map +0 -1
  69. package/dist/index.js.map +0 -1
  70. package/dist/providers/base.d.ts.map +0 -1
  71. package/dist/providers/base.js.map +0 -1
  72. package/dist/providers/memory.d.ts.map +0 -1
  73. package/dist/providers/memory.js.map +0 -1
  74. package/dist/runtime/audit-logger.d.ts.map +0 -1
  75. package/dist/runtime/audit-logger.js.map +0 -1
  76. package/dist/runtime/base.d.ts.map +0 -1
  77. package/dist/runtime/base.js.map +0 -1
  78. package/dist/services/access-control.service.d.ts.map +0 -1
  79. package/dist/services/access-control.service.js.map +0 -1
  80. package/dist/services/authorization/authorization-registry.d.ts.map +0 -1
  81. package/dist/services/authorization/authorization-registry.js.map +0 -1
  82. package/dist/services/authorization/types.d.ts.map +0 -1
  83. package/dist/services/authorization/types.js.map +0 -1
  84. package/dist/services/batch-delegation.service.d.ts.map +0 -1
  85. package/dist/services/batch-delegation.service.js.map +0 -1
  86. package/dist/services/crypto.service.d.ts.map +0 -1
  87. package/dist/services/crypto.service.js.map +0 -1
  88. package/dist/services/errors.d.ts.map +0 -1
  89. package/dist/services/errors.js.map +0 -1
  90. package/dist/services/index.d.ts.map +0 -1
  91. package/dist/services/index.js.map +0 -1
  92. package/dist/services/oauth-config.service.d.ts.map +0 -1
  93. package/dist/services/oauth-config.service.js.map +0 -1
  94. package/dist/services/oauth-provider-registry.d.ts.map +0 -1
  95. package/dist/services/oauth-provider-registry.js.map +0 -1
  96. package/dist/services/oauth-service.d.ts.map +0 -1
  97. package/dist/services/oauth-service.js.map +0 -1
  98. package/dist/services/oauth-token-retrieval.service.d.ts.map +0 -1
  99. package/dist/services/oauth-token-retrieval.service.js.map +0 -1
  100. package/dist/services/proof-verifier.d.ts.map +0 -1
  101. package/dist/services/proof-verifier.js.map +0 -1
  102. package/dist/services/provider-resolver.d.ts.map +0 -1
  103. package/dist/services/provider-resolver.js.map +0 -1
  104. package/dist/services/provider-validator.d.ts.map +0 -1
  105. package/dist/services/provider-validator.js.map +0 -1
  106. package/dist/services/session-registration.service.d.ts.map +0 -1
  107. package/dist/services/session-registration.service.js.map +0 -1
  108. package/dist/services/storage.service.d.ts.map +0 -1
  109. package/dist/services/storage.service.js.map +0 -1
  110. package/dist/services/tool-context-builder.d.ts.map +0 -1
  111. package/dist/services/tool-context-builder.js.map +0 -1
  112. package/dist/services/tool-protection.service.d.ts.map +0 -1
  113. package/dist/services/tool-protection.service.js.map +0 -1
  114. package/dist/types/oauth-required-error.d.ts.map +0 -1
  115. package/dist/types/oauth-required-error.js.map +0 -1
  116. package/dist/types/tool-protection.d.ts.map +0 -1
  117. package/dist/types/tool-protection.js.map +0 -1
  118. package/dist/utils/base58.d.ts.map +0 -1
  119. package/dist/utils/base58.js.map +0 -1
  120. package/dist/utils/base64.d.ts.map +0 -1
  121. package/dist/utils/base64.js.map +0 -1
  122. package/dist/utils/cors.d.ts.map +0 -1
  123. package/dist/utils/cors.js.map +0 -1
  124. package/dist/utils/did-helpers.d.ts.map +0 -1
  125. package/dist/utils/did-helpers.js.map +0 -1
  126. package/dist/utils/index.d.ts.map +0 -1
  127. package/dist/utils/index.js.map +0 -1
  128. package/dist/utils/storage-keys.d.ts.map +0 -1
  129. package/dist/utils/storage-keys.js.map +0 -1
  130. package/docs/API_REFERENCE.md +0 -1362
  131. package/docs/COMPLIANCE_MATRIX.md +0 -691
  132. package/docs/STATUSLIST2021_GUIDE.md +0 -696
  133. package/docs/W3C_VC_DELEGATION_GUIDE.md +0 -710
  134. package/src/__tests__/cache/tool-protection-cache.test.ts +0 -640
  135. package/src/__tests__/config/provider-runtime-config.test.ts +0 -309
  136. package/src/__tests__/delegation-e2e.test.ts +0 -690
  137. package/src/__tests__/identity/user-did-manager.test.ts +0 -232
  138. package/src/__tests__/index.test.ts +0 -56
  139. package/src/__tests__/integration/full-flow.test.ts +0 -789
  140. package/src/__tests__/integration.test.ts +0 -281
  141. package/src/__tests__/providers/base.test.ts +0 -173
  142. package/src/__tests__/providers/memory.test.ts +0 -319
  143. package/src/__tests__/regression/phase2-regression.test.ts +0 -429
  144. package/src/__tests__/runtime/audit-logger.test.ts +0 -154
  145. package/src/__tests__/runtime/base-extensions.test.ts +0 -595
  146. package/src/__tests__/runtime/base.test.ts +0 -869
  147. package/src/__tests__/runtime/delegation-flow.test.ts +0 -164
  148. package/src/__tests__/runtime/proof-client-did.test.ts +0 -376
  149. package/src/__tests__/runtime/route-interception.test.ts +0 -686
  150. package/src/__tests__/runtime/tool-protection-enforcement.test.ts +0 -908
  151. package/src/__tests__/services/agentshield-integration.test.ts +0 -791
  152. package/src/__tests__/services/cache-busting.test.ts +0 -125
  153. package/src/__tests__/services/oauth-service-pkce.test.ts +0 -556
  154. package/src/__tests__/services/provider-resolver-edge-cases.test.ts +0 -591
  155. package/src/__tests__/services/tool-protection-merged-config.test.ts +0 -485
  156. package/src/__tests__/services/tool-protection-oauth-provider.test.ts +0 -480
  157. package/src/__tests__/services/tool-protection.service.test.ts +0 -1373
  158. package/src/__tests__/utils/mock-providers.ts +0 -340
  159. package/src/cache/oauth-config-cache.d.ts +0 -69
  160. package/src/cache/oauth-config-cache.d.ts.map +0 -1
  161. package/src/cache/oauth-config-cache.js.map +0 -1
  162. package/src/cache/oauth-config-cache.ts +0 -123
  163. package/src/cache/tool-protection-cache.ts +0 -171
  164. package/src/compliance/EXAMPLE.md +0 -412
  165. package/src/compliance/__tests__/schema-verifier.test.ts +0 -797
  166. package/src/compliance/index.ts +0 -8
  167. package/src/compliance/schema-registry.ts +0 -460
  168. package/src/compliance/schema-verifier.ts +0 -708
  169. package/src/config/__tests__/merged-config.spec.ts +0 -445
  170. package/src/config/__tests__/remote-config.spec.ts +0 -268
  171. package/src/config/remote-config.ts +0 -264
  172. package/src/config.ts +0 -312
  173. package/src/delegation/__tests__/audience-validator.test.ts +0 -112
  174. package/src/delegation/__tests__/bitstring.test.ts +0 -346
  175. package/src/delegation/__tests__/cascading-revocation.test.ts +0 -628
  176. package/src/delegation/__tests__/delegation-graph.test.ts +0 -584
  177. package/src/delegation/__tests__/did-key-resolver.test.ts +0 -265
  178. package/src/delegation/__tests__/utils.test.ts +0 -152
  179. package/src/delegation/__tests__/vc-issuer.test.ts +0 -442
  180. package/src/delegation/__tests__/vc-verifier.test.ts +0 -922
  181. package/src/delegation/audience-validator.ts +0 -52
  182. package/src/delegation/bitstring.ts +0 -278
  183. package/src/delegation/cascading-revocation.ts +0 -370
  184. package/src/delegation/delegation-graph.ts +0 -299
  185. package/src/delegation/did-key-resolver.ts +0 -179
  186. package/src/delegation/index.ts +0 -14
  187. package/src/delegation/statuslist-manager.ts +0 -353
  188. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +0 -366
  189. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +0 -228
  190. package/src/delegation/storage/index.ts +0 -9
  191. package/src/delegation/storage/memory-graph-storage.ts +0 -178
  192. package/src/delegation/storage/memory-statuslist-storage.ts +0 -77
  193. package/src/delegation/utils.ts +0 -221
  194. package/src/delegation/vc-issuer.ts +0 -232
  195. package/src/delegation/vc-verifier.ts +0 -568
  196. package/src/identity/idp-token-resolver.ts +0 -181
  197. package/src/identity/idp-token-storage.interface.ts +0 -94
  198. package/src/identity/user-did-manager.ts +0 -526
  199. package/src/index.ts +0 -310
  200. package/src/providers/base.d.ts +0 -91
  201. package/src/providers/base.d.ts.map +0 -1
  202. package/src/providers/base.js.map +0 -1
  203. package/src/providers/base.ts +0 -96
  204. package/src/providers/memory.ts +0 -142
  205. package/src/runtime/audit-logger.ts +0 -39
  206. package/src/runtime/base.ts +0 -1392
  207. package/src/services/__tests__/access-control.integration.test.ts +0 -443
  208. package/src/services/__tests__/access-control.proof-response-validation.test.ts +0 -578
  209. package/src/services/__tests__/access-control.service.test.ts +0 -970
  210. package/src/services/__tests__/batch-delegation.service.test.ts +0 -351
  211. package/src/services/__tests__/crypto.service.test.ts +0 -531
  212. package/src/services/__tests__/oauth-provider-registry.test.ts +0 -142
  213. package/src/services/__tests__/proof-verifier.integration.test.ts +0 -485
  214. package/src/services/__tests__/proof-verifier.test.ts +0 -489
  215. package/src/services/__tests__/provider-resolution.integration.test.ts +0 -202
  216. package/src/services/__tests__/provider-resolver.test.ts +0 -213
  217. package/src/services/__tests__/storage.service.test.ts +0 -358
  218. package/src/services/access-control.service.ts +0 -990
  219. package/src/services/authorization/authorization-registry.ts +0 -66
  220. package/src/services/authorization/types.ts +0 -71
  221. package/src/services/batch-delegation.service.ts +0 -137
  222. package/src/services/crypto.service.ts +0 -302
  223. package/src/services/errors.ts +0 -76
  224. package/src/services/index.ts +0 -18
  225. package/src/services/oauth-config.service.d.ts +0 -53
  226. package/src/services/oauth-config.service.d.ts.map +0 -1
  227. package/src/services/oauth-config.service.js.map +0 -1
  228. package/src/services/oauth-config.service.ts +0 -192
  229. package/src/services/oauth-provider-registry.d.ts +0 -57
  230. package/src/services/oauth-provider-registry.d.ts.map +0 -1
  231. package/src/services/oauth-provider-registry.js.map +0 -1
  232. package/src/services/oauth-provider-registry.ts +0 -141
  233. package/src/services/oauth-service.ts +0 -544
  234. package/src/services/oauth-token-retrieval.service.ts +0 -245
  235. package/src/services/proof-verifier.ts +0 -478
  236. package/src/services/provider-resolver.d.ts +0 -48
  237. package/src/services/provider-resolver.d.ts.map +0 -1
  238. package/src/services/provider-resolver.js.map +0 -1
  239. package/src/services/provider-resolver.ts +0 -146
  240. package/src/services/provider-validator.ts +0 -170
  241. package/src/services/session-registration.service.ts +0 -251
  242. package/src/services/storage.service.ts +0 -566
  243. package/src/services/tool-context-builder.ts +0 -237
  244. package/src/services/tool-protection.service.ts +0 -1070
  245. package/src/types/oauth-required-error.ts +0 -63
  246. package/src/types/tool-protection.ts +0 -155
  247. package/src/utils/__tests__/did-helpers.test.ts +0 -156
  248. package/src/utils/base58.ts +0 -109
  249. package/src/utils/base64.ts +0 -148
  250. package/src/utils/cors.ts +0 -83
  251. package/src/utils/did-helpers.ts +0 -210
  252. package/src/utils/index.ts +0 -8
  253. package/src/utils/storage-keys.ts +0 -278
  254. package/tsconfig.json +0 -21
  255. package/vitest.config.ts +0 -56
@@ -1,125 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { ToolProtectionService } from "../../services/tool-protection.service";
3
- import { InMemoryToolProtectionCache } from "../../cache/tool-protection-cache";
4
-
5
- describe("Cache Busting in clearAndRefresh", () => {
6
- let cache: InMemoryToolProtectionCache;
7
- let service: ToolProtectionService;
8
- const agentDid = "did:key:test-agent";
9
- const projectId = "test-project-123";
10
-
11
- beforeEach(() => {
12
- cache = new InMemoryToolProtectionCache();
13
- service = new ToolProtectionService(
14
- {
15
- apiUrl: "https://test.agentshield.com",
16
- apiKey: "test-api-key",
17
- projectId,
18
- cacheTtl: 300000,
19
- debug: true,
20
- },
21
- cache
22
- );
23
- });
24
-
25
- it("clearAndRefresh should call fetchFromApi with bypassCDNCache: true", async () => {
26
- // Spy on the private fetchFromApi method
27
- const fetchSpy = vi.spyOn(service as any, "fetchFromApi").mockResolvedValue({
28
- success: true,
29
- data: {
30
- toolProtections: {
31
- greet: { requiresDelegation: true, requiredScopes: ["greet:execute"] },
32
- },
33
- },
34
- });
35
-
36
- await service.clearAndRefresh(agentDid);
37
-
38
- // Verify fetchFromApi was called with bypassCDNCache: true
39
- expect(fetchSpy).toHaveBeenCalledTimes(1);
40
- expect(fetchSpy).toHaveBeenCalledWith(agentDid, { bypassCDNCache: true });
41
- });
42
-
43
- it("fetchFromApi should add cache-busting query param when bypassCDNCache is true", async () => {
44
- // Mock global fetch
45
- const fetchMock = vi.fn().mockResolvedValue({
46
- ok: true,
47
- json: () => Promise.resolve({ success: true, data: { toolProtections: {} } }),
48
- });
49
- global.fetch = fetchMock;
50
-
51
- // Call fetchFromApi directly with bypassCDNCache: true
52
- await (service as any).fetchFromApi(agentDid, { bypassCDNCache: true });
53
-
54
- // Verify the URL contains cache-busting timestamp
55
- const calledUrl = fetchMock.mock.calls[0][0] as string;
56
- expect(calledUrl).toContain("?_t=");
57
- expect(calledUrl).toMatch(/\?_t=\d+$/); // Ends with ?_t=<timestamp>
58
- });
59
-
60
- it("fetchFromApi should add Cache-Control headers when bypassCDNCache is true", async () => {
61
- // Mock global fetch
62
- const fetchMock = vi.fn().mockResolvedValue({
63
- ok: true,
64
- json: () => Promise.resolve({ success: true, data: { toolProtections: {} } }),
65
- });
66
- global.fetch = fetchMock;
67
-
68
- // Call fetchFromApi directly with bypassCDNCache: true
69
- await (service as any).fetchFromApi(agentDid, { bypassCDNCache: true });
70
-
71
- // Verify the headers contain cache-control
72
- const calledOptions = fetchMock.mock.calls[0][1] as RequestInit;
73
- const headers = calledOptions.headers as Record<string, string>;
74
-
75
- expect(headers["Cache-Control"]).toBe("no-cache, no-store, must-revalidate");
76
- expect(headers["Pragma"]).toBe("no-cache");
77
- });
78
-
79
- it("fetchFromApi should NOT add cache-busting when bypassCDNCache is false/undefined", async () => {
80
- // Mock global fetch
81
- const fetchMock = vi.fn().mockResolvedValue({
82
- ok: true,
83
- json: () => Promise.resolve({ success: true, data: { toolProtections: {} } }),
84
- });
85
- global.fetch = fetchMock;
86
-
87
- // Call fetchFromApi without bypassCDNCache
88
- await (service as any).fetchFromApi(agentDid);
89
-
90
- // Verify the URL does NOT contain cache-busting timestamp
91
- const calledUrl = fetchMock.mock.calls[0][0] as string;
92
- expect(calledUrl).not.toContain("?_t=");
93
-
94
- // Verify no cache-control header
95
- const calledOptions = fetchMock.mock.calls[0][1] as RequestInit;
96
- const headers = calledOptions.headers as Record<string, string>;
97
- expect(headers["Cache-Control"]).toBeUndefined();
98
- });
99
-
100
- it("cache should be empty after clearAndRefresh deletes it", async () => {
101
- const cacheKey = `config:tool-protections:${projectId}`;
102
-
103
- // Pre-populate cache
104
- await cache.set(cacheKey, { toolProtections: { old: { requiresDelegation: false, requiredScopes: [] } } }, 300000);
105
-
106
- // Verify cache has data
107
- const before = await cache.get(cacheKey);
108
- expect(before).not.toBeNull();
109
-
110
- // Mock fetchFromApi to avoid network call
111
- vi.spyOn(service as any, "fetchFromApi").mockResolvedValue({
112
- success: true,
113
- data: { toolProtections: {} },
114
- });
115
-
116
- // Clear and refresh
117
- await service.clearAndRefresh(agentDid);
118
-
119
- // Note: clearAndRefresh deletes then re-populates with fresh data
120
- // But since our mock returns empty toolProtections, cache should have empty config
121
- const after = await cache.get(cacheKey);
122
- expect(after).toBeDefined();
123
- });
124
- });
125
-
@@ -1,556 +0,0 @@
1
- /**
2
- * Tests for OAuthService PKCE Token Exchange
3
- *
4
- * Verifies:
5
- * 1. Successful PKCE token exchange
6
- * 2. Handling of GitHub-style 200-status errors
7
- * 3. Handling of standard 4xx errors
8
- * 4. Token response validation
9
- */
10
-
11
- import { describe, it, expect, vi, beforeEach } from "vitest";
12
- import { OAuthService } from "../../services/oauth-service";
13
- import type { OAuthConfigService } from "../../services/oauth-config.service";
14
- import type { FetchProvider } from "../../providers/types";
15
- import type { OAuthProvider } from "@kya-os/contracts/config";
16
-
17
- describe("OAuthService PKCE Token Exchange", () => {
18
- let mockConfigService: OAuthConfigService;
19
- let mockFetchProvider: FetchProvider;
20
- let oauthService: OAuthService;
21
-
22
- const mockGitHubProvider: OAuthProvider = {
23
- clientId: "test-client-id",
24
- clientSecret: null,
25
- authorizationUrl: "https://github.com/login/oauth/authorize",
26
- tokenUrl: "https://github.com/login/oauth/access_token",
27
- supportsPKCE: true,
28
- requiresClientSecret: false,
29
- scopes: [],
30
- };
31
-
32
- const mockOAuthConfig = {
33
- providers: {
34
- github: mockGitHubProvider,
35
- },
36
- configuredProvider: "github",
37
- };
38
-
39
- beforeEach(() => {
40
- mockConfigService = {
41
- getOAuthConfig: vi.fn().mockResolvedValue(mockOAuthConfig),
42
- } as unknown as OAuthConfigService;
43
-
44
- mockFetchProvider = {
45
- fetch: vi.fn(),
46
- resolveDID: vi.fn(),
47
- fetchStatusList: vi.fn(),
48
- fetchDelegationChain: vi.fn(),
49
- };
50
-
51
- oauthService = new OAuthService({
52
- configService: mockConfigService,
53
- fetchProvider: mockFetchProvider,
54
- agentShieldApiUrl: "https://test.agentshield.com",
55
- agentShieldApiKey: "test-api-key",
56
- projectId: "test-project",
57
- logger: vi.fn(),
58
- });
59
- });
60
-
61
- describe("Successful Token Exchange", () => {
62
- it("should exchange code for access_token successfully", async () => {
63
- const mockTokenResponse = {
64
- access_token: "gho_test_access_token_123",
65
- token_type: "bearer",
66
- scope: "greet:execute",
67
- expires_in: 3600,
68
- };
69
-
70
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
71
- ok: true,
72
- json: () => Promise.resolve(mockTokenResponse),
73
- });
74
-
75
- const result = await oauthService.exchangeToken(
76
- "github",
77
- "test-auth-code",
78
- "test-code-verifier",
79
- "https://test.workers.dev/oauth/callback"
80
- );
81
-
82
- expect(result.access_token).toBe("gho_test_access_token_123");
83
- expect(result.token_type).toBe("bearer");
84
- expect(result.scope).toBe("greet:execute");
85
- expect(result.expires_in).toBe(3600);
86
- expect(result.expires_at).toBeGreaterThan(Date.now());
87
- });
88
-
89
- it("should handle missing expires_in with default value", async () => {
90
- const mockTokenResponse = {
91
- access_token: "gho_test_access_token_123",
92
- token_type: "bearer",
93
- // No expires_in - should default to 3600
94
- };
95
-
96
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
97
- ok: true,
98
- json: () => Promise.resolve(mockTokenResponse),
99
- });
100
-
101
- const result = await oauthService.exchangeToken(
102
- "github",
103
- "test-auth-code",
104
- "test-code-verifier",
105
- "https://test.workers.dev/oauth/callback"
106
- );
107
-
108
- expect(result.expires_in).toBe(3600);
109
- });
110
-
111
- it("should handle refresh_token when provided", async () => {
112
- const mockTokenResponse = {
113
- access_token: "gho_test_access_token_123",
114
- refresh_token: "ghr_test_refresh_token_456",
115
- token_type: "bearer",
116
- expires_in: 3600,
117
- };
118
-
119
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
120
- ok: true,
121
- json: () => Promise.resolve(mockTokenResponse),
122
- });
123
-
124
- const result = await oauthService.exchangeToken(
125
- "github",
126
- "test-auth-code",
127
- "test-code-verifier",
128
- "https://test.workers.dev/oauth/callback"
129
- );
130
-
131
- expect(result.refresh_token).toBe("ghr_test_refresh_token_456");
132
- });
133
- });
134
-
135
- describe("GitHub 200-Status Error Handling", () => {
136
- it("should handle bad_verification_code error (200 status)", async () => {
137
- const mockErrorResponse = {
138
- error: "bad_verification_code",
139
- error_description: "The code passed is incorrect or expired.",
140
- error_uri:
141
- "https://docs.github.com/apps/managing-oauth-apps/troubleshooting-oauth-app-access-token-request-errors/#bad-verification-code",
142
- };
143
-
144
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
145
- ok: true, // GitHub returns 200 even for errors!
146
- json: () => Promise.resolve(mockErrorResponse),
147
- });
148
-
149
- await expect(
150
- oauthService.exchangeToken(
151
- "github",
152
- "expired-code",
153
- "test-code-verifier",
154
- "https://test.workers.dev/oauth/callback"
155
- )
156
- ).rejects.toThrow("The code passed is incorrect or expired.");
157
- });
158
-
159
- it("should handle incorrect_client_credentials error (200 status)", async () => {
160
- const mockErrorResponse = {
161
- error: "incorrect_client_credentials",
162
- error_description:
163
- "The client_id and/or client_secret passed are incorrect.",
164
- };
165
-
166
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
167
- ok: true,
168
- json: () => Promise.resolve(mockErrorResponse),
169
- });
170
-
171
- await expect(
172
- oauthService.exchangeToken(
173
- "github",
174
- "test-code",
175
- "test-code-verifier",
176
- "https://test.workers.dev/oauth/callback"
177
- )
178
- ).rejects.toThrow(
179
- "The client_id and/or client_secret passed are incorrect."
180
- );
181
- });
182
-
183
- it("should handle redirect_uri_mismatch error (200 status)", async () => {
184
- const mockErrorResponse = {
185
- error: "redirect_uri_mismatch",
186
- error_description:
187
- "The redirect_uri MUST match the registered callback URL for this application.",
188
- };
189
-
190
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
191
- ok: true,
192
- json: () => Promise.resolve(mockErrorResponse),
193
- });
194
-
195
- await expect(
196
- oauthService.exchangeToken(
197
- "github",
198
- "test-code",
199
- "test-code-verifier",
200
- "https://wrong-redirect.workers.dev/oauth/callback"
201
- )
202
- ).rejects.toThrow("The redirect_uri MUST match the registered callback");
203
- });
204
-
205
- it("should fallback to error code when error_description is missing", async () => {
206
- const mockErrorResponse = {
207
- error: "access_denied",
208
- // No error_description
209
- };
210
-
211
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
212
- ok: true,
213
- json: () => Promise.resolve(mockErrorResponse),
214
- });
215
-
216
- await expect(
217
- oauthService.exchangeToken(
218
- "github",
219
- "test-code",
220
- "test-code-verifier",
221
- "https://test.workers.dev/oauth/callback"
222
- )
223
- ).rejects.toThrow("access_denied");
224
- });
225
- });
226
-
227
- describe("Standard HTTP Error Handling", () => {
228
- it("should handle 400 Bad Request error", async () => {
229
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
230
- ok: false,
231
- status: 400,
232
- text: () =>
233
- Promise.resolve(
234
- JSON.stringify({
235
- error: "invalid_request",
236
- error_description: "Missing required parameter",
237
- })
238
- ),
239
- });
240
-
241
- await expect(
242
- oauthService.exchangeToken(
243
- "github",
244
- "test-code",
245
- "test-code-verifier",
246
- "https://test.workers.dev/oauth/callback"
247
- )
248
- ).rejects.toThrow("Missing required parameter");
249
- });
250
-
251
- it("should handle 401 Unauthorized error", async () => {
252
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
253
- ok: false,
254
- status: 401,
255
- text: () =>
256
- Promise.resolve(
257
- JSON.stringify({
258
- error: "invalid_client",
259
- error_description: "Client authentication failed",
260
- })
261
- ),
262
- });
263
-
264
- await expect(
265
- oauthService.exchangeToken(
266
- "github",
267
- "test-code",
268
- "test-code-verifier",
269
- "https://test.workers.dev/oauth/callback"
270
- )
271
- ).rejects.toThrow("Client authentication failed");
272
- });
273
-
274
- it("should handle non-JSON error response", async () => {
275
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
276
- ok: false,
277
- status: 500,
278
- text: () => Promise.resolve("Internal Server Error"),
279
- });
280
-
281
- await expect(
282
- oauthService.exchangeToken(
283
- "github",
284
- "test-code",
285
- "test-code-verifier",
286
- "https://test.workers.dev/oauth/callback"
287
- )
288
- ).rejects.toThrow("Internal Server Error");
289
- });
290
- });
291
-
292
- describe("Token Response Validation", () => {
293
- it("should throw error when access_token is missing from valid response", async () => {
294
- const mockInvalidResponse = {
295
- token_type: "bearer",
296
- scope: "greet:execute",
297
- // Missing access_token
298
- };
299
-
300
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
301
- ok: true,
302
- json: () => Promise.resolve(mockInvalidResponse),
303
- });
304
-
305
- await expect(
306
- oauthService.exchangeToken(
307
- "github",
308
- "test-code",
309
- "test-code-verifier",
310
- "https://test.workers.dev/oauth/callback"
311
- )
312
- ).rejects.toThrow("Token response missing access_token");
313
- });
314
-
315
- it("should throw error when access_token is empty string", async () => {
316
- const mockInvalidResponse = {
317
- access_token: "",
318
- token_type: "bearer",
319
- };
320
-
321
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
322
- ok: true,
323
- json: () => Promise.resolve(mockInvalidResponse),
324
- });
325
-
326
- await expect(
327
- oauthService.exchangeToken(
328
- "github",
329
- "test-code",
330
- "test-code-verifier",
331
- "https://test.workers.dev/oauth/callback"
332
- )
333
- ).rejects.toThrow("Token response missing access_token");
334
- });
335
- });
336
-
337
- describe("PKCE Request Format", () => {
338
- it("should send correct PKCE parameters", async () => {
339
- const mockTokenResponse = {
340
- access_token: "gho_test_access_token_123",
341
- };
342
-
343
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
344
- ok: true,
345
- json: () => Promise.resolve(mockTokenResponse),
346
- });
347
-
348
- await oauthService.exchangeToken(
349
- "github",
350
- "test-auth-code",
351
- "test-code-verifier-12345",
352
- "https://test.workers.dev/oauth/callback"
353
- );
354
-
355
- expect(mockFetchProvider.fetch).toHaveBeenCalledWith(
356
- "https://github.com/login/oauth/access_token",
357
- expect.objectContaining({
358
- method: "POST",
359
- headers: expect.objectContaining({
360
- "Content-Type": "application/x-www-form-urlencoded",
361
- Accept: "application/json",
362
- }),
363
- })
364
- );
365
-
366
- // Verify request body contains correct PKCE parameters
367
- const callArgs = (mockFetchProvider.fetch as ReturnType<typeof vi.fn>)
368
- .mock.calls[0];
369
- const body = callArgs[1].body as string;
370
-
371
- expect(body).toContain("grant_type=authorization_code");
372
- expect(body).toContain("code=test-auth-code");
373
- expect(body).toContain("code_verifier=test-code-verifier-12345");
374
- expect(body).toContain("client_id=test-client-id");
375
- expect(body).toContain(
376
- "redirect_uri=https%3A%2F%2Ftest.workers.dev%2Foauth%2Fcallback"
377
- );
378
- });
379
- });
380
-
381
- describe("Provider Not Found", () => {
382
- it("should throw error for unconfigured provider", async () => {
383
- await expect(
384
- oauthService.exchangeToken(
385
- "unknown-provider",
386
- "test-code",
387
- "test-code-verifier",
388
- "https://test.workers.dev/oauth/callback"
389
- )
390
- ).rejects.toThrow('Provider "unknown-provider" not configured');
391
- });
392
- });
393
-
394
- describe("PKCE Requirement", () => {
395
- it("should throw error when codeVerifier is missing for PKCE provider", async () => {
396
- await expect(
397
- oauthService.exchangeToken(
398
- "github",
399
- "test-code",
400
- undefined, // Missing code_verifier
401
- "https://test.workers.dev/oauth/callback"
402
- )
403
- ).rejects.toThrow('Provider "github" requires PKCE code_verifier');
404
- });
405
- });
406
-
407
- describe("Client Secret Handling in PKCE", () => {
408
- it("should include client_secret when provider has one configured (GitHub OAuth Apps)", async () => {
409
- // GitHub OAuth Apps require client_secret even with PKCE
410
- const mockGitHubOAuthAppProvider: OAuthProvider = {
411
- clientId: "github-oauth-app-client-id",
412
- clientSecret: "github-oauth-app-client-secret",
413
- authorizationUrl: "https://github.com/login/oauth/authorize",
414
- tokenUrl: "https://github.com/login/oauth/access_token",
415
- supportsPKCE: true,
416
- requiresClientSecret: true,
417
- scopes: [],
418
- };
419
-
420
- const mockConfigWithSecret = {
421
- providers: {
422
- github: mockGitHubOAuthAppProvider,
423
- },
424
- configuredProvider: "github",
425
- };
426
-
427
- (mockConfigService.getOAuthConfig as ReturnType<typeof vi.fn>).mockResolvedValue(
428
- mockConfigWithSecret
429
- );
430
-
431
- const mockTokenResponse = {
432
- access_token: "gho_test_access_token_123",
433
- token_type: "bearer",
434
- };
435
-
436
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
437
- ok: true,
438
- json: () => Promise.resolve(mockTokenResponse),
439
- });
440
-
441
- await oauthService.exchangeToken(
442
- "github",
443
- "test-auth-code",
444
- "test-code-verifier",
445
- "https://test.workers.dev/oauth/callback"
446
- );
447
-
448
- // Verify client_secret is included in request body
449
- const callArgs = (mockFetchProvider.fetch as ReturnType<typeof vi.fn>)
450
- .mock.calls[0];
451
- const body = callArgs[1].body as string;
452
-
453
- expect(body).toContain("client_id=github-oauth-app-client-id");
454
- expect(body).toContain("client_secret=github-oauth-app-client-secret");
455
- expect(body).toContain("code_verifier=test-code-verifier");
456
- });
457
-
458
- it("should NOT include client_secret when provider has null clientSecret (GitHub Apps)", async () => {
459
- // GitHub Apps (not OAuth Apps) can use PKCE without client_secret
460
- const mockGitHubAppProvider: OAuthProvider = {
461
- clientId: "github-app-client-id",
462
- clientSecret: null, // No secret for GitHub Apps with PKCE
463
- authorizationUrl: "https://github.com/login/oauth/authorize",
464
- tokenUrl: "https://github.com/login/oauth/access_token",
465
- supportsPKCE: true,
466
- requiresClientSecret: false,
467
- scopes: [],
468
- };
469
-
470
- const mockConfigWithoutSecret = {
471
- providers: {
472
- github: mockGitHubAppProvider,
473
- },
474
- configuredProvider: "github",
475
- };
476
-
477
- (mockConfigService.getOAuthConfig as ReturnType<typeof vi.fn>).mockResolvedValue(
478
- mockConfigWithoutSecret
479
- );
480
-
481
- const mockTokenResponse = {
482
- access_token: "gho_test_access_token_123",
483
- token_type: "bearer",
484
- };
485
-
486
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
487
- ok: true,
488
- json: () => Promise.resolve(mockTokenResponse),
489
- });
490
-
491
- await oauthService.exchangeToken(
492
- "github",
493
- "test-auth-code",
494
- "test-code-verifier",
495
- "https://test.workers.dev/oauth/callback"
496
- );
497
-
498
- // Verify client_secret is NOT included in request body
499
- const callArgs = (mockFetchProvider.fetch as ReturnType<typeof vi.fn>)
500
- .mock.calls[0];
501
- const body = callArgs[1].body as string;
502
-
503
- expect(body).toContain("client_id=github-app-client-id");
504
- expect(body).not.toContain("client_secret");
505
- expect(body).toContain("code_verifier=test-code-verifier");
506
- });
507
-
508
- it("should NOT include client_secret when it is empty string", async () => {
509
- const mockProviderWithEmptySecret: OAuthProvider = {
510
- clientId: "test-client-id",
511
- clientSecret: "", // Empty string should be treated as no secret
512
- authorizationUrl: "https://example.com/oauth/authorize",
513
- tokenUrl: "https://example.com/oauth/token",
514
- supportsPKCE: true,
515
- requiresClientSecret: false,
516
- scopes: [],
517
- };
518
-
519
- const mockConfigWithEmptySecret = {
520
- providers: {
521
- github: mockProviderWithEmptySecret,
522
- },
523
- configuredProvider: "github",
524
- };
525
-
526
- (mockConfigService.getOAuthConfig as ReturnType<typeof vi.fn>).mockResolvedValue(
527
- mockConfigWithEmptySecret
528
- );
529
-
530
- const mockTokenResponse = {
531
- access_token: "test_access_token",
532
- token_type: "bearer",
533
- };
534
-
535
- (mockFetchProvider.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
536
- ok: true,
537
- json: () => Promise.resolve(mockTokenResponse),
538
- });
539
-
540
- await oauthService.exchangeToken(
541
- "github",
542
- "test-auth-code",
543
- "test-code-verifier",
544
- "https://test.workers.dev/oauth/callback"
545
- );
546
-
547
- // Verify client_secret is NOT included when empty
548
- const callArgs = (mockFetchProvider.fetch as ReturnType<typeof vi.fn>)
549
- .mock.calls[0];
550
- const body = callArgs[1].body as string;
551
-
552
- expect(body).not.toContain("client_secret");
553
- });
554
- });
555
- });
556
-