@payloadcms/figma 0.0.1-alpha.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 (305) hide show
  1. package/LICENSE.md +11 -0
  2. package/bin/cli.js +4 -0
  3. package/dist/api/control-plane.d.ts +124 -0
  4. package/dist/api/control-plane.d.ts.map +1 -0
  5. package/dist/api/control-plane.js +245 -0
  6. package/dist/api/control-plane.js.map +1 -0
  7. package/dist/api/control-plane.spec.d.ts +2 -0
  8. package/dist/api/control-plane.spec.d.ts.map +1 -0
  9. package/dist/api/control-plane.spec.js +296 -0
  10. package/dist/api/control-plane.spec.js.map +1 -0
  11. package/dist/api/figma-api.d.ts +31 -0
  12. package/dist/api/figma-api.d.ts.map +1 -0
  13. package/dist/api/figma-api.js +169 -0
  14. package/dist/api/figma-api.js.map +1 -0
  15. package/dist/api/figma-api.spec.d.ts +14 -0
  16. package/dist/api/figma-api.spec.d.ts.map +1 -0
  17. package/dist/api/figma-api.spec.js +201 -0
  18. package/dist/api/figma-api.spec.js.map +1 -0
  19. package/dist/auth/__tests__/fixtures.d.ts +26 -0
  20. package/dist/auth/__tests__/fixtures.d.ts.map +1 -0
  21. package/dist/auth/__tests__/fixtures.js +67 -0
  22. package/dist/auth/__tests__/fixtures.js.map +1 -0
  23. package/dist/auth/__tests__/mocks.d.ts +33 -0
  24. package/dist/auth/__tests__/mocks.d.ts.map +1 -0
  25. package/dist/auth/__tests__/mocks.js +63 -0
  26. package/dist/auth/__tests__/mocks.js.map +1 -0
  27. package/dist/auth/browser.d.ts +31 -0
  28. package/dist/auth/browser.d.ts.map +1 -0
  29. package/dist/auth/browser.js +41 -0
  30. package/dist/auth/browser.js.map +1 -0
  31. package/dist/auth/callback-server.d.ts +61 -0
  32. package/dist/auth/callback-server.d.ts.map +1 -0
  33. package/dist/auth/callback-server.js +168 -0
  34. package/dist/auth/callback-server.js.map +1 -0
  35. package/dist/auth/callback-server.spec.d.ts +2 -0
  36. package/dist/auth/callback-server.spec.d.ts.map +1 -0
  37. package/dist/auth/callback-server.spec.js +85 -0
  38. package/dist/auth/callback-server.spec.js.map +1 -0
  39. package/dist/auth/crypto-utils.d.ts +33 -0
  40. package/dist/auth/crypto-utils.d.ts.map +1 -0
  41. package/dist/auth/crypto-utils.js +96 -0
  42. package/dist/auth/crypto-utils.js.map +1 -0
  43. package/dist/auth/crypto-utils.spec.d.ts +2 -0
  44. package/dist/auth/crypto-utils.spec.d.ts.map +1 -0
  45. package/dist/auth/crypto-utils.spec.js +36 -0
  46. package/dist/auth/crypto-utils.spec.js.map +1 -0
  47. package/dist/auth/jwt-validator.d.ts +41 -0
  48. package/dist/auth/jwt-validator.d.ts.map +1 -0
  49. package/dist/auth/jwt-validator.js +113 -0
  50. package/dist/auth/jwt-validator.js.map +1 -0
  51. package/dist/auth/jwt-validator.spec.d.ts +10 -0
  52. package/dist/auth/jwt-validator.spec.d.ts.map +1 -0
  53. package/dist/auth/jwt-validator.spec.js +236 -0
  54. package/dist/auth/jwt-validator.spec.js.map +1 -0
  55. package/dist/auth/oauth-flow.d.ts +62 -0
  56. package/dist/auth/oauth-flow.d.ts.map +1 -0
  57. package/dist/auth/oauth-flow.js +172 -0
  58. package/dist/auth/oauth-flow.js.map +1 -0
  59. package/dist/auth/oauth-flow.spec.d.ts +2 -0
  60. package/dist/auth/oauth-flow.spec.d.ts.map +1 -0
  61. package/dist/auth/oauth-flow.spec.js +134 -0
  62. package/dist/auth/oauth-flow.spec.js.map +1 -0
  63. package/dist/auth/pages/error.html +85 -0
  64. package/dist/auth/pages/success.html +74 -0
  65. package/dist/auth/pkce.d.ts +51 -0
  66. package/dist/auth/pkce.d.ts.map +1 -0
  67. package/dist/auth/pkce.js +71 -0
  68. package/dist/auth/pkce.js.map +1 -0
  69. package/dist/auth/pkce.spec.d.ts +2 -0
  70. package/dist/auth/pkce.spec.d.ts.map +1 -0
  71. package/dist/auth/pkce.spec.js +32 -0
  72. package/dist/auth/pkce.spec.js.map +1 -0
  73. package/dist/auth/project-token.d.ts +47 -0
  74. package/dist/auth/project-token.d.ts.map +1 -0
  75. package/dist/auth/project-token.js +134 -0
  76. package/dist/auth/project-token.js.map +1 -0
  77. package/dist/auth/project-token.spec.d.ts +2 -0
  78. package/dist/auth/project-token.spec.d.ts.map +1 -0
  79. package/dist/auth/project-token.spec.js +332 -0
  80. package/dist/auth/project-token.spec.js.map +1 -0
  81. package/dist/auth/refresh.d.ts +35 -0
  82. package/dist/auth/refresh.d.ts.map +1 -0
  83. package/dist/auth/refresh.js +149 -0
  84. package/dist/auth/refresh.js.map +1 -0
  85. package/dist/auth/refresh.spec.d.ts +2 -0
  86. package/dist/auth/refresh.spec.d.ts.map +1 -0
  87. package/dist/auth/refresh.spec.js +105 -0
  88. package/dist/auth/refresh.spec.js.map +1 -0
  89. package/dist/auth/token-store.d.ts +110 -0
  90. package/dist/auth/token-store.d.ts.map +1 -0
  91. package/dist/auth/token-store.js +188 -0
  92. package/dist/auth/token-store.js.map +1 -0
  93. package/dist/auth/token-store.spec.d.ts +2 -0
  94. package/dist/auth/token-store.spec.d.ts.map +1 -0
  95. package/dist/auth/token-store.spec.js +276 -0
  96. package/dist/auth/token-store.spec.js.map +1 -0
  97. package/dist/auth/types.d.ts +197 -0
  98. package/dist/auth/types.d.ts.map +1 -0
  99. package/dist/auth/types.js +6 -0
  100. package/dist/auth/types.js.map +1 -0
  101. package/dist/cli.d.ts +5 -0
  102. package/dist/cli.d.ts.map +1 -0
  103. package/dist/cli.js +108 -0
  104. package/dist/cli.js.map +1 -0
  105. package/dist/commands/__tests__/test-utils.d.ts +28 -0
  106. package/dist/commands/__tests__/test-utils.d.ts.map +1 -0
  107. package/dist/commands/__tests__/test-utils.js +34 -0
  108. package/dist/commands/__tests__/test-utils.js.map +1 -0
  109. package/dist/commands/deploy.d.ts +16 -0
  110. package/dist/commands/deploy.d.ts.map +1 -0
  111. package/dist/commands/deploy.js +297 -0
  112. package/dist/commands/deploy.js.map +1 -0
  113. package/dist/commands/deploy.spec.d.ts +2 -0
  114. package/dist/commands/deploy.spec.d.ts.map +1 -0
  115. package/dist/commands/deploy.spec.js +18 -0
  116. package/dist/commands/deploy.spec.js.map +1 -0
  117. package/dist/commands/init.d.ts +30 -0
  118. package/dist/commands/init.d.ts.map +1 -0
  119. package/dist/commands/init.js +410 -0
  120. package/dist/commands/init.js.map +1 -0
  121. package/dist/commands/init.spec.d.ts +2 -0
  122. package/dist/commands/init.spec.d.ts.map +1 -0
  123. package/dist/commands/init.spec.js +283 -0
  124. package/dist/commands/init.spec.js.map +1 -0
  125. package/dist/commands/list-tokens.d.ts +16 -0
  126. package/dist/commands/list-tokens.d.ts.map +1 -0
  127. package/dist/commands/list-tokens.js +44 -0
  128. package/dist/commands/list-tokens.js.map +1 -0
  129. package/dist/commands/login.d.ts +17 -0
  130. package/dist/commands/login.d.ts.map +1 -0
  131. package/dist/commands/login.js +69 -0
  132. package/dist/commands/login.js.map +1 -0
  133. package/dist/commands/logout.d.ts +16 -0
  134. package/dist/commands/logout.d.ts.map +1 -0
  135. package/dist/commands/logout.js +43 -0
  136. package/dist/commands/logout.js.map +1 -0
  137. package/dist/config/oauth.d.ts +19 -0
  138. package/dist/config/oauth.d.ts.map +1 -0
  139. package/dist/config/oauth.js +43 -0
  140. package/dist/config/oauth.js.map +1 -0
  141. package/dist/constants.d.ts +27 -0
  142. package/dist/constants.d.ts.map +1 -0
  143. package/dist/constants.js +27 -0
  144. package/dist/constants.js.map +1 -0
  145. package/dist/index.d.ts +5 -0
  146. package/dist/index.d.ts.map +1 -0
  147. package/dist/index.js +6 -0
  148. package/dist/index.js.map +1 -0
  149. package/dist/plugin/adapter.d.ts +5 -0
  150. package/dist/plugin/adapter.d.ts.map +1 -0
  151. package/dist/plugin/adapter.js +139 -0
  152. package/dist/plugin/adapter.js.map +1 -0
  153. package/dist/plugin/build-config.d.ts +37 -0
  154. package/dist/plugin/build-config.d.ts.map +1 -0
  155. package/dist/plugin/build-config.js +25 -0
  156. package/dist/plugin/build-config.js.map +1 -0
  157. package/dist/plugin/build-config.spec.d.ts +2 -0
  158. package/dist/plugin/build-config.spec.d.ts.map +1 -0
  159. package/dist/plugin/build-config.spec.js +72 -0
  160. package/dist/plugin/build-config.spec.js.map +1 -0
  161. package/dist/types/config.d.ts +45 -0
  162. package/dist/types/config.d.ts.map +1 -0
  163. package/dist/types/config.js +9 -0
  164. package/dist/types/config.js.map +1 -0
  165. package/dist/types.d.ts +60 -0
  166. package/dist/types.d.ts.map +1 -0
  167. package/dist/types.js +3 -0
  168. package/dist/types.js.map +1 -0
  169. package/dist/utils/asset-collection.d.ts +30 -0
  170. package/dist/utils/asset-collection.d.ts.map +1 -0
  171. package/dist/utils/asset-collection.js +117 -0
  172. package/dist/utils/asset-collection.js.map +1 -0
  173. package/dist/utils/asset-collection.spec.d.ts +2 -0
  174. package/dist/utils/asset-collection.spec.d.ts.map +1 -0
  175. package/dist/utils/asset-collection.spec.js +155 -0
  176. package/dist/utils/asset-collection.spec.js.map +1 -0
  177. package/dist/utils/build-detection.d.ts +32 -0
  178. package/dist/utils/build-detection.d.ts.map +1 -0
  179. package/dist/utils/build-detection.js +55 -0
  180. package/dist/utils/build-detection.js.map +1 -0
  181. package/dist/utils/build-detection.spec.d.ts +2 -0
  182. package/dist/utils/build-detection.spec.d.ts.map +1 -0
  183. package/dist/utils/build-detection.spec.js +110 -0
  184. package/dist/utils/build-detection.spec.js.map +1 -0
  185. package/dist/utils/config.d.ts +33 -0
  186. package/dist/utils/config.d.ts.map +1 -0
  187. package/dist/utils/config.js +78 -0
  188. package/dist/utils/config.js.map +1 -0
  189. package/dist/utils/config.spec.d.ts +2 -0
  190. package/dist/utils/config.spec.d.ts.map +1 -0
  191. package/dist/utils/config.spec.js +167 -0
  192. package/dist/utils/config.spec.js.map +1 -0
  193. package/dist/utils/download-template.d.ts +15 -0
  194. package/dist/utils/download-template.d.ts.map +1 -0
  195. package/dist/utils/download-template.js +74 -0
  196. package/dist/utils/download-template.js.map +1 -0
  197. package/dist/utils/download-template.spec.d.ts +2 -0
  198. package/dist/utils/download-template.spec.d.ts.map +1 -0
  199. package/dist/utils/download-template.spec.js +76 -0
  200. package/dist/utils/download-template.spec.js.map +1 -0
  201. package/dist/utils/env-management.d.ts +16 -0
  202. package/dist/utils/env-management.d.ts.map +1 -0
  203. package/dist/utils/env-management.js +98 -0
  204. package/dist/utils/env-management.js.map +1 -0
  205. package/dist/utils/env-management.spec.d.ts +2 -0
  206. package/dist/utils/env-management.spec.d.ts.map +1 -0
  207. package/dist/utils/env-management.spec.js +123 -0
  208. package/dist/utils/env-management.spec.js.map +1 -0
  209. package/dist/utils/format.d.ts +23 -0
  210. package/dist/utils/format.d.ts.map +1 -0
  211. package/dist/utils/format.js +49 -0
  212. package/dist/utils/format.js.map +1 -0
  213. package/dist/utils/formatter.d.ts +11 -0
  214. package/dist/utils/formatter.d.ts.map +1 -0
  215. package/dist/utils/formatter.js +47 -0
  216. package/dist/utils/formatter.js.map +1 -0
  217. package/dist/utils/git.d.ts +15 -0
  218. package/dist/utils/git.d.ts.map +1 -0
  219. package/dist/utils/git.js +92 -0
  220. package/dist/utils/git.js.map +1 -0
  221. package/dist/utils/git.spec.d.ts +2 -0
  222. package/dist/utils/git.spec.d.ts.map +1 -0
  223. package/dist/utils/git.spec.js +99 -0
  224. package/dist/utils/git.spec.js.map +1 -0
  225. package/dist/utils/is-debug.d.ts +6 -0
  226. package/dist/utils/is-debug.d.ts.map +1 -0
  227. package/dist/utils/is-debug.js +8 -0
  228. package/dist/utils/is-debug.js.map +1 -0
  229. package/dist/utils/lambda-config.d.ts +7 -0
  230. package/dist/utils/lambda-config.d.ts.map +1 -0
  231. package/dist/utils/lambda-config.js +144 -0
  232. package/dist/utils/lambda-config.js.map +1 -0
  233. package/dist/utils/lambda-config.spec.d.ts +2 -0
  234. package/dist/utils/lambda-config.spec.d.ts.map +1 -0
  235. package/dist/utils/lambda-config.spec.js +141 -0
  236. package/dist/utils/lambda-config.spec.js.map +1 -0
  237. package/dist/utils/log.d.ts +6 -0
  238. package/dist/utils/log.d.ts.map +1 -0
  239. package/dist/utils/log.js +22 -0
  240. package/dist/utils/log.js.map +1 -0
  241. package/dist/utils/messages.d.ts +10 -0
  242. package/dist/utils/messages.d.ts.map +1 -0
  243. package/dist/utils/messages.js +96 -0
  244. package/dist/utils/messages.js.map +1 -0
  245. package/dist/utils/package-manager.d.ts +12 -0
  246. package/dist/utils/package-manager.d.ts.map +1 -0
  247. package/dist/utils/package-manager.js +37 -0
  248. package/dist/utils/package-manager.js.map +1 -0
  249. package/dist/utils/payload-config-ast.d.ts +30 -0
  250. package/dist/utils/payload-config-ast.d.ts.map +1 -0
  251. package/dist/utils/payload-config-ast.js +259 -0
  252. package/dist/utils/payload-config-ast.js.map +1 -0
  253. package/dist/utils/payload-config-ast.spec.d.ts +2 -0
  254. package/dist/utils/payload-config-ast.spec.d.ts.map +1 -0
  255. package/dist/utils/payload-config-ast.spec.js +303 -0
  256. package/dist/utils/payload-config-ast.spec.js.map +1 -0
  257. package/dist/utils/payload-config-finder.d.ts +9 -0
  258. package/dist/utils/payload-config-finder.d.ts.map +1 -0
  259. package/dist/utils/payload-config-finder.js +26 -0
  260. package/dist/utils/payload-config-finder.js.map +1 -0
  261. package/dist/utils/payload-config-modifier.d.ts +18 -0
  262. package/dist/utils/payload-config-modifier.d.ts.map +1 -0
  263. package/dist/utils/payload-config-modifier.int.spec.d.ts +2 -0
  264. package/dist/utils/payload-config-modifier.int.spec.d.ts.map +1 -0
  265. package/dist/utils/payload-config-modifier.int.spec.js +116 -0
  266. package/dist/utils/payload-config-modifier.int.spec.js.map +1 -0
  267. package/dist/utils/payload-config-modifier.js +232 -0
  268. package/dist/utils/payload-config-modifier.js.map +1 -0
  269. package/dist/utils/payload-config-modifier.spec.d.ts +2 -0
  270. package/dist/utils/payload-config-modifier.spec.d.ts.map +1 -0
  271. package/dist/utils/payload-config-modifier.spec.js +162 -0
  272. package/dist/utils/payload-config-modifier.spec.js.map +1 -0
  273. package/dist/utils/payload-package-check.d.ts +15 -0
  274. package/dist/utils/payload-package-check.d.ts.map +1 -0
  275. package/dist/utils/payload-package-check.js +116 -0
  276. package/dist/utils/payload-package-check.js.map +1 -0
  277. package/dist/utils/payload-package-check.spec.d.ts +2 -0
  278. package/dist/utils/payload-package-check.spec.d.ts.map +1 -0
  279. package/dist/utils/payload-package-check.spec.js +148 -0
  280. package/dist/utils/payload-package-check.spec.js.map +1 -0
  281. package/dist/utils/project.d.ts +58 -0
  282. package/dist/utils/project.d.ts.map +1 -0
  283. package/dist/utils/project.js +252 -0
  284. package/dist/utils/project.js.map +1 -0
  285. package/dist/utils/project.spec.d.ts +2 -0
  286. package/dist/utils/project.spec.d.ts.map +1 -0
  287. package/dist/utils/project.spec.js +222 -0
  288. package/dist/utils/project.spec.js.map +1 -0
  289. package/dist/utils/s3-upload.d.ts +43 -0
  290. package/dist/utils/s3-upload.d.ts.map +1 -0
  291. package/dist/utils/s3-upload.js +128 -0
  292. package/dist/utils/s3-upload.js.map +1 -0
  293. package/dist/utils/s3-upload.spec.d.ts +2 -0
  294. package/dist/utils/s3-upload.spec.d.ts.map +1 -0
  295. package/dist/utils/s3-upload.spec.js +140 -0
  296. package/dist/utils/s3-upload.spec.js.map +1 -0
  297. package/dist/utils/token-display.d.ts +14 -0
  298. package/dist/utils/token-display.d.ts.map +1 -0
  299. package/dist/utils/token-display.js +62 -0
  300. package/dist/utils/token-display.js.map +1 -0
  301. package/dist/utils/typescript-validator.d.ts +11 -0
  302. package/dist/utils/typescript-validator.d.ts.map +1 -0
  303. package/dist/utils/typescript-validator.js +58 -0
  304. package/dist/utils/typescript-validator.js.map +1 -0
  305. package/package.json +69 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/oauth-flow.ts"],"sourcesContent":["import * as p from '@clack/prompts'\nimport crypto from 'crypto'\n\nimport type { TokenStore } from './token-store.js'\nimport type { FigmaTokens } from './types.js'\n\nimport { OAUTH_CONFIG } from '../config/oauth.js'\nimport { isDebug } from '../utils/is-debug.js'\nimport * as log from '../utils/log.js'\nimport { buildAuthorizationUrl, openBrowser } from './browser.js'\nimport { CallbackServer } from './callback-server.js'\nimport { generatePKCEPair } from './pkce.js'\nimport { exchangeCodeForTokens, refreshAccessToken } from './refresh.js'\n\n/**\n * Error during OAuth flow\n */\nexport class OAuthFlowError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message)\n this.name = 'OAuthFlowError'\n }\n}\n\n/**\n * Options for OAuth flow\n */\nexport interface OAuthFlowOptions {\n /** OAuth2 client ID (defaults to config) */\n clientId?: string\n /** Callback server port (defaults to 3000) */\n port?: number\n /** Redirect URI (defaults to config) */\n redirectUri?: string\n /** OAuth scopes (defaults to config) */\n scopes?: string[]\n /** Callback timeout in milliseconds (defaults to 120000) */\n timeoutMs?: number\n}\n\n/**\n * Result of OAuth flow\n */\nexport interface OAuthFlowResult {\n /** OAuth2 tokens */\n tokens: FigmaTokens\n /** Figma user ID */\n userId?: string\n}\n\n/**\n * Execute complete OAuth2 authorization flow\n *\n * This orchestrates the full OAuth flow:\n * 1. Generate PKCE pair for security\n * 2. Start local callback server\n * 3. Open browser to authorization URL\n * 4. Wait for user to authorize\n * 5. Exchange authorization code for tokens\n * 6. Store tokens securely\n *\n * @param tokenStore - Token store for persisting tokens\n * @param options - Flow configuration options\n * @returns Promise resolving to OAuth flow result\n * @throws OAuthFlowError if any step fails\n */\nexport async function executeOAuthFlow(\n tokenStore: TokenStore,\n options: OAuthFlowOptions = {},\n): Promise<OAuthFlowResult> {\n // Check if OAuth flow should be mocked\n // Default to true for OAuth (allow testing without real authentication by default)\n const shouldMock = process.env.FIGMA_MOCK_OAUTH !== 'false'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Return mock tokens for testing\n if (isDebug()) {\n log.debug('Using mock OAuth flow (FIGMA_MOCK_OAUTH != \"false\")')\n }\n const mockTokens: FigmaTokens = {\n accessToken: 'figt_mock_access_token_' + crypto.randomBytes(16).toString('hex'),\n expiresAt: Date.now() + 2 * 60 * 60 * 1000, // 2 hours\n refreshToken: 'figt_mock_refresh_token_' + crypto.randomBytes(16).toString('hex'),\n scopes: (options.scopes || OAUTH_CONFIG.scopes) as FigmaTokens['scopes'],\n tokenType: 'Bearer',\n }\n\n // Store mock tokens\n tokenStore.setTokens(mockTokens)\n\n return {\n tokens: mockTokens,\n userId: 'mock_user_123',\n }\n }\n\n // REAL OAUTH IMPLEMENTATION\n const {\n clientId = OAUTH_CONFIG.clientId,\n port = 3000,\n redirectUri = OAUTH_CONFIG.redirectUri,\n scopes = OAUTH_CONFIG.scopes,\n timeoutMs = 120000,\n } = options\n\n // Generate PKCE pair for enhanced security\n const { codeChallenge, codeChallengeMethod, codeVerifier } = generatePKCEPair()\n\n // Generate random state for CSRF protection\n const state = crypto.randomBytes(32).toString('base64url')\n\n // Create callback server\n const callbackServer = new CallbackServer({ port, timeoutMs })\n\n try {\n // Start waiting for callback (this starts the server and captures actual port)\n const callbackPromise = callbackServer.waitForCallback(state)\n\n // Wait a bit for server to start and determine actual port\n await new Promise((resolve) => setTimeout(resolve, 100))\n\n // Get the actual port (may differ from preferred if that was in use)\n const actualPort = callbackServer.getActualPort()\n\n // Build redirect URI with actual port\n const actualRedirectUri = redirectUri.replace(/:\\d+/, `:${actualPort}`)\n\n // Build authorization URL\n const authUrl = buildAuthorizationUrl({\n authorizationUrl: OAUTH_CONFIG.authorizationUrl,\n clientId,\n codeChallenge,\n codeChallengeMethod,\n redirectUri: actualRedirectUri,\n scopes,\n state,\n })\n\n // Prompt user to open browser\n const shouldOpenBrowser = await p.confirm({\n initialValue: true,\n message: 'Log in or create Figma account in your browser?',\n })\n\n if (p.isCancel(shouldOpenBrowser)) {\n throw new OAuthFlowError('Authentication cancelled by user')\n }\n\n if (shouldOpenBrowser) {\n // Try to open browser, but don't fail if it doesn't work\n try {\n await openBrowser(authUrl)\n } catch (_error) {\n // Browser opening failed, show URL for manual opening\n log.warning('Could not open browser automatically.')\n log.info(`Please open this URL in your browser:`)\n log.info(` ${authUrl}`)\n }\n } else {\n // User declined automatic opening, show URL\n log.info(`Please open this URL manually in your browser:`)\n log.info(` ${authUrl}`)\n }\n\n // Wait for callback\n const { code } = await callbackPromise\n\n // Exchange authorization code for tokens\n let tokens: FigmaTokens\n try {\n tokens = await exchangeCodeForTokens(code, codeVerifier, actualRedirectUri, clientId)\n } catch (error) {\n throw new OAuthFlowError(\n 'Failed to exchange authorization code for tokens',\n error instanceof Error ? error : undefined,\n )\n }\n\n // Store tokens\n try {\n tokenStore.setTokens(tokens)\n } catch (error) {\n throw new OAuthFlowError('Failed to store tokens', error instanceof Error ? error : undefined)\n }\n\n return {\n tokens,\n userId: tokens.userId,\n }\n } catch (error) {\n // Clean up server on error\n callbackServer.stop()\n\n if (error instanceof OAuthFlowError) {\n throw error\n }\n\n throw new OAuthFlowError(\n `OAuth flow failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined,\n )\n } finally {\n // Ensure server is always stopped\n callbackServer.stop()\n }\n}\n\n/**\n * Get valid access token, refreshing if necessary\n *\n * Checks if stored tokens exist and are valid. If expired, automatically\n * refreshes using the refresh token. If no tokens exist, returns null.\n *\n * @param tokenStore - Token store to use\n * @returns Promise resolving to access token or null if no valid tokens\n * @throws OAuthFlowError if refresh fails\n */\nexport async function getValidAccessToken(tokenStore: TokenStore): Promise<null | string> {\n // Check if we have valid tokens\n if (tokenStore.hasValidTokens()) {\n return tokenStore.getAccessToken()\n }\n\n // Check if we have a refresh token\n const refreshToken = tokenStore.getRefreshToken()\n if (!refreshToken) {\n return null\n }\n\n // Try to refresh\n try {\n const tokens = await refreshAccessToken(refreshToken)\n tokenStore.setTokens(tokens)\n return tokens.accessToken\n } catch (error) {\n // Refresh failed - clear invalid tokens\n tokenStore.clearTokens()\n throw new OAuthFlowError(\n 'Failed to refresh access token',\n error instanceof Error ? error : undefined,\n )\n }\n}\n"],"names":["p","crypto","OAUTH_CONFIG","isDebug","log","buildAuthorizationUrl","openBrowser","CallbackServer","generatePKCEPair","exchangeCodeForTokens","refreshAccessToken","OAuthFlowError","Error","message","cause","name","executeOAuthFlow","tokenStore","options","shouldMock","process","env","FIGMA_MOCK_OAUTH","debug","mockTokens","accessToken","randomBytes","toString","expiresAt","Date","now","refreshToken","scopes","tokenType","setTokens","tokens","userId","clientId","port","redirectUri","timeoutMs","codeChallenge","codeChallengeMethod","codeVerifier","state","callbackServer","callbackPromise","waitForCallback","Promise","resolve","setTimeout","actualPort","getActualPort","actualRedirectUri","replace","authUrl","authorizationUrl","shouldOpenBrowser","confirm","initialValue","isCancel","_error","warning","info","code","error","undefined","stop","getValidAccessToken","hasValidTokens","getAccessToken","getRefreshToken","clearTokens"],"mappings":"AAAA,YAAYA,OAAO,iBAAgB;AACnC,OAAOC,YAAY,SAAQ;AAK3B,SAASC,YAAY,QAAQ,qBAAoB;AACjD,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,YAAYC,SAAS,kBAAiB;AACtC,SAASC,qBAAqB,EAAEC,WAAW,QAAQ,eAAc;AACjE,SAASC,cAAc,QAAQ,uBAAsB;AACrD,SAASC,gBAAgB,QAAQ,YAAW;AAC5C,SAASC,qBAAqB,EAAEC,kBAAkB,QAAQ,eAAc;AAExE;;CAEC,GACD,OAAO,MAAMC,uBAAuBC;;IAClC,YACEC,OAAe,EACf,AAAOC,KAAa,CACpB;QACA,KAAK,CAACD,eAFCC,QAAAA;QAGP,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AA4BA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,eAAeC,iBACpBC,UAAsB,EACtBC,UAA4B,CAAC,CAAC;IAE9B,uCAAuC;IACvC,mFAAmF;IACnF,MAAMC,aAAaC,QAAQC,GAAG,CAACC,gBAAgB,KAAK;IAEpD,IAAIH,YAAY;QACd,sDAAsD;QACtD,IAAIhB,WAAW;YACbC,IAAImB,KAAK,CAAC;QACZ;QACA,MAAMC,aAA0B;YAC9BC,aAAa,4BAA4BxB,OAAOyB,WAAW,CAAC,IAAIC,QAAQ,CAAC;YACzEC,WAAWC,KAAKC,GAAG,KAAK,IAAI,KAAK,KAAK;YACtCC,cAAc,6BAA6B9B,OAAOyB,WAAW,CAAC,IAAIC,QAAQ,CAAC;YAC3EK,QAASd,QAAQc,MAAM,IAAI9B,aAAa8B,MAAM;YAC9CC,WAAW;QACb;QAEA,oBAAoB;QACpBhB,WAAWiB,SAAS,CAACV;QAErB,OAAO;YACLW,QAAQX;YACRY,QAAQ;QACV;IACF;IAEA,4BAA4B;IAC5B,MAAM,EACJC,WAAWnC,aAAamC,QAAQ,EAChCC,OAAO,IAAI,EACXC,cAAcrC,aAAaqC,WAAW,EACtCP,SAAS9B,aAAa8B,MAAM,EAC5BQ,YAAY,MAAM,EACnB,GAAGtB;IAEJ,2CAA2C;IAC3C,MAAM,EAAEuB,aAAa,EAAEC,mBAAmB,EAAEC,YAAY,EAAE,GAAGnC;IAE7D,4CAA4C;IAC5C,MAAMoC,QAAQ3C,OAAOyB,WAAW,CAAC,IAAIC,QAAQ,CAAC;IAE9C,yBAAyB;IACzB,MAAMkB,iBAAiB,IAAItC,eAAe;QAAE+B;QAAME;IAAU;IAE5D,IAAI;QACF,+EAA+E;QAC/E,MAAMM,kBAAkBD,eAAeE,eAAe,CAACH;QAEvD,2DAA2D;QAC3D,MAAM,IAAII,QAAQ,CAACC,UAAYC,WAAWD,SAAS;QAEnD,qEAAqE;QACrE,MAAME,aAAaN,eAAeO,aAAa;QAE/C,sCAAsC;QACtC,MAAMC,oBAAoBd,YAAYe,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAEH,YAAY;QAEtE,0BAA0B;QAC1B,MAAMI,UAAUlD,sBAAsB;YACpCmD,kBAAkBtD,aAAasD,gBAAgB;YAC/CnB;YACAI;YACAC;YACAH,aAAac;YACbrB;YACAY;QACF;QAEA,8BAA8B;QAC9B,MAAMa,oBAAoB,MAAMzD,EAAE0D,OAAO,CAAC;YACxCC,cAAc;YACd9C,SAAS;QACX;QAEA,IAAIb,EAAE4D,QAAQ,CAACH,oBAAoB;YACjC,MAAM,IAAI9C,eAAe;QAC3B;QAEA,IAAI8C,mBAAmB;YACrB,yDAAyD;YACzD,IAAI;gBACF,MAAMnD,YAAYiD;YACpB,EAAE,OAAOM,QAAQ;gBACf,sDAAsD;gBACtDzD,IAAI0D,OAAO,CAAC;gBACZ1D,IAAI2D,IAAI,CAAC,CAAC,qCAAqC,CAAC;gBAChD3D,IAAI2D,IAAI,CAAC,CAAC,EAAE,EAAER,SAAS;YACzB;QACF,OAAO;YACL,4CAA4C;YAC5CnD,IAAI2D,IAAI,CAAC,CAAC,8CAA8C,CAAC;YACzD3D,IAAI2D,IAAI,CAAC,CAAC,EAAE,EAAER,SAAS;QACzB;QAEA,oBAAoB;QACpB,MAAM,EAAES,IAAI,EAAE,GAAG,MAAMlB;QAEvB,yCAAyC;QACzC,IAAIX;QACJ,IAAI;YACFA,SAAS,MAAM1B,sBAAsBuD,MAAMrB,cAAcU,mBAAmBhB;QAC9E,EAAE,OAAO4B,OAAO;YACd,MAAM,IAAItD,eACR,oDACAsD,iBAAiBrD,QAAQqD,QAAQC;QAErC;QAEA,eAAe;QACf,IAAI;YACFjD,WAAWiB,SAAS,CAACC;QACvB,EAAE,OAAO8B,OAAO;YACd,MAAM,IAAItD,eAAe,0BAA0BsD,iBAAiBrD,QAAQqD,QAAQC;QACtF;QAEA,OAAO;YACL/B;YACAC,QAAQD,OAAOC,MAAM;QACvB;IACF,EAAE,OAAO6B,OAAO;QACd,2BAA2B;QAC3BpB,eAAesB,IAAI;QAEnB,IAAIF,iBAAiBtD,gBAAgB;YACnC,MAAMsD;QACR;QAEA,MAAM,IAAItD,eACR,CAAC,mBAAmB,EAAEsD,iBAAiBrD,QAAQqD,MAAMpD,OAAO,GAAG,iBAAiB,EAChFoD,iBAAiBrD,QAAQqD,QAAQC;IAErC,SAAU;QACR,kCAAkC;QAClCrB,eAAesB,IAAI;IACrB;AACF;AAEA;;;;;;;;;CASC,GACD,OAAO,eAAeC,oBAAoBnD,UAAsB;IAC9D,gCAAgC;IAChC,IAAIA,WAAWoD,cAAc,IAAI;QAC/B,OAAOpD,WAAWqD,cAAc;IAClC;IAEA,mCAAmC;IACnC,MAAMvC,eAAed,WAAWsD,eAAe;IAC/C,IAAI,CAACxC,cAAc;QACjB,OAAO;IACT;IAEA,iBAAiB;IACjB,IAAI;QACF,MAAMI,SAAS,MAAMzB,mBAAmBqB;QACxCd,WAAWiB,SAAS,CAACC;QACrB,OAAOA,OAAOV,WAAW;IAC3B,EAAE,OAAOwC,OAAO;QACd,wCAAwC;QACxChD,WAAWuD,WAAW;QACtB,MAAM,IAAI7D,eACR,kCACAsD,iBAAiBrD,QAAQqD,QAAQC;IAErC;AACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=oauth-flow.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-flow.spec.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-flow.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { CallbackServer } from './callback-server.js';
3
+ import { executeOAuthFlow, getValidAccessToken, OAuthFlowError } from './oauth-flow.js';
4
+ import { refreshAccessToken } from './refresh.js';
5
+ import { TokenStore } from './token-store.js';
6
+ // Mock the refresh module
7
+ vi.mock('./refresh.js', ()=>({
8
+ exchangeCodeForTokens: vi.fn(),
9
+ refreshAccessToken: vi.fn()
10
+ }));
11
+ // Mock browser and callback server
12
+ vi.mock('./browser.js', ()=>({
13
+ buildAuthorizationUrl: vi.fn(()=>'https://www.figma.com/oauth?...'),
14
+ openBrowser: vi.fn()
15
+ }));
16
+ // Mock @clack/prompts
17
+ vi.mock('@clack/prompts', ()=>({
18
+ confirm: vi.fn().mockResolvedValue(true),
19
+ isCancel: vi.fn().mockReturnValue(false)
20
+ }));
21
+ // Create mock factory for CallbackServer
22
+ const createMockCallbackServer = ()=>{
23
+ const mock = {
24
+ getActualPort: vi.fn().mockReturnValue(3000),
25
+ stop: vi.fn(),
26
+ waitForCallback: vi.fn()
27
+ };
28
+ return mock;
29
+ };
30
+ vi.mock('./callback-server.js', ()=>({
31
+ CallbackServer: vi.fn()
32
+ }));
33
+ describe('oauth-flow', ()=>{
34
+ describe('getValidAccessToken', ()=>{
35
+ let tokenStore;
36
+ beforeEach(()=>{
37
+ tokenStore = new TokenStore({
38
+ configName: 'test-payloadcms-figma-oauth-flow'
39
+ });
40
+ tokenStore.clearTokens();
41
+ });
42
+ it('should return access token if valid tokens exist', async ()=>{
43
+ const mockTokens = {
44
+ accessToken: 'valid-token',
45
+ expiresAt: Date.now() + 3600000,
46
+ refreshToken: 'refresh-token'
47
+ };
48
+ tokenStore.setTokens(mockTokens);
49
+ const token = await getValidAccessToken(tokenStore);
50
+ expect(token).toBe('valid-token');
51
+ });
52
+ it('should refresh expired tokens', async ()=>{
53
+ const expiredTokens = {
54
+ accessToken: 'expired-token',
55
+ expiresAt: Date.now() - 1000,
56
+ refreshToken: 'refresh-token'
57
+ };
58
+ tokenStore.setTokens(expiredTokens);
59
+ const newTokens = {
60
+ accessToken: 'new-token',
61
+ expiresAt: Date.now() + 3600000,
62
+ refreshToken: 'new-refresh-token'
63
+ };
64
+ // Mock the refresh function
65
+ vi.mocked(refreshAccessToken).mockResolvedValueOnce(newTokens);
66
+ const token = await getValidAccessToken(tokenStore);
67
+ expect(token).toBe('new-token');
68
+ // Verify tokens were stored
69
+ const storedTokens = tokenStore.getTokens();
70
+ expect(storedTokens?.accessToken).toBe('new-token');
71
+ });
72
+ it('should clear tokens and throw on refresh failure', async ()=>{
73
+ const expiredTokens = {
74
+ accessToken: 'expired-token',
75
+ expiresAt: Date.now() - 1000,
76
+ refreshToken: 'refresh-token'
77
+ };
78
+ tokenStore.setTokens(expiredTokens);
79
+ // Mock refresh to fail
80
+ vi.mocked(refreshAccessToken).mockRejectedValueOnce(new Error('Refresh failed'));
81
+ await expect(getValidAccessToken(tokenStore)).rejects.toThrow(OAuthFlowError);
82
+ // Verify tokens were cleared
83
+ expect(tokenStore.getTokens()).toBeNull();
84
+ });
85
+ });
86
+ describe('executeOAuthFlow', ()=>{
87
+ let tokenStore;
88
+ let mockServer;
89
+ beforeEach(()=>{
90
+ tokenStore = new TokenStore({
91
+ configName: 'test-payloadcms-figma-oauth-execute'
92
+ });
93
+ tokenStore.clearTokens();
94
+ vi.clearAllMocks();
95
+ // Setup mock server with default pending callback
96
+ mockServer = createMockCallbackServer();
97
+ mockServer.waitForCallback.mockReturnValue(new Promise(()=>{
98
+ /* pending forever by default */ }));
99
+ vi.mocked(CallbackServer).mockImplementation(()=>mockServer);
100
+ });
101
+ it('should execute mock OAuth flow successfully (default behavior)', async ()=>{
102
+ const result = await executeOAuthFlow(tokenStore);
103
+ // Verify mock tokens were generated and stored
104
+ const storedTokens = tokenStore.getTokens();
105
+ expect(storedTokens).toBeDefined();
106
+ expect(storedTokens?.accessToken).toMatch(/^figt_mock_access_token_/);
107
+ expect(storedTokens?.refreshToken).toMatch(/^figt_mock_refresh_token_/);
108
+ expect(storedTokens?.tokenType).toBe('Bearer');
109
+ expect(storedTokens?.expiresAt).toBeGreaterThan(Date.now());
110
+ // Verify result
111
+ expect(result.tokens).toEqual(storedTokens);
112
+ expect(result.userId).toBe('mock_user_123');
113
+ // Verify callback server was NOT started (mock flow skips it)
114
+ expect(mockServer.waitForCallback).not.toHaveBeenCalled();
115
+ });
116
+ it('should handle callback timeout (real OAuth flow)', async ()=>{
117
+ // Temporarily disable mocking to test real OAuth flow error handling
118
+ const originalEnv = process.env.FIGMA_MOCK_OAUTH;
119
+ process.env.FIGMA_MOCK_OAUTH = 'false';
120
+ mockServer.waitForCallback.mockRejectedValueOnce(new Error('Callback timed out'));
121
+ await expect(executeOAuthFlow(tokenStore)).rejects.toThrow(OAuthFlowError);
122
+ // Server should be stopped
123
+ expect(mockServer.stop).toHaveBeenCalled();
124
+ // Restore original environment
125
+ if (originalEnv === undefined) {
126
+ delete process.env.FIGMA_MOCK_OAUTH;
127
+ } else {
128
+ process.env.FIGMA_MOCK_OAUTH = originalEnv;
129
+ }
130
+ });
131
+ });
132
+ });
133
+
134
+ //# sourceMappingURL=oauth-flow.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/oauth-flow.spec.ts"],"sourcesContent":["import { beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport type { FigmaTokens } from './types.js'\n\nimport { CallbackServer } from './callback-server.js'\nimport { executeOAuthFlow, getValidAccessToken, OAuthFlowError } from './oauth-flow.js'\nimport { refreshAccessToken } from './refresh.js'\nimport { TokenStore } from './token-store.js'\n\n// Mock the refresh module\nvi.mock('./refresh.js', () => ({\n exchangeCodeForTokens: vi.fn(),\n refreshAccessToken: vi.fn(),\n}))\n\n// Mock browser and callback server\nvi.mock('./browser.js', () => ({\n buildAuthorizationUrl: vi.fn(() => 'https://www.figma.com/oauth?...'),\n openBrowser: vi.fn(),\n}))\n\n// Mock @clack/prompts\nvi.mock('@clack/prompts', () => ({\n confirm: vi.fn().mockResolvedValue(true),\n isCancel: vi.fn().mockReturnValue(false),\n}))\n\n// Create mock factory for CallbackServer\nconst createMockCallbackServer = () => {\n const mock = {\n getActualPort: vi.fn().mockReturnValue(3000),\n stop: vi.fn(),\n waitForCallback: vi.fn(),\n }\n return mock\n}\n\nvi.mock('./callback-server.js', () => ({\n CallbackServer: vi.fn(),\n}))\n\ndescribe('oauth-flow', () => {\n describe('getValidAccessToken', () => {\n let tokenStore: TokenStore\n\n beforeEach(() => {\n tokenStore = new TokenStore({ configName: 'test-payloadcms-figma-oauth-flow' })\n tokenStore.clearTokens()\n })\n\n it('should return access token if valid tokens exist', async () => {\n const mockTokens: FigmaTokens = {\n accessToken: 'valid-token',\n expiresAt: Date.now() + 3600000, // 1 hour from now\n refreshToken: 'refresh-token',\n }\n\n tokenStore.setTokens(mockTokens)\n\n const token = await getValidAccessToken(tokenStore)\n expect(token).toBe('valid-token')\n })\n\n it('should refresh expired tokens', async () => {\n const expiredTokens: FigmaTokens = {\n accessToken: 'expired-token',\n expiresAt: Date.now() - 1000, // Expired\n refreshToken: 'refresh-token',\n }\n\n tokenStore.setTokens(expiredTokens)\n\n const newTokens: FigmaTokens = {\n accessToken: 'new-token',\n expiresAt: Date.now() + 3600000,\n refreshToken: 'new-refresh-token',\n }\n\n // Mock the refresh function\n vi.mocked(refreshAccessToken).mockResolvedValueOnce(newTokens)\n\n const token = await getValidAccessToken(tokenStore)\n expect(token).toBe('new-token')\n\n // Verify tokens were stored\n const storedTokens = tokenStore.getTokens()\n expect(storedTokens?.accessToken).toBe('new-token')\n })\n\n it('should clear tokens and throw on refresh failure', async () => {\n const expiredTokens: FigmaTokens = {\n accessToken: 'expired-token',\n expiresAt: Date.now() - 1000,\n refreshToken: 'refresh-token',\n }\n\n tokenStore.setTokens(expiredTokens)\n\n // Mock refresh to fail\n vi.mocked(refreshAccessToken).mockRejectedValueOnce(new Error('Refresh failed'))\n\n await expect(getValidAccessToken(tokenStore)).rejects.toThrow(OAuthFlowError)\n\n // Verify tokens were cleared\n expect(tokenStore.getTokens()).toBeNull()\n })\n })\n\n describe('executeOAuthFlow', () => {\n let tokenStore: TokenStore\n let mockServer: ReturnType<typeof createMockCallbackServer>\n\n beforeEach(() => {\n tokenStore = new TokenStore({ configName: 'test-payloadcms-figma-oauth-execute' })\n tokenStore.clearTokens()\n vi.clearAllMocks()\n\n // Setup mock server with default pending callback\n mockServer = createMockCallbackServer()\n mockServer.waitForCallback.mockReturnValue(\n new Promise(() => {\n /* pending forever by default */\n }),\n )\n vi.mocked(CallbackServer).mockImplementation(() => mockServer as unknown as CallbackServer)\n })\n\n it('should execute mock OAuth flow successfully (default behavior)', async () => {\n const result = await executeOAuthFlow(tokenStore)\n\n // Verify mock tokens were generated and stored\n const storedTokens = tokenStore.getTokens()\n expect(storedTokens).toBeDefined()\n expect(storedTokens?.accessToken).toMatch(/^figt_mock_access_token_/)\n expect(storedTokens?.refreshToken).toMatch(/^figt_mock_refresh_token_/)\n expect(storedTokens?.tokenType).toBe('Bearer')\n expect(storedTokens?.expiresAt).toBeGreaterThan(Date.now())\n\n // Verify result\n expect(result.tokens).toEqual(storedTokens)\n expect(result.userId).toBe('mock_user_123')\n\n // Verify callback server was NOT started (mock flow skips it)\n expect(mockServer.waitForCallback).not.toHaveBeenCalled()\n })\n\n it('should handle callback timeout (real OAuth flow)', async () => {\n // Temporarily disable mocking to test real OAuth flow error handling\n const originalEnv = process.env.FIGMA_MOCK_OAUTH\n process.env.FIGMA_MOCK_OAUTH = 'false'\n\n mockServer.waitForCallback.mockRejectedValueOnce(new Error('Callback timed out'))\n\n await expect(executeOAuthFlow(tokenStore)).rejects.toThrow(OAuthFlowError)\n\n // Server should be stopped\n expect(mockServer.stop).toHaveBeenCalled()\n\n // Restore original environment\n if (originalEnv === undefined) {\n delete process.env.FIGMA_MOCK_OAUTH\n } else {\n process.env.FIGMA_MOCK_OAUTH = originalEnv\n }\n })\n })\n})\n"],"names":["beforeEach","describe","expect","it","vi","CallbackServer","executeOAuthFlow","getValidAccessToken","OAuthFlowError","refreshAccessToken","TokenStore","mock","exchangeCodeForTokens","fn","buildAuthorizationUrl","openBrowser","confirm","mockResolvedValue","isCancel","mockReturnValue","createMockCallbackServer","getActualPort","stop","waitForCallback","tokenStore","configName","clearTokens","mockTokens","accessToken","expiresAt","Date","now","refreshToken","setTokens","token","toBe","expiredTokens","newTokens","mocked","mockResolvedValueOnce","storedTokens","getTokens","mockRejectedValueOnce","Error","rejects","toThrow","toBeNull","mockServer","clearAllMocks","Promise","mockImplementation","result","toBeDefined","toMatch","tokenType","toBeGreaterThan","tokens","toEqual","userId","not","toHaveBeenCalled","originalEnv","process","env","FIGMA_MOCK_OAUTH","undefined"],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Figma Authentication Failed</title>
6
+ <style>
7
+ body {
8
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
9
+ max-width: 600px;
10
+ margin: 40px auto;
11
+ padding: 40px 20px;
12
+ background: #ffffff;
13
+ color: #000000;
14
+ }
15
+ .container {
16
+ text-align: center;
17
+ }
18
+ .logo {
19
+ margin-bottom: 32px;
20
+ }
21
+ .logo svg {
22
+ width: 80px;
23
+ height: 100px;
24
+ display: block;
25
+ margin: 0 auto;
26
+ }
27
+ .error-icon {
28
+ width: 64px;
29
+ height: 64px;
30
+ margin: 0 auto 24px;
31
+ background: #FF3737;
32
+ border-radius: 50%;
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ font-size: 32px;
37
+ color: #ffffff;
38
+ }
39
+ h1 {
40
+ font-size: 24px;
41
+ font-weight: 600;
42
+ margin: 0 0 16px 0;
43
+ color: #000000;
44
+ }
45
+ p {
46
+ font-size: 14px;
47
+ line-height: 1.5;
48
+ color: #697485;
49
+ margin: 0 0 16px 0;
50
+ }
51
+ .error-code {
52
+ font-family: 'Monaco', 'Courier New', monospace;
53
+ background: #000000;
54
+ color: #ffffff;
55
+ padding: 8px 12px;
56
+ border-radius: 4px;
57
+ display: inline-block;
58
+ margin: 8px 0;
59
+ }
60
+ .footer {
61
+ margin-top: 32px;
62
+ font-size: 13px;
63
+ color: #697485;
64
+ }
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <div class="container">
69
+ <div class="logo">
70
+ <svg width="80" height="100" viewBox="0 0 1024 1280" fill="none" xmlns="http://www.w3.org/2000/svg">
71
+ <path d="M312 840C312 784.772 356.772 740 412 740H512V840C512 895.228 467.228 940 412 940C356.772 940 312 895.228 312 840Z" fill="#24CB71"/>
72
+ <path d="M512 340V540H612C667.228 540 712 495.228 712 440C712 384.772 667.228 340 612 340H512Z" fill="#FF7237"/>
73
+ <circle cx="611.167" cy="640" r="100" fill="#00B6FF"/>
74
+ <path d="M312 440C312 495.228 356.772 540 412 540H512V340H412C356.772 340 312 384.772 312 440Z" fill="#FF3737"/>
75
+ <path d="M312 640C312 695.228 356.772 740 412 740H512V540H412C356.772 540 312 584.772 312 640Z" fill="#874FFF"/>
76
+ </svg>
77
+ </div>
78
+ <div class="error-icon">✕</div>
79
+ <h1>Authentication Failed</h1>
80
+ <p>{{description}}</p>
81
+ {{error}}
82
+ <p class="footer">Please close this window and try again from your terminal.</p>
83
+ </div>
84
+ </body>
85
+ </html>
@@ -0,0 +1,74 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Figma Authentication Successful</title>
6
+ <style>
7
+ body {
8
+ font-family:
9
+ 'Inter',
10
+ -apple-system,
11
+ BlinkMacSystemFont,
12
+ 'Segoe UI',
13
+ sans-serif;
14
+ max-width: 600px;
15
+ margin: 40px auto;
16
+ padding: 40px 20px;
17
+ background: #ffffff;
18
+ color: #1e1e1e;
19
+ }
20
+ .container {
21
+ text-align: center;
22
+ }
23
+ .logo {
24
+ margin-bottom: 32px;
25
+ }
26
+ .logo svg {
27
+ width: 80px;
28
+ height: 100px;
29
+ display: block;
30
+ margin: 0 auto;
31
+ }
32
+ .success-icon {
33
+ width: 64px;
34
+ height: 64px;
35
+ margin: 0 auto 24px;
36
+ background: #24CB71;
37
+ border-radius: 50%;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ font-size: 32px;
42
+ color: #ffffff;
43
+ }
44
+ h1 {
45
+ font-size: 24px;
46
+ font-weight: 600;
47
+ margin: 0 0 16px 0;
48
+ color: #000000;
49
+ }
50
+ p {
51
+ font-size: 14px;
52
+ line-height: 1.5;
53
+ color: #697485;
54
+ margin: 0;
55
+ }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="container">
60
+ <div class="logo">
61
+ <svg width="80" height="100" viewBox="0 0 1024 1280" fill="none" xmlns="http://www.w3.org/2000/svg">
62
+ <path d="M312 840C312 784.772 356.772 740 412 740H512V840C512 895.228 467.228 940 412 940C356.772 940 312 895.228 312 840Z" fill="#24CB71"/>
63
+ <path d="M512 340V540H612C667.228 540 712 495.228 712 440C712 384.772 667.228 340 612 340H512Z" fill="#FF7237"/>
64
+ <circle cx="611.167" cy="640" r="100" fill="#00B6FF"/>
65
+ <path d="M312 440C312 495.228 356.772 540 412 540H512V340H412C356.772 340 312 384.772 312 440Z" fill="#FF3737"/>
66
+ <path d="M312 640C312 695.228 356.772 740 412 740H512V540H412C356.772 540 312 584.772 312 640Z" fill="#874FFF"/>
67
+ </svg>
68
+ </div>
69
+ <div class="success-icon">✓</div>
70
+ <h1>Authentication Successful</h1>
71
+ <p>You can now close this window and return to your terminal.</p>
72
+ </div>
73
+ </body>
74
+ </html>
@@ -0,0 +1,51 @@
1
+ /**
2
+ * PKCE code verifier and challenge pair
3
+ * @see https://datatracker.ietf.org/doc/html/rfc7636
4
+ */
5
+ export interface PKCEPair {
6
+ /** SHA256 hash of verifier, base64url encoded (without padding) */
7
+ codeChallenge: string;
8
+ /** Challenge method (always S256 for SHA256) */
9
+ codeChallengeMethod: 'S256';
10
+ /** Random code verifier (43-128 characters from [A-Z, a-z, 0-9, -, ., _, ~]) */
11
+ codeVerifier: string;
12
+ }
13
+ /**
14
+ * Generate a cryptographically random PKCE code verifier
15
+ *
16
+ * Creates a high-entropy random string suitable for use as an OAuth 2.0 PKCE code verifier.
17
+ * The verifier must be 43-128 characters and use only [A-Z, a-z, 0-9, -, ., _, ~].
18
+ *
19
+ * @returns Random code verifier string (43 characters from 32 random bytes)
20
+ * @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
21
+ */
22
+ export declare function generateCodeVerifier(): string;
23
+ /**
24
+ * Generate code challenge from verifier using S256 method
25
+ *
26
+ * Creates a SHA256 hash of the code verifier and encodes it in base64url format.
27
+ * Formula: challenge = BASE64URL(SHA256(ASCII(code_verifier)))
28
+ *
29
+ * @param verifier - The code verifier to hash
30
+ * @returns Code challenge string (43 characters)
31
+ * @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
32
+ */
33
+ export declare function generateCodeChallenge(verifier: string): string;
34
+ /**
35
+ * Generate complete PKCE pair for OAuth flow
36
+ *
37
+ * Creates both a code verifier and its corresponding code challenge.
38
+ * Use this at the start of an OAuth flow:
39
+ * 1. Generate pair
40
+ * 2. Store verifier securely (needed for token exchange)
41
+ * 3. Send challenge in authorization request
42
+ * 4. Send verifier in token exchange request
43
+ *
44
+ * @returns Complete PKCE pair with verifier, challenge, and method
45
+ * @example
46
+ * const { codeVerifier, codeChallenge, codeChallengeMethod } = generatePKCEPair()
47
+ * // Store codeVerifier for later
48
+ * // Include codeChallenge and codeChallengeMethod in authorization URL
49
+ */
50
+ export declare function generatePKCEPair(): PKCEPair;
51
+ //# sourceMappingURL=pkce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/auth/pkce.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAA;IACrB,gDAAgD;IAChD,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gFAAgF;IAChF,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAK7C;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,IAAI,QAAQ,CAS3C"}
@@ -0,0 +1,71 @@
1
+ import crypto from 'crypto';
2
+ /**
3
+ * Generate a cryptographically random PKCE code verifier
4
+ *
5
+ * Creates a high-entropy random string suitable for use as an OAuth 2.0 PKCE code verifier.
6
+ * The verifier must be 43-128 characters and use only [A-Z, a-z, 0-9, -, ., _, ~].
7
+ *
8
+ * @returns Random code verifier string (43 characters from 32 random bytes)
9
+ * @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
10
+ */ export function generateCodeVerifier() {
11
+ // Generate 32 random bytes (256 bits of entropy)
12
+ // This will produce a 43-character base64url string
13
+ const buffer = crypto.randomBytes(32);
14
+ return base64UrlEncode(buffer);
15
+ }
16
+ /**
17
+ * Generate code challenge from verifier using S256 method
18
+ *
19
+ * Creates a SHA256 hash of the code verifier and encodes it in base64url format.
20
+ * Formula: challenge = BASE64URL(SHA256(ASCII(code_verifier)))
21
+ *
22
+ * @param verifier - The code verifier to hash
23
+ * @returns Code challenge string (43 characters)
24
+ * @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
25
+ */ export function generateCodeChallenge(verifier) {
26
+ const hash = crypto.createHash('sha256').update(verifier).digest();
27
+ return base64UrlEncode(hash);
28
+ }
29
+ /**
30
+ * Generate complete PKCE pair for OAuth flow
31
+ *
32
+ * Creates both a code verifier and its corresponding code challenge.
33
+ * Use this at the start of an OAuth flow:
34
+ * 1. Generate pair
35
+ * 2. Store verifier securely (needed for token exchange)
36
+ * 3. Send challenge in authorization request
37
+ * 4. Send verifier in token exchange request
38
+ *
39
+ * @returns Complete PKCE pair with verifier, challenge, and method
40
+ * @example
41
+ * const { codeVerifier, codeChallenge, codeChallengeMethod } = generatePKCEPair()
42
+ * // Store codeVerifier for later
43
+ * // Include codeChallenge and codeChallengeMethod in authorization URL
44
+ */ export function generatePKCEPair() {
45
+ const codeVerifier = generateCodeVerifier();
46
+ const codeChallenge = generateCodeChallenge(codeVerifier);
47
+ return {
48
+ codeChallenge,
49
+ codeChallengeMethod: 'S256',
50
+ codeVerifier
51
+ };
52
+ }
53
+ /**
54
+ * Base64URL encode without padding (RFC 7636 requirement)
55
+ *
56
+ * Converts a buffer to base64url format by:
57
+ * 1. Base64 encoding
58
+ * 2. Replacing + with -
59
+ * 3. Replacing / with _
60
+ * 4. Removing = padding
61
+ *
62
+ * This format is URL-safe and required by OAuth 2.0 PKCE spec.
63
+ *
64
+ * @param buffer - Buffer to encode
65
+ * @returns Base64url encoded string (no padding)
66
+ * @see https://datatracker.ietf.org/doc/html/rfc7636#appendix-A
67
+ */ function base64UrlEncode(buffer) {
68
+ return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
69
+ }
70
+
71
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/pkce.ts"],"sourcesContent":["import crypto from 'crypto'\n\n/**\n * PKCE code verifier and challenge pair\n * @see https://datatracker.ietf.org/doc/html/rfc7636\n */\nexport interface PKCEPair {\n /** SHA256 hash of verifier, base64url encoded (without padding) */\n codeChallenge: string\n /** Challenge method (always S256 for SHA256) */\n codeChallengeMethod: 'S256'\n /** Random code verifier (43-128 characters from [A-Z, a-z, 0-9, -, ., _, ~]) */\n codeVerifier: string\n}\n\n/**\n * Generate a cryptographically random PKCE code verifier\n *\n * Creates a high-entropy random string suitable for use as an OAuth 2.0 PKCE code verifier.\n * The verifier must be 43-128 characters and use only [A-Z, a-z, 0-9, -, ., _, ~].\n *\n * @returns Random code verifier string (43 characters from 32 random bytes)\n * @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.1\n */\nexport function generateCodeVerifier(): string {\n // Generate 32 random bytes (256 bits of entropy)\n // This will produce a 43-character base64url string\n const buffer = crypto.randomBytes(32)\n return base64UrlEncode(buffer)\n}\n\n/**\n * Generate code challenge from verifier using S256 method\n *\n * Creates a SHA256 hash of the code verifier and encodes it in base64url format.\n * Formula: challenge = BASE64URL(SHA256(ASCII(code_verifier)))\n *\n * @param verifier - The code verifier to hash\n * @returns Code challenge string (43 characters)\n * @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.2\n */\nexport function generateCodeChallenge(verifier: string): string {\n const hash = crypto.createHash('sha256').update(verifier).digest()\n return base64UrlEncode(hash)\n}\n\n/**\n * Generate complete PKCE pair for OAuth flow\n *\n * Creates both a code verifier and its corresponding code challenge.\n * Use this at the start of an OAuth flow:\n * 1. Generate pair\n * 2. Store verifier securely (needed for token exchange)\n * 3. Send challenge in authorization request\n * 4. Send verifier in token exchange request\n *\n * @returns Complete PKCE pair with verifier, challenge, and method\n * @example\n * const { codeVerifier, codeChallenge, codeChallengeMethod } = generatePKCEPair()\n * // Store codeVerifier for later\n * // Include codeChallenge and codeChallengeMethod in authorization URL\n */\nexport function generatePKCEPair(): PKCEPair {\n const codeVerifier = generateCodeVerifier()\n const codeChallenge = generateCodeChallenge(codeVerifier)\n\n return {\n codeChallenge,\n codeChallengeMethod: 'S256',\n codeVerifier,\n }\n}\n\n/**\n * Base64URL encode without padding (RFC 7636 requirement)\n *\n * Converts a buffer to base64url format by:\n * 1. Base64 encoding\n * 2. Replacing + with -\n * 3. Replacing / with _\n * 4. Removing = padding\n *\n * This format is URL-safe and required by OAuth 2.0 PKCE spec.\n *\n * @param buffer - Buffer to encode\n * @returns Base64url encoded string (no padding)\n * @see https://datatracker.ietf.org/doc/html/rfc7636#appendix-A\n */\nfunction base64UrlEncode(buffer: Buffer): string {\n return buffer.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '')\n}\n"],"names":["crypto","generateCodeVerifier","buffer","randomBytes","base64UrlEncode","generateCodeChallenge","verifier","hash","createHash","update","digest","generatePKCEPair","codeVerifier","codeChallenge","codeChallengeMethod","toString","replace"],"mappings":"AAAA,OAAOA,YAAY,SAAQ;AAe3B;;;;;;;;CAQC,GACD,OAAO,SAASC;IACd,iDAAiD;IACjD,oDAAoD;IACpD,MAAMC,SAASF,OAAOG,WAAW,CAAC;IAClC,OAAOC,gBAAgBF;AACzB;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASG,sBAAsBC,QAAgB;IACpD,MAAMC,OAAOP,OAAOQ,UAAU,CAAC,UAAUC,MAAM,CAACH,UAAUI,MAAM;IAChE,OAAON,gBAAgBG;AACzB;AAEA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASI;IACd,MAAMC,eAAeX;IACrB,MAAMY,gBAAgBR,sBAAsBO;IAE5C,OAAO;QACLC;QACAC,qBAAqB;QACrBF;IACF;AACF;AAEA;;;;;;;;;;;;;;CAcC,GACD,SAASR,gBAAgBF,MAAc;IACrC,OAAOA,OAAOa,QAAQ,CAAC,UAAUC,OAAO,CAAC,OAAO,KAAKA,OAAO,CAAC,OAAO,KAAKA,OAAO,CAAC,MAAM;AACzF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pkce.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.spec.d.ts","sourceRoot":"","sources":["../../src/auth/pkce.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { generateCodeChallenge, generatePKCEPair } from './pkce.js';
3
+ describe('generateCodeChallenge', ()=>{
4
+ it('should generate consistent challenge for same verifier', ()=>{
5
+ const verifier = 'test-verifier-123456789012345678901234567890';
6
+ const challenge1 = generateCodeChallenge(verifier);
7
+ const challenge2 = generateCodeChallenge(verifier);
8
+ expect(challenge1).toBe(challenge2);
9
+ });
10
+ it('should generate different challenges for different verifiers', ()=>{
11
+ const verifier1 = 'test-verifier-1';
12
+ const verifier2 = 'test-verifier-2';
13
+ const challenge1 = generateCodeChallenge(verifier1);
14
+ const challenge2 = generateCodeChallenge(verifier2);
15
+ expect(challenge1).not.toBe(challenge2);
16
+ });
17
+ });
18
+ describe('generatePKCEPair', ()=>{
19
+ it('should generate a complete PKCE pair', ()=>{
20
+ const pair = generatePKCEPair();
21
+ expect(pair).toHaveProperty('codeVerifier');
22
+ expect(pair).toHaveProperty('codeChallenge');
23
+ expect(pair).toHaveProperty('codeChallengeMethod');
24
+ });
25
+ it('should generate challenge derived from verifier', ()=>{
26
+ const pair = generatePKCEPair();
27
+ const derivedChallenge = generateCodeChallenge(pair.codeVerifier);
28
+ expect(pair.codeChallenge).toBe(derivedChallenge);
29
+ });
30
+ });
31
+
32
+ //# sourceMappingURL=pkce.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/pkce.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport { generateCodeChallenge, generatePKCEPair } from './pkce.js'\n\ndescribe('generateCodeChallenge', () => {\n it('should generate consistent challenge for same verifier', () => {\n const verifier = 'test-verifier-123456789012345678901234567890'\n const challenge1 = generateCodeChallenge(verifier)\n const challenge2 = generateCodeChallenge(verifier)\n expect(challenge1).toBe(challenge2)\n })\n\n it('should generate different challenges for different verifiers', () => {\n const verifier1 = 'test-verifier-1'\n const verifier2 = 'test-verifier-2'\n const challenge1 = generateCodeChallenge(verifier1)\n const challenge2 = generateCodeChallenge(verifier2)\n expect(challenge1).not.toBe(challenge2)\n })\n})\n\ndescribe('generatePKCEPair', () => {\n it('should generate a complete PKCE pair', () => {\n const pair = generatePKCEPair()\n\n expect(pair).toHaveProperty('codeVerifier')\n expect(pair).toHaveProperty('codeChallenge')\n expect(pair).toHaveProperty('codeChallengeMethod')\n })\n\n it('should generate challenge derived from verifier', () => {\n const pair = generatePKCEPair()\n const derivedChallenge = generateCodeChallenge(pair.codeVerifier)\n expect(pair.codeChallenge).toBe(derivedChallenge)\n })\n})\n"],"names":["describe","expect","it","generateCodeChallenge","generatePKCEPair","verifier","challenge1","challenge2","toBe","verifier1","verifier2","not","pair","toHaveProperty","derivedChallenge","codeVerifier","codeChallenge"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,qBAAqB,EAAEC,gBAAgB,QAAQ,YAAW;AAEnEJ,SAAS,yBAAyB;IAChCE,GAAG,0DAA0D;QAC3D,MAAMG,WAAW;QACjB,MAAMC,aAAaH,sBAAsBE;QACzC,MAAME,aAAaJ,sBAAsBE;QACzCJ,OAAOK,YAAYE,IAAI,CAACD;IAC1B;IAEAL,GAAG,gEAAgE;QACjE,MAAMO,YAAY;QAClB,MAAMC,YAAY;QAClB,MAAMJ,aAAaH,sBAAsBM;QACzC,MAAMF,aAAaJ,sBAAsBO;QACzCT,OAAOK,YAAYK,GAAG,CAACH,IAAI,CAACD;IAC9B;AACF;AAEAP,SAAS,oBAAoB;IAC3BE,GAAG,wCAAwC;QACzC,MAAMU,OAAOR;QAEbH,OAAOW,MAAMC,cAAc,CAAC;QAC5BZ,OAAOW,MAAMC,cAAc,CAAC;QAC5BZ,OAAOW,MAAMC,cAAc,CAAC;IAC9B;IAEAX,GAAG,mDAAmD;QACpD,MAAMU,OAAOR;QACb,MAAMU,mBAAmBX,sBAAsBS,KAAKG,YAAY;QAChEd,OAAOW,KAAKI,aAAa,EAAER,IAAI,CAACM;IAClC;AACF"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Project Token Management
3
+ *
4
+ * Handles project token (JWT) generation and refresh for authenticating
5
+ * to the Content API. Project tokens are scoped to a specific tenant/CMS
6
+ * and have a shorter lifespan (15-30 minutes) than OAuth tokens.
7
+ */
8
+ import type { TokenStore } from './token-store.js';
9
+ /**
10
+ * Error during project token operations
11
+ */
12
+ export declare class ProjectTokenError extends Error {
13
+ cause?: Error | undefined;
14
+ constructor(message: string, cause?: Error | undefined);
15
+ }
16
+ /**
17
+ * Get a valid project token for a tenant, refreshing if necessary
18
+ *
19
+ * This function:
20
+ * 1. Checks if a stored project token exists and is valid (not expired)
21
+ * 2. If expired or missing, gets a valid user access token
22
+ * 3. Calls the Figma API to generate a new project token
23
+ * 4. Stores the new project token in encrypted storage
24
+ * 5. Returns the JWT string
25
+ *
26
+ * The project token is used by local Payload instances to authenticate
27
+ * requests to the Content API without requiring a round-trip to Sinatra.
28
+ *
29
+ * @param tokenStore - Token store for persisting tokens
30
+ * @param tenantId - The tenant/CMS ID to get a token for
31
+ * @returns Promise resolving to JWT token string or null if failed
32
+ * @throws {ProjectTokenError} If token generation fails
33
+ */
34
+ export declare function getValidProjectToken(tokenStore: TokenStore, tenantId: string): Promise<null | string>;
35
+ /**
36
+ * Refresh a project token for a tenant
37
+ *
38
+ * Forces a refresh of the project token even if the current one is still valid.
39
+ * Useful for testing or when you want to ensure you have the freshest token.
40
+ *
41
+ * @param tokenStore - Token store for persisting tokens
42
+ * @param tenantId - The tenant/CMS ID to refresh token for
43
+ * @returns Promise resolving to JWT token string or null if failed
44
+ * @throws {ProjectTokenError} If token refresh fails
45
+ */
46
+ export declare function refreshProjectToken(tokenStore: TokenStore, tenantId: string): Promise<null | string>;
47
+ //# sourceMappingURL=project-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-token.d.ts","sourceRoot":"","sources":["../../src/auth/project-token.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAUlD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAGjC,KAAK,CAAC,EAAE,KAAK;gBADpB,OAAO,EAAE,MAAM,EACR,KAAK,CAAC,EAAE,KAAK,YAAA;CAKvB;AAkCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAmExB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAMxB"}