@payloadcms/figma 0.0.1-alpha.74 → 0.0.1-alpha.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/figma-api.d.ts +2 -1
- package/dist/api/figma-api.js +33 -3
- package/dist/auth/jwt-validator.d.ts +12 -2
- package/dist/auth/jwt-validator.js +19 -1
- package/dist/auth/project-token.js +3 -2
- package/dist/auth/types.d.ts +5 -1
- package/dist/auth/types.js +1 -1
- package/dist/cli.js +7 -0
- package/dist/commands/upload-schema.d.ts +8 -0
- package/dist/commands/upload-schema.js +91 -0
- package/dist/db-content-api/generated/content-api-types.d.ts +4188 -591
- package/dist/db-content-api/utilities/data/pointShape.d.ts +3 -0
- package/dist/db-content-api/utilities/data/pointShape.js +43 -0
- package/dist/db-content-api/utilities/data/richTextShape.d.ts +3 -0
- package/dist/db-content-api/utilities/data/richTextShape.js +56 -0
- package/dist/db-content-api/utilities/schema/buildDocumentSchema.d.ts +56 -0
- package/dist/db-content-api/utilities/schema/buildDocumentSchema.js +183 -0
- package/dist/oauth/defaults.js +8 -0
- package/dist/oauth/endpoints/getLoginEndpoint.js +4 -6
- package/dist/oauth/strategy/index.js +11 -19
- package/dist/oauth/utilities/establishSession.d.ts +4 -4
- package/dist/oauth/utilities/establishSession.js +9 -10
- package/dist/oauth/utilities/refreshTokens.js +13 -15
- package/dist/plugin/build-config.d.ts +1 -0
- package/dist/plugin/build-config.js +66 -1
- package/dist/utils/load-payload-config.d.ts +9 -0
- package/dist/utils/load-payload-config.js +27 -0
- package/dist/utils/messages.js +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payload stores a point as GeoJSON; the v1 server uses a bare `[lng, lat]`
|
|
3
|
+
* tuple. Unwrap on write, re-wrap on read so afterRead can restore the tuple.
|
|
4
|
+
*/ function isPlainObject(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function isCoordinateTuple(value) {
|
|
8
|
+
return Array.isArray(value) && value.length === 2 && value.every((v)=>typeof v === 'number');
|
|
9
|
+
}
|
|
10
|
+
function pointLeafToWire(value) {
|
|
11
|
+
if (isPlainObject(value) && isCoordinateTuple(value.coordinates)) {
|
|
12
|
+
return value.coordinates;
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function pointLeafFromWire(value) {
|
|
17
|
+
if (isCoordinateTuple(value)) {
|
|
18
|
+
return {
|
|
19
|
+
type: 'Point',
|
|
20
|
+
coordinates: value
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function mapPoint(field, value, mapLeaf) {
|
|
26
|
+
if (field.type !== 'point' || value === null || value === undefined) {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
if ('localized' in field && field.localized && isPlainObject(value)) {
|
|
30
|
+
const result = {};
|
|
31
|
+
for (const [locale, localeValue] of Object.entries(value)){
|
|
32
|
+
result[locale] = mapLeaf(localeValue);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
return mapLeaf(value);
|
|
37
|
+
}
|
|
38
|
+
export function encodePointForWrite(field, value) {
|
|
39
|
+
return mapPoint(field, value, pointLeafToWire);
|
|
40
|
+
}
|
|
41
|
+
export function decodePointFromRead(field, value) {
|
|
42
|
+
return mapPoint(field, value, pointLeafFromWire);
|
|
43
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Field, Payload, TabAsField } from 'payload';
|
|
2
|
+
export declare function encodeRichTextForWrite(field: Field | TabAsField, value: unknown): unknown;
|
|
3
|
+
export declare function decodeRichTextFromRead(field: Field | TabAsField, value: unknown, payload: Payload, fieldPath: string, collectionSlug: string): unknown;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The v1 server stores richText as a serialized JSON string, so stringify on
|
|
3
|
+
* write and parse on read. A localized richText is a per-locale map, serialized
|
|
4
|
+
* and parsed per entry (not as one blob).
|
|
5
|
+
*/ function isPlainObject(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function richTextLeafToWire(value) {
|
|
9
|
+
if (value === null || value === undefined || typeof value === 'string') {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
return JSON.stringify(value);
|
|
13
|
+
}
|
|
14
|
+
export function encodeRichTextForWrite(field, value) {
|
|
15
|
+
if (field.type !== 'richText' || value === null || value === undefined) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
if ('localized' in field && field.localized && isPlainObject(value)) {
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const [locale, localeValue] of Object.entries(value)){
|
|
21
|
+
result[locale] = richTextLeafToWire(localeValue);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
return richTextLeafToWire(value);
|
|
26
|
+
}
|
|
27
|
+
function richTextLeafFromWire(value, onError) {
|
|
28
|
+
if (typeof value !== 'string') {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(value);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
onError(error);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function decodeRichTextFromRead(field, value, payload, fieldPath, collectionSlug) {
|
|
39
|
+
if (field.type !== 'richText' || value === null || value === undefined) {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
const onError = (error)=>{
|
|
43
|
+
payload.logger.warn({
|
|
44
|
+
err: error instanceof Error ? error : new Error(String(error)),
|
|
45
|
+
msg: `Failed to parse richtext field '${fieldPath}' in collection '${collectionSlug}'`
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
if ('localized' in field && field.localized && isPlainObject(value)) {
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const [locale, localeValue] of Object.entries(value)){
|
|
51
|
+
result[locale] = richTextLeafFromWire(localeValue, onError);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
return richTextLeafFromWire(value, onError);
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Config } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Wire form of the v1 `DocumentSchema` sent on every request so the server can
|
|
4
|
+
* classify each queried path against the index tables. Paths are
|
|
5
|
+
* period-delimited (e.g. `author.name`).
|
|
6
|
+
*/
|
|
7
|
+
export interface DocumentSchemaWire {
|
|
8
|
+
collections: Record<string, WireCollectionSchema>;
|
|
9
|
+
version: 1;
|
|
10
|
+
}
|
|
11
|
+
interface WireCollectionSchema {
|
|
12
|
+
hints: WireHint[];
|
|
13
|
+
name: string;
|
|
14
|
+
paths: Record<string, WireFieldSchema>;
|
|
15
|
+
}
|
|
16
|
+
type WireHint = {
|
|
17
|
+
paths: string[];
|
|
18
|
+
type: 'unique';
|
|
19
|
+
};
|
|
20
|
+
type WireFieldSchema = {
|
|
21
|
+
collection: string | string[];
|
|
22
|
+
on: string;
|
|
23
|
+
type: 'join';
|
|
24
|
+
} | {
|
|
25
|
+
collection: string | string[];
|
|
26
|
+
hasMany: boolean;
|
|
27
|
+
localized: boolean;
|
|
28
|
+
type: 'relationship' | 'upload';
|
|
29
|
+
unique: boolean;
|
|
30
|
+
} | {
|
|
31
|
+
format: 'lexical';
|
|
32
|
+
localized: boolean;
|
|
33
|
+
type: 'richText';
|
|
34
|
+
} | {
|
|
35
|
+
hasMany: boolean;
|
|
36
|
+
localized: boolean;
|
|
37
|
+
type: 'group';
|
|
38
|
+
} | {
|
|
39
|
+
hasMany: boolean;
|
|
40
|
+
localized: boolean;
|
|
41
|
+
type: 'number' | 'select' | 'text';
|
|
42
|
+
unique: boolean;
|
|
43
|
+
} | {
|
|
44
|
+
localized: boolean;
|
|
45
|
+
type: 'array' | 'blocks';
|
|
46
|
+
} | {
|
|
47
|
+
localized: boolean;
|
|
48
|
+
type: 'checkbox' | 'date' | 'json' | 'radio';
|
|
49
|
+
} | {
|
|
50
|
+
localized: boolean;
|
|
51
|
+
type: 'code' | 'email' | 'point' | 'textarea';
|
|
52
|
+
unique: boolean;
|
|
53
|
+
};
|
|
54
|
+
export declare function buildDocumentSchema(config: Config): DocumentSchemaWire;
|
|
55
|
+
export declare function buildVersionDocumentSchema(schema: DocumentSchemaWire, targetCollectionSlug: string): DocumentSchemaWire;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export function buildDocumentSchema(config) {
|
|
2
|
+
const collections = {};
|
|
3
|
+
const blocksBySlug = new Map();
|
|
4
|
+
for (const block of config.blocks ?? []){
|
|
5
|
+
blocksBySlug.set(block.slug, block);
|
|
6
|
+
}
|
|
7
|
+
for (const collection of config.collections ?? []){
|
|
8
|
+
collections[collection.slug] = buildCollectionSchema(collection.slug, collection.fields, blocksBySlug);
|
|
9
|
+
}
|
|
10
|
+
for (const global of config.globals ?? []){
|
|
11
|
+
const slug = `_global-${global.slug}`;
|
|
12
|
+
collections[slug] = buildCollectionSchema(slug, global.fields, blocksBySlug);
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
collections,
|
|
16
|
+
version: 1
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function buildVersionDocumentSchema(schema, targetCollectionSlug) {
|
|
20
|
+
const target = schema.collections[targetCollectionSlug];
|
|
21
|
+
if (!target) {
|
|
22
|
+
return schema;
|
|
23
|
+
}
|
|
24
|
+
const prefixedPaths = {};
|
|
25
|
+
for (const [path, fieldSchema] of Object.entries(target.paths)){
|
|
26
|
+
prefixedPaths[`version.${path}`] = fieldSchema;
|
|
27
|
+
}
|
|
28
|
+
prefixedPaths['version._payloadAutosave'] = {
|
|
29
|
+
type: 'checkbox',
|
|
30
|
+
localized: false
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
...schema,
|
|
34
|
+
collections: {
|
|
35
|
+
...schema.collections,
|
|
36
|
+
[targetCollectionSlug]: {
|
|
37
|
+
...target,
|
|
38
|
+
paths: prefixedPaths
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function buildCollectionSchema(name, fields, blocksBySlug) {
|
|
44
|
+
const paths = {};
|
|
45
|
+
collectPaths(fields, '', false, paths, blocksBySlug);
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
hints: [],
|
|
49
|
+
paths
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function collectPaths(fields, prefix, absorbed, paths, blocksBySlug) {
|
|
53
|
+
for (const field of fields){
|
|
54
|
+
if (field.type === 'row' || field.type === 'collapsible' || field.type === 'group' && (!('name' in field) || !field.name)) {
|
|
55
|
+
collectPaths(field.fields, prefix, absorbed, paths, blocksBySlug);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (field.type === 'tabs') {
|
|
59
|
+
for (const tab of field.tabs){
|
|
60
|
+
const tabOwnLocalized = 'localized' in tab && tab.localized === true;
|
|
61
|
+
if ('name' in tab && tab.name) {
|
|
62
|
+
paths[`${prefix}${tab.name}`] = {
|
|
63
|
+
type: 'group',
|
|
64
|
+
hasMany: false,
|
|
65
|
+
localized: tabOwnLocalized && !absorbed
|
|
66
|
+
};
|
|
67
|
+
collectPaths(tab.fields, `${prefix}${tab.name}.`, absorbed || tabOwnLocalized, paths, blocksBySlug);
|
|
68
|
+
} else {
|
|
69
|
+
collectPaths(tab.fields, prefix, absorbed, paths, blocksBySlug);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (field.type === 'ui' || !('name' in field) || !field.name) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
collectNamedField(field, prefix, absorbed, paths, blocksBySlug);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
|
|
81
|
+
if (!('name' in field) || !field.name) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const path = `${prefix}${field.name}`;
|
|
85
|
+
const localized = 'localized' in field && field.localized === true && !absorbed;
|
|
86
|
+
const childAbsorbed = absorbed || 'localized' in field && field.localized === true;
|
|
87
|
+
const hasMany = 'hasMany' in field ? Boolean(field.hasMany) : false;
|
|
88
|
+
const unique = 'unique' in field ? Boolean(field.unique) : false;
|
|
89
|
+
switch(field.type){
|
|
90
|
+
case 'array':
|
|
91
|
+
paths[path] = {
|
|
92
|
+
type: 'array',
|
|
93
|
+
localized
|
|
94
|
+
};
|
|
95
|
+
collectPaths(field.fields, `${path}.`, childAbsorbed, paths, blocksBySlug);
|
|
96
|
+
return;
|
|
97
|
+
case 'blocks':
|
|
98
|
+
{
|
|
99
|
+
paths[path] = {
|
|
100
|
+
type: 'blocks',
|
|
101
|
+
localized
|
|
102
|
+
};
|
|
103
|
+
paths[`${path}.blockType`] = {
|
|
104
|
+
type: 'text',
|
|
105
|
+
hasMany: false,
|
|
106
|
+
localized: false,
|
|
107
|
+
unique: false
|
|
108
|
+
};
|
|
109
|
+
const blockFields = [];
|
|
110
|
+
for (const block of field.blocks){
|
|
111
|
+
const resolved = typeof block === 'string' ? blocksBySlug.get(block) : block;
|
|
112
|
+
if (resolved) {
|
|
113
|
+
blockFields.push(...resolved.fields);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
collectPaths(blockFields, `${path}.`, childAbsorbed, paths, blocksBySlug);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
case 'checkbox':
|
|
120
|
+
case 'date':
|
|
121
|
+
case 'json':
|
|
122
|
+
case 'radio':
|
|
123
|
+
paths[path] = {
|
|
124
|
+
type: field.type,
|
|
125
|
+
localized
|
|
126
|
+
};
|
|
127
|
+
return;
|
|
128
|
+
case 'code':
|
|
129
|
+
case 'email':
|
|
130
|
+
case 'point':
|
|
131
|
+
case 'textarea':
|
|
132
|
+
paths[path] = {
|
|
133
|
+
type: field.type,
|
|
134
|
+
localized,
|
|
135
|
+
unique
|
|
136
|
+
};
|
|
137
|
+
return;
|
|
138
|
+
case 'group':
|
|
139
|
+
paths[path] = {
|
|
140
|
+
type: 'group',
|
|
141
|
+
hasMany,
|
|
142
|
+
localized
|
|
143
|
+
};
|
|
144
|
+
collectPaths(field.fields, `${path}.`, childAbsorbed, paths, blocksBySlug);
|
|
145
|
+
return;
|
|
146
|
+
case 'join':
|
|
147
|
+
paths[path] = {
|
|
148
|
+
type: 'join',
|
|
149
|
+
collection: field.collection,
|
|
150
|
+
on: field.on
|
|
151
|
+
};
|
|
152
|
+
return;
|
|
153
|
+
case 'number':
|
|
154
|
+
case 'select':
|
|
155
|
+
case 'text':
|
|
156
|
+
paths[path] = {
|
|
157
|
+
type: field.type,
|
|
158
|
+
hasMany,
|
|
159
|
+
localized,
|
|
160
|
+
unique
|
|
161
|
+
};
|
|
162
|
+
return;
|
|
163
|
+
case 'relationship':
|
|
164
|
+
case 'upload':
|
|
165
|
+
paths[path] = {
|
|
166
|
+
type: field.type,
|
|
167
|
+
collection: field.relationTo,
|
|
168
|
+
hasMany,
|
|
169
|
+
localized,
|
|
170
|
+
unique
|
|
171
|
+
};
|
|
172
|
+
return;
|
|
173
|
+
case 'richText':
|
|
174
|
+
paths[path] = {
|
|
175
|
+
type: 'richText',
|
|
176
|
+
format: 'lexical',
|
|
177
|
+
localized
|
|
178
|
+
};
|
|
179
|
+
return;
|
|
180
|
+
default:
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
package/dist/oauth/defaults.js
CHANGED
|
@@ -138,6 +138,14 @@ export const defaultVerify = ({ collection, strategyName, userInfoCookieName = D
|
|
|
138
138
|
user.collection = collection.slug;
|
|
139
139
|
user._strategy = strategyName;
|
|
140
140
|
user.exp = token.exp;
|
|
141
|
+
// userGroups/makePermission are virtual (never persisted). Surface the live
|
|
142
|
+
// JWT claims on the authenticated user for this request so access control
|
|
143
|
+
// and self-reads see current values. Claims are snake_case on the wire; the
|
|
144
|
+
// collection fields are camelCase. The field afterRead hooks
|
|
145
|
+
// (see build-config.ts) read these back off req.user.
|
|
146
|
+
const userWithFigmaClaims = user;
|
|
147
|
+
userWithFigmaClaims.userGroups = Array.isArray(token.user_groups) ? token.user_groups : undefined;
|
|
148
|
+
userWithFigmaClaims.makePermission = token.make_permission === 'edit' || token.make_permission === 'view' ? token.make_permission : undefined;
|
|
141
149
|
return {
|
|
142
150
|
responseHeaders,
|
|
143
151
|
user
|
|
@@ -163,9 +163,8 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
|
|
|
163
163
|
strategy
|
|
164
164
|
});
|
|
165
165
|
debugLogger.info({
|
|
166
|
-
msg: '
|
|
167
|
-
path
|
|
168
|
-
tokenRes
|
|
166
|
+
msg: 'Received token response',
|
|
167
|
+
path
|
|
169
168
|
});
|
|
170
169
|
const { access_token, error, error_description, expires_in: refreshTokenExpires } = tokenRes;
|
|
171
170
|
if (error) {
|
|
@@ -180,8 +179,7 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
|
|
|
180
179
|
const errMsg = 'No access_token was returned from the identity provider.';
|
|
181
180
|
req.payload.logger.error({
|
|
182
181
|
msg: errMsg,
|
|
183
|
-
path
|
|
184
|
-
tokenRes
|
|
182
|
+
path
|
|
185
183
|
});
|
|
186
184
|
throw new APIError(errMsg);
|
|
187
185
|
}
|
|
@@ -206,8 +204,8 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
|
|
|
206
204
|
accessToken: access_token,
|
|
207
205
|
collection,
|
|
208
206
|
collectionOptions,
|
|
209
|
-
contentSystemId: config.custom.figma.contentSystemId,
|
|
210
207
|
debugLogger,
|
|
208
|
+
environmentId: config.custom.figma.environmentId,
|
|
211
209
|
pluginOptions,
|
|
212
210
|
refreshTokenExpiresIn: refreshTokenExpires,
|
|
213
211
|
req,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as jose from 'jose';
|
|
2
2
|
import { APIError, parseCookies } from 'payload';
|
|
3
|
-
import { JWTValidationError,
|
|
3
|
+
import { JWTValidationError, validatePayloadAdminToken } from '../../auth/jwt-validator.js';
|
|
4
4
|
import { getInfraEnvironment } from '../../constants.js';
|
|
5
5
|
import { createDebugLogger } from '../utilities/createDebugLogger.js';
|
|
6
6
|
import { getTokenExp } from '../utilities/getTokenExp.js';
|
|
@@ -45,16 +45,15 @@ export class Strategy {
|
|
|
45
45
|
let oauthToken = cookies.get(this.cookieName);
|
|
46
46
|
const refreshToken = cookies.get(`${this.cookieName}-refresh`);
|
|
47
47
|
debugLogger.info({
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
hasOauthToken: !!oauthToken,
|
|
49
|
+
hasRefreshToken: !!refreshToken,
|
|
50
|
+
msg: 'Retrieved tokens from cookies'
|
|
51
51
|
});
|
|
52
52
|
if (!this.collectionOptions.disableJWTFromHeader) {
|
|
53
53
|
// Parse Authorization header if exists
|
|
54
54
|
const jwtFromHeader = headers.get('authorization');
|
|
55
55
|
if (typeof jwtFromHeader === 'string' && jwtFromHeader.startsWith('Bearer ')) {
|
|
56
56
|
debugLogger.info({
|
|
57
|
-
jwtFromHeader,
|
|
58
57
|
msg: 'Found bearer token in authorization header'
|
|
59
58
|
});
|
|
60
59
|
oauthToken = jwtFromHeader.replace('Bearer ', '');
|
|
@@ -67,8 +66,7 @@ export class Strategy {
|
|
|
67
66
|
// full page load when canSetHeaders=true.
|
|
68
67
|
if (refreshToken) {
|
|
69
68
|
debugLogger.info({
|
|
70
|
-
msg: 'No access token found, but refresh token exists. Attempting to refresh...'
|
|
71
|
-
refreshToken
|
|
69
|
+
msg: 'No access token found, but refresh token exists. Attempting to refresh...'
|
|
72
70
|
});
|
|
73
71
|
const refreshed = await refreshTokens({
|
|
74
72
|
payload,
|
|
@@ -103,9 +101,7 @@ export class Strategy {
|
|
|
103
101
|
};
|
|
104
102
|
} else {
|
|
105
103
|
debugLogger.info({
|
|
106
|
-
msg: 'Token refresh failed'
|
|
107
|
-
refreshed,
|
|
108
|
-
refreshToken
|
|
104
|
+
msg: 'Token refresh failed'
|
|
109
105
|
});
|
|
110
106
|
}
|
|
111
107
|
}
|
|
@@ -156,9 +152,7 @@ export class Strategy {
|
|
|
156
152
|
strategy: this
|
|
157
153
|
});
|
|
158
154
|
debugLogger.info({
|
|
159
|
-
msg: 'Token expired. Refreshing...'
|
|
160
|
-
refreshed,
|
|
161
|
-
refreshToken
|
|
155
|
+
msg: 'Token expired. Refreshing...'
|
|
162
156
|
});
|
|
163
157
|
if (refreshed) {
|
|
164
158
|
const verifyResult = await this.verify({
|
|
@@ -185,9 +179,7 @@ export class Strategy {
|
|
|
185
179
|
};
|
|
186
180
|
} else {
|
|
187
181
|
debugLogger.info({
|
|
188
|
-
msg: 'Token refresh failed'
|
|
189
|
-
refreshed,
|
|
190
|
-
refreshToken
|
|
182
|
+
msg: 'Token refresh failed'
|
|
191
183
|
});
|
|
192
184
|
}
|
|
193
185
|
}
|
|
@@ -282,11 +274,11 @@ export class Strategy {
|
|
|
282
274
|
}
|
|
283
275
|
async jwtVerify({ payload, token }) {
|
|
284
276
|
try {
|
|
285
|
-
const audience = payload.config.custom.figma.
|
|
277
|
+
const audience = payload.config.custom.figma.environmentId;
|
|
286
278
|
if (!audience) {
|
|
287
|
-
throw new Error('Cannot validate token audience:
|
|
279
|
+
throw new Error('Cannot validate token audience: environmentId is not configured');
|
|
288
280
|
}
|
|
289
|
-
const verified = await
|
|
281
|
+
const verified = await validatePayloadAdminToken({
|
|
290
282
|
audience,
|
|
291
283
|
environment: getInfraEnvironment(),
|
|
292
284
|
token
|
|
@@ -5,8 +5,8 @@ interface Args {
|
|
|
5
5
|
accessToken: string;
|
|
6
6
|
collection: CollectionConfig;
|
|
7
7
|
collectionOptions: CollectionOptions;
|
|
8
|
-
contentSystemId: string;
|
|
9
8
|
debugLogger: DebugLogger;
|
|
9
|
+
environmentId: string;
|
|
10
10
|
pluginOptions: PluginOptions;
|
|
11
11
|
/** From the IdP token response (`expires_in`); used as a refresh-cookie expiry fallback. */
|
|
12
12
|
refreshTokenExpiresIn?: number;
|
|
@@ -15,8 +15,8 @@ interface Args {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Given a valid OAuth access_token: fetch user info (non-fatal), mint a
|
|
18
|
-
*
|
|
19
|
-
* `req.responseHeaders`. Throws `APIError` if the
|
|
18
|
+
* Payload Admin token, and write the main + refresh + user-info cookies to
|
|
19
|
+
* `req.responseHeaders`. Throws `APIError` if the Admin token mint fails.
|
|
20
20
|
*/
|
|
21
|
-
export declare const establishSession: ({ accessToken, collection, collectionOptions,
|
|
21
|
+
export declare const establishSession: ({ accessToken, collection, collectionOptions, debugLogger, environmentId, pluginOptions, refreshTokenExpiresIn, req, strategy, }: Args) => Promise<void>;
|
|
22
22
|
export {};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { APIError, generateCookie } from 'payload';
|
|
2
|
-
import {
|
|
2
|
+
import { getPayloadAdminToken, getUserInfo } from '../../api/figma-api.js';
|
|
3
3
|
import { createCookieOptions } from './createCookieOptions.js';
|
|
4
4
|
import { getCookieExpiration } from './getCookieExpiration.js';
|
|
5
5
|
import { withPartitionedCookie } from './withPartitionedCookie.js';
|
|
6
6
|
/**
|
|
7
7
|
* Given a valid OAuth access_token: fetch user info (non-fatal), mint a
|
|
8
|
-
*
|
|
9
|
-
* `req.responseHeaders`. Throws `APIError` if the
|
|
10
|
-
*/ export const establishSession = async ({ accessToken, collection, collectionOptions,
|
|
8
|
+
* Payload Admin token, and write the main + refresh + user-info cookies to
|
|
9
|
+
* `req.responseHeaders`. Throws `APIError` if the Admin token mint fails.
|
|
10
|
+
*/ export const establishSession = async ({ accessToken, collection, collectionOptions, debugLogger, environmentId, pluginOptions, refreshTokenExpiresIn, req, strategy })=>{
|
|
11
11
|
const oauthCredential = {
|
|
12
12
|
type: 'oauth',
|
|
13
13
|
token: accessToken
|
|
@@ -17,7 +17,6 @@ import { withPartitionedCookie } from './withPartitionedCookie.js';
|
|
|
17
17
|
try {
|
|
18
18
|
figmaUserInfo = await getUserInfo(oauthCredential);
|
|
19
19
|
debugLogger.info({
|
|
20
|
-
figmaUserInfo,
|
|
21
20
|
msg: 'Fetched Figma user info'
|
|
22
21
|
});
|
|
23
22
|
} catch (err) {
|
|
@@ -26,9 +25,9 @@ import { withPartitionedCookie } from './withPartitionedCookie.js';
|
|
|
26
25
|
msg: 'Failed to fetch Figma user info'
|
|
27
26
|
});
|
|
28
27
|
}
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
31
|
-
const errMsg = 'No
|
|
28
|
+
const adminToken = await getPayloadAdminToken(oauthCredential, environmentId);
|
|
29
|
+
if (!adminToken.token) {
|
|
30
|
+
const errMsg = 'No Payload Admin token returned from Figma API';
|
|
32
31
|
req.payload.logger.error({
|
|
33
32
|
msg: errMsg
|
|
34
33
|
});
|
|
@@ -54,13 +53,13 @@ import { withPartitionedCookie } from './withPartitionedCookie.js';
|
|
|
54
53
|
});
|
|
55
54
|
req.responseHeaders.append('Set-Cookie', withPartitionedCookie(refreshCookie));
|
|
56
55
|
}
|
|
57
|
-
const tokenExpires = cookieOptions?.expires ? getCookieExpiration(cookieOptions.expires) : new Date(
|
|
56
|
+
const tokenExpires = cookieOptions?.expires ? getCookieExpiration(cookieOptions.expires) : new Date(adminToken.expiresAt);
|
|
58
57
|
const tokenCookie = generateCookie({
|
|
59
58
|
...cookieOptions,
|
|
60
59
|
name: strategy.cookieName,
|
|
61
60
|
expires: tokenExpires,
|
|
62
61
|
returnCookieAsObject: false,
|
|
63
|
-
value:
|
|
62
|
+
value: adminToken.token
|
|
64
63
|
});
|
|
65
64
|
req.responseHeaders.append('Set-Cookie', withPartitionedCookie(tokenCookie));
|
|
66
65
|
// Short-lived handoff cookie consumed by defaultVerify on the next request.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateCookie } from 'payload';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { getPayloadAdminToken } from '../../api/figma-api.js';
|
|
3
|
+
import { validatePayloadAdminToken } from '../../auth/jwt-validator.js';
|
|
4
4
|
import { getInfraEnvironment } from '../../constants.js';
|
|
5
5
|
import { createCookieOptions } from './createCookieOptions.js';
|
|
6
6
|
import { createDebugLogger } from './createDebugLogger.js';
|
|
@@ -16,15 +16,18 @@ export const refreshTokens = async ({ payload, refreshToken, strategy })=>{
|
|
|
16
16
|
// but refresh token is still valid
|
|
17
17
|
// so we will refresh both tokens
|
|
18
18
|
try {
|
|
19
|
-
// Get another
|
|
20
|
-
const
|
|
19
|
+
// Get another Payload Admin token.
|
|
20
|
+
const environmentId = payload.config.custom.figma.environmentId;
|
|
21
|
+
if (!environmentId) {
|
|
22
|
+
throw new Error('Cannot refresh Payload Admin token: environmentId is not configured');
|
|
23
|
+
}
|
|
24
|
+
const { expiresAt, token } = await getPayloadAdminToken({
|
|
21
25
|
type: 'oauth',
|
|
22
26
|
token: refreshToken
|
|
23
|
-
},
|
|
27
|
+
}, environmentId);
|
|
24
28
|
debugLogger.info({
|
|
25
29
|
expiresAt,
|
|
26
|
-
msg: 'Received new
|
|
27
|
-
token
|
|
30
|
+
msg: 'Received new Payload Admin token from Figma API'
|
|
28
31
|
});
|
|
29
32
|
// On error, no user
|
|
30
33
|
if (!token) {
|
|
@@ -35,8 +38,7 @@ export const refreshTokens = async ({ payload, refreshToken, strategy })=>{
|
|
|
35
38
|
}
|
|
36
39
|
if (token) {
|
|
37
40
|
debugLogger.info({
|
|
38
|
-
msg: 'Successfully refreshed
|
|
39
|
-
token
|
|
41
|
+
msg: 'Successfully refreshed Payload Admin token'
|
|
40
42
|
});
|
|
41
43
|
const expires_in = new Date(expiresAt);
|
|
42
44
|
const cookieOptions = createCookieOptions({
|
|
@@ -52,12 +54,8 @@ export const refreshTokens = async ({ payload, refreshToken, strategy })=>{
|
|
|
52
54
|
value: token
|
|
53
55
|
});
|
|
54
56
|
headers.append('Set-Cookie', withPartitionedCookie(tokenCookie));
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
throw new Error('Cannot validate token audience: contentSystemId is not configured');
|
|
58
|
-
}
|
|
59
|
-
const decodedOauthToken = await validateProjectToken({
|
|
60
|
-
audience,
|
|
57
|
+
const decodedOauthToken = await validatePayloadAdminToken({
|
|
58
|
+
audience: environmentId,
|
|
61
59
|
environment: getInfraEnvironment(),
|
|
62
60
|
token
|
|
63
61
|
});
|