@payloadcms/figma 0.0.1-alpha.28 → 0.0.1-alpha.3
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/control-plane.d.ts +1 -19
- package/dist/api/control-plane.d.ts.map +1 -1
- package/dist/api/control-plane.js +13 -48
- package/dist/api/control-plane.js.map +1 -1
- package/dist/api/figma-api.d.ts.map +1 -1
- package/dist/api/figma-api.js +12 -38
- package/dist/api/figma-api.js.map +1 -1
- package/dist/auth/crypto-utils.d.ts.map +1 -1
- package/dist/auth/crypto-utils.js +5 -5
- package/dist/auth/crypto-utils.js.map +1 -1
- package/dist/auth/oauth-flow.js +3 -3
- package/dist/auth/oauth-flow.js.map +1 -1
- package/dist/auth/project-token.d.ts.map +1 -1
- package/dist/auth/project-token.js +4 -9
- package/dist/auth/project-token.js.map +1 -1
- package/dist/auth/token-store.d.ts +0 -10
- package/dist/auth/token-store.d.ts.map +1 -1
- package/dist/auth/token-store.js +0 -20
- package/dist/auth/token-store.js.map +1 -1
- package/dist/cli.js +0 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/deploy.d.ts +0 -2
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +45 -12
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/init.d.ts +5 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +85 -90
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list-tokens.js +2 -2
- package/dist/commands/list-tokens.js.map +1 -1
- package/dist/commands/login.d.ts +1 -8
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +6 -12
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.js +2 -2
- package/dist/commands/logout.js.map +1 -1
- package/dist/config/oauth.d.ts.map +1 -1
- package/dist/config/oauth.js +3 -4
- package/dist/config/oauth.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/oauth/components/LoginButton/index.d.ts.map +1 -1
- package/dist/oauth/components/LoginButton/index.js +6 -50
- package/dist/oauth/components/LoginButton/index.js.map +1 -1
- package/dist/oauth/components/LoginButton/index.scss +3 -50
- package/dist/oauth/components/LogoutButton/index.d.ts.map +1 -1
- package/dist/oauth/components/LogoutButton/index.js +1 -0
- package/dist/oauth/components/LogoutButton/index.js.map +1 -1
- package/dist/oauth/endpoints/getLoginEndpoint.d.ts.map +1 -1
- package/dist/oauth/endpoints/getLoginEndpoint.js +1 -1
- package/dist/oauth/endpoints/getLoginEndpoint.js.map +1 -1
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js +6 -30
- package/dist/oauth/index.js.map +1 -1
- package/dist/oauth/strategy/index.d.ts.map +1 -1
- package/dist/oauth/strategy/index.js +0 -4
- package/dist/oauth/strategy/index.js.map +1 -1
- package/dist/oauth/types.d.ts +1 -1
- package/dist/oauth/types.d.ts.map +1 -1
- package/dist/oauth/types.js.map +1 -1
- package/dist/oauth/utilities/createCookieOptions.d.ts.map +1 -1
- package/dist/oauth/utilities/createCookieOptions.js.map +1 -1
- package/dist/oauth/utilities/refreshTokens.d.ts.map +1 -1
- package/dist/oauth/utilities/refreshTokens.js +1 -1
- package/dist/oauth/utilities/refreshTokens.js.map +1 -1
- package/dist/plugin/build-config.d.ts +4 -20
- package/dist/plugin/build-config.d.ts.map +1 -1
- package/dist/plugin/build-config.js +9 -51
- package/dist/plugin/build-config.js.map +1 -1
- package/dist/utils/env-management.d.ts +11 -4
- package/dist/utils/env-management.d.ts.map +1 -1
- package/dist/utils/env-management.js +79 -46
- package/dist/utils/env-management.js.map +1 -1
- package/dist/utils/messages.d.ts.map +1 -1
- package/dist/utils/messages.js +7 -5
- package/dist/utils/messages.js.map +1 -1
- package/dist/utils/payload-config-ast.d.ts +3 -22
- package/dist/utils/payload-config-ast.d.ts.map +1 -1
- package/dist/utils/payload-config-ast.js +31 -89
- package/dist/utils/payload-config-ast.js.map +1 -1
- package/dist/utils/payload-config-finder.d.ts +2 -2
- package/dist/utils/payload-config-finder.d.ts.map +1 -1
- package/dist/utils/payload-config-finder.js +9 -48
- package/dist/utils/payload-config-finder.js.map +1 -1
- package/dist/utils/payload-config-modifier.d.ts.map +1 -1
- package/dist/utils/payload-config-modifier.js +4 -33
- package/dist/utils/payload-config-modifier.js.map +1 -1
- package/dist/utils/payload-package-check.d.ts +0 -5
- package/dist/utils/payload-package-check.d.ts.map +1 -1
- package/dist/utils/payload-package-check.js +0 -41
- package/dist/utils/payload-package-check.js.map +1 -1
- package/dist/utils/project.d.ts.map +1 -1
- package/dist/utils/project.js +3 -1
- package/dist/utils/project.js.map +1 -1
- package/dist/utils/s3-upload.d.ts +1 -0
- package/dist/utils/s3-upload.d.ts.map +1 -1
- package/dist/utils/s3-upload.js +3 -10
- package/dist/utils/s3-upload.js.map +1 -1
- package/dist/utils/token-display.d.ts.map +1 -1
- package/dist/utils/token-display.js +5 -11
- package/dist/utils/token-display.js.map +1 -1
- package/package.json +4 -18
- package/dist/db-content-api/generated/content-api-types.d.ts +0 -3129
- package/dist/db-content-api/generated/content-api-types.d.ts.map +0 -1
- package/dist/db-content-api/generated/content-api-types.js +0 -7
- package/dist/db-content-api/generated/content-api-types.js.map +0 -1
- package/dist/db-content-api/index.d.ts +0 -31
- package/dist/db-content-api/index.d.ts.map +0 -1
- package/dist/db-content-api/index.js +0 -842
- package/dist/db-content-api/index.js.map +0 -1
- package/dist/db-content-api/types.d.ts +0 -31
- package/dist/db-content-api/types.d.ts.map +0 -1
- package/dist/db-content-api/types.js +0 -7
- package/dist/db-content-api/types.js.map +0 -1
- package/dist/endpoints/health.d.ts +0 -3
- package/dist/endpoints/health.d.ts.map +0 -1
- package/dist/endpoints/health.js +0 -11
- package/dist/endpoints/health.js.map +0 -1
- package/dist/exports/client.d.ts +0 -3
- package/dist/exports/client.d.ts.map +0 -1
- package/dist/exports/client.js +0 -4
- package/dist/exports/client.js.map +0 -1
|
@@ -44,8 +44,8 @@ export interface CreateDeploymentResponse {
|
|
|
44
44
|
export interface CreateDeploymentApiResponse {
|
|
45
45
|
error: boolean;
|
|
46
46
|
meta: {
|
|
47
|
+
code_upload_url: string;
|
|
47
48
|
deployment_id: string;
|
|
48
|
-
lambda_zip_upload_url: string;
|
|
49
49
|
static_asset_upload_urls: Record<string, string>;
|
|
50
50
|
};
|
|
51
51
|
status: number;
|
|
@@ -75,15 +75,6 @@ export interface PerformDeploymentApiResponse {
|
|
|
75
75
|
};
|
|
76
76
|
status: number;
|
|
77
77
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Response from getDatasetInfo endpoint
|
|
80
|
-
*/
|
|
81
|
-
export type DatasetInfo = {
|
|
82
|
-
cmsId: string;
|
|
83
|
-
oauthClientId: string;
|
|
84
|
-
oauthClientSecret: string;
|
|
85
|
-
tenantId: string;
|
|
86
|
-
};
|
|
87
78
|
/**
|
|
88
79
|
* List all tenant instances for the authenticated user
|
|
89
80
|
* Maps to: GET /v1/tenant
|
|
@@ -130,13 +121,4 @@ export declare function createDeployment(accessToken: string, tenantId: string,
|
|
|
130
121
|
* @returns Deployment status and message
|
|
131
122
|
*/
|
|
132
123
|
export declare function performDeployment(accessToken: string, tenantId: string, options: PerformDeploymentOptions): Promise<PerformDeploymentResponse>;
|
|
133
|
-
/**
|
|
134
|
-
* Get dataset info including tenant ID and OAuth credentials
|
|
135
|
-
* Maps to: GET /v1/cms/dataset/:datasetId
|
|
136
|
-
*
|
|
137
|
-
* @param accessToken - OAuth access token
|
|
138
|
-
* @param datasetId - Content System ID (dataset ID)
|
|
139
|
-
* @returns Dataset info with tenant ID and OAuth credentials
|
|
140
|
-
*/
|
|
141
|
-
export declare function getDatasetInfo(accessToken: string, datasetId: string): Promise<DatasetInfo>;
|
|
142
124
|
//# sourceMappingURL=control-plane.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../../src/api/control-plane.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAoBhD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAGjC,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;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,WAAW,CAAA;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kDAAkD;IAClD,aAAa,EAAE,MAAM,CAAA;IACrB,2BAA2B;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9C;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE;QACJ,
|
|
1
|
+
{"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../../src/api/control-plane.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAoBhD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAGjC,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;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,WAAW,CAAA;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kDAAkD;IAClD,aAAa,EAAE,MAAM,CAAA;IACrB,2BAA2B;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9C;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE;QACJ,eAAe,EAAE,MAAM,CAAA;QACvB,aAAa,EAAE,MAAM,CAAA;QACrB,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACjD,CAAA;IACD,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAA;IACpB,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE;QACJ,aAAa,EAAE,MAAM,CAAA;QACrB,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;IACD,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAyCxE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAuCjB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgC7F;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CA0DnC;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,yBAAyB,CAAC,CAwCpC"}
|
|
@@ -8,7 +8,7 @@ import * as log from '../utils/log.js';
|
|
|
8
8
|
* Control Plane API base URLs
|
|
9
9
|
*/ const CONTROL_PLANE_API_URLS = {
|
|
10
10
|
production: 'https://api.figma.com',
|
|
11
|
-
staging:
|
|
11
|
+
staging: 'https://api.staging.figma.com'
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
* Get the Control Plane API base URL based on environment
|
|
@@ -32,7 +32,9 @@ import * as log from '../utils/log.js';
|
|
|
32
32
|
* @param accessToken - OAuth access token
|
|
33
33
|
* @returns Array of tenant instances
|
|
34
34
|
*/ export async function listTenants(accessToken) {
|
|
35
|
-
|
|
35
|
+
// Check if control plane API should be mocked
|
|
36
|
+
// Default to true until real API is available
|
|
37
|
+
const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false';
|
|
36
38
|
if (shouldMock) {
|
|
37
39
|
// MOCK IMPLEMENTATION: Return sample tenants for testing
|
|
38
40
|
log.debug('Using mock Control Plane API for listTenants (FIGMA_MOCK_CONTROL_PLANE != "false")');
|
|
@@ -75,7 +77,7 @@ import * as log from '../utils/log.js';
|
|
|
75
77
|
* @returns Newly created tenant
|
|
76
78
|
*/ export async function createTenant(accessToken, options) {
|
|
77
79
|
// Check if control plane API should be mocked
|
|
78
|
-
const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE
|
|
80
|
+
const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false';
|
|
79
81
|
if (shouldMock) {
|
|
80
82
|
// MOCK IMPLEMENTATION: Generate mock tenant for testing
|
|
81
83
|
log.debug('Using mock Control Plane API for createTenant (FIGMA_MOCK_CONTROL_PLANE != "false")');
|
|
@@ -116,7 +118,7 @@ import * as log from '../utils/log.js';
|
|
|
116
118
|
* @returns Tenant details
|
|
117
119
|
*/ export async function getTenantDetails(accessToken, tenantId) {
|
|
118
120
|
// Check if control plane API should be mocked
|
|
119
|
-
const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE
|
|
121
|
+
const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false';
|
|
120
122
|
if (shouldMock) {
|
|
121
123
|
// MOCK IMPLEMENTATION: Return mock tenant details for testing
|
|
122
124
|
log.debug('Using mock Control Plane API for getTenantDetails (FIGMA_MOCK_CONTROL_PLANE != "false")');
|
|
@@ -150,10 +152,10 @@ import * as log from '../utils/log.js';
|
|
|
150
152
|
* @returns Deployment ID and upload URLs
|
|
151
153
|
*/ export async function createDeployment(accessToken, tenantId, options) {
|
|
152
154
|
// Check if control plane API should be mocked
|
|
153
|
-
const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE
|
|
155
|
+
const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false';
|
|
154
156
|
if (shouldMock) {
|
|
155
157
|
// MOCK IMPLEMENTATION: Generate mock deployment and signed URLs
|
|
156
|
-
log.debug('Using mock Control Plane API for createDeployment (FIGMA_MOCK_CONTROL_PLANE
|
|
158
|
+
log.debug('Using mock Control Plane API for createDeployment (FIGMA_MOCK_CONTROL_PLANE != "false")');
|
|
157
159
|
const deploymentId = `deploy_${Math.random().toString(36).substring(2, 11)}`;
|
|
158
160
|
const mockBucket = 'figma-cms-deployments-mock';
|
|
159
161
|
const mockRegion = 'us-west-2';
|
|
@@ -172,10 +174,8 @@ import * as log from '../utils/log.js';
|
|
|
172
174
|
staticAssetUploadUrls
|
|
173
175
|
};
|
|
174
176
|
}
|
|
175
|
-
const url = `${getControlPlaneBaseUrl()}/v1/cms/tenant/${tenantId}/deploy/create`;
|
|
176
|
-
log.debug(`Calling createDeployment API at ${url}`);
|
|
177
177
|
// REAL API IMPLEMENTATION
|
|
178
|
-
const response = await fetch(
|
|
178
|
+
const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant/${tenantId}/deploy/create`, {
|
|
179
179
|
body: JSON.stringify({
|
|
180
180
|
static_assets: options.staticAssets
|
|
181
181
|
}),
|
|
@@ -191,7 +191,7 @@ import * as log from '../utils/log.js';
|
|
|
191
191
|
const data = await response.json();
|
|
192
192
|
// Map API response format to our interface
|
|
193
193
|
return {
|
|
194
|
-
codeUploadUrl: data.meta.
|
|
194
|
+
codeUploadUrl: data.meta.code_upload_url,
|
|
195
195
|
deploymentId: data.meta.deployment_id,
|
|
196
196
|
staticAssetUploadUrls: data.meta.static_asset_upload_urls
|
|
197
197
|
};
|
|
@@ -205,7 +205,8 @@ import * as log from '../utils/log.js';
|
|
|
205
205
|
* @param options - Deployment perform options
|
|
206
206
|
* @returns Deployment status and message
|
|
207
207
|
*/ export async function performDeployment(accessToken, tenantId, options) {
|
|
208
|
-
|
|
208
|
+
// Check if control plane API should be mocked
|
|
209
|
+
const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false';
|
|
209
210
|
if (shouldMock) {
|
|
210
211
|
// MOCK IMPLEMENTATION: Return mock deployment status
|
|
211
212
|
log.debug('Using mock Control Plane API for performDeployment (FIGMA_MOCK_CONTROL_PLANE != "false")');
|
|
@@ -214,10 +215,8 @@ import * as log from '../utils/log.js';
|
|
|
214
215
|
message: 'Deployment initiated successfully'
|
|
215
216
|
};
|
|
216
217
|
}
|
|
217
|
-
const url = `${getControlPlaneBaseUrl()}/v1/cms/tenant/${tenantId}/deploy/perform`;
|
|
218
|
-
log.debug(`Calling performDeployment API at ${url}`);
|
|
219
218
|
// REAL API IMPLEMENTATION
|
|
220
|
-
const response = await fetch(
|
|
219
|
+
const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant/${tenantId}/deploy/perform`, {
|
|
221
220
|
body: JSON.stringify({
|
|
222
221
|
deployment_id: options.deploymentId
|
|
223
222
|
}),
|
|
@@ -237,39 +236,5 @@ import * as log from '../utils/log.js';
|
|
|
237
236
|
message: data.meta.message || 'Deployment initiated'
|
|
238
237
|
};
|
|
239
238
|
}
|
|
240
|
-
/**
|
|
241
|
-
* Get dataset info including tenant ID and OAuth credentials
|
|
242
|
-
* Maps to: GET /v1/cms/dataset/:datasetId
|
|
243
|
-
*
|
|
244
|
-
* @param accessToken - OAuth access token
|
|
245
|
-
* @param datasetId - Content System ID (dataset ID)
|
|
246
|
-
* @returns Dataset info with tenant ID and OAuth credentials
|
|
247
|
-
*/ export async function getDatasetInfo(accessToken, datasetId) {
|
|
248
|
-
const url = `${getControlPlaneBaseUrl()}/v1/cms/dataset/${datasetId}`;
|
|
249
|
-
log.debug(`Calling getDatasetInfo API at ${url}`);
|
|
250
|
-
const response = await fetch(url, {
|
|
251
|
-
headers: {
|
|
252
|
-
Authorization: `Bearer ${accessToken}`
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
if (!response.ok) {
|
|
256
|
-
throw new ControlPlaneError(`Failed to get dataset info: ${response.status} ${response.statusText}`, response.status);
|
|
257
|
-
}
|
|
258
|
-
const data = await response.json();
|
|
259
|
-
const sanitizedData = {
|
|
260
|
-
...data,
|
|
261
|
-
meta: {
|
|
262
|
-
...data.meta,
|
|
263
|
-
oauth_client_secret: '[REDACTED]'
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
log.debug(`Dataset API response: ${JSON.stringify(sanitizedData, null, 2)}`);
|
|
267
|
-
return {
|
|
268
|
-
cmsId: data.meta.cms_resource_id,
|
|
269
|
-
oauthClientId: data.meta.oauth_client_id,
|
|
270
|
-
oauthClientSecret: data.meta.oauth_client_secret,
|
|
271
|
-
tenantId: data.meta.tenant_instance_id
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
239
|
|
|
275
240
|
//# sourceMappingURL=control-plane.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/control-plane.ts"],"sourcesContent":["/**\n * Control Plane API Client\n *\n * TODO: Replace with real API calls when Control Plane is available\n */\n\nimport type { Tenant } from '../types/config.js'\n\nimport { getEnvironment } from '../constants.js'\nimport * as log from '../utils/log.js'\n\n/**\n * Control Plane API base URLs\n */\nconst CONTROL_PLANE_API_URLS = {\n production: 'https://api.figma.com',\n staging: process.env.FIGMA_API_URL || 'https://api.staging.figma.com',\n}\n\n/**\n * Get the Control Plane API base URL based on environment\n */\nfunction getControlPlaneBaseUrl(): string {\n return process.env.FIGMA_API_BASE_URL || CONTROL_PLANE_API_URLS[getEnvironment()]\n}\n\n/**\n * Error thrown when Control Plane API calls fail\n */\nexport class ControlPlaneError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public cause?: Error,\n ) {\n super(message)\n this.name = 'ControlPlaneError'\n }\n}\n\n/**\n * Options for creating a new tenant\n */\nexport interface CreateTenantOptions {\n /** Name for the new CMS instance */\n name: string\n /** Optional parent ID */\n parentId?: string\n /** Optional parent type */\n parentType?: 'org' | 'team' | 'workspace'\n}\n\n/**\n * Options for creating a new deployment\n */\nexport interface CreateDeploymentOptions {\n /** List of static asset paths that need upload URLs */\n staticAssets: string[]\n}\n\n/**\n * Response from creating a new deployment\n */\nexport interface CreateDeploymentResponse {\n /** Upload URL for the Lambda function code zip */\n codeUploadUrl: string\n /** Unique deployment ID */\n deploymentId: string\n /** Map of static asset paths to their signed S3 upload URLs */\n staticAssetUploadUrls: Record<string, string>\n}\n\nexport interface CreateDeploymentApiResponse {\n error: boolean\n meta: {\n deployment_id: string\n lambda_zip_upload_url: string\n static_asset_upload_urls: Record<string, string>\n }\n status: number\n}\n\n/**\n * Options for performing a deployment\n */\nexport interface PerformDeploymentOptions {\n /** Deployment ID from createDeployment */\n deploymentId: string\n}\n\n/**\n * Response from performing a deployment\n */\nexport interface PerformDeploymentResponse {\n /** Deployment ID that was deployed */\n deploymentId: string\n /** Status message */\n message: string\n}\n\nexport interface PerformDeploymentApiResponse {\n error: boolean\n meta: {\n deployment_id: string\n message: string\n status: string\n }\n status: number\n}\n\n/**\n * Response from getDatasetInfo endpoint\n */\nexport type DatasetInfo = {\n cmsId: string\n oauthClientId: string\n oauthClientSecret: string\n tenantId: string\n}\n\n/**\n * Raw API response from /v1/cms/dataset/:datasetId\n */\ntype DatasetInfoApiResponse = {\n error: boolean\n meta: {\n cms_resource_id: string\n oauth_client_id: string\n oauth_client_secret: string\n tenant_instance_id: string\n }\n status: number\n}\n\n/**\n * List all tenant instances for the authenticated user\n * Maps to: GET /v1/tenant\n *\n * @param accessToken - OAuth access token\n * @returns Array of tenant instances\n */\nexport async function listTenants(accessToken: string): Promise<Tenant[]> {\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE === 'true'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Return sample tenants for testing\n log.debug('Using mock Control Plane API for listTenants (FIGMA_MOCK_CONTROL_PLANE != \"false\")')\n return [\n {\n id: 'tenant_abc123',\n createdAt: '2025-01-15T10:00:00Z',\n domain: 'my-blog.figmacms.com',\n parentId: 'team_456',\n parentType: 'team',\n status: 'active',\n },\n {\n id: 'tenant_def456',\n createdAt: '2025-01-10T14:30:00Z',\n domain: 'my-store.figmacms.com',\n parentId: 'team_456',\n parentType: 'team',\n status: 'active',\n },\n ]\n }\n\n // REAL API IMPLEMENTATION\n const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant`, {\n headers: { Authorization: `Bearer ${accessToken}` },\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to list tenants: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n return response.json()\n}\n\n/**\n * Create a new tenant instance\n * Maps to: POST /v1/tenant\n *\n * @param accessToken - OAuth access token\n * @param options - Tenant creation options\n * @returns Newly created tenant\n */\nexport async function createTenant(\n accessToken: string,\n options: CreateTenantOptions,\n): Promise<Tenant> {\n // Check if control plane API should be mocked\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE === 'true'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Generate mock tenant for testing\n log.debug('Using mock Control Plane API for createTenant (FIGMA_MOCK_CONTROL_PLANE != \"false\")')\n const tenantId = `tenant_${Math.random().toString(36).substring(2, 11)}`\n const slug = options.name.toLowerCase().replace(/[^a-z0-9]+/g, '-')\n const domain = `${slug}.figmacms.com`\n\n return {\n id: tenantId,\n createdAt: new Date().toISOString(),\n domain,\n parentId: options.parentId || 'team_456',\n parentType: options.parentType || 'team',\n status: 'provisioning',\n }\n }\n\n // REAL API IMPLEMENTATION\n const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant`, {\n body: JSON.stringify({ name: options.name }),\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to create tenant: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n return response.json()\n}\n\n/**\n * Get details for a specific tenant\n * Maps to: GET /v1/tenant/{tenant_id}\n *\n * @param accessToken - OAuth access token\n * @param tenantId - Tenant ID\n * @returns Tenant details\n */\nexport async function getTenantDetails(accessToken: string, tenantId: string): Promise<Tenant> {\n // Check if control plane API should be mocked\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE === 'true'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Return mock tenant details for testing\n log.debug(\n 'Using mock Control Plane API for getTenantDetails (FIGMA_MOCK_CONTROL_PLANE != \"false\")',\n )\n return {\n id: tenantId,\n createdAt: '2025-01-15T10:00:00Z',\n domain: 'example.figmacms.com',\n parentId: 'team_456',\n parentType: 'team',\n status: 'active',\n }\n }\n\n // REAL API IMPLEMENTATION\n const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant/${tenantId}`, {\n headers: { Authorization: `Bearer ${accessToken}` },\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to get tenant details: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n return response.json()\n}\n\n/**\n * Create a new deployment and get signed upload URLs\n * Maps to: POST /v1/tenant/{tenant_id}/deploy/create\n *\n * @param accessToken - OAuth access token\n * @param tenantId - Tenant ID\n * @param options - Deployment creation options\n * @returns Deployment ID and upload URLs\n */\nexport async function createDeployment(\n accessToken: string,\n tenantId: string,\n options: CreateDeploymentOptions,\n): Promise<CreateDeploymentResponse> {\n // Check if control plane API should be mocked\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE === 'true'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Generate mock deployment and signed URLs\n log.debug(\n 'Using mock Control Plane API for createDeployment (FIGMA_MOCK_CONTROL_PLANE === \"true\")',\n )\n\n const deploymentId = `deploy_${Math.random().toString(36).substring(2, 11)}`\n const mockBucket = 'figma-cms-deployments-mock'\n const mockRegion = 'us-west-2'\n const mockBaseUrl = `https://${mockBucket}.s3.${mockRegion}.amazonaws.com`\n\n // Generate mock signed URLs for static assets\n const staticAssetUploadUrls: Record<string, string> = {}\n for (const assetPath of options.staticAssets) {\n const encodedPath = encodeURIComponent(assetPath)\n staticAssetUploadUrls[assetPath] =\n `${mockBaseUrl}/${tenantId}/${deploymentId}/static/${encodedPath}?X-Amz-Signature=mock`\n }\n\n // Generate mock signed URL for code zip\n const codeUploadUrl = `${mockBaseUrl}/${tenantId}/${deploymentId}/lambda.zip?X-Amz-Signature=mock`\n\n return {\n codeUploadUrl,\n deploymentId,\n staticAssetUploadUrls,\n }\n }\n\n const url = `${getControlPlaneBaseUrl()}/v1/cms/tenant/${tenantId}/deploy/create`\n\n log.debug(`Calling createDeployment API at ${url}`)\n\n // REAL API IMPLEMENTATION\n const response = await fetch(url, {\n body: JSON.stringify({ static_assets: options.staticAssets }),\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to create deployment: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n const data = (await response.json()) as CreateDeploymentApiResponse\n\n // Map API response format to our interface\n return {\n codeUploadUrl: data.meta.lambda_zip_upload_url,\n deploymentId: data.meta.deployment_id,\n staticAssetUploadUrls: data.meta.static_asset_upload_urls,\n }\n}\n\n/**\n * Perform deployment for a created deployment ID\n * Maps to: POST /v1/tenant/{tenant_id}/deploy/perform\n *\n * @param accessToken - OAuth access token\n * @param tenantId - Tenant ID\n * @param options - Deployment perform options\n * @returns Deployment status and message\n */\nexport async function performDeployment(\n accessToken: string,\n tenantId: string,\n options: PerformDeploymentOptions,\n): Promise<PerformDeploymentResponse> {\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE === 'true'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Return mock deployment status\n log.debug(\n 'Using mock Control Plane API for performDeployment (FIGMA_MOCK_CONTROL_PLANE != \"false\")',\n )\n\n return {\n deploymentId: options.deploymentId,\n message: 'Deployment initiated successfully',\n }\n }\n\n const url = `${getControlPlaneBaseUrl()}/v1/cms/tenant/${tenantId}/deploy/perform`\n log.debug(`Calling performDeployment API at ${url}`)\n\n // REAL API IMPLEMENTATION\n const response = await fetch(url, {\n body: JSON.stringify({ deployment_id: options.deploymentId }),\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to perform deployment: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n const data = (await response.json()) as PerformDeploymentApiResponse\n\n // Map API response to our interface\n return {\n deploymentId: data.meta.deployment_id,\n message: data.meta.message || 'Deployment initiated',\n }\n}\n\n/**\n * Get dataset info including tenant ID and OAuth credentials\n * Maps to: GET /v1/cms/dataset/:datasetId\n *\n * @param accessToken - OAuth access token\n * @param datasetId - Content System ID (dataset ID)\n * @returns Dataset info with tenant ID and OAuth credentials\n */\nexport async function getDatasetInfo(accessToken: string, datasetId: string): Promise<DatasetInfo> {\n const url = `${getControlPlaneBaseUrl()}/v1/cms/dataset/${datasetId}`\n log.debug(`Calling getDatasetInfo API at ${url}`)\n\n const response = await fetch(url, {\n headers: { Authorization: `Bearer ${accessToken}` },\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to get dataset info: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n const data = (await response.json()) as DatasetInfoApiResponse\n const sanitizedData = {\n ...data,\n meta: { ...data.meta, oauth_client_secret: '[REDACTED]' },\n }\n log.debug(`Dataset API response: ${JSON.stringify(sanitizedData, null, 2)}`)\n\n return {\n cmsId: data.meta.cms_resource_id,\n oauthClientId: data.meta.oauth_client_id,\n oauthClientSecret: data.meta.oauth_client_secret,\n tenantId: data.meta.tenant_instance_id,\n }\n}\n"],"names":["getEnvironment","log","CONTROL_PLANE_API_URLS","production","staging","process","env","FIGMA_API_URL","getControlPlaneBaseUrl","FIGMA_API_BASE_URL","ControlPlaneError","Error","message","statusCode","cause","name","listTenants","accessToken","shouldMock","FIGMA_MOCK_CONTROL_PLANE","debug","id","createdAt","domain","parentId","parentType","status","response","fetch","headers","Authorization","ok","statusText","json","createTenant","options","tenantId","Math","random","toString","substring","slug","toLowerCase","replace","Date","toISOString","body","JSON","stringify","method","getTenantDetails","createDeployment","deploymentId","mockBucket","mockRegion","mockBaseUrl","staticAssetUploadUrls","assetPath","staticAssets","encodedPath","encodeURIComponent","codeUploadUrl","url","static_assets","data","meta","lambda_zip_upload_url","deployment_id","static_asset_upload_urls","performDeployment","getDatasetInfo","datasetId","sanitizedData","oauth_client_secret","cmsId","cms_resource_id","oauthClientId","oauth_client_id","oauthClientSecret","tenant_instance_id"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/api/control-plane.ts"],"sourcesContent":["/**\n * Control Plane API Client\n *\n * TODO: Replace with real API calls when Control Plane is available\n */\n\nimport type { Tenant } from '../types/config.js'\n\nimport { getEnvironment } from '../constants.js'\nimport * as log from '../utils/log.js'\n\n/**\n * Control Plane API base URLs\n */\nconst CONTROL_PLANE_API_URLS = {\n production: 'https://api.figma.com',\n staging: 'https://api.staging.figma.com',\n}\n\n/**\n * Get the Control Plane API base URL based on environment\n */\nfunction getControlPlaneBaseUrl(): string {\n return process.env.FIGMA_API_BASE_URL || CONTROL_PLANE_API_URLS[getEnvironment()]\n}\n\n/**\n * Error thrown when Control Plane API calls fail\n */\nexport class ControlPlaneError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public cause?: Error,\n ) {\n super(message)\n this.name = 'ControlPlaneError'\n }\n}\n\n/**\n * Options for creating a new tenant\n */\nexport interface CreateTenantOptions {\n /** Name for the new CMS instance */\n name: string\n /** Optional parent ID */\n parentId?: string\n /** Optional parent type */\n parentType?: 'org' | 'team' | 'workspace'\n}\n\n/**\n * Options for creating a new deployment\n */\nexport interface CreateDeploymentOptions {\n /** List of static asset paths that need upload URLs */\n staticAssets: string[]\n}\n\n/**\n * Response from creating a new deployment\n */\nexport interface CreateDeploymentResponse {\n /** Upload URL for the Lambda function code zip */\n codeUploadUrl: string\n /** Unique deployment ID */\n deploymentId: string\n /** Map of static asset paths to their signed S3 upload URLs */\n staticAssetUploadUrls: Record<string, string>\n}\n\nexport interface CreateDeploymentApiResponse {\n error: boolean\n meta: {\n code_upload_url: string\n deployment_id: string\n static_asset_upload_urls: Record<string, string>\n }\n status: number\n}\n\n/**\n * Options for performing a deployment\n */\nexport interface PerformDeploymentOptions {\n /** Deployment ID from createDeployment */\n deploymentId: string\n}\n\n/**\n * Response from performing a deployment\n */\nexport interface PerformDeploymentResponse {\n /** Deployment ID that was deployed */\n deploymentId: string\n /** Status message */\n message: string\n}\n\nexport interface PerformDeploymentApiResponse {\n error: boolean\n meta: {\n deployment_id: string\n message: string\n status: string\n }\n status: number\n}\n\n/**\n * List all tenant instances for the authenticated user\n * Maps to: GET /v1/tenant\n *\n * @param accessToken - OAuth access token\n * @returns Array of tenant instances\n */\nexport async function listTenants(accessToken: string): Promise<Tenant[]> {\n // Check if control plane API should be mocked\n // Default to true until real API is available\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Return sample tenants for testing\n log.debug('Using mock Control Plane API for listTenants (FIGMA_MOCK_CONTROL_PLANE != \"false\")')\n return [\n {\n id: 'tenant_abc123',\n createdAt: '2025-01-15T10:00:00Z',\n domain: 'my-blog.figmacms.com',\n parentId: 'team_456',\n parentType: 'team',\n status: 'active',\n },\n {\n id: 'tenant_def456',\n createdAt: '2025-01-10T14:30:00Z',\n domain: 'my-store.figmacms.com',\n parentId: 'team_456',\n parentType: 'team',\n status: 'active',\n },\n ]\n }\n\n // REAL API IMPLEMENTATION\n const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant`, {\n headers: { Authorization: `Bearer ${accessToken}` },\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to list tenants: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n return response.json()\n}\n\n/**\n * Create a new tenant instance\n * Maps to: POST /v1/tenant\n *\n * @param accessToken - OAuth access token\n * @param options - Tenant creation options\n * @returns Newly created tenant\n */\nexport async function createTenant(\n accessToken: string,\n options: CreateTenantOptions,\n): Promise<Tenant> {\n // Check if control plane API should be mocked\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Generate mock tenant for testing\n log.debug('Using mock Control Plane API for createTenant (FIGMA_MOCK_CONTROL_PLANE != \"false\")')\n const tenantId = `tenant_${Math.random().toString(36).substring(2, 11)}`\n const slug = options.name.toLowerCase().replace(/[^a-z0-9]+/g, '-')\n const domain = `${slug}.figmacms.com`\n\n return {\n id: tenantId,\n createdAt: new Date().toISOString(),\n domain,\n parentId: options.parentId || 'team_456',\n parentType: options.parentType || 'team',\n status: 'provisioning',\n }\n }\n\n // REAL API IMPLEMENTATION\n const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant`, {\n body: JSON.stringify({ name: options.name }),\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to create tenant: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n return response.json()\n}\n\n/**\n * Get details for a specific tenant\n * Maps to: GET /v1/tenant/{tenant_id}\n *\n * @param accessToken - OAuth access token\n * @param tenantId - Tenant ID\n * @returns Tenant details\n */\nexport async function getTenantDetails(accessToken: string, tenantId: string): Promise<Tenant> {\n // Check if control plane API should be mocked\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Return mock tenant details for testing\n log.debug(\n 'Using mock Control Plane API for getTenantDetails (FIGMA_MOCK_CONTROL_PLANE != \"false\")',\n )\n return {\n id: tenantId,\n createdAt: '2025-01-15T10:00:00Z',\n domain: 'example.figmacms.com',\n parentId: 'team_456',\n parentType: 'team',\n status: 'active',\n }\n }\n\n // REAL API IMPLEMENTATION\n const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant/${tenantId}`, {\n headers: { Authorization: `Bearer ${accessToken}` },\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to get tenant details: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n return response.json()\n}\n\n/**\n * Create a new deployment and get signed upload URLs\n * Maps to: POST /v1/tenant/{tenant_id}/deploy/create\n *\n * @param accessToken - OAuth access token\n * @param tenantId - Tenant ID\n * @param options - Deployment creation options\n * @returns Deployment ID and upload URLs\n */\nexport async function createDeployment(\n accessToken: string,\n tenantId: string,\n options: CreateDeploymentOptions,\n): Promise<CreateDeploymentResponse> {\n // Check if control plane API should be mocked\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Generate mock deployment and signed URLs\n log.debug(\n 'Using mock Control Plane API for createDeployment (FIGMA_MOCK_CONTROL_PLANE != \"false\")',\n )\n\n const deploymentId = `deploy_${Math.random().toString(36).substring(2, 11)}`\n const mockBucket = 'figma-cms-deployments-mock'\n const mockRegion = 'us-west-2'\n const mockBaseUrl = `https://${mockBucket}.s3.${mockRegion}.amazonaws.com`\n\n // Generate mock signed URLs for static assets\n const staticAssetUploadUrls: Record<string, string> = {}\n for (const assetPath of options.staticAssets) {\n const encodedPath = encodeURIComponent(assetPath)\n staticAssetUploadUrls[assetPath] =\n `${mockBaseUrl}/${tenantId}/${deploymentId}/static/${encodedPath}?X-Amz-Signature=mock`\n }\n\n // Generate mock signed URL for code zip\n const codeUploadUrl = `${mockBaseUrl}/${tenantId}/${deploymentId}/lambda.zip?X-Amz-Signature=mock`\n\n return {\n codeUploadUrl,\n deploymentId,\n staticAssetUploadUrls,\n }\n }\n\n // REAL API IMPLEMENTATION\n const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant/${tenantId}/deploy/create`, {\n body: JSON.stringify({ static_assets: options.staticAssets }),\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to create deployment: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n const data = (await response.json()) as CreateDeploymentApiResponse\n\n // Map API response format to our interface\n return {\n codeUploadUrl: data.meta.code_upload_url,\n deploymentId: data.meta.deployment_id,\n staticAssetUploadUrls: data.meta.static_asset_upload_urls,\n }\n}\n\n/**\n * Perform deployment for a created deployment ID\n * Maps to: POST /v1/tenant/{tenant_id}/deploy/perform\n *\n * @param accessToken - OAuth access token\n * @param tenantId - Tenant ID\n * @param options - Deployment perform options\n * @returns Deployment status and message\n */\nexport async function performDeployment(\n accessToken: string,\n tenantId: string,\n options: PerformDeploymentOptions,\n): Promise<PerformDeploymentResponse> {\n // Check if control plane API should be mocked\n const shouldMock = process.env.FIGMA_MOCK_CONTROL_PLANE !== 'false'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Return mock deployment status\n log.debug(\n 'Using mock Control Plane API for performDeployment (FIGMA_MOCK_CONTROL_PLANE != \"false\")',\n )\n\n return {\n deploymentId: options.deploymentId,\n message: 'Deployment initiated successfully',\n }\n }\n\n // REAL API IMPLEMENTATION\n const response = await fetch(`${getControlPlaneBaseUrl()}/v1/tenant/${tenantId}/deploy/perform`, {\n body: JSON.stringify({ deployment_id: options.deploymentId }),\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new ControlPlaneError(\n `Failed to perform deployment: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n const data = (await response.json()) as PerformDeploymentApiResponse\n\n // Map API response to our interface\n return {\n deploymentId: data.meta.deployment_id,\n message: data.meta.message || 'Deployment initiated',\n }\n}\n"],"names":["getEnvironment","log","CONTROL_PLANE_API_URLS","production","staging","getControlPlaneBaseUrl","process","env","FIGMA_API_BASE_URL","ControlPlaneError","Error","message","statusCode","cause","name","listTenants","accessToken","shouldMock","FIGMA_MOCK_CONTROL_PLANE","debug","id","createdAt","domain","parentId","parentType","status","response","fetch","headers","Authorization","ok","statusText","json","createTenant","options","tenantId","Math","random","toString","substring","slug","toLowerCase","replace","Date","toISOString","body","JSON","stringify","method","getTenantDetails","createDeployment","deploymentId","mockBucket","mockRegion","mockBaseUrl","staticAssetUploadUrls","assetPath","staticAssets","encodedPath","encodeURIComponent","codeUploadUrl","static_assets","data","meta","code_upload_url","deployment_id","static_asset_upload_urls","performDeployment"],"mappings":"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"}
|
|
@@ -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;AAI1E;;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;AAmBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,
|
|
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;AAI1E;;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;AAmBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CA6FvB"}
|
package/dist/api/figma-api.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Base URLs for Figma API
|
|
19
19
|
*/ const API_BASE_URLS = {
|
|
20
20
|
prod: 'https://api.figma.com',
|
|
21
|
-
staging:
|
|
21
|
+
staging: 'https://api.staging.figma.com'
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
24
|
* Get the Figma API base URL based on environment
|
|
@@ -41,41 +41,13 @@
|
|
|
41
41
|
* @returns ProjectToken with JWT and expiry information
|
|
42
42
|
* @throws {FigmaApiError} If the API call fails
|
|
43
43
|
*/ export async function getProjectToken(accessToken, tenantId) {
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
log.debug('Using direct project token from FIGMA_MOCK_PROJECT_TOKEN_VALUE');
|
|
48
|
-
try {
|
|
49
|
-
// Parse JWT payload to extract claims
|
|
50
|
-
const parts = envTokenValue.split('.');
|
|
51
|
-
if (parts.length !== 3) {
|
|
52
|
-
throw new Error('Invalid JWT format: must have 3 parts (header.payload.signature)');
|
|
53
|
-
}
|
|
54
|
-
const payload = parts[1];
|
|
55
|
-
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
56
|
-
const paddedBase64 = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, '=');
|
|
57
|
-
const decodedPayload = JSON.parse(Buffer.from(paddedBase64, 'base64').toString('utf-8'));
|
|
58
|
-
if (!decodedPayload.exp || typeof decodedPayload.exp !== 'number') {
|
|
59
|
-
throw new Error('JWT payload missing exp claim');
|
|
60
|
-
}
|
|
61
|
-
// Extract tenant ID from aud claim (use parameter as fallback)
|
|
62
|
-
const extractedTenantId = decodedPayload.aud || tenantId;
|
|
63
|
-
return {
|
|
64
|
-
expiresAt: decodedPayload.exp * 1000,
|
|
65
|
-
tenantId: extractedTenantId,
|
|
66
|
-
token: envTokenValue
|
|
67
|
-
};
|
|
68
|
-
} catch (error) {
|
|
69
|
-
throw new FigmaApiError(`Invalid JWT token in FIGMA_MOCK_PROJECT_TOKEN_VALUE: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, error instanceof Error ? error : undefined);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// Priority 2: Check if project token API should be mocked
|
|
73
|
-
// Default to false (use real API)
|
|
74
|
-
const shouldMock = process.env.FIGMA_MOCK_PROJECT_TOKEN === 'true';
|
|
44
|
+
// Check if project token API should be mocked
|
|
45
|
+
// Default to true until real API is available
|
|
46
|
+
const shouldMock = process.env.FIGMA_MOCK_PROJECT_TOKEN !== 'false';
|
|
75
47
|
try {
|
|
76
48
|
if (shouldMock) {
|
|
77
49
|
// MOCK IMPLEMENTATION: Generate a mock JWT token for testing
|
|
78
|
-
log.debug('Using mock project token API for getProjectToken (FIGMA_MOCK_PROJECT_TOKEN
|
|
50
|
+
log.debug('Using mock project token API for getProjectToken (FIGMA_MOCK_PROJECT_TOKEN != "false")');
|
|
79
51
|
const mockToken = createMockJWT(tenantId, 25) // 25 minutes expiry
|
|
80
52
|
;
|
|
81
53
|
// Mock response data
|
|
@@ -98,6 +70,11 @@
|
|
|
98
70
|
// REAL API IMPLEMENTATION
|
|
99
71
|
const baseUrl = _getApiBaseUrl();
|
|
100
72
|
const url = `${baseUrl}/v1/cms/${tenantId}/token/`;
|
|
73
|
+
console.log({
|
|
74
|
+
accessToken,
|
|
75
|
+
msg: 'Fetching project token from Figma API',
|
|
76
|
+
url
|
|
77
|
+
});
|
|
101
78
|
const response = await fetch(url, {
|
|
102
79
|
headers: {
|
|
103
80
|
Authorization: `Bearer ${accessToken}`
|
|
@@ -109,11 +86,8 @@
|
|
|
109
86
|
let errorMessage = `Failed to get project token: ${response.status} ${response.statusText}`;
|
|
110
87
|
try {
|
|
111
88
|
const errorBody = await response.json();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
errorMessage = errorBody.message;
|
|
115
|
-
} else if (typeof errorBody.error === 'string') {
|
|
116
|
-
errorMessage = errorBody.error;
|
|
89
|
+
if (errorBody.error || errorBody.message) {
|
|
90
|
+
errorMessage = errorBody.error || errorBody.message;
|
|
117
91
|
}
|
|
118
92
|
} catch {
|
|
119
93
|
// Ignore JSON parse errors for error body
|
|
@@ -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 * 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 * Base URLs for Figma API\n */\nconst API_BASE_URLS = {\n prod: 'https://api.figma.com',\n staging: process.env.FIGMA_API_URL || 'https://api.staging.figma.com',\n}\n\n/**\n * Get the Figma API base URL based on environment\n * TODO: Use this once real API endpoints are available\n */\nfunction _getApiBaseUrl(): string {\n // For now, default to staging to match OAuth config\n return process.env.FIGMA_API_BASE_URL || API_BASE_URLS.staging\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 baseUrl = _getApiBaseUrl()\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 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":["log","FigmaApiError","Error","message","statusCode","cause","name","API_BASE_URLS","prod","staging","process","env","FIGMA_API_URL","_getApiBaseUrl","FIGMA_API_BASE_URL","getProjectToken","accessToken","tenantId","envTokenValue","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","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":"AAAA;;;;;CAKC,GAID,YAAYA,SAAS,kBAAiB;AAEtC;;CAEC,GACD,OAAO,MAAMC,sBAAsBC;;;IACjC,YACEC,OAAe,EACf,AAAOC,UAAmB,EAC1B,AAAOC,KAAa,CACpB;QACA,KAAK,CAACF,eAHCC,aAAAA,iBACAC,QAAAA;QAGP,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA;;CAEC,GACD,MAAMC,gBAAgB;IACpBC,MAAM;IACNC,SAASC,QAAQC,GAAG,CAACC,aAAa,IAAI;AACxC;AAEA;;;CAGC,GACD,SAASC;IACP,oDAAoD;IACpD,OAAOH,QAAQC,GAAG,CAACG,kBAAkB,IAAIP,cAAcE,OAAO;AAChE;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,eAAeM,gBACpBC,WAAmB,EACnBC,QAAgB;IAEhB,4DAA4D;IAC5D,MAAMC,gBAAgBR,QAAQC,GAAG,CAACQ,8BAA8B;IAChE,IAAID,eAAe;QACjBlB,IAAIoB,KAAK,CAAC;QACV,IAAI;YACF,sCAAsC;YACtC,MAAMC,QAAQH,cAAcI,KAAK,CAAC;YAClC,IAAID,MAAME,MAAM,KAAK,GAAG;gBACtB,MAAM,IAAIrB,MAAM;YAClB;YAEA,MAAMsB,UAAUH,KAAK,CAAC,EAAE;YACxB,MAAMI,SAASD,QAAQE,OAAO,CAAC,MAAM,KAAKA,OAAO,CAAC,MAAM;YACxD,MAAMC,eAAeF,OAAOG,MAAM,CAACH,OAAOF,MAAM,GAAI,AAAC,CAAA,IAAKE,OAAOF,MAAM,GAAG,CAAC,IAAK,GAAI;YACpF,MAAMM,iBAAiBC,KAAKC,KAAK,CAACC,OAAOC,IAAI,CAACN,cAAc,UAAUO,QAAQ,CAAC;YAK/E,IAAI,CAACL,eAAeM,GAAG,IAAI,OAAON,eAAeM,GAAG,KAAK,UAAU;gBACjE,MAAM,IAAIjC,MAAM;YAClB;YAEA,+DAA+D;YAC/D,MAAMkC,oBAAoBP,eAAeQ,GAAG,IAAIpB;YAEhD,OAAO;gBACLqB,WAAWT,eAAeM,GAAG,GAAG;gBAChClB,UAAUmB;gBACVG,OAAOrB;YACT;QACF,EAAE,OAAOsB,OAAO;YACd,MAAM,IAAIvC,cACR,CAAC,qDAAqD,EAAEuC,iBAAiBtC,QAAQsC,MAAMrC,OAAO,GAAG,iBAAiB,EAClHsC,WACAD,iBAAiBtC,QAAQsC,QAAQC;QAErC;IACF;IAEA,0DAA0D;IAC1D,kCAAkC;IAClC,MAAMC,aAAahC,QAAQC,GAAG,CAACgC,wBAAwB,KAAK;IAE5D,IAAI;QACF,IAAID,YAAY;YACd,6DAA6D;YAC7D1C,IAAIoB,KAAK,CACP;YAEF,MAAMwB,YAAYC,cAAc5B,UAAU,IAAI,oBAAoB;;YAElE,qBAAqB;YACrB,MAAM6B,OAA6B;gBACjCC,MAAM;oBACJR,OAAOK;gBACT;YACF;YAEA,IAAI,CAACE,KAAKC,IAAI,EAAER,OAAO;gBACrB,MAAM,IAAItC,cAAc;YAC1B;YAEA,wDAAwD;YACxD,MAAMqC,YAAYU,eAAeF,KAAKC,IAAI,CAACR,KAAK;YAEhD,OAAO;gBACLD;gBACArB;gBACAsB,OAAOO,KAAKC,IAAI,CAACR,KAAK;YACxB;QACF;QAEA,0BAA0B;QAC1B,MAAMU,UAAUpC;QAChB,MAAMqC,MAAM,GAAGD,QAAQ,QAAQ,EAAEhC,SAAS,OAAO,CAAC;QAElD,MAAMkC,WAAW,MAAMC,MAAMF,KAAK;YAChCG,SAAS;gBACPC,eAAe,CAAC,OAAO,EAAEtC,aAAa;YACxC;YACAuC,QAAQ;QACV;QAEA,IAAI,CAACJ,SAASK,EAAE,EAAE;YAChB,yBAAyB;YACzB,IAAIC,eAAe,CAAC,6BAA6B,EAAEN,SAASO,MAAM,CAAC,CAAC,EAAEP,SAASQ,UAAU,EAAE;YAE3F,IAAI;gBACF,MAAMC,YAAY,MAAMT,SAASU,IAAI;gBACrC,kEAAkE;gBAClE,IAAI,OAAOD,UAAUzD,OAAO,KAAK,UAAU;oBACzCsD,eAAeG,UAAUzD,OAAO;gBAClC,OAAO,IAAI,OAAOyD,UAAUpB,KAAK,KAAK,UAAU;oBAC9CiB,eAAeG,UAAUpB,KAAK;gBAChC;YACF,EAAE,OAAM;YACN,0CAA0C;YAC5C;YAEA,MAAM,IAAIvC,cAAcwD,cAAcN,SAASO,MAAM;QACvD;QAEA,MAAMZ,OAA6B,MAAMK,SAASU,IAAI;QAEtD,IAAI,CAACf,KAAKC,IAAI,EAAER,OAAO;YACrB,MAAM,IAAItC,cAAc;QAC1B;QAEA,wDAAwD;QACxD,MAAMqC,YAAYU,eAAeF,KAAKC,IAAI,CAACR,KAAK;QAEhD,OAAO;YACLD;YACArB;YACAsB,OAAOO,KAAKC,IAAI,CAACR,KAAK;QACxB;IACF,EAAE,OAAOC,OAAO;QACd,IAAIA,iBAAiBvC,eAAe;YAClC,MAAMuC;QACR;QAEA,oDAAoD;QACpD,MAAM,IAAIvC,cACR,CAAC,6BAA6B,EAAEuC,iBAAiBtC,QAAQsC,MAAMrC,OAAO,GAAG,iBAAiB,EAC1FsC,WACAD,iBAAiBtC,QAAQsC,QAAQC;IAErC;AACF;AAEA;;;CAGC,GACD,SAASI,cAAc5B,QAAgB,EAAE6C,gBAAwB;IAC/D,MAAMC,SAAS;QACbC,KAAK;QACLC,KAAK;IACP;IAEA,MAAMC,MAAMC,KAAKC,KAAK,CAACC,KAAKH,GAAG,KAAK,MAAM,0BAA0B;;IACpE,MAAM/B,MAAM+B,MAAMJ,mBAAmB,GAAG,oBAAoB;;IAE5D,MAAMtC,UAAU;QACda,KAAKpB;QACLqD,OAAO;QACPnC;QACAoC,KAAKL;QACLM,KAAK;IACP;IAEA,mBAAmB;IACnB,MAAMC,kBAAkB,CAACC;QACvB,OAAO1C,OAAOC,IAAI,CAACH,KAAK6C,SAAS,CAACD,MAC/BxC,QAAQ,CAAC,UACTR,OAAO,CAAC,OAAO,KACfA,OAAO,CAAC,OAAO,KACfA,OAAO,CAAC,MAAM;IACnB;IAEA,MAAMkD,gBAAgBH,gBAAgBV;IACtC,MAAMc,iBAAiBJ,gBAAgBjD;IACvC,MAAMsD,YAAY,oBAAoBX,KAAKY,MAAM,GAAG7C,QAAQ,CAAC,IAAI8C,SAAS,CAAC,GAAG;IAE9E,OAAO,GAAGJ,cAAc,CAAC,EAAEC,eAAe,CAAC,EAAEC,WAAW;AAC1D;AAEA;;;;;;;CAOC,GACD,SAAS9B,eAAeT,KAAa;IACnC,IAAI;QACF,uCAAuC;QACvC,MAAMlB,QAAQkB,MAAMjB,KAAK,CAAC;QAC1B,IAAID,MAAME,MAAM,KAAK,GAAG;YACtB,MAAM,IAAIrB,MAAM;QAClB;QAEA,mCAAmC;QACnC,MAAMsB,UAAUH,KAAK,CAAC,EAAE;QACxB,2EAA2E;QAC3E,MAAMI,SAASD,QAAQE,OAAO,CAAC,MAAM,KAAKA,OAAO,CAAC,MAAM;QACxD,MAAMC,eAAeF,OAAOG,MAAM,CAACH,OAAOF,MAAM,GAAI,AAAC,CAAA,IAAKE,OAAOF,MAAM,GAAG,CAAC,IAAK,GAAI;QAEpF,MAAMM,iBAAiBC,KAAKC,KAAK,CAACC,OAAOC,IAAI,CAACN,cAAc,UAAUO,QAAQ,CAAC;QAI/E,IAAI,CAACL,eAAeM,GAAG,IAAI,OAAON,eAAeM,GAAG,KAAK,UAAU;YACjE,MAAM,IAAIjC,MAAM;QAClB;QAEA,uDAAuD;QACvD,OAAO2B,eAAeM,GAAG,GAAG;IAC9B,EAAE,OAAOK,OAAO;QACd,MAAM,IAAIvC,cACR,CAAC,4BAA4B,EAAEuC,iBAAiBtC,QAAQsC,MAAMrC,OAAO,GAAG,iBAAiB,EACzFsC,WACAD,iBAAiBtC,QAAQsC,QAAQC;IAErC;AACF"}
|
|
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 * 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 * Base URLs for Figma API\n */\nconst API_BASE_URLS = {\n prod: 'https://api.figma.com',\n staging: 'https://api.staging.figma.com',\n}\n\n/**\n * Get the Figma API base URL based on environment\n * TODO: Use this once real API endpoints are available\n */\nfunction _getApiBaseUrl(): string {\n // For now, default to staging to match OAuth config\n return process.env.FIGMA_API_BASE_URL || API_BASE_URLS.staging\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 // Check if project token API should be mocked\n // Default to true until real API is available\n const shouldMock = process.env.FIGMA_MOCK_PROJECT_TOKEN !== 'false'\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 != \"false\")',\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 baseUrl = _getApiBaseUrl()\n const url = `${baseUrl}/v1/cms/${tenantId}/token/`\n\n console.log({\n accessToken,\n msg: 'Fetching project token from Figma API',\n url,\n })\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 if (errorBody.error || errorBody.message) {\n errorMessage = errorBody.error || errorBody.message\n }\n } catch {\n // Ignore JSON parse errors for error body\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":["log","FigmaApiError","Error","message","statusCode","cause","name","API_BASE_URLS","prod","staging","_getApiBaseUrl","process","env","FIGMA_API_BASE_URL","getProjectToken","accessToken","tenantId","shouldMock","FIGMA_MOCK_PROJECT_TOKEN","debug","mockToken","createMockJWT","data","meta","token","expiresAt","parseJwtExpiry","baseUrl","url","console","msg","response","fetch","headers","Authorization","method","ok","errorMessage","status","statusText","errorBody","json","error","undefined","expiresInMinutes","header","alg","typ","now","Math","floor","Date","exp","payload","aud","email","iat","sub","encodeBase64Url","obj","Buffer","from","JSON","stringify","toString","replace","headerEncoded","payloadEncoded","signature","random","substring","parts","split","length","base64","paddedBase64","padEnd","decodedPayload","parse"],"mappings":"AAAA;;;;;CAKC,GAID,YAAYA,SAAS,kBAAiB;AAEtC;;CAEC,GACD,OAAO,MAAMC,sBAAsBC;;;IACjC,YACEC,OAAe,EACf,AAAOC,UAAmB,EAC1B,AAAOC,KAAa,CACpB;QACA,KAAK,CAACF,eAHCC,aAAAA,iBACAC,QAAAA;QAGP,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA;;CAEC,GACD,MAAMC,gBAAgB;IACpBC,MAAM;IACNC,SAAS;AACX;AAEA;;;CAGC,GACD,SAASC;IACP,oDAAoD;IACpD,OAAOC,QAAQC,GAAG,CAACC,kBAAkB,IAAIN,cAAcE,OAAO;AAChE;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,eAAeK,gBACpBC,WAAmB,EACnBC,QAAgB;IAEhB,8CAA8C;IAC9C,8CAA8C;IAC9C,MAAMC,aAAaN,QAAQC,GAAG,CAACM,wBAAwB,KAAK;IAE5D,IAAI;QACF,IAAID,YAAY;YACd,6DAA6D;YAC7DjB,IAAImB,KAAK,CACP;YAEF,MAAMC,YAAYC,cAAcL,UAAU,IAAI,oBAAoB;;YAElE,qBAAqB;YACrB,MAAMM,OAA6B;gBACjCC,MAAM;oBACJC,OAAOJ;gBACT;YACF;YAEA,IAAI,CAACE,KAAKC,IAAI,EAAEC,OAAO;gBACrB,MAAM,IAAIvB,cAAc;YAC1B;YAEA,wDAAwD;YACxD,MAAMwB,YAAYC,eAAeJ,KAAKC,IAAI,CAACC,KAAK;YAEhD,OAAO;gBACLC;gBACAT;gBACAQ,OAAOF,KAAKC,IAAI,CAACC,KAAK;YACxB;QACF;QAEA,0BAA0B;QAC1B,MAAMG,UAAUjB;QAChB,MAAMkB,MAAM,GAAGD,QAAQ,QAAQ,EAAEX,SAAS,OAAO,CAAC;QAElDa,QAAQ7B,GAAG,CAAC;YACVe;YACAe,KAAK;YACLF;QACF;QAEA,MAAMG,WAAW,MAAMC,MAAMJ,KAAK;YAChCK,SAAS;gBACPC,eAAe,CAAC,OAAO,EAAEnB,aAAa;YACxC;YACAoB,QAAQ;QACV;QAEA,IAAI,CAACJ,SAASK,EAAE,EAAE;YAChB,yBAAyB;YACzB,IAAIC,eAAe,CAAC,6BAA6B,EAAEN,SAASO,MAAM,CAAC,CAAC,EAAEP,SAASQ,UAAU,EAAE;YAE3F,IAAI;gBACF,MAAMC,YAAY,MAAMT,SAASU,IAAI;gBACrC,IAAID,UAAUE,KAAK,IAAIF,UAAUrC,OAAO,EAAE;oBACxCkC,eAAeG,UAAUE,KAAK,IAAIF,UAAUrC,OAAO;gBACrD;YACF,EAAE,OAAM;YACN,0CAA0C;YAC5C;YAEA,MAAM,IAAIF,cAAcoC,cAAcN,SAASO,MAAM;QACvD;QAEA,MAAMhB,OAA6B,MAAMS,SAASU,IAAI;QAEtD,IAAI,CAACnB,KAAKC,IAAI,EAAEC,OAAO;YACrB,MAAM,IAAIvB,cAAc;QAC1B;QAEA,wDAAwD;QACxD,MAAMwB,YAAYC,eAAeJ,KAAKC,IAAI,CAACC,KAAK;QAEhD,OAAO;YACLC;YACAT;YACAQ,OAAOF,KAAKC,IAAI,CAACC,KAAK;QACxB;IACF,EAAE,OAAOkB,OAAO;QACd,IAAIA,iBAAiBzC,eAAe;YAClC,MAAMyC;QACR;QAEA,oDAAoD;QACpD,MAAM,IAAIzC,cACR,CAAC,6BAA6B,EAAEyC,iBAAiBxC,QAAQwC,MAAMvC,OAAO,GAAG,iBAAiB,EAC1FwC,WACAD,iBAAiBxC,QAAQwC,QAAQC;IAErC;AACF;AAEA;;;CAGC,GACD,SAAStB,cAAcL,QAAgB,EAAE4B,gBAAwB;IAC/D,MAAMC,SAAS;QACbC,KAAK;QACLC,KAAK;IACP;IAEA,MAAMC,MAAMC,KAAKC,KAAK,CAACC,KAAKH,GAAG,KAAK,MAAM,0BAA0B;;IACpE,MAAMI,MAAMJ,MAAMJ,mBAAmB,GAAG,oBAAoB;;IAE5D,MAAMS,UAAU;QACdC,KAAKtC;QACLuC,OAAO;QACPH;QACAI,KAAKR;QACLS,KAAK;IACP;IAEA,mBAAmB;IACnB,MAAMC,kBAAkB,CAACC;QACvB,OAAOC,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACJ,MAC/BK,QAAQ,CAAC,UACTC,OAAO,CAAC,OAAO,KACfA,OAAO,CAAC,OAAO,KACfA,OAAO,CAAC,MAAM;IACnB;IAEA,MAAMC,gBAAgBR,gBAAgBb;IACtC,MAAMsB,iBAAiBT,gBAAgBL;IACvC,MAAMe,YAAY,oBAAoBnB,KAAKoB,MAAM,GAAGL,QAAQ,CAAC,IAAIM,SAAS,CAAC,GAAG;IAE9E,OAAO,GAAGJ,cAAc,CAAC,EAAEC,eAAe,CAAC,EAAEC,WAAW;AAC1D;AAEA;;;;;;;CAOC,GACD,SAAS1C,eAAeF,KAAa;IACnC,IAAI;QACF,uCAAuC;QACvC,MAAM+C,QAAQ/C,MAAMgD,KAAK,CAAC;QAC1B,IAAID,MAAME,MAAM,KAAK,GAAG;YACtB,MAAM,IAAIvE,MAAM;QAClB;QAEA,mCAAmC;QACnC,MAAMmD,UAAUkB,KAAK,CAAC,EAAE;QACxB,2EAA2E;QAC3E,MAAMG,SAASrB,QAAQY,OAAO,CAAC,MAAM,KAAKA,OAAO,CAAC,MAAM;QACxD,MAAMU,eAAeD,OAAOE,MAAM,CAACF,OAAOD,MAAM,GAAI,AAAC,CAAA,IAAKC,OAAOD,MAAM,GAAG,CAAC,IAAK,GAAI;QAEpF,MAAMI,iBAAiBf,KAAKgB,KAAK,CAAClB,OAAOC,IAAI,CAACc,cAAc,UAAUX,QAAQ,CAAC;QAI/E,IAAI,CAACa,eAAezB,GAAG,IAAI,OAAOyB,eAAezB,GAAG,KAAK,UAAU;YACjE,MAAM,IAAIlD,MAAM;QAClB;QAEA,uDAAuD;QACvD,OAAO2E,eAAezB,GAAG,GAAG;IAC9B,EAAE,OAAOV,OAAO;QACd,MAAM,IAAIzC,cACR,CAAC,4BAA4B,EAAEyC,iBAAiBxC,QAAQwC,MAAMvC,OAAO,GAAG,iBAAiB,EACzFwC,WACAD,iBAAiBxC,QAAQwC,QAAQC;IAErC;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto-utils.d.ts","sourceRoot":"","sources":["../../src/auth/crypto-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"crypto-utils.d.ts","sourceRoot":"","sources":["../../src/auth/crypto-utils.ts"],"names":[],"mappings":"AAqEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAe5C"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
2
|
import crypto from 'crypto';
|
|
3
3
|
import os from 'os';
|
|
4
|
+
// Configuration store for cryptographic salt
|
|
5
|
+
const cryptoConf = new Conf({
|
|
6
|
+
configName: 'payloadcms-figma-crypto',
|
|
7
|
+
projectName: 'payloadcms-figma'
|
|
8
|
+
});
|
|
4
9
|
/**
|
|
5
10
|
* Get or generate a random salt for key derivation
|
|
6
11
|
*
|
|
@@ -10,11 +15,6 @@ import os from 'os';
|
|
|
10
15
|
*
|
|
11
16
|
* @returns 32-byte hex string (64 characters)
|
|
12
17
|
*/ function getOrCreateSalt() {
|
|
13
|
-
// Configuration store for cryptographic salt
|
|
14
|
-
const cryptoConf = new Conf({
|
|
15
|
-
configName: 'payloadcms-figma-crypto',
|
|
16
|
-
projectName: 'payloadcms-figma'
|
|
17
|
-
});
|
|
18
18
|
let salt = cryptoConf.get('salt');
|
|
19
19
|
if (!salt) {
|
|
20
20
|
// Generate cryptographically secure random salt (32 bytes = 256 bits)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/crypto-utils.ts"],"sourcesContent":["import Conf from 'conf'\nimport crypto from 'crypto'\nimport os from 'os'\n\n/**\n * Get or generate a random salt for key derivation\n *\n * Generates a cryptographically secure random salt on first run and persists it\n * for future use. This ensures the encryption key is unique per machine and not\n * reproducible even if machine identifiers are known.\n *\n * @returns 32-byte hex string (64 characters)\n */\nfunction getOrCreateSalt(): string {\n
|
|
1
|
+
{"version":3,"sources":["../../src/auth/crypto-utils.ts"],"sourcesContent":["import Conf from 'conf'\nimport crypto from 'crypto'\nimport os from 'os'\n\n// Configuration store for cryptographic salt\nconst cryptoConf = new Conf<{ salt: string }>({\n configName: 'payloadcms-figma-crypto',\n projectName: 'payloadcms-figma',\n})\n\n/**\n * Get or generate a random salt for key derivation\n *\n * Generates a cryptographically secure random salt on first run and persists it\n * for future use. This ensures the encryption key is unique per machine and not\n * reproducible even if machine identifiers are known.\n *\n * @returns 32-byte hex string (64 characters)\n */\nfunction getOrCreateSalt(): string {\n let salt = cryptoConf.get('salt')\n if (!salt) {\n // Generate cryptographically secure random salt (32 bytes = 256 bits)\n salt = crypto.randomBytes(32).toString('hex')\n cryptoConf.set('salt', salt)\n }\n return salt\n}\n\n/**\n * Gather machine-specific entropy sources\n *\n * Collects multiple machine and user identifiers to create a unique input\n * for key derivation. More entropy sources make the key harder to guess.\n *\n * @returns Combined machine identifier string\n * @throws Error if unable to gather sufficient entropy\n */\nfunction gatherMachineEntropy(): string {\n try {\n const userInfo = os.userInfo()\n const networkInterfaces = os.networkInterfaces()\n\n // Get MAC address from first available network interface (if available)\n const macAddress = Object.values(networkInterfaces)\n .flat()\n .find((iface) => iface && !iface.internal && iface.mac !== '00:00:00:00:00:00')?.mac\n\n const entropy = [\n os.hostname(), // Machine hostname\n os.homedir(), // User home directory path\n userInfo.username, // OS username\n os.platform(), // Operating system platform\n os.arch(), // CPU architecture\n macAddress, // MAC address (if available)\n ].filter(Boolean) // Remove any undefined values\n\n if (entropy.length < 4) {\n throw new Error('Insufficient machine entropy available')\n }\n\n return entropy.join('::')\n } catch (error) {\n throw new Error(\n `Failed to gather machine entropy: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n }\n}\n\n/**\n * Derive a machine-specific encryption key for token storage\n *\n * Creates a deterministic encryption key using PBKDF2 with machine-specific\n * identifiers and a random salt. This provides stronger security than simple\n * hashing by making brute force attacks computationally expensive.\n *\n * Security model - What this protects against:\n * ✅ Accidental file exposure (cloud backups, shared drives)\n * ✅ Non-technical users reading token files\n * ✅ Brute force attacks (100k PBKDF2 iterations)\n * ✅ Key guessing even with known machine identifiers (random salt)\n *\n * Security model - What this DOES NOT protect against:\n * ❌ Local user compromise (same user can run this function)\n * ❌ Root/admin access attacks\n * ❌ Sophisticated attackers with system access\n *\n * For stronger security, use OS keychain (@napi-rs/keyring) instead.\n * This is a fallback for environments without keychain access.\n *\n * Implementation details:\n * - Uses PBKDF2 with SHA-256 and 100,000 iterations (~500ms-1s)\n * - Combines multiple machine identifiers for entropy\n * - Uses persistent random salt unique to each machine\n * - Produces consistent key for same machine (deterministic)\n * - Tokens are not portable between machines (by design)\n *\n * @returns 64-character hex string (32-byte key) suitable for AES-256 encryption\n * @throws Error if unable to derive key due to missing machine information\n */\nexport function deriveEncryptionKey(): string {\n const machineId = gatherMachineEntropy()\n const salt = getOrCreateSalt()\n\n // PBKDF2 with 100k iterations provides strong security against brute force\n // while remaining performant for CLI use (~500ms-1s key derivation time)\n const key = crypto.pbkdf2Sync(\n machineId, // Password (machine identifiers)\n salt, // Salt (random, persisted)\n 100000, // Iterations (100k for strong security)\n 32, // Key length (32 bytes = 256 bits for AES-256)\n 'sha256', // Hash algorithm\n )\n\n return key.toString('hex')\n}\n"],"names":["Conf","crypto","os","cryptoConf","configName","projectName","getOrCreateSalt","salt","get","randomBytes","toString","set","gatherMachineEntropy","userInfo","networkInterfaces","macAddress","Object","values","flat","find","iface","internal","mac","entropy","hostname","homedir","username","platform","arch","filter","Boolean","length","Error","join","error","message","deriveEncryptionKey","machineId","key","pbkdf2Sync"],"mappings":"AAAA,OAAOA,UAAU,OAAM;AACvB,OAAOC,YAAY,SAAQ;AAC3B,OAAOC,QAAQ,KAAI;AAEnB,6CAA6C;AAC7C,MAAMC,aAAa,IAAIH,KAAuB;IAC5CI,YAAY;IACZC,aAAa;AACf;AAEA;;;;;;;;CAQC,GACD,SAASC;IACP,IAAIC,OAAOJ,WAAWK,GAAG,CAAC;IAC1B,IAAI,CAACD,MAAM;QACT,sEAAsE;QACtEA,OAAON,OAAOQ,WAAW,CAAC,IAAIC,QAAQ,CAAC;QACvCP,WAAWQ,GAAG,CAAC,QAAQJ;IACzB;IACA,OAAOA;AACT;AAEA;;;;;;;;CAQC,GACD,SAASK;IACP,IAAI;QACF,MAAMC,WAAWX,GAAGW,QAAQ;QAC5B,MAAMC,oBAAoBZ,GAAGY,iBAAiB;QAE9C,wEAAwE;QACxE,MAAMC,aAAaC,OAAOC,MAAM,CAACH,mBAC9BI,IAAI,GACJC,IAAI,CAAC,CAACC,QAAUA,SAAS,CAACA,MAAMC,QAAQ,IAAID,MAAME,GAAG,KAAK,sBAAsBA;QAEnF,MAAMC,UAAU;YACdrB,GAAGsB,QAAQ;YACXtB,GAAGuB,OAAO;YACVZ,SAASa,QAAQ;YACjBxB,GAAGyB,QAAQ;YACXzB,GAAG0B,IAAI;YACPb;SACD,CAACc,MAAM,CAACC,SAAS,8BAA8B;;QAEhD,IAAIP,QAAQQ,MAAM,GAAG,GAAG;YACtB,MAAM,IAAIC,MAAM;QAClB;QAEA,OAAOT,QAAQU,IAAI,CAAC;IACtB,EAAE,OAAOC,OAAO;QACd,MAAM,IAAIF,MACR,CAAC,kCAAkC,EAAEE,iBAAiBF,QAAQE,MAAMC,OAAO,GAAG,iBAAiB;IAEnG;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GACD,OAAO,SAASC;IACd,MAAMC,YAAYzB;IAClB,MAAML,OAAOD;IAEb,2EAA2E;IAC3E,yEAAyE;IACzE,MAAMgC,MAAMrC,OAAOsC,UAAU,CAC3BF,WACA9B,MACA,QACA,IACA;IAGF,OAAO+B,IAAI5B,QAAQ,CAAC;AACtB"}
|
package/dist/auth/oauth-flow.js
CHANGED
|
@@ -32,11 +32,11 @@ import { exchangeCodeForTokens, refreshAccessToken } from './refresh.js';
|
|
|
32
32
|
* @throws OAuthFlowError if any step fails
|
|
33
33
|
*/ export async function executeOAuthFlow(tokenStore, options = {}) {
|
|
34
34
|
// Check if OAuth flow should be mocked
|
|
35
|
-
// Default to
|
|
36
|
-
const shouldMock = process.env.FIGMA_MOCK_OAUTH
|
|
35
|
+
// Default to true for OAuth (allow testing without real authentication by default)
|
|
36
|
+
const shouldMock = process.env.FIGMA_MOCK_OAUTH !== 'false';
|
|
37
37
|
if (shouldMock) {
|
|
38
38
|
// MOCK IMPLEMENTATION: Return mock tokens for testing
|
|
39
|
-
log.debug('Using mock OAuth flow (FIGMA_MOCK_OAUTH
|
|
39
|
+
log.debug('Using mock OAuth flow (FIGMA_MOCK_OAUTH != "false")');
|
|
40
40
|
const mockTokens = {
|
|
41
41
|
accessToken: 'figt_mock_access_token_' + crypto.randomBytes(16).toString('hex'),
|
|
42
42
|
expiresAt: Date.now() + 2 * 60 * 60 * 1000,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/oauth-flow.ts"],"sourcesContent":["import * as p from '@clack/prompts'\nimport crypto from 'crypto'\n\nimport type { TokenStore } from './token-store.js'\nimport type { FigmaTokens } from './types.js'\n\nimport { OAUTH_CONFIG } from '../config/oauth.js'\nimport * as log from '../utils/log.js'\nimport { buildAuthorizationUrl, openBrowser } from './browser.js'\nimport { CallbackServer } from './callback-server.js'\nimport { generatePKCEPair } from './pkce.js'\nimport { exchangeCodeForTokens, refreshAccessToken } from './refresh.js'\n\n/**\n * Error during OAuth flow\n */\nexport class OAuthFlowError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message)\n this.name = 'OAuthFlowError'\n }\n}\n\n/**\n * Options for OAuth flow\n */\nexport interface OAuthFlowOptions {\n /** OAuth2 client ID (defaults to config) */\n clientId?: string\n /** Callback server port (defaults to 3000) */\n port?: number\n /** Redirect URI (defaults to config) */\n redirectUri?: string\n /** OAuth scopes (defaults to config) */\n scopes?: string[]\n /** Callback timeout in milliseconds (defaults to 120000) */\n timeoutMs?: number\n}\n\n/**\n * Result of OAuth flow\n */\nexport interface OAuthFlowResult {\n /** OAuth2 tokens */\n tokens: FigmaTokens\n /** Figma user ID */\n userId?: string\n}\n\n/**\n * Execute complete OAuth2 authorization flow\n *\n * This orchestrates the full OAuth flow:\n * 1. Generate PKCE pair for security\n * 2. Start local callback server\n * 3. Open browser to authorization URL\n * 4. Wait for user to authorize\n * 5. Exchange authorization code for tokens\n * 6. Store tokens securely\n *\n * @param tokenStore - Token store for persisting tokens\n * @param options - Flow configuration options\n * @returns Promise resolving to OAuth flow result\n * @throws OAuthFlowError if any step fails\n */\nexport async function executeOAuthFlow(\n tokenStore: TokenStore,\n options: OAuthFlowOptions = {},\n): Promise<OAuthFlowResult> {\n // Check if OAuth flow should be mocked\n // Default to false for OAuth\n const shouldMock = process.env.FIGMA_MOCK_OAUTH === 'true'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Return mock tokens for testing\n log.debug('Using mock OAuth flow (FIGMA_MOCK_OAUTH = true)')\n const mockTokens: FigmaTokens = {\n accessToken: 'figt_mock_access_token_' + crypto.randomBytes(16).toString('hex'),\n expiresAt: Date.now() + 2 * 60 * 60 * 1000, // 2 hours\n refreshToken: 'figt_mock_refresh_token_' + crypto.randomBytes(16).toString('hex'),\n scopes: (options.scopes || OAUTH_CONFIG.scopes) as FigmaTokens['scopes'],\n tokenType: 'Bearer',\n }\n\n // Store mock tokens\n tokenStore.setTokens(mockTokens)\n\n return {\n tokens: mockTokens,\n userId: 'mock_user_123',\n }\n }\n\n // REAL OAUTH IMPLEMENTATION\n const {\n clientId = OAUTH_CONFIG.clientId,\n port = 3000,\n redirectUri = OAUTH_CONFIG.redirectUri,\n scopes = OAUTH_CONFIG.scopes,\n timeoutMs = 120000,\n } = options\n\n // Generate PKCE pair for enhanced security\n const { codeChallenge, codeChallengeMethod, codeVerifier } = generatePKCEPair()\n\n // Generate random state for CSRF protection\n const state = crypto.randomBytes(32).toString('base64url')\n\n // Create callback server\n const callbackServer = new CallbackServer({ port, timeoutMs })\n\n try {\n // Start waiting for callback (this starts the server and captures actual port)\n const callbackPromise = callbackServer.waitForCallback(state)\n\n // Wait a bit for server to start and determine actual port\n await new Promise((resolve) => setTimeout(resolve, 100))\n\n // Get the actual port (may differ from preferred if that was in use)\n const actualPort = callbackServer.getActualPort()\n\n // Build redirect URI with actual port\n const actualRedirectUri = redirectUri.replace(/:\\d+/, `:${actualPort}`)\n\n // Build authorization URL\n const authUrl = buildAuthorizationUrl({\n authorizationUrl: OAUTH_CONFIG.authorizationUrl,\n clientId,\n codeChallenge,\n codeChallengeMethod,\n redirectUri: actualRedirectUri,\n scopes,\n state,\n })\n\n // Prompt user to open browser\n const shouldOpenBrowser = await p.confirm({\n initialValue: true,\n message: 'Log in or create Figma account in your browser?',\n })\n\n if (p.isCancel(shouldOpenBrowser)) {\n throw new OAuthFlowError('Authentication cancelled by user')\n }\n\n if (shouldOpenBrowser) {\n // Try to open browser, but don't fail if it doesn't work\n try {\n await openBrowser(authUrl)\n } catch (_error) {\n // Browser opening failed, show URL for manual opening\n log.warning('Could not open browser automatically.')\n log.info(`Please open this URL in your browser:`)\n log.info(` ${authUrl}`)\n }\n } else {\n // User declined automatic opening, show URL\n log.info(`Please open this URL manually in your browser:`)\n log.info(` ${authUrl}`)\n }\n\n // Wait for callback\n const { code } = await callbackPromise\n\n // Exchange authorization code for tokens\n let tokens: FigmaTokens\n try {\n tokens = await exchangeCodeForTokens(code, codeVerifier, actualRedirectUri, clientId)\n } catch (error) {\n throw new OAuthFlowError(\n 'Failed to exchange authorization code for tokens',\n error instanceof Error ? error : undefined,\n )\n }\n\n // Store tokens\n try {\n tokenStore.setTokens(tokens)\n } catch (error) {\n throw new OAuthFlowError('Failed to store tokens', error instanceof Error ? error : undefined)\n }\n\n return {\n tokens,\n userId: tokens.userId,\n }\n } catch (error) {\n // Clean up server on error\n callbackServer.stop()\n\n if (error instanceof OAuthFlowError) {\n throw error\n }\n\n throw new OAuthFlowError(\n `OAuth flow failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined,\n )\n } finally {\n // Ensure server is always stopped\n callbackServer.stop()\n }\n}\n\n/**\n * Get valid access token, refreshing if necessary\n *\n * Checks if stored tokens exist and are valid. If expired, automatically\n * refreshes using the refresh token. If no tokens exist, returns null.\n *\n * @param tokenStore - Token store to use\n * @returns Promise resolving to access token or null if no valid tokens\n * @throws OAuthFlowError if refresh fails\n */\nexport async function getValidAccessToken(tokenStore: TokenStore): Promise<null | string> {\n // Check if we have valid tokens\n if (tokenStore.hasValidTokens()) {\n return tokenStore.getAccessToken()\n }\n\n // Check if we have a refresh token\n const refreshToken = tokenStore.getRefreshToken()\n if (!refreshToken) {\n return null\n }\n\n // Try to refresh\n try {\n const tokens = await refreshAccessToken(refreshToken)\n tokenStore.setTokens(tokens)\n return tokens.accessToken\n } catch (error) {\n // Refresh failed - clear invalid tokens\n tokenStore.clearTokens()\n throw new OAuthFlowError(\n 'Failed to refresh access token',\n error instanceof Error ? error : undefined,\n )\n }\n}\n"],"names":["p","crypto","OAUTH_CONFIG","log","buildAuthorizationUrl","openBrowser","CallbackServer","generatePKCEPair","exchangeCodeForTokens","refreshAccessToken","OAuthFlowError","Error","message","cause","name","executeOAuthFlow","tokenStore","options","shouldMock","process","env","FIGMA_MOCK_OAUTH","debug","mockTokens","accessToken","randomBytes","toString","expiresAt","Date","now","refreshToken","scopes","tokenType","setTokens","tokens","userId","clientId","port","redirectUri","timeoutMs","codeChallenge","codeChallengeMethod","codeVerifier","state","callbackServer","callbackPromise","waitForCallback","Promise","resolve","setTimeout","actualPort","getActualPort","actualRedirectUri","replace","authUrl","authorizationUrl","shouldOpenBrowser","confirm","initialValue","isCancel","_error","warning","info","code","error","undefined","stop","getValidAccessToken","hasValidTokens","getAccessToken","getRefreshToken","clearTokens"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/auth/oauth-flow.ts"],"sourcesContent":["import * as p from '@clack/prompts'\nimport crypto from 'crypto'\n\nimport type { TokenStore } from './token-store.js'\nimport type { FigmaTokens } from './types.js'\n\nimport { OAUTH_CONFIG } from '../config/oauth.js'\nimport * as log from '../utils/log.js'\nimport { buildAuthorizationUrl, openBrowser } from './browser.js'\nimport { CallbackServer } from './callback-server.js'\nimport { generatePKCEPair } from './pkce.js'\nimport { exchangeCodeForTokens, refreshAccessToken } from './refresh.js'\n\n/**\n * Error during OAuth flow\n */\nexport class OAuthFlowError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message)\n this.name = 'OAuthFlowError'\n }\n}\n\n/**\n * Options for OAuth flow\n */\nexport interface OAuthFlowOptions {\n /** OAuth2 client ID (defaults to config) */\n clientId?: string\n /** Callback server port (defaults to 3000) */\n port?: number\n /** Redirect URI (defaults to config) */\n redirectUri?: string\n /** OAuth scopes (defaults to config) */\n scopes?: string[]\n /** Callback timeout in milliseconds (defaults to 120000) */\n timeoutMs?: number\n}\n\n/**\n * Result of OAuth flow\n */\nexport interface OAuthFlowResult {\n /** OAuth2 tokens */\n tokens: FigmaTokens\n /** Figma user ID */\n userId?: string\n}\n\n/**\n * Execute complete OAuth2 authorization flow\n *\n * This orchestrates the full OAuth flow:\n * 1. Generate PKCE pair for security\n * 2. Start local callback server\n * 3. Open browser to authorization URL\n * 4. Wait for user to authorize\n * 5. Exchange authorization code for tokens\n * 6. Store tokens securely\n *\n * @param tokenStore - Token store for persisting tokens\n * @param options - Flow configuration options\n * @returns Promise resolving to OAuth flow result\n * @throws OAuthFlowError if any step fails\n */\nexport async function executeOAuthFlow(\n tokenStore: TokenStore,\n options: OAuthFlowOptions = {},\n): Promise<OAuthFlowResult> {\n // Check if OAuth flow should be mocked\n // Default to true for OAuth (allow testing without real authentication by default)\n const shouldMock = process.env.FIGMA_MOCK_OAUTH !== 'false'\n\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Return mock tokens for testing\n log.debug('Using mock OAuth flow (FIGMA_MOCK_OAUTH != \"false\")')\n const mockTokens: FigmaTokens = {\n accessToken: 'figt_mock_access_token_' + crypto.randomBytes(16).toString('hex'),\n expiresAt: Date.now() + 2 * 60 * 60 * 1000, // 2 hours\n refreshToken: 'figt_mock_refresh_token_' + crypto.randomBytes(16).toString('hex'),\n scopes: (options.scopes || OAUTH_CONFIG.scopes) as FigmaTokens['scopes'],\n tokenType: 'Bearer',\n }\n\n // Store mock tokens\n tokenStore.setTokens(mockTokens)\n\n return {\n tokens: mockTokens,\n userId: 'mock_user_123',\n }\n }\n\n // REAL OAUTH IMPLEMENTATION\n const {\n clientId = OAUTH_CONFIG.clientId,\n port = 3000,\n redirectUri = OAUTH_CONFIG.redirectUri,\n scopes = OAUTH_CONFIG.scopes,\n timeoutMs = 120000,\n } = options\n\n // Generate PKCE pair for enhanced security\n const { codeChallenge, codeChallengeMethod, codeVerifier } = generatePKCEPair()\n\n // Generate random state for CSRF protection\n const state = crypto.randomBytes(32).toString('base64url')\n\n // Create callback server\n const callbackServer = new CallbackServer({ port, timeoutMs })\n\n try {\n // Start waiting for callback (this starts the server and captures actual port)\n const callbackPromise = callbackServer.waitForCallback(state)\n\n // Wait a bit for server to start and determine actual port\n await new Promise((resolve) => setTimeout(resolve, 100))\n\n // Get the actual port (may differ from preferred if that was in use)\n const actualPort = callbackServer.getActualPort()\n\n // Build redirect URI with actual port\n const actualRedirectUri = redirectUri.replace(/:\\d+/, `:${actualPort}`)\n\n // Build authorization URL\n const authUrl = buildAuthorizationUrl({\n authorizationUrl: OAUTH_CONFIG.authorizationUrl,\n clientId,\n codeChallenge,\n codeChallengeMethod,\n redirectUri: actualRedirectUri,\n scopes,\n state,\n })\n\n // Prompt user to open browser\n const shouldOpenBrowser = await p.confirm({\n initialValue: true,\n message: 'Log in or create Figma account in your browser?',\n })\n\n if (p.isCancel(shouldOpenBrowser)) {\n throw new OAuthFlowError('Authentication cancelled by user')\n }\n\n if (shouldOpenBrowser) {\n // Try to open browser, but don't fail if it doesn't work\n try {\n await openBrowser(authUrl)\n } catch (_error) {\n // Browser opening failed, show URL for manual opening\n log.warning('Could not open browser automatically.')\n log.info(`Please open this URL in your browser:`)\n log.info(` ${authUrl}`)\n }\n } else {\n // User declined automatic opening, show URL\n log.info(`Please open this URL manually in your browser:`)\n log.info(` ${authUrl}`)\n }\n\n // Wait for callback\n const { code } = await callbackPromise\n\n // Exchange authorization code for tokens\n let tokens: FigmaTokens\n try {\n tokens = await exchangeCodeForTokens(code, codeVerifier, actualRedirectUri, clientId)\n } catch (error) {\n throw new OAuthFlowError(\n 'Failed to exchange authorization code for tokens',\n error instanceof Error ? error : undefined,\n )\n }\n\n // Store tokens\n try {\n tokenStore.setTokens(tokens)\n } catch (error) {\n throw new OAuthFlowError('Failed to store tokens', error instanceof Error ? error : undefined)\n }\n\n return {\n tokens,\n userId: tokens.userId,\n }\n } catch (error) {\n // Clean up server on error\n callbackServer.stop()\n\n if (error instanceof OAuthFlowError) {\n throw error\n }\n\n throw new OAuthFlowError(\n `OAuth flow failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined,\n )\n } finally {\n // Ensure server is always stopped\n callbackServer.stop()\n }\n}\n\n/**\n * Get valid access token, refreshing if necessary\n *\n * Checks if stored tokens exist and are valid. If expired, automatically\n * refreshes using the refresh token. If no tokens exist, returns null.\n *\n * @param tokenStore - Token store to use\n * @returns Promise resolving to access token or null if no valid tokens\n * @throws OAuthFlowError if refresh fails\n */\nexport async function getValidAccessToken(tokenStore: TokenStore): Promise<null | string> {\n // Check if we have valid tokens\n if (tokenStore.hasValidTokens()) {\n return tokenStore.getAccessToken()\n }\n\n // Check if we have a refresh token\n const refreshToken = tokenStore.getRefreshToken()\n if (!refreshToken) {\n return null\n }\n\n // Try to refresh\n try {\n const tokens = await refreshAccessToken(refreshToken)\n tokenStore.setTokens(tokens)\n return tokens.accessToken\n } catch (error) {\n // Refresh failed - clear invalid tokens\n tokenStore.clearTokens()\n throw new OAuthFlowError(\n 'Failed to refresh access token',\n error instanceof Error ? error : undefined,\n )\n }\n}\n"],"names":["p","crypto","OAUTH_CONFIG","log","buildAuthorizationUrl","openBrowser","CallbackServer","generatePKCEPair","exchangeCodeForTokens","refreshAccessToken","OAuthFlowError","Error","message","cause","name","executeOAuthFlow","tokenStore","options","shouldMock","process","env","FIGMA_MOCK_OAUTH","debug","mockTokens","accessToken","randomBytes","toString","expiresAt","Date","now","refreshToken","scopes","tokenType","setTokens","tokens","userId","clientId","port","redirectUri","timeoutMs","codeChallenge","codeChallengeMethod","codeVerifier","state","callbackServer","callbackPromise","waitForCallback","Promise","resolve","setTimeout","actualPort","getActualPort","actualRedirectUri","replace","authUrl","authorizationUrl","shouldOpenBrowser","confirm","initialValue","isCancel","_error","warning","info","code","error","undefined","stop","getValidAccessToken","hasValidTokens","getAccessToken","getRefreshToken","clearTokens"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-token.d.ts","sourceRoot":"","sources":["../../src/auth/project-token.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AASlD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAGjC,KAAK,CAAC,EAAE,KAAK;gBADpB,OAAO,EAAE,MAAM,EACR,KAAK,CAAC,EAAE,KAAK,YAAA;CAKvB;AAkCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"project-token.d.ts","sourceRoot":"","sources":["../../src/auth/project-token.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AASlD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAGjC,KAAK,CAAC,EAAE,KAAK;gBADpB,OAAO,EAAE,MAAM,EACR,KAAK,CAAC,EAAE,KAAK,YAAA;CAKvB;AAkCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAiExB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAMxB"}
|
|
@@ -60,17 +60,13 @@ import { getValidAccessToken } from './oauth-flow.js';
|
|
|
60
60
|
* @returns Promise resolving to JWT token string or null if failed
|
|
61
61
|
* @throws {ProjectTokenError} If token generation fails
|
|
62
62
|
*/ export async function getValidProjectToken(tokenStore, tenantId) {
|
|
63
|
-
if
|
|
64
|
-
log.debug('FIGMA_CONTENT_API_ACCESS_KEY is set; skipping project token retrieval from API');
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
// Check for existing valid project token
|
|
63
|
+
// Check if we have a valid project token already
|
|
68
64
|
if (tokenStore.hasValidProjectToken(tenantId)) {
|
|
69
65
|
const projectToken = tokenStore.getProjectToken(tenantId);
|
|
70
66
|
return projectToken?.token || null;
|
|
71
67
|
}
|
|
72
68
|
// We need to refresh the project token
|
|
73
|
-
//
|
|
69
|
+
// First, get a valid user access token (will auto-refresh if needed)
|
|
74
70
|
let accessToken;
|
|
75
71
|
try {
|
|
76
72
|
accessToken = await getValidAccessToken(tokenStore);
|
|
@@ -84,12 +80,11 @@ import { getValidAccessToken } from './oauth-flow.js';
|
|
|
84
80
|
// Fetch a new project token from the Figma API
|
|
85
81
|
const projectToken = await fetchProjectToken(accessToken, tenantId);
|
|
86
82
|
// Validate the JWT signature and claims (skip for mocks)
|
|
87
|
-
const hasEnvToken = !!process.env.FIGMA_MOCK_PROJECT_TOKEN_VALUE;
|
|
88
83
|
const shouldMock = process.env.FIGMA_MOCK_PROJECT_TOKEN !== 'false';
|
|
89
84
|
let validatedClaims;
|
|
90
|
-
if (
|
|
85
|
+
if (shouldMock) {
|
|
91
86
|
// MOCK: Skip JWT validation and parse claims directly
|
|
92
|
-
log.debug('Skipping JWT validation for mock
|
|
87
|
+
log.debug('Skipping JWT validation for mock project token');
|
|
93
88
|
validatedClaims = parseJWTClaims(projectToken.token);
|
|
94
89
|
} else {
|
|
95
90
|
// REAL: Validate JWT signature against JWKS
|