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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/auth/callback-server.d.ts +19 -7
  2. package/dist/auth/callback-server.js +72 -31
  3. package/dist/auth/crypto-utils.d.ts +11 -0
  4. package/dist/auth/crypto-utils.js +22 -1
  5. package/dist/auth/oauth-flow.d.ts +3 -1
  6. package/dist/auth/oauth-flow.js +13 -6
  7. package/dist/auth/token-store.d.ts +2 -0
  8. package/dist/auth/token-store.js +41 -1
  9. package/dist/auth/types.d.ts +7 -0
  10. package/dist/cli.js +6 -1
  11. package/dist/commands/init.d.ts +4 -0
  12. package/dist/commands/init.js +44 -2
  13. package/dist/config/oauth.d.ts +2 -1
  14. package/dist/config/oauth.js +7 -1
  15. package/dist/db-content-api/generated/content-api-types.d.ts +6 -0
  16. package/dist/db-content-api/index.d.ts +2 -0
  17. package/dist/db-content-api/index.js +9 -1
  18. package/dist/lib/download-skill.d.ts +13 -0
  19. package/dist/lib/download-skill.js +79 -0
  20. package/dist/oauth/endpoints/getLoginEndpoint.js +26 -107
  21. package/dist/oauth/endpoints/getTokenLoginEndpoint.d.ts +17 -0
  22. package/dist/oauth/endpoints/getTokenLoginEndpoint.js +105 -0
  23. package/dist/oauth/index.js +8 -0
  24. package/dist/oauth/utilities/establishSession.d.ts +23 -0
  25. package/dist/oauth/utilities/establishSession.js +82 -0
  26. package/dist/oauth/utilities/exchangeCodeForAccessToken.d.ts +24 -0
  27. package/dist/oauth/utilities/exchangeCodeForAccessToken.js +28 -0
  28. package/dist/oauth/utilities/isAbsoluteURL.d.ts +2 -0
  29. package/dist/oauth/utilities/isAbsoluteURL.js +3 -0
  30. package/dist/plugin/build-config.js +3 -0
  31. package/dist/types.d.ts +2 -0
  32. package/dist/utils/download-template.d.ts +9 -1
  33. package/dist/utils/download-template.js +24 -19
  34. package/dist/utils/messages.js +2 -0
  35. package/dist/utils/parse-template-spec.d.ts +12 -0
  36. package/dist/utils/parse-template-spec.js +62 -0
  37. package/dist/utils/project.d.ts +2 -1
  38. package/dist/utils/project.js +2 -2
  39. package/package.json +9 -1
  40. package/dist/db-content-api/README.md +0 -98
@@ -213,7 +213,7 @@ async function createVersion(args) {
213
213
  contentSystemId: this.contentSystemId,
214
214
  versionDoc: {
215
215
  createdAt: args.createdAt,
216
- latest: !args.autosave,
216
+ latest: true,
217
217
  parent: String(args.parent),
218
218
  updatedAt: args.updatedAt,
219
219
  version: dataToContentAPI(this.payload, args.collectionSlug, args.versionData, {
@@ -365,6 +365,7 @@ async function updateMany(args) {
365
365
  contentSystemId: this.contentSystemId,
366
366
  createOnMissing: false,
367
367
  doc: dataToContentAPI(this.payload, args.collection, args.data),
368
+ environmentName: this.environmentName,
368
369
  limit: args.limit,
369
370
  locale,
370
371
  returning: {},
@@ -411,6 +412,7 @@ async function updateOne(args) {
411
412
  contentSystemId: this.contentSystemId,
412
413
  createOnMissing: false,
413
414
  doc: dataToContentAPI(this.payload, args.collection, args.data),
415
+ environmentName: this.environmentName,
414
416
  locale,
415
417
  returning: {},
416
418
  where: convertPayloadWhereToContentAPI(where),
@@ -447,6 +449,7 @@ async function deleteMany(args) {
447
449
  body: {
448
450
  collection: args.collection,
449
451
  contentSystemId: this.contentSystemId,
452
+ environmentName: this.environmentName,
450
453
  locale,
451
454
  where: convertPayloadWhereToContentAPI(args.where),
452
455
  ...buildMeta(this.payload, {
@@ -466,6 +469,7 @@ async function deleteOne(args) {
466
469
  body: {
467
470
  collection: args.collection,
468
471
  contentSystemId: this.contentSystemId,
472
+ environmentName: this.environmentName,
469
473
  locale,
470
474
  returning: {},
471
475
  where: convertPayloadWhereToContentAPI(args.where),
@@ -525,6 +529,7 @@ async function create(args) {
525
529
  }),
526
530
  id
527
531
  },
532
+ environmentName: this.environmentName,
528
533
  locale,
529
534
  ...buildMeta(this.payload, {
530
535
  collection: args.collection,
@@ -643,6 +648,7 @@ async function upsert(args) {
643
648
  applyDefaults: true,
644
649
  createdAt: true
645
650
  }),
651
+ environmentName: this.environmentName,
646
652
  locale,
647
653
  where: convertPayloadWhereToContentAPI(args.where),
648
654
  ...buildMeta(this.payload, {
@@ -708,6 +714,7 @@ async function updateJobs(args) {
708
714
  contentSystemId: this.contentSystemId,
709
715
  createOnMissing: false,
710
716
  doc: docData,
717
+ environmentName: this.environmentName,
711
718
  ...limit != null && {
712
719
  limit
713
720
  },
@@ -862,6 +869,7 @@ export const contentAPIAdapter = (opts)=>({
862
869
  deleteMany: deleteMany,
863
870
  deleteOne: deleteOne,
864
871
  deleteVersions: deleteVersions,
872
+ environmentName: opts.environmentName,
865
873
  find: find,
866
874
  findDistinct: findDistinct,
867
875
  findGlobal: findGlobal,
@@ -0,0 +1,13 @@
1
+ export type DownloadSkillResult = {
2
+ detail?: string;
3
+ ok: false;
4
+ reason: 'already-installed' | 'empty-extract' | 'error' | 'fetch-failed';
5
+ } | {
6
+ ok: true;
7
+ };
8
+ export declare function downloadSkill(args: {
9
+ branch?: string;
10
+ debug?: boolean;
11
+ projectDir: string;
12
+ }): Promise<DownloadSkillResult>;
13
+ //# sourceMappingURL=download-skill.d.ts.map
@@ -0,0 +1,79 @@
1
+ import fs from 'fs/promises';
2
+ import { Readable } from 'node:stream';
3
+ import { pipeline } from 'node:stream/promises';
4
+ import path from 'path';
5
+ import { x } from 'tar';
6
+ export async function downloadSkill(args) {
7
+ const { branch = 'main', debug, projectDir } = args;
8
+ const destDir = path.join(projectDir, '.claude', 'skills', 'payload');
9
+ if (await pathExists(path.join(destDir, 'SKILL.md'))) {
10
+ return {
11
+ ok: false,
12
+ reason: 'already-installed'
13
+ };
14
+ }
15
+ const url = `https://codeload.github.com/payloadcms/payload/tar.gz/${branch}`;
16
+ const filter = `payload-${branch.replace(/^v/, '').replaceAll('/', '-')}/tools/claude-plugin/skills/payload/`;
17
+ if (debug) {
18
+ // eslint-disable-next-line no-console
19
+ console.log(`[download-skill] url=${url} dest=${destDir}`);
20
+ }
21
+ try {
22
+ const res = await fetch(url);
23
+ if (!res.ok) {
24
+ return {
25
+ detail: `${res.status} ${res.statusText}`,
26
+ ok: false,
27
+ reason: 'fetch-failed'
28
+ };
29
+ }
30
+ if (!res.body) {
31
+ return {
32
+ detail: 'empty response body',
33
+ ok: false,
34
+ reason: 'fetch-failed'
35
+ };
36
+ }
37
+ await fs.mkdir(destDir, {
38
+ recursive: true
39
+ });
40
+ await pipeline(Readable.from(res.body), x({
41
+ cwd: destDir,
42
+ filter: (p)=>p.includes(filter),
43
+ strip: filter.split('/').length - 1
44
+ }));
45
+ if (!await pathExists(path.join(destDir, 'SKILL.md'))) {
46
+ await fs.rm(destDir, {
47
+ force: true,
48
+ recursive: true
49
+ });
50
+ return {
51
+ ok: false,
52
+ reason: 'empty-extract'
53
+ };
54
+ }
55
+ return {
56
+ ok: true
57
+ };
58
+ } catch (err) {
59
+ await fs.rm(destDir, {
60
+ force: true,
61
+ recursive: true
62
+ }).catch(()=>{});
63
+ return {
64
+ detail: err instanceof Error ? err.message : String(err),
65
+ ok: false,
66
+ reason: 'error'
67
+ };
68
+ }
69
+ }
70
+ async function pathExists(p) {
71
+ try {
72
+ await fs.access(p);
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ //# sourceMappingURL=download-skill.js.map
@@ -1,11 +1,10 @@
1
- import { APIError, generateCookie, parseCookies } from 'payload';
1
+ import { APIError, parseCookies } from 'payload';
2
2
  import { formatAdminURL, getSafeRedirect } from 'payload/shared';
3
- import { getProjectToken, getUserInfo } from '../../api/figma-api.js';
4
- import { createCookieOptions } from '../utilities/createCookieOptions.js';
5
3
  import { createDebugLogger } from '../utilities/createDebugLogger.js';
4
+ import { establishSession } from '../utilities/establishSession.js';
5
+ import { exchangeCodeForAccessToken } from '../utilities/exchangeCodeForAccessToken.js';
6
6
  import { extractOrigin } from '../utilities/extractOrigin.js';
7
- import { getCookieExpiration } from '../utilities/getCookieExpiration.js';
8
- const isAbsoluteURL = (url)=>/^[a-z][a-z\d+.-]*:/.test(url);
7
+ import { isAbsoluteURL } from '../utilities/isAbsoluteURL.js';
9
8
  export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug, pluginOptions, strategy })=>({
10
9
  handler: async (req)=>{
11
10
  const { config } = req.payload;
@@ -127,32 +126,21 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
127
126
  });
128
127
  return Response.redirect(failedRedirect);
129
128
  }
130
- const body = {
131
- client_id: strategy.clientID,
132
- code,
133
- code_verifier: req.payload.decrypt(state.verifier),
134
- grant_type: 'authorization_code',
135
- redirect_uri: formatAdminURL({
136
- apiRoute: config.routes?.api || '/api',
137
- path: `/${collection.slug}/${endpointSlug}/login`,
138
- serverURL: redirectServerURL
139
- })
140
- };
141
- if (strategy.collectionOptions.clientSecret) {
142
- body.client_secret = strategy.collectionOptions.clientSecret;
143
- }
129
+ const redirectUri = formatAdminURL({
130
+ apiRoute: config.routes?.api || '/api',
131
+ path: `/${collection.slug}/${endpointSlug}/login`,
132
+ serverURL: redirectServerURL
133
+ });
144
134
  debugLogger.info({
145
135
  msg: 'Exchanging code for access token',
146
- path,
147
- requestBody: body
136
+ path
137
+ });
138
+ const tokenRes = await exchangeCodeForAccessToken({
139
+ code,
140
+ codeVerifier: req.payload.decrypt(state.verifier),
141
+ redirectUri,
142
+ strategy
148
143
  });
149
- const tokenRes = await fetch(strategy.meta?.token_endpoint, {
150
- body: new URLSearchParams(body),
151
- headers: {
152
- 'Content-Type': 'application/x-www-form-urlencoded'
153
- },
154
- method: 'POST'
155
- }).then((r)=>r.json());
156
144
  debugLogger.info({
157
145
  msg: 'Token Response',
158
146
  path,
@@ -167,25 +155,6 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
167
155
  });
168
156
  return Response.redirect(failedRedirect);
169
157
  }
170
- // Fetch Figma user info for handle and profile image
171
- let figmaUserInfo = null;
172
- try {
173
- const oauthCredential = {
174
- type: 'oauth',
175
- token: access_token
176
- };
177
- figmaUserInfo = await getUserInfo(oauthCredential);
178
- debugLogger.info({
179
- figmaUserInfo,
180
- msg: 'Fetched Figma user info'
181
- });
182
- } catch (err) {
183
- // Non-fatal: user will just not have handle/image
184
- debugLogger.error({
185
- err,
186
- msg: 'Failed to fetch Figma user info'
187
- });
188
- }
189
158
  if (!access_token) {
190
159
  const errMsg = 'No access_token was returned from the identity provider.';
191
160
  req.payload.logger.error({
@@ -195,30 +164,6 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
195
164
  });
196
165
  throw new APIError(errMsg);
197
166
  }
198
- // Swap for project token
199
- const oauthCredential = {
200
- type: 'oauth',
201
- token: access_token
202
- };
203
- const project_token = await getProjectToken(oauthCredential, config.custom.figma.contentSystemId);
204
- const refresh_token = access_token;
205
- // Store project token as access_token
206
- const token = project_token.token;
207
- const expiresIn = project_token.expiresAt;
208
- if (!token) {
209
- const errMsg = `No ${collectionOptions.token || 'access_token'} was returned from the identity provider.`;
210
- req.payload.logger.error({
211
- msg: errMsg,
212
- path
213
- });
214
- throw new APIError(errMsg);
215
- }
216
- const cookieOptions = createCookieOptions({
217
- collection,
218
- headers: req.headers,
219
- pluginOptions
220
- });
221
- // get the payloadRedirect cookie value
222
167
  const cookies = parseCookies(req.headers);
223
168
  const payloadRedirect = 'payloadRedirect' in cookies ? cookies.payloadRedirect : '';
224
169
  if (!req.responseHeaders) {
@@ -232,43 +177,17 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
232
177
  }
233
178
  req.responseHeaders.append('Set-Cookie', `payloadRedirect=${redirectToUse}; Max-Age=0; Path=/;`);
234
179
  }
235
- if (refresh_token && !collectionOptions.usePayloadJWT) {
236
- debugLogger.info('Setting refresh token cookie');
237
- const refreshCookie = generateCookie({
238
- returnCookieAsObject: false,
239
- ...cookieOptions,
240
- name: `${strategy.cookieName}-refresh`,
241
- value: refresh_token,
242
- // Default to 1 year if no expiration
243
- expires: getCookieExpiration(cookieOptions?.expires ?? refreshTokenExpires ?? 31_556_952)
244
- });
245
- req.responseHeaders.append('Set-Cookie', refreshCookie);
246
- }
247
- const tokenExpires = cookieOptions?.expires ? getCookieExpiration(cookieOptions.expires) : new Date(expiresIn) // Project token uses epoch time
248
- ;
249
- const tokenCookie = generateCookie({
250
- ...cookieOptions,
251
- name: strategy.cookieName,
252
- expires: tokenExpires,
253
- returnCookieAsObject: false,
254
- value: token
180
+ await establishSession({
181
+ accessToken: access_token,
182
+ collection,
183
+ collectionOptions,
184
+ contentSystemId: config.custom.figma.contentSystemId,
185
+ debugLogger,
186
+ pluginOptions,
187
+ refreshTokenExpiresIn: refreshTokenExpires,
188
+ req,
189
+ strategy
255
190
  });
256
- req.responseHeaders.append('Set-Cookie', tokenCookie);
257
- // Store user info in short-lived cookie for defaultVerify to consume
258
- if (figmaUserInfo) {
259
- const userInfoValue = Buffer.from(JSON.stringify({
260
- handle: figmaUserInfo.handle,
261
- img_url: figmaUserInfo.img_url
262
- })).toString('base64');
263
- const userInfoCookie = generateCookie({
264
- ...cookieOptions,
265
- name: pluginOptions?.userInfoCookieName ?? 'figma-user-info',
266
- expires: new Date(Date.now() + 60_000),
267
- returnCookieAsObject: false,
268
- value: userInfoValue
269
- });
270
- req.responseHeaders.append('Set-Cookie', userInfoCookie);
271
- }
272
191
  return Response.redirect(redirectToUse);
273
192
  } catch (err) {
274
193
  req.payload.logger.error({
@@ -0,0 +1,17 @@
1
+ import type { CollectionConfig, Endpoint } from 'payload';
2
+ import type { Strategy } from '../strategy/index.js';
3
+ import type { CollectionOptions, PluginOptions } from '../types.js';
4
+ interface Args {
5
+ collection: CollectionConfig;
6
+ collectionOptions: CollectionOptions;
7
+ endpointSlug: string;
8
+ pluginOptions: PluginOptions;
9
+ strategy: Strategy;
10
+ }
11
+ /**
12
+ * Auto-login endpoint for embedded contexts where the interactive OAuth flow
13
+ * cannot run.
14
+ */
15
+ export declare const getTokenLoginEndpoint: ({ collection, collectionOptions, endpointSlug, pluginOptions, strategy, }: Args) => Endpoint;
16
+ export {};
17
+ //# sourceMappingURL=getTokenLoginEndpoint.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { formatAdminURL, getSafeRedirect } from 'payload/shared';
2
+ import { createDebugLogger } from '../utilities/createDebugLogger.js';
3
+ import { establishSession } from '../utilities/establishSession.js';
4
+ import { exchangeCodeForAccessToken } from '../utilities/exchangeCodeForAccessToken.js';
5
+ import { isAbsoluteURL } from '../utilities/isAbsoluteURL.js';
6
+ /**
7
+ * Auto-login endpoint for embedded contexts where the interactive OAuth flow
8
+ * cannot run.
9
+ */ export const getTokenLoginEndpoint = ({ collection, collectionOptions, endpointSlug, pluginOptions, strategy })=>({
10
+ handler: async (req)=>{
11
+ const { config } = req.payload;
12
+ const adminRoute = config.routes?.admin || '/admin';
13
+ const debugLogger = createDebugLogger(req.payload, pluginOptions.debug);
14
+ const adminURL = formatAdminURL({
15
+ adminRoute,
16
+ serverURL: config.serverURL
17
+ });
18
+ const failedRedirect = formatAdminURL({
19
+ adminRoute,
20
+ path: '/login',
21
+ serverURL: config.serverURL
22
+ });
23
+ try {
24
+ await strategy.ensureMeta();
25
+ const code = req.query.code;
26
+ const redirectQuery = req.query.redirect;
27
+ let redirectToUse = adminURL;
28
+ if (typeof redirectQuery === 'string' && redirectQuery.length > 0) {
29
+ const safeRedirect = getSafeRedirect({
30
+ allowAbsoluteUrls: false,
31
+ redirectTo: redirectQuery
32
+ });
33
+ if (safeRedirect && !isAbsoluteURL(safeRedirect)) {
34
+ redirectToUse = `${config.serverURL ?? ''}${safeRedirect}`;
35
+ }
36
+ }
37
+ if (typeof code !== 'string' || code.length === 0) {
38
+ debugLogger.error({
39
+ msg: 'token_login: missing code'
40
+ });
41
+ return Response.redirect(failedRedirect);
42
+ }
43
+ if (!strategy.meta?.token_endpoint) {
44
+ debugLogger.error({
45
+ msg: 'token_login: no token_endpoint discovered from identity provider'
46
+ });
47
+ return Response.redirect(failedRedirect);
48
+ }
49
+ debugLogger.info({
50
+ msg: 'token_login: exchanging code for access token'
51
+ });
52
+ const tokenRes = await exchangeCodeForAccessToken({
53
+ code,
54
+ redirectUri: formatAdminURL({
55
+ apiRoute: config.routes?.api || '/api',
56
+ path: `/${collection.slug}/${endpointSlug}/token_login`,
57
+ serverURL: config.serverURL
58
+ }),
59
+ strategy
60
+ });
61
+ const { access_token, error, error_description, expires_in } = tokenRes;
62
+ if (error) {
63
+ req.payload.logger.error({
64
+ err: error,
65
+ error_description,
66
+ msg: 'token_login: error exchanging code for access token'
67
+ });
68
+ return Response.redirect(failedRedirect);
69
+ }
70
+ if (typeof access_token !== 'string' || access_token.length === 0) {
71
+ req.payload.logger.error({
72
+ msg: 'token_login: no access_token returned from identity provider'
73
+ });
74
+ return Response.redirect(failedRedirect);
75
+ }
76
+ await establishSession({
77
+ accessToken: access_token,
78
+ collection,
79
+ collectionOptions,
80
+ contentSystemId: config.custom.figma.contentSystemId,
81
+ debugLogger,
82
+ pluginOptions,
83
+ refreshTokenExpiresIn: expires_in,
84
+ req,
85
+ strategy
86
+ });
87
+ return Response.redirect(redirectToUse);
88
+ } catch (err) {
89
+ req.payload.logger.error({
90
+ err,
91
+ msg: 'Error during token_login',
92
+ path: req.pathname
93
+ });
94
+ return Response.json({
95
+ error: 'Error during token_login'
96
+ }, {
97
+ status: 500
98
+ });
99
+ }
100
+ },
101
+ method: 'get',
102
+ path: `/${endpointSlug}/token_login`
103
+ });
104
+
105
+ //# sourceMappingURL=getTokenLoginEndpoint.js.map
@@ -4,6 +4,7 @@ import { getLoginEndpoint } from './endpoints/getLoginEndpoint.js';
4
4
  import { getLogoutEndpoint } from './endpoints/getLogoutEndpoint.js';
5
5
  import { getMetaEndpoint } from './endpoints/getMetaEndpoint.js';
6
6
  import { getRedirectToLoginEndpoint } from './endpoints/getRedirectToLoginEndpoint.js';
7
+ import { getTokenLoginEndpoint } from './endpoints/getTokenLoginEndpoint.js';
7
8
  import { getAfterLogout } from './hooks/afterLogout.js';
8
9
  import { getMeHook } from './hooks/me.js';
9
10
  import { getRefreshHook } from './hooks/refresh.js';
@@ -206,6 +207,13 @@ export const oAuth2Plugin = (pluginOptions)=>(config)=>{
206
207
  endpointSlug,
207
208
  pluginOptions,
208
209
  strategy
210
+ }),
211
+ getTokenLoginEndpoint({
212
+ collection: existingCollection,
213
+ collectionOptions,
214
+ endpointSlug,
215
+ pluginOptions,
216
+ strategy
209
217
  })
210
218
  ],
211
219
  fields,
@@ -0,0 +1,23 @@
1
+ import type { CollectionConfig, PayloadRequest } from 'payload';
2
+ import type { Strategy } from '../strategy/index.js';
3
+ import type { CollectionOptions, DebugLogger, PluginOptions } from '../types.js';
4
+ interface Args {
5
+ accessToken: string;
6
+ collection: CollectionConfig;
7
+ collectionOptions: CollectionOptions;
8
+ contentSystemId: string;
9
+ debugLogger: DebugLogger;
10
+ pluginOptions: PluginOptions;
11
+ /** From the IdP token response (`expires_in`); used as a refresh-cookie expiry fallback. */
12
+ refreshTokenExpiresIn?: number;
13
+ req: PayloadRequest;
14
+ strategy: Strategy;
15
+ }
16
+ /**
17
+ * Given a valid OAuth access_token: fetch user info (non-fatal), mint a
18
+ * project token, and write the main + refresh + user-info cookies to
19
+ * `req.responseHeaders`. Throws `APIError` if the project token mint fails.
20
+ */
21
+ export declare const establishSession: ({ accessToken, collection, collectionOptions, contentSystemId, debugLogger, pluginOptions, refreshTokenExpiresIn, req, strategy, }: Args) => Promise<void>;
22
+ export {};
23
+ //# sourceMappingURL=establishSession.d.ts.map
@@ -0,0 +1,82 @@
1
+ import { APIError, generateCookie } from 'payload';
2
+ import { getProjectToken, getUserInfo } from '../../api/figma-api.js';
3
+ import { createCookieOptions } from './createCookieOptions.js';
4
+ import { getCookieExpiration } from './getCookieExpiration.js';
5
+ /**
6
+ * Given a valid OAuth access_token: fetch user info (non-fatal), mint a
7
+ * project token, and write the main + refresh + user-info cookies to
8
+ * `req.responseHeaders`. Throws `APIError` if the project token mint fails.
9
+ */ export const establishSession = async ({ accessToken, collection, collectionOptions, contentSystemId, debugLogger, pluginOptions, refreshTokenExpiresIn, req, strategy })=>{
10
+ const oauthCredential = {
11
+ type: 'oauth',
12
+ token: accessToken
13
+ };
14
+ // Non-fatal: user just won't have handle/image on first request.
15
+ let figmaUserInfo = null;
16
+ try {
17
+ figmaUserInfo = await getUserInfo(oauthCredential);
18
+ debugLogger.info({
19
+ figmaUserInfo,
20
+ msg: 'Fetched Figma user info'
21
+ });
22
+ } catch (err) {
23
+ debugLogger.error({
24
+ err,
25
+ msg: 'Failed to fetch Figma user info'
26
+ });
27
+ }
28
+ const projectToken = await getProjectToken(oauthCredential, contentSystemId);
29
+ if (!projectToken.token) {
30
+ const errMsg = 'No project token returned from Figma API';
31
+ req.payload.logger.error({
32
+ msg: errMsg
33
+ });
34
+ throw new APIError(errMsg);
35
+ }
36
+ const cookieOptions = createCookieOptions({
37
+ collection,
38
+ headers: req.headers,
39
+ pluginOptions
40
+ });
41
+ if (!req.responseHeaders) {
42
+ req.responseHeaders = new Headers();
43
+ }
44
+ // The -refresh cookie holds the OAuth access_token so refreshTokens can
45
+ // re-mint project_tokens from it on JWT expiry.
46
+ if (!collectionOptions.usePayloadJWT) {
47
+ const refreshCookie = generateCookie({
48
+ ...cookieOptions,
49
+ name: `${strategy.cookieName}-refresh`,
50
+ expires: getCookieExpiration(cookieOptions?.expires ?? refreshTokenExpiresIn ?? 31_556_952),
51
+ returnCookieAsObject: false,
52
+ value: accessToken
53
+ });
54
+ req.responseHeaders.append('Set-Cookie', refreshCookie);
55
+ }
56
+ const tokenExpires = cookieOptions?.expires ? getCookieExpiration(cookieOptions.expires) : new Date(projectToken.expiresAt);
57
+ const tokenCookie = generateCookie({
58
+ ...cookieOptions,
59
+ name: strategy.cookieName,
60
+ expires: tokenExpires,
61
+ returnCookieAsObject: false,
62
+ value: projectToken.token
63
+ });
64
+ req.responseHeaders.append('Set-Cookie', tokenCookie);
65
+ // Short-lived handoff cookie consumed by defaultVerify on the next request.
66
+ if (figmaUserInfo) {
67
+ const userInfoValue = Buffer.from(JSON.stringify({
68
+ handle: figmaUserInfo.handle,
69
+ img_url: figmaUserInfo.img_url
70
+ })).toString('base64');
71
+ const userInfoCookie = generateCookie({
72
+ ...cookieOptions,
73
+ name: pluginOptions?.userInfoCookieName ?? 'figma-user-info',
74
+ expires: new Date(Date.now() + 60_000),
75
+ returnCookieAsObject: false,
76
+ value: userInfoValue
77
+ });
78
+ req.responseHeaders.append('Set-Cookie', userInfoCookie);
79
+ }
80
+ };
81
+
82
+ //# sourceMappingURL=establishSession.js.map
@@ -0,0 +1,24 @@
1
+ import type { Strategy } from '../strategy/index.js';
2
+ interface Args {
3
+ /** Authorization code received from the IdP redirect. */
4
+ code: string;
5
+ /** PKCE verifier; omit for non-PKCE flows. */
6
+ codeVerifier?: string;
7
+ /** Must match the redirect_uri the authorization request used. */
8
+ redirectUri: string;
9
+ strategy: Strategy;
10
+ }
11
+ export interface TokenEndpointResponse {
12
+ access_token?: null | string;
13
+ error?: string;
14
+ error_description?: string;
15
+ expires_in?: number;
16
+ }
17
+ /**
18
+ * POSTs an authorization code to the IdP's `token_endpoint` and returns the
19
+ * raw response body. Callers handle error/missing-token logging in their own
20
+ * style — this helper only owns the request shape.
21
+ */
22
+ export declare const exchangeCodeForAccessToken: ({ code, codeVerifier, redirectUri, strategy, }: Args) => Promise<TokenEndpointResponse>;
23
+ export {};
24
+ //# sourceMappingURL=exchangeCodeForAccessToken.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * POSTs an authorization code to the IdP's `token_endpoint` and returns the
3
+ * raw response body. Callers handle error/missing-token logging in their own
4
+ * style — this helper only owns the request shape.
5
+ */ export const exchangeCodeForAccessToken = async ({ code, codeVerifier, redirectUri, strategy })=>{
6
+ const body = {
7
+ client_id: strategy.clientID,
8
+ code,
9
+ grant_type: 'authorization_code',
10
+ redirect_uri: redirectUri
11
+ };
12
+ if (codeVerifier) {
13
+ body.code_verifier = codeVerifier;
14
+ }
15
+ if (strategy.collectionOptions.clientSecret) {
16
+ body.client_secret = strategy.collectionOptions.clientSecret;
17
+ }
18
+ const res = await fetch(strategy.meta.token_endpoint, {
19
+ body: new URLSearchParams(body),
20
+ headers: {
21
+ 'Content-Type': 'application/x-www-form-urlencoded'
22
+ },
23
+ method: 'POST'
24
+ });
25
+ return await res.json();
26
+ };
27
+
28
+ //# sourceMappingURL=exchangeCodeForAccessToken.js.map
@@ -0,0 +1,2 @@
1
+ export declare const isAbsoluteURL: (url: string) => boolean;
2
+ //# sourceMappingURL=isAbsoluteURL.d.ts.map
@@ -0,0 +1,3 @@
1
+ export const isAbsoluteURL = (url)=>/^[a-z][a-z\d+.-]*:/.test(url);
2
+
3
+ //# sourceMappingURL=isAbsoluteURL.js.map