@payloadcms/figma 0.0.1-alpha.45 → 0.0.1-alpha.46
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.
- package/dist/api/figma-api.d.ts +20 -0
- package/dist/api/figma-api.d.ts.map +1 -1
- package/dist/api/figma-api.js +34 -0
- package/dist/api/figma-api.js.map +1 -1
- package/dist/exports/client.d.ts +2 -0
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +2 -0
- package/dist/exports/client.js.map +1 -1
- package/dist/oauth/components/FigmaAvatar/index.d.ts +4 -0
- package/dist/oauth/components/FigmaAvatar/index.d.ts.map +1 -0
- package/dist/oauth/components/FigmaAvatar/index.js +27 -0
- package/dist/oauth/components/FigmaAvatar/index.js.map +1 -0
- package/dist/oauth/components/FigmaAvatar/index.scss +12 -0
- package/dist/oauth/components/FigmaAvatarCell/index.d.ts +5 -0
- package/dist/oauth/components/FigmaAvatarCell/index.d.ts.map +1 -0
- package/dist/oauth/components/FigmaAvatarCell/index.js +25 -0
- package/dist/oauth/components/FigmaAvatarCell/index.js.map +1 -0
- package/dist/oauth/components/FigmaAvatarCell/index.scss +5 -0
- package/dist/oauth/components/FigmaAvatarPlaceholder/index.d.ts +9 -0
- package/dist/oauth/components/FigmaAvatarPlaceholder/index.d.ts.map +1 -0
- package/dist/oauth/components/FigmaAvatarPlaceholder/index.js +37 -0
- package/dist/oauth/components/FigmaAvatarPlaceholder/index.js.map +1 -0
- package/dist/oauth/components/FigmaAvatarPlaceholder/index.scss +28 -0
- package/dist/oauth/defaults.d.ts.map +1 -1
- package/dist/oauth/defaults.js +38 -5
- package/dist/oauth/defaults.js.map +1 -1
- package/dist/oauth/endpoints/getLoginEndpoint.d.ts.map +1 -1
- package/dist/oauth/endpoints/getLoginEndpoint.js +31 -1
- package/dist/oauth/endpoints/getLoginEndpoint.js.map +1 -1
- package/dist/plugin/build-config.d.ts.map +1 -1
- package/dist/plugin/build-config.js +44 -2
- package/dist/plugin/build-config.js.map +1 -1
- package/package.json +1 -1
package/dist/api/figma-api.d.ts
CHANGED
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
* Handles project token generation and other CMS-related API calls
|
|
6
6
|
*/
|
|
7
7
|
import type { ProjectToken } from '../auth/types.js';
|
|
8
|
+
/**
|
|
9
|
+
* User info from Figma API /v1/me endpoint
|
|
10
|
+
*/
|
|
11
|
+
export type FigmaUserInfo = {
|
|
12
|
+
email: string;
|
|
13
|
+
handle: string;
|
|
14
|
+
id: string;
|
|
15
|
+
img_url: string;
|
|
16
|
+
};
|
|
8
17
|
/**
|
|
9
18
|
* Error thrown when Figma API calls fail
|
|
10
19
|
*/
|
|
@@ -28,4 +37,15 @@ export declare class FigmaApiError extends Error {
|
|
|
28
37
|
* @throws {FigmaApiError} If the API call fails
|
|
29
38
|
*/
|
|
30
39
|
export declare function getProjectToken(accessToken: string, tenantId: string): Promise<ProjectToken>;
|
|
40
|
+
/**
|
|
41
|
+
* Get user info from Figma API
|
|
42
|
+
*
|
|
43
|
+
* Endpoint: GET https://api.figma.com/v1/me
|
|
44
|
+
* Requires OAuth access_token (not project token)
|
|
45
|
+
*
|
|
46
|
+
* @param accessToken - OAuth access token for authentication
|
|
47
|
+
* @returns FigmaUserInfo with user profile data
|
|
48
|
+
* @throws {FigmaApiError} If the API call fails
|
|
49
|
+
*/
|
|
50
|
+
export declare function getUserInfo(accessToken: string): Promise<FigmaUserInfo>;
|
|
31
51
|
//# sourceMappingURL=figma-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figma-api.d.ts","sourceRoot":"","sources":["../../src/api/figma-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAwB,MAAM,kBAAkB,CAAA;AAK1E;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAG7B,UAAU,CAAC,EAAE,MAAM;IACnB,KAAK,CAAC,EAAE,KAAK;gBAFpB,OAAO,EAAE,MAAM,EACR,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,KAAK,CAAC,EAAE,KAAK,YAAA;CAKvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAwIvB"}
|
|
1
|
+
{"version":3,"file":"figma-api.d.ts","sourceRoot":"","sources":["../../src/api/figma-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAwB,MAAM,kBAAkB,CAAA;AAK1E;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAG7B,UAAU,CAAC,EAAE,MAAM;IACnB,KAAK,CAAC,EAAE,KAAK;gBAFpB,OAAO,EAAE,MAAM,EACR,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,KAAK,CAAC,EAAE,KAAK,YAAA;CAKvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAwIvB;AAgFD;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqC7E"}
|
package/dist/api/figma-api.js
CHANGED
|
@@ -189,5 +189,39 @@ import * as log from '../utils/log.js';
|
|
|
189
189
|
throw new FigmaApiError(`Failed to parse JWT expiry: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, error instanceof Error ? error : undefined);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Get user info from Figma API
|
|
194
|
+
*
|
|
195
|
+
* Endpoint: GET https://api.figma.com/v1/me
|
|
196
|
+
* Requires OAuth access_token (not project token)
|
|
197
|
+
*
|
|
198
|
+
* @param accessToken - OAuth access token for authentication
|
|
199
|
+
* @returns FigmaUserInfo with user profile data
|
|
200
|
+
* @throws {FigmaApiError} If the API call fails
|
|
201
|
+
*/ export async function getUserInfo(accessToken) {
|
|
202
|
+
const env = getEnvironment();
|
|
203
|
+
const baseUrl = process.env.FIGMA_API_BASE_URL || ENV_CONFIG[env].apiBaseUrl;
|
|
204
|
+
const url = `${baseUrl}/v1/me`;
|
|
205
|
+
try {
|
|
206
|
+
const response = await fetch(url, {
|
|
207
|
+
headers: {
|
|
208
|
+
Authorization: `Bearer ${accessToken}`
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
if (!response.ok) {
|
|
212
|
+
throw new FigmaApiError(`Failed to get user info: ${response.status} ${response.statusText}`, response.status);
|
|
213
|
+
}
|
|
214
|
+
const data = await response.json();
|
|
215
|
+
if (!data.id || !data.email || typeof data.handle !== 'string') {
|
|
216
|
+
throw new FigmaApiError('Invalid user info response: missing required fields');
|
|
217
|
+
}
|
|
218
|
+
return data;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error instanceof FigmaApiError) {
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
throw new FigmaApiError(`Failed to get user info: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, error instanceof Error ? error : undefined);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
192
226
|
|
|
193
227
|
//# sourceMappingURL=figma-api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/figma-api.ts"],"sourcesContent":["/**\n * Figma API Client\n *\n * Client for Figma REST API (Pixie) endpoints\n * Handles project token generation and other CMS-related API calls\n */\n\nimport type { ProjectToken, ProjectTokenResponse } from '../auth/types.js'\n\nimport { ENV_CONFIG, getEnvironment } from '../constants.js'\nimport * as log from '../utils/log.js'\n\n/**\n * Error thrown when Figma API calls fail\n */\nexport class FigmaApiError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public cause?: Error,\n ) {\n super(message)\n this.name = 'FigmaApiError'\n }\n}\n\n/**\n * Get a project token (JWT) for a specific tenant/CMS\n *\n * This token is used by local Payload instances to authenticate\n * requests to the Content API. The token contains the CMS ID and\n * user identifying information in its claims.\n *\n * Endpoint: GET https://api.figma.com/v1/cms/:id/token/\n *\n * @param accessToken - OAuth access token for authentication\n * @param tenantId - The tenant/CMS ID\n * @returns ProjectToken with JWT and expiry information\n * @throws {FigmaApiError} If the API call fails\n */\nexport async function getProjectToken(\n accessToken: string,\n tenantId: string,\n): Promise<ProjectToken> {\n // Priority 1: Check for direct token value from environment\n const envTokenValue = process.env.FIGMA_MOCK_PROJECT_TOKEN_VALUE\n if (envTokenValue) {\n log.debug('Using direct project token from FIGMA_MOCK_PROJECT_TOKEN_VALUE')\n try {\n // Parse JWT payload to extract claims\n const parts = envTokenValue.split('.')\n if (parts.length !== 3) {\n throw new Error('Invalid JWT format: must have 3 parts (header.payload.signature)')\n }\n\n const payload = parts[1]\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')\n const paddedBase64 = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')\n const decodedPayload = JSON.parse(Buffer.from(paddedBase64, 'base64').toString('utf-8')) as {\n aud?: string\n exp?: number\n }\n\n if (!decodedPayload.exp || typeof decodedPayload.exp !== 'number') {\n throw new Error('JWT payload missing exp claim')\n }\n\n // Extract tenant ID from aud claim (use parameter as fallback)\n const extractedTenantId = decodedPayload.aud || tenantId\n\n return {\n expiresAt: decodedPayload.exp * 1000, // Convert from seconds to milliseconds\n tenantId: extractedTenantId,\n token: envTokenValue,\n }\n } catch (error) {\n throw new FigmaApiError(\n `Invalid JWT token in FIGMA_MOCK_PROJECT_TOKEN_VALUE: ${error instanceof Error ? error.message : 'Unknown error'}`,\n undefined,\n error instanceof Error ? error : undefined,\n )\n }\n }\n\n // Priority 2: Check if project token API should be mocked\n // Default to false (use real API)\n const shouldMock = process.env.FIGMA_MOCK_PROJECT_TOKEN === 'true'\n\n try {\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Generate a mock JWT token for testing\n log.debug(\n 'Using mock project token API for getProjectToken (FIGMA_MOCK_PROJECT_TOKEN = \"true\")',\n )\n const mockToken = createMockJWT(tenantId, 25) // 25 minutes expiry\n\n // Mock response data\n const data: ProjectTokenResponse = {\n meta: {\n token: mockToken,\n },\n }\n\n if (!data.meta?.token) {\n throw new FigmaApiError('Invalid response: missing token in meta')\n }\n\n // Parse the JWT to extract expiry (it's in the payload)\n const expiresAt = parseJwtExpiry(data.meta.token)\n\n return {\n expiresAt,\n tenantId,\n token: data.meta.token,\n }\n }\n\n // REAL API IMPLEMENTATION\n const env = getEnvironment()\n const baseUrl = process.env.FIGMA_API_BASE_URL || ENV_CONFIG[env].apiBaseUrl\n const url = `${baseUrl}/v1/cms/${tenantId}/token/`\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n // Handle error responses\n let errorMessage = `Failed to get project token: ${response.status} ${response.statusText}`\n\n try {\n const errorBody = await response.json()\n // Only use string error messages (API may return { error: true })\n if (typeof errorBody.message === 'string') {\n errorMessage = errorBody.message\n } else if (typeof errorBody.error === 'string') {\n errorMessage = errorBody.error\n }\n } catch {\n // Ignore JSON parse errors for error body\n }\n\n // Provide actionable error for common cases\n if (response.status === 404) {\n errorMessage = `Content System ID \"${tenantId}\" not found. Ensure FIGMA_CONTENT_SYSTEM_ID is set to a valid content system ID in your .env file.`\n }\n\n throw new FigmaApiError(errorMessage, response.status)\n }\n\n const data: ProjectTokenResponse = await response.json()\n\n if (!data.meta?.token) {\n throw new FigmaApiError('Invalid response: missing token in meta')\n }\n\n // Parse the JWT to extract expiry (it's in the payload)\n const expiresAt = parseJwtExpiry(data.meta.token)\n\n return {\n expiresAt,\n tenantId,\n token: data.meta.token,\n }\n } catch (error) {\n if (error instanceof FigmaApiError) {\n throw error\n }\n\n // Handle network errors and other unexpected errors\n throw new FigmaApiError(\n `Failed to get project token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n undefined,\n error instanceof Error ? error : undefined,\n )\n }\n}\n\n/**\n * Create a mock JWT token for testing\n * TODO: Remove this when real API is available\n */\nfunction createMockJWT(tenantId: string, expiresInMinutes: number): string {\n const header = {\n alg: 'RS256',\n typ: 'JWT',\n }\n\n const now = Math.floor(Date.now() / 1000) // Current time in seconds\n const exp = now + expiresInMinutes * 60 // Expiry in seconds\n\n const payload = {\n aud: tenantId, // Audience claim contains the CMS ID\n email: 'developer@example.com',\n exp,\n iat: now,\n sub: 'user123',\n }\n\n // Base64url encode\n const encodeBase64Url = (obj: object): string => {\n return Buffer.from(JSON.stringify(obj))\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n }\n\n const headerEncoded = encodeBase64Url(header)\n const payloadEncoded = encodeBase64Url(payload)\n const signature = 'mock_signature_' + Math.random().toString(36).substring(2, 15)\n\n return `${headerEncoded}.${payloadEncoded}.${signature}`\n}\n\n/**\n * Parse JWT expiry from token\n * JWTs contain an 'exp' claim in the payload (second part)\n *\n * @param token - JWT token string\n * @returns Unix timestamp in milliseconds when token expires\n * @throws {FigmaApiError} If JWT cannot be parsed\n */\nfunction parseJwtExpiry(token: string): number {\n try {\n // JWT format: header.payload.signature\n const parts = token.split('.')\n if (parts.length !== 3) {\n throw new Error('Invalid JWT format')\n }\n\n // Decode the payload (second part)\n const payload = parts[1]\n // Use atob for base64url decoding (handle padding and URL-safe characters)\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')\n const paddedBase64 = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')\n\n const decodedPayload = JSON.parse(Buffer.from(paddedBase64, 'base64').toString('utf-8')) as {\n exp?: number\n }\n\n if (!decodedPayload.exp || typeof decodedPayload.exp !== 'number') {\n throw new Error('JWT payload missing exp claim')\n }\n\n // JWT exp claim is in seconds, convert to milliseconds\n return decodedPayload.exp * 1000\n } catch (error) {\n throw new FigmaApiError(\n `Failed to parse JWT expiry: ${error instanceof Error ? error.message : 'Unknown error'}`,\n undefined,\n error instanceof Error ? error : undefined,\n )\n }\n}\n"],"names":["ENV_CONFIG","getEnvironment","log","FigmaApiError","Error","message","statusCode","cause","name","getProjectToken","accessToken","tenantId","envTokenValue","process","env","FIGMA_MOCK_PROJECT_TOKEN_VALUE","debug","parts","split","length","payload","base64","replace","paddedBase64","padEnd","decodedPayload","JSON","parse","Buffer","from","toString","exp","extractedTenantId","aud","expiresAt","token","error","undefined","shouldMock","FIGMA_MOCK_PROJECT_TOKEN","mockToken","createMockJWT","data","meta","parseJwtExpiry","baseUrl","FIGMA_API_BASE_URL","apiBaseUrl","url","response","fetch","headers","Authorization","method","ok","errorMessage","status","statusText","errorBody","json","expiresInMinutes","header","alg","typ","now","Math","floor","Date","email","iat","sub","encodeBase64Url","obj","stringify","headerEncoded","payloadEncoded","signature","random","substring"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/api/figma-api.ts"],"sourcesContent":["/**\n * Figma API Client\n *\n * Client for Figma REST API (Pixie) endpoints\n * Handles project token generation and other CMS-related API calls\n */\n\nimport type { ProjectToken, ProjectTokenResponse } from '../auth/types.js'\n\nimport { ENV_CONFIG, getEnvironment } from '../constants.js'\nimport * as log from '../utils/log.js'\n\n/**\n * User info from Figma API /v1/me endpoint\n */\nexport type FigmaUserInfo = {\n email: string\n handle: string\n id: string\n img_url: string\n}\n\n/**\n * Error thrown when Figma API calls fail\n */\nexport class FigmaApiError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public cause?: Error,\n ) {\n super(message)\n this.name = 'FigmaApiError'\n }\n}\n\n/**\n * Get a project token (JWT) for a specific tenant/CMS\n *\n * This token is used by local Payload instances to authenticate\n * requests to the Content API. The token contains the CMS ID and\n * user identifying information in its claims.\n *\n * Endpoint: GET https://api.figma.com/v1/cms/:id/token/\n *\n * @param accessToken - OAuth access token for authentication\n * @param tenantId - The tenant/CMS ID\n * @returns ProjectToken with JWT and expiry information\n * @throws {FigmaApiError} If the API call fails\n */\nexport async function getProjectToken(\n accessToken: string,\n tenantId: string,\n): Promise<ProjectToken> {\n // Priority 1: Check for direct token value from environment\n const envTokenValue = process.env.FIGMA_MOCK_PROJECT_TOKEN_VALUE\n if (envTokenValue) {\n log.debug('Using direct project token from FIGMA_MOCK_PROJECT_TOKEN_VALUE')\n try {\n // Parse JWT payload to extract claims\n const parts = envTokenValue.split('.')\n if (parts.length !== 3) {\n throw new Error('Invalid JWT format: must have 3 parts (header.payload.signature)')\n }\n\n const payload = parts[1]\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')\n const paddedBase64 = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')\n const decodedPayload = JSON.parse(Buffer.from(paddedBase64, 'base64').toString('utf-8')) as {\n aud?: string\n exp?: number\n }\n\n if (!decodedPayload.exp || typeof decodedPayload.exp !== 'number') {\n throw new Error('JWT payload missing exp claim')\n }\n\n // Extract tenant ID from aud claim (use parameter as fallback)\n const extractedTenantId = decodedPayload.aud || tenantId\n\n return {\n expiresAt: decodedPayload.exp * 1000, // Convert from seconds to milliseconds\n tenantId: extractedTenantId,\n token: envTokenValue,\n }\n } catch (error) {\n throw new FigmaApiError(\n `Invalid JWT token in FIGMA_MOCK_PROJECT_TOKEN_VALUE: ${error instanceof Error ? error.message : 'Unknown error'}`,\n undefined,\n error instanceof Error ? error : undefined,\n )\n }\n }\n\n // Priority 2: Check if project token API should be mocked\n // Default to false (use real API)\n const shouldMock = process.env.FIGMA_MOCK_PROJECT_TOKEN === 'true'\n\n try {\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Generate a mock JWT token for testing\n log.debug(\n 'Using mock project token API for getProjectToken (FIGMA_MOCK_PROJECT_TOKEN = \"true\")',\n )\n const mockToken = createMockJWT(tenantId, 25) // 25 minutes expiry\n\n // Mock response data\n const data: ProjectTokenResponse = {\n meta: {\n token: mockToken,\n },\n }\n\n if (!data.meta?.token) {\n throw new FigmaApiError('Invalid response: missing token in meta')\n }\n\n // Parse the JWT to extract expiry (it's in the payload)\n const expiresAt = parseJwtExpiry(data.meta.token)\n\n return {\n expiresAt,\n tenantId,\n token: data.meta.token,\n }\n }\n\n // REAL API IMPLEMENTATION\n const env = getEnvironment()\n const baseUrl = process.env.FIGMA_API_BASE_URL || ENV_CONFIG[env].apiBaseUrl\n const url = `${baseUrl}/v1/cms/${tenantId}/token/`\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n // Handle error responses\n let errorMessage = `Failed to get project token: ${response.status} ${response.statusText}`\n\n try {\n const errorBody = await response.json()\n // Only use string error messages (API may return { error: true })\n if (typeof errorBody.message === 'string') {\n errorMessage = errorBody.message\n } else if (typeof errorBody.error === 'string') {\n errorMessage = errorBody.error\n }\n } catch {\n // Ignore JSON parse errors for error body\n }\n\n // Provide actionable error for common cases\n if (response.status === 404) {\n errorMessage = `Content System ID \"${tenantId}\" not found. Ensure FIGMA_CONTENT_SYSTEM_ID is set to a valid content system ID in your .env file.`\n }\n\n throw new FigmaApiError(errorMessage, response.status)\n }\n\n const data: ProjectTokenResponse = await response.json()\n\n if (!data.meta?.token) {\n throw new FigmaApiError('Invalid response: missing token in meta')\n }\n\n // Parse the JWT to extract expiry (it's in the payload)\n const expiresAt = parseJwtExpiry(data.meta.token)\n\n return {\n expiresAt,\n tenantId,\n token: data.meta.token,\n }\n } catch (error) {\n if (error instanceof FigmaApiError) {\n throw error\n }\n\n // Handle network errors and other unexpected errors\n throw new FigmaApiError(\n `Failed to get project token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n undefined,\n error instanceof Error ? error : undefined,\n )\n }\n}\n\n/**\n * Create a mock JWT token for testing\n * TODO: Remove this when real API is available\n */\nfunction createMockJWT(tenantId: string, expiresInMinutes: number): string {\n const header = {\n alg: 'RS256',\n typ: 'JWT',\n }\n\n const now = Math.floor(Date.now() / 1000) // Current time in seconds\n const exp = now + expiresInMinutes * 60 // Expiry in seconds\n\n const payload = {\n aud: tenantId, // Audience claim contains the CMS ID\n email: 'developer@example.com',\n exp,\n iat: now,\n sub: 'user123',\n }\n\n // Base64url encode\n const encodeBase64Url = (obj: object): string => {\n return Buffer.from(JSON.stringify(obj))\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n }\n\n const headerEncoded = encodeBase64Url(header)\n const payloadEncoded = encodeBase64Url(payload)\n const signature = 'mock_signature_' + Math.random().toString(36).substring(2, 15)\n\n return `${headerEncoded}.${payloadEncoded}.${signature}`\n}\n\n/**\n * Parse JWT expiry from token\n * JWTs contain an 'exp' claim in the payload (second part)\n *\n * @param token - JWT token string\n * @returns Unix timestamp in milliseconds when token expires\n * @throws {FigmaApiError} If JWT cannot be parsed\n */\nfunction parseJwtExpiry(token: string): number {\n try {\n // JWT format: header.payload.signature\n const parts = token.split('.')\n if (parts.length !== 3) {\n throw new Error('Invalid JWT format')\n }\n\n // Decode the payload (second part)\n const payload = parts[1]\n // Use atob for base64url decoding (handle padding and URL-safe characters)\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')\n const paddedBase64 = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')\n\n const decodedPayload = JSON.parse(Buffer.from(paddedBase64, 'base64').toString('utf-8')) as {\n exp?: number\n }\n\n if (!decodedPayload.exp || typeof decodedPayload.exp !== 'number') {\n throw new Error('JWT payload missing exp claim')\n }\n\n // JWT exp claim is in seconds, convert to milliseconds\n return decodedPayload.exp * 1000\n } catch (error) {\n throw new FigmaApiError(\n `Failed to parse JWT expiry: ${error instanceof Error ? error.message : 'Unknown error'}`,\n undefined,\n error instanceof Error ? error : undefined,\n )\n }\n}\n\n/**\n * Get user info from Figma API\n *\n * Endpoint: GET https://api.figma.com/v1/me\n * Requires OAuth access_token (not project token)\n *\n * @param accessToken - OAuth access token for authentication\n * @returns FigmaUserInfo with user profile data\n * @throws {FigmaApiError} If the API call fails\n */\nexport async function getUserInfo(accessToken: string): Promise<FigmaUserInfo> {\n const env = getEnvironment()\n const baseUrl = process.env.FIGMA_API_BASE_URL || ENV_CONFIG[env].apiBaseUrl\n const url = `${baseUrl}/v1/me`\n\n try {\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n })\n\n if (!response.ok) {\n throw new FigmaApiError(\n `Failed to get user info: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n const data = await response.json()\n\n if (!data.id || !data.email || typeof data.handle !== 'string') {\n throw new FigmaApiError('Invalid user info response: missing required fields')\n }\n\n return data as FigmaUserInfo\n } catch (error) {\n if (error instanceof FigmaApiError) {\n throw error\n }\n\n throw new FigmaApiError(\n `Failed to get user info: ${error instanceof Error ? error.message : 'Unknown error'}`,\n undefined,\n error instanceof Error ? error : undefined,\n )\n }\n}\n"],"names":["ENV_CONFIG","getEnvironment","log","FigmaApiError","Error","message","statusCode","cause","name","getProjectToken","accessToken","tenantId","envTokenValue","process","env","FIGMA_MOCK_PROJECT_TOKEN_VALUE","debug","parts","split","length","payload","base64","replace","paddedBase64","padEnd","decodedPayload","JSON","parse","Buffer","from","toString","exp","extractedTenantId","aud","expiresAt","token","error","undefined","shouldMock","FIGMA_MOCK_PROJECT_TOKEN","mockToken","createMockJWT","data","meta","parseJwtExpiry","baseUrl","FIGMA_API_BASE_URL","apiBaseUrl","url","response","fetch","headers","Authorization","method","ok","errorMessage","status","statusText","errorBody","json","expiresInMinutes","header","alg","typ","now","Math","floor","Date","email","iat","sub","encodeBase64Url","obj","stringify","headerEncoded","payloadEncoded","signature","random","substring","getUserInfo","id","handle"],"mappings":"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"}
|
package/dist/exports/client.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { FigmaAvatar } from '../oauth/components/FigmaAvatar/index.js';
|
|
2
|
+
export { FigmaAvatarCell } from '../oauth/components/FigmaAvatarCell/index.js';
|
|
1
3
|
export { DefaultLoginButton } from '../oauth/components/LoginButton/index.js';
|
|
2
4
|
export { LogoutButton } from '../oauth/components/LogoutButton/index.js';
|
|
3
5
|
export { ContentApiClientUploadHandler } from '../storage-content-api/client-uploads/ClientUploadHandler.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAA;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAA;AACxE,OAAO,EAAE,6BAA6B,EAAE,MAAM,8DAA8D,CAAA"}
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,8CAA8C,CAAA;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAA;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAA;AACxE,OAAO,EAAE,6BAA6B,EAAE,MAAM,8DAA8D,CAAA"}
|
package/dist/exports/client.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { FigmaAvatar } from '../oauth/components/FigmaAvatar/index.js';
|
|
2
|
+
export { FigmaAvatarCell } from '../oauth/components/FigmaAvatarCell/index.js';
|
|
1
3
|
export { DefaultLoginButton } from '../oauth/components/LoginButton/index.js';
|
|
2
4
|
export { LogoutButton } from '../oauth/components/LogoutButton/index.js';
|
|
3
5
|
export { ContentApiClientUploadHandler } from '../storage-content-api/client-uploads/ClientUploadHandler.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { DefaultLoginButton } from '../oauth/components/LoginButton/index.js'\nexport { LogoutButton } from '../oauth/components/LogoutButton/index.js'\nexport { ContentApiClientUploadHandler } from '../storage-content-api/client-uploads/ClientUploadHandler.js'\n"],"names":["DefaultLoginButton","LogoutButton","ContentApiClientUploadHandler"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,2CAA0C;AAC7E,SAASC,YAAY,QAAQ,4CAA2C;AACxE,SAASC,6BAA6B,QAAQ,+DAA8D"}
|
|
1
|
+
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { FigmaAvatar } from '../oauth/components/FigmaAvatar/index.js'\nexport { FigmaAvatarCell } from '../oauth/components/FigmaAvatarCell/index.js'\nexport { DefaultLoginButton } from '../oauth/components/LoginButton/index.js'\nexport { LogoutButton } from '../oauth/components/LogoutButton/index.js'\nexport { ContentApiClientUploadHandler } from '../storage-content-api/client-uploads/ClientUploadHandler.js'\n"],"names":["FigmaAvatar","FigmaAvatarCell","DefaultLoginButton","LogoutButton","ContentApiClientUploadHandler"],"mappings":"AAAA,SAASA,WAAW,QAAQ,2CAA0C;AACtE,SAASC,eAAe,QAAQ,+CAA8C;AAC9E,SAASC,kBAAkB,QAAQ,2CAA0C;AAC7E,SAASC,YAAY,QAAQ,4CAA2C;AACxE,SAASC,6BAA6B,QAAQ,+DAA8D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/oauth/components/FigmaAvatar/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,OAAO,cAAc,CAAA;AAQrB,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAoB/B,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useAuth } from '@payloadcms/ui';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import { FigmaAvatarPlaceholder, isValidImageUrl } from '../FigmaAvatarPlaceholder/index.js';
|
|
6
|
+
import './index.scss';
|
|
7
|
+
const baseClass = 'figma-avatar';
|
|
8
|
+
export const FigmaAvatar = ()=>{
|
|
9
|
+
const { user } = useAuth();
|
|
10
|
+
const [hasError, setHasError] = useState(false);
|
|
11
|
+
const figmaImageUrl = user?.figmaImageUrl;
|
|
12
|
+
if (!figmaImageUrl || hasError || !isValidImageUrl(figmaImageUrl)) {
|
|
13
|
+
return /*#__PURE__*/ _jsx(FigmaAvatarPlaceholder, {
|
|
14
|
+
size: 25
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return /*#__PURE__*/ _jsx("img", {
|
|
18
|
+
alt: "Profile",
|
|
19
|
+
className: `${baseClass}__image`,
|
|
20
|
+
height: 25,
|
|
21
|
+
onError: ()=>setHasError(true),
|
|
22
|
+
src: figmaImageUrl,
|
|
23
|
+
width: 25
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/oauth/components/FigmaAvatar/index.tsx"],"sourcesContent":["'use client'\nimport type React from 'react'\n\nimport { useAuth } from '@payloadcms/ui'\nimport { useState } from 'react'\n\nimport { FigmaAvatarPlaceholder, isValidImageUrl } from '../FigmaAvatarPlaceholder/index.js'\nimport './index.scss'\n\nconst baseClass = 'figma-avatar'\n\ntype UserWithFigmaFields = {\n figmaImageUrl?: string\n}\n\nexport const FigmaAvatar: React.FC = () => {\n const { user } = useAuth()\n const [hasError, setHasError] = useState(false)\n\n const figmaImageUrl = (user as UserWithFigmaFields)?.figmaImageUrl\n\n if (!figmaImageUrl || hasError || !isValidImageUrl(figmaImageUrl)) {\n return <FigmaAvatarPlaceholder size={25} />\n }\n\n return (\n <img\n alt=\"Profile\"\n className={`${baseClass}__image`}\n height={25}\n onError={() => setHasError(true)}\n src={figmaImageUrl}\n width={25}\n />\n )\n}\n"],"names":["useAuth","useState","FigmaAvatarPlaceholder","isValidImageUrl","baseClass","FigmaAvatar","user","hasError","setHasError","figmaImageUrl","size","img","alt","className","height","onError","src","width"],"mappings":"AAAA;;AAGA,SAASA,OAAO,QAAQ,iBAAgB;AACxC,SAASC,QAAQ,QAAQ,QAAO;AAEhC,SAASC,sBAAsB,EAAEC,eAAe,QAAQ,qCAAoC;AAC5F,OAAO,eAAc;AAErB,MAAMC,YAAY;AAMlB,OAAO,MAAMC,cAAwB;IACnC,MAAM,EAAEC,IAAI,EAAE,GAAGN;IACjB,MAAM,CAACO,UAAUC,YAAY,GAAGP,SAAS;IAEzC,MAAMQ,gBAAiBH,MAA8BG;IAErD,IAAI,CAACA,iBAAiBF,YAAY,CAACJ,gBAAgBM,gBAAgB;QACjE,qBAAO,KAACP;YAAuBQ,MAAM;;IACvC;IAEA,qBACE,KAACC;QACCC,KAAI;QACJC,WAAW,GAAGT,UAAU,OAAO,CAAC;QAChCU,QAAQ;QACRC,SAAS,IAAMP,YAAY;QAC3BQ,KAAKP;QACLQ,OAAO;;AAGb,EAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/oauth/components/FigmaAvatarCell/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AACxD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,cAAc,CAAA;AAIrB,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAkB/D,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { FigmaAvatarPlaceholder, isValidImageUrl } from '../FigmaAvatarPlaceholder/index.js';
|
|
5
|
+
import './index.scss';
|
|
6
|
+
const baseClass = 'figma-avatar-cell';
|
|
7
|
+
export const FigmaAvatarCell = ({ cellData })=>{
|
|
8
|
+
const [hasError, setHasError] = useState(false);
|
|
9
|
+
const figmaImageUrl = cellData;
|
|
10
|
+
if (!figmaImageUrl || hasError || !isValidImageUrl(figmaImageUrl)) {
|
|
11
|
+
return /*#__PURE__*/ _jsx(FigmaAvatarPlaceholder, {
|
|
12
|
+
size: 20
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return /*#__PURE__*/ _jsx("img", {
|
|
16
|
+
alt: "Avatar",
|
|
17
|
+
className: `${baseClass}__image`,
|
|
18
|
+
height: 20,
|
|
19
|
+
onError: ()=>setHasError(true),
|
|
20
|
+
src: figmaImageUrl,
|
|
21
|
+
width: 20
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/oauth/components/FigmaAvatarCell/index.tsx"],"sourcesContent":["'use client'\nimport type { DefaultCellComponentProps } from 'payload'\nimport type React from 'react'\n\nimport { useState } from 'react'\n\nimport { FigmaAvatarPlaceholder, isValidImageUrl } from '../FigmaAvatarPlaceholder/index.js'\nimport './index.scss'\n\nconst baseClass = 'figma-avatar-cell'\n\nexport const FigmaAvatarCell: React.FC<DefaultCellComponentProps> = ({ cellData }) => {\n const [hasError, setHasError] = useState(false)\n const figmaImageUrl = cellData as string | undefined\n\n if (!figmaImageUrl || hasError || !isValidImageUrl(figmaImageUrl)) {\n return <FigmaAvatarPlaceholder size={20} />\n }\n\n return (\n <img\n alt=\"Avatar\"\n className={`${baseClass}__image`}\n height={20}\n onError={() => setHasError(true)}\n src={figmaImageUrl}\n width={20}\n />\n )\n}\n"],"names":["useState","FigmaAvatarPlaceholder","isValidImageUrl","baseClass","FigmaAvatarCell","cellData","hasError","setHasError","figmaImageUrl","size","img","alt","className","height","onError","src","width"],"mappings":"AAAA;;AAIA,SAASA,QAAQ,QAAQ,QAAO;AAEhC,SAASC,sBAAsB,EAAEC,eAAe,QAAQ,qCAAoC;AAC5F,OAAO,eAAc;AAErB,MAAMC,YAAY;AAElB,OAAO,MAAMC,kBAAuD,CAAC,EAAEC,QAAQ,EAAE;IAC/E,MAAM,CAACC,UAAUC,YAAY,GAAGP,SAAS;IACzC,MAAMQ,gBAAgBH;IAEtB,IAAI,CAACG,iBAAiBF,YAAY,CAACJ,gBAAgBM,gBAAgB;QACjE,qBAAO,KAACP;YAAuBQ,MAAM;;IACvC;IAEA,qBACE,KAACC;QACCC,KAAI;QACJC,WAAW,GAAGT,UAAU,OAAO,CAAC;QAChCU,QAAQ;QACRC,SAAS,IAAMP,YAAY;QAC3BQ,KAAKP;QACLQ,OAAO;;AAGb,EAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import './index.scss';
|
|
3
|
+
type FigmaAvatarPlaceholderProps = {
|
|
4
|
+
size?: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const FigmaAvatarPlaceholder: React.FC<FigmaAvatarPlaceholderProps>;
|
|
7
|
+
export declare function isValidImageUrl(url: string): boolean;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/oauth/components/FigmaAvatarPlaceholder/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,cAAc,CAAA;AAIrB,KAAK,2BAA2B,GAAG;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAMxE,CAAA;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOpD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import './index.scss';
|
|
3
|
+
const baseClass = 'figma-avatar-placeholder';
|
|
4
|
+
export const FigmaAvatarPlaceholder = ({ size = 25 })=>/*#__PURE__*/ _jsxs("svg", {
|
|
5
|
+
className: baseClass,
|
|
6
|
+
height: size,
|
|
7
|
+
viewBox: "0 0 25 25",
|
|
8
|
+
width: size,
|
|
9
|
+
children: [
|
|
10
|
+
/*#__PURE__*/ _jsx("circle", {
|
|
11
|
+
className: `${baseClass}__bg`,
|
|
12
|
+
cx: "12.5",
|
|
13
|
+
cy: "12.5",
|
|
14
|
+
r: "11.5"
|
|
15
|
+
}),
|
|
16
|
+
/*#__PURE__*/ _jsx("circle", {
|
|
17
|
+
className: `${baseClass}__head`,
|
|
18
|
+
cx: "12.5",
|
|
19
|
+
cy: "10",
|
|
20
|
+
r: "4"
|
|
21
|
+
}),
|
|
22
|
+
/*#__PURE__*/ _jsx("path", {
|
|
23
|
+
className: `${baseClass}__body`,
|
|
24
|
+
d: "M12.5 15c-4 0-7 2-7 4.5v1h14v-1c0-2.5-3-4.5-7-4.5z"
|
|
25
|
+
})
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
export function isValidImageUrl(url) {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = new URL(url);
|
|
31
|
+
return parsed.protocol === 'https:' || parsed.protocol === 'http:';
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/oauth/components/FigmaAvatarPlaceholder/index.tsx"],"sourcesContent":["import type React from 'react'\n\nimport './index.scss'\n\nconst baseClass = 'figma-avatar-placeholder'\n\ntype FigmaAvatarPlaceholderProps = {\n size?: number\n}\n\nexport const FigmaAvatarPlaceholder: React.FC<FigmaAvatarPlaceholderProps> = ({ size = 25 }) => (\n <svg className={baseClass} height={size} viewBox=\"0 0 25 25\" width={size}>\n <circle className={`${baseClass}__bg`} cx=\"12.5\" cy=\"12.5\" r=\"11.5\" />\n <circle className={`${baseClass}__head`} cx=\"12.5\" cy=\"10\" r=\"4\" />\n <path className={`${baseClass}__body`} d=\"M12.5 15c-4 0-7 2-7 4.5v1h14v-1c0-2.5-3-4.5-7-4.5z\" />\n </svg>\n)\n\nexport function isValidImageUrl(url: string): boolean {\n try {\n const parsed = new URL(url)\n return parsed.protocol === 'https:' || parsed.protocol === 'http:'\n } catch {\n return false\n }\n}\n"],"names":["baseClass","FigmaAvatarPlaceholder","size","svg","className","height","viewBox","width","circle","cx","cy","r","path","d","isValidImageUrl","url","parsed","URL","protocol"],"mappings":";AAEA,OAAO,eAAc;AAErB,MAAMA,YAAY;AAMlB,OAAO,MAAMC,yBAAgE,CAAC,EAAEC,OAAO,EAAE,EAAE,iBACzF,MAACC;QAAIC,WAAWJ;QAAWK,QAAQH;QAAMI,SAAQ;QAAYC,OAAOL;;0BAClE,KAACM;gBAAOJ,WAAW,GAAGJ,UAAU,IAAI,CAAC;gBAAES,IAAG;gBAAOC,IAAG;gBAAOC,GAAE;;0BAC7D,KAACH;gBAAOJ,WAAW,GAAGJ,UAAU,MAAM,CAAC;gBAAES,IAAG;gBAAOC,IAAG;gBAAKC,GAAE;;0BAC7D,KAACC;gBAAKR,WAAW,GAAGJ,UAAU,MAAM,CAAC;gBAAEa,GAAE;;;OAE5C;AAED,OAAO,SAASC,gBAAgBC,GAAW;IACzC,IAAI;QACF,MAAMC,SAAS,IAAIC,IAAIF;QACvB,OAAOC,OAAOE,QAAQ,KAAK,YAAYF,OAAOE,QAAQ,KAAK;IAC7D,EAAE,OAAM;QACN,OAAO;IACT;AACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@layer payload-default {
|
|
2
|
+
.figma-avatar-placeholder {
|
|
3
|
+
&__bg {
|
|
4
|
+
fill: var(--theme-elevation-50);
|
|
5
|
+
stroke: var(--theme-elevation-200);
|
|
6
|
+
stroke-width: 1px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
&__head,
|
|
10
|
+
&__body {
|
|
11
|
+
fill: var(--theme-elevation-200);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
&:hover {
|
|
15
|
+
.figma-avatar-placeholder {
|
|
16
|
+
&__bg {
|
|
17
|
+
fill: var(--theme-elevation-200);
|
|
18
|
+
stroke: var(--theme-elevation-600);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&__head,
|
|
22
|
+
&__body {
|
|
23
|
+
fill: var(--theme-elevation-600);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/oauth/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/oauth/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAa,MAAM,SAAS,CAAA;AAKjF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA4BhD,eAAO,MAAM,YAAY,UAAiC,CAAA;AAE1D,eAAO,MAAM,oBAAoB,EAAE,SAQlC,CAAA;AAED,eAAO,MAAM,aAAa,iDAKrB;IACD,UAAU,EAAE,gBAAgB,CAAA;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,UAAU,GAAG,SAAS,CAAA;CACtC,KAAG,cAkIH,CAAA"}
|
package/dist/oauth/defaults.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
import { parseCookies } from 'payload';
|
|
1
2
|
import { v4 as uuid } from 'uuid';
|
|
2
3
|
import { hasUserTokenPropsChanged } from './utilities/hasUserTokenPropsChanged.js';
|
|
3
4
|
import { isDuplicateKeyError } from './utilities/isDuplicateKeyError.js';
|
|
5
|
+
function getFigmaUserInfo(headers) {
|
|
6
|
+
try {
|
|
7
|
+
const cookies = parseCookies(headers);
|
|
8
|
+
const encoded = cookies.get('figma-user-info');
|
|
9
|
+
if (!encoded) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'));
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
4
17
|
export const defaultScope = [
|
|
5
18
|
'openid',
|
|
6
19
|
'profile',
|
|
@@ -15,7 +28,7 @@ export const defaultUsernameField = {
|
|
|
15
28
|
},
|
|
16
29
|
unique: true
|
|
17
30
|
};
|
|
18
|
-
export const defaultVerify = ({ collection, strategyName, usernameField })=>async ({ payload, token })=>{
|
|
31
|
+
export const defaultVerify = ({ collection, strategyName, usernameField })=>async ({ headers, payload, token })=>{
|
|
19
32
|
let tokenUsername = 'preferred_username';
|
|
20
33
|
if (usernameField.name !== 'preferredUsername') {
|
|
21
34
|
tokenUsername = usernameField.name;
|
|
@@ -25,6 +38,13 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
|
|
|
25
38
|
user: null
|
|
26
39
|
};
|
|
27
40
|
}
|
|
41
|
+
const figmaUserInfo = getFigmaUserInfo(headers);
|
|
42
|
+
let responseHeaders;
|
|
43
|
+
// Clear the cookie after reading
|
|
44
|
+
if (figmaUserInfo) {
|
|
45
|
+
responseHeaders = new Headers();
|
|
46
|
+
responseHeaders.append('Set-Cookie', 'figma-user-info=; Max-Age=0; Path=/;');
|
|
47
|
+
}
|
|
28
48
|
let user = null;
|
|
29
49
|
const depth = typeof collection.auth === 'object' ? collection.auth.depth : undefined;
|
|
30
50
|
try {
|
|
@@ -41,13 +61,19 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
|
|
|
41
61
|
});
|
|
42
62
|
if (results.docs.length > 0) {
|
|
43
63
|
user = results.docs[0];
|
|
44
|
-
//
|
|
45
|
-
|
|
64
|
+
// Check if update needed (token changed OR figma info missing/changed)
|
|
65
|
+
const userWithFigma = user;
|
|
66
|
+
const needsFigmaUpdate = figmaUserInfo && (userWithFigma.figmaHandle !== figmaUserInfo.handle || userWithFigma.figmaImageUrl !== figmaUserInfo.img_url);
|
|
67
|
+
if (hasUserTokenPropsChanged(user, token) || needsFigmaUpdate) {
|
|
46
68
|
user = await payload.update({
|
|
47
69
|
id: user.id,
|
|
48
70
|
collection: collection.slug,
|
|
49
71
|
data: {
|
|
50
|
-
...token
|
|
72
|
+
...token,
|
|
73
|
+
...figmaUserInfo && {
|
|
74
|
+
figmaHandle: figmaUserInfo.handle,
|
|
75
|
+
figmaImageUrl: figmaUserInfo.img_url
|
|
76
|
+
}
|
|
51
77
|
},
|
|
52
78
|
depth
|
|
53
79
|
});
|
|
@@ -56,7 +82,11 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
|
|
|
56
82
|
const newUser = {
|
|
57
83
|
email: token.preferredUsername || token.email,
|
|
58
84
|
[usernameField.name]: token[tokenUsername],
|
|
59
|
-
...token
|
|
85
|
+
...token,
|
|
86
|
+
...figmaUserInfo && {
|
|
87
|
+
figmaHandle: figmaUserInfo.handle,
|
|
88
|
+
figmaImageUrl: figmaUserInfo.img_url
|
|
89
|
+
}
|
|
60
90
|
};
|
|
61
91
|
if (typeof collection.auth === 'object' && !collection.auth.disableLocalStrategy) {
|
|
62
92
|
newUser.password = uuid();
|
|
@@ -96,6 +126,7 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
|
|
|
96
126
|
msg: 'Error verifying user from defaultVerify.'
|
|
97
127
|
});
|
|
98
128
|
return {
|
|
129
|
+
responseHeaders,
|
|
99
130
|
user: null
|
|
100
131
|
};
|
|
101
132
|
}
|
|
@@ -104,10 +135,12 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
|
|
|
104
135
|
user._strategy = strategyName;
|
|
105
136
|
user.exp = token.exp;
|
|
106
137
|
return {
|
|
138
|
+
responseHeaders,
|
|
107
139
|
user
|
|
108
140
|
};
|
|
109
141
|
}
|
|
110
142
|
return {
|
|
143
|
+
responseHeaders,
|
|
111
144
|
user: null
|
|
112
145
|
};
|
|
113
146
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/oauth/defaults.ts"],"sourcesContent":["import type { CollectionConfig, EmailField, TextField,
|
|
1
|
+
{"version":3,"sources":["../../src/oauth/defaults.ts"],"sourcesContent":["import type { CollectionConfig, EmailField, TextField, TypedUser } from 'payload'\n\nimport { parseCookies } from 'payload'\nimport { v4 as uuid } from 'uuid'\n\nimport type { VerifyFunction } from './types.js'\n\nimport { hasUserTokenPropsChanged } from './utilities/hasUserTokenPropsChanged.js'\nimport { isDuplicateKeyError } from './utilities/isDuplicateKeyError.js'\n\ntype FigmaUserInfoCookie = {\n handle: string\n img_url: string\n}\n\ntype UserWithFigmaFields = {\n figmaHandle?: string\n figmaImageUrl?: string\n} & TypedUser\n\nfunction getFigmaUserInfo(headers: Headers): FigmaUserInfoCookie | null {\n try {\n const cookies = parseCookies(headers)\n const encoded = cookies.get('figma-user-info')\n if (!encoded) {\n return null\n }\n return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'))\n } catch {\n return null\n }\n}\n\nexport const defaultScope = ['openid', 'profile', 'email']\n\nexport const defaultUsernameField: TextField = {\n name: 'preferredUsername',\n type: 'text',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n unique: true,\n}\n\nexport const defaultVerify =\n ({\n collection,\n strategyName,\n usernameField,\n }: {\n collection: CollectionConfig\n strategyName: string\n usernameField: EmailField | TextField\n }): VerifyFunction =>\n async ({ headers, payload, token }) => {\n let tokenUsername = 'preferred_username'\n\n if (usernameField.name !== 'preferredUsername') {\n tokenUsername = usernameField.name\n }\n\n if (!token[tokenUsername]) {\n return {\n user: null,\n }\n }\n\n const figmaUserInfo = getFigmaUserInfo(headers)\n let responseHeaders: Headers | undefined\n\n // Clear the cookie after reading\n if (figmaUserInfo) {\n responseHeaders = new Headers()\n responseHeaders.append('Set-Cookie', 'figma-user-info=; Max-Age=0; Path=/;')\n }\n\n let user: null | TypedUser | UserWithFigmaFields = null\n const depth = typeof collection.auth === 'object' ? collection.auth.depth : undefined\n\n try {\n const results = await payload.find({\n collection: collection.slug,\n depth,\n limit: 1,\n pagination: false,\n where: {\n [usernameField.name]: { equals: token[tokenUsername] },\n },\n })\n\n if (results.docs.length > 0) {\n user = results.docs[0] as UserWithFigmaFields\n\n // Check if update needed (token changed OR figma info missing/changed)\n const userWithFigma = user\n const needsFigmaUpdate =\n figmaUserInfo &&\n (userWithFigma.figmaHandle !== figmaUserInfo.handle ||\n userWithFigma.figmaImageUrl !== figmaUserInfo.img_url)\n\n if (hasUserTokenPropsChanged(user, token) || needsFigmaUpdate) {\n user = (await payload.update({\n id: user.id,\n collection: collection.slug,\n data: {\n ...token,\n ...(figmaUserInfo && {\n figmaHandle: figmaUserInfo.handle,\n figmaImageUrl: figmaUserInfo.img_url,\n }),\n },\n depth,\n })) as TypedUser\n }\n } else {\n const newUser: Record<string, unknown> = {\n email: token.preferredUsername || token.email,\n [usernameField.name]: token[tokenUsername],\n ...token,\n ...(figmaUserInfo && {\n figmaHandle: figmaUserInfo.handle,\n figmaImageUrl: figmaUserInfo.img_url,\n }),\n }\n\n if (typeof collection.auth === 'object' && !collection.auth.disableLocalStrategy) {\n newUser.password = uuid()\n }\n\n try {\n user = (await payload.create({\n collection: collection.slug,\n data: newUser,\n depth,\n overrideAccess: true,\n })) as TypedUser\n } catch (createErr: unknown) {\n // Race condition: if unique constraint violation, user was created by concurrent request\n if (isDuplicateKeyError(createErr)) {\n const retryResults = await payload.find({\n collection: collection.slug,\n depth,\n limit: 1,\n pagination: false,\n where: {\n [usernameField.name]: { equals: token[tokenUsername] },\n },\n })\n\n if (retryResults.docs.length > 0) {\n user = retryResults.docs[0] as TypedUser\n }\n } else {\n throw createErr\n }\n }\n }\n } catch (err: unknown) {\n payload.logger.error({\n err,\n msg: 'Error verifying user from defaultVerify.',\n })\n return {\n responseHeaders,\n user: null,\n }\n }\n\n if (user) {\n user.collection = collection.slug\n user._strategy = strategyName\n user.exp = token.exp\n\n return {\n responseHeaders,\n user,\n }\n }\n\n return {\n responseHeaders,\n user: null,\n }\n }\n"],"names":["parseCookies","v4","uuid","hasUserTokenPropsChanged","isDuplicateKeyError","getFigmaUserInfo","headers","cookies","encoded","get","JSON","parse","Buffer","from","toString","defaultScope","defaultUsernameField","name","type","admin","position","readOnly","unique","defaultVerify","collection","strategyName","usernameField","payload","token","tokenUsername","user","figmaUserInfo","responseHeaders","Headers","append","depth","auth","undefined","results","find","slug","limit","pagination","where","equals","docs","length","userWithFigma","needsFigmaUpdate","figmaHandle","handle","figmaImageUrl","img_url","update","id","data","newUser","email","preferredUsername","disableLocalStrategy","password","create","overrideAccess","createErr","retryResults","err","logger","error","msg","_strategy","exp"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getLoginEndpoint.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/getLoginEndpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"getLoginEndpoint.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/getLoginEndpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAMzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAY,MAAM,aAAa,CAAA;AAQ7E,UAAU,IAAI;IACZ,UAAU,EAAE,gBAAgB,CAAA;IAC5B,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,aAAa,CAAA;IAC5B,QAAQ,EAAE,QAAQ,CAAA;CACnB;AAID,eAAO,MAAM,gBAAgB,8EAM1B,IAAI,KAAG,QAyRR,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { APIError, generateCookie, parseCookies } from 'payload';
|
|
2
2
|
import { formatAdminURL, getSafeRedirect } from 'payload/shared';
|
|
3
|
-
import { getProjectToken } from '../../api/figma-api.js';
|
|
3
|
+
import { getProjectToken, getUserInfo } from '../../api/figma-api.js';
|
|
4
4
|
import { createCookieOptions } from '../utilities/createCookieOptions.js';
|
|
5
5
|
import { createDebugLogger } from '../utilities/createDebugLogger.js';
|
|
6
6
|
import { extractOrigin } from '../utilities/extractOrigin.js';
|
|
@@ -167,6 +167,21 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
|
|
|
167
167
|
});
|
|
168
168
|
return Response.redirect(failedRedirect);
|
|
169
169
|
}
|
|
170
|
+
// Fetch Figma user info for handle and profile image
|
|
171
|
+
let figmaUserInfo = null;
|
|
172
|
+
try {
|
|
173
|
+
figmaUserInfo = await getUserInfo(access_token);
|
|
174
|
+
debugLogger.info({
|
|
175
|
+
figmaUserInfo,
|
|
176
|
+
msg: 'Fetched Figma user info'
|
|
177
|
+
});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
// Non-fatal: user will just not have handle/image
|
|
180
|
+
debugLogger.error({
|
|
181
|
+
err,
|
|
182
|
+
msg: 'Failed to fetch Figma user info'
|
|
183
|
+
});
|
|
184
|
+
}
|
|
170
185
|
if (!access_token) {
|
|
171
186
|
const errMsg = 'No access_token was returned from the identity provider.';
|
|
172
187
|
req.payload.logger.error({
|
|
@@ -231,6 +246,21 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
|
|
|
231
246
|
value: token
|
|
232
247
|
});
|
|
233
248
|
req.responseHeaders.append('Set-Cookie', tokenCookie);
|
|
249
|
+
// Store user info in short-lived cookie for defaultVerify to consume
|
|
250
|
+
if (figmaUserInfo) {
|
|
251
|
+
const userInfoValue = Buffer.from(JSON.stringify({
|
|
252
|
+
handle: figmaUserInfo.handle,
|
|
253
|
+
img_url: figmaUserInfo.img_url
|
|
254
|
+
})).toString('base64');
|
|
255
|
+
const userInfoCookie = generateCookie({
|
|
256
|
+
...cookieOptions,
|
|
257
|
+
name: 'figma-user-info',
|
|
258
|
+
expires: new Date(Date.now() + 60_000),
|
|
259
|
+
returnCookieAsObject: false,
|
|
260
|
+
value: userInfoValue
|
|
261
|
+
});
|
|
262
|
+
req.responseHeaders.append('Set-Cookie', userInfoCookie);
|
|
263
|
+
}
|
|
234
264
|
return Response.redirect(redirectToUse);
|
|
235
265
|
} catch (err) {
|
|
236
266
|
req.payload.logger.error({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/oauth/endpoints/getLoginEndpoint.ts"],"sourcesContent":["import type { CollectionConfig, Endpoint } from 'payload'\n\nimport { APIError, generateCookie, parseCookies } from 'payload'\nimport { formatAdminURL, getSafeRedirect } from 'payload/shared'\n\nimport type { Strategy } from '../strategy/index.js'\nimport type { CollectionOptions, PluginOptions, StateObj } from '../types.js'\n\nimport { getProjectToken } from '../../api/figma-api.js'\nimport { createCookieOptions } from '../utilities/createCookieOptions.js'\nimport { createDebugLogger } from '../utilities/createDebugLogger.js'\nimport { extractOrigin } from '../utilities/extractOrigin.js'\nimport { getCookieExpiration } from '../utilities/getCookieExpiration.js'\n\ninterface Args {\n collection: CollectionConfig\n collectionOptions: CollectionOptions\n endpointSlug: string\n pluginOptions: PluginOptions\n strategy: Strategy\n}\n\nconst isAbsoluteURL = (url: string) => /^[a-z][a-z\\d+.-]*:/.test(url)\n\nexport const getLoginEndpoint = ({\n collection,\n collectionOptions,\n endpointSlug,\n pluginOptions,\n strategy,\n}: Args): Endpoint => ({\n handler: async (req) => {\n const { config } = req.payload\n const adminRoute = config.routes?.admin || '/admin'\n\n try {\n await strategy.ensureMeta()\n\n const path = req.pathname\n const debugLogger = createDebugLogger(req.payload, pluginOptions.debug)\n\n let jsonBody: Record<string, unknown> = {}\n\n const contentType = req.headers.get('content-type')\n\n // Type narrow PayloadRequst's Partial<Request>\n if (typeof req.json !== 'function' || typeof req.formData !== 'function') {\n debugLogger.info({\n msg: 'Request does not have json or formData methods',\n path,\n })\n\n return Response.redirect(`${pluginOptions.redirectServerURL || config.serverURL}/login`)\n }\n\n if (collectionOptions.authorizationURLParams?.response_mode === 'query') {\n if (Object.keys(req.query).length > 0) {\n jsonBody = req.query\n }\n } else if (contentType === 'application/json') {\n jsonBody = await req.json()\n } else if (contentType === 'application/x-www-form-urlencoded') {\n const formData = await req.formData()\n jsonBody = Object.fromEntries(formData.entries())\n }\n\n const stateRaw = jsonBody.state as string\n const state: StateObj = JSON.parse(Buffer.from(stateRaw, 'base64').toString('utf-8'))\n\n const redirectServerURL =\n state.serverURL || pluginOptions.redirectServerURL || config.serverURL\n let failedRedirect = formatAdminURL({\n adminRoute,\n path: '/login',\n serverURL: redirectServerURL,\n })\n let redirectToUse = formatAdminURL({\n adminRoute,\n serverURL: redirectServerURL,\n })\n\n if (state.redirect) {\n redirectToUse = state.redirect\n }\n\n if (state.failedRedirect) {\n failedRedirect = state.failedRedirect\n }\n\n failedRedirect = getSafeRedirect({ allowAbsoluteUrls: true, redirectTo: failedRedirect })\n redirectToUse = getSafeRedirect({ allowAbsoluteUrls: true, redirectTo: redirectToUse })\n\n if (!isAbsoluteURL(failedRedirect)) {\n return Response.json({ error: 'Invalid failed redirect provided' }, { status: 403 })\n }\n if (!isAbsoluteURL(redirectToUse)) {\n return Response.json({ error: 'Invalid redirect provided' }, { status: 403 })\n }\n\n if (Array.isArray(req.payload.config.csrf) && req.payload.config.csrf.length > 0) {\n const failedRedirectOrigin = extractOrigin(failedRedirect)\n const redirectToUseOrigin = extractOrigin(redirectToUse)\n\n if (failedRedirectOrigin && !req.payload.config.csrf.includes(failedRedirectOrigin)) {\n req.payload.logger.error(\n `Failed redirect origin ${failedRedirectOrigin} is not allowed by CSRF configuration.`,\n )\n return Response.json(\n { error: 'Failed redirect origin is not allowed by CSRF configuration.' },\n { status: 403 },\n )\n }\n\n if (redirectToUseOrigin && !req.payload.config.csrf.includes(redirectToUseOrigin)) {\n req.payload.logger.error(\n `Redirect origin ${redirectToUseOrigin} is not allowed by CSRF configuration.`,\n )\n return Response.json(\n { error: 'Redirect origin is not allowed by CSRF configuration.' },\n { status: 403 },\n )\n }\n }\n\n // Verify signature\n if (strategy.name !== req.payload.decrypt(state.sig)) {\n req.payload.logger.error('Signature mismatch. Failing login and redirecting.')\n return Response.redirect(failedRedirect)\n }\n\n if (!strategy.meta?.token_endpoint) {\n debugLogger.error({\n msg: `No metadata was obtained from the identity provider. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n debugLogger.info({ body: jsonBody, msg: 'Received login response body', path })\n\n if (!jsonBody?.code) {\n debugLogger.error({\n msg: `No authorization code present in body. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n // Exchange `code` from body for refresh token\n const { code } = jsonBody\n\n if (typeof code !== 'string') {\n debugLogger.error({\n msg: `Authorization code present in body is not a string. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n const body: Record<string, string> = {\n client_id: strategy.clientID,\n code,\n code_verifier: req.payload.decrypt(state.verifier),\n grant_type: 'authorization_code',\n redirect_uri: formatAdminURL({\n apiRoute: config.routes?.api || '/api',\n path: `/${collection.slug}/${endpointSlug}/login`,\n serverURL: redirectServerURL,\n }),\n }\n\n if (strategy.collectionOptions.clientSecret) {\n body.client_secret = strategy.collectionOptions.clientSecret\n }\n\n debugLogger.info({\n msg: 'Exchanging code for access token',\n path,\n requestBody: body,\n })\n\n const tokenRes = await fetch(strategy.meta?.token_endpoint, {\n body: new URLSearchParams(body),\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n method: 'POST',\n }).then((r) => r.json())\n\n debugLogger.info({ msg: 'Token Response', path, tokenRes })\n\n const { access_token, error, error_description, expires_in: refreshTokenExpires } = tokenRes\n\n if (error) {\n req.payload.logger.error('Error swapping access code with access token:')\n req.payload.logger.error({ err: error, error_description })\n return Response.redirect(failedRedirect)\n }\n\n if (!access_token) {\n const errMsg = 'No access_token was returned from the identity provider.'\n req.payload.logger.error({ msg: errMsg, path, tokenRes })\n throw new APIError(errMsg)\n }\n\n // Swap for project token\n const project_token = await getProjectToken(access_token, config.custom.figma.contentSystemId)\n const refresh_token = access_token\n\n // Store project token as access_token\n\n const token = project_token.token\n const expiresIn = project_token.expiresAt\n\n if (!token) {\n const errMsg = `No ${\n collectionOptions.token || 'access_token'\n } was returned from the identity provider.`\n req.payload.logger.error({ msg: errMsg, path })\n throw new APIError(errMsg)\n }\n\n const cookieOptions = createCookieOptions({ collection, headers: req.headers, pluginOptions })\n\n // get the payloadRedirect cookie value\n const cookies = parseCookies(req.headers)\n\n const payloadRedirect =\n 'payloadRedirect' in cookies ? (cookies.payloadRedirect as string) : ''\n\n if (!req.responseHeaders) {\n req.responseHeaders = new Headers()\n }\n\n if (payloadRedirect) {\n if (isAbsoluteURL(payloadRedirect)) {\n redirectToUse = payloadRedirect\n } else {\n redirectToUse += payloadRedirect\n }\n\n req.responseHeaders.append(\n 'Set-Cookie',\n `payloadRedirect=${redirectToUse}; Max-Age=0; Path=/;`,\n )\n }\n\n if (refresh_token && !collectionOptions.usePayloadJWT) {\n debugLogger.info('Setting refresh token cookie')\n const refreshCookie = generateCookie({\n returnCookieAsObject: false,\n ...cookieOptions,\n name: `${strategy.cookieName}-refresh`,\n value: refresh_token,\n // Default to 1 year if no expiration\n expires: getCookieExpiration(cookieOptions?.expires ?? refreshTokenExpires ?? 31_556_952),\n }) as string\n\n req.responseHeaders.append('Set-Cookie', refreshCookie)\n }\n\n const tokenExpires = cookieOptions?.expires\n ? getCookieExpiration(cookieOptions.expires)\n : new Date(expiresIn) // Project token uses epoch time\n const tokenCookie = generateCookie({\n ...cookieOptions,\n name: strategy.cookieName,\n expires: tokenExpires,\n returnCookieAsObject: false,\n value: token,\n }) as string\n\n req.responseHeaders.append('Set-Cookie', tokenCookie)\n\n return Response.redirect(redirectToUse)\n } catch (err: unknown) {\n req.payload.logger.error({ err, msg: 'Error while attempting to login', path: req.pathname })\n\n return Response.json({ error: 'Error while attempting to login' }, { status: 500 })\n }\n },\n method: collectionOptions.authorizationURLParams?.response_mode === 'query' ? 'get' : 'post',\n path: `/${endpointSlug}/login`,\n})\n"],"names":["APIError","generateCookie","parseCookies","formatAdminURL","getSafeRedirect","getProjectToken","createCookieOptions","createDebugLogger","extractOrigin","getCookieExpiration","isAbsoluteURL","url","test","getLoginEndpoint","collection","collectionOptions","endpointSlug","pluginOptions","strategy","handler","req","config","payload","adminRoute","routes","admin","ensureMeta","path","pathname","debugLogger","debug","jsonBody","contentType","headers","get","json","formData","info","msg","Response","redirect","redirectServerURL","serverURL","authorizationURLParams","response_mode","Object","keys","query","length","fromEntries","entries","stateRaw","state","JSON","parse","Buffer","from","toString","failedRedirect","redirectToUse","allowAbsoluteUrls","redirectTo","error","status","Array","isArray","csrf","failedRedirectOrigin","redirectToUseOrigin","includes","logger","name","decrypt","sig","meta","token_endpoint","body","code","client_id","clientID","code_verifier","verifier","grant_type","redirect_uri","apiRoute","api","slug","clientSecret","client_secret","requestBody","tokenRes","fetch","URLSearchParams","method","then","r","access_token","error_description","expires_in","refreshTokenExpires","err","errMsg","project_token","custom","figma","contentSystemId","refresh_token","token","expiresIn","expiresAt","cookieOptions","cookies","payloadRedirect","responseHeaders","Headers","append","usePayloadJWT","refreshCookie","returnCookieAsObject","cookieName","value","expires","tokenExpires","Date","tokenCookie"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/oauth/endpoints/getLoginEndpoint.ts"],"sourcesContent":["import type { CollectionConfig, Endpoint } from 'payload'\n\nimport { APIError, generateCookie, parseCookies } from 'payload'\nimport { formatAdminURL, getSafeRedirect } from 'payload/shared'\n\nimport type { FigmaUserInfo } from '../../api/figma-api.js'\nimport type { Strategy } from '../strategy/index.js'\nimport type { CollectionOptions, PluginOptions, StateObj } from '../types.js'\n\nimport { getProjectToken, getUserInfo } from '../../api/figma-api.js'\nimport { createCookieOptions } from '../utilities/createCookieOptions.js'\nimport { createDebugLogger } from '../utilities/createDebugLogger.js'\nimport { extractOrigin } from '../utilities/extractOrigin.js'\nimport { getCookieExpiration } from '../utilities/getCookieExpiration.js'\n\ninterface Args {\n collection: CollectionConfig\n collectionOptions: CollectionOptions\n endpointSlug: string\n pluginOptions: PluginOptions\n strategy: Strategy\n}\n\nconst isAbsoluteURL = (url: string) => /^[a-z][a-z\\d+.-]*:/.test(url)\n\nexport const getLoginEndpoint = ({\n collection,\n collectionOptions,\n endpointSlug,\n pluginOptions,\n strategy,\n}: Args): Endpoint => ({\n handler: async (req) => {\n const { config } = req.payload\n const adminRoute = config.routes?.admin || '/admin'\n\n try {\n await strategy.ensureMeta()\n\n const path = req.pathname\n const debugLogger = createDebugLogger(req.payload, pluginOptions.debug)\n\n let jsonBody: Record<string, unknown> = {}\n\n const contentType = req.headers.get('content-type')\n\n // Type narrow PayloadRequst's Partial<Request>\n if (typeof req.json !== 'function' || typeof req.formData !== 'function') {\n debugLogger.info({\n msg: 'Request does not have json or formData methods',\n path,\n })\n\n return Response.redirect(`${pluginOptions.redirectServerURL || config.serverURL}/login`)\n }\n\n if (collectionOptions.authorizationURLParams?.response_mode === 'query') {\n if (Object.keys(req.query).length > 0) {\n jsonBody = req.query\n }\n } else if (contentType === 'application/json') {\n jsonBody = await req.json()\n } else if (contentType === 'application/x-www-form-urlencoded') {\n const formData = await req.formData()\n jsonBody = Object.fromEntries(formData.entries())\n }\n\n const stateRaw = jsonBody.state as string\n const state: StateObj = JSON.parse(Buffer.from(stateRaw, 'base64').toString('utf-8'))\n\n const redirectServerURL =\n state.serverURL || pluginOptions.redirectServerURL || config.serverURL\n let failedRedirect = formatAdminURL({\n adminRoute,\n path: '/login',\n serverURL: redirectServerURL,\n })\n let redirectToUse = formatAdminURL({\n adminRoute,\n serverURL: redirectServerURL,\n })\n\n if (state.redirect) {\n redirectToUse = state.redirect\n }\n\n if (state.failedRedirect) {\n failedRedirect = state.failedRedirect\n }\n\n failedRedirect = getSafeRedirect({ allowAbsoluteUrls: true, redirectTo: failedRedirect })\n redirectToUse = getSafeRedirect({ allowAbsoluteUrls: true, redirectTo: redirectToUse })\n\n if (!isAbsoluteURL(failedRedirect)) {\n return Response.json({ error: 'Invalid failed redirect provided' }, { status: 403 })\n }\n if (!isAbsoluteURL(redirectToUse)) {\n return Response.json({ error: 'Invalid redirect provided' }, { status: 403 })\n }\n\n if (Array.isArray(req.payload.config.csrf) && req.payload.config.csrf.length > 0) {\n const failedRedirectOrigin = extractOrigin(failedRedirect)\n const redirectToUseOrigin = extractOrigin(redirectToUse)\n\n if (failedRedirectOrigin && !req.payload.config.csrf.includes(failedRedirectOrigin)) {\n req.payload.logger.error(\n `Failed redirect origin ${failedRedirectOrigin} is not allowed by CSRF configuration.`,\n )\n return Response.json(\n { error: 'Failed redirect origin is not allowed by CSRF configuration.' },\n { status: 403 },\n )\n }\n\n if (redirectToUseOrigin && !req.payload.config.csrf.includes(redirectToUseOrigin)) {\n req.payload.logger.error(\n `Redirect origin ${redirectToUseOrigin} is not allowed by CSRF configuration.`,\n )\n return Response.json(\n { error: 'Redirect origin is not allowed by CSRF configuration.' },\n { status: 403 },\n )\n }\n }\n\n // Verify signature\n if (strategy.name !== req.payload.decrypt(state.sig)) {\n req.payload.logger.error('Signature mismatch. Failing login and redirecting.')\n return Response.redirect(failedRedirect)\n }\n\n if (!strategy.meta?.token_endpoint) {\n debugLogger.error({\n msg: `No metadata was obtained from the identity provider. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n debugLogger.info({ body: jsonBody, msg: 'Received login response body', path })\n\n if (!jsonBody?.code) {\n debugLogger.error({\n msg: `No authorization code present in body. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n // Exchange `code` from body for refresh token\n const { code } = jsonBody\n\n if (typeof code !== 'string') {\n debugLogger.error({\n msg: `Authorization code present in body is not a string. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n const body: Record<string, string> = {\n client_id: strategy.clientID,\n code,\n code_verifier: req.payload.decrypt(state.verifier),\n grant_type: 'authorization_code',\n redirect_uri: formatAdminURL({\n apiRoute: config.routes?.api || '/api',\n path: `/${collection.slug}/${endpointSlug}/login`,\n serverURL: redirectServerURL,\n }),\n }\n\n if (strategy.collectionOptions.clientSecret) {\n body.client_secret = strategy.collectionOptions.clientSecret\n }\n\n debugLogger.info({\n msg: 'Exchanging code for access token',\n path,\n requestBody: body,\n })\n\n const tokenRes = await fetch(strategy.meta?.token_endpoint, {\n body: new URLSearchParams(body),\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n method: 'POST',\n }).then((r) => r.json())\n\n debugLogger.info({ msg: 'Token Response', path, tokenRes })\n\n const { access_token, error, error_description, expires_in: refreshTokenExpires } = tokenRes\n\n if (error) {\n req.payload.logger.error('Error swapping access code with access token:')\n req.payload.logger.error({ err: error, error_description })\n return Response.redirect(failedRedirect)\n }\n\n // Fetch Figma user info for handle and profile image\n let figmaUserInfo: FigmaUserInfo | null = null\n try {\n figmaUserInfo = await getUserInfo(access_token)\n debugLogger.info({ figmaUserInfo, msg: 'Fetched Figma user info' })\n } catch (err) {\n // Non-fatal: user will just not have handle/image\n debugLogger.error({ err, msg: 'Failed to fetch Figma user info' })\n }\n\n if (!access_token) {\n const errMsg = 'No access_token was returned from the identity provider.'\n req.payload.logger.error({ msg: errMsg, path, tokenRes })\n throw new APIError(errMsg)\n }\n\n // Swap for project token\n const project_token = await getProjectToken(access_token, config.custom.figma.contentSystemId)\n const refresh_token = access_token\n\n // Store project token as access_token\n\n const token = project_token.token\n const expiresIn = project_token.expiresAt\n\n if (!token) {\n const errMsg = `No ${\n collectionOptions.token || 'access_token'\n } was returned from the identity provider.`\n req.payload.logger.error({ msg: errMsg, path })\n throw new APIError(errMsg)\n }\n\n const cookieOptions = createCookieOptions({ collection, headers: req.headers, pluginOptions })\n\n // get the payloadRedirect cookie value\n const cookies = parseCookies(req.headers)\n\n const payloadRedirect =\n 'payloadRedirect' in cookies ? (cookies.payloadRedirect as string) : ''\n\n if (!req.responseHeaders) {\n req.responseHeaders = new Headers()\n }\n\n if (payloadRedirect) {\n if (isAbsoluteURL(payloadRedirect)) {\n redirectToUse = payloadRedirect\n } else {\n redirectToUse += payloadRedirect\n }\n\n req.responseHeaders.append(\n 'Set-Cookie',\n `payloadRedirect=${redirectToUse}; Max-Age=0; Path=/;`,\n )\n }\n\n if (refresh_token && !collectionOptions.usePayloadJWT) {\n debugLogger.info('Setting refresh token cookie')\n const refreshCookie = generateCookie({\n returnCookieAsObject: false,\n ...cookieOptions,\n name: `${strategy.cookieName}-refresh`,\n value: refresh_token,\n // Default to 1 year if no expiration\n expires: getCookieExpiration(cookieOptions?.expires ?? refreshTokenExpires ?? 31_556_952),\n }) as string\n\n req.responseHeaders.append('Set-Cookie', refreshCookie)\n }\n\n const tokenExpires = cookieOptions?.expires\n ? getCookieExpiration(cookieOptions.expires)\n : new Date(expiresIn) // Project token uses epoch time\n const tokenCookie = generateCookie({\n ...cookieOptions,\n name: strategy.cookieName,\n expires: tokenExpires,\n returnCookieAsObject: false,\n value: token,\n }) as string\n\n req.responseHeaders.append('Set-Cookie', tokenCookie)\n\n // Store user info in short-lived cookie for defaultVerify to consume\n if (figmaUserInfo) {\n const userInfoValue = Buffer.from(\n JSON.stringify({\n handle: figmaUserInfo.handle,\n img_url: figmaUserInfo.img_url,\n }),\n ).toString('base64')\n const userInfoCookie = generateCookie({\n ...cookieOptions,\n name: 'figma-user-info',\n expires: new Date(Date.now() + 60_000), // 60 seconds\n returnCookieAsObject: false,\n value: userInfoValue,\n }) as string\n req.responseHeaders.append('Set-Cookie', userInfoCookie)\n }\n\n return Response.redirect(redirectToUse)\n } catch (err: unknown) {\n req.payload.logger.error({ err, msg: 'Error while attempting to login', path: req.pathname })\n\n return Response.json({ error: 'Error while attempting to login' }, { status: 500 })\n }\n },\n method: collectionOptions.authorizationURLParams?.response_mode === 'query' ? 'get' : 'post',\n path: `/${endpointSlug}/login`,\n})\n"],"names":["APIError","generateCookie","parseCookies","formatAdminURL","getSafeRedirect","getProjectToken","getUserInfo","createCookieOptions","createDebugLogger","extractOrigin","getCookieExpiration","isAbsoluteURL","url","test","getLoginEndpoint","collection","collectionOptions","endpointSlug","pluginOptions","strategy","handler","req","config","payload","adminRoute","routes","admin","ensureMeta","path","pathname","debugLogger","debug","jsonBody","contentType","headers","get","json","formData","info","msg","Response","redirect","redirectServerURL","serverURL","authorizationURLParams","response_mode","Object","keys","query","length","fromEntries","entries","stateRaw","state","JSON","parse","Buffer","from","toString","failedRedirect","redirectToUse","allowAbsoluteUrls","redirectTo","error","status","Array","isArray","csrf","failedRedirectOrigin","redirectToUseOrigin","includes","logger","name","decrypt","sig","meta","token_endpoint","body","code","client_id","clientID","code_verifier","verifier","grant_type","redirect_uri","apiRoute","api","slug","clientSecret","client_secret","requestBody","tokenRes","fetch","URLSearchParams","method","then","r","access_token","error_description","expires_in","refreshTokenExpires","err","figmaUserInfo","errMsg","project_token","custom","figma","contentSystemId","refresh_token","token","expiresIn","expiresAt","cookieOptions","cookies","payloadRedirect","responseHeaders","Headers","append","usePayloadJWT","refreshCookie","returnCookieAsObject","cookieName","value","expires","tokenExpires","Date","tokenCookie","userInfoValue","stringify","handle","img_url","userInfoCookie","now"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-config.d.ts","sourceRoot":"","sources":["../../src/plugin/build-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"build-config.d.ts","sourceRoot":"","sources":["../../src/plugin/build-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAS,eAAe,EAAE,MAAM,SAAS,CAAA;AAkB7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;CAC1B,GAAG;IACF,KAAK,EAAE;QACL,eAAe,EAAE,MAAM,CAAA;QACvB,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAC3B,CAAA;CACF,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,QAAQ,GAAG,QAAQ,CAAC,GAC1C,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;AA6B7B;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CA8IpF"}
|
|
@@ -10,6 +10,31 @@ import { oAuth2Plugin } from '../oauth/index.js';
|
|
|
10
10
|
import { createStorageClient } from '../storage-content-api/client.js';
|
|
11
11
|
import { getGenerateSignedURLHandler } from '../storage-content-api/client-uploads/generateSignedURL.js';
|
|
12
12
|
import { contentApiStorageAdapter } from '../storage-content-api/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* Fields added to users collection for Figma profile info
|
|
15
|
+
*/ const figmaUserFields = [
|
|
16
|
+
{
|
|
17
|
+
name: 'figmaHandle',
|
|
18
|
+
type: 'text',
|
|
19
|
+
admin: {
|
|
20
|
+
readOnly: true
|
|
21
|
+
},
|
|
22
|
+
label: 'Name'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'figmaImageUrl',
|
|
26
|
+
type: 'text',
|
|
27
|
+
admin: {
|
|
28
|
+
components: {
|
|
29
|
+
Cell: '@payloadcms/figma/client#FigmaAvatarCell'
|
|
30
|
+
},
|
|
31
|
+
disableBulkEdit: true,
|
|
32
|
+
hidden: true,
|
|
33
|
+
readOnly: true
|
|
34
|
+
},
|
|
35
|
+
label: 'Profile Picture'
|
|
36
|
+
}
|
|
37
|
+
];
|
|
13
38
|
/**
|
|
14
39
|
* Figma platform wrapper for Payload's buildConfig.
|
|
15
40
|
* Automatically injects:
|
|
@@ -107,9 +132,10 @@ import { contentApiStorageAdapter } from '../storage-content-api/index.js';
|
|
|
107
132
|
...config,
|
|
108
133
|
admin: {
|
|
109
134
|
// Auto-refresh tokens before expiry to prevent unexpected logouts.
|
|
110
|
-
// The project token (access token) expires in ~15 minutes, so the client
|
|
111
|
-
// will automatically refresh at ~14 minutes to keep the session alive.
|
|
112
135
|
autoRefresh: true,
|
|
136
|
+
avatar: {
|
|
137
|
+
Component: '@payloadcms/figma/client#FigmaAvatar'
|
|
138
|
+
},
|
|
113
139
|
...config.admin
|
|
114
140
|
},
|
|
115
141
|
custom: {
|
|
@@ -152,6 +178,22 @@ import { contentApiStorageAdapter } from '../storage-content-api/index.js';
|
|
|
152
178
|
})
|
|
153
179
|
]
|
|
154
180
|
};
|
|
181
|
+
// Inject figma fields into users collection
|
|
182
|
+
const collections = configWithFigmaDefaults.collections?.map((col)=>{
|
|
183
|
+
if (col.slug === 'users') {
|
|
184
|
+
return {
|
|
185
|
+
...col,
|
|
186
|
+
fields: [
|
|
187
|
+
...col.fields || [],
|
|
188
|
+
...figmaUserFields
|
|
189
|
+
]
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return col;
|
|
193
|
+
});
|
|
194
|
+
if (collections) {
|
|
195
|
+
configWithFigmaDefaults.collections = collections;
|
|
196
|
+
}
|
|
155
197
|
// Delegate to Payload's buildConfig
|
|
156
198
|
return await payloadBuildConfig(configWithFigmaDefaults);
|
|
157
199
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugin/build-config.ts"],"sourcesContent":["import type { Config, SanitizedConfig } from 'payload'\n\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport { initClientUploads } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { lexicalEditor } from '@payloadcms/richtext-lexical'\nimport { buildConfig as payloadBuildConfig } from 'payload'\n\nimport type { StorageClientConfig } from '../storage-content-api/index.js'\n\nimport { getTokenStore } from '../auth/token-store.js'\nimport { ENV_CONFIG, getEnvironment } from '../constants.js'\nimport { contentAPIAdapter } from '../db-content-api/index.js'\nimport { health } from '../endpoints/health.js'\nimport { oAuth2Plugin } from '../oauth/index.js'\nimport { createStorageClient } from '../storage-content-api/client.js'\nimport { getGenerateSignedURLHandler } from '../storage-content-api/client-uploads/generateSignedURL.js'\nimport { contentApiStorageAdapter } from '../storage-content-api/index.js'\n\n/**\n * Configuration type for Payload projects on the Figma platform.\n *\n * Differences from standard Payload Config:\n * - `db`: Omitted (auto-injected by Figma platform)\n * - `secret`: Omitted (auto-injected by Figma platform)\n * - `editor`: Optional (defaults to lexicalEditor() if not provided)\n *\n * @example\n * // Minimal config\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!\n * },\n * collections: [...]\n * }\n *\n * @example\n * // With custom editor\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!\n * },\n * collections: [...],\n * editor: lexicalEditor({ features: [...] })\n * }\n *\n * @example\n * // Disable Content System (use custom db)\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!,\n * useContentSystem: false\n * },\n * db: mongooseAdapter({ url: process.env.DATABASE_URI }),\n * collections: [...]\n * }\n */\nexport type FigmaConfig = {\n editor?: Config['editor']\n} & {\n figma: {\n contentSystemId: string\n storage?: boolean\n useContentSystem?: boolean\n }\n} & Omit<Config, 'db' | 'editor' | 'secret'> &\n Partial<Pick<Config, 'db'>>\n\n/**\n * Figma platform wrapper for Payload's buildConfig.\n * Automatically injects:\n * - Figma database adapter\n * - Secret (platform-provided)\n * - Default lexicalEditor (if no custom editor specified)\n *\n * @param config - Payload configuration (db and secret omitted, editor optional)\n * @returns Promise<SanitizedConfig> - Built Payload configuration\n */\nexport async function buildFigmaConfig(config: FigmaConfig): Promise<SanitizedConfig> {\n // Validate required environment variable\n if (!config.figma.contentSystemId) {\n throw new Error(\n 'FIGMA_CONTENT_SYSTEM_ID is required. ' +\n 'Ensure your .env file contains: FIGMA_CONTENT_SYSTEM_ID=your-content-system-id',\n )\n }\n\n const env = getEnvironment()\n const url = process.env.FIGMA_CONTENT_API_URL || ENV_CONFIG[env].contentApiUrl\n const { contentSystemId } = config.figma\n\n // Determine database adapter based on environment\n let db: Config['db']\n\n if (config.figma.useContentSystem === false) {\n db = config.db!\n } else if (process.env.FIGMA_CONTENT_API_ACCESS_KEY) {\n db = contentAPIAdapter({\n auth: { apiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY, mode: 'apiKey' },\n contentSystemId,\n url,\n })\n } else if (process.env.FIGMA_DEV_JWT === 'true') {\n db = contentAPIAdapter({\n auth: { mode: 'devJwt' },\n contentSystemId,\n url,\n })\n } else {\n db = contentAPIAdapter({\n auth: { mode: 'tokenStore', tokenStore: getTokenStore(getEnvironment()) },\n contentSystemId,\n url,\n })\n }\n\n // Build storage plugin if there are upload collections and storage is not disabled\n const uploadCollections = (config.collections || []).filter((c) => c.upload)\n let storagePlugin: ReturnType<typeof cloudStoragePlugin> | undefined\n\n if (uploadCollections.length > 0 && config.figma.storage !== false) {\n const storageConfig: StorageClientConfig = process.env.FIGMA_CONTENT_API_ACCESS_KEY\n ? { baseUrl: url, contentApiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY }\n : process.env.FIGMA_DEV_JWT === 'true'\n ? {\n auth: { mode: 'devJwt' },\n baseUrl: url,\n contentSystemId,\n }\n : {\n auth: { mode: 'tokenStore', tokenStore: getTokenStore(getEnvironment()) },\n baseUrl: url,\n contentSystemId,\n }\n\n const adapter = contentApiStorageAdapter(storageConfig)\n const storageClient = createStorageClient(storageConfig)\n\n const collectionsMap = uploadCollections.reduce(\n (acc, c) => {\n acc[c.slug] = { adapter, disableLocalStorage: true }\n return acc\n },\n {} as Record<string, { adapter: typeof adapter; disableLocalStorage: boolean }>,\n )\n\n initClientUploads({\n clientHandler: '@payloadcms/figma/client#ContentApiClientUploadHandler',\n collections: collectionsMap,\n config: config as unknown as Config,\n enabled: true,\n serverHandler: getGenerateSignedURLHandler({ client: storageClient }),\n serverHandlerPath: '/content-api-storage-signed-url',\n })\n\n storagePlugin = cloudStoragePlugin({ collections: collectionsMap })\n }\n\n // Build complete config with Figma platform defaults\n const configWithFigmaDefaults: Config = {\n ...config,\n admin: {\n // Auto-refresh tokens before expiry to prevent unexpected logouts.\n // The project token (access token) expires in ~15 minutes, so the client\n // will automatically refresh at ~14 minutes to keep the session alive.\n autoRefresh: true,\n ...config.admin,\n },\n custom: {\n figma: config.figma || {},\n },\n db,\n editor: config.editor ?? lexicalEditor(),\n endpoints: [...(config.endpoints ?? []), health],\n secret: 'MY_SECRET',\n // Add oauth to plugins if not already present\n plugins: [\n ...(config.plugins ?? []),\n ...(storagePlugin ? [storagePlugin] : []),\n oAuth2Plugin({\n collections: [\n {\n slug: 'users',\n // Use adapter because Figma app doesn't support OIDC scopes\n authorizationURLParams: {\n response_mode: 'query',\n response_type: 'code',\n },\n clientID: process.env.FIGMA_OAUTH_CLIENT_ID!,\n clientSecret: process.env.FIGMA_OAUTH_CLIENT_SECRET,\n identityMetadata: ENV_CONFIG[env].identityMetadata,\n scope: ['current_user:read'],\n token: 'id_token', // Adapter creates JWT id_token from Figma user info\n usernameField: 'email',\n },\n ],\n debug: !!process.env.DEBUG,\n disabled: false,\n }),\n ],\n }\n\n // Delegate to Payload's buildConfig\n return await payloadBuildConfig(configWithFigmaDefaults)\n}\n"],"names":["cloudStoragePlugin","initClientUploads","lexicalEditor","buildConfig","payloadBuildConfig","getTokenStore","ENV_CONFIG","getEnvironment","contentAPIAdapter","health","oAuth2Plugin","createStorageClient","getGenerateSignedURLHandler","contentApiStorageAdapter","buildFigmaConfig","config","figma","contentSystemId","Error","env","url","process","FIGMA_CONTENT_API_URL","contentApiUrl","db","useContentSystem","FIGMA_CONTENT_API_ACCESS_KEY","auth","apiKey","mode","FIGMA_DEV_JWT","tokenStore","uploadCollections","collections","filter","c","upload","storagePlugin","length","storage","storageConfig","baseUrl","contentApiKey","adapter","storageClient","collectionsMap","reduce","acc","slug","disableLocalStorage","clientHandler","enabled","serverHandler","client","serverHandlerPath","configWithFigmaDefaults","admin","autoRefresh","custom","editor","endpoints","secret","plugins","authorizationURLParams","response_mode","response_type","clientID","FIGMA_OAUTH_CLIENT_ID","clientSecret","FIGMA_OAUTH_CLIENT_SECRET","identityMetadata","scope","token","usernameField","debug","DEBUG","disabled"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/plugin/build-config.ts"],"sourcesContent":["import type { Config, Field, SanitizedConfig } from 'payload'\n\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport { initClientUploads } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { lexicalEditor } from '@payloadcms/richtext-lexical'\nimport { buildConfig as payloadBuildConfig } from 'payload'\n\nimport type { StorageClientConfig } from '../storage-content-api/index.js'\n\nimport { getTokenStore } from '../auth/token-store.js'\nimport { ENV_CONFIG, getEnvironment } from '../constants.js'\nimport { contentAPIAdapter } from '../db-content-api/index.js'\nimport { health } from '../endpoints/health.js'\nimport { oAuth2Plugin } from '../oauth/index.js'\nimport { createStorageClient } from '../storage-content-api/client.js'\nimport { getGenerateSignedURLHandler } from '../storage-content-api/client-uploads/generateSignedURL.js'\nimport { contentApiStorageAdapter } from '../storage-content-api/index.js'\n\n/**\n * Configuration type for Payload projects on the Figma platform.\n *\n * Differences from standard Payload Config:\n * - `db`: Omitted (auto-injected by Figma platform)\n * - `secret`: Omitted (auto-injected by Figma platform)\n * - `editor`: Optional (defaults to lexicalEditor() if not provided)\n *\n * @example\n * // Minimal config\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!\n * },\n * collections: [...]\n * }\n *\n * @example\n * // With custom editor\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!\n * },\n * collections: [...],\n * editor: lexicalEditor({ features: [...] })\n * }\n *\n * @example\n * // Disable Content System (use custom db)\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!,\n * useContentSystem: false\n * },\n * db: mongooseAdapter({ url: process.env.DATABASE_URI }),\n * collections: [...]\n * }\n */\nexport type FigmaConfig = {\n editor?: Config['editor']\n} & {\n figma: {\n contentSystemId: string\n storage?: boolean\n useContentSystem?: boolean\n }\n} & Omit<Config, 'db' | 'editor' | 'secret'> &\n Partial<Pick<Config, 'db'>>\n\n/**\n * Fields added to users collection for Figma profile info\n */\nconst figmaUserFields: Field[] = [\n {\n name: 'figmaHandle',\n type: 'text',\n admin: {\n readOnly: true,\n },\n label: 'Name',\n },\n {\n name: 'figmaImageUrl',\n type: 'text',\n admin: {\n components: {\n Cell: '@payloadcms/figma/client#FigmaAvatarCell',\n },\n disableBulkEdit: true,\n hidden: true,\n readOnly: true,\n },\n label: 'Profile Picture',\n },\n]\n\n/**\n * Figma platform wrapper for Payload's buildConfig.\n * Automatically injects:\n * - Figma database adapter\n * - Secret (platform-provided)\n * - Default lexicalEditor (if no custom editor specified)\n *\n * @param config - Payload configuration (db and secret omitted, editor optional)\n * @returns Promise<SanitizedConfig> - Built Payload configuration\n */\nexport async function buildFigmaConfig(config: FigmaConfig): Promise<SanitizedConfig> {\n // Validate required environment variable\n if (!config.figma.contentSystemId) {\n throw new Error(\n 'FIGMA_CONTENT_SYSTEM_ID is required. ' +\n 'Ensure your .env file contains: FIGMA_CONTENT_SYSTEM_ID=your-content-system-id',\n )\n }\n\n const env = getEnvironment()\n const url = process.env.FIGMA_CONTENT_API_URL || ENV_CONFIG[env].contentApiUrl\n const { contentSystemId } = config.figma\n\n // Determine database adapter based on environment\n let db: Config['db']\n\n if (config.figma.useContentSystem === false) {\n db = config.db!\n } else if (process.env.FIGMA_CONTENT_API_ACCESS_KEY) {\n db = contentAPIAdapter({\n auth: { apiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY, mode: 'apiKey' },\n contentSystemId,\n url,\n })\n } else if (process.env.FIGMA_DEV_JWT === 'true') {\n db = contentAPIAdapter({\n auth: { mode: 'devJwt' },\n contentSystemId,\n url,\n })\n } else {\n db = contentAPIAdapter({\n auth: { mode: 'tokenStore', tokenStore: getTokenStore(getEnvironment()) },\n contentSystemId,\n url,\n })\n }\n\n // Build storage plugin if there are upload collections and storage is not disabled\n const uploadCollections = (config.collections || []).filter((c) => c.upload)\n let storagePlugin: ReturnType<typeof cloudStoragePlugin> | undefined\n\n if (uploadCollections.length > 0 && config.figma.storage !== false) {\n const storageConfig: StorageClientConfig = process.env.FIGMA_CONTENT_API_ACCESS_KEY\n ? { baseUrl: url, contentApiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY }\n : process.env.FIGMA_DEV_JWT === 'true'\n ? {\n auth: { mode: 'devJwt' },\n baseUrl: url,\n contentSystemId,\n }\n : {\n auth: { mode: 'tokenStore', tokenStore: getTokenStore(getEnvironment()) },\n baseUrl: url,\n contentSystemId,\n }\n\n const adapter = contentApiStorageAdapter(storageConfig)\n const storageClient = createStorageClient(storageConfig)\n\n const collectionsMap = uploadCollections.reduce(\n (acc, c) => {\n acc[c.slug] = { adapter, disableLocalStorage: true }\n return acc\n },\n {} as Record<string, { adapter: typeof adapter; disableLocalStorage: boolean }>,\n )\n\n initClientUploads({\n clientHandler: '@payloadcms/figma/client#ContentApiClientUploadHandler',\n collections: collectionsMap,\n config: config as unknown as Config,\n enabled: true,\n serverHandler: getGenerateSignedURLHandler({ client: storageClient }),\n serverHandlerPath: '/content-api-storage-signed-url',\n })\n\n storagePlugin = cloudStoragePlugin({ collections: collectionsMap })\n }\n\n // Build complete config with Figma platform defaults\n const configWithFigmaDefaults: Config = {\n ...config,\n admin: {\n // Auto-refresh tokens before expiry to prevent unexpected logouts.\n autoRefresh: true,\n avatar: {\n Component: '@payloadcms/figma/client#FigmaAvatar',\n },\n ...config.admin,\n },\n custom: {\n figma: config.figma || {},\n },\n db,\n editor: config.editor ?? lexicalEditor(),\n endpoints: [...(config.endpoints ?? []), health],\n secret: 'MY_SECRET',\n // Add oauth to plugins if not already present\n plugins: [\n ...(config.plugins ?? []),\n ...(storagePlugin ? [storagePlugin] : []),\n oAuth2Plugin({\n collections: [\n {\n slug: 'users',\n // Use adapter because Figma app doesn't support OIDC scopes\n authorizationURLParams: {\n response_mode: 'query',\n response_type: 'code',\n },\n clientID: process.env.FIGMA_OAUTH_CLIENT_ID!,\n clientSecret: process.env.FIGMA_OAUTH_CLIENT_SECRET,\n identityMetadata: ENV_CONFIG[env].identityMetadata,\n scope: ['current_user:read'],\n token: 'id_token', // Adapter creates JWT id_token from Figma user info\n usernameField: 'email',\n },\n ],\n debug: !!process.env.DEBUG,\n disabled: false,\n }),\n ],\n }\n\n // Inject figma fields into users collection\n const collections = configWithFigmaDefaults.collections?.map((col) => {\n if (col.slug === 'users') {\n return {\n ...col,\n fields: [...(col.fields || []), ...figmaUserFields],\n }\n }\n return col\n })\n\n if (collections) {\n configWithFigmaDefaults.collections = collections\n }\n\n // Delegate to Payload's buildConfig\n return await payloadBuildConfig(configWithFigmaDefaults)\n}\n"],"names":["cloudStoragePlugin","initClientUploads","lexicalEditor","buildConfig","payloadBuildConfig","getTokenStore","ENV_CONFIG","getEnvironment","contentAPIAdapter","health","oAuth2Plugin","createStorageClient","getGenerateSignedURLHandler","contentApiStorageAdapter","figmaUserFields","name","type","admin","readOnly","label","components","Cell","disableBulkEdit","hidden","buildFigmaConfig","config","figma","contentSystemId","Error","env","url","process","FIGMA_CONTENT_API_URL","contentApiUrl","db","useContentSystem","FIGMA_CONTENT_API_ACCESS_KEY","auth","apiKey","mode","FIGMA_DEV_JWT","tokenStore","uploadCollections","collections","filter","c","upload","storagePlugin","length","storage","storageConfig","baseUrl","contentApiKey","adapter","storageClient","collectionsMap","reduce","acc","slug","disableLocalStorage","clientHandler","enabled","serverHandler","client","serverHandlerPath","configWithFigmaDefaults","autoRefresh","avatar","Component","custom","editor","endpoints","secret","plugins","authorizationURLParams","response_mode","response_type","clientID","FIGMA_OAUTH_CLIENT_ID","clientSecret","FIGMA_OAUTH_CLIENT_SECRET","identityMetadata","scope","token","usernameField","debug","DEBUG","disabled","map","col","fields"],"mappings":"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"}
|