@payloadcms/figma 0.0.1-alpha.63 → 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 (48) 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 +14 -6
  7. package/dist/auth/project-token.d.ts +15 -10
  8. package/dist/auth/project-token.js +147 -64
  9. package/dist/auth/token-store-migration.js +2 -2
  10. package/dist/auth/token-store.d.ts +2 -0
  11. package/dist/auth/token-store.js +43 -3
  12. package/dist/auth/types.d.ts +11 -0
  13. package/dist/cli.js +15 -1
  14. package/dist/commands/bootstrap.d.ts +18 -0
  15. package/dist/commands/bootstrap.js +90 -0
  16. package/dist/commands/init.d.ts +4 -0
  17. package/dist/commands/init.js +76 -4
  18. package/dist/config/oauth.d.ts +2 -1
  19. package/dist/config/oauth.js +7 -1
  20. package/dist/db-content-api/generated/content-api-types.d.ts +6 -0
  21. package/dist/db-content-api/index.d.ts +2 -0
  22. package/dist/db-content-api/index.js +36 -74
  23. package/dist/lib/download-skill.d.ts +13 -0
  24. package/dist/lib/download-skill.js +79 -0
  25. package/dist/oauth/endpoints/getLoginEndpoint.js +26 -107
  26. package/dist/oauth/endpoints/getTokenLoginEndpoint.d.ts +17 -0
  27. package/dist/oauth/endpoints/getTokenLoginEndpoint.js +105 -0
  28. package/dist/oauth/index.js +8 -0
  29. package/dist/oauth/utilities/establishSession.d.ts +23 -0
  30. package/dist/oauth/utilities/establishSession.js +82 -0
  31. package/dist/oauth/utilities/exchangeCodeForAccessToken.d.ts +24 -0
  32. package/dist/oauth/utilities/exchangeCodeForAccessToken.js +28 -0
  33. package/dist/oauth/utilities/isAbsoluteURL.d.ts +2 -0
  34. package/dist/oauth/utilities/isAbsoluteURL.js +3 -0
  35. package/dist/plugin/build-config.js +65 -46
  36. package/dist/types.d.ts +2 -0
  37. package/dist/utils/download-template.d.ts +9 -1
  38. package/dist/utils/download-template.js +24 -19
  39. package/dist/utils/messages.js +9 -0
  40. package/dist/utils/parse-template-spec.d.ts +12 -0
  41. package/dist/utils/parse-template-spec.js +62 -0
  42. package/dist/utils/payload-config-modifier.js +96 -107
  43. package/dist/utils/payload-package-check.d.ts +21 -1
  44. package/dist/utils/payload-package-check.js +66 -26
  45. package/dist/utils/project.d.ts +2 -1
  46. package/dist/utils/project.js +2 -2
  47. package/package.json +9 -1
  48. 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, {
@@ -673,41 +679,6 @@ async function upsert(args) {
673
679
  payload: this.payload
674
680
  });
675
681
  }
