@payloadcms/figma 0.0.1-alpha.62 → 0.0.1-alpha.64

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 (42) hide show
  1. package/dist/auth/crypto-utils.d.ts +11 -0
  2. package/dist/auth/crypto-utils.js +51 -0
  3. package/dist/auth/oauth-flow.js +4 -3
  4. package/dist/auth/project-token.d.ts +32 -16
  5. package/dist/auth/project-token.js +165 -64
  6. package/dist/auth/token-store-migration.d.ts +58 -0
  7. package/dist/auth/token-store-migration.js +156 -0
  8. package/dist/auth/token-store.d.ts +51 -128
  9. package/dist/auth/token-store.js +326 -187
  10. package/dist/auth/types.d.ts +12 -1
  11. package/dist/cli.js +15 -0
  12. package/dist/commands/bootstrap.d.ts +18 -0
  13. package/dist/commands/bootstrap.js +90 -0
  14. package/dist/commands/debug.js +16 -12
  15. package/dist/commands/dump-tokens.d.ts +12 -0
  16. package/dist/commands/dump-tokens.js +69 -0
  17. package/dist/commands/init.js +39 -5
  18. package/dist/commands/list-tokens.js +2 -2
  19. package/dist/commands/login.js +1 -1
  20. package/dist/commands/logout.js +23 -4
  21. package/dist/db-content-api/index.js +27 -73
  22. package/dist/db-content-api/utilities/auth.js +4 -1
  23. package/dist/oauth/defaults.d.ts +3 -1
  24. package/dist/oauth/defaults.js +6 -5
  25. package/dist/oauth/endpoints/getLoginEndpoint.js +1 -1
  26. package/dist/oauth/index.js +1 -0
  27. package/dist/oauth/types.d.ts +10 -0
  28. package/dist/plugin/auth-preflight.d.ts +8 -0
  29. package/dist/plugin/auth-preflight.js +20 -0
  30. package/dist/plugin/bootstrap-preflight.d.ts +23 -0
  31. package/dist/plugin/bootstrap-preflight.js +45 -0
  32. package/dist/plugin/build-config.js +135 -58
  33. package/dist/plugin/dev-cookie-names.d.ts +14 -0
  34. package/dist/plugin/dev-cookie-names.js +19 -0
  35. package/dist/storage-content-api/client.js +4 -1
  36. package/dist/utils/messages.js +7 -0
  37. package/dist/utils/payload-config-modifier.js +96 -107
  38. package/dist/utils/payload-package-check.d.ts +21 -1
  39. package/dist/utils/payload-package-check.js +66 -26
  40. package/dist/utils/token-display.d.ts +5 -1
  41. package/dist/utils/token-display.js +47 -26
  42. package/package.json +2 -1
