@payloadcms/figma 0.0.1-alpha.0 → 0.0.1-alpha.10

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 (308) hide show
  1. package/dist/api/control-plane.d.ts.map +1 -1
  2. package/dist/api/control-plane.js +2 -7
  3. package/dist/api/control-plane.js.map +1 -1
  4. package/dist/api/figma-api.js +3 -3
  5. package/dist/api/figma-api.js.map +1 -1
  6. package/dist/auth/jwt-validator.js.map +1 -1
  7. package/dist/auth/oauth-flow.d.ts.map +1 -1
  8. package/dist/auth/oauth-flow.js +3 -6
  9. package/dist/auth/oauth-flow.js.map +1 -1
  10. package/dist/auth/project-token.d.ts.map +1 -1
  11. package/dist/auth/project-token.js +1 -4
  12. package/dist/auth/project-token.js.map +1 -1
  13. package/dist/auth/refresh.d.ts.map +1 -1
  14. package/dist/auth/refresh.js +19 -28
  15. package/dist/auth/refresh.js.map +1 -1
  16. package/dist/auth/types.d.ts +1 -1
  17. package/dist/auth/types.d.ts.map +1 -1
  18. package/dist/auth/types.js.map +1 -1
  19. package/dist/cli.js +1 -3
  20. package/dist/cli.js.map +1 -1
  21. package/dist/commands/deploy.d.ts.map +1 -1
  22. package/dist/commands/deploy.js +17 -10
  23. package/dist/commands/deploy.js.map +1 -1
  24. package/dist/commands/init.d.ts +4 -0
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +21 -85
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/list-tokens.d.ts +1 -1
  29. package/dist/commands/list-tokens.d.ts.map +1 -1
  30. package/dist/commands/list-tokens.js +6 -5
  31. package/dist/commands/list-tokens.js.map +1 -1
  32. package/dist/commands/login.d.ts +2 -11
  33. package/dist/commands/login.d.ts.map +1 -1
  34. package/dist/commands/login.js +4 -7
  35. package/dist/commands/login.js.map +1 -1
  36. package/dist/commands/logout.d.ts +1 -1
  37. package/dist/commands/logout.js +1 -1
  38. package/dist/commands/logout.js.map +1 -1
  39. package/dist/exports/client.d.ts +3 -0
  40. package/dist/exports/client.d.ts.map +1 -0
  41. package/dist/exports/client.js +4 -0
  42. package/dist/exports/client.js.map +1 -0
  43. package/dist/oauth/components/LoginButton/index.d.ts +9 -0
  44. package/dist/oauth/components/LoginButton/index.d.ts.map +1 -0
  45. package/dist/oauth/components/LoginButton/index.js +52 -0
  46. package/dist/oauth/components/LoginButton/index.js.map +1 -0
  47. package/dist/oauth/components/LoginButton/index.scss +10 -0
  48. package/dist/oauth/components/LogoutButton/index.d.ts +7 -0
  49. package/dist/oauth/components/LogoutButton/index.d.ts.map +1 -0
  50. package/dist/oauth/components/LogoutButton/index.js +67 -0
  51. package/dist/oauth/components/LogoutButton/index.js.map +1 -0
  52. package/dist/oauth/components/LogoutButton/index.scss +10 -0
  53. package/dist/oauth/defaults.d.ts +10 -0
  54. package/dist/oauth/defaults.d.ts.map +1 -0
  55. package/dist/oauth/defaults.js +91 -0
  56. package/dist/oauth/defaults.js.map +1 -0
  57. package/dist/oauth/endpoints/getLoginEndpoint.d.ts +13 -0
  58. package/dist/oauth/endpoints/getLoginEndpoint.d.ts.map +1 -0
  59. package/dist/oauth/endpoints/getLoginEndpoint.js +237 -0
  60. package/dist/oauth/endpoints/getLoginEndpoint.js.map +1 -0
  61. package/dist/oauth/endpoints/getLogoutEndpoint.d.ts +12 -0
  62. package/dist/oauth/endpoints/getLogoutEndpoint.d.ts.map +1 -0
  63. package/dist/oauth/endpoints/getLogoutEndpoint.js +99 -0
  64. package/dist/oauth/endpoints/getLogoutEndpoint.js.map +1 -0
  65. package/dist/oauth/endpoints/getMetaEndpoint.d.ts +13 -0
  66. package/dist/oauth/endpoints/getMetaEndpoint.d.ts.map +1 -0
  67. package/dist/oauth/endpoints/getMetaEndpoint.js +25 -0
  68. package/dist/oauth/endpoints/getMetaEndpoint.js.map +1 -0
  69. package/dist/oauth/endpoints/getRedirectToLoginEndpoint.d.ts +13 -0
  70. package/dist/oauth/endpoints/getRedirectToLoginEndpoint.d.ts.map +1 -0
  71. package/dist/oauth/endpoints/getRedirectToLoginEndpoint.js +21 -0
  72. package/dist/oauth/endpoints/getRedirectToLoginEndpoint.js.map +1 -0
  73. package/dist/oauth/exports/client.d.ts +3 -0
  74. package/dist/oauth/exports/client.d.ts.map +1 -0
  75. package/dist/oauth/exports/client.js +4 -0
  76. package/dist/oauth/exports/client.js.map +1 -0
  77. package/dist/oauth/hooks/afterLogout.d.ts +8 -0
  78. package/dist/oauth/hooks/afterLogout.d.ts.map +1 -0
  79. package/dist/oauth/hooks/afterLogout.js +27 -0
  80. package/dist/oauth/hooks/afterLogout.js.map +1 -0
  81. package/dist/oauth/hooks/me.d.ts +5 -0
  82. package/dist/oauth/hooks/me.d.ts.map +1 -0
  83. package/dist/oauth/hooks/me.js +55 -0
  84. package/dist/oauth/hooks/me.js.map +1 -0
  85. package/dist/oauth/hooks/refresh.d.ts +8 -0
  86. package/dist/oauth/hooks/refresh.d.ts.map +1 -0
  87. package/dist/oauth/hooks/refresh.js +91 -0
  88. package/dist/oauth/hooks/refresh.js.map +1 -0
  89. package/dist/oauth/index.d.ts +4 -0
  90. package/dist/oauth/index.d.ts.map +1 -0
  91. package/dist/oauth/index.js +222 -0
  92. package/dist/oauth/index.js.map +1 -0
  93. package/dist/oauth/strategy/index.d.ts +54 -0
  94. package/dist/oauth/strategy/index.d.ts.map +1 -0
  95. package/dist/oauth/strategy/index.js +280 -0
  96. package/dist/oauth/strategy/index.js.map +1 -0
  97. package/dist/oauth/types.d.ts +192 -0
  98. package/dist/oauth/types.d.ts.map +1 -0
  99. package/dist/oauth/types.js +3 -0
  100. package/dist/oauth/types.js.map +1 -0
  101. package/dist/oauth/utilities/clearCookie.d.ts +9 -0
  102. package/dist/oauth/utilities/clearCookie.d.ts.map +1 -0
  103. package/dist/oauth/utilities/clearCookie.js +21 -0
  104. package/dist/oauth/utilities/clearCookie.js.map +1 -0
  105. package/dist/oauth/utilities/createCookieOptions.d.ts +10 -0
  106. package/dist/oauth/utilities/createCookieOptions.d.ts.map +1 -0
  107. package/dist/oauth/utilities/createCookieOptions.js +42 -0
  108. package/dist/oauth/utilities/createCookieOptions.js.map +1 -0
  109. package/dist/oauth/utilities/createDebugLogger.d.ts +4 -0
  110. package/dist/oauth/utilities/createDebugLogger.d.ts.map +1 -0
  111. package/dist/oauth/utilities/createDebugLogger.js +16 -0
  112. package/dist/oauth/utilities/createDebugLogger.js.map +1 -0
  113. package/dist/oauth/utilities/extractOrigin.d.ts +2 -0
  114. package/dist/oauth/utilities/extractOrigin.d.ts.map +1 -0
  115. package/dist/oauth/utilities/extractOrigin.js +12 -0
  116. package/dist/oauth/utilities/extractOrigin.js.map +1 -0
  117. package/dist/oauth/utilities/getAdminCollectionSlug.d.ts +3 -0
  118. package/dist/oauth/utilities/getAdminCollectionSlug.d.ts.map +1 -0
  119. package/dist/oauth/utilities/getAdminCollectionSlug.js +18 -0
  120. package/dist/oauth/utilities/getAdminCollectionSlug.js.map +1 -0
  121. package/dist/oauth/utilities/getAdminPath.d.ts +8 -0
  122. package/dist/oauth/utilities/getAdminPath.d.ts.map +1 -0
  123. package/dist/oauth/utilities/getAdminPath.js +9 -0
  124. package/dist/oauth/utilities/getAdminPath.js.map +1 -0
  125. package/dist/oauth/utilities/getAuthorizeURL.d.ts +17 -0
  126. package/dist/oauth/utilities/getAuthorizeURL.d.ts.map +1 -0
  127. package/dist/oauth/utilities/getAuthorizeURL.js +45 -0
  128. package/dist/oauth/utilities/getAuthorizeURL.js.map +1 -0
  129. package/dist/oauth/utilities/getCookieExpiration.d.ts +4 -0
  130. package/dist/oauth/utilities/getCookieExpiration.d.ts.map +1 -0
  131. package/dist/oauth/utilities/getCookieExpiration.js +11 -0
  132. package/dist/oauth/utilities/getCookieExpiration.js.map +1 -0
  133. package/dist/oauth/utilities/getSecondsFromTokenExp.d.ts +2 -0
  134. package/dist/oauth/utilities/getSecondsFromTokenExp.d.ts.map +1 -0
  135. package/dist/oauth/utilities/getSecondsFromTokenExp.js +7 -0
  136. package/dist/oauth/utilities/getSecondsFromTokenExp.js.map +1 -0
  137. package/dist/oauth/utilities/getTokenExp.d.ts +6 -0
  138. package/dist/oauth/utilities/getTokenExp.d.ts.map +1 -0
  139. package/dist/oauth/utilities/getTokenExp.js +20 -0
  140. package/dist/oauth/utilities/getTokenExp.js.map +1 -0
  141. package/dist/oauth/utilities/getUsernameField.d.ts +9 -0
  142. package/dist/oauth/utilities/getUsernameField.d.ts.map +1 -0
  143. package/dist/oauth/utilities/getUsernameField.js +20 -0
  144. package/dist/oauth/utilities/getUsernameField.js.map +1 -0
  145. package/dist/oauth/utilities/hasUserTokenPropsChanged.d.ts +8 -0
  146. package/dist/oauth/utilities/hasUserTokenPropsChanged.d.ts.map +1 -0
  147. package/dist/oauth/utilities/hasUserTokenPropsChanged.js +25 -0
  148. package/dist/oauth/utilities/hasUserTokenPropsChanged.js.map +1 -0
  149. package/dist/oauth/utilities/mergeHeaders.d.ts +2 -0
  150. package/dist/oauth/utilities/mergeHeaders.d.ts.map +1 -0
  151. package/dist/oauth/utilities/mergeHeaders.js +37 -0
  152. package/dist/oauth/utilities/mergeHeaders.js.map +1 -0
  153. package/dist/oauth/utilities/refreshTokens.d.ts +16 -0
  154. package/dist/oauth/utilities/refreshTokens.d.ts.map +1 -0
  155. package/dist/oauth/utilities/refreshTokens.js +67 -0
  156. package/dist/oauth/utilities/refreshTokens.js.map +1 -0
  157. package/dist/plugin/build-config.d.ts +7 -1
  158. package/dist/plugin/build-config.d.ts.map +1 -1
  159. package/dist/plugin/build-config.js +29 -2
  160. package/dist/plugin/build-config.js.map +1 -1
  161. package/dist/types/config.d.ts +0 -11
  162. package/dist/types/config.d.ts.map +1 -1
  163. package/dist/types/config.js +1 -1
  164. package/dist/types/config.js.map +1 -1
  165. package/dist/utils/config.d.ts +0 -22
  166. package/dist/utils/config.d.ts.map +1 -1
  167. package/dist/utils/config.js +0 -46
  168. package/dist/utils/config.js.map +1 -1
  169. package/dist/utils/messages.js +11 -11
  170. package/dist/utils/messages.js.map +1 -1
  171. package/dist/utils/payload-config-ast.d.ts +17 -1
  172. package/dist/utils/payload-config-ast.d.ts.map +1 -1
  173. package/dist/utils/payload-config-ast.js +166 -14
  174. package/dist/utils/payload-config-ast.js.map +1 -1
  175. package/dist/utils/payload-config-modifier.d.ts +2 -1
  176. package/dist/utils/payload-config-modifier.d.ts.map +1 -1
  177. package/dist/utils/payload-config-modifier.js +50 -79
  178. package/dist/utils/payload-config-modifier.js.map +1 -1
  179. package/dist/utils/payload-package-check.d.ts.map +1 -1
  180. package/dist/utils/payload-package-check.js +14 -33
  181. package/dist/utils/payload-package-check.js.map +1 -1
  182. package/dist/utils/project.d.ts +1 -1
  183. package/dist/utils/project.d.ts.map +1 -1
  184. package/dist/utils/project.js +3 -5
  185. package/dist/utils/project.js.map +1 -1
  186. package/dist/utils/s3-upload.d.ts.map +1 -1
  187. package/dist/utils/s3-upload.js +3 -8
  188. package/dist/utils/s3-upload.js.map +1 -1
  189. package/dist/utils/token-display.d.ts.map +1 -1
  190. package/dist/utils/token-display.js +3 -2
  191. package/dist/utils/token-display.js.map +1 -1
  192. package/package.json +13 -5
  193. package/dist/api/control-plane.spec.d.ts +0 -2
  194. package/dist/api/control-plane.spec.d.ts.map +0 -1
  195. package/dist/api/control-plane.spec.js +0 -296
  196. package/dist/api/control-plane.spec.js.map +0 -1
  197. package/dist/api/figma-api.spec.d.ts +0 -14
  198. package/dist/api/figma-api.spec.d.ts.map +0 -1
  199. package/dist/api/figma-api.spec.js +0 -201
  200. package/dist/api/figma-api.spec.js.map +0 -1
  201. package/dist/auth/__tests__/fixtures.d.ts +0 -26
  202. package/dist/auth/__tests__/fixtures.d.ts.map +0 -1
  203. package/dist/auth/__tests__/fixtures.js +0 -67
  204. package/dist/auth/__tests__/fixtures.js.map +0 -1
  205. package/dist/auth/__tests__/mocks.d.ts +0 -33
  206. package/dist/auth/__tests__/mocks.d.ts.map +0 -1
  207. package/dist/auth/__tests__/mocks.js +0 -63
  208. package/dist/auth/__tests__/mocks.js.map +0 -1
  209. package/dist/auth/callback-server.spec.d.ts +0 -2
  210. package/dist/auth/callback-server.spec.d.ts.map +0 -1
  211. package/dist/auth/callback-server.spec.js +0 -85
  212. package/dist/auth/callback-server.spec.js.map +0 -1
  213. package/dist/auth/crypto-utils.spec.d.ts +0 -2
  214. package/dist/auth/crypto-utils.spec.d.ts.map +0 -1
  215. package/dist/auth/crypto-utils.spec.js +0 -36
  216. package/dist/auth/crypto-utils.spec.js.map +0 -1
  217. package/dist/auth/jwt-validator.spec.d.ts +0 -10
  218. package/dist/auth/jwt-validator.spec.d.ts.map +0 -1
  219. package/dist/auth/jwt-validator.spec.js +0 -236
  220. package/dist/auth/jwt-validator.spec.js.map +0 -1
  221. package/dist/auth/oauth-flow.spec.d.ts +0 -2
  222. package/dist/auth/oauth-flow.spec.d.ts.map +0 -1
  223. package/dist/auth/oauth-flow.spec.js +0 -134
  224. package/dist/auth/oauth-flow.spec.js.map +0 -1
  225. package/dist/auth/pkce.spec.d.ts +0 -2
  226. package/dist/auth/pkce.spec.d.ts.map +0 -1
  227. package/dist/auth/pkce.spec.js +0 -32
  228. package/dist/auth/pkce.spec.js.map +0 -1
  229. package/dist/auth/project-token.spec.d.ts +0 -2
  230. package/dist/auth/project-token.spec.d.ts.map +0 -1
  231. package/dist/auth/project-token.spec.js +0 -332
  232. package/dist/auth/project-token.spec.js.map +0 -1
  233. package/dist/auth/refresh.spec.d.ts +0 -2
  234. package/dist/auth/refresh.spec.d.ts.map +0 -1
  235. package/dist/auth/refresh.spec.js +0 -105
  236. package/dist/auth/refresh.spec.js.map +0 -1
  237. package/dist/auth/token-store.spec.d.ts +0 -2
  238. package/dist/auth/token-store.spec.d.ts.map +0 -1
  239. package/dist/auth/token-store.spec.js +0 -276
  240. package/dist/auth/token-store.spec.js.map +0 -1
  241. package/dist/commands/__tests__/test-utils.d.ts +0 -28
  242. package/dist/commands/__tests__/test-utils.d.ts.map +0 -1
  243. package/dist/commands/__tests__/test-utils.js +0 -34
  244. package/dist/commands/__tests__/test-utils.js.map +0 -1
  245. package/dist/commands/deploy.spec.d.ts +0 -2
  246. package/dist/commands/deploy.spec.d.ts.map +0 -1
  247. package/dist/commands/deploy.spec.js +0 -18
  248. package/dist/commands/deploy.spec.js.map +0 -1
  249. package/dist/commands/init.spec.d.ts +0 -2
  250. package/dist/commands/init.spec.d.ts.map +0 -1
  251. package/dist/commands/init.spec.js +0 -283
  252. package/dist/commands/init.spec.js.map +0 -1
  253. package/dist/plugin/build-config.spec.d.ts +0 -2
  254. package/dist/plugin/build-config.spec.d.ts.map +0 -1
  255. package/dist/plugin/build-config.spec.js +0 -72
  256. package/dist/plugin/build-config.spec.js.map +0 -1
  257. package/dist/utils/asset-collection.spec.d.ts +0 -2
  258. package/dist/utils/asset-collection.spec.d.ts.map +0 -1
  259. package/dist/utils/asset-collection.spec.js +0 -155
  260. package/dist/utils/asset-collection.spec.js.map +0 -1
  261. package/dist/utils/build-detection.spec.d.ts +0 -2
  262. package/dist/utils/build-detection.spec.d.ts.map +0 -1
  263. package/dist/utils/build-detection.spec.js +0 -110
  264. package/dist/utils/build-detection.spec.js.map +0 -1
  265. package/dist/utils/config.spec.d.ts +0 -2
  266. package/dist/utils/config.spec.d.ts.map +0 -1
  267. package/dist/utils/config.spec.js +0 -167
  268. package/dist/utils/config.spec.js.map +0 -1
  269. package/dist/utils/download-template.spec.d.ts +0 -2
  270. package/dist/utils/download-template.spec.d.ts.map +0 -1
  271. package/dist/utils/download-template.spec.js +0 -76
  272. package/dist/utils/download-template.spec.js.map +0 -1
  273. package/dist/utils/env-management.spec.d.ts +0 -2
  274. package/dist/utils/env-management.spec.d.ts.map +0 -1
  275. package/dist/utils/env-management.spec.js +0 -123
  276. package/dist/utils/env-management.spec.js.map +0 -1
  277. package/dist/utils/git.spec.d.ts +0 -2
  278. package/dist/utils/git.spec.d.ts.map +0 -1
  279. package/dist/utils/git.spec.js +0 -99
  280. package/dist/utils/git.spec.js.map +0 -1
  281. package/dist/utils/lambda-config.spec.d.ts +0 -2
  282. package/dist/utils/lambda-config.spec.d.ts.map +0 -1
  283. package/dist/utils/lambda-config.spec.js +0 -141
  284. package/dist/utils/lambda-config.spec.js.map +0 -1
  285. package/dist/utils/payload-config-ast.spec.d.ts +0 -2
  286. package/dist/utils/payload-config-ast.spec.d.ts.map +0 -1
  287. package/dist/utils/payload-config-ast.spec.js +0 -303
  288. package/dist/utils/payload-config-ast.spec.js.map +0 -1
  289. package/dist/utils/payload-config-modifier.int.spec.d.ts +0 -2
  290. package/dist/utils/payload-config-modifier.int.spec.d.ts.map +0 -1
  291. package/dist/utils/payload-config-modifier.int.spec.js +0 -116
  292. package/dist/utils/payload-config-modifier.int.spec.js.map +0 -1
  293. package/dist/utils/payload-config-modifier.spec.d.ts +0 -2
  294. package/dist/utils/payload-config-modifier.spec.d.ts.map +0 -1
  295. package/dist/utils/payload-config-modifier.spec.js +0 -162
  296. package/dist/utils/payload-config-modifier.spec.js.map +0 -1
  297. package/dist/utils/payload-package-check.spec.d.ts +0 -2
  298. package/dist/utils/payload-package-check.spec.d.ts.map +0 -1
  299. package/dist/utils/payload-package-check.spec.js +0 -148
  300. package/dist/utils/payload-package-check.spec.js.map +0 -1
  301. package/dist/utils/project.spec.d.ts +0 -2
  302. package/dist/utils/project.spec.d.ts.map +0 -1
  303. package/dist/utils/project.spec.js +0 -222
  304. package/dist/utils/project.spec.js.map +0 -1
  305. package/dist/utils/s3-upload.spec.d.ts +0 -2
  306. package/dist/utils/s3-upload.spec.d.ts.map +0 -1
  307. package/dist/utils/s3-upload.spec.js +0 -140
  308. package/dist/utils/s3-upload.spec.js.map +0 -1
@@ -1,332 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import * as figmaApi from '../api/figma-api.js';
3
- import * as constants from '../constants.js';
4
- import { JWTValidationError, validateProjectToken } from './jwt-validator.js';
5
- import * as oauthFlow from './oauth-flow.js';
6
- import { getValidProjectToken, ProjectTokenError, refreshProjectToken } from './project-token.js';
7
- import { TokenStore } from './token-store.js';
8
- // Mock all external dependencies
9
- vi.mock('../api/figma-api.js');
10
- vi.mock('./oauth-flow.js');
11
- vi.mock('../constants.js');
12
- // Mock only the validateProjectToken function from jwt-validator
13
- // Keep JWTValidationError class unmocked so it works properly in tests
14
- vi.mock('./jwt-validator.js', async (importOriginal)=>{
15
- // eslint-disable-next-line @typescript-eslint/consistent-type-imports
16
- const actual = await importOriginal();
17
- return {
18
- ...actual,
19
- validateProjectToken: vi.fn()
20
- };
21
- });
22
- describe('Project Token Management', ()=>{
23
- let tokenStore;
24
- const mockTenantId = 'test-tenant-123';
25
- const mockAccessToken = 'figt_mock_access_token';
26
- const mockProjectTokenJWT = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.mock.signature';
27
- let originalEnv;
28
- beforeEach(()=>{
29
- // Create a unique test store for each test
30
- tokenStore = new TokenStore({
31
- configName: `payloadcms-figma-test-${Date.now()}`
32
- });
33
- // Disable mocking to test real validation flow
34
- originalEnv = process.env.FIGMA_MOCK_PROJECT_TOKEN;
35
- process.env.FIGMA_MOCK_PROJECT_TOKEN = 'false';
36
- // Default mock implementations
37
- vi.mocked(constants.getEnvironment).mockReturnValue('staging');
38
- vi.mocked(oauthFlow.getValidAccessToken).mockResolvedValue(mockAccessToken);
39
- vi.mocked(figmaApi.getProjectToken).mockResolvedValue({
40
- expiresAt: Date.now() + 30 * 60 * 1000,
41
- tenantId: mockTenantId,
42
- token: mockProjectTokenJWT
43
- });
44
- vi.mocked(validateProjectToken).mockResolvedValue({
45
- aud: mockTenantId,
46
- email: 'test@example.com',
47
- exp: Math.floor(Date.now() / 1000) + 1800,
48
- iat: Math.floor(Date.now() / 1000),
49
- seat_type: 'full'
50
- });
51
- });
52
- afterEach(()=>{
53
- tokenStore.clearTokens();
54
- tokenStore.clearAllProjectTokens();
55
- vi.clearAllMocks();
56
- // Restore original environment
57
- if (originalEnv === undefined) {
58
- delete process.env.FIGMA_MOCK_PROJECT_TOKEN;
59
- } else {
60
- process.env.FIGMA_MOCK_PROJECT_TOKEN = originalEnv;
61
- }
62
- });
63
- describe('getValidProjectToken', ()=>{
64
- describe('with valid cached token', ()=>{
65
- it('should return cached token immediately without API calls', async ()=>{
66
- // Store a valid project token
67
- const validToken = {
68
- claims: {
69
- aud: mockTenantId,
70
- email: 'test@example.com',
71
- exp: Math.floor(Date.now() / 1000) + 1800,
72
- iat: Math.floor(Date.now() / 1000),
73
- seat_type: 'full'
74
- },
75
- expiresAt: Date.now() + 30 * 60 * 1000,
76
- tenantId: mockTenantId,
77
- token: mockProjectTokenJWT
78
- };
79
- tokenStore.setProjectToken(mockTenantId, validToken);
80
- const result = await getValidProjectToken(tokenStore, mockTenantId);
81
- expect(result).toBe(mockProjectTokenJWT);
82
- // Should not call any external APIs
83
- expect(oauthFlow.getValidAccessToken).not.toHaveBeenCalled();
84
- expect(figmaApi.getProjectToken).not.toHaveBeenCalled();
85
- expect(validateProjectToken).not.toHaveBeenCalled();
86
- });
87
- it('should use hasValidProjectToken for expiry check', async ()=>{
88
- // Mock hasValidProjectToken to return true
89
- const hasValidSpy = vi.spyOn(tokenStore, 'hasValidProjectToken').mockReturnValue(true);
90
- const getTokenSpy = vi.spyOn(tokenStore, 'getProjectToken').mockReturnValue({
91
- claims: {
92
- exp: Math.floor(Date.now() / 1000) + 1800
93
- },
94
- expiresAt: Date.now() + 30 * 60 * 1000,
95
- tenantId: mockTenantId,
96
- token: mockProjectTokenJWT
97
- });
98
- await getValidProjectToken(tokenStore, mockTenantId);
99
- expect(hasValidSpy).toHaveBeenCalledWith(mockTenantId);
100
- expect(getTokenSpy).toHaveBeenCalledWith(mockTenantId);
101
- });
102
- });
103
- describe('with expired or missing token', ()=>{
104
- it('should fetch new token when no cached token exists', async ()=>{
105
- const result = await getValidProjectToken(tokenStore, mockTenantId);
106
- expect(result).toBe(mockProjectTokenJWT);
107
- expect(oauthFlow.getValidAccessToken).toHaveBeenCalledWith(tokenStore);
108
- expect(figmaApi.getProjectToken).toHaveBeenCalledWith(mockAccessToken, mockTenantId);
109
- expect(validateProjectToken).toHaveBeenCalledWith(mockProjectTokenJWT, 'staging');
110
- });
111
- it('should fetch new token when cached token is expired', async ()=>{
112
- // Mock hasValidProjectToken to return false (expired)
113
- vi.spyOn(tokenStore, 'hasValidProjectToken').mockReturnValue(false);
114
- const result = await getValidProjectToken(tokenStore, mockTenantId);
115
- expect(result).toBe(mockProjectTokenJWT);
116
- expect(figmaApi.getProjectToken).toHaveBeenCalled();
117
- });
118
- it('should apply 5-minute expiry buffer through hasValidProjectToken', async ()=>{
119
- // Store a token expiring in 4 minutes (within 5-minute buffer)
120
- const expiringToken = {
121
- claims: {
122
- exp: Math.floor(Date.now() / 1000) + 240
123
- },
124
- expiresAt: Date.now() + 4 * 60 * 1000,
125
- tenantId: mockTenantId,
126
- token: 'expiring-token'
127
- };
128
- tokenStore.setProjectToken(mockTenantId, expiringToken);
129
- // hasValidProjectToken should return false due to buffer
130
- expect(tokenStore.hasValidProjectToken(mockTenantId)).toBe(false);
131
- // Should refresh the token
132
- await getValidProjectToken(tokenStore, mockTenantId);
133
- expect(figmaApi.getProjectToken).toHaveBeenCalled();
134
- });
135
- });
136
- describe('JWT validation integration', ()=>{
137
- it('should validate JWT signature after fetching', async ()=>{
138
- await getValidProjectToken(tokenStore, mockTenantId);
139
- expect(validateProjectToken).toHaveBeenCalledWith(mockProjectTokenJWT, 'staging');
140
- });
141
- it('should use correct environment for validation', async ()=>{
142
- vi.mocked(constants.getEnvironment).mockReturnValue('production');
143
- await getValidProjectToken(tokenStore, mockTenantId);
144
- expect(validateProjectToken).toHaveBeenCalledWith(mockProjectTokenJWT, 'production');
145
- });
146
- it('should throw ProjectTokenError on validation failure', async ()=>{
147
- const validationError = new JWTValidationError('Invalid signature', 'SIGNATURE_INVALID');
148
- vi.mocked(validateProjectToken).mockRejectedValue(validationError);
149
- try {
150
- await getValidProjectToken(tokenStore, mockTenantId);
151
- expect.fail('Should have thrown ProjectTokenError');
152
- } catch (error) {
153
- expect(error).toBeInstanceOf(ProjectTokenError);
154
- if (error instanceof ProjectTokenError) {
155
- expect(error.message).toContain('Project token validation failed');
156
- expect(error.message).toContain('Invalid signature');
157
- expect(error.cause).toBe(validationError);
158
- }
159
- }
160
- });
161
- it('should rethrow non-JWTValidationError errors', async ()=>{
162
- const genericError = new Error('Network error');
163
- vi.mocked(validateProjectToken).mockRejectedValue(genericError);
164
- await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(genericError);
165
- });
166
- it('should throw error if JWT missing exp claim', async ()=>{
167
- // Mock validation returning claims without exp
168
- vi.mocked(validateProjectToken).mockResolvedValue({
169
- aud: mockTenantId
170
- });
171
- await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(ProjectTokenError);
172
- await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow('missing expiration (exp) claim');
173
- });
174
- });
175
- describe('claims storage', ()=>{
176
- it('should store validated claims with token', async ()=>{
177
- const mockClaims = {
178
- aud: mockTenantId,
179
- email: 'user@example.com',
180
- exp: Math.floor(Date.now() / 1000) + 1800,
181
- iat: Math.floor(Date.now() / 1000),
182
- seat_type: 'full'
183
- };
184
- vi.mocked(validateProjectToken).mockResolvedValue(mockClaims);
185
- await getValidProjectToken(tokenStore, mockTenantId);
186
- const storedToken = tokenStore.getProjectToken(mockTenantId);
187
- expect(storedToken?.claims).toEqual(mockClaims);
188
- });
189
- it('should convert exp from seconds to milliseconds for expiresAt', async ()=>{
190
- const expInSeconds = Math.floor(Date.now() / 1000) + 1800 // 30 min from now
191
- ;
192
- vi.mocked(validateProjectToken).mockResolvedValue({
193
- aud: mockTenantId,
194
- exp: expInSeconds
195
- });
196
- await getValidProjectToken(tokenStore, mockTenantId);
197
- const storedToken = tokenStore.getProjectToken(mockTenantId);
198
- expect(storedToken?.expiresAt).toBe(expInSeconds * 1000);
199
- });
200
- it('should store token with correct tenantId', async ()=>{
201
- await getValidProjectToken(tokenStore, mockTenantId);
202
- const storedToken = tokenStore.getProjectToken(mockTenantId);
203
- expect(storedToken?.tenantId).toBe(mockTenantId);
204
- expect(storedToken?.token).toBe(mockProjectTokenJWT);
205
- });
206
- });
207
- describe('error handling', ()=>{
208
- it('should throw ProjectTokenError when no user access token available', async ()=>{
209
- vi.mocked(oauthFlow.getValidAccessToken).mockRejectedValue(new Error('User not authenticated'));
210
- await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(ProjectTokenError);
211
- await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow('Failed to get valid access token');
212
- });
213
- it('should return null when getValidAccessToken returns null', async ()=>{
214
- vi.mocked(oauthFlow.getValidAccessToken).mockResolvedValue(null);
215
- const result = await getValidProjectToken(tokenStore, mockTenantId);
216
- expect(result).toBeNull();
217
- // Should not call API if no access token
218
- expect(figmaApi.getProjectToken).not.toHaveBeenCalled();
219
- });
220
- it('should handle API 401 errors', async ()=>{
221
- const apiError = new figmaApi.FigmaApiError('Unauthorized', 401);
222
- vi.mocked(figmaApi.getProjectToken).mockRejectedValue(apiError);
223
- try {
224
- await getValidProjectToken(tokenStore, mockTenantId);
225
- expect.fail('Should have thrown error');
226
- } catch (error) {
227
- expect(error).toBe(apiError);
228
- expect(error).toBeInstanceOf(figmaApi.FigmaApiError);
229
- }
230
- });
231
- it('should handle API 404 errors (tenant not found)', async ()=>{
232
- const apiError = new figmaApi.FigmaApiError('Tenant not found', 404);
233
- vi.mocked(figmaApi.getProjectToken).mockRejectedValue(apiError);
234
- try {
235
- await getValidProjectToken(tokenStore, mockTenantId);
236
- expect.fail('Should have thrown error');
237
- } catch (error) {
238
- expect(error).toBe(apiError);
239
- expect(error).toBeInstanceOf(figmaApi.FigmaApiError);
240
- }
241
- });
242
- it('should handle network errors', async ()=>{
243
- const networkError = new Error('Network timeout');
244
- vi.mocked(figmaApi.getProjectToken).mockRejectedValue(networkError);
245
- await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(networkError);
246
- });
247
- it('should wrap getValidAccessToken errors in ProjectTokenError', async ()=>{
248
- const authError = new Error('OAuth refresh failed');
249
- vi.mocked(oauthFlow.getValidAccessToken).mockRejectedValue(authError);
250
- try {
251
- await getValidProjectToken(tokenStore, mockTenantId);
252
- expect.fail('Should have thrown ProjectTokenError');
253
- } catch (error) {
254
- expect(error).toBeInstanceOf(ProjectTokenError);
255
- if (error instanceof ProjectTokenError) {
256
- expect(error.cause).toBe(authError);
257
- expect(error.message).toContain('Failed to get valid access token');
258
- }
259
- }
260
- });
261
- });
262
- });
263
- describe('refreshProjectToken', ()=>{
264
- it('should clear existing token before fetching new one', async ()=>{
265
- // Store an existing token
266
- const existingToken = {
267
- claims: {
268
- exp: Math.floor(Date.now() / 1000) + 1800
269
- },
270
- expiresAt: Date.now() + 30 * 60 * 1000,
271
- tenantId: mockTenantId,
272
- token: 'old-token'
273
- };
274
- tokenStore.setProjectToken(mockTenantId, existingToken);
275
- const clearSpy = vi.spyOn(tokenStore, 'clearProjectToken');
276
- await refreshProjectToken(tokenStore, mockTenantId);
277
- expect(clearSpy).toHaveBeenCalledWith(mockTenantId);
278
- });
279
- it('should call getValidProjectToken after clearing', async ()=>{
280
- const result = await refreshProjectToken(tokenStore, mockTenantId);
281
- expect(result).toBe(mockProjectTokenJWT);
282
- // Verify API was called (meaning cache was bypassed)
283
- expect(figmaApi.getProjectToken).toHaveBeenCalled();
284
- });
285
- it('should force refresh even if token is still valid', async ()=>{
286
- // Store a valid token
287
- const validToken = {
288
- claims: {
289
- exp: Math.floor(Date.now() / 1000) + 3600
290
- },
291
- expiresAt: Date.now() + 60 * 60 * 1000,
292
- tenantId: mockTenantId,
293
- token: 'valid-token'
294
- };
295
- tokenStore.setProjectToken(mockTenantId, validToken);
296
- // Verify it's considered valid
297
- expect(tokenStore.hasValidProjectToken(mockTenantId)).toBe(true);
298
- await refreshProjectToken(tokenStore, mockTenantId);
299
- // Should have fetched new token despite valid cache
300
- expect(figmaApi.getProjectToken).toHaveBeenCalled();
301
- });
302
- it('should return new token after refresh', async ()=>{
303
- const newTokenJWT = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.new.signature';
304
- vi.mocked(figmaApi.getProjectToken).mockResolvedValue({
305
- expiresAt: Date.now() + 30 * 60 * 1000,
306
- tenantId: mockTenantId,
307
- token: newTokenJWT
308
- });
309
- const result = await refreshProjectToken(tokenStore, mockTenantId);
310
- expect(result).toBe(newTokenJWT);
311
- expect(tokenStore.getProjectToken(mockTenantId)?.token).toBe(newTokenJWT);
312
- });
313
- it('should propagate errors from getValidProjectToken', async ()=>{
314
- const apiError = new figmaApi.FigmaApiError('API Error', 500);
315
- vi.mocked(figmaApi.getProjectToken).mockRejectedValue(apiError);
316
- try {
317
- await refreshProjectToken(tokenStore, mockTenantId);
318
- expect.fail('Should have thrown error');
319
- } catch (error) {
320
- expect(error).toBe(apiError);
321
- expect(error).toBeInstanceOf(figmaApi.FigmaApiError);
322
- }
323
- });
324
- it('should return null when refresh fails due to no access token', async ()=>{
325
- vi.mocked(oauthFlow.getValidAccessToken).mockResolvedValue(null);
326
- const result = await refreshProjectToken(tokenStore, mockTenantId);
327
- expect(result).toBeNull();
328
- });
329
- });
330
- });
331
-
332
- //# sourceMappingURL=project-token.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/auth/project-token.spec.ts"],"sourcesContent":["import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport type { ProjectToken } from './types.js'\n\nimport * as figmaApi from '../api/figma-api.js'\nimport * as constants from '../constants.js'\nimport { JWTValidationError, validateProjectToken } from './jwt-validator.js'\nimport * as oauthFlow from './oauth-flow.js'\nimport { getValidProjectToken, ProjectTokenError, refreshProjectToken } from './project-token.js'\nimport { TokenStore } from './token-store.js'\n\n// Mock all external dependencies\nvi.mock('../api/figma-api.js')\nvi.mock('./oauth-flow.js')\nvi.mock('../constants.js')\n\n// Mock only the validateProjectToken function from jwt-validator\n// Keep JWTValidationError class unmocked so it works properly in tests\nvi.mock('./jwt-validator.js', async (importOriginal) => {\n // eslint-disable-next-line @typescript-eslint/consistent-type-imports\n const actual = await importOriginal<typeof import('./jwt-validator.js')>()\n return {\n ...actual,\n validateProjectToken: vi.fn(),\n }\n})\n\ndescribe('Project Token Management', () => {\n let tokenStore: TokenStore\n const mockTenantId = 'test-tenant-123'\n const mockAccessToken = 'figt_mock_access_token'\n const mockProjectTokenJWT = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.mock.signature'\n let originalEnv: string | undefined\n\n beforeEach(() => {\n // Create a unique test store for each test\n tokenStore = new TokenStore({ configName: `payloadcms-figma-test-${Date.now()}` })\n\n // Disable mocking to test real validation flow\n originalEnv = process.env.FIGMA_MOCK_PROJECT_TOKEN\n process.env.FIGMA_MOCK_PROJECT_TOKEN = 'false'\n\n // Default mock implementations\n vi.mocked(constants.getEnvironment).mockReturnValue('staging')\n vi.mocked(oauthFlow.getValidAccessToken).mockResolvedValue(mockAccessToken)\n vi.mocked(figmaApi.getProjectToken).mockResolvedValue({\n expiresAt: Date.now() + 30 * 60 * 1000, // 30 minutes (milliseconds)\n tenantId: mockTenantId,\n token: mockProjectTokenJWT,\n })\n vi.mocked(validateProjectToken).mockResolvedValue({\n aud: mockTenantId, // Audience claim contains the CMS ID\n email: 'test@example.com',\n exp: Math.floor(Date.now() / 1000) + 1800, // 30 minutes in seconds\n iat: Math.floor(Date.now() / 1000),\n seat_type: 'full',\n })\n })\n\n afterEach(() => {\n tokenStore.clearTokens()\n tokenStore.clearAllProjectTokens()\n vi.clearAllMocks()\n\n // Restore original environment\n if (originalEnv === undefined) {\n delete process.env.FIGMA_MOCK_PROJECT_TOKEN\n } else {\n process.env.FIGMA_MOCK_PROJECT_TOKEN = originalEnv\n }\n })\n\n describe('getValidProjectToken', () => {\n describe('with valid cached token', () => {\n it('should return cached token immediately without API calls', async () => {\n // Store a valid project token\n const validToken: ProjectToken = {\n claims: {\n aud: mockTenantId, // Audience claim contains the CMS ID\n email: 'test@example.com',\n exp: Math.floor(Date.now() / 1000) + 1800, // 30 minutes from now\n iat: Math.floor(Date.now() / 1000),\n seat_type: 'full',\n },\n expiresAt: Date.now() + 30 * 60 * 1000, // 30 minutes\n tenantId: mockTenantId,\n token: mockProjectTokenJWT,\n }\n tokenStore.setProjectToken(mockTenantId, validToken)\n\n const result = await getValidProjectToken(tokenStore, mockTenantId)\n\n expect(result).toBe(mockProjectTokenJWT)\n // Should not call any external APIs\n expect(oauthFlow.getValidAccessToken).not.toHaveBeenCalled()\n expect(figmaApi.getProjectToken).not.toHaveBeenCalled()\n expect(validateProjectToken).not.toHaveBeenCalled()\n })\n\n it('should use hasValidProjectToken for expiry check', async () => {\n // Mock hasValidProjectToken to return true\n const hasValidSpy = vi.spyOn(tokenStore, 'hasValidProjectToken').mockReturnValue(true)\n const getTokenSpy = vi.spyOn(tokenStore, 'getProjectToken').mockReturnValue({\n claims: { exp: Math.floor(Date.now() / 1000) + 1800 },\n expiresAt: Date.now() + 30 * 60 * 1000,\n tenantId: mockTenantId,\n token: mockProjectTokenJWT,\n })\n\n await getValidProjectToken(tokenStore, mockTenantId)\n\n expect(hasValidSpy).toHaveBeenCalledWith(mockTenantId)\n expect(getTokenSpy).toHaveBeenCalledWith(mockTenantId)\n })\n })\n\n describe('with expired or missing token', () => {\n it('should fetch new token when no cached token exists', async () => {\n const result = await getValidProjectToken(tokenStore, mockTenantId)\n\n expect(result).toBe(mockProjectTokenJWT)\n expect(oauthFlow.getValidAccessToken).toHaveBeenCalledWith(tokenStore)\n expect(figmaApi.getProjectToken).toHaveBeenCalledWith(mockAccessToken, mockTenantId)\n expect(validateProjectToken).toHaveBeenCalledWith(mockProjectTokenJWT, 'staging')\n })\n\n it('should fetch new token when cached token is expired', async () => {\n // Mock hasValidProjectToken to return false (expired)\n vi.spyOn(tokenStore, 'hasValidProjectToken').mockReturnValue(false)\n\n const result = await getValidProjectToken(tokenStore, mockTenantId)\n\n expect(result).toBe(mockProjectTokenJWT)\n expect(figmaApi.getProjectToken).toHaveBeenCalled()\n })\n\n it('should apply 5-minute expiry buffer through hasValidProjectToken', async () => {\n // Store a token expiring in 4 minutes (within 5-minute buffer)\n const expiringToken: ProjectToken = {\n claims: { exp: Math.floor(Date.now() / 1000) + 240 }, // 4 minutes\n expiresAt: Date.now() + 4 * 60 * 1000,\n tenantId: mockTenantId,\n token: 'expiring-token',\n }\n tokenStore.setProjectToken(mockTenantId, expiringToken)\n\n // hasValidProjectToken should return false due to buffer\n expect(tokenStore.hasValidProjectToken(mockTenantId)).toBe(false)\n\n // Should refresh the token\n await getValidProjectToken(tokenStore, mockTenantId)\n\n expect(figmaApi.getProjectToken).toHaveBeenCalled()\n })\n })\n\n describe('JWT validation integration', () => {\n it('should validate JWT signature after fetching', async () => {\n await getValidProjectToken(tokenStore, mockTenantId)\n\n expect(validateProjectToken).toHaveBeenCalledWith(mockProjectTokenJWT, 'staging')\n })\n\n it('should use correct environment for validation', async () => {\n vi.mocked(constants.getEnvironment).mockReturnValue('production')\n\n await getValidProjectToken(tokenStore, mockTenantId)\n\n expect(validateProjectToken).toHaveBeenCalledWith(mockProjectTokenJWT, 'production')\n })\n\n it('should throw ProjectTokenError on validation failure', async () => {\n const validationError = new JWTValidationError('Invalid signature', 'SIGNATURE_INVALID')\n vi.mocked(validateProjectToken).mockRejectedValue(validationError)\n\n try {\n await getValidProjectToken(tokenStore, mockTenantId)\n expect.fail('Should have thrown ProjectTokenError')\n } catch (error) {\n expect(error).toBeInstanceOf(ProjectTokenError)\n if (error instanceof ProjectTokenError) {\n expect(error.message).toContain('Project token validation failed')\n expect(error.message).toContain('Invalid signature')\n expect(error.cause).toBe(validationError)\n }\n }\n })\n\n it('should rethrow non-JWTValidationError errors', async () => {\n const genericError = new Error('Network error')\n vi.mocked(validateProjectToken).mockRejectedValue(genericError)\n\n await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(genericError)\n })\n\n it('should throw error if JWT missing exp claim', async () => {\n // Mock validation returning claims without exp\n vi.mocked(validateProjectToken).mockResolvedValue({\n aud: mockTenantId,\n // No exp claim - intentionally omitted to test validation\n })\n\n await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(\n ProjectTokenError,\n )\n await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(\n 'missing expiration (exp) claim',\n )\n })\n })\n\n describe('claims storage', () => {\n it('should store validated claims with token', async () => {\n const mockClaims = {\n aud: mockTenantId,\n email: 'user@example.com',\n exp: Math.floor(Date.now() / 1000) + 1800,\n iat: Math.floor(Date.now() / 1000),\n seat_type: 'full',\n }\n vi.mocked(validateProjectToken).mockResolvedValue(mockClaims)\n\n await getValidProjectToken(tokenStore, mockTenantId)\n\n const storedToken = tokenStore.getProjectToken(mockTenantId)\n expect(storedToken?.claims).toEqual(mockClaims)\n })\n\n it('should convert exp from seconds to milliseconds for expiresAt', async () => {\n const expInSeconds = Math.floor(Date.now() / 1000) + 1800 // 30 min from now\n vi.mocked(validateProjectToken).mockResolvedValue({\n aud: mockTenantId,\n exp: expInSeconds,\n })\n\n await getValidProjectToken(tokenStore, mockTenantId)\n\n const storedToken = tokenStore.getProjectToken(mockTenantId)\n expect(storedToken?.expiresAt).toBe(expInSeconds * 1000)\n })\n\n it('should store token with correct tenantId', async () => {\n await getValidProjectToken(tokenStore, mockTenantId)\n\n const storedToken = tokenStore.getProjectToken(mockTenantId)\n expect(storedToken?.tenantId).toBe(mockTenantId)\n expect(storedToken?.token).toBe(mockProjectTokenJWT)\n })\n })\n\n describe('error handling', () => {\n it('should throw ProjectTokenError when no user access token available', async () => {\n vi.mocked(oauthFlow.getValidAccessToken).mockRejectedValue(\n new Error('User not authenticated'),\n )\n\n await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(\n ProjectTokenError,\n )\n await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(\n 'Failed to get valid access token',\n )\n })\n\n it('should return null when getValidAccessToken returns null', async () => {\n vi.mocked(oauthFlow.getValidAccessToken).mockResolvedValue(null)\n\n const result = await getValidProjectToken(tokenStore, mockTenantId)\n\n expect(result).toBeNull()\n // Should not call API if no access token\n expect(figmaApi.getProjectToken).not.toHaveBeenCalled()\n })\n\n it('should handle API 401 errors', async () => {\n const apiError = new figmaApi.FigmaApiError('Unauthorized', 401)\n vi.mocked(figmaApi.getProjectToken).mockRejectedValue(apiError)\n\n try {\n await getValidProjectToken(tokenStore, mockTenantId)\n expect.fail('Should have thrown error')\n } catch (error) {\n expect(error).toBe(apiError)\n expect(error).toBeInstanceOf(figmaApi.FigmaApiError)\n }\n })\n\n it('should handle API 404 errors (tenant not found)', async () => {\n const apiError = new figmaApi.FigmaApiError('Tenant not found', 404)\n vi.mocked(figmaApi.getProjectToken).mockRejectedValue(apiError)\n\n try {\n await getValidProjectToken(tokenStore, mockTenantId)\n expect.fail('Should have thrown error')\n } catch (error) {\n expect(error).toBe(apiError)\n expect(error).toBeInstanceOf(figmaApi.FigmaApiError)\n }\n })\n\n it('should handle network errors', async () => {\n const networkError = new Error('Network timeout')\n vi.mocked(figmaApi.getProjectToken).mockRejectedValue(networkError)\n\n await expect(getValidProjectToken(tokenStore, mockTenantId)).rejects.toThrow(networkError)\n })\n\n it('should wrap getValidAccessToken errors in ProjectTokenError', async () => {\n const authError = new Error('OAuth refresh failed')\n vi.mocked(oauthFlow.getValidAccessToken).mockRejectedValue(authError)\n\n try {\n await getValidProjectToken(tokenStore, mockTenantId)\n expect.fail('Should have thrown ProjectTokenError')\n } catch (error) {\n expect(error).toBeInstanceOf(ProjectTokenError)\n if (error instanceof ProjectTokenError) {\n expect(error.cause).toBe(authError)\n expect(error.message).toContain('Failed to get valid access token')\n }\n }\n })\n })\n })\n\n describe('refreshProjectToken', () => {\n it('should clear existing token before fetching new one', async () => {\n // Store an existing token\n const existingToken: ProjectToken = {\n claims: { exp: Math.floor(Date.now() / 1000) + 1800 },\n expiresAt: Date.now() + 30 * 60 * 1000,\n tenantId: mockTenantId,\n token: 'old-token',\n }\n tokenStore.setProjectToken(mockTenantId, existingToken)\n\n const clearSpy = vi.spyOn(tokenStore, 'clearProjectToken')\n\n await refreshProjectToken(tokenStore, mockTenantId)\n\n expect(clearSpy).toHaveBeenCalledWith(mockTenantId)\n })\n\n it('should call getValidProjectToken after clearing', async () => {\n const result = await refreshProjectToken(tokenStore, mockTenantId)\n\n expect(result).toBe(mockProjectTokenJWT)\n // Verify API was called (meaning cache was bypassed)\n expect(figmaApi.getProjectToken).toHaveBeenCalled()\n })\n\n it('should force refresh even if token is still valid', async () => {\n // Store a valid token\n const validToken: ProjectToken = {\n claims: { exp: Math.floor(Date.now() / 1000) + 3600 }, // 1 hour\n expiresAt: Date.now() + 60 * 60 * 1000,\n tenantId: mockTenantId,\n token: 'valid-token',\n }\n tokenStore.setProjectToken(mockTenantId, validToken)\n\n // Verify it's considered valid\n expect(tokenStore.hasValidProjectToken(mockTenantId)).toBe(true)\n\n await refreshProjectToken(tokenStore, mockTenantId)\n\n // Should have fetched new token despite valid cache\n expect(figmaApi.getProjectToken).toHaveBeenCalled()\n })\n\n it('should return new token after refresh', async () => {\n const newTokenJWT = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.new.signature'\n vi.mocked(figmaApi.getProjectToken).mockResolvedValue({\n expiresAt: Date.now() + 30 * 60 * 1000, // 30 minutes (milliseconds)\n tenantId: mockTenantId,\n token: newTokenJWT,\n })\n\n const result = await refreshProjectToken(tokenStore, mockTenantId)\n\n expect(result).toBe(newTokenJWT)\n expect(tokenStore.getProjectToken(mockTenantId)?.token).toBe(newTokenJWT)\n })\n\n it('should propagate errors from getValidProjectToken', async () => {\n const apiError = new figmaApi.FigmaApiError('API Error', 500)\n vi.mocked(figmaApi.getProjectToken).mockRejectedValue(apiError)\n\n try {\n await refreshProjectToken(tokenStore, mockTenantId)\n expect.fail('Should have thrown error')\n } catch (error) {\n expect(error).toBe(apiError)\n expect(error).toBeInstanceOf(figmaApi.FigmaApiError)\n }\n })\n\n it('should return null when refresh fails due to no access token', async () => {\n vi.mocked(oauthFlow.getValidAccessToken).mockResolvedValue(null)\n\n const result = await refreshProjectToken(tokenStore, mockTenantId)\n\n expect(result).toBeNull()\n })\n })\n})\n"],"names":["afterEach","beforeEach","describe","expect","it","vi","figmaApi","constants","JWTValidationError","validateProjectToken","oauthFlow","getValidProjectToken","ProjectTokenError","refreshProjectToken","TokenStore","mock","importOriginal","actual","fn","tokenStore","mockTenantId","mockAccessToken","mockProjectTokenJWT","originalEnv","configName","Date","now","process","env","FIGMA_MOCK_PROJECT_TOKEN","mocked","getEnvironment","mockReturnValue","getValidAccessToken","mockResolvedValue","getProjectToken","expiresAt","tenantId","token","aud","email","exp","Math","floor","iat","seat_type","clearTokens","clearAllProjectTokens","clearAllMocks","undefined","validToken","claims","setProjectToken","result","toBe","not","toHaveBeenCalled","hasValidSpy","spyOn","getTokenSpy","toHaveBeenCalledWith","expiringToken","hasValidProjectToken","validationError","mockRejectedValue","fail","error","toBeInstanceOf","message","toContain","cause","genericError","Error","rejects","toThrow","mockClaims","storedToken","toEqual","expInSeconds","toBeNull","apiError","FigmaApiError","networkError","authError","existingToken","clearSpy","newTokenJWT"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=refresh.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"refresh.spec.d.ts","sourceRoot":"","sources":["../../src/auth/refresh.spec.ts"],"names":[],"mappings":""}
@@ -1,105 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { mockFigmaTokenResponse } from './__tests__/fixtures.js';
3
- import { mockFetchFailure, mockFetchSuccess, restoreFetch } from './__tests__/mocks.js';
4
- import { exchangeCodeForTokens, refreshAccessToken, TokenRefreshError } from './refresh.js';
5
- describe('refreshAccessToken', ()=>{
6
- const testRefreshToken = 'figt_test_refresh_token_987654321';
7
- const testClientId = 'test-client-id';
8
- const originalDebug = process.env.DEBUG;
9
- beforeEach(()=>{
10
- vi.clearAllMocks();
11
- delete process.env.DEBUG;
12
- });
13
- afterEach(()=>{
14
- restoreFetch();
15
- if (originalDebug !== undefined) {
16
- process.env.DEBUG = originalDebug;
17
- }
18
- });
19
- it('should successfully refresh access token with correct HTTP request', async ()=>{
20
- const mockResponse = mockFigmaTokenResponse();
21
- const mockFetch = mockFetchSuccess(mockResponse);
22
- const result = await refreshAccessToken(testRefreshToken, testClientId);
23
- // Verify response transformation
24
- expect(result).toMatchObject({
25
- accessToken: mockResponse.access_token,
26
- refreshToken: mockResponse.refresh_token,
27
- userId: mockResponse.user_id_string
28
- });
29
- expect(result.expiresAt).toBeGreaterThan(Date.now());
30
- // Verify HTTP request details
31
- expect(mockFetch).toHaveBeenCalledWith('https://api.staging.figma.com/v1/oauth/refresh', expect.objectContaining({
32
- method: 'POST'
33
- }));
34
- // Verify headers (public client - no Authorization header)
35
- const callArgs = mockFetch.mock.calls[0];
36
- const headers = callArgs[1].headers;
37
- expect(headers['Content-Type']).toBe('application/x-www-form-urlencoded');
38
- expect(headers['Authorization']).toBeUndefined(); // Public client - no Basic Auth
39
- // Verify request body includes client_id
40
- const body = callArgs[1].body;
41
- expect(body).toContain('grant_type=refresh_token');
42
- expect(body).toContain(`refresh_token=${testRefreshToken}`);
43
- expect(body).toContain(`client_id=${testClientId}`);
44
- });
45
- it('should throw TokenRefreshError on HTTP error', async ()=>{
46
- mockFetchFailure(401, 'Invalid refresh token');
47
- await expect(refreshAccessToken(testRefreshToken, testClientId)).rejects.toThrow(TokenRefreshError);
48
- });
49
- });
50
- describe('exchangeCodeForTokens', ()=>{
51
- const testAuthCode = 'auth_code_123';
52
- const testCodeVerifier = 'code_verifier_123456789012345678901234567890';
53
- const testRedirectUri = 'http://localhost:3000/callback';
54
- const testClientId = 'test-client-id';
55
- const originalDebug = process.env.DEBUG;
56
- beforeEach(()=>{
57
- vi.clearAllMocks();
58
- delete process.env.DEBUG;
59
- });
60
- afterEach(()=>{
61
- restoreFetch();
62
- if (originalDebug !== undefined) {
63
- process.env.DEBUG = originalDebug;
64
- }
65
- });
66
- it('should successfully exchange authorization code for tokens with PKCE', async ()=>{
67
- const mockResponse = mockFigmaTokenResponse();
68
- const mockFetch = mockFetchSuccess(mockResponse);
69
- const result = await exchangeCodeForTokens(testAuthCode, testCodeVerifier, testRedirectUri, testClientId);
70
- // Verify response transformation
71
- expect(result).toMatchObject({
72
- accessToken: mockResponse.access_token,
73
- refreshToken: mockResponse.refresh_token,
74
- userId: mockResponse.user_id_string
75
- });
76
- expect(result.expiresAt).toBeGreaterThan(Date.now());
77
- // Verify HTTP request
78
- expect(mockFetch).toHaveBeenCalledWith('https://api.staging.figma.com/v1/oauth/token', expect.objectContaining({
79
- method: 'POST'
80
- }));
81
- // Verify headers (public client - no Authorization header)
82
- const callArgs = mockFetch.mock.calls[0];
83
- const headers = callArgs[1].headers;
84
- expect(headers['Content-Type']).toBe('application/x-www-form-urlencoded');
85
- expect(headers['Authorization']).toBeUndefined(); // Public client - no Basic Auth
86
- });
87
- it('should send correct request body with PKCE', async ()=>{
88
- const mockResponse = mockFigmaTokenResponse();
89
- const mockFetch = mockFetchSuccess(mockResponse);
90
- await exchangeCodeForTokens(testAuthCode, testCodeVerifier, testRedirectUri, testClientId);
91
- const callArgs = mockFetch.mock.calls[0];
92
- const body = callArgs[1].body;
93
- expect(body).toContain('grant_type=authorization_code');
94
- expect(body).toContain(`code=${testAuthCode}`);
95
- expect(body).toContain(`redirect_uri=${encodeURIComponent(testRedirectUri)}`);
96
- expect(body).toContain(`client_id=${testClientId}`);
97
- expect(body).toContain(`code_verifier=${testCodeVerifier}`);
98
- });
99
- it('should throw TokenRefreshError on invalid authorization code', async ()=>{
100
- mockFetchFailure(400, 'Invalid authorization code');
101
- await expect(exchangeCodeForTokens(testAuthCode, testCodeVerifier, testRedirectUri, testClientId)).rejects.toThrow(TokenRefreshError);
102
- });
103
- });
104
-
105
- //# sourceMappingURL=refresh.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/auth/refresh.spec.ts"],"sourcesContent":["import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport { mockFigmaTokenResponse } from './__tests__/fixtures.js'\nimport { mockFetchFailure, mockFetchSuccess, restoreFetch } from './__tests__/mocks.js'\nimport { exchangeCodeForTokens, refreshAccessToken, TokenRefreshError } from './refresh.js'\n\ndescribe('refreshAccessToken', () => {\n const testRefreshToken = 'figt_test_refresh_token_987654321'\n const testClientId = 'test-client-id'\n const originalDebug = process.env.DEBUG\n\n beforeEach(() => {\n vi.clearAllMocks()\n delete process.env.DEBUG\n })\n\n afterEach(() => {\n restoreFetch()\n if (originalDebug !== undefined) {\n process.env.DEBUG = originalDebug\n }\n })\n\n it('should successfully refresh access token with correct HTTP request', async () => {\n const mockResponse = mockFigmaTokenResponse()\n const mockFetch = mockFetchSuccess(mockResponse)\n\n const result = await refreshAccessToken(testRefreshToken, testClientId)\n\n // Verify response transformation\n expect(result).toMatchObject({\n accessToken: mockResponse.access_token,\n refreshToken: mockResponse.refresh_token,\n userId: mockResponse.user_id_string,\n })\n expect(result.expiresAt).toBeGreaterThan(Date.now())\n\n // Verify HTTP request details\n expect(mockFetch).toHaveBeenCalledWith(\n 'https://api.staging.figma.com/v1/oauth/refresh',\n expect.objectContaining({\n method: 'POST',\n }),\n )\n\n // Verify headers (public client - no Authorization header)\n const callArgs = mockFetch.mock.calls[0]\n const headers = callArgs[1].headers\n expect(headers['Content-Type']).toBe('application/x-www-form-urlencoded')\n expect(headers['Authorization']).toBeUndefined() // Public client - no Basic Auth\n\n // Verify request body includes client_id\n const body = callArgs[1].body\n expect(body).toContain('grant_type=refresh_token')\n expect(body).toContain(`refresh_token=${testRefreshToken}`)\n expect(body).toContain(`client_id=${testClientId}`)\n })\n\n it('should throw TokenRefreshError on HTTP error', async () => {\n mockFetchFailure(401, 'Invalid refresh token')\n\n await expect(refreshAccessToken(testRefreshToken, testClientId)).rejects.toThrow(\n TokenRefreshError,\n )\n })\n})\n\ndescribe('exchangeCodeForTokens', () => {\n const testAuthCode = 'auth_code_123'\n const testCodeVerifier = 'code_verifier_123456789012345678901234567890'\n const testRedirectUri = 'http://localhost:3000/callback'\n const testClientId = 'test-client-id'\n const originalDebug = process.env.DEBUG\n\n beforeEach(() => {\n vi.clearAllMocks()\n delete process.env.DEBUG\n })\n\n afterEach(() => {\n restoreFetch()\n if (originalDebug !== undefined) {\n process.env.DEBUG = originalDebug\n }\n })\n\n it('should successfully exchange authorization code for tokens with PKCE', async () => {\n const mockResponse = mockFigmaTokenResponse()\n const mockFetch = mockFetchSuccess(mockResponse)\n\n const result = await exchangeCodeForTokens(\n testAuthCode,\n testCodeVerifier,\n testRedirectUri,\n testClientId,\n )\n\n // Verify response transformation\n expect(result).toMatchObject({\n accessToken: mockResponse.access_token,\n refreshToken: mockResponse.refresh_token,\n userId: mockResponse.user_id_string,\n })\n expect(result.expiresAt).toBeGreaterThan(Date.now())\n\n // Verify HTTP request\n expect(mockFetch).toHaveBeenCalledWith(\n 'https://api.staging.figma.com/v1/oauth/token',\n expect.objectContaining({\n method: 'POST',\n }),\n )\n\n // Verify headers (public client - no Authorization header)\n const callArgs = mockFetch.mock.calls[0]\n const headers = callArgs[1].headers\n expect(headers['Content-Type']).toBe('application/x-www-form-urlencoded')\n expect(headers['Authorization']).toBeUndefined() // Public client - no Basic Auth\n })\n\n it('should send correct request body with PKCE', async () => {\n const mockResponse = mockFigmaTokenResponse()\n const mockFetch = mockFetchSuccess(mockResponse)\n\n await exchangeCodeForTokens(testAuthCode, testCodeVerifier, testRedirectUri, testClientId)\n\n const callArgs = mockFetch.mock.calls[0]\n const body = callArgs[1].body\n\n expect(body).toContain('grant_type=authorization_code')\n expect(body).toContain(`code=${testAuthCode}`)\n expect(body).toContain(`redirect_uri=${encodeURIComponent(testRedirectUri)}`)\n expect(body).toContain(`client_id=${testClientId}`)\n expect(body).toContain(`code_verifier=${testCodeVerifier}`)\n })\n\n it('should throw TokenRefreshError on invalid authorization code', async () => {\n mockFetchFailure(400, 'Invalid authorization code')\n\n await expect(\n exchangeCodeForTokens(testAuthCode, testCodeVerifier, testRedirectUri, testClientId),\n ).rejects.toThrow(TokenRefreshError)\n })\n})\n"],"names":["afterEach","beforeEach","describe","expect","it","vi","mockFigmaTokenResponse","mockFetchFailure","mockFetchSuccess","restoreFetch","exchangeCodeForTokens","refreshAccessToken","TokenRefreshError","testRefreshToken","testClientId","originalDebug","process","env","DEBUG","clearAllMocks","undefined","mockResponse","mockFetch","result","toMatchObject","accessToken","access_token","refreshToken","refresh_token","userId","user_id_string","expiresAt","toBeGreaterThan","Date","now","toHaveBeenCalledWith","objectContaining","method","callArgs","mock","calls","headers","toBe","toBeUndefined","body","toContain","rejects","toThrow","testAuthCode","testCodeVerifier","testRedirectUri","encodeURIComponent"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=token-store.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"token-store.spec.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.spec.ts"],"names":[],"mappings":""}