676
- // Mirrors MAX_DOCUMENT_UPDATES in the content API's updateDocuments.ts.
677
- // updateJobs may be called with larger limits (e.g. 150), so we batch sequentially.
678
- const CONTENT_API_MAX_UPDATES = 20;
679
- // Issues a single update request against payload-jobs and returns the unwrapped docs.
680
- async function updateJobsBatch({ batchSize, docData, meta, sortClause, whereQuery }) {
681
- const { data: response, error } = await this.client.POST('/api/v0/documents:update', {
682
- body: {
683
- collection: 'payload-jobs',
684
- contentSystemId: this.contentSystemId,
685
- createOnMissing: false,
686
- doc: docData,
687
- ...batchSize != null && {
688
- limit: batchSize
689
- },
690
- returning: {},
691
- sort: sortClause,
692
- where: whereQuery,
693
- ...meta
694
- }
695
- });
696
- if (error) {
697
- throw new Error(`Content API updateJobs error: ${JSON.stringify(error)}`);
698
- }
699
- if (!response) {
700
- throw new Error('No response from updateJobs');
701
- }
702
- if (!response.result || !('data' in response.result)) {
703
- return [];
704
- }
705
- return response.result.data.map((doc)=>unwrapDocument({
706
- collectionSlug: 'payload-jobs',
707
- doc,
708
- payload: this.payload
709
- }));
710
- }
711
682
  async function updateJobs(args) {
712
683
  const { id, limit, returning, sort, where } = args;
713
684
  if (id == null && where == null) {
@@ -737,49 +708,39 @@ async function updateJobs(args) {
737
708
  locale: undefined,
738
709
  where: whereClause
739
710
  });
740
- // When limit exceeds the content API's per-request cap, batch sequentially.
741
- const needsBatching = limit != null && limit > CONTENT_API_MAX_UPDATES;
742
- if (!needsBatching) {
743
- const docs = await updateJobsBatch.call(this, {
744
- batchSize: limit,
745
- docData,
746
- meta,
747
- sortClause,
748
- whereQuery
749
- });
750
- if (returning === false) {
751
- return null;
752
- }
753
- return docs;
754
- }
755
- // Batched path: issue sequential requests of ≤ CONTENT_API_MAX_UPDATES each.
756
- // This relies on the update itself causing matched jobs to no longer satisfy the where
757
- // clause on subsequent batches (e.g. setting processing: true removes them from a
758
- // "processing: false" query). If a future updateJobs call in Payload core updates jobs
759
- // in a way that does NOT change their match status, the same jobs could be updated
760
- // multiple times across batches.
761
- const allDocs = [];
762
- let remaining = limit;
763
- while(remaining > 0){
764
- const batchSize = Math.min(remaining, CONTENT_API_MAX_UPDATES);
765
- const batchDocs = await updateJobsBatch.call(this, {
766
- batchSize,
767
- docData,
768
- meta,
769
- sortClause,
770
- whereQuery
771
- });
772
- allDocs.push(...batchDocs);
773
- remaining -= batchSize;
774
- // Stop early when this batch returned fewer docs than requested — no more matching docs.
775
- if (batchDocs.length < batchSize) {
776
- break;
711
+ const { data: response, error } = await this.client.POST('/api/v0/documents:update', {
712
+ body: {
713
+ collection: 'payload-jobs',
714
+ contentSystemId: this.contentSystemId,
715
+ createOnMissing: false,
716
+ doc: docData,
717
+ environmentName: this.environmentName,
718
+ ...limit != null && {
719
+ limit
720
+ },
721
+ returning: {},
722
+ sort: sortClause,
723
+ where: whereQuery,
724
+ ...meta
777
725
  }
726
+ });
727
+ if (error) {
728
+ throw new Error(`Content API updateJobs error: ${JSON.stringify(error)}`);
729
+ }
730
+ if (!response) {
731
+ throw new Error('No response from updateJobs');
778
732
  }
779
733
  if (returning === false) {
780
734
  return null;
781
735
  }
782
- return allDocs;
736
+ if (!response.result || !('data' in response.result)) {
737
+ return [];
738
+ }
739
+ return response.result.data.map((doc)=>unwrapDocument({
740
+ collectionSlug: 'payload-jobs',
741
+ doc,
742
+ payload: this.payload
743
+ }));
783
744
  }
784
745
  function createGlobal(args) {
785
746
  // Globals are singletons identified by their slug. Use upsert so concurrent calls
@@ -908,6 +869,7 @@ export const contentAPIAdapter = (opts)=>({
908
869
  deleteMany: deleteMany,
909
870
  deleteOne: deleteOne,
910
871
  deleteVersions: deleteVersions,
872
+ environmentName: opts.environmentName,
911
873
  find: find,
912
874
  findDistinct: findDistinct,
913
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