@@ -0,0 +1,156 @@
1
+ import Conf from 'conf';
2
+ import fsSync from 'node:fs';
3
+ import path from 'node:path';
4
+ import * as log from '../utils/log.js';
5
+ const OAUTH_STORE_SCHEMA_VERSION = 1;
6
+ const PROJECT_STORE_SCHEMA_VERSION = 1;
7
+ /**
8
+ * Check if migration is needed.
9
+ * Returns true only when legacy file exists AND new oauth file does not.
10
+ */ export function needsMigration(params) {
11
+ const checkExists = params.checkFileExists ?? fsSync.existsSync;
12
+ const newExists = checkExists(params.newOAuthFilePath);
13
+ if (newExists) {
14
+ return false;
15
+ }
16
+ return checkExists(params.legacyFilePath);
17
+ }
18
+ /**
19
+ * Migrate OAuth tokens from legacy store to new oauth store.
20
+ * Reads from legacy location, writes to new location with root-level keys.
21
+ *
22
+ * `clearInvalidConfig` is deliberately OFF so a transient decrypt error does
23
+ * not wipe the legacy file — older CLI versions elsewhere on disk still read
24
+ * it. If we cannot open the file, we log and skip migration.
25
+ */ export function migrateOAuth(params) {
26
+ const legacyConfig = openLegacyConfig({
27
+ encryptionKey: params.encryptionKey,
28
+ legacyConfigName: params.legacyConfigName,
29
+ legacyCwd: params.legacyCwd,
30
+ projectName: params.projectName
31
+ });
32
+ if (!legacyConfig) {
33
+ return;
34
+ }
35
+ const legacyTokens = safeGet(legacyConfig, 'tokens');
36
+ if (!legacyTokens) {
37
+ return;
38
+ }
39
+ // Ensure target directory exists
40
+ if (!fsSync.existsSync(params.newOAuthCwd)) {
41
+ fsSync.mkdirSync(params.newOAuthCwd, {
42
+ recursive: true
43
+ });
44
+ }
45
+ const newOAuthConfig = new Conf({
46
+ configName: params.newOAuthConfigName,
47
+ cwd: params.newOAuthCwd,
48
+ encryptionKey: params.encryptionKey,
49
+ projectName: params.projectName
50
+ });
51
+ // Write root-level keys
52
+ newOAuthConfig.set('_schemaVersion', OAUTH_STORE_SCHEMA_VERSION);
53
+ newOAuthConfig.set('accessToken', legacyTokens.accessToken);
54
+ newOAuthConfig.set('expiresAt', legacyTokens.expiresAt);
55
+ newOAuthConfig.set('refreshToken', legacyTokens.refreshToken);
56
+ if (legacyTokens.scopes) {
57
+ newOAuthConfig.set('scopes', legacyTokens.scopes);
58
+ }
59
+ if (legacyTokens.tokenType) {
60
+ newOAuthConfig.set('tokenType', legacyTokens.tokenType);
61
+ }
62
+ if (legacyTokens.userId) {
63
+ newOAuthConfig.set('userId', legacyTokens.userId);
64
+ }
65
+ // Leave the legacy file untouched so older CLI versions installed in other
66
+ // projects on the same machine can keep reading their last known state.
67
+ }
68
+ /**
69
+ * Migrate per-project bootstrap data and project tokens from the legacy
70
+ * combined store into individual per-project stores.
71
+ *
72
+ * Walks legacy `bootstrapData` (keyed by `projectId:environmentName`), writes
73
+ * each entry into its own project store file, and stashes the matching
74
+ * `projectToken` (looked up by `bootstrapData.contentSystemId`) alongside it.
75
+ * Target files that already exist are left alone.
76
+ */ export function migrateProjectData(params) {
77
+ const legacyConfig = openLegacyConfig({
78
+ encryptionKey: params.encryptionKey,
79
+ legacyConfigName: params.legacyConfigName,
80
+ legacyCwd: params.legacyCwd,
81
+ projectName: params.projectName
82
+ });
83
+ if (!legacyConfig) {
84
+ return;
85
+ }
86
+ const bootstrapData = safeGet(legacyConfig, 'bootstrapData') || {};
87
+ const projectTokens = safeGet(legacyConfig, 'projectTokens') || {};
88
+ if (Object.keys(bootstrapData).length === 0 && Object.keys(projectTokens).length === 0) {
89
+ return;
90
+ }
91
+ for (const [key, bootstrap] of Object.entries(bootstrapData)){
92
+ const separatorIdx = key.indexOf(':');
93
+ if (separatorIdx === -1) {
94
+ continue;
95
+ }
96
+ const projectId = key.slice(0, separatorIdx);
97
+ const environmentName = key.slice(separatorIdx + 1);
98
+ if (!projectId || !environmentName) {
99
+ continue;
100
+ }
101
+ const { configName, cwd } = params.resolveProjectStorePath({
102
+ environmentName,
103
+ projectId
104
+ });
105
+ const targetPath = path.join(cwd, `${configName}.json`);
106
+ if (fsSync.existsSync(targetPath)) {
107
+ continue;
108
+ }
109
+ if (!fsSync.existsSync(cwd)) {
110
+ fsSync.mkdirSync(cwd, {
111
+ recursive: true
112
+ });
113
+ }
114
+ const projectConfig = new Conf({
115
+ configName,
116
+ cwd,
117
+ encryptionKey: params.encryptionKey,
118
+ projectName: params.projectName
119
+ });
120
+ projectConfig.set('_schemaVersion', PROJECT_STORE_SCHEMA_VERSION);
121
+ projectConfig.set('bootstrapData', bootstrap);
122
+ const legacyProjectToken = bootstrap.contentSystemId ? projectTokens[bootstrap.contentSystemId] : undefined;
123
+ if (legacyProjectToken) {
124
+ projectConfig.set('projectToken', legacyProjectToken);
125
+ }
126
+ }
127
+ // Leave the legacy file untouched so older CLI versions installed in other
128
+ // projects on the same machine can keep reading their last known state.
129
+ }
130
+ function safeGet(config, key) {
131
+ try {
132
+ return config.get(key);
133
+ } catch (error) {
134
+ log.debug(`Migration read failed (key: ${String(key)}). ` + `File: ${config.path}. ` + `Error: ${error instanceof Error ? error.message : 'Unknown'}`);
135
+ return undefined;
136
+ }
137
+ }
138
+ /**
139
+ * Open the legacy Conf store without `clearInvalidConfig`, so a decrypt
140
+ * error does not destroy the file. Returns null when the file cannot be
141
+ * opened — callers treat that as "nothing to migrate, leave file alone".
142
+ */ function openLegacyConfig(params) {
143
+ try {
144
+ return new Conf({
145
+ configName: params.legacyConfigName,
146
+ cwd: params.legacyCwd,
147
+ encryptionKey: params.encryptionKey,
148
+ projectName: params.projectName
149
+ });
150
+ } catch (error) {
151
+ log.debug(`Legacy token store could not be opened; skipping migration and leaving file intact. ` + `Error: ${error instanceof Error ? error.message : 'Unknown'}`);
152
+ return null;
153
+ }
154
+ }
155
+
156
+ //# sourceMappingURL=token-store-migration.js.map
@@ -1,151 +1,74 @@
1
1
  import type { Environment } from '../constants.js';
