@kya-os/mcp-i-core 1.3.12 → 1.3.14

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 (254) hide show
  1. package/dist/config/remote-config.js +9 -12
  2. package/dist/runtime/base.js +11 -0
  3. package/dist/services/access-control.service.js +5 -0
  4. package/dist/services/tool-protection.service.js +17 -8
  5. package/package.json +2 -2
  6. package/.turbo/turbo-build.log +0 -4
  7. package/.turbo/turbo-test$colon$coverage.log +0 -4586
  8. package/.turbo/turbo-test.log +0 -3169
  9. package/COMPLIANCE_IMPROVEMENT_REPORT.md +0 -483
  10. package/Composer 3.md +0 -615
  11. package/GPT-5.md +0 -1169
  12. package/OPUS-plan.md +0 -352
  13. package/PHASE_3_AND_4.1_SUMMARY.md +0 -585
  14. package/PHASE_3_SUMMARY.md +0 -317
  15. package/PHASE_4.1.3_SUMMARY.md +0 -428
  16. package/PHASE_4.1_COMPLETE.md +0 -525
  17. package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +0 -1240
  18. package/SCHEMA_COMPLIANCE_REPORT.md +0 -275
  19. package/TEST_PLAN.md +0 -571
  20. package/coverage/coverage-final.json +0 -60
  21. package/dist/cache/oauth-config-cache.d.ts.map +0 -1
  22. package/dist/cache/oauth-config-cache.js.map +0 -1
  23. package/dist/cache/tool-protection-cache.d.ts.map +0 -1
  24. package/dist/cache/tool-protection-cache.js.map +0 -1
  25. package/dist/compliance/index.d.ts.map +0 -1
  26. package/dist/compliance/index.js.map +0 -1
  27. package/dist/compliance/schema-registry.d.ts.map +0 -1
  28. package/dist/compliance/schema-registry.js.map +0 -1
  29. package/dist/compliance/schema-verifier.d.ts.map +0 -1
  30. package/dist/compliance/schema-verifier.js.map +0 -1
  31. package/dist/config/remote-config.d.ts.map +0 -1
  32. package/dist/config/remote-config.js.map +0 -1
  33. package/dist/config.d.ts.map +0 -1
  34. package/dist/config.js.map +0 -1
  35. package/dist/delegation/audience-validator.d.ts.map +0 -1
  36. package/dist/delegation/audience-validator.js.map +0 -1
  37. package/dist/delegation/bitstring.d.ts.map +0 -1
  38. package/dist/delegation/bitstring.js.map +0 -1
  39. package/dist/delegation/cascading-revocation.d.ts.map +0 -1
  40. package/dist/delegation/cascading-revocation.js.map +0 -1
  41. package/dist/delegation/delegation-graph.d.ts.map +0 -1
  42. package/dist/delegation/delegation-graph.js.map +0 -1
  43. package/dist/delegation/did-key-resolver.d.ts.map +0 -1
  44. package/dist/delegation/did-key-resolver.js.map +0 -1
  45. package/dist/delegation/index.d.ts.map +0 -1
  46. package/dist/delegation/index.js.map +0 -1
  47. package/dist/delegation/statuslist-manager.d.ts.map +0 -1
  48. package/dist/delegation/statuslist-manager.js.map +0 -1
  49. package/dist/delegation/storage/index.d.ts.map +0 -1
  50. package/dist/delegation/storage/index.js.map +0 -1
  51. package/dist/delegation/storage/memory-graph-storage.d.ts.map +0 -1
  52. package/dist/delegation/storage/memory-graph-storage.js.map +0 -1
  53. package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +0 -1
  54. package/dist/delegation/storage/memory-statuslist-storage.js.map +0 -1
  55. package/dist/delegation/utils.d.ts.map +0 -1
  56. package/dist/delegation/utils.js.map +0 -1
  57. package/dist/delegation/vc-issuer.d.ts.map +0 -1
  58. package/dist/delegation/vc-issuer.js.map +0 -1
  59. package/dist/delegation/vc-verifier.d.ts.map +0 -1
  60. package/dist/delegation/vc-verifier.js.map +0 -1
  61. package/dist/identity/idp-token-resolver.d.ts.map +0 -1
  62. package/dist/identity/idp-token-resolver.js.map +0 -1
  63. package/dist/identity/idp-token-storage.interface.d.ts.map +0 -1
  64. package/dist/identity/idp-token-storage.interface.js.map +0 -1
  65. package/dist/identity/user-did-manager.d.ts.map +0 -1
  66. package/dist/identity/user-did-manager.js.map +0 -1
  67. package/dist/index.d.ts.map +0 -1
  68. package/dist/index.js.map +0 -1
  69. package/dist/providers/base.d.ts.map +0 -1
  70. package/dist/providers/base.js.map +0 -1
  71. package/dist/providers/memory.d.ts.map +0 -1
  72. package/dist/providers/memory.js.map +0 -1
  73. package/dist/runtime/audit-logger.d.ts.map +0 -1
  74. package/dist/runtime/audit-logger.js.map +0 -1
  75. package/dist/runtime/base.d.ts.map +0 -1
  76. package/dist/runtime/base.js.map +0 -1
  77. package/dist/services/access-control.service.d.ts.map +0 -1
  78. package/dist/services/access-control.service.js.map +0 -1
  79. package/dist/services/authorization/authorization-registry.d.ts.map +0 -1
  80. package/dist/services/authorization/authorization-registry.js.map +0 -1
  81. package/dist/services/authorization/types.d.ts.map +0 -1
  82. package/dist/services/authorization/types.js.map +0 -1
  83. package/dist/services/batch-delegation.service.d.ts.map +0 -1
  84. package/dist/services/batch-delegation.service.js.map +0 -1
  85. package/dist/services/crypto.service.d.ts.map +0 -1
  86. package/dist/services/crypto.service.js.map +0 -1
  87. package/dist/services/errors.d.ts.map +0 -1
  88. package/dist/services/errors.js.map +0 -1
  89. package/dist/services/index.d.ts.map +0 -1
  90. package/dist/services/index.js.map +0 -1
  91. package/dist/services/oauth-config.service.d.ts.map +0 -1
  92. package/dist/services/oauth-config.service.js.map +0 -1
  93. package/dist/services/oauth-provider-registry.d.ts.map +0 -1
  94. package/dist/services/oauth-provider-registry.js.map +0 -1
  95. package/dist/services/oauth-service.d.ts.map +0 -1
  96. package/dist/services/oauth-service.js.map +0 -1
  97. package/dist/services/oauth-token-retrieval.service.d.ts.map +0 -1
  98. package/dist/services/oauth-token-retrieval.service.js.map +0 -1
  99. package/dist/services/proof-verifier.d.ts.map +0 -1
  100. package/dist/services/proof-verifier.js.map +0 -1
  101. package/dist/services/provider-resolver.d.ts.map +0 -1
  102. package/dist/services/provider-resolver.js.map +0 -1
  103. package/dist/services/provider-validator.d.ts.map +0 -1
  104. package/dist/services/provider-validator.js.map +0 -1
  105. package/dist/services/session-registration.service.d.ts.map +0 -1
  106. package/dist/services/session-registration.service.js.map +0 -1
  107. package/dist/services/storage.service.d.ts.map +0 -1
  108. package/dist/services/storage.service.js.map +0 -1
  109. package/dist/services/tool-context-builder.d.ts.map +0 -1
  110. package/dist/services/tool-context-builder.js.map +0 -1
  111. package/dist/services/tool-protection.service.d.ts.map +0 -1
  112. package/dist/services/tool-protection.service.js.map +0 -1
  113. package/dist/types/oauth-required-error.d.ts.map +0 -1
  114. package/dist/types/oauth-required-error.js.map +0 -1
  115. package/dist/types/tool-protection.d.ts.map +0 -1
  116. package/dist/types/tool-protection.js.map +0 -1
  117. package/dist/utils/base58.d.ts.map +0 -1
  118. package/dist/utils/base58.js.map +0 -1
  119. package/dist/utils/base64.d.ts.map +0 -1
  120. package/dist/utils/base64.js.map +0 -1
  121. package/dist/utils/cors.d.ts.map +0 -1
  122. package/dist/utils/cors.js.map +0 -1
  123. package/dist/utils/did-helpers.d.ts.map +0 -1
  124. package/dist/utils/did-helpers.js.map +0 -1
  125. package/dist/utils/index.d.ts.map +0 -1
  126. package/dist/utils/index.js.map +0 -1
  127. package/dist/utils/storage-keys.d.ts.map +0 -1
  128. package/dist/utils/storage-keys.js.map +0 -1
  129. package/docs/API_REFERENCE.md +0 -1362
  130. package/docs/COMPLIANCE_MATRIX.md +0 -691
  131. package/docs/STATUSLIST2021_GUIDE.md +0 -696
  132. package/docs/W3C_VC_DELEGATION_GUIDE.md +0 -710
  133. package/src/__tests__/cache/tool-protection-cache.test.ts +0 -640
  134. package/src/__tests__/config/provider-runtime-config.test.ts +0 -309
  135. package/src/__tests__/delegation-e2e.test.ts +0 -690
  136. package/src/__tests__/identity/user-did-manager.test.ts +0 -232
  137. package/src/__tests__/index.test.ts +0 -56
  138. package/src/__tests__/integration/full-flow.test.ts +0 -789
  139. package/src/__tests__/integration.test.ts +0 -281
  140. package/src/__tests__/providers/base.test.ts +0 -173
  141. package/src/__tests__/providers/memory.test.ts +0 -319
  142. package/src/__tests__/regression/phase2-regression.test.ts +0 -429
  143. package/src/__tests__/runtime/audit-logger.test.ts +0 -154
  144. package/src/__tests__/runtime/base-extensions.test.ts +0 -595
  145. package/src/__tests__/runtime/base.test.ts +0 -869
  146. package/src/__tests__/runtime/delegation-flow.test.ts +0 -164
  147. package/src/__tests__/runtime/proof-client-did.test.ts +0 -376
  148. package/src/__tests__/runtime/route-interception.test.ts +0 -686
  149. package/src/__tests__/runtime/tool-protection-enforcement.test.ts +0 -908
  150. package/src/__tests__/services/agentshield-integration.test.ts +0 -791
  151. package/src/__tests__/services/cache-busting.test.ts +0 -125
  152. package/src/__tests__/services/oauth-service-pkce.test.ts +0 -556
  153. package/src/__tests__/services/provider-resolver-edge-cases.test.ts +0 -591
  154. package/src/__tests__/services/tool-protection-merged-config.test.ts +0 -485
  155. package/src/__tests__/services/tool-protection-oauth-provider.test.ts +0 -480
  156. package/src/__tests__/services/tool-protection.service.test.ts +0 -1373
  157. package/src/__tests__/utils/mock-providers.ts +0 -340
  158. package/src/cache/oauth-config-cache.d.ts +0 -69
  159. package/src/cache/oauth-config-cache.d.ts.map +0 -1
  160. package/src/cache/oauth-config-cache.js.map +0 -1
  161. package/src/cache/oauth-config-cache.ts +0 -123
  162. package/src/cache/tool-protection-cache.ts +0 -171
  163. package/src/compliance/EXAMPLE.md +0 -412
  164. package/src/compliance/__tests__/schema-verifier.test.ts +0 -797
  165. package/src/compliance/index.ts +0 -8
  166. package/src/compliance/schema-registry.ts +0 -460
  167. package/src/compliance/schema-verifier.ts +0 -708
  168. package/src/config/__tests__/merged-config.spec.ts +0 -445
  169. package/src/config/__tests__/remote-config.spec.ts +0 -268
  170. package/src/config/remote-config.ts +0 -264
  171. package/src/config.ts +0 -312
  172. package/src/delegation/__tests__/audience-validator.test.ts +0 -112
  173. package/src/delegation/__tests__/bitstring.test.ts +0 -346
  174. package/src/delegation/__tests__/cascading-revocation.test.ts +0 -628
  175. package/src/delegation/__tests__/delegation-graph.test.ts +0 -584
  176. package/src/delegation/__tests__/did-key-resolver.test.ts +0 -265
  177. package/src/delegation/__tests__/utils.test.ts +0 -152
  178. package/src/delegation/__tests__/vc-issuer.test.ts +0 -442
  179. package/src/delegation/__tests__/vc-verifier.test.ts +0 -922
  180. package/src/delegation/audience-validator.ts +0 -52
  181. package/src/delegation/bitstring.ts +0 -278
  182. package/src/delegation/cascading-revocation.ts +0 -370
  183. package/src/delegation/delegation-graph.ts +0 -299
  184. package/src/delegation/did-key-resolver.ts +0 -179
  185. package/src/delegation/index.ts +0 -14
  186. package/src/delegation/statuslist-manager.ts +0 -353
  187. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +0 -366
  188. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +0 -228
  189. package/src/delegation/storage/index.ts +0 -9
  190. package/src/delegation/storage/memory-graph-storage.ts +0 -178
  191. package/src/delegation/storage/memory-statuslist-storage.ts +0 -77
  192. package/src/delegation/utils.ts +0 -221
  193. package/src/delegation/vc-issuer.ts +0 -232
  194. package/src/delegation/vc-verifier.ts +0 -568
  195. package/src/identity/idp-token-resolver.ts +0 -181
  196. package/src/identity/idp-token-storage.interface.ts +0 -94
  197. package/src/identity/user-did-manager.ts +0 -526
  198. package/src/index.ts +0 -310
  199. package/src/providers/base.d.ts +0 -91
  200. package/src/providers/base.d.ts.map +0 -1
  201. package/src/providers/base.js.map +0 -1
  202. package/src/providers/base.ts +0 -96
  203. package/src/providers/memory.ts +0 -142
  204. package/src/runtime/audit-logger.ts +0 -39
  205. package/src/runtime/base.ts +0 -1392
  206. package/src/services/__tests__/access-control.integration.test.ts +0 -443
  207. package/src/services/__tests__/access-control.proof-response-validation.test.ts +0 -578
  208. package/src/services/__tests__/access-control.service.test.ts +0 -970
  209. package/src/services/__tests__/batch-delegation.service.test.ts +0 -351
  210. package/src/services/__tests__/crypto.service.test.ts +0 -531
  211. package/src/services/__tests__/oauth-provider-registry.test.ts +0 -142
  212. package/src/services/__tests__/proof-verifier.integration.test.ts +0 -485
  213. package/src/services/__tests__/proof-verifier.test.ts +0 -489
  214. package/src/services/__tests__/provider-resolution.integration.test.ts +0 -202
  215. package/src/services/__tests__/provider-resolver.test.ts +0 -213
  216. package/src/services/__tests__/storage.service.test.ts +0 -358
  217. package/src/services/access-control.service.ts +0 -990
  218. package/src/services/authorization/authorization-registry.ts +0 -66
  219. package/src/services/authorization/types.ts +0 -71
  220. package/src/services/batch-delegation.service.ts +0 -137
  221. package/src/services/crypto.service.ts +0 -302
  222. package/src/services/errors.ts +0 -76
  223. package/src/services/index.ts +0 -18
  224. package/src/services/oauth-config.service.d.ts +0 -53
  225. package/src/services/oauth-config.service.d.ts.map +0 -1
  226. package/src/services/oauth-config.service.js.map +0 -1
  227. package/src/services/oauth-config.service.ts +0 -192
  228. package/src/services/oauth-provider-registry.d.ts +0 -57
  229. package/src/services/oauth-provider-registry.d.ts.map +0 -1
  230. package/src/services/oauth-provider-registry.js.map +0 -1
  231. package/src/services/oauth-provider-registry.ts +0 -141
  232. package/src/services/oauth-service.ts +0 -544
  233. package/src/services/oauth-token-retrieval.service.ts +0 -245
  234. package/src/services/proof-verifier.ts +0 -478
  235. package/src/services/provider-resolver.d.ts +0 -48
  236. package/src/services/provider-resolver.d.ts.map +0 -1
  237. package/src/services/provider-resolver.js.map +0 -1
  238. package/src/services/provider-resolver.ts +0 -146
  239. package/src/services/provider-validator.ts +0 -170
  240. package/src/services/session-registration.service.ts +0 -251
  241. package/src/services/storage.service.ts +0 -566
  242. package/src/services/tool-context-builder.ts +0 -237
  243. package/src/services/tool-protection.service.ts +0 -1070
  244. package/src/types/oauth-required-error.ts +0 -63
  245. package/src/types/tool-protection.ts +0 -155
  246. package/src/utils/__tests__/did-helpers.test.ts +0 -156
  247. package/src/utils/base58.ts +0 -109
  248. package/src/utils/base64.ts +0 -148
  249. package/src/utils/cors.ts +0 -83
  250. package/src/utils/did-helpers.ts +0 -210
  251. package/src/utils/index.ts +0 -8
  252. package/src/utils/storage-keys.ts +0 -278
  253. package/tsconfig.json +0 -21
  254. 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
-