@37signals/basecamp 0.2.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 (356) hide show
  1. package/README.md +600 -0
  2. package/dist/auth-strategy.d.ts +36 -0
  3. package/dist/auth-strategy.d.ts.map +1 -0
  4. package/dist/auth-strategy.js +33 -0
  5. package/dist/auth-strategy.js.map +1 -0
  6. package/dist/client.d.ts +224 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +715 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/errors.d.ts +137 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +269 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/generated/metadata.json +2281 -0
  15. package/dist/generated/openapi-stripped.json +21268 -0
  16. package/dist/generated/path-mapping.d.ts +8 -0
  17. package/dist/generated/path-mapping.d.ts.map +1 -0
  18. package/dist/generated/path-mapping.js +192 -0
  19. package/dist/generated/path-mapping.js.map +1 -0
  20. package/dist/generated/path-mapping.ts +199 -0
  21. package/dist/generated/schema.d.ts +14883 -0
  22. package/dist/generated/services/attachments.d.ts +27 -0
  23. package/dist/generated/services/attachments.d.ts.map +1 -0
  24. package/dist/generated/services/attachments.js +49 -0
  25. package/dist/generated/services/attachments.js.map +1 -0
  26. package/dist/generated/services/attachments.ts +59 -0
  27. package/dist/generated/services/boosts.d.ts +114 -0
  28. package/dist/generated/services/boosts.d.ts.map +1 -0
  29. package/dist/generated/services/boosts.js +179 -0
  30. package/dist/generated/services/boosts.js.map +1 -0
  31. package/dist/generated/services/boosts.ts +250 -0
  32. package/dist/generated/services/campfires.d.ts +202 -0
  33. package/dist/generated/services/campfires.d.ts.map +1 -0
  34. package/dist/generated/services/campfires.js +309 -0
  35. package/dist/generated/services/campfires.js.map +1 -0
  36. package/dist/generated/services/campfires.ts +433 -0
  37. package/dist/generated/services/card-columns.d.ts +163 -0
  38. package/dist/generated/services/card-columns.d.ts.map +1 -0
  39. package/dist/generated/services/card-columns.js +264 -0
  40. package/dist/generated/services/card-columns.js.map +1 -0
  41. package/dist/generated/services/card-columns.ts +360 -0
  42. package/dist/generated/services/card-steps.d.ts +105 -0
  43. package/dist/generated/services/card-steps.d.ts.map +1 -0
  44. package/dist/generated/services/card-steps.js +148 -0
  45. package/dist/generated/services/card-steps.js.map +1 -0
  46. package/dist/generated/services/card-steps.ts +221 -0
  47. package/dist/generated/services/card-tables.d.ts +27 -0
  48. package/dist/generated/services/card-tables.d.ts.map +1 -0
  49. package/dist/generated/services/card-tables.js +40 -0
  50. package/dist/generated/services/card-tables.js.map +1 -0
  51. package/dist/generated/services/card-tables.ts +55 -0
  52. package/dist/generated/services/cards.d.ts +118 -0
  53. package/dist/generated/services/cards.d.ts.map +1 -0
  54. package/dist/generated/services/cards.js +166 -0
  55. package/dist/generated/services/cards.js.map +1 -0
  56. package/dist/generated/services/cards.ts +247 -0
  57. package/dist/generated/services/checkins.d.ts +278 -0
  58. package/dist/generated/services/checkins.d.ts.map +1 -0
  59. package/dist/generated/services/checkins.js +420 -0
  60. package/dist/generated/services/checkins.js.map +1 -0
  61. package/dist/generated/services/checkins.ts +600 -0
  62. package/dist/generated/services/client-approvals.d.ts +45 -0
  63. package/dist/generated/services/client-approvals.d.ts.map +1 -0
  64. package/dist/generated/services/client-approvals.js +58 -0
  65. package/dist/generated/services/client-approvals.js.map +1 -0
  66. package/dist/generated/services/client-approvals.ts +89 -0
  67. package/dist/generated/services/client-correspondences.d.ts +45 -0
  68. package/dist/generated/services/client-correspondences.d.ts.map +1 -0
  69. package/dist/generated/services/client-correspondences.js +58 -0
  70. package/dist/generated/services/client-correspondences.js.map +1 -0
  71. package/dist/generated/services/client-correspondences.ts +89 -0
  72. package/dist/generated/services/client-replies.d.ts +47 -0
  73. package/dist/generated/services/client-replies.d.ts.map +1 -0
  74. package/dist/generated/services/client-replies.js +65 -0
  75. package/dist/generated/services/client-replies.js.map +1 -0
  76. package/dist/generated/services/client-replies.ts +95 -0
  77. package/dist/generated/services/client-visibility.d.ts +35 -0
  78. package/dist/generated/services/client-visibility.d.ts.map +1 -0
  79. package/dist/generated/services/client-visibility.js +44 -0
  80. package/dist/generated/services/client-visibility.js.map +1 -0
  81. package/dist/generated/services/client-visibility.ts +68 -0
  82. package/dist/generated/services/comments.d.ts +86 -0
  83. package/dist/generated/services/comments.d.ts.map +1 -0
  84. package/dist/generated/services/comments.js +129 -0
  85. package/dist/generated/services/comments.js.map +1 -0
  86. package/dist/generated/services/comments.ts +185 -0
  87. package/dist/generated/services/documents.d.ts +92 -0
  88. package/dist/generated/services/documents.d.ts.map +1 -0
  89. package/dist/generated/services/documents.js +129 -0
  90. package/dist/generated/services/documents.js.map +1 -0
  91. package/dist/generated/services/documents.ts +191 -0
  92. package/dist/generated/services/events.d.ts +34 -0
  93. package/dist/generated/services/events.d.ts.map +1 -0
  94. package/dist/generated/services/events.js +39 -0
  95. package/dist/generated/services/events.js.map +1 -0
  96. package/dist/generated/services/events.ts +64 -0
  97. package/dist/generated/services/forwards.d.ts +112 -0
  98. package/dist/generated/services/forwards.d.ts.map +1 -0
  99. package/dist/generated/services/forwards.js +172 -0
  100. package/dist/generated/services/forwards.js.map +1 -0
  101. package/dist/generated/services/forwards.ts +241 -0
  102. package/dist/generated/services/index.d.ts +39 -0
  103. package/dist/generated/services/index.d.ts.map +1 -0
  104. package/dist/generated/services/index.js +39 -0
  105. package/dist/generated/services/index.js.map +1 -0
  106. package/dist/generated/services/index.ts +38 -0
  107. package/dist/generated/services/lineup.d.ts +67 -0
  108. package/dist/generated/services/lineup.d.ts.map +1 -0
  109. package/dist/generated/services/lineup.js +99 -0
  110. package/dist/generated/services/lineup.js.map +1 -0
  111. package/dist/generated/services/lineup.ts +143 -0
  112. package/dist/generated/services/message-boards.d.ts +27 -0
  113. package/dist/generated/services/message-boards.d.ts.map +1 -0
  114. package/dist/generated/services/message-boards.js +40 -0
  115. package/dist/generated/services/message-boards.js.map +1 -0
  116. package/dist/generated/services/message-boards.ts +55 -0
  117. package/dist/generated/services/message-types.d.ts +100 -0
  118. package/dist/generated/services/message-types.d.ts.map +1 -0
  119. package/dist/generated/services/message-types.js +144 -0
  120. package/dist/generated/services/message-types.js.map +1 -0
  121. package/dist/generated/services/message-types.ts +210 -0
  122. package/dist/generated/services/messages.d.ts +122 -0
  123. package/dist/generated/services/messages.d.ts.map +1 -0
  124. package/dist/generated/services/messages.js +180 -0
  125. package/dist/generated/services/messages.js.map +1 -0
  126. package/dist/generated/services/messages.ts +258 -0
  127. package/dist/generated/services/people.d.ts +122 -0
  128. package/dist/generated/services/people.d.ts.map +1 -0
  129. package/dist/generated/services/people.js +167 -0
  130. package/dist/generated/services/people.js.map +1 -0
  131. package/dist/generated/services/people.ts +252 -0
  132. package/dist/generated/services/projects.d.ts +109 -0
  133. package/dist/generated/services/projects.d.ts.map +1 -0
  134. package/dist/generated/services/projects.js +153 -0
  135. package/dist/generated/services/projects.js.map +1 -0
  136. package/dist/generated/services/projects.ts +224 -0
  137. package/dist/generated/services/recordings.d.ts +93 -0
  138. package/dist/generated/services/recordings.d.ts.map +1 -0
  139. package/dist/generated/services/recordings.js +138 -0
  140. package/dist/generated/services/recordings.js.map +1 -0
  141. package/dist/generated/services/recordings.ts +191 -0
  142. package/dist/generated/services/reports.d.ts +90 -0
  143. package/dist/generated/services/reports.d.ts.map +1 -0
  144. package/dist/generated/services/reports.js +124 -0
  145. package/dist/generated/services/reports.js.map +1 -0
  146. package/dist/generated/services/reports.ts +187 -0
  147. package/dist/generated/services/schedules.d.ts +162 -0
  148. package/dist/generated/services/schedules.d.ts.map +1 -0
  149. package/dist/generated/services/schedules.js +228 -0
  150. package/dist/generated/services/schedules.js.map +1 -0
  151. package/dist/generated/services/schedules.ts +335 -0
  152. package/dist/generated/services/search.d.ts +45 -0
  153. package/dist/generated/services/search.d.ts.map +1 -0
  154. package/dist/generated/services/search.js +56 -0
  155. package/dist/generated/services/search.js.map +1 -0
  156. package/dist/generated/services/search.ts +89 -0
  157. package/dist/generated/services/subscriptions.d.ts +73 -0
  158. package/dist/generated/services/subscriptions.d.ts.map +1 -0
  159. package/dist/generated/services/subscriptions.js +119 -0
  160. package/dist/generated/services/subscriptions.js.map +1 -0
  161. package/dist/generated/services/subscriptions.ts +160 -0
  162. package/dist/generated/services/templates.d.ts +140 -0
  163. package/dist/generated/services/templates.d.ts.map +1 -0
  164. package/dist/generated/services/templates.js +207 -0
  165. package/dist/generated/services/templates.js.map +1 -0
  166. package/dist/generated/services/templates.ts +294 -0
  167. package/dist/generated/services/timeline.d.ts +31 -0
  168. package/dist/generated/services/timeline.d.ts.map +1 -0
  169. package/dist/generated/services/timeline.js +39 -0
  170. package/dist/generated/services/timeline.js.map +1 -0
  171. package/dist/generated/services/timeline.ts +62 -0
  172. package/dist/generated/services/timesheets.d.ts +145 -0
  173. package/dist/generated/services/timesheets.d.ts.map +1 -0
  174. package/dist/generated/services/timesheets.js +186 -0
  175. package/dist/generated/services/timesheets.js.map +1 -0
  176. package/dist/generated/services/timesheets.ts +289 -0
  177. package/dist/generated/services/todolist-groups.d.ts +74 -0
  178. package/dist/generated/services/todolist-groups.d.ts.map +1 -0
  179. package/dist/generated/services/todolist-groups.js +100 -0
  180. package/dist/generated/services/todolist-groups.js.map +1 -0
  181. package/dist/generated/services/todolist-groups.ts +151 -0
  182. package/dist/generated/services/todolists.d.ts +95 -0
  183. package/dist/generated/services/todolists.d.ts.map +1 -0
  184. package/dist/generated/services/todolists.js +130 -0
  185. package/dist/generated/services/todolists.js.map +1 -0
  186. package/dist/generated/services/todolists.ts +192 -0
  187. package/dist/generated/services/todos.d.ts +175 -0
  188. package/dist/generated/services/todos.d.ts.map +1 -0
  189. package/dist/generated/services/todos.js +255 -0
  190. package/dist/generated/services/todos.js.map +1 -0
  191. package/dist/generated/services/todos.ts +369 -0
  192. package/dist/generated/services/todosets.d.ts +27 -0
  193. package/dist/generated/services/todosets.d.ts.map +1 -0
  194. package/dist/generated/services/todosets.js +40 -0
  195. package/dist/generated/services/todosets.js.map +1 -0
  196. package/dist/generated/services/todosets.ts +55 -0
  197. package/dist/generated/services/tools.d.ts +122 -0
  198. package/dist/generated/services/tools.d.ts.map +1 -0
  199. package/dist/generated/services/tools.js +197 -0
  200. package/dist/generated/services/tools.js.map +1 -0
  201. package/dist/generated/services/tools.ts +267 -0
  202. package/dist/generated/services/uploads.d.ts +109 -0
  203. package/dist/generated/services/uploads.d.ts.map +1 -0
  204. package/dist/generated/services/uploads.js +153 -0
  205. package/dist/generated/services/uploads.js.map +1 -0
  206. package/dist/generated/services/uploads.ts +227 -0
  207. package/dist/generated/services/vaults.d.ts +86 -0
  208. package/dist/generated/services/vaults.d.ts.map +1 -0
  209. package/dist/generated/services/vaults.js +126 -0
  210. package/dist/generated/services/vaults.js.map +1 -0
  211. package/dist/generated/services/vaults.ts +182 -0
  212. package/dist/generated/services/webhooks.d.ts +106 -0
  213. package/dist/generated/services/webhooks.d.ts.map +1 -0
  214. package/dist/generated/services/webhooks.js +157 -0
  215. package/dist/generated/services/webhooks.js.map +1 -0
  216. package/dist/generated/services/webhooks.ts +226 -0
  217. package/dist/hooks/otel.d.ts +136 -0
  218. package/dist/hooks/otel.d.ts.map +1 -0
  219. package/dist/hooks/otel.js +240 -0
  220. package/dist/hooks/otel.js.map +1 -0
  221. package/dist/hooks.d.ts +171 -0
  222. package/dist/hooks.d.ts.map +1 -0
  223. package/dist/hooks.js +196 -0
  224. package/dist/hooks.js.map +1 -0
  225. package/dist/index.d.ts +90 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +122 -0
  228. package/dist/index.js.map +1 -0
  229. package/dist/oauth/authorize.d.ts +48 -0
  230. package/dist/oauth/authorize.d.ts.map +1 -0
  231. package/dist/oauth/authorize.js +56 -0
  232. package/dist/oauth/authorize.js.map +1 -0
  233. package/dist/oauth/callback-server.d.ts +56 -0
  234. package/dist/oauth/callback-server.d.ts.map +1 -0
  235. package/dist/oauth/callback-server.js +150 -0
  236. package/dist/oauth/callback-server.js.map +1 -0
  237. package/dist/oauth/discovery.d.ts +54 -0
  238. package/dist/oauth/discovery.d.ts.map +1 -0
  239. package/dist/oauth/discovery.js +116 -0
  240. package/dist/oauth/discovery.js.map +1 -0
  241. package/dist/oauth/exchange.d.ts +96 -0
  242. package/dist/oauth/exchange.d.ts.map +1 -0
  243. package/dist/oauth/exchange.js +324 -0
  244. package/dist/oauth/exchange.js.map +1 -0
  245. package/dist/oauth/identity.d.ts +30 -0
  246. package/dist/oauth/identity.d.ts.map +1 -0
  247. package/dist/oauth/identity.js +82 -0
  248. package/dist/oauth/identity.js.map +1 -0
  249. package/dist/oauth/index.d.ts +50 -0
  250. package/dist/oauth/index.d.ts.map +1 -0
  251. package/dist/oauth/index.js +58 -0
  252. package/dist/oauth/index.js.map +1 -0
  253. package/dist/oauth/interactive-login.d.ts +62 -0
  254. package/dist/oauth/interactive-login.d.ts.map +1 -0
  255. package/dist/oauth/interactive-login.js +106 -0
  256. package/dist/oauth/interactive-login.js.map +1 -0
  257. package/dist/oauth/pkce.d.ts +68 -0
  258. package/dist/oauth/pkce.d.ts.map +1 -0
  259. package/dist/oauth/pkce.js +80 -0
  260. package/dist/oauth/pkce.js.map +1 -0
  261. package/dist/oauth/token-manager.d.ts +92 -0
  262. package/dist/oauth/token-manager.d.ts.map +1 -0
  263. package/dist/oauth/token-manager.js +126 -0
  264. package/dist/oauth/token-manager.js.map +1 -0
  265. package/dist/oauth/token-store.d.ts +45 -0
  266. package/dist/oauth/token-store.d.ts.map +1 -0
  267. package/dist/oauth/token-store.js +105 -0
  268. package/dist/oauth/token-store.js.map +1 -0
  269. package/dist/oauth/types.d.ts +98 -0
  270. package/dist/oauth/types.d.ts.map +1 -0
  271. package/dist/oauth/types.js +8 -0
  272. package/dist/oauth/types.js.map +1 -0
  273. package/dist/pagination-utils.d.ts +27 -0
  274. package/dist/pagination-utils.d.ts.map +1 -0
  275. package/dist/pagination-utils.js +54 -0
  276. package/dist/pagination-utils.js.map +1 -0
  277. package/dist/pagination.d.ts +56 -0
  278. package/dist/pagination.d.ts.map +1 -0
  279. package/dist/pagination.js +57 -0
  280. package/dist/pagination.js.map +1 -0
  281. package/dist/security.d.ts +68 -0
  282. package/dist/security.d.ts.map +1 -0
  283. package/dist/security.js +107 -0
  284. package/dist/security.js.map +1 -0
  285. package/dist/services/authorization.d.ts +124 -0
  286. package/dist/services/authorization.d.ts.map +1 -0
  287. package/dist/services/authorization.js +117 -0
  288. package/dist/services/authorization.js.map +1 -0
  289. package/dist/services/base.d.ts +102 -0
  290. package/dist/services/base.d.ts.map +1 -0
  291. package/dist/services/base.js +230 -0
  292. package/dist/services/base.js.map +1 -0
  293. package/dist/webhooks/adapters/node-http.d.ts +14 -0
  294. package/dist/webhooks/adapters/node-http.d.ts.map +1 -0
  295. package/dist/webhooks/adapters/node-http.js +60 -0
  296. package/dist/webhooks/adapters/node-http.js.map +1 -0
  297. package/dist/webhooks/events.d.ts +52 -0
  298. package/dist/webhooks/events.d.ts.map +1 -0
  299. package/dist/webhooks/events.js +52 -0
  300. package/dist/webhooks/events.js.map +1 -0
  301. package/dist/webhooks/handler.d.ts +62 -0
  302. package/dist/webhooks/handler.d.ts.map +1 -0
  303. package/dist/webhooks/handler.js +226 -0
  304. package/dist/webhooks/handler.js.map +1 -0
  305. package/dist/webhooks/index.d.ts +5 -0
  306. package/dist/webhooks/index.d.ts.map +1 -0
  307. package/dist/webhooks/index.js +5 -0
  308. package/dist/webhooks/index.js.map +1 -0
  309. package/dist/webhooks/verify.d.ts +11 -0
  310. package/dist/webhooks/verify.d.ts.map +1 -0
  311. package/dist/webhooks/verify.js +25 -0
  312. package/dist/webhooks/verify.js.map +1 -0
  313. package/package.json +70 -0
  314. package/src/generated/metadata.json +2281 -0
  315. package/src/generated/openapi-stripped.json +21268 -0
  316. package/src/generated/path-mapping.ts +199 -0
  317. package/src/generated/schema.d.ts +14883 -0
  318. package/src/generated/services/attachments.ts +59 -0
  319. package/src/generated/services/boosts.ts +250 -0
  320. package/src/generated/services/campfires.ts +433 -0
  321. package/src/generated/services/card-columns.ts +360 -0
  322. package/src/generated/services/card-steps.ts +221 -0
  323. package/src/generated/services/card-tables.ts +55 -0
  324. package/src/generated/services/cards.ts +247 -0
  325. package/src/generated/services/checkins.ts +600 -0
  326. package/src/generated/services/client-approvals.ts +89 -0
  327. package/src/generated/services/client-correspondences.ts +89 -0
  328. package/src/generated/services/client-replies.ts +95 -0
  329. package/src/generated/services/client-visibility.ts +68 -0
  330. package/src/generated/services/comments.ts +185 -0
  331. package/src/generated/services/documents.ts +191 -0
  332. package/src/generated/services/events.ts +64 -0
  333. package/src/generated/services/forwards.ts +241 -0
  334. package/src/generated/services/index.ts +38 -0
  335. package/src/generated/services/lineup.ts +143 -0
  336. package/src/generated/services/message-boards.ts +55 -0
  337. package/src/generated/services/message-types.ts +210 -0
  338. package/src/generated/services/messages.ts +258 -0
  339. package/src/generated/services/people.ts +252 -0
  340. package/src/generated/services/projects.ts +224 -0
  341. package/src/generated/services/recordings.ts +191 -0
  342. package/src/generated/services/reports.ts +187 -0
  343. package/src/generated/services/schedules.ts +335 -0
  344. package/src/generated/services/search.ts +89 -0
  345. package/src/generated/services/subscriptions.ts +160 -0
  346. package/src/generated/services/templates.ts +294 -0
  347. package/src/generated/services/timeline.ts +62 -0
  348. package/src/generated/services/timesheets.ts +289 -0
  349. package/src/generated/services/todolist-groups.ts +151 -0
  350. package/src/generated/services/todolists.ts +192 -0
  351. package/src/generated/services/todos.ts +369 -0
  352. package/src/generated/services/todosets.ts +55 -0
  353. package/src/generated/services/tools.ts +267 -0
  354. package/src/generated/services/uploads.ts +227 -0
  355. package/src/generated/services/vaults.ts +182 -0
  356. package/src/generated/services/webhooks.ts +226 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Interactive OAuth login flow for CLI and desktop applications.
3
+ *
4
+ * Orchestrates the full OAuth 2.0 authorization code flow:
5
+ * discovery, PKCE, local callback server, browser launch, code exchange.
6
+ */
7
+ import { discover, discoverLaunchpad } from "./discovery.js";
8
+ import { generateState, generatePKCE } from "./pkce.js";
9
+ import { buildAuthorizationUrl } from "./authorize.js";
10
+ import { startCallbackServer } from "./callback-server.js";
11
+ import { exchangeCode } from "./exchange.js";
12
+ import { BasecampError } from "../errors.js";
13
+ /**
14
+ * Performs the full interactive OAuth login flow.
15
+ *
16
+ * Steps:
17
+ * 1. Discover OAuth endpoints
18
+ * 2. Generate PKCE and state parameters
19
+ * 3. Start local callback server
20
+ * 4. Build authorization URL and open browser
21
+ * 5. Wait for callback with authorization code
22
+ * 6. Exchange code for tokens
23
+ * 7. Save token to store
24
+ *
25
+ * @param options - Login flow configuration
26
+ * @returns The resulting OAuth token
27
+ * @throws BasecampError on any flow failure
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import open from "open"; // or use child_process
32
+ *
33
+ * const token = await performInteractiveLogin({
34
+ * clientId: process.env.BASECAMP_CLIENT_ID!,
35
+ * clientSecret: process.env.BASECAMP_CLIENT_SECRET!,
36
+ * store: new FileTokenStore("~/.config/basecamp/tokens.json"),
37
+ * openBrowser: (url) => open(url),
38
+ * onStatus: (msg) => console.log(msg),
39
+ * });
40
+ * ```
41
+ */
42
+ export async function performInteractiveLogin(options) {
43
+ const { clientId, clientSecret, store, baseUrl, useLegacyFormat = true, callbackPort, openBrowser, promptForManualVisit, onStatus, } = options;
44
+ // 1. Discover OAuth endpoints
45
+ onStatus?.("Discovering OAuth endpoints...");
46
+ const config = baseUrl ? await discover(baseUrl) : await discoverLaunchpad();
47
+ // 2. Generate PKCE and state
48
+ const state = generateState();
49
+ const pkce = await generatePKCE();
50
+ // 3. Start callback server
51
+ onStatus?.("Starting callback server...");
52
+ const { url: redirectUri, waitForCallback, close } = await startCallbackServer({
53
+ port: callbackPort,
54
+ expectedState: state,
55
+ });
56
+ try {
57
+ // 4. Build authorization URL
58
+ const authUrl = buildAuthorizationUrl({
59
+ authorizationEndpoint: config.authorizationEndpoint,
60
+ clientId,
61
+ redirectUri,
62
+ state,
63
+ pkce,
64
+ });
65
+ // 5. Open browser
66
+ onStatus?.("Opening browser for authorization...");
67
+ let browserOpened = false;
68
+ try {
69
+ await openBrowser(authUrl.toString());
70
+ browserOpened = true;
71
+ }
72
+ catch {
73
+ // Browser launch failed
74
+ }
75
+ if (!browserOpened) {
76
+ if (promptForManualVisit) {
77
+ await promptForManualVisit(authUrl.toString());
78
+ }
79
+ else {
80
+ throw new BasecampError("auth", "Failed to open browser and no manual visit prompt configured");
81
+ }
82
+ }
83
+ // 6. Wait for callback
84
+ onStatus?.("Waiting for authorization...");
85
+ const { code } = await waitForCallback();
86
+ // 7. Exchange code for tokens
87
+ onStatus?.("Exchanging authorization code for tokens...");
88
+ const token = await exchangeCode({
89
+ tokenEndpoint: config.tokenEndpoint,
90
+ code,
91
+ redirectUri,
92
+ clientId,
93
+ clientSecret,
94
+ codeVerifier: pkce.verifier,
95
+ useLegacyFormat,
96
+ });
97
+ // 8. Save token
98
+ await store.save(token);
99
+ onStatus?.("Authorization complete.");
100
+ return token;
101
+ }
102
+ finally {
103
+ close();
104
+ }
105
+ }
106
+ //# sourceMappingURL=interactive-login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive-login.js","sourceRoot":"","sources":["../../src/oauth/interactive-login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * PKCE (Proof Key for Code Exchange) utilities for OAuth 2.0.
3
+ *
4
+ * Provides cryptographically secure code verifier and challenge generation
5
+ * to protect against authorization code interception attacks.
6
+ */
7
+ /**
8
+ * PKCE parameters for OAuth 2.0 authorization code flow with PKCE.
9
+ */
10
+ export interface PKCE {
11
+ /** The code_verifier to send during token exchange */
12
+ verifier: string;
13
+ /** The code_challenge (SHA256 hash of verifier) to send during authorization */
14
+ challenge: string;
15
+ }
16
+ /**
17
+ * Generates a cryptographically secure PKCE code verifier and challenge.
18
+ *
19
+ * The verifier is 43 characters (32 random bytes, base64url-encoded).
20
+ * The challenge is the base64url-encoded SHA256 hash of the verifier.
21
+ *
22
+ * Use code_challenge_method=S256 with the challenge in the authorization request.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const pkce = await generatePKCE();
27
+ *
28
+ * // In authorization request:
29
+ * const authUrl = new URL(authEndpoint);
30
+ * authUrl.searchParams.set("code_challenge", pkce.challenge);
31
+ * authUrl.searchParams.set("code_challenge_method", "S256");
32
+ *
33
+ * // Later, in token exchange:
34
+ * const token = await exchangeCode({
35
+ * code,
36
+ * codeVerifier: pkce.verifier,
37
+ * // ...
38
+ * });
39
+ * ```
40
+ */
41
+ export declare function generatePKCE(): Promise<PKCE>;
42
+ /**
43
+ * Generates a cryptographically secure OAuth state parameter.
44
+ *
45
+ * The state is 22 characters (16 random bytes, base64url-encoded).
46
+ * Use this to prevent CSRF attacks on the OAuth flow.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const state = generateState();
51
+ *
52
+ * // Store state before redirect:
53
+ * sessionStorage.setItem("oauth_state", state);
54
+ *
55
+ * // In authorization request:
56
+ * const authUrl = new URL(authEndpoint);
57
+ * authUrl.searchParams.set("state", state);
58
+ *
59
+ * // In callback handler:
60
+ * const returnedState = new URL(window.location.href).searchParams.get("state");
61
+ * const savedState = sessionStorage.getItem("oauth_state");
62
+ * if (returnedState !== savedState) {
63
+ * throw new Error("State mismatch - possible CSRF attack");
64
+ * }
65
+ * ```
66
+ */
67
+ export declare function generateState(): string;
68
+ //# sourceMappingURL=pkce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/oauth/pkce.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;CACnB;AAYD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAWlD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAGtC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * PKCE (Proof Key for Code Exchange) utilities for OAuth 2.0.
3
+ *
4
+ * Provides cryptographically secure code verifier and challenge generation
5
+ * to protect against authorization code interception attacks.
6
+ */
7
+ /**
8
+ * Base64url encodes a Uint8Array without padding.
9
+ */
10
+ function base64UrlEncode(bytes) {
11
+ // Convert to base64
12
+ const base64 = btoa(String.fromCharCode(...bytes));
13
+ // Convert to base64url (replace + with -, / with _, remove padding)
14
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
15
+ }
16
+ /**
17
+ * Generates a cryptographically secure PKCE code verifier and challenge.
18
+ *
19
+ * The verifier is 43 characters (32 random bytes, base64url-encoded).
20
+ * The challenge is the base64url-encoded SHA256 hash of the verifier.
21
+ *
22
+ * Use code_challenge_method=S256 with the challenge in the authorization request.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const pkce = await generatePKCE();
27
+ *
28
+ * // In authorization request:
29
+ * const authUrl = new URL(authEndpoint);
30
+ * authUrl.searchParams.set("code_challenge", pkce.challenge);
31
+ * authUrl.searchParams.set("code_challenge_method", "S256");
32
+ *
33
+ * // Later, in token exchange:
34
+ * const token = await exchangeCode({
35
+ * code,
36
+ * codeVerifier: pkce.verifier,
37
+ * // ...
38
+ * });
39
+ * ```
40
+ */
41
+ export async function generatePKCE() {
42
+ // Generate 32 random bytes
43
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
44
+ const verifier = base64UrlEncode(bytes);
45
+ // Compute SHA256 hash of the verifier
46
+ const encoder = new TextEncoder();
47
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoder.encode(verifier));
48
+ const challenge = base64UrlEncode(new Uint8Array(hashBuffer));
49
+ return { verifier, challenge };
50
+ }
51
+ /**
52
+ * Generates a cryptographically secure OAuth state parameter.
53
+ *
54
+ * The state is 22 characters (16 random bytes, base64url-encoded).
55
+ * Use this to prevent CSRF attacks on the OAuth flow.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const state = generateState();
60
+ *
61
+ * // Store state before redirect:
62
+ * sessionStorage.setItem("oauth_state", state);
63
+ *
64
+ * // In authorization request:
65
+ * const authUrl = new URL(authEndpoint);
66
+ * authUrl.searchParams.set("state", state);
67
+ *
68
+ * // In callback handler:
69
+ * const returnedState = new URL(window.location.href).searchParams.get("state");
70
+ * const savedState = sessionStorage.getItem("oauth_state");
71
+ * if (returnedState !== savedState) {
72
+ * throw new Error("State mismatch - possible CSRF attack");
73
+ * }
74
+ * ```
75
+ */
76
+ export function generateState() {
77
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
78
+ return base64UrlEncode(bytes);
79
+ }
80
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/oauth/pkce.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH;;GAEG;AACH,SAAS,eAAe,CAAC,KAAiB;IACxC,oBAAoB;IACpB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IACnD,oEAAoE;IACpE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,2BAA2B;IAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IAExC,sCAAsC;IACtC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IACnF,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IAE9D,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IACzD,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * OAuth 2.0 token lifecycle management.
3
+ *
4
+ * Handles automatic token refresh, expiry detection, and refresh deduplication.
5
+ */
6
+ import type { TokenStore } from "./token-store.js";
7
+ import type { RefreshRequest, OAuthToken } from "./types.js";
8
+ /**
9
+ * Options for creating a TokenManager.
10
+ */
11
+ export interface TokenManagerOptions {
12
+ /** Token persistence store */
13
+ store: TokenStore;
14
+ /** Function to perform the token refresh HTTP call */
15
+ refreshToken: (req: RefreshRequest) => Promise<OAuthToken>;
16
+ /** URL of the token endpoint */
17
+ tokenEndpoint: string;
18
+ /** The client identifier (optional) */
19
+ clientId?: string;
20
+ /** The client secret (optional) */
21
+ clientSecret?: string;
22
+ /** Use Launchpad's non-standard token format (optional) */
23
+ useLegacyFormat?: boolean;
24
+ /** Seconds before expiry to trigger refresh (default: 120) */
25
+ bufferSeconds?: number;
26
+ }
27
+ /**
28
+ * Manages OAuth token lifecycle with automatic refresh and persistence.
29
+ *
30
+ * Loads tokens from a store on first access, refreshes when expired,
31
+ * and deduplicates concurrent refresh requests.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const manager = new TokenManager({
36
+ * store: new FileTokenStore("~/.config/basecamp/tokens.json"),
37
+ * refreshToken,
38
+ * tokenEndpoint: config.tokenEndpoint,
39
+ * clientId: "my_client_id",
40
+ * clientSecret: "my_client_secret",
41
+ * useLegacyFormat: true,
42
+ * });
43
+ *
44
+ * // Use as a TokenProvider
45
+ * const client = createBasecampClient({
46
+ * accountId: "12345",
47
+ * accessToken: () => manager.getToken(),
48
+ * });
49
+ * ```
50
+ */
51
+ export declare class TokenManager {
52
+ private readonly store;
53
+ private readonly doRefresh;
54
+ private readonly tokenEndpoint;
55
+ private readonly clientId?;
56
+ private readonly clientSecret?;
57
+ private readonly useLegacyFormat?;
58
+ private readonly bufferSeconds;
59
+ private token;
60
+ private loaded;
61
+ private refreshPromise;
62
+ constructor(options: TokenManagerOptions);
63
+ /**
64
+ * Returns a valid access token string, refreshing if necessary.
65
+ *
66
+ * On first call, loads from store. If expired, refreshes and saves.
67
+ * Concurrent calls during refresh share the same promise.
68
+ *
69
+ * @throws if no token is available and refresh fails
70
+ */
71
+ getToken(): Promise<string>;
72
+ /**
73
+ * Forces a token refresh regardless of expiry.
74
+ *
75
+ * Concurrent calls share the same in-flight request.
76
+ *
77
+ * @throws if no refresh token is available or refresh fails
78
+ */
79
+ forceRefresh(): Promise<OAuthToken>;
80
+ /**
81
+ * Checks if the current token is expired.
82
+ *
83
+ * Returns true if no token is loaded or the token has expired.
84
+ */
85
+ isExpired(): boolean;
86
+ /**
87
+ * The currently loaded token, or null if none.
88
+ */
89
+ get currentToken(): OAuthToken | null;
90
+ private executeRefresh;
91
+ }
92
+ //# sourceMappingURL=token-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/oauth/token-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB,sDAAsD;IACtD,YAAY,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3D,gCAAgC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+C;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAU;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAoC;gBAE9C,OAAO,EAAE,mBAAmB;IAUxC;;;;;;;OAOG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAcjC;;;;;;OAMG;IACG,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC;IAczC;;;;OAIG;IACH,SAAS,IAAI,OAAO;IAKpB;;OAEG;IACH,IAAI,YAAY,IAAI,UAAU,GAAG,IAAI,CAEpC;YAEa,cAAc;CAuB7B"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * OAuth 2.0 token lifecycle management.
3
+ *
4
+ * Handles automatic token refresh, expiry detection, and refresh deduplication.
5
+ */
6
+ import { isTokenExpired } from "./exchange.js";
7
+ /**
8
+ * Manages OAuth token lifecycle with automatic refresh and persistence.
9
+ *
10
+ * Loads tokens from a store on first access, refreshes when expired,
11
+ * and deduplicates concurrent refresh requests.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const manager = new TokenManager({
16
+ * store: new FileTokenStore("~/.config/basecamp/tokens.json"),
17
+ * refreshToken,
18
+ * tokenEndpoint: config.tokenEndpoint,
19
+ * clientId: "my_client_id",
20
+ * clientSecret: "my_client_secret",
21
+ * useLegacyFormat: true,
22
+ * });
23
+ *
24
+ * // Use as a TokenProvider
25
+ * const client = createBasecampClient({
26
+ * accountId: "12345",
27
+ * accessToken: () => manager.getToken(),
28
+ * });
29
+ * ```
30
+ */
31
+ export class TokenManager {
32
+ store;
33
+ doRefresh;
34
+ tokenEndpoint;
35
+ clientId;
36
+ clientSecret;
37
+ useLegacyFormat;
38
+ bufferSeconds;
39
+ token = null;
40
+ loaded = false;
41
+ refreshPromise = null;
42
+ constructor(options) {
43
+ this.store = options.store;
44
+ this.doRefresh = options.refreshToken;
45
+ this.tokenEndpoint = options.tokenEndpoint;
46
+ this.clientId = options.clientId;
47
+ this.clientSecret = options.clientSecret;
48
+ this.useLegacyFormat = options.useLegacyFormat;
49
+ this.bufferSeconds = options.bufferSeconds ?? 120;
50
+ }
51
+ /**
52
+ * Returns a valid access token string, refreshing if necessary.
53
+ *
54
+ * On first call, loads from store. If expired, refreshes and saves.
55
+ * Concurrent calls during refresh share the same promise.
56
+ *
57
+ * @throws if no token is available and refresh fails
58
+ */
59
+ async getToken() {
60
+ if (!this.loaded) {
61
+ this.token = await this.store.load();
62
+ this.loaded = true;
63
+ }
64
+ if (this.token && !isTokenExpired(this.token, this.bufferSeconds)) {
65
+ return this.token.accessToken;
66
+ }
67
+ const refreshed = await this.forceRefresh();
68
+ return refreshed.accessToken;
69
+ }
70
+ /**
71
+ * Forces a token refresh regardless of expiry.
72
+ *
73
+ * Concurrent calls share the same in-flight request.
74
+ *
75
+ * @throws if no refresh token is available or refresh fails
76
+ */
77
+ async forceRefresh() {
78
+ if (this.refreshPromise) {
79
+ return this.refreshPromise;
80
+ }
81
+ this.refreshPromise = this.executeRefresh();
82
+ try {
83
+ return await this.refreshPromise;
84
+ }
85
+ finally {
86
+ this.refreshPromise = null;
87
+ }
88
+ }
89
+ /**
90
+ * Checks if the current token is expired.
91
+ *
92
+ * Returns true if no token is loaded or the token has expired.
93
+ */
94
+ isExpired() {
95
+ if (!this.token)
96
+ return true;
97
+ return isTokenExpired(this.token, this.bufferSeconds);
98
+ }
99
+ /**
100
+ * The currently loaded token, or null if none.
101
+ */
102
+ get currentToken() {
103
+ return this.token;
104
+ }
105
+ async executeRefresh() {
106
+ const refreshTokenValue = this.token?.refreshToken;
107
+ if (!refreshTokenValue) {
108
+ throw new Error("No refresh token available");
109
+ }
110
+ const newToken = await this.doRefresh({
111
+ tokenEndpoint: this.tokenEndpoint,
112
+ refreshToken: refreshTokenValue,
113
+ clientId: this.clientId,
114
+ clientSecret: this.clientSecret,
115
+ useLegacyFormat: this.useLegacyFormat,
116
+ });
117
+ // Preserve the previous refresh token when the server omits one
118
+ const merged = newToken.refreshToken
119
+ ? newToken
120
+ : { ...newToken, refreshToken: refreshTokenValue };
121
+ this.token = merged;
122
+ await this.store.save(merged);
123
+ return merged;
124
+ }
125
+ }
126
+ //# sourceMappingURL=token-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/oauth/token-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Token persistence for OAuth 2.0 tokens.
3
+ *
4
+ * Provides a file-based token store with atomic writes and secure permissions.
5
+ */
6
+ import type { OAuthToken } from "./types.js";
7
+ /**
8
+ * Interface for persisting OAuth tokens.
9
+ */
10
+ export interface TokenStore {
11
+ /** Load a previously saved token, or null if none exists */
12
+ load(): Promise<OAuthToken | null>;
13
+ /** Save a token for later retrieval */
14
+ save(token: OAuthToken): Promise<void>;
15
+ /** Remove the stored token */
16
+ clear(): Promise<void>;
17
+ }
18
+ /**
19
+ * File-based token store with atomic writes and secure permissions.
20
+ *
21
+ * Tokens are stored as JSON with 0o600 permissions (owner read/write only).
22
+ * Writes are atomic: data is written to a temporary file then renamed.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const store = new FileTokenStore("~/.config/basecamp/tokens.json");
27
+ *
28
+ * // Save a token
29
+ * await store.save(token);
30
+ *
31
+ * // Load it back
32
+ * const loaded = await store.load();
33
+ *
34
+ * // Clear when done
35
+ * await store.clear();
36
+ * ```
37
+ */
38
+ export declare class FileTokenStore implements TokenStore {
39
+ private readonly filePath;
40
+ constructor(filePath: string);
41
+ load(): Promise<OAuthToken | null>;
42
+ save(token: OAuthToken): Promise<void>;
43
+ clear(): Promise<void>;
44
+ }
45
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/oauth/token-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,IAAI,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnC,uCAAuC;IACvC,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,8BAA8B;IAC9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA2BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,QAAQ,EAAE,MAAM;IAItB,IAAI,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAuBlC,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAU7B"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Token persistence for OAuth 2.0 tokens.
3
+ *
4
+ * Provides a file-based token store with atomic writes and secure permissions.
5
+ */
6
+ import { readFile, writeFile, rename, unlink, mkdir } from "node:fs/promises";
7
+ import { dirname } from "node:path";
8
+ import { homedir } from "node:os";
9
+ /**
10
+ * Resolves ~ to the user's home directory.
11
+ */
12
+ function expandHome(filePath) {
13
+ if (filePath.startsWith("~/")) {
14
+ return filePath.replace("~", homedir());
15
+ }
16
+ if (filePath === "~") {
17
+ return homedir();
18
+ }
19
+ return filePath;
20
+ }
21
+ /**
22
+ * File-based token store with atomic writes and secure permissions.
23
+ *
24
+ * Tokens are stored as JSON with 0o600 permissions (owner read/write only).
25
+ * Writes are atomic: data is written to a temporary file then renamed.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const store = new FileTokenStore("~/.config/basecamp/tokens.json");
30
+ *
31
+ * // Save a token
32
+ * await store.save(token);
33
+ *
34
+ * // Load it back
35
+ * const loaded = await store.load();
36
+ *
37
+ * // Clear when done
38
+ * await store.clear();
39
+ * ```
40
+ */
41
+ export class FileTokenStore {
42
+ filePath;
43
+ constructor(filePath) {
44
+ this.filePath = expandHome(filePath);
45
+ }
46
+ async load() {
47
+ let raw;
48
+ try {
49
+ raw = await readFile(this.filePath, "utf-8");
50
+ }
51
+ catch (err) {
52
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
53
+ return null;
54
+ }
55
+ throw err;
56
+ }
57
+ const data = JSON.parse(raw);
58
+ return {
59
+ accessToken: data.accessToken,
60
+ refreshToken: data.refreshToken,
61
+ tokenType: data.tokenType,
62
+ expiresIn: data.expiresIn,
63
+ expiresAt: data.expiresAt ? new Date(data.expiresAt) : undefined,
64
+ scope: data.scope,
65
+ };
66
+ }
67
+ async save(token) {
68
+ const serialized = {
69
+ accessToken: token.accessToken,
70
+ refreshToken: token.refreshToken,
71
+ tokenType: token.tokenType,
72
+ expiresIn: token.expiresIn,
73
+ expiresAt: token.expiresAt?.toISOString(),
74
+ scope: token.scope,
75
+ };
76
+ const json = JSON.stringify(serialized, null, 2) + "\n";
77
+ const tmpPath = this.filePath + ".tmp";
78
+ // Ensure directory exists
79
+ await mkdir(dirname(this.filePath), { recursive: true });
80
+ // Remove stale tmp file so writeFile creates a fresh one with 0o600
81
+ try {
82
+ await unlink(tmpPath);
83
+ }
84
+ catch (err) {
85
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
86
+ throw err;
87
+ }
88
+ }
89
+ // Atomic write: write to tmp, then rename
90
+ await writeFile(tmpPath, json, { mode: 0o600 });
91
+ await rename(tmpPath, this.filePath);
92
+ }
93
+ async clear() {
94
+ try {
95
+ await unlink(this.filePath);
96
+ }
97
+ catch (err) {
98
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
99
+ return; // Already gone
100
+ }
101
+ throw err;
102
+ }
103
+ }
104
+ }
105
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/oauth/token-store.ts"],"names":[],"mappings":"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"}