2
- import type { BootstrapData, FigmaTokens, JWTPayload, ProjectToken, TokenStoreConfig } from './types.js';
2
+ import type { BootstrapData, FigmaTokens, ProjectStoreScope, ProjectToken, TokenStoreConfig } from './types.js';
3
3
  /**
4
- * Get the singleton TokenStore instance for an environment
5
- * Use this for all production code to ensure a single shared instance per environment
6
- *
7
- * @param environment - The environment to get the token store for
8
- * @returns The singleton TokenStore instance for that environment
4
+ * Get the singleton TokenStore instance for an environment.
9
5
  */
10
6
  export declare function getTokenStore(environment?: Environment): TokenStore;
11
7
  /**
12
- * Secure storage manager for Figma OAuth2 tokens
13
- *
14
- * Uses the `conf` library to store tokens in an OS-specific secure location:
15
- * - macOS: ~/Library/Preferences/payloadcms-figma
16
- * - Linux: ~/.config/payloadcms-figma or $XDG_CONFIG_HOME/payloadcms-figma
17
- * - Windows: %APPDATA%/payloadcms-figma/Config
18
- *
19
- * Tokens are encrypted at rest with a machine-specific key and file permissions
20
- * are set to 0600 (owner read/write only)
8
+ * Secure storage manager for OAuth tokens and project-scoped bootstrap/token data.
21
9
  *
22
10
  * For production code, use getTokenStore() to get the singleton instance.
23
11
  * The constructor is still exported for test isolation.
24
12
  */
