@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.
- package/dist/auth/callback-server.d.ts +19 -7
- package/dist/auth/callback-server.js +72 -31
- package/dist/auth/crypto-utils.d.ts +11 -0
- package/dist/auth/crypto-utils.js +22 -1
- package/dist/auth/oauth-flow.d.ts +3 -1
- package/dist/auth/oauth-flow.js +14 -6
- package/dist/auth/project-token.d.ts +15 -10
- package/dist/auth/project-token.js +147 -64
- package/dist/auth/token-store-migration.js +2 -2
- package/dist/auth/token-store.d.ts +2 -0
- package/dist/auth/token-store.js +43 -3
- package/dist/auth/types.d.ts +11 -0
- package/dist/cli.js +15 -1
- package/dist/commands/bootstrap.d.ts +18 -0
- package/dist/commands/bootstrap.js +90 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +76 -4
- package/dist/config/oauth.d.ts +2 -1
- package/dist/config/oauth.js +7 -1
- package/dist/db-content-api/generated/content-api-types.d.ts +6 -0
- package/dist/db-content-api/index.d.ts +2 -0
- package/dist/db-content-api/index.js +36 -74
- package/dist/lib/download-skill.d.ts +13 -0
- package/dist/lib/download-skill.js +79 -0
- package/dist/oauth/endpoints/getLoginEndpoint.js +26 -107
- package/dist/oauth/endpoints/getTokenLoginEndpoint.d.ts +17 -0
- package/dist/oauth/endpoints/getTokenLoginEndpoint.js +105 -0
- package/dist/oauth/index.js +8 -0
- package/dist/oauth/utilities/establishSession.d.ts +23 -0
- package/dist/oauth/utilities/establishSession.js +82 -0
- package/dist/oauth/utilities/exchangeCodeForAccessToken.d.ts +24 -0
- package/dist/oauth/utilities/exchangeCodeForAccessToken.js +28 -0
- package/dist/oauth/utilities/isAbsoluteURL.d.ts +2 -0
- package/dist/oauth/utilities/isAbsoluteURL.js +3 -0
- package/dist/plugin/build-config.js +65 -46
- package/dist/types.d.ts +2 -0
- package/dist/utils/download-template.d.ts +9 -1
- package/dist/utils/download-template.js +24 -19
- package/dist/utils/messages.js +9 -0
- package/dist/utils/parse-template-spec.d.ts +12 -0
- package/dist/utils/parse-template-spec.js +62 -0
- package/dist/utils/payload-config-modifier.js +96 -107
- package/dist/utils/payload-package-check.d.ts +21 -1
- package/dist/utils/payload-package-check.js +66 -26
- package/dist/utils/project.d.ts +2 -1
- package/dist/utils/project.js +2 -2
- package/package.json +9 -1
- 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:
|
|
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
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
docData,
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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
|
-
|
|
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,
|
|
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 {
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
package/dist/oauth/index.js
CHANGED
|
@@ -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
|