@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,168 @@
1
+ import { readFileSync } from 'fs';
2
+ import { createServer } from 'http';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ const filename = fileURLToPath(import.meta.url);
6
+ const dirname = path.dirname(filename);
7
+ /**
8
+ * Error from OAuth callback
9
+ */ export class CallbackError extends Error {
10
+ code;
11
+ constructor(message, code){
12
+ super(message), this.code = code;
13
+ this.name = 'CallbackError';
14
+ }
15
+ }
16
+ /**
17
+ * Local HTTP server for handling OAuth callbacks
18
+ *
19
+ * Starts a temporary server on localhost to receive the OAuth authorization code.
20
+ * The server automatically shuts down after receiving the callback or on timeout.
21
+ *
22
+ * Implements dynamic port allocation: tries preferred port first, falls back to
23
+ * OS-assigned port if unavailable.
24
+ */ export class CallbackServer {
25
+ actualPort;
26
+ preferredPort;
27
+ server = null;
28
+ timeoutMs;
29
+ constructor(options = {}){
30
+ this.preferredPort = options.port || 3000;
31
+ this.timeoutMs = options.timeoutMs || 120000; // 2 minutes default
32
+ this.server = createServer();
33
+ }
34
+ /**
35
+ * Render error page shown in browser when auth fails
36
+ */ renderErrorPage(error, description) {
37
+ const template = readFileSync(path.join(dirname, 'pages', 'error.html'), 'utf-8');
38
+ return template.replace('{{description}}', description || 'An error occurred during authentication.').replace('{{error}}', error ? `<p>Error: <span class="error-code">${error}</span></p>` : '');
39
+ }
40
+ /**
41
+ * Render success page shown in browser after successful auth
42
+ */ renderSuccessPage() {
43
+ return readFileSync(path.join(dirname, 'pages', 'success.html'), 'utf-8');
44
+ }
45
+ /**
46
+ * Get the actual port the server is listening on
47
+ * @returns Port number (may differ from preferred if that was in use)
48
+ */ getActualPort() {
49
+ return this.actualPort || this.preferredPort;
50
+ }
51
+ /**
52
+ * Stop the server if running
53
+ */ stop() {
54
+ if (this.server) {
55
+ this.server.close();
56
+ this.server = null;
57
+ }
58
+ }
59
+ /**
60
+ * Start server and wait for OAuth callback
61
+ *
62
+ * @param expectedState - State parameter to verify CSRF protection
63
+ * @returns Promise resolving to callback result with code and state
64
+ * @throws CallbackError if callback fails or times out
65
+ */ async waitForCallback(expectedState) {
66
+ return new Promise((resolve, reject)=>{
67
+ const cleanup = (handle)=>{
68
+ clearTimeout(handle);
69
+ if (this.server) {
70
+ this.server.close();
71
+ this.server = null;
72
+ }
73
+ };
74
+ // Set timeout for waiting
75
+ const timeoutHandle = setTimeout(()=>{
76
+ cleanup(timeoutHandle);
77
+ reject(new CallbackError('OAuth authorization timed out. Please try again.', 'timeout'));
78
+ }, this.timeoutMs);
79
+ // Create HTTP server
80
+ this.server = createServer((req, res)=>{
81
+ const url = new URL(req.url || '', `http://localhost:${this.getActualPort()}`);
82
+ // Only handle /callback path
83
+ if (url.pathname !== '/callback') {
84
+ res.writeHead(404, {
85
+ 'Content-Type': 'text/html'
86
+ });
87
+ res.end('<html><body><h1>404 Not Found</h1></body></html>');
88
+ return;
89
+ }
90
+ // Extract code and state from query params
91
+ const code = url.searchParams.get('code');
92
+ const state = url.searchParams.get('state');
93
+ const error = url.searchParams.get('error');
94
+ const errorDescription = url.searchParams.get('error_description');
95
+ // Handle OAuth errors
96
+ if (error) {
97
+ cleanup(timeoutHandle);
98
+ res.writeHead(400, {
99
+ 'Content-Type': 'text/html'
100
+ });
101
+ res.end(this.renderErrorPage(error, errorDescription || undefined));
102
+ reject(new CallbackError(`OAuth error: ${error}${errorDescription ? ` - ${errorDescription}` : ''}`, error));
103
+ return;
104
+ }
105
+ // Validate required parameters
106
+ if (!code || !state) {
107
+ cleanup(timeoutHandle);
108
+ res.writeHead(400, {
109
+ 'Content-Type': 'text/html'
110
+ });
111
+ res.end(this.renderErrorPage('invalid_request', 'Missing code or state parameter'));
112
+ reject(new CallbackError('Invalid callback: missing code or state', 'invalid_request'));
113
+ return;
114
+ }
115
+ // Verify state matches (CSRF protection)
116
+ if (state !== expectedState) {
117
+ cleanup(timeoutHandle);
118
+ res.writeHead(400, {
119
+ 'Content-Type': 'text/html'
120
+ });
121
+ res.end(this.renderErrorPage('invalid_state', 'State parameter mismatch'));
122
+ reject(new CallbackError('State mismatch: possible CSRF attack', 'invalid_state'));
123
+ return;
124
+ }
125
+ // Success! Send response to browser
126
+ res.writeHead(200, {
127
+ 'Content-Type': 'text/html'
128
+ });
129
+ res.end(this.renderSuccessPage());
130
+ // Resolve with the callback result
131
+ cleanup(timeoutHandle);
132
+ resolve({
133
+ code,
134
+ state
135
+ });
136
+ });
137
+ // Try to start server with dynamic port allocation
138
+ const tryListen = (port, isRetry = false)=>{
139
+ if (!this.server) {
140
+ reject(new CallbackError('Server instance is not available', 'server_error'));
141
+ return;
142
+ }
143
+ this.server.listen(port, '127.0.0.1', ()=>{
144
+ // Capture the actual port that was assigned
145
+ const addr = this.server.address();
146
+ if (addr && typeof addr === 'object') {
147
+ this.actualPort = addr.port;
148
+ }
149
+ });
150
+ // Handle server errors (port in use, etc)
151
+ this.server.once('error', (err)=>{
152
+ if (err.code === 'EADDRINUSE' && !isRetry) {
153
+ // Port in use, try with OS-assigned port (0 = dynamic)
154
+ this.server.removeAllListeners();
155
+ tryListen(0, true);
156
+ } else {
157
+ cleanup(timeoutHandle);
158
+ reject(new CallbackError(`Failed to start callback server: ${err.message}${!isRetry ? ' (tried fallback port)' : ''}`));
159
+ }
160
+ });
161
+ };
162
+ // Start with preferred port
163
+ tryListen(this.preferredPort);
164
+ });
165
+ }
166
+ }
167
+
168
+ //# sourceMappingURL=callback-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/callback-server.ts"],"sourcesContent":["import { readFileSync } from 'fs'\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'http'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\n\nconst filename = fileURLToPath(import.meta.url)\nconst dirname = path.dirname(filename)\n\n/**\n * Result from OAuth callback server\n */\nexport interface CallbackResult {\n /** Authorization code from OAuth provider */\n code: string\n /** State parameter for CSRF verification */\n state: string\n}\n\n/**\n * Error from OAuth callback\n */\nexport class CallbackError extends Error {\n constructor(\n message: string,\n public code?: string,\n ) {\n super(message)\n this.name = 'CallbackError'\n }\n}\n\n/**\n * Local HTTP server for handling OAuth callbacks\n *\n * Starts a temporary server on localhost to receive the OAuth authorization code.\n * The server automatically shuts down after receiving the callback or on timeout.\n *\n * Implements dynamic port allocation: tries preferred port first, falls back to\n * OS-assigned port if unavailable.\n */\nexport class CallbackServer {\n private actualPort?: number\n private preferredPort: number\n private server: null | Server = null\n private timeoutMs: number\n\n constructor(options: { port?: number; timeoutMs?: number } = {}) {\n this.preferredPort = options.port || 3000\n this.timeoutMs = options.timeoutMs || 120000 // 2 minutes default\n this.server = createServer()\n }\n\n /**\n * Render error page shown in browser when auth fails\n */\n private renderErrorPage(error: string, description?: string): string {\n const template = readFileSync(path.join(dirname, 'pages', 'error.html'), 'utf-8')\n return template\n .replace('{{description}}', description || 'An error occurred during authentication.')\n .replace('{{error}}', error ? `<p>Error: <span class=\"error-code\">${error}</span></p>` : '')\n }\n\n /**\n * Render success page shown in browser after successful auth\n */\n private renderSuccessPage(): string {\n return readFileSync(path.join(dirname, 'pages', 'success.html'), 'utf-8')\n }\n\n /**\n * Get the actual port the server is listening on\n * @returns Port number (may differ from preferred if that was in use)\n */\n getActualPort(): number {\n return this.actualPort || this.preferredPort\n }\n\n /**\n * Stop the server if running\n */\n stop(): void {\n if (this.server) {\n this.server.close()\n this.server = null\n }\n }\n\n /**\n * Start server and wait for OAuth callback\n *\n * @param expectedState - State parameter to verify CSRF protection\n * @returns Promise resolving to callback result with code and state\n * @throws CallbackError if callback fails or times out\n */\n async waitForCallback(expectedState: string): Promise<CallbackResult> {\n return new Promise((resolve, reject) => {\n const cleanup = (handle: NodeJS.Timeout) => {\n clearTimeout(handle)\n if (this.server) {\n this.server.close()\n this.server = null\n }\n }\n\n // Set timeout for waiting\n const timeoutHandle = setTimeout(() => {\n cleanup(timeoutHandle)\n reject(new CallbackError('OAuth authorization timed out. Please try again.', 'timeout'))\n }, this.timeoutMs)\n\n // Create HTTP server\n this.server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '', `http://localhost:${this.getActualPort()}`)\n\n // Only handle /callback path\n if (url.pathname !== '/callback') {\n res.writeHead(404, { 'Content-Type': 'text/html' })\n res.end('<html><body><h1>404 Not Found</h1></body></html>')\n return\n }\n\n // Extract code and state from query params\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n const error = url.searchParams.get('error')\n const errorDescription = url.searchParams.get('error_description')\n\n // Handle OAuth errors\n if (error) {\n cleanup(timeoutHandle)\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end(this.renderErrorPage(error, errorDescription || undefined))\n reject(\n new CallbackError(\n `OAuth error: ${error}${errorDescription ? ` - ${errorDescription}` : ''}`,\n error,\n ),\n )\n return\n }\n\n // Validate required parameters\n if (!code || !state) {\n cleanup(timeoutHandle)\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end(this.renderErrorPage('invalid_request', 'Missing code or state parameter'))\n reject(new CallbackError('Invalid callback: missing code or state', 'invalid_request'))\n return\n }\n\n // Verify state matches (CSRF protection)\n if (state !== expectedState) {\n cleanup(timeoutHandle)\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end(this.renderErrorPage('invalid_state', 'State parameter mismatch'))\n reject(new CallbackError('State mismatch: possible CSRF attack', 'invalid_state'))\n return\n }\n\n // Success! Send response to browser\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(this.renderSuccessPage())\n\n // Resolve with the callback result\n cleanup(timeoutHandle)\n resolve({ code, state })\n })\n\n // Try to start server with dynamic port allocation\n const tryListen = (port: number, isRetry: boolean = false) => {\n if (!this.server) {\n reject(new CallbackError('Server instance is not available', 'server_error'))\n return\n }\n\n this.server.listen(port, '127.0.0.1', () => {\n // Capture the actual port that was assigned\n const addr = this.server!.address()\n if (addr && typeof addr === 'object') {\n this.actualPort = addr.port\n }\n })\n\n // Handle server errors (port in use, etc)\n this.server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE' && !isRetry) {\n // Port in use, try with OS-assigned port (0 = dynamic)\n this.server!.removeAllListeners()\n tryListen(0, true)\n } else {\n cleanup(timeoutHandle)\n reject(\n new CallbackError(\n `Failed to start callback server: ${err.message}${!isRetry ? ' (tried fallback port)' : ''}`,\n ),\n )\n }\n })\n }\n\n // Start with preferred port\n tryListen(this.preferredPort)\n })\n }\n}\n"],"names":["readFileSync","createServer","path","fileURLToPath","filename","url","dirname","CallbackError","Error","message","code","name","CallbackServer","actualPort","preferredPort","server","timeoutMs","options","port","renderErrorPage","error","description","template","join","replace","renderSuccessPage","getActualPort","stop","close","waitForCallback","expectedState","Promise","resolve","reject","cleanup","handle","clearTimeout","timeoutHandle","setTimeout","req","res","URL","pathname","writeHead","end","searchParams","get","state","errorDescription","undefined","tryListen","isRetry","listen","addr","address","once","err","removeAllListeners"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=callback-server.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-server.spec.d.ts","sourceRoot":"","sources":["../../src/auth/callback-server.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,85 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { CallbackError, CallbackServer } from './callback-server.js';
3
+ describe('CallbackServer', ()=>{
4
+ describe('waitForCallback', ()=>{
5
+ it('should resolve with code and state on successful callback', async ()=>{
6
+ const server = new CallbackServer({
7
+ port: 3001,
8
+ timeoutMs: 5000
9
+ });
10
+ const expectedState = 'test-state-123';
11
+ // Start waiting for callback
12
+ const callbackPromise = server.waitForCallback(expectedState);
13
+ // Simulate OAuth callback
14
+ await fetch(`http://localhost:3001/callback?code=auth-code-123&state=${expectedState}`);
15
+ const result = await callbackPromise;
16
+ expect(result.code).toBe('auth-code-123');
17
+ expect(result.state).toBe(expectedState);
18
+ server.stop();
19
+ });
20
+ it('should reject with CallbackError on state mismatch', async ()=>{
21
+ const server = new CallbackServer({
22
+ port: 3002,
23
+ timeoutMs: 5000
24
+ });
25
+ const expectedState = 'test-state-123';
26
+ const callbackPromise = server.waitForCallback(expectedState);
27
+ // Send wrong state
28
+ const fetchPromise = fetch(`http://localhost:3002/callback?code=auth-code-123&state=wrong-state`);
29
+ await expect(callbackPromise).rejects.toThrow(CallbackError);
30
+ await expect(callbackPromise).rejects.toThrow('State mismatch');
31
+ // Wait for fetch to complete
32
+ await fetchPromise;
33
+ server.stop();
34
+ });
35
+ it('should reject with CallbackError on OAuth error', async ()=>{
36
+ const server = new CallbackServer({
37
+ port: 3003,
38
+ timeoutMs: 5000
39
+ });
40
+ const expectedState = 'test-state-123';
41
+ const callbackPromise = server.waitForCallback(expectedState);
42
+ // Send OAuth error
43
+ const fetchPromise = fetch(`http://localhost:3003/callback?error=access_denied&error_description=User+denied`);
44
+ await expect(callbackPromise).rejects.toThrow(CallbackError);
45
+ await expect(callbackPromise).rejects.toThrow('access_denied');
46
+ // Wait for fetch to complete
47
+ await fetchPromise;
48
+ server.stop();
49
+ });
50
+ it('should reject with CallbackError on timeout', async ()=>{
51
+ const server = new CallbackServer({
52
+ port: 3005,
53
+ timeoutMs: 100
54
+ });
55
+ const expectedState = 'test-state-123';
56
+ const callbackPromise = server.waitForCallback(expectedState);
57
+ // Don't send callback - let it timeout
58
+ await expect(callbackPromise).rejects.toThrow(CallbackError);
59
+ await expect(callbackPromise).rejects.toThrow('timed out');
60
+ server.stop();
61
+ }, 1000);
62
+ it('should return 404 for non-callback paths', async ()=>{
63
+ const server = new CallbackServer({
64
+ port: 3006,
65
+ timeoutMs: 200
66
+ });
67
+ const expectedState = 'test-state-123';
68
+ // Start server
69
+ const callbackPromise = server.waitForCallback(expectedState).catch(()=>{
70
+ // Ignore timeout error
71
+ });
72
+ // Wait a bit for server to start
73
+ await new Promise((resolve)=>setTimeout(resolve, 50));
74
+ // Try to access wrong path
75
+ const response = await fetch('http://localhost:3006/not-callback');
76
+ expect(response.status).toBe(404);
77
+ // Clean up
78
+ server.stop();
79
+ // Wait for promise to complete
80
+ await callbackPromise;
81
+ }, 2000);
82
+ });
83
+ });
84
+
85
+ //# sourceMappingURL=callback-server.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/callback-server.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport { CallbackError, CallbackServer } from './callback-server.js'\n\ndescribe('CallbackServer', () => {\n describe('waitForCallback', () => {\n it('should resolve with code and state on successful callback', async () => {\n const server = new CallbackServer({ port: 3001, timeoutMs: 5000 })\n const expectedState = 'test-state-123'\n\n // Start waiting for callback\n const callbackPromise = server.waitForCallback(expectedState)\n\n // Simulate OAuth callback\n await fetch(`http://localhost:3001/callback?code=auth-code-123&state=${expectedState}`)\n\n const result = await callbackPromise\n expect(result.code).toBe('auth-code-123')\n expect(result.state).toBe(expectedState)\n\n server.stop()\n })\n\n it('should reject with CallbackError on state mismatch', async () => {\n const server = new CallbackServer({ port: 3002, timeoutMs: 5000 })\n const expectedState = 'test-state-123'\n\n const callbackPromise = server.waitForCallback(expectedState)\n\n // Send wrong state\n const fetchPromise = fetch(\n `http://localhost:3002/callback?code=auth-code-123&state=wrong-state`,\n )\n\n await expect(callbackPromise).rejects.toThrow(CallbackError)\n await expect(callbackPromise).rejects.toThrow('State mismatch')\n\n // Wait for fetch to complete\n await fetchPromise\n\n server.stop()\n })\n\n it('should reject with CallbackError on OAuth error', async () => {\n const server = new CallbackServer({ port: 3003, timeoutMs: 5000 })\n const expectedState = 'test-state-123'\n\n const callbackPromise = server.waitForCallback(expectedState)\n\n // Send OAuth error\n const fetchPromise = fetch(\n `http://localhost:3003/callback?error=access_denied&error_description=User+denied`,\n )\n\n await expect(callbackPromise).rejects.toThrow(CallbackError)\n await expect(callbackPromise).rejects.toThrow('access_denied')\n\n // Wait for fetch to complete\n await fetchPromise\n\n server.stop()\n })\n\n it('should reject with CallbackError on timeout', async () => {\n const server = new CallbackServer({ port: 3005, timeoutMs: 100 })\n const expectedState = 'test-state-123'\n\n const callbackPromise = server.waitForCallback(expectedState)\n\n // Don't send callback - let it timeout\n await expect(callbackPromise).rejects.toThrow(CallbackError)\n await expect(callbackPromise).rejects.toThrow('timed out')\n\n server.stop()\n }, 1000)\n\n it('should return 404 for non-callback paths', async () => {\n const server = new CallbackServer({ port: 3006, timeoutMs: 200 })\n const expectedState = 'test-state-123'\n\n // Start server\n const callbackPromise = server.waitForCallback(expectedState).catch(() => {\n // Ignore timeout error\n })\n\n // Wait a bit for server to start\n await new Promise((resolve) => setTimeout(resolve, 50))\n\n // Try to access wrong path\n const response = await fetch('http://localhost:3006/not-callback')\n expect(response.status).toBe(404)\n\n // Clean up\n server.stop()\n\n // Wait for promise to complete\n await callbackPromise\n }, 2000)\n })\n})\n"],"names":["describe","expect","it","CallbackError","CallbackServer","server","port","timeoutMs","expectedState","callbackPromise","waitForCallback","fetch","result","code","toBe","state","stop","fetchPromise","rejects","toThrow","catch","Promise","resolve","setTimeout","response","status"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,aAAa,EAAEC,cAAc,QAAQ,uBAAsB;AAEpEJ,SAAS,kBAAkB;IACzBA,SAAS,mBAAmB;QAC1BE,GAAG,6DAA6D;YAC9D,MAAMG,SAAS,IAAID,eAAe;gBAAEE,MAAM;gBAAMC,WAAW;YAAK;YAChE,MAAMC,gBAAgB;YAEtB,6BAA6B;YAC7B,MAAMC,kBAAkBJ,OAAOK,eAAe,CAACF;YAE/C,0BAA0B;YAC1B,MAAMG,MAAM,CAAC,wDAAwD,EAAEH,eAAe;YAEtF,MAAMI,SAAS,MAAMH;YACrBR,OAAOW,OAAOC,IAAI,EAAEC,IAAI,CAAC;YACzBb,OAAOW,OAAOG,KAAK,EAAED,IAAI,CAACN;YAE1BH,OAAOW,IAAI;QACb;QAEAd,GAAG,sDAAsD;YACvD,MAAMG,SAAS,IAAID,eAAe;gBAAEE,MAAM;gBAAMC,WAAW;YAAK;YAChE,MAAMC,gBAAgB;YAEtB,MAAMC,kBAAkBJ,OAAOK,eAAe,CAACF;YAE/C,mBAAmB;YACnB,MAAMS,eAAeN,MACnB,CAAC,mEAAmE,CAAC;YAGvE,MAAMV,OAAOQ,iBAAiBS,OAAO,CAACC,OAAO,CAAChB;YAC9C,MAAMF,OAAOQ,iBAAiBS,OAAO,CAACC,OAAO,CAAC;YAE9C,6BAA6B;YAC7B,MAAMF;YAENZ,OAAOW,IAAI;QACb;QAEAd,GAAG,mDAAmD;YACpD,MAAMG,SAAS,IAAID,eAAe;gBAAEE,MAAM;gBAAMC,WAAW;YAAK;YAChE,MAAMC,gBAAgB;YAEtB,MAAMC,kBAAkBJ,OAAOK,eAAe,CAACF;YAE/C,mBAAmB;YACnB,MAAMS,eAAeN,MACnB,CAAC,gFAAgF,CAAC;YAGpF,MAAMV,OAAOQ,iBAAiBS,OAAO,CAACC,OAAO,CAAChB;YAC9C,MAAMF,OAAOQ,iBAAiBS,OAAO,CAACC,OAAO,CAAC;YAE9C,6BAA6B;YAC7B,MAAMF;YAENZ,OAAOW,IAAI;QACb;QAEAd,GAAG,+CAA+C;YAChD,MAAMG,SAAS,IAAID,eAAe;gBAAEE,MAAM;gBAAMC,WAAW;YAAI;YAC/D,MAAMC,gBAAgB;YAEtB,MAAMC,kBAAkBJ,OAAOK,eAAe,CAACF;YAE/C,uCAAuC;YACvC,MAAMP,OAAOQ,iBAAiBS,OAAO,CAACC,OAAO,CAAChB;YAC9C,MAAMF,OAAOQ,iBAAiBS,OAAO,CAACC,OAAO,CAAC;YAE9Cd,OAAOW,IAAI;QACb,GAAG;QAEHd,GAAG,4CAA4C;YAC7C,MAAMG,SAAS,IAAID,eAAe;gBAAEE,MAAM;gBAAMC,WAAW;YAAI;YAC/D,MAAMC,gBAAgB;YAEtB,eAAe;YACf,MAAMC,kBAAkBJ,OAAOK,eAAe,CAACF,eAAeY,KAAK,CAAC;YAClE,uBAAuB;YACzB;YAEA,iCAAiC;YACjC,MAAM,IAAIC,QAAQ,CAACC,UAAYC,WAAWD,SAAS;YAEnD,2BAA2B;YAC3B,MAAME,WAAW,MAAMb,MAAM;YAC7BV,OAAOuB,SAASC,MAAM,EAAEX,IAAI,CAAC;YAE7B,WAAW;YACXT,OAAOW,IAAI;YAEX,+BAA+B;YAC/B,MAAMP;QACR,GAAG;IACL;AACF"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Derive a machine-specific encryption key for token storage
3
+ *
4
+ * Creates a deterministic encryption key using PBKDF2 with machine-specific
5
+ * identifiers and a random salt. This provides stronger security than simple
6
+ * hashing by making brute force attacks computationally expensive.
7
+ *
8
+ * Security model - What this protects against:
9
+ * ✅ Accidental file exposure (cloud backups, shared drives)
10
+ * ✅ Non-technical users reading token files
11
+ * ✅ Brute force attacks (100k PBKDF2 iterations)
12
+ * ✅ Key guessing even with known machine identifiers (random salt)
13
+ *
14
+ * Security model - What this DOES NOT protect against:
15
+ * ❌ Local user compromise (same user can run this function)
16
+ * ❌ Root/admin access attacks
17
+ * ❌ Sophisticated attackers with system access
18
+ *
19
+ * For stronger security, use OS keychain (@napi-rs/keyring) instead.
20
+ * This is a fallback for environments without keychain access.
21
+ *
22
+ * Implementation details:
23
+ * - Uses PBKDF2 with SHA-256 and 100,000 iterations (~500ms-1s)
24
+ * - Combines multiple machine identifiers for entropy
25
+ * - Uses persistent random salt unique to each machine
26
+ * - Produces consistent key for same machine (deterministic)
27
+ * - Tokens are not portable between machines (by design)
28
+ *
29
+ * @returns 64-character hex string (32-byte key) suitable for AES-256 encryption
30
+ * @throws Error if unable to derive key due to missing machine information
31
+ */
32
+ export declare function deriveEncryptionKey(): string;
33
+ //# sourceMappingURL=crypto-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-utils.d.ts","sourceRoot":"","sources":["../../src/auth/crypto-utils.ts"],"names":[],"mappings":"AAqEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAe5C"}
@@ -0,0 +1,96 @@
1
+ import Conf from 'conf';
2
+ import crypto from 'crypto';
3
+ import os from 'os';
4
+ // Configuration store for cryptographic salt
5
+ const cryptoConf = new Conf({
6
+ configName: 'payloadcms-figma-crypto',
7
+ projectName: 'payloadcms-figma'
8
+ });
9
+ /**
10
+ * Get or generate a random salt for key derivation
11
+ *
12
+ * Generates a cryptographically secure random salt on first run and persists it
13
+ * for future use. This ensures the encryption key is unique per machine and not
14
+ * reproducible even if machine identifiers are known.
15
+ *
16
+ * @returns 32-byte hex string (64 characters)
17
+ */ function getOrCreateSalt() {
18
+ let salt = cryptoConf.get('salt');
19
+ if (!salt) {
20
+ // Generate cryptographically secure random salt (32 bytes = 256 bits)
21
+ salt = crypto.randomBytes(32).toString('hex');
22
+ cryptoConf.set('salt', salt);
23
+ }
24
+ return salt;
25
+ }
26
+ /**
27
+ * Gather machine-specific entropy sources
28
+ *
29
+ * Collects multiple machine and user identifiers to create a unique input
30
+ * for key derivation. More entropy sources make the key harder to guess.
31
+ *
32
+ * @returns Combined machine identifier string
33
+ * @throws Error if unable to gather sufficient entropy
34
+ */ function gatherMachineEntropy() {
35
+ try {
36
+ const userInfo = os.userInfo();
37
+ const networkInterfaces = os.networkInterfaces();
38
+ // Get MAC address from first available network interface (if available)
39
+ const macAddress = Object.values(networkInterfaces).flat().find((iface)=>iface && !iface.internal && iface.mac !== '00:00:00:00:00:00')?.mac;
40
+ const entropy = [
41
+ os.hostname(),
42
+ os.homedir(),
43
+ userInfo.username,
44
+ os.platform(),
45
+ os.arch(),
46
+ macAddress
47
+ ].filter(Boolean) // Remove any undefined values
48
+ ;
49
+ if (entropy.length < 4) {
50
+ throw new Error('Insufficient machine entropy available');
51
+ }
52
+ return entropy.join('::');
53
+ } catch (error) {
54
+ throw new Error(`Failed to gather machine entropy: ${error instanceof Error ? error.message : 'Unknown error'}`);
55
+ }
56
+ }
57
+ /**
58
+ * Derive a machine-specific encryption key for token storage
59
+ *
60
+ * Creates a deterministic encryption key using PBKDF2 with machine-specific
61
+ * identifiers and a random salt. This provides stronger security than simple
62
+ * hashing by making brute force attacks computationally expensive.
63
+ *
64
+ * Security model - What this protects against:
65
+ * ✅ Accidental file exposure (cloud backups, shared drives)
66
+ * ✅ Non-technical users reading token files
67
+ * ✅ Brute force attacks (100k PBKDF2 iterations)
68
+ * ✅ Key guessing even with known machine identifiers (random salt)
69
+ *
70
+ * Security model - What this DOES NOT protect against:
71
+ * ❌ Local user compromise (same user can run this function)
72
+ * ❌ Root/admin access attacks
73
+ * ❌ Sophisticated attackers with system access
74
+ *
75
+ * For stronger security, use OS keychain (@napi-rs/keyring) instead.
76
+ * This is a fallback for environments without keychain access.
77
+ *
78
+ * Implementation details:
79
+ * - Uses PBKDF2 with SHA-256 and 100,000 iterations (~500ms-1s)
80
+ * - Combines multiple machine identifiers for entropy
81
+ * - Uses persistent random salt unique to each machine
82
+ * - Produces consistent key for same machine (deterministic)
83
+ * - Tokens are not portable between machines (by design)
84
+ *
85
+ * @returns 64-character hex string (32-byte key) suitable for AES-256 encryption
86
+ * @throws Error if unable to derive key due to missing machine information
87
+ */ export function deriveEncryptionKey() {
88
+ const machineId = gatherMachineEntropy();
89
+ const salt = getOrCreateSalt();
90
+ // PBKDF2 with 100k iterations provides strong security against brute force
91
+ // while remaining performant for CLI use (~500ms-1s key derivation time)
92
+ const key = crypto.pbkdf2Sync(machineId, salt, 100000, 32, 'sha256');
93
+ return key.toString('hex');
94
+ }
95
+
96
+ //# sourceMappingURL=crypto-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/crypto-utils.ts"],"sourcesContent":["import Conf from 'conf'\nimport crypto from 'crypto'\nimport os from 'os'\n\n// Configuration store for cryptographic salt\nconst cryptoConf = new Conf<{ salt: string }>({\n configName: 'payloadcms-figma-crypto',\n projectName: 'payloadcms-figma',\n})\n\n/**\n * Get or generate a random salt for key derivation\n *\n * Generates a cryptographically secure random salt on first run and persists it\n * for future use. This ensures the encryption key is unique per machine and not\n * reproducible even if machine identifiers are known.\n *\n * @returns 32-byte hex string (64 characters)\n */\nfunction getOrCreateSalt(): string {\n let salt = cryptoConf.get('salt')\n if (!salt) {\n // Generate cryptographically secure random salt (32 bytes = 256 bits)\n salt = crypto.randomBytes(32).toString('hex')\n cryptoConf.set('salt', salt)\n }\n return salt\n}\n\n/**\n * Gather machine-specific entropy sources\n *\n * Collects multiple machine and user identifiers to create a unique input\n * for key derivation. More entropy sources make the key harder to guess.\n *\n * @returns Combined machine identifier string\n * @throws Error if unable to gather sufficient entropy\n */\nfunction gatherMachineEntropy(): string {\n try {\n const userInfo = os.userInfo()\n const networkInterfaces = os.networkInterfaces()\n\n // Get MAC address from first available network interface (if available)\n const macAddress = Object.values(networkInterfaces)\n .flat()\n .find((iface) => iface && !iface.internal && iface.mac !== '00:00:00:00:00:00')?.mac\n\n const entropy = [\n os.hostname(), // Machine hostname\n os.homedir(), // User home directory path\n userInfo.username, // OS username\n os.platform(), // Operating system platform\n os.arch(), // CPU architecture\n macAddress, // MAC address (if available)\n ].filter(Boolean) // Remove any undefined values\n\n if (entropy.length < 4) {\n throw new Error('Insufficient machine entropy available')\n }\n\n return entropy.join('::')\n } catch (error) {\n throw new Error(\n `Failed to gather machine entropy: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n }\n}\n\n/**\n * Derive a machine-specific encryption key for token storage\n *\n * Creates a deterministic encryption key using PBKDF2 with machine-specific\n * identifiers and a random salt. This provides stronger security than simple\n * hashing by making brute force attacks computationally expensive.\n *\n * Security model - What this protects against:\n * ✅ Accidental file exposure (cloud backups, shared drives)\n * ✅ Non-technical users reading token files\n * ✅ Brute force attacks (100k PBKDF2 iterations)\n * ✅ Key guessing even with known machine identifiers (random salt)\n *\n * Security model - What this DOES NOT protect against:\n * ❌ Local user compromise (same user can run this function)\n * ❌ Root/admin access attacks\n * ❌ Sophisticated attackers with system access\n *\n * For stronger security, use OS keychain (@napi-rs/keyring) instead.\n * This is a fallback for environments without keychain access.\n *\n * Implementation details:\n * - Uses PBKDF2 with SHA-256 and 100,000 iterations (~500ms-1s)\n * - Combines multiple machine identifiers for entropy\n * - Uses persistent random salt unique to each machine\n * - Produces consistent key for same machine (deterministic)\n * - Tokens are not portable between machines (by design)\n *\n * @returns 64-character hex string (32-byte key) suitable for AES-256 encryption\n * @throws Error if unable to derive key due to missing machine information\n */\nexport function deriveEncryptionKey(): string {\n const machineId = gatherMachineEntropy()\n const salt = getOrCreateSalt()\n\n // PBKDF2 with 100k iterations provides strong security against brute force\n // while remaining performant for CLI use (~500ms-1s key derivation time)\n const key = crypto.pbkdf2Sync(\n machineId, // Password (machine identifiers)\n salt, // Salt (random, persisted)\n 100000, // Iterations (100k for strong security)\n 32, // Key length (32 bytes = 256 bits for AES-256)\n 'sha256', // Hash algorithm\n )\n\n return key.toString('hex')\n}\n"],"names":["Conf","crypto","os","cryptoConf","configName","projectName","getOrCreateSalt","salt","get","randomBytes","toString","set","gatherMachineEntropy","userInfo","networkInterfaces","macAddress","Object","values","flat","find","iface","internal","mac","entropy","hostname","homedir","username","platform","arch","filter","Boolean","length","Error","join","error","message","deriveEncryptionKey","machineId","key","pbkdf2Sync"],"mappings":"AAAA,OAAOA,UAAU,OAAM;AACvB,OAAOC,YAAY,SAAQ;AAC3B,OAAOC,QAAQ,KAAI;AAEnB,6CAA6C;AAC7C,MAAMC,aAAa,IAAIH,KAAuB;IAC5CI,YAAY;IACZC,aAAa;AACf;AAEA;;;;;;;;CAQC,GACD,SAASC;IACP,IAAIC,OAAOJ,WAAWK,GAAG,CAAC;IAC1B,IAAI,CAACD,MAAM;QACT,sEAAsE;QACtEA,OAAON,OAAOQ,WAAW,CAAC,IAAIC,QAAQ,CAAC;QACvCP,WAAWQ,GAAG,CAAC,QAAQJ;IACzB;IACA,OAAOA;AACT;AAEA;;;;;;;;CAQC,GACD,SAASK;IACP,IAAI;QACF,MAAMC,WAAWX,GAAGW,QAAQ;QAC5B,MAAMC,oBAAoBZ,GAAGY,iBAAiB;QAE9C,wEAAwE;QACxE,MAAMC,aAAaC,OAAOC,MAAM,CAACH,mBAC9BI,IAAI,GACJC,IAAI,CAAC,CAACC,QAAUA,SAAS,CAACA,MAAMC,QAAQ,IAAID,MAAME,GAAG,KAAK,sBAAsBA;QAEnF,MAAMC,UAAU;YACdrB,GAAGsB,QAAQ;YACXtB,GAAGuB,OAAO;YACVZ,SAASa,QAAQ;YACjBxB,GAAGyB,QAAQ;YACXzB,GAAG0B,IAAI;YACPb;SACD,CAACc,MAAM,CAACC,SAAS,8BAA8B;;QAEhD,IAAIP,QAAQQ,MAAM,GAAG,GAAG;YACtB,MAAM,IAAIC,MAAM;QAClB;QAEA,OAAOT,QAAQU,IAAI,CAAC;IACtB,EAAE,OAAOC,OAAO;QACd,MAAM,IAAIF,MACR,CAAC,kCAAkC,EAAEE,iBAAiBF,QAAQE,MAAMC,OAAO,GAAG,iBAAiB;IAEnG;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GACD,OAAO,SAASC;IACd,MAAMC,YAAYzB;IAClB,MAAML,OAAOD;IAEb,2EAA2E;IAC3E,yEAAyE;IACzE,MAAMgC,MAAMrC,OAAOsC,UAAU,CAC3BF,WACA9B,MACA,QACA,IACA;IAGF,OAAO+B,IAAI5B,QAAQ,CAAC;AACtB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crypto-utils.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-utils.spec.d.ts","sourceRoot":"","sources":["../../src/auth/crypto-utils.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { deriveEncryptionKey } from './crypto-utils.js';
3
+ describe('crypto-utils', ()=>{
4
+ describe('deriveEncryptionKey', ()=>{
5
+ it('should return the same key on multiple calls (deterministic)', ()=>{
6
+ const key1 = deriveEncryptionKey();
7
+ const key2 = deriveEncryptionKey();
8
+ const key3 = deriveEncryptionKey();
9
+ expect(key1).toBe(key2);
10
+ expect(key2).toBe(key3);
11
+ });
12
+ it('should produce keys suitable for AES-256 encryption', ()=>{
13
+ const key = deriveEncryptionKey();
14
+ // AES-256 requires 256 bits = 32 bytes = 64 hex characters
15
+ expect(key.length).toBe(64);
16
+ // Should be valid hex (can be converted to Buffer)
17
+ expect(()=>Buffer.from(key, 'hex')).not.toThrow();
18
+ const buffer = Buffer.from(key, 'hex');
19
+ expect(buffer.length).toBe(32); // 32 bytes
20
+ });
21
+ it('should complete key derivation in reasonable time', ()=>{
22
+ // Test that 100k PBKDF2 iterations complete quickly enough for CLI use
23
+ const startTime = Date.now();
24
+ // Run multiple times to get average
25
+ for(let i = 0; i < 5; i++){
26
+ deriveEncryptionKey();
27
+ }
28
+ const endTime = Date.now();
29
+ const avgTime = (endTime - startTime) / 5;
30
+ // Each call should take less than 2 seconds on average
31
+ expect(avgTime).toBeLessThan(2000);
32
+ });
33
+ });
34
+ });
35
+
36
+ //# sourceMappingURL=crypto-utils.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/crypto-utils.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport { deriveEncryptionKey } from './crypto-utils.js'\n\ndescribe('crypto-utils', () => {\n describe('deriveEncryptionKey', () => {\n it('should return the same key on multiple calls (deterministic)', () => {\n const key1 = deriveEncryptionKey()\n const key2 = deriveEncryptionKey()\n const key3 = deriveEncryptionKey()\n\n expect(key1).toBe(key2)\n expect(key2).toBe(key3)\n })\n\n it('should produce keys suitable for AES-256 encryption', () => {\n const key = deriveEncryptionKey()\n\n // AES-256 requires 256 bits = 32 bytes = 64 hex characters\n expect(key.length).toBe(64)\n\n // Should be valid hex (can be converted to Buffer)\n expect(() => Buffer.from(key, 'hex')).not.toThrow()\n\n const buffer = Buffer.from(key, 'hex')\n expect(buffer.length).toBe(32) // 32 bytes\n })\n\n it('should complete key derivation in reasonable time', () => {\n // Test that 100k PBKDF2 iterations complete quickly enough for CLI use\n const startTime = Date.now()\n\n // Run multiple times to get average\n for (let i = 0; i < 5; i++) {\n deriveEncryptionKey()\n }\n\n const endTime = Date.now()\n const avgTime = (endTime - startTime) / 5\n\n // Each call should take less than 2 seconds on average\n expect(avgTime).toBeLessThan(2000)\n })\n })\n})\n"],"names":["describe","expect","it","deriveEncryptionKey","key1","key2","key3","toBe","key","length","Buffer","from","not","toThrow","buffer","startTime","Date","now","i","endTime","avgTime","toBeLessThan"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,mBAAmB,QAAQ,oBAAmB;AAEvDH,SAAS,gBAAgB;IACvBA,SAAS,uBAAuB;QAC9BE,GAAG,gEAAgE;YACjE,MAAME,OAAOD;YACb,MAAME,OAAOF;YACb,MAAMG,OAAOH;YAEbF,OAAOG,MAAMG,IAAI,CAACF;YAClBJ,OAAOI,MAAME,IAAI,CAACD;QACpB;QAEAJ,GAAG,uDAAuD;YACxD,MAAMM,MAAML;YAEZ,2DAA2D;YAC3DF,OAAOO,IAAIC,MAAM,EAAEF,IAAI,CAAC;YAExB,mDAAmD;YACnDN,OAAO,IAAMS,OAAOC,IAAI,CAACH,KAAK,QAAQI,GAAG,CAACC,OAAO;YAEjD,MAAMC,SAASJ,OAAOC,IAAI,CAACH,KAAK;YAChCP,OAAOa,OAAOL,MAAM,EAAEF,IAAI,CAAC,KAAI,WAAW;QAC5C;QAEAL,GAAG,qDAAqD;YACtD,uEAAuE;YACvE,MAAMa,YAAYC,KAAKC,GAAG;YAE1B,oCAAoC;YACpC,IAAK,IAAIC,IAAI,GAAGA,IAAI,GAAGA,IAAK;gBAC1Bf;YACF;YAEA,MAAMgB,UAAUH,KAAKC,GAAG;YACxB,MAAMG,UAAU,AAACD,CAAAA,UAAUJ,SAAQ,IAAK;YAExC,uDAAuD;YACvDd,OAAOmB,SAASC,YAAY,CAAC;QAC/B;IACF;AACF"}
@@ -0,0 +1,41 @@
1
+ import type { JWTPayload } from './types.js';
2
+ /**
3
+ * JWT validation error with specific error codes
4
+ */
5
+ export declare class JWTValidationError extends Error {
6
+ code: 'EXPIRED' | 'JWKS_FETCH_FAILED' | 'MISSING_KID' | 'SIGNATURE_INVALID';
7
+ cause?: Error | undefined;
8
+ constructor(message: string, code: 'EXPIRED' | 'JWKS_FETCH_FAILED' | 'MISSING_KID' | 'SIGNATURE_INVALID', cause?: Error | undefined);
9
+ }
10
+ /**
11
+ * Validate a project token JWT
12
+ *
13
+ * This function:
14
+ * 1. Gets the JWKS resolver for the environment (cached)
15
+ * 2. Uses jose's jwtVerify to validate signature and claims in one call
16
+ * 3. Maps jose errors to our custom error types
17
+ *
18
+ * Jose automatically:
19
+ * - Fetches JWKS from the URI (with caching)
20
+ * - Matches the key by kid from the JWT header
21
+ * - Verifies the signature using the correct algorithm
22
+ * - Validates standard claims (exp, iat, nbf)
23
+ * - Handles clock skew tolerance
24
+ *
25
+ * @param token - JWT token string
26
+ * @param environment - Figma environment ('production' or 'staging')
27
+ * @returns Promise resolving to validated JWT payload
28
+ * @throws {JWTValidationError} If validation fails
29
+ */
30
+ export declare function validateProjectToken(token: string, environment: 'production' | 'staging'): Promise<JWTPayload>;
31
+ /**
32
+ * Clear JWKS cache (useful for testing or forcing refresh)
33
+ *
34
+ * This clears the cached JWKS resolvers, forcing fresh fetches
35
+ * on the next validation. Useful for:
36
+ * - Testing with different JWKS
37
+ * - Forcing key rotation updates
38
+ * - Clearing state between test runs
39
+ */
40
+ export declare function clearJWKSCache(): void;
41
+ //# sourceMappingURL=jwt-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-validator.d.ts","sourceRoot":"","sources":["../../src/auth/jwt-validator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAI5C;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAGlC,IAAI,EAAE,SAAS,GAAG,mBAAmB,GAAG,aAAa,GAAG,mBAAmB;IAC3E,KAAK,CAAC,EAAE,KAAK;gBAFpB,OAAO,EAAE,MAAM,EACR,IAAI,EAAE,SAAS,GAAG,mBAAmB,GAAG,aAAa,GAAG,mBAAmB,EAC3E,KAAK,CAAC,EAAE,KAAK,YAAA;CAKvB;AAqCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,YAAY,GAAG,SAAS,GACpC,OAAO,CAAC,UAAU,CAAC,CA8ErB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC"}