25
13
  export declare class TokenStore {
26
- private config;
14
+ private baseConfigName;
15
+ private encryptionKey;
27
16
  private environment;
17
+ private legacyRootDir;
18
+ private oauthConfig;
19
+ private projectName;
20
+ private projectStoreConfigs;
28
21
  constructor(options?: TokenStoreConfig);
29
- private safeGet;
30
- /**
31
- * Retrieve stored tokens
32
- * @returns FigmaTokens if stored, null otherwise
33
- */
34
- getTokens(): FigmaTokens | null;
35
- /**
36
- * Store OAuth2 tokens securely
37
- * @param tokens - Figma OAuth2 tokens to store
38
- */
22
+ getOauthInfo(): FigmaTokens | null;
39
23
  setTokens(tokens: FigmaTokens): void;
40
- /**
41
- * Clear all stored tokens (used for logout)
42
- */
43
24
  clearTokens(): void;
44
- /**
45
- * Check if valid (non-expired) tokens exist
46
- * @returns true if tokens exist and are not expired
47
- */
48
- hasValidTokens(): boolean;
49
- /**
50
- * Check if the current access token is expired
51
- * Includes a buffer time to avoid using tokens that are about to expire
52
- * @returns true if token is expired or will expire within buffer time
53
- */
54
- isExpired(): boolean;
55
- /**
56
- * Get the access token if it exists and is valid
57
- * @returns Access token string or null if expired/missing
58
- */
59
- getAccessToken(): null | string;
60
- /**
61
- * Get the refresh token if it exists
62
- * @returns Refresh token string or null if missing
63
- */
64
- getRefreshToken(): null | string;
65
- /**
66
- * Update just the access token after a refresh
67
- * Preserves the existing refresh token and other metadata
68
- * @param accessToken - New access token
69
- * @param expiresIn - Expiration time in seconds
70
- */
25
+ hasValidOauthToken(): boolean;
26
+ isOauthExpired(): boolean;
27
+ getCurrentOauthToken(): null | string;
28
+ getCurrentRefreshToken(): null | string;
71
29
  updateAccessToken(accessToken: string, expiresIn: number): void;
72
30
  /**
73
- * Get the path to the config file (useful for debugging)
74
- * @returns Absolute path to the token storage file
31
+ * Returns the OAuth storage file path.
75
32
  */
76
33
  getStoragePath(): string;
77
- /**
78
- * Retrieve a project token for a specific tenant
79
- * Automatically removes expired tokens from storage
80
- * @param tenantId - The tenant/CMS ID
81
- * @returns ProjectToken if stored and valid, null otherwise
82
- */
83
- getProjectToken(tenantId: string): null | ProjectToken;
84
- /**
85
- * Store a project token for a specific tenant
86
- * @param tenantId - The tenant/CMS ID
87
- * @param projectToken - The project token to store
88
- */
89
- setProjectToken(tenantId: string, projectToken: ProjectToken): void;
90
- /**
91
- * Clear a project token for a specific tenant
92
- * @param tenantId - The tenant/CMS ID
93
- */
94
- clearProjectToken(tenantId: string): void;
95
- /**
96
- * Clear all project tokens
97
- */
98
- clearAllProjectTokens(): void;
99
- /**
100
- * Check if a project token is expired
101
- * Includes a buffer time to avoid using tokens that are about to expire
102
- * @param projectToken - The project token to check
103
- * @returns true if token is expired or will expire within buffer time
104
- */
105
- isProjectTokenExpired(projectToken: ProjectToken): boolean;
106
- /**
107
- * Check if a valid (non-expired) project token exists for a tenant
108
- * @param tenantId - The tenant/CMS ID
109
- * @returns true if token exists and is not expired
110
- */
111
- hasValidProjectToken(tenantId: string): boolean;
112
- /**
113
- * Get validated JWT claims from a project token
114
- * @param tenantId - The tenant/CMS ID
115
- * @returns JWT payload claims or null if token not found or has no claims
116
- */
117
- getProjectTokenClaims(tenantId: string): JWTPayload | null;
118
- /**
119
- * Get all tenant IDs that have stored project tokens
120
- * @returns Array of tenant IDs
121
- */
122
- getAllProjectTokenTenantIds(): string[];
123
- /**
124
- * Build the composite key for bootstrap data storage
125
- * @param projectId - The CMS resource/project ID
126
- * @param environmentName - The environment name (e.g. 'production', 'staging')
127
- * @returns Composite key string
128
- */
129
- private bootstrapKey;
130
- /**
131
- * Retrieve bootstrap data for a project + environment
132
- * @param projectId - The CMS resource/project ID
133
- * @param environmentName - The environment name
134
- * @returns BootstrapData if stored, null otherwise
135
- */
34
+ getProjectToken({ projectInfo, }?: {
35
+ projectInfo?: ProjectStoreScope;
36
+ }): null | ProjectToken;
37
+ setProjectToken({ projectInfo, token, }: {
38
+ projectInfo?: ProjectStoreScope;
39
+ token: ProjectToken;
40
+ }): void;
41
+ clearProjectToken({ projectInfo }?: {
42
+ projectInfo?: ProjectStoreScope;
43
+ }): void;
44
+ isProjectTokenExpired(token: ProjectToken): boolean;
45
+ hasValidProjectToken({ projectInfo }?: {
46
+ projectInfo?: ProjectStoreScope;
47
+ }): boolean;
48
+ /**
49
+ * Get the tenantId (contentSystemId) for the project scope.
50
+ * Returns null if bootstrap data is not available.
51
+ */
52
+ getTenantId({ projectInfo }?: {
53
+ projectInfo?: ProjectStoreScope;
54
+ }): null | string;
55
+ /**
56
+ * Derive project info from environment variables.
57
+ * Returns null if either FIGMA_PROJECT_ID or FIGMA_ENVIRONMENT_NAME is not set.
58
+ */
59
+ private getProjectInfoFromEnv;
136
60
  getBootstrapData(projectId: string, environmentName: string): BootstrapData | null;
137
- /**
138
- * Store bootstrap data for a project + environment
139
- * @param projectId - The CMS resource/project ID
140
- * @param environmentName - The environment name
141
- * @param data - The bootstrap data to store
142
- */
143
61
  setBootstrapData(projectId: string, environmentName: string, data: BootstrapData): void;
144
- /**
145
- * Clear bootstrap data for a project + environment
146
- * @param projectId - The CMS resource/project ID
147
- * @param environmentName - The environment name
148
- */
149
62
  clearBootstrapData(projectId: string, environmentName: string): void;
63
+ private buildOAuthConfigName;
64
+ private buildOAuthStoreCwd;
65
+ private buildProjectStoreConfigName;
66
+ private buildProjectStoreCwd;
67
+ private sanitizeConfigSegment;
68
+ private projectInfoKey;
69
+ private getLegacyRootDir;
70
+ private getProjectStore;
71
+ private createConfig;
72
+ private safeGet;
150
73
  }
151
74
  //# sourceMappingURL=token-store.d.ts.map