@padua/cli 1.13.1 → 2.0.0

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 (265) hide show
  1. package/README.md +121 -6
  2. package/dist/commands/doctor/index.d.ts.map +1 -1
  3. package/dist/commands/doctor/index.js +85 -0
  4. package/dist/commands/doctor/index.js.map +1 -1
  5. package/dist/commands/doctor/mcp-checks.d.ts +36 -0
  6. package/dist/commands/doctor/mcp-checks.d.ts.map +1 -0
  7. package/dist/commands/doctor/mcp-checks.js +235 -0
  8. package/dist/commands/doctor/mcp-checks.js.map +1 -0
  9. package/dist/commands/doctor/mcp-service-checks.d.ts +35 -0
  10. package/dist/commands/doctor/mcp-service-checks.d.ts.map +1 -0
  11. package/dist/commands/doctor/mcp-service-checks.js +146 -0
  12. package/dist/commands/doctor/mcp-service-checks.js.map +1 -0
  13. package/dist/commands/doctor/types.d.ts +4 -1
  14. package/dist/commands/doctor/types.d.ts.map +1 -1
  15. package/dist/commands/doctor/types.js +1 -0
  16. package/dist/commands/doctor/types.js.map +1 -1
  17. package/dist/commands/login/index.d.ts +1 -1
  18. package/dist/commands/login/index.d.ts.map +1 -1
  19. package/dist/commands/login/index.js +44 -185
  20. package/dist/commands/login/index.js.map +1 -1
  21. package/dist/commands/login/mcp-steps.d.ts +38 -0
  22. package/dist/commands/login/mcp-steps.d.ts.map +1 -0
  23. package/dist/commands/login/mcp-steps.js +176 -0
  24. package/dist/commands/login/mcp-steps.js.map +1 -0
  25. package/dist/commands/login/orchestrator.d.ts +9 -0
  26. package/dist/commands/login/orchestrator.d.ts.map +1 -0
  27. package/dist/commands/login/orchestrator.js +251 -0
  28. package/dist/commands/login/orchestrator.js.map +1 -0
  29. package/dist/commands/login/types.d.ts +11 -0
  30. package/dist/commands/login/types.d.ts.map +1 -1
  31. package/dist/commands/login/types.js.map +1 -1
  32. package/dist/commands/status/aws-checks.d.ts +14 -0
  33. package/dist/commands/status/aws-checks.d.ts.map +1 -0
  34. package/dist/commands/status/aws-checks.js +145 -0
  35. package/dist/commands/status/aws-checks.js.map +1 -0
  36. package/dist/commands/status/checks.d.ts +9 -25
  37. package/dist/commands/status/checks.d.ts.map +1 -1
  38. package/dist/commands/status/checks.js +52 -254
  39. package/dist/commands/status/checks.js.map +1 -1
  40. package/dist/commands/status/index.d.ts.map +1 -1
  41. package/dist/commands/status/index.js +53 -1
  42. package/dist/commands/status/index.js.map +1 -1
  43. package/dist/commands/status/mcp-checks.d.ts +35 -0
  44. package/dist/commands/status/mcp-checks.d.ts.map +1 -0
  45. package/dist/commands/status/mcp-checks.js +175 -0
  46. package/dist/commands/status/mcp-checks.js.map +1 -0
  47. package/dist/commands/status/types.d.ts +34 -0
  48. package/dist/commands/status/types.d.ts.map +1 -1
  49. package/dist/mcp/config/index.d.ts +4 -0
  50. package/dist/mcp/config/index.d.ts.map +1 -0
  51. package/dist/mcp/config/index.js +14 -0
  52. package/dist/mcp/config/index.js.map +1 -0
  53. package/dist/mcp/config/loaders.d.ts +45 -0
  54. package/dist/mcp/config/loaders.d.ts.map +1 -0
  55. package/dist/mcp/config/loaders.js +149 -0
  56. package/dist/mcp/config/loaders.js.map +1 -0
  57. package/dist/mcp/config/types.d.ts +234 -0
  58. package/dist/mcp/config/types.d.ts.map +1 -0
  59. package/dist/mcp/config/types.js +45 -0
  60. package/dist/mcp/config/types.js.map +1 -0
  61. package/dist/mcp/daemon/entry-logic.d.ts +28 -0
  62. package/dist/mcp/daemon/entry-logic.d.ts.map +1 -0
  63. package/dist/mcp/daemon/entry-logic.js +82 -0
  64. package/dist/mcp/daemon/entry-logic.js.map +1 -0
  65. package/dist/mcp/daemon/entry.d.ts +8 -0
  66. package/dist/mcp/daemon/entry.d.ts.map +1 -0
  67. package/dist/mcp/daemon/entry.js +34 -0
  68. package/dist/mcp/daemon/entry.js.map +1 -0
  69. package/dist/mcp/daemon/fork.d.ts +21 -0
  70. package/dist/mcp/daemon/fork.d.ts.map +1 -0
  71. package/dist/mcp/daemon/fork.js +188 -0
  72. package/dist/mcp/daemon/fork.js.map +1 -0
  73. package/dist/mcp/daemon/health.d.ts +8 -0
  74. package/dist/mcp/daemon/health.d.ts.map +1 -0
  75. package/dist/mcp/daemon/health.js +50 -0
  76. package/dist/mcp/daemon/health.js.map +1 -0
  77. package/dist/mcp/daemon/index.d.ts +6 -0
  78. package/dist/mcp/daemon/index.d.ts.map +1 -0
  79. package/dist/mcp/daemon/index.js +22 -0
  80. package/dist/mcp/daemon/index.js.map +1 -0
  81. package/dist/mcp/daemon/types.d.ts +63 -0
  82. package/dist/mcp/daemon/types.d.ts.map +1 -0
  83. package/dist/mcp/daemon/types.js +18 -0
  84. package/dist/mcp/daemon/types.js.map +1 -0
  85. package/dist/mcp/errors/index.d.ts +3 -0
  86. package/dist/mcp/errors/index.d.ts.map +1 -0
  87. package/dist/mcp/errors/index.js +13 -0
  88. package/dist/mcp/errors/index.js.map +1 -0
  89. package/dist/mcp/errors/types.d.ts +83 -0
  90. package/dist/mcp/errors/types.d.ts.map +1 -0
  91. package/dist/mcp/errors/types.js +148 -0
  92. package/dist/mcp/errors/types.js.map +1 -0
  93. package/dist/mcp/providers/atlassian/auth.d.ts +34 -0
  94. package/dist/mcp/providers/atlassian/auth.d.ts.map +1 -0
  95. package/dist/mcp/providers/atlassian/auth.js +107 -0
  96. package/dist/mcp/providers/atlassian/auth.js.map +1 -0
  97. package/dist/mcp/providers/atlassian/client.d.ts +15 -0
  98. package/dist/mcp/providers/atlassian/client.d.ts.map +1 -0
  99. package/dist/mcp/providers/atlassian/client.js +38 -0
  100. package/dist/mcp/providers/atlassian/client.js.map +1 -0
  101. package/dist/mcp/providers/atlassian/index.d.ts +6 -0
  102. package/dist/mcp/providers/atlassian/index.d.ts.map +1 -0
  103. package/dist/mcp/providers/atlassian/index.js +11 -0
  104. package/dist/mcp/providers/atlassian/index.js.map +1 -0
  105. package/dist/mcp/providers/atlassian/markdown-to-adf/index.d.ts +17 -0
  106. package/dist/mcp/providers/atlassian/markdown-to-adf/index.d.ts.map +1 -0
  107. package/dist/mcp/providers/atlassian/markdown-to-adf/index.js +29 -0
  108. package/dist/mcp/providers/atlassian/markdown-to-adf/index.js.map +1 -0
  109. package/dist/mcp/providers/atlassian/markdown-to-adf/nodes.d.ts +43 -0
  110. package/dist/mcp/providers/atlassian/markdown-to-adf/nodes.d.ts.map +1 -0
  111. package/dist/mcp/providers/atlassian/markdown-to-adf/nodes.js +101 -0
  112. package/dist/mcp/providers/atlassian/markdown-to-adf/nodes.js.map +1 -0
  113. package/dist/mcp/providers/atlassian/markdown-to-adf/parser.d.ts +14 -0
  114. package/dist/mcp/providers/atlassian/markdown-to-adf/parser.d.ts.map +1 -0
  115. package/dist/mcp/providers/atlassian/markdown-to-adf/parser.js +250 -0
  116. package/dist/mcp/providers/atlassian/markdown-to-adf/parser.js.map +1 -0
  117. package/dist/mcp/providers/atlassian/provider.d.ts +38 -0
  118. package/dist/mcp/providers/atlassian/provider.d.ts.map +1 -0
  119. package/dist/mcp/providers/atlassian/provider.js +101 -0
  120. package/dist/mcp/providers/atlassian/provider.js.map +1 -0
  121. package/dist/mcp/providers/atlassian/resources.d.ts +4 -0
  122. package/dist/mcp/providers/atlassian/resources.d.ts.map +1 -0
  123. package/dist/mcp/providers/atlassian/resources.js +67 -0
  124. package/dist/mcp/providers/atlassian/resources.js.map +1 -0
  125. package/dist/mcp/providers/atlassian/tools/confluence.d.ts +4 -0
  126. package/dist/mcp/providers/atlassian/tools/confluence.d.ts.map +1 -0
  127. package/dist/mcp/providers/atlassian/tools/confluence.js +169 -0
  128. package/dist/mcp/providers/atlassian/tools/confluence.js.map +1 -0
  129. package/dist/mcp/providers/atlassian/tools/jira.d.ts +4 -0
  130. package/dist/mcp/providers/atlassian/tools/jira.d.ts.map +1 -0
  131. package/dist/mcp/providers/atlassian/tools/jira.js +274 -0
  132. package/dist/mcp/providers/atlassian/tools/jira.js.map +1 -0
  133. package/dist/mcp/providers/gitlab/auth.d.ts +10 -0
  134. package/dist/mcp/providers/gitlab/auth.d.ts.map +1 -0
  135. package/dist/mcp/providers/gitlab/auth.js +23 -0
  136. package/dist/mcp/providers/gitlab/auth.js.map +1 -0
  137. package/dist/mcp/providers/gitlab/client.d.ts +23 -0
  138. package/dist/mcp/providers/gitlab/client.d.ts.map +1 -0
  139. package/dist/mcp/providers/gitlab/client.js +17 -0
  140. package/dist/mcp/providers/gitlab/client.js.map +1 -0
  141. package/dist/mcp/providers/gitlab/index.d.ts +5 -0
  142. package/dist/mcp/providers/gitlab/index.d.ts.map +1 -0
  143. package/dist/mcp/providers/gitlab/index.js +10 -0
  144. package/dist/mcp/providers/gitlab/index.js.map +1 -0
  145. package/dist/mcp/providers/gitlab/provider.d.ts +25 -0
  146. package/dist/mcp/providers/gitlab/provider.d.ts.map +1 -0
  147. package/dist/mcp/providers/gitlab/provider.js +48 -0
  148. package/dist/mcp/providers/gitlab/provider.js.map +1 -0
  149. package/dist/mcp/providers/gitlab/resources.d.ts +11 -0
  150. package/dist/mcp/providers/gitlab/resources.d.ts.map +1 -0
  151. package/dist/mcp/providers/gitlab/resources.js +54 -0
  152. package/dist/mcp/providers/gitlab/resources.js.map +1 -0
  153. package/dist/mcp/providers/gitlab/tools/issues.d.ts +4 -0
  154. package/dist/mcp/providers/gitlab/tools/issues.d.ts.map +1 -0
  155. package/dist/mcp/providers/gitlab/tools/issues.js +120 -0
  156. package/dist/mcp/providers/gitlab/tools/issues.js.map +1 -0
  157. package/dist/mcp/providers/gitlab/tools/merge-requests.d.ts +11 -0
  158. package/dist/mcp/providers/gitlab/tools/merge-requests.d.ts.map +1 -0
  159. package/dist/mcp/providers/gitlab/tools/merge-requests.js +282 -0
  160. package/dist/mcp/providers/gitlab/tools/merge-requests.js.map +1 -0
  161. package/dist/mcp/providers/gitlab/tools/pipelines.d.ts +10 -0
  162. package/dist/mcp/providers/gitlab/tools/pipelines.d.ts.map +1 -0
  163. package/dist/mcp/providers/gitlab/tools/pipelines.js +173 -0
  164. package/dist/mcp/providers/gitlab/tools/pipelines.js.map +1 -0
  165. package/dist/mcp/providers/gitlab/tools/repository.d.ts +4 -0
  166. package/dist/mcp/providers/gitlab/tools/repository.d.ts.map +1 -0
  167. package/dist/mcp/providers/gitlab/tools/repository.js +191 -0
  168. package/dist/mcp/providers/gitlab/tools/repository.js.map +1 -0
  169. package/dist/mcp/providers/index.d.ts +4 -0
  170. package/dist/mcp/providers/index.d.ts.map +1 -0
  171. package/dist/mcp/providers/index.js +6 -0
  172. package/dist/mcp/providers/index.js.map +1 -0
  173. package/dist/mcp/providers/registry.d.ts +90 -0
  174. package/dist/mcp/providers/registry.d.ts.map +1 -0
  175. package/dist/mcp/providers/registry.js +128 -0
  176. package/dist/mcp/providers/registry.js.map +1 -0
  177. package/dist/mcp/providers/tool-helpers.d.ts +14 -0
  178. package/dist/mcp/providers/tool-helpers.d.ts.map +1 -0
  179. package/dist/mcp/providers/tool-helpers.js +12 -0
  180. package/dist/mcp/providers/tool-helpers.js.map +1 -0
  181. package/dist/mcp/providers/types.d.ts +80 -0
  182. package/dist/mcp/providers/types.d.ts.map +1 -0
  183. package/dist/mcp/providers/types.js +13 -0
  184. package/dist/mcp/providers/types.js.map +1 -0
  185. package/dist/mcp/server/auth.d.ts +8 -0
  186. package/dist/mcp/server/auth.d.ts.map +1 -0
  187. package/dist/mcp/server/auth.js +36 -0
  188. package/dist/mcp/server/auth.js.map +1 -0
  189. package/dist/mcp/server/health.d.ts +24 -0
  190. package/dist/mcp/server/health.d.ts.map +1 -0
  191. package/dist/mcp/server/health.js +37 -0
  192. package/dist/mcp/server/health.js.map +1 -0
  193. package/dist/mcp/server/index.d.ts +11 -0
  194. package/dist/mcp/server/index.d.ts.map +1 -0
  195. package/dist/mcp/server/index.js +27 -0
  196. package/dist/mcp/server/index.js.map +1 -0
  197. package/dist/mcp/server/logging.d.ts +46 -0
  198. package/dist/mcp/server/logging.d.ts.map +1 -0
  199. package/dist/mcp/server/logging.js +109 -0
  200. package/dist/mcp/server/logging.js.map +1 -0
  201. package/dist/mcp/server/ratelimit.d.ts +3 -0
  202. package/dist/mcp/server/ratelimit.d.ts.map +1 -0
  203. package/dist/mcp/server/ratelimit.js +70 -0
  204. package/dist/mcp/server/ratelimit.js.map +1 -0
  205. package/dist/mcp/server/routes.d.ts +3 -0
  206. package/dist/mcp/server/routes.d.ts.map +1 -0
  207. package/dist/mcp/server/routes.js +8 -0
  208. package/dist/mcp/server/routes.js.map +1 -0
  209. package/dist/mcp/server/server.d.ts +21 -0
  210. package/dist/mcp/server/server.d.ts.map +1 -0
  211. package/dist/mcp/server/server.js +114 -0
  212. package/dist/mcp/server/server.js.map +1 -0
  213. package/dist/mcp/server/validation.d.ts +22 -0
  214. package/dist/mcp/server/validation.d.ts.map +1 -0
  215. package/dist/mcp/server/validation.js +32 -0
  216. package/dist/mcp/server/validation.js.map +1 -0
  217. package/dist/mcp/store/encrypt.d.ts +22 -0
  218. package/dist/mcp/store/encrypt.d.ts.map +1 -0
  219. package/dist/mcp/store/encrypt.js +66 -0
  220. package/dist/mcp/store/encrypt.js.map +1 -0
  221. package/dist/mcp/store/index.d.ts +12 -0
  222. package/dist/mcp/store/index.d.ts.map +1 -0
  223. package/dist/mcp/store/index.js +16 -0
  224. package/dist/mcp/store/index.js.map +1 -0
  225. package/dist/mcp/store/migrate.d.ts +70 -0
  226. package/dist/mcp/store/migrate.d.ts.map +1 -0
  227. package/dist/mcp/store/migrate.js +211 -0
  228. package/dist/mcp/store/migrate.js.map +1 -0
  229. package/dist/mcp/store/null-token-store.d.ts +22 -0
  230. package/dist/mcp/store/null-token-store.d.ts.map +1 -0
  231. package/dist/mcp/store/null-token-store.js +40 -0
  232. package/dist/mcp/store/null-token-store.js.map +1 -0
  233. package/dist/mcp/store/sqlite.d.ts +27 -0
  234. package/dist/mcp/store/sqlite.d.ts.map +1 -0
  235. package/dist/mcp/store/sqlite.js +100 -0
  236. package/dist/mcp/store/sqlite.js.map +1 -0
  237. package/dist/mcp/store/types.d.ts +183 -0
  238. package/dist/mcp/store/types.d.ts.map +1 -0
  239. package/dist/mcp/store/types.js +13 -0
  240. package/dist/mcp/store/types.js.map +1 -0
  241. package/dist/mcp/token/http-client.d.ts +3 -0
  242. package/dist/mcp/token/http-client.d.ts.map +1 -0
  243. package/dist/mcp/token/http-client.js +186 -0
  244. package/dist/mcp/token/http-client.js.map +1 -0
  245. package/dist/mcp/token/index.d.ts +5 -0
  246. package/dist/mcp/token/index.d.ts.map +1 -0
  247. package/dist/mcp/token/index.js +15 -0
  248. package/dist/mcp/token/index.js.map +1 -0
  249. package/dist/mcp/token/manager.d.ts +54 -0
  250. package/dist/mcp/token/manager.d.ts.map +1 -0
  251. package/dist/mcp/token/manager.js +194 -0
  252. package/dist/mcp/token/manager.js.map +1 -0
  253. package/dist/mcp/token/null-token-manager.d.ts +19 -0
  254. package/dist/mcp/token/null-token-manager.d.ts.map +1 -0
  255. package/dist/mcp/token/null-token-manager.js +50 -0
  256. package/dist/mcp/token/null-token-manager.js.map +1 -0
  257. package/dist/mcp/token/oauth.d.ts +44 -0
  258. package/dist/mcp/token/oauth.d.ts.map +1 -0
  259. package/dist/mcp/token/oauth.js +257 -0
  260. package/dist/mcp/token/oauth.js.map +1 -0
  261. package/dist/mcp/token/types.d.ts +81 -0
  262. package/dist/mcp/token/types.d.ts.map +1 -0
  263. package/dist/mcp/token/types.js +6 -0
  264. package/dist/mcp/token/types.js.map +1 -0
  265. package/package.json +10 -3
@@ -0,0 +1,54 @@
1
+ /**
2
+ * OAuthTokenManager — concrete implementation of the TokenManager interface.
3
+ *
4
+ * Manages the full token lifecycle for a single OAuth service:
5
+ * - Transparent refresh with exponential backoff
6
+ * - Circuit breaker to avoid hammering a failing token endpoint
7
+ * - Mutex to coalesce concurrent refresh attempts into a single HTTP call
8
+ * - Optional cloud-ID resolution for Atlassian providers
9
+ */
10
+ import type { TokenStore, StoredToken } from '../store/types';
11
+ import type { OAuthConfig, TokenManager, TokenResult, TokenStatus } from './types';
12
+ /**
13
+ * Formats a duration in seconds into a human-readable string.
14
+ * "2h 15m" when >= 1 hour, "3m 45s" when < 1 hour.
15
+ */
16
+ export declare function formatExpiresIn(seconds: number): string;
17
+ /**
18
+ * Maps a raw TokenResult from the token endpoint to the StoredToken shape.
19
+ * Carries over cloudId/cloudUrl from the existing token when present.
20
+ */
21
+ export declare function tokenResultToStoredToken(result: TokenResult, existing?: StoredToken): StoredToken;
22
+ export declare class OAuthTokenManager implements TokenManager {
23
+ private readonly config;
24
+ private readonly store;
25
+ private readonly service;
26
+ private readonly callbackPort;
27
+ private readonly options?;
28
+ private consecutiveFailures;
29
+ private circuitOpenUntil;
30
+ private retryAfterMs;
31
+ private refreshPromise;
32
+ constructor(config: OAuthConfig, store: TokenStore, service: string, callbackPort: number, options?: {
33
+ cloudIdResolver?: (accessToken: string) => Promise<{
34
+ cloudId: string;
35
+ cloudUrl: string;
36
+ }>;
37
+ /** Overrides openBrowser for testing. Defaults to the real openBrowser. */
38
+ openBrowser?: (url: string) => void;
39
+ });
40
+ getAccessToken(): Promise<string>;
41
+ authenticate(): Promise<void>;
42
+ isAuthenticated(): boolean;
43
+ getTokenStatus(): TokenStatus;
44
+ shutdown(): void;
45
+ private ensureFresh;
46
+ private doRefresh;
47
+ /**
48
+ * Computes and returns the exponential backoff delay in milliseconds.
49
+ * Base 1000ms, cap 30000ms, ±20% jitter.
50
+ * The caller decides whether to await it — inline retries are not done here.
51
+ */
52
+ private computeBackoffMs;
53
+ }
54
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../src/mcp/token/manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAwBnF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAWvD;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,WAAW,EACnB,QAAQ,CAAC,EAAE,WAAW,GACrB,WAAW,CAgBb;AAMD,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAIvB;IAGF,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,gBAAgB,CAAK;IAE7B,OAAO,CAAC,YAAY,CAAK;IAGzB,OAAO,CAAC,cAAc,CAA8B;gBAGlD,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;QACR,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC1F,2EAA2E;QAC3E,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;KACrC;IAaG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAKjC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBnC,eAAe,IAAI,OAAO;IAI1B,cAAc,IAAI,WAAW;IAkB7B,QAAQ,IAAI,IAAI;YAYF,WAAW;YA6BX,SAAS;IAkCvB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;CAKzB"}
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ /**
3
+ * OAuthTokenManager — concrete implementation of the TokenManager interface.
4
+ *
5
+ * Manages the full token lifecycle for a single OAuth service:
6
+ * - Transparent refresh with exponential backoff
7
+ * - Circuit breaker to avoid hammering a failing token endpoint
8
+ * - Mutex to coalesce concurrent refresh attempts into a single HTTP call
9
+ * - Optional cloud-ID resolution for Atlassian providers
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.OAuthTokenManager = void 0;
13
+ exports.formatExpiresIn = formatExpiresIn;
14
+ exports.tokenResultToStoredToken = tokenResultToStoredToken;
15
+ const node_crypto_1 = require("node:crypto");
16
+ const errors_1 = require("../errors");
17
+ const oauth_1 = require("./oauth");
18
+ // ---------------------------------------------------------------------------
19
+ // Constants
20
+ // ---------------------------------------------------------------------------
21
+ const EXPIRY_THRESHOLD_SECONDS = 60;
22
+ const MAX_FAILURES = 5;
23
+ const CIRCUIT_OPEN_MS = 5 * 60 * 1000; // 5 minutes
24
+ const BACKOFF_BASE_MS = 1000;
25
+ const BACKOFF_CAP_MS = 30_000;
26
+ // ---------------------------------------------------------------------------
27
+ // Helpers
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Formats a duration in seconds into a human-readable string.
31
+ * "2h 15m" when >= 1 hour, "3m 45s" when < 1 hour.
32
+ */
33
+ function formatExpiresIn(seconds) {
34
+ if (seconds <= 0)
35
+ return '0s';
36
+ const hours = Math.floor(seconds / 3600);
37
+ const minutes = Math.floor((seconds % 3600) / 60);
38
+ const secs = Math.floor(seconds % 60);
39
+ if (hours > 0) {
40
+ return `${hours}h ${minutes}m`;
41
+ }
42
+ return `${minutes}m ${secs}s`;
43
+ }
44
+ /**
45
+ * Maps a raw TokenResult from the token endpoint to the StoredToken shape.
46
+ * Carries over cloudId/cloudUrl from the existing token when present.
47
+ */
48
+ function tokenResultToStoredToken(result, existing) {
49
+ const expiresAt = Math.floor(Date.now() / 1000) + result.expiresIn;
50
+ const scopes = result.scope ? result.scope.split(' ') : (existing?.scopes ?? []);
51
+ const token = {
52
+ accessToken: result.accessToken,
53
+ refreshToken: result.refreshToken,
54
+ tokenType: result.tokenType,
55
+ expiresAt,
56
+ scopes,
57
+ };
58
+ if (existing?.cloudId)
59
+ token.cloudId = existing.cloudId;
60
+ if (existing?.cloudUrl)
61
+ token.cloudUrl = existing.cloudUrl;
62
+ return token;
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // OAuthTokenManager
66
+ // ---------------------------------------------------------------------------
67
+ class OAuthTokenManager {
68
+ config;
69
+ store;
70
+ service;
71
+ callbackPort;
72
+ options;
73
+ // Circuit breaker state
74
+ consecutiveFailures = 0;
75
+ circuitOpenUntil = 0;
76
+ // Computed backoff for diagnostics / future retry wiring
77
+ retryAfterMs = 0;
78
+ // Refresh mutex — collapses concurrent refresh attempts
79
+ refreshPromise = null;
80
+ constructor(config, store, service, callbackPort, options) {
81
+ this.config = config;
82
+ this.store = store;
83
+ this.service = service;
84
+ this.callbackPort = callbackPort;
85
+ this.options = options;
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Public API
89
+ // ---------------------------------------------------------------------------
90
+ async getAccessToken() {
91
+ const token = await this.ensureFresh();
92
+ return token.accessToken;
93
+ }
94
+ async authenticate() {
95
+ const pkce = (0, oauth_1.generatePKCE)();
96
+ const state = (0, node_crypto_1.randomBytes)(16).toString('hex');
97
+ const authUrl = (0, oauth_1.buildAuthUrl)(this.config, state, pkce.codeChallenge);
98
+ const callbackPromise = (0, oauth_1.startCallbackServer)(this.callbackPort, state);
99
+ const browserFn = this.options?.openBrowser ?? oauth_1.openBrowser;
100
+ browserFn(authUrl);
101
+ const { code } = await callbackPromise;
102
+ const result = await (0, oauth_1.exchangeCode)(this.config, code, pkce.codeVerifier);
103
+ let token = tokenResultToStoredToken(result);
104
+ if (this.options?.cloudIdResolver) {
105
+ const { cloudId, cloudUrl } = await this.options.cloudIdResolver(result.accessToken);
106
+ token = { ...token, cloudId, cloudUrl };
107
+ }
108
+ this.store.upsert(this.service, token);
109
+ }
110
+ isAuthenticated() {
111
+ return this.store.get(this.service) !== null;
112
+ }
113
+ getTokenStatus() {
114
+ const token = this.store.get(this.service);
115
+ if (!token) {
116
+ return { authenticated: false, refreshable: false };
117
+ }
118
+ const now = Math.floor(Date.now() / 1000);
119
+ const secondsRemaining = token.expiresAt - now;
120
+ return {
121
+ authenticated: true,
122
+ expiresAt: token.expiresAt,
123
+ expiresIn: formatExpiresIn(secondsRemaining),
124
+ refreshable: Boolean(token.refreshToken),
125
+ };
126
+ }
127
+ shutdown() {
128
+ // Reset circuit-breaker and mutex state
129
+ this.refreshPromise = null;
130
+ this.consecutiveFailures = 0;
131
+ this.circuitOpenUntil = 0;
132
+ this.retryAfterMs = 0;
133
+ }
134
+ // ---------------------------------------------------------------------------
135
+ // Private: token freshness + mutex
136
+ // ---------------------------------------------------------------------------
137
+ async ensureFresh() {
138
+ const token = this.store.get(this.service);
139
+ if (!token) {
140
+ throw new errors_1.AuthError(`Not authenticated for ${this.service}`, errors_1.ErrorCodes.TOKEN_REFRESH_FAILED);
141
+ }
142
+ const now = Math.floor(Date.now() / 1000);
143
+ if (token.expiresAt - now > EXPIRY_THRESHOLD_SECONDS) {
144
+ return token; // still fresh
145
+ }
146
+ // Coalesce concurrent refresh attempts
147
+ if (!this.refreshPromise) {
148
+ this.refreshPromise = this.doRefresh(token).finally(() => {
149
+ this.refreshPromise = null;
150
+ });
151
+ }
152
+ await this.refreshPromise;
153
+ return this.store.get(this.service);
154
+ }
155
+ // ---------------------------------------------------------------------------
156
+ // Private: circuit breaker + refresh
157
+ // ---------------------------------------------------------------------------
158
+ async doRefresh(token) {
159
+ const now = Date.now();
160
+ if (this.circuitOpenUntil > now) {
161
+ throw new errors_1.AuthError(`Circuit breaker open for ${this.service} — too many consecutive refresh failures`, errors_1.ErrorCodes.CIRCUIT_BREAKER_OPEN);
162
+ }
163
+ try {
164
+ const result = await (0, oauth_1.refreshToken)(this.config, token.refreshToken);
165
+ const refreshed = tokenResultToStoredToken(result, token);
166
+ this.store.upsert(this.service, refreshed);
167
+ this.consecutiveFailures = 0;
168
+ this.retryAfterMs = 0;
169
+ }
170
+ catch (err) {
171
+ this.consecutiveFailures += 1;
172
+ // Record backoff for diagnostics; no inline sleep — callers surface the
173
+ // failure immediately and the next call will check the circuit state.
174
+ this.retryAfterMs = this.computeBackoffMs();
175
+ if (this.consecutiveFailures >= MAX_FAILURES) {
176
+ this.circuitOpenUntil = Date.now() + CIRCUIT_OPEN_MS;
177
+ throw new errors_1.AuthError(`Circuit breaker opened for ${this.service} after ${this.consecutiveFailures} consecutive failures`, errors_1.ErrorCodes.CIRCUIT_BREAKER_OPEN);
178
+ }
179
+ throw err;
180
+ }
181
+ }
182
+ /**
183
+ * Computes and returns the exponential backoff delay in milliseconds.
184
+ * Base 1000ms, cap 30000ms, ±20% jitter.
185
+ * The caller decides whether to await it — inline retries are not done here.
186
+ */
187
+ computeBackoffMs() {
188
+ const exponent = Math.min(this.consecutiveFailures - 1, 10);
189
+ const base = Math.min(BACKOFF_BASE_MS * Math.pow(2, exponent), BACKOFF_CAP_MS);
190
+ return base * (0.8 + Math.random() * 0.4);
191
+ }
192
+ }
193
+ exports.OAuthTokenManager = OAuthTokenManager;
194
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/mcp/token/manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { TokenManager, TokenStatus } from './types';
2
+ /**
3
+ * Null-object TokenManager for testing and fallback.
4
+ * Two factories: createAuthenticated() returns a manager with a fake token,
5
+ * createUnauthenticated() returns one that throws on getAccessToken().
6
+ */
7
+ export declare class NullTokenManager implements TokenManager {
8
+ private readonly _authenticated;
9
+ private readonly _token;
10
+ private constructor();
11
+ static createAuthenticated(token?: string): NullTokenManager;
12
+ static createUnauthenticated(): NullTokenManager;
13
+ getAccessToken(): Promise<string>;
14
+ authenticate(): Promise<void>;
15
+ isAuthenticated(): boolean;
16
+ getTokenStatus(): TokenStatus;
17
+ shutdown(): void;
18
+ }
19
+ //# sourceMappingURL=null-token-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null-token-manager.d.ts","sourceRoot":"","sources":["../../../src/mcp/token/null-token-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEzD;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,OAAO;IAKP,MAAM,CAAC,mBAAmB,CAAC,KAAK,SAAkC,GAAG,gBAAgB;IAIrF,MAAM,CAAC,qBAAqB,IAAI,gBAAgB;IAI1C,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAOjC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAInC,eAAe,IAAI,OAAO;IAI1B,cAAc,IAAI,WAAW;IAY7B,QAAQ,IAAI,IAAI;CAGjB"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NullTokenManager = void 0;
4
+ /**
5
+ * Null-object TokenManager for testing and fallback.
6
+ * Two factories: createAuthenticated() returns a manager with a fake token,
7
+ * createUnauthenticated() returns one that throws on getAccessToken().
8
+ */
9
+ class NullTokenManager {
10
+ _authenticated;
11
+ _token;
12
+ constructor(authenticated, token) {
13
+ this._authenticated = authenticated;
14
+ this._token = token;
15
+ }
16
+ static createAuthenticated(token = 'null-token-manager-fake-token') {
17
+ return new NullTokenManager(true, token);
18
+ }
19
+ static createUnauthenticated() {
20
+ return new NullTokenManager(false, '');
21
+ }
22
+ async getAccessToken() {
23
+ if (!this._authenticated) {
24
+ throw new Error('Not authenticated');
25
+ }
26
+ return this._token;
27
+ }
28
+ async authenticate() {
29
+ // no-op
30
+ }
31
+ isAuthenticated() {
32
+ return this._authenticated;
33
+ }
34
+ getTokenStatus() {
35
+ if (this._authenticated) {
36
+ return {
37
+ authenticated: true,
38
+ expiresAt: Math.floor(Date.now() / 1000) + 3600,
39
+ expiresIn: '1h 0m',
40
+ refreshable: true,
41
+ };
42
+ }
43
+ return { authenticated: false, refreshable: false };
44
+ }
45
+ shutdown() {
46
+ // no-op
47
+ }
48
+ }
49
+ exports.NullTokenManager = NullTokenManager;
50
+ //# sourceMappingURL=null-token-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null-token-manager.js","sourceRoot":"","sources":["../../../src/mcp/token/null-token-manager.ts"],"names":[],"mappings":";;;AAEA;;;;GAIG;AACH,MAAa,gBAAgB;IACV,cAAc,CAAU;IACxB,MAAM,CAAS;IAEhC,YAAoB,aAAsB,EAAE,KAAa;QACvD,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;QACpC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,MAAM,CAAC,mBAAmB,CAAC,KAAK,GAAG,+BAA+B;QAChE,OAAO,IAAI,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,qBAAqB;QAC1B,OAAO,IAAI,gBAAgB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,QAAQ;IACV,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED,cAAc;QACZ,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO;gBACL,aAAa,EAAE,IAAI;gBACnB,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI;gBAC/C,SAAS,EAAE,OAAO;gBAClB,WAAW,EAAE,IAAI;aAClB,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IACtD,CAAC;IAED,QAAQ;QACN,QAAQ;IACV,CAAC;CACF;AA/CD,4CA+CC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * OAuth2 PKCE utilities for the MCP server integration.
3
+ *
4
+ * Implements the Authorization Code + PKCE flow as specified in RFC 7636.
5
+ * All HTTPS requests use the default TLS verification — rejectUnauthorized
6
+ * is never set to false.
7
+ */
8
+ import type { OAuthConfig, TokenResult, PKCEPair } from './types';
9
+ /**
10
+ * Generates a PKCE code verifier and its S256 code challenge.
11
+ * codeVerifier: 32 random bytes as base64url = 43 characters (RFC 7636 minimum).
12
+ */
13
+ export declare function generatePKCE(): PKCEPair;
14
+ /**
15
+ * Constructs the OAuth2 authorization URL with all required query parameters.
16
+ */
17
+ export declare function buildAuthUrl(config: OAuthConfig, state: string, codeChallenge?: string): string;
18
+ /**
19
+ * Exchanges an authorization code for tokens at the token endpoint.
20
+ * Throws AuthError(OAUTH_CODE_EXCHANGE_FAILED) on non-2xx responses.
21
+ */
22
+ export declare function exchangeCode(config: OAuthConfig, code: string, codeVerifier?: string): Promise<TokenResult>;
23
+ /**
24
+ * Exchanges a refresh token for a new token set at the token endpoint.
25
+ * Throws AuthError(TOKEN_REFRESH_FAILED) on non-2xx responses.
26
+ */
27
+ export declare function refreshToken(config: OAuthConfig, currentRefreshToken: string): Promise<TokenResult>;
28
+ /**
29
+ * Starts a local HTTP server on 127.0.0.1 to receive the OAuth2 callback.
30
+ * Resolves with { code, state } on success.
31
+ * Rejects with AuthError on OAuth error, state mismatch, port conflict, or timeout.
32
+ * The server is closed after handling the first request or on timeout.
33
+ * The authorization code is never logged.
34
+ */
35
+ export declare function startCallbackServer(port: number, expectedState: string, timeoutMs?: number): Promise<{
36
+ code: string;
37
+ state: string;
38
+ }>;
39
+ /**
40
+ * Opens the given URL in the default system browser.
41
+ * Fire-and-forget — errors are silently ignored.
42
+ */
43
+ export declare function openBrowser(url: string): void;
44
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/mcp/token/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAkBlE;;;GAGG;AACH,wBAAgB,YAAY,IAAI,QAAQ,CAIvC;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAqB/F;AAkFD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,CAAC,CA2BtB;AAMD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,WAAW,CAAC,CAsBtB;AAQD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EACrB,SAAS,SAAqB,GAC7B,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAoE1C;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAa7C"}
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ /**
3
+ * OAuth2 PKCE utilities for the MCP server integration.
4
+ *
5
+ * Implements the Authorization Code + PKCE flow as specified in RFC 7636.
6
+ * All HTTPS requests use the default TLS verification — rejectUnauthorized
7
+ * is never set to false.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.generatePKCE = generatePKCE;
11
+ exports.buildAuthUrl = buildAuthUrl;
12
+ exports.exchangeCode = exchangeCode;
13
+ exports.refreshToken = refreshToken;
14
+ exports.startCallbackServer = startCallbackServer;
15
+ exports.openBrowser = openBrowser;
16
+ const node_crypto_1 = require("node:crypto");
17
+ const node_http_1 = require("node:http");
18
+ const node_child_process_1 = require("node:child_process");
19
+ const node_https_1 = require("node:https");
20
+ const node_http_2 = require("node:http");
21
+ const node_url_1 = require("node:url");
22
+ const errors_1 = require("../errors");
23
+ // ---------------------------------------------------------------------------
24
+ // generatePKCE
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Generates a PKCE code verifier and its S256 code challenge.
28
+ * codeVerifier: 32 random bytes as base64url = 43 characters (RFC 7636 minimum).
29
+ */
30
+ function generatePKCE() {
31
+ const codeVerifier = (0, node_crypto_1.randomBytes)(32).toString('base64url');
32
+ const codeChallenge = (0, node_crypto_1.createHash)('sha256').update(codeVerifier).digest('base64url');
33
+ return { codeVerifier, codeChallenge };
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // buildAuthUrl
37
+ // ---------------------------------------------------------------------------
38
+ /**
39
+ * Constructs the OAuth2 authorization URL with all required query parameters.
40
+ */
41
+ function buildAuthUrl(config, state, codeChallenge) {
42
+ const url = new node_url_1.URL(config.authUrl);
43
+ url.searchParams.set('client_id', config.clientId);
44
+ url.searchParams.set('redirect_uri', config.redirectUri);
45
+ url.searchParams.set('response_type', 'code');
46
+ url.searchParams.set('scope', config.scopes.join(' '));
47
+ url.searchParams.set('state', state);
48
+ if (config.usePKCE && codeChallenge) {
49
+ url.searchParams.set('code_challenge', codeChallenge);
50
+ url.searchParams.set('code_challenge_method', 'S256');
51
+ }
52
+ if (config.additionalAuthParams) {
53
+ for (const [key, value] of Object.entries(config.additionalAuthParams)) {
54
+ url.searchParams.set(key, value);
55
+ }
56
+ }
57
+ return url.toString();
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Internal HTTP POST helper
61
+ // ---------------------------------------------------------------------------
62
+ /**
63
+ * Posts form-encoded data to a URL using the appropriate protocol module.
64
+ * Only http: is used for localhost test endpoints; all real endpoints use https:.
65
+ */
66
+ function postForm(url, params) {
67
+ return new Promise((resolve, reject) => {
68
+ const parsed = new node_url_1.URL(url);
69
+ const body = new node_url_1.URLSearchParams(params).toString();
70
+ const isHttp = parsed.protocol === 'http:';
71
+ const requestFn = isHttp ? node_http_2.request : node_https_1.request;
72
+ const options = {
73
+ hostname: parsed.hostname,
74
+ port: parsed.port || (isHttp ? 80 : 443),
75
+ path: parsed.pathname + parsed.search,
76
+ method: 'POST',
77
+ headers: {
78
+ 'Content-Type': 'application/x-www-form-urlencoded',
79
+ 'Content-Length': Buffer.byteLength(body),
80
+ },
81
+ };
82
+ const req = requestFn(options, (res) => {
83
+ const chunks = [];
84
+ res.on('data', (chunk) => chunks.push(chunk));
85
+ res.on('end', () => {
86
+ resolve({
87
+ status: res.statusCode ?? 0,
88
+ body: Buffer.concat(chunks).toString(),
89
+ });
90
+ });
91
+ });
92
+ req.on('error', reject);
93
+ req.write(body);
94
+ req.end();
95
+ });
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // extractOAuthError — safe extraction of error fields from token endpoint
99
+ // ---------------------------------------------------------------------------
100
+ function extractOAuthError(body) {
101
+ try {
102
+ const parsed = JSON.parse(body);
103
+ const error = parsed.error ?? '';
104
+ const desc = parsed.error_description ?? '';
105
+ const detail = [error, desc].filter(Boolean).join(' — ');
106
+ return detail || 'Unknown error';
107
+ }
108
+ catch {
109
+ return 'Non-JSON error response';
110
+ }
111
+ }
112
+ // ---------------------------------------------------------------------------
113
+ // mapTokenResponse
114
+ // ---------------------------------------------------------------------------
115
+ function mapTokenResponse(raw) {
116
+ return {
117
+ accessToken: raw.access_token,
118
+ refreshToken: raw.refresh_token,
119
+ tokenType: raw.token_type,
120
+ expiresIn: raw.expires_in,
121
+ scope: raw.scope,
122
+ };
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // exchangeCode
126
+ // ---------------------------------------------------------------------------
127
+ /**
128
+ * Exchanges an authorization code for tokens at the token endpoint.
129
+ * Throws AuthError(OAUTH_CODE_EXCHANGE_FAILED) on non-2xx responses.
130
+ */
131
+ async function exchangeCode(config, code, codeVerifier) {
132
+ const params = {
133
+ grant_type: 'authorization_code',
134
+ client_id: config.clientId,
135
+ code,
136
+ redirect_uri: config.redirectUri,
137
+ };
138
+ if (config.clientSecret) {
139
+ params.client_secret = config.clientSecret;
140
+ }
141
+ if (codeVerifier) {
142
+ params.code_verifier = codeVerifier;
143
+ }
144
+ const { status, body } = await postForm(config.tokenUrl, params);
145
+ if (status < 200 || status >= 300) {
146
+ const detail = extractOAuthError(body);
147
+ throw new errors_1.AuthError(`Token exchange failed with status ${status}: ${detail}`, errors_1.ErrorCodes.OAUTH_CODE_EXCHANGE_FAILED);
148
+ }
149
+ return mapTokenResponse(JSON.parse(body));
150
+ }
151
+ // ---------------------------------------------------------------------------
152
+ // refreshToken
153
+ // ---------------------------------------------------------------------------
154
+ /**
155
+ * Exchanges a refresh token for a new token set at the token endpoint.
156
+ * Throws AuthError(TOKEN_REFRESH_FAILED) on non-2xx responses.
157
+ */
158
+ async function refreshToken(config, currentRefreshToken) {
159
+ const params = {
160
+ grant_type: 'refresh_token',
161
+ client_id: config.clientId,
162
+ refresh_token: currentRefreshToken,
163
+ };
164
+ if (config.clientSecret) {
165
+ params.client_secret = config.clientSecret;
166
+ }
167
+ const { status, body } = await postForm(config.tokenUrl, params);
168
+ if (status < 200 || status >= 300) {
169
+ const detail = extractOAuthError(body);
170
+ throw new errors_1.AuthError(`Token refresh failed with status ${status}: ${detail}`, errors_1.ErrorCodes.TOKEN_REFRESH_FAILED);
171
+ }
172
+ return mapTokenResponse(JSON.parse(body));
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // startCallbackServer
176
+ // ---------------------------------------------------------------------------
177
+ const DEFAULT_TIMEOUT_MS = 120_000;
178
+ /**
179
+ * Starts a local HTTP server on 127.0.0.1 to receive the OAuth2 callback.
180
+ * Resolves with { code, state } on success.
181
+ * Rejects with AuthError on OAuth error, state mismatch, port conflict, or timeout.
182
+ * The server is closed after handling the first request or on timeout.
183
+ * The authorization code is never logged.
184
+ */
185
+ function startCallbackServer(port, expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
186
+ return new Promise((resolve, reject) => {
187
+ let settled = false;
188
+ const settle = (action, server) => {
189
+ if (settled)
190
+ return;
191
+ settled = true;
192
+ server.close(() => action());
193
+ };
194
+ const server = (0, node_http_1.createServer)((req, res) => {
195
+ const reqUrl = new node_url_1.URL(req.url ?? '/', `http://127.0.0.1:${port}`);
196
+ const errorParam = reqUrl.searchParams.get('error');
197
+ const errorDescription = reqUrl.searchParams.get('error_description') ?? '';
198
+ const state = reqUrl.searchParams.get('state') ?? '';
199
+ const code = reqUrl.searchParams.get('code');
200
+ if (errorParam) {
201
+ res.writeHead(400, { 'Content-Type': 'text/html' });
202
+ res.end(`<html><body><h1>Authentication Error</h1><p>${errorParam}: ${errorDescription}</p></body></html>`);
203
+ settle(() => reject(new errors_1.AuthError(`OAuth error: ${errorParam} — ${errorDescription}`, errors_1.ErrorCodes.OAUTH_CALLBACK_ERROR)), server);
204
+ return;
205
+ }
206
+ if (state !== expectedState) {
207
+ res.writeHead(400, { 'Content-Type': 'text/html' });
208
+ res.end('<html><body><h1>State Mismatch</h1><p>OAuth state parameter does not match. Please try again.</p></body></html>');
209
+ settle(() => reject(new errors_1.AuthError('OAuth state mismatch — possible CSRF attack', errors_1.ErrorCodes.OAUTH_STATE_EXPIRED)), server);
210
+ return;
211
+ }
212
+ if (code) {
213
+ res.writeHead(200, { 'Content-Type': 'text/html' });
214
+ res.end('<html><body><h1>Authentication successful</h1><p>You can close this tab.</p></body></html>');
215
+ settle(() => resolve({ code, state }), server);
216
+ return;
217
+ }
218
+ res.writeHead(400, { 'Content-Type': 'text/html' });
219
+ res.end('<html><body><h1>Bad Request</h1><p>Missing authorization code.</p></body></html>');
220
+ });
221
+ server.listen(port, '127.0.0.1', () => {
222
+ const timer = setTimeout(() => {
223
+ settle(() => reject(new errors_1.AuthError('OAuth callback timed out — no response received', errors_1.ErrorCodes.OAUTH_CALLBACK_TIMEOUT)), server);
224
+ }, timeoutMs);
225
+ // Ensure the timer does not prevent the process from exiting
226
+ timer.unref();
227
+ });
228
+ server.on('error', (err) => {
229
+ if (err.code === 'EADDRINUSE') {
230
+ reject(new errors_1.AuthError(`Port ${port} is already in use`, errors_1.ErrorCodes.CALLBACK_PORT_IN_USE));
231
+ return;
232
+ }
233
+ reject(err);
234
+ });
235
+ });
236
+ }
237
+ // ---------------------------------------------------------------------------
238
+ // openBrowser
239
+ // ---------------------------------------------------------------------------
240
+ /**
241
+ * Opens the given URL in the default system browser.
242
+ * Fire-and-forget — errors are silently ignored.
243
+ */
244
+ function openBrowser(url) {
245
+ const commands = {
246
+ darwin: 'open',
247
+ linux: 'xdg-open',
248
+ win32: 'start',
249
+ };
250
+ const cmd = commands[process.platform];
251
+ if (!cmd)
252
+ return;
253
+ (0, node_child_process_1.execFile)(cmd, [url], () => {
254
+ // intentionally empty — errors are ignored
255
+ });
256
+ }
257
+ //# sourceMappingURL=oauth.js.map