@payloadcms/figma 0.1.0-alpha.6 → 0.1.0-alpha.8
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/LICENSE.md +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/oauth/components/LoginButton/index.js +66 -19
- package/dist/oauth/components/LoginButton/index.scss +121 -15
- package/dist/plugin/build-config.d.ts +4 -2
- package/dist/plugin/build-config.js +34 -22
- package/dist/plugin/make-permission-access/make-permission-access.d.ts +8 -0
- package/dist/plugin/make-permission-access/make-permission-access.js +24 -0
- package/dist/storage-content-api/client-uploads/generate.js +2 -1
- package/dist/storage-content-api/index.js +1 -0
- package/dist/storage-content-api/staticHandler.js +21 -82
- package/dist/storage-content-api/types.d.ts +4 -1
- package/dist/utils/adapters/nextjs.js +5 -13
- package/dist/utils/adapters/nitro.js +39 -23
- package/dist/utils/adapters/vite.js +15 -1
- package/dist/utils/asset-collection.d.ts +7 -2
- package/dist/utils/asset-collection.js +44 -10
- package/dist/utils/build-lambda-zip.js +48 -16
- package/dist/utils/configureNextjsCache.d.ts +7 -0
- package/dist/utils/configureNextjsCache.js +55 -0
- package/dist/utils/fs-utils.d.ts +31 -4
- package/dist/utils/fs-utils.js +95 -15
- package/dist/utils/lambda-runtime/nextjs-cache.d.ts +22 -0
- package/dist/utils/lambda-runtime/nextjs-cache.js +162 -0
- package/package.json +12 -7
- package/dist/storage-content-api/buildBaseMediaResponseHeaders.d.ts +0 -1
- package/dist/storage-content-api/buildBaseMediaResponseHeaders.js +0 -6
- package/dist/storage-content-api/buildClientUploadFetchBackHeaders.d.ts +0 -10
- package/dist/storage-content-api/buildClientUploadFetchBackHeaders.js +0 -21
- package/dist/storage-content-api/buildMediaResponseHeaders.d.ts +0 -4
- package/dist/storage-content-api/buildMediaResponseHeaders.js +0 -45
- package/dist/storage-content-api/getInlineContentType.d.ts +0 -1
- package/dist/storage-content-api/getInlineContentType.js +0 -33
- package/dist/storage-content-api/hasSupportedContentEncoding.d.ts +0 -1
- package/dist/storage-content-api/hasSupportedContentEncoding.js +0 -4
- package/dist/storage-content-api/mediaErrorResponse.d.ts +0 -1
- package/dist/storage-content-api/mediaErrorResponse.js +0 -12
package/LICENSE.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export { figmaContentSystem } from './plugin/adapter.js';
|
|
|
4
4
|
export type { FigmaContentSystemOptions } from './plugin/adapter.js';
|
|
5
5
|
export { buildFigmaConfig } from './plugin/build-config.js';
|
|
6
6
|
export type { FigmaConfig } from './plugin/build-config.js';
|
|
7
|
+
export { passesMakeViewerWriteGate } from './plugin/make-permission-access/make-permission-access.js';
|
package/dist/index.js
CHANGED
|
@@ -4,3 +4,5 @@ export { contentAPIAdapter } from './db-content-api/index.js';
|
|
|
4
4
|
export { figmaContentSystem } from './plugin/adapter.js';
|
|
5
5
|
// Export buildConfig wrapper and its config type
|
|
6
6
|
export { buildFigmaConfig } from './plugin/build-config.js';
|
|
7
|
+
// Export the Make permission predicate for slug-specific baseAccess overrides.
|
|
8
|
+
export { passesMakeViewerWriteGate } from './plugin/make-permission-access/make-permission-access.js';
|
|
@@ -6,6 +6,17 @@ import { getSearchParam } from '../../utils/getSearchParam.js';
|
|
|
6
6
|
import { resolveParentTargetOrigin, setupIframeAutoLogin } from './iframeAutoLogin.js';
|
|
7
7
|
import './index.scss';
|
|
8
8
|
const baseClass = 'oauth-login';
|
|
9
|
+
const COLLECTION_SKELETON_KEYS = [
|
|
10
|
+
'collection-1',
|
|
11
|
+
'collection-2',
|
|
12
|
+
'collection-3',
|
|
13
|
+
'collection-4'
|
|
14
|
+
];
|
|
15
|
+
const GLOBAL_SKELETON_KEYS = [
|
|
16
|
+
'global-1',
|
|
17
|
+
'global-2',
|
|
18
|
+
'global-3'
|
|
19
|
+
];
|
|
9
20
|
// Max time we wait for the parent (figma.com) to respond with a code after
|
|
10
21
|
// posting `mint-cms-oauth-code`. If we hit this without a reply, we assume
|
|
11
22
|
// the parent isn't going to respond and fall back to the manual login button.
|
|
@@ -49,6 +60,57 @@ const FigmaIcon = ()=>/*#__PURE__*/ _jsx("svg", {
|
|
|
49
60
|
fillRule: "evenodd"
|
|
50
61
|
})
|
|
51
62
|
});
|
|
63
|
+
const PayloadAdminLoadingShimmer = ()=>/*#__PURE__*/ _jsx("div", {
|
|
64
|
+
"aria-label": "Signing in",
|
|
65
|
+
"aria-live": "polite",
|
|
66
|
+
className: `${baseClass}__admin-shimmer`,
|
|
67
|
+
role: "status",
|
|
68
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
69
|
+
"aria-hidden": "true",
|
|
70
|
+
className: `${baseClass}__admin-shimmer-main`,
|
|
71
|
+
children: [
|
|
72
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
73
|
+
className: `${baseClass}__shimmer-heading-row`,
|
|
74
|
+
children: [
|
|
75
|
+
/*#__PURE__*/ _jsx("div", {
|
|
76
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-heading-icon`
|
|
77
|
+
}),
|
|
78
|
+
/*#__PURE__*/ _jsx("div", {
|
|
79
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-heading-label`
|
|
80
|
+
})
|
|
81
|
+
]
|
|
82
|
+
}),
|
|
83
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
84
|
+
className: `${baseClass}__shimmer-section`,
|
|
85
|
+
children: [
|
|
86
|
+
/*#__PURE__*/ _jsx("div", {
|
|
87
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-section-label`
|
|
88
|
+
}),
|
|
89
|
+
/*#__PURE__*/ _jsx("div", {
|
|
90
|
+
className: `${baseClass}__shimmer-card-grid`,
|
|
91
|
+
children: COLLECTION_SKELETON_KEYS.map((key)=>/*#__PURE__*/ _jsx("div", {
|
|
92
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-card`
|
|
93
|
+
}, key))
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
}),
|
|
97
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
98
|
+
className: `${baseClass}__shimmer-section`,
|
|
99
|
+
children: [
|
|
100
|
+
/*#__PURE__*/ _jsx("div", {
|
|
101
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-section-label`
|
|
102
|
+
}),
|
|
103
|
+
/*#__PURE__*/ _jsx("div", {
|
|
104
|
+
className: `${baseClass}__shimmer-card-grid`,
|
|
105
|
+
children: GLOBAL_SKELETON_KEYS.map((key)=>/*#__PURE__*/ _jsx("div", {
|
|
106
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-card`
|
|
107
|
+
}, key))
|
|
108
|
+
})
|
|
109
|
+
]
|
|
110
|
+
})
|
|
111
|
+
]
|
|
112
|
+
})
|
|
113
|
+
});
|
|
52
114
|
export const DefaultLoginButton = ({ disabled, endpointSlug })=>{
|
|
53
115
|
const { config: { admin: { user: userSlug }, routes: { api }, serverURL } } = useConfig();
|
|
54
116
|
const [authorizeURL, setAuthorizeURL] = useState('');
|
|
@@ -150,26 +212,11 @@ export const DefaultLoginButton = ({ disabled, endpointSlug })=>{
|
|
|
150
212
|
endpointSlug
|
|
151
213
|
]);
|
|
152
214
|
if (!shouldRenderButton) {
|
|
153
|
-
// In the iframe auto-login window, show a
|
|
154
|
-
//
|
|
155
|
-
//
|
|
215
|
+
// In the iframe auto-login window, show a Payload admin-shaped shimmer
|
|
216
|
+
// until the flow either navigates them away or times out into the manual
|
|
217
|
+
// button.
|
|
156
218
|
if (isInIframe === true && !disabled) {
|
|
157
|
-
return /*#__PURE__*/ _jsx(
|
|
158
|
-
className: baseClass,
|
|
159
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
160
|
-
className: `${baseClass}__signing-in`,
|
|
161
|
-
role: "status",
|
|
162
|
-
children: [
|
|
163
|
-
/*#__PURE__*/ _jsx("span", {
|
|
164
|
-
children: "Signing in"
|
|
165
|
-
}),
|
|
166
|
-
/*#__PURE__*/ _jsx("div", {
|
|
167
|
-
"aria-hidden": "true",
|
|
168
|
-
className: `${baseClass}__spinner`
|
|
169
|
-
})
|
|
170
|
-
]
|
|
171
|
-
})
|
|
172
|
-
});
|
|
219
|
+
return /*#__PURE__*/ _jsx(PayloadAdminLoadingShimmer, {});
|
|
173
220
|
}
|
|
174
221
|
return null;
|
|
175
222
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
.oauth-login,
|
|
2
|
+
.oauth-login__admin-shimmer {
|
|
3
|
+
--oauth-login-shimmer-bg: var(--theme-bg, var(--theme-elevation-0, #ffffff));
|
|
4
|
+
--oauth-login-shimmer-block: var(--theme-elevation-100, #efefef);
|
|
5
|
+
--oauth-login-shimmer-highlight: var(--theme-elevation-200, #f7f7f7);
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
.oauth-login {
|
|
2
9
|
display: flex;
|
|
3
10
|
justify-content: center;
|
|
@@ -55,28 +62,127 @@
|
|
|
55
62
|
Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
&
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
&__admin-shimmer {
|
|
66
|
+
position: fixed;
|
|
67
|
+
inset: 0;
|
|
68
|
+
z-index: 1000;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
background: var(--oauth-login-shimmer-bg);
|
|
64
71
|
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
65
72
|
Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
&
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
&__admin-shimmer-main {
|
|
76
|
+
padding: 2rem 2.5rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&__shimmer-block {
|
|
80
|
+
position: relative;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
border-radius: 0.25rem;
|
|
83
|
+
background-color: var(--oauth-login-shimmer-block);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&__shimmer-block::after {
|
|
87
|
+
content: '';
|
|
88
|
+
position: absolute;
|
|
89
|
+
inset: 0;
|
|
90
|
+
border-radius: inherit;
|
|
91
|
+
background-image: linear-gradient(
|
|
92
|
+
100deg,
|
|
93
|
+
transparent 0%,
|
|
94
|
+
transparent 35%,
|
|
95
|
+
var(--oauth-login-shimmer-highlight) 50%,
|
|
96
|
+
transparent 65%,
|
|
97
|
+
transparent 100%
|
|
98
|
+
);
|
|
99
|
+
pointer-events: none;
|
|
100
|
+
transform: translateX(-100%);
|
|
101
|
+
animation: oauth-login-shimmer 1.45s linear infinite;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&__shimmer-heading-row {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 0.5rem;
|
|
108
|
+
height: 1.75rem;
|
|
109
|
+
margin-bottom: 1rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
&__shimmer-heading-icon {
|
|
113
|
+
width: 1.5rem;
|
|
114
|
+
height: 1.5rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&__shimmer-heading-label {
|
|
118
|
+
width: 7.5rem;
|
|
119
|
+
height: 1.25rem;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&__shimmer-section {
|
|
123
|
+
margin-bottom: 1.5rem;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
&__shimmer-section-label {
|
|
127
|
+
width: 7.5rem;
|
|
128
|
+
height: 1.25rem;
|
|
129
|
+
margin-bottom: 0.5rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
&__shimmer-card-grid {
|
|
133
|
+
display: grid;
|
|
134
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
135
|
+
gap: 1rem;
|
|
136
|
+
max-width: 51.625rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&__shimmer-card {
|
|
140
|
+
height: 4rem;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@media (prefers-color-scheme: dark) {
|
|
145
|
+
.oauth-login,
|
|
146
|
+
.oauth-login__admin-shimmer {
|
|
147
|
+
--oauth-login-shimmer-bg: var(--theme-bg, var(--theme-elevation-0, #2b2b2b));
|
|
148
|
+
--oauth-login-shimmer-block: var(--theme-elevation-100, #383838);
|
|
149
|
+
--oauth-login-shimmer-highlight: var(--theme-elevation-200, #444444);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@media (max-width: 48rem) {
|
|
154
|
+
.oauth-login {
|
|
155
|
+
&__shimmer-card-grid {
|
|
156
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
157
|
+
}
|
|
75
158
|
}
|
|
76
159
|
}
|
|
77
160
|
|
|
78
|
-
@
|
|
161
|
+
@media (max-width: 30rem) {
|
|
162
|
+
.oauth-login {
|
|
163
|
+
&__admin-shimmer-main {
|
|
164
|
+
padding-right: 0.75rem;
|
|
165
|
+
padding-left: 0.75rem;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&__shimmer-card-grid {
|
|
169
|
+
grid-template-columns: 1fr;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@media (prefers-reduced-motion: reduce) {
|
|
175
|
+
.oauth-login__shimmer-block::after {
|
|
176
|
+
animation: none;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@keyframes oauth-login-shimmer {
|
|
181
|
+
from {
|
|
182
|
+
transform: translateX(-100%);
|
|
183
|
+
}
|
|
184
|
+
|
|
79
185
|
to {
|
|
80
|
-
transform:
|
|
186
|
+
transform: translateX(100%);
|
|
81
187
|
}
|
|
82
188
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Config, SanitizedConfig } from 'payload';
|
|
1
|
+
import type { Config, SanitizedConfig, UploadInstructionsAccess } from 'payload';
|
|
2
2
|
/**
|
|
3
3
|
* Worker-trusted marker injected by Figma's sites-worker after it validates the
|
|
4
4
|
* X-Figma-Job-Queues-Auth-Bypass secret on a Payload jobs endpoint. The default
|
|
@@ -48,7 +48,9 @@ export type FigmaConfig = {
|
|
|
48
48
|
figma: {
|
|
49
49
|
contentSystemId?: string;
|
|
50
50
|
environmentId?: string;
|
|
51
|
-
storage?:
|
|
51
|
+
storage?: {
|
|
52
|
+
uploadInstructionsAccess?: UploadInstructionsAccess;
|
|
53
|
+
} | boolean;
|
|
52
54
|
useContentSystem?: boolean;
|
|
53
55
|
};
|
|
54
56
|
} & Omit<Config, 'db' | 'editor' | 'secret'> & Partial<Pick<Config, 'db'>>;
|
|
@@ -22,6 +22,7 @@ import { logMissingCliAuth } from './auth-preflight.js';
|
|
|
22
22
|
import { logMissingContentSystemId } from './bootstrap-preflight.js';
|
|
23
23
|
import { LIMIT_REACHED_VIEW_KEY, LIMIT_REACHED_VIEW_PATH } from './cloud-limits/routes.js';
|
|
24
24
|
import { getDevCookieNames } from './dev-cookie-names.js';
|
|
25
|
+
import { passesMakeViewerWriteGate, withMakeViewerWriteBaseAccessDefaults } from './make-permission-access/make-permission-access.js';
|
|
25
26
|
import { createSandboxUploadFetchPlugin } from './sandbox-upload-fetch.js';
|
|
26
27
|
/**
|
|
27
28
|
* Worker-trusted marker injected by Figma's sites-worker after it validates the
|
|
@@ -170,23 +171,26 @@ function messageOf(error) {
|
|
|
170
171
|
}
|
|
171
172
|
/**
|
|
172
173
|
* Creates storage configuration for upload collections
|
|
173
|
-
*/ function createStorageConfig(url, contentSystemId) {
|
|
174
|
-
const storageConfig =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
174
|
+
*/ function createStorageConfig(url, contentSystemId, uploadInstructionsAccess) {
|
|
175
|
+
const storageConfig = {
|
|
176
|
+
...process.env.FIGMA_CONTENT_API_ACCESS_KEY ? {
|
|
177
|
+
baseUrl: url,
|
|
178
|
+
contentApiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY
|
|
179
|
+
} : process.env.FIGMA_DEV_JWT === 'true' ? {
|
|
180
|
+
auth: {
|
|
181
|
+
mode: 'devJwt'
|
|
182
|
+
},
|
|
183
|
+
baseUrl: url,
|
|
184
|
+
contentSystemId
|
|
185
|
+
} : {
|
|
186
|
+
auth: {
|
|
187
|
+
mode: 'tokenStore',
|
|
188
|
+
tokenStore: getTokenStore()
|
|
189
|
+
},
|
|
190
|
+
baseUrl: url,
|
|
191
|
+
contentSystemId
|
|
187
192
|
},
|
|
188
|
-
|
|
189
|
-
contentSystemId
|
|
193
|
+
uploadInstructionsAccess
|
|
190
194
|
};
|
|
191
195
|
const adapter = contentApiStorageAdapter(storageConfig);
|
|
192
196
|
const storageClient = createStorageClient(storageConfig);
|
|
@@ -198,13 +202,13 @@ function messageOf(error) {
|
|
|
198
202
|
/**
|
|
199
203
|
* Plugin that applies cloud storage configuration to all upload-enabled collections.
|
|
200
204
|
* Runs at the end of the plugin chain so it sees collections added by user plugins too.
|
|
201
|
-
*/ function createStoragePlugin(url, contentSystemId) {
|
|
205
|
+
*/ function createStoragePlugin(url, contentSystemId, uploadInstructionsAccess) {
|
|
202
206
|
return (incomingConfig)=>{
|
|
203
207
|
const uploadCollections = (incomingConfig.collections || []).filter((c)=>c.upload);
|
|
204
208
|
if (uploadCollections.length === 0) {
|
|
205
209
|
return incomingConfig;
|
|
206
210
|
}
|
|
207
|
-
const { adapter } = createStorageConfig(url, contentSystemId);
|
|
211
|
+
const { adapter } = createStorageConfig(url, contentSystemId, uploadInstructionsAccess);
|
|
208
212
|
const collectionsMap = uploadCollections.reduce((acc, c)=>{
|
|
209
213
|
acc[c.slug] = {
|
|
210
214
|
adapter,
|
|
@@ -338,9 +342,15 @@ export async function buildFigmaConfig(config) {
|
|
|
338
342
|
]
|
|
339
343
|
}
|
|
340
344
|
},
|
|
345
|
+
// Payload applies this policy during sanitization, after user plugins and
|
|
346
|
+
// generated resources have registered their collections and globals.
|
|
347
|
+
baseAccess: withMakeViewerWriteBaseAccessDefaults(config.baseAccess),
|
|
341
348
|
custom: {
|
|
342
349
|
figma: {
|
|
343
350
|
...config.figma,
|
|
351
|
+
...typeof config.figma.storage === 'object' ? {
|
|
352
|
+
storage: true
|
|
353
|
+
} : {},
|
|
344
354
|
contentSystemId,
|
|
345
355
|
environmentId
|
|
346
356
|
}
|
|
@@ -358,8 +368,8 @@ export async function buildFigmaConfig(config) {
|
|
|
358
368
|
},
|
|
359
369
|
// Gate /api/payload-jobs/run and /api/payload-jobs/handle-schedules. Honor the
|
|
360
370
|
// sites-worker trust marker so Figma infra job runs never break, defer to the
|
|
361
|
-
// tenant's jobs.access.run if they set one, otherwise
|
|
362
|
-
//
|
|
371
|
+
// tenant's jobs.access.run if they set one, otherwise require an authenticated
|
|
372
|
+
// user who is not a known Make viewer.
|
|
363
373
|
jobs: {
|
|
364
374
|
...config.jobs ?? {},
|
|
365
375
|
access: {
|
|
@@ -371,7 +381,9 @@ export async function buildFigmaConfig(config) {
|
|
|
371
381
|
if (userJobsAccessRun) {
|
|
372
382
|
return userJobsAccessRun(args);
|
|
373
383
|
}
|
|
374
|
-
|
|
384
|
+
// Allow undefined Make permissions during rollout, but fail closed for empty,
|
|
385
|
+
// explicit view, and other unrecognized values.
|
|
386
|
+
return Boolean(args.req.user) && passesMakeViewerWriteGate(args.req.user);
|
|
375
387
|
}
|
|
376
388
|
}
|
|
377
389
|
},
|
|
@@ -385,7 +397,7 @@ export async function buildFigmaConfig(config) {
|
|
|
385
397
|
plugins: [
|
|
386
398
|
...config.plugins ?? [],
|
|
387
399
|
...config.figma.storage !== false ? [
|
|
388
|
-
createStoragePlugin(url, contentSystemId)
|
|
400
|
+
createStoragePlugin(url, contentSystemId, typeof config.figma.storage === 'object' ? config.figma.storage.uploadInstructionsAccess : undefined)
|
|
389
401
|
] : [],
|
|
390
402
|
oAuth2Plugin({
|
|
391
403
|
collections: [
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Access, BaseAccess } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* we will still let a user edit if makePermission is null or undefined, because we are
|
|
4
|
+
* specifically restricting users for a figma cloud project backed by make
|
|
5
|
+
*/
|
|
6
|
+
export declare function passesMakeViewerWriteGate(user: unknown): boolean;
|
|
7
|
+
export declare const makeViewerWriteBaseAccess: Access;
|
|
8
|
+
export declare function withMakeViewerWriteBaseAccessDefaults(existingBaseAccess?: BaseAccess): BaseAccess;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* we will still let a user edit if makePermission is null or undefined, because we are
|
|
3
|
+
* specifically restricting users for a figma cloud project backed by make
|
|
4
|
+
*/ export function passesMakeViewerWriteGate(user) {
|
|
5
|
+
const makePermission = user?.makePermission;
|
|
6
|
+
return makePermission === undefined || makePermission === null || makePermission === 'edit';
|
|
7
|
+
}
|
|
8
|
+
export const makeViewerWriteBaseAccess = ({ req })=>passesMakeViewerWriteGate(req.user);
|
|
9
|
+
export function withMakeViewerWriteBaseAccessDefaults(existingBaseAccess = {}) {
|
|
10
|
+
return {
|
|
11
|
+
...existingBaseAccess,
|
|
12
|
+
collections: {
|
|
13
|
+
...existingBaseAccess.collections,
|
|
14
|
+
create: existingBaseAccess.collections?.create ?? makeViewerWriteBaseAccess,
|
|
15
|
+
delete: existingBaseAccess.collections?.delete ?? makeViewerWriteBaseAccess,
|
|
16
|
+
unlock: existingBaseAccess.collections?.unlock ?? makeViewerWriteBaseAccess,
|
|
17
|
+
update: existingBaseAccess.collections?.update ?? makeViewerWriteBaseAccess
|
|
18
|
+
},
|
|
19
|
+
globals: {
|
|
20
|
+
...existingBaseAccess.globals,
|
|
21
|
+
update: existingBaseAccess.globals?.update ?? makeViewerWriteBaseAccess
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { APIError, Forbidden } from 'payload';
|
|
3
|
+
import { passesMakeViewerWriteGate } from '../../plugin/make-permission-access/make-permission-access.js';
|
|
3
4
|
import { getSafeFilename } from '../utilities/index.js';
|
|
4
|
-
const defaultAccess = ({ req })
|
|
5
|
+
const defaultAccess = ({ req })=>Boolean(req.user) && passesMakeViewerWriteGate(req.user);
|
|
5
6
|
/**
|
|
6
7
|
* v4 client-upload entry point. Payload core calls this from
|
|
7
8
|
* `POST /api/upload-instructions`; we sign a Content API upload URL and hand the
|
|
@@ -1,29 +1,7 @@
|
|
|
1
1
|
import { getFilePrefix } from '@payloadcms/plugin-cloud-storage/utilities';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { isImage } from 'payload/shared';
|
|
4
|
-
import { buildClientUploadFetchBackHeaders } from './buildClientUploadFetchBackHeaders.js';
|
|
5
|
-
import { buildMediaResponseHeaders } from './buildMediaResponseHeaders.js';
|
|
6
|
-
import { hasSupportedContentEncoding } from './hasSupportedContentEncoding.js';
|
|
7
|
-
import { mediaErrorResponse } from './mediaErrorResponse.js';
|
|
8
4
|
import { parseUploadReference } from './utilities/index.js';
|
|
9
|
-
const isRunningInLambda = ()=>process.env.AWS_EXECUTION_ENV?.startsWith('AWS_Lambda_') === true;
|
|
10
|
-
const shouldReturnRedirect = ({ uploadReference })=>{
|
|
11
|
-
if (!isRunningInLambda()) {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
if (!uploadReference) {
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
const { mimeType } = parseUploadReference(uploadReference);
|
|
18
|
-
return Boolean(mimeType && !isImage(mimeType));
|
|
19
|
-
};
|
|
20
|
-
const cancelResponseBody = async (response)=>{
|
|
21
|
-
try {
|
|
22
|
-
await response.body?.cancel();
|
|
23
|
-
} catch {
|
|
24
|
-
// The response will not be consumed after an error; cancellation is best-effort cleanup.
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
5
|
export const getHandler = ({ client, collection })=>{
|
|
28
6
|
return async (req, { params: { filename, uploadReference } })=>{
|
|
29
7
|
try {
|
|
@@ -47,68 +25,29 @@ export const getHandler = ({ client, collection })=>{
|
|
|
47
25
|
status: 500
|
|
48
26
|
});
|
|
49
27
|
}
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
28
|
+
// On a client-upload fetch-back, core reads this response body into req.file.data to
|
|
29
|
+
// size the image; a 302's empty body crashes getImageSize. Return real bytes for
|
|
30
|
+
// images (and unknown mimetypes, to be safe), but keep the redirect otherwise so we
|
|
31
|
+
// don't download large non-images server-side.
|
|
32
|
+
if (uploadReference) {
|
|
33
|
+
const { mimeType } = parseUploadReference(uploadReference);
|
|
34
|
+
if (!mimeType || isImage(mimeType)) {
|
|
35
|
+
const fileResponse = await fetch(data.url);
|
|
36
|
+
if (!fileResponse.ok) {
|
|
37
|
+
req.payload.logger.error(`Failed to fetch file for client-upload buffer fetch-back: ${fileResponse.status}`);
|
|
38
|
+
return new Response('Internal Server Error', {
|
|
39
|
+
status: 500
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const contentType = fileResponse.headers.get('Content-Type') ?? mimeType ?? 'application/octet-stream';
|
|
43
|
+
return new Response(await fileResponse.arrayBuffer(), {
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': contentType
|
|
46
|
+
}
|
|
47
|
+
});
|
|
69
48
|
}
|
|
70
49
|
}
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
fileResponse = await fetch(data.url, {
|
|
74
|
-
credentials: 'omit',
|
|
75
|
-
headers: requestHeaders,
|
|
76
|
-
redirect: 'manual',
|
|
77
|
-
referrerPolicy: 'no-referrer',
|
|
78
|
-
signal: req.signal
|
|
79
|
-
});
|
|
80
|
-
} catch {
|
|
81
|
-
req.payload.logger.error('Failed to fetch file from storage');
|
|
82
|
-
return mediaErrorResponse(500, 'Internal Server Error');
|
|
83
|
-
}
|
|
84
|
-
if (fileResponse.status === 416 && range) {
|
|
85
|
-
await cancelResponseBody(fileResponse);
|
|
86
|
-
return mediaErrorResponse(416, 'Range Not Satisfiable', fileResponse.headers.get('Content-Range') ?? undefined);
|
|
87
|
-
}
|
|
88
|
-
if (!fileResponse.ok) {
|
|
89
|
-
await cancelResponseBody(fileResponse);
|
|
90
|
-
req.payload.logger.error(`Failed to fetch file from storage: ${fileResponse.status}`);
|
|
91
|
-
return mediaErrorResponse(500, 'Internal Server Error');
|
|
92
|
-
}
|
|
93
|
-
if (!hasSupportedContentEncoding(fileResponse.headers)) {
|
|
94
|
-
await cancelResponseBody(fileResponse);
|
|
95
|
-
req.payload.logger.error('Unsupported Content-Encoding from storage');
|
|
96
|
-
return mediaErrorResponse(502, 'Bad Gateway');
|
|
97
|
-
}
|
|
98
|
-
const clientUploadMimeType = uploadReference ? parseUploadReference(uploadReference).mimeType : undefined;
|
|
99
|
-
const responseHeaders = uploadReference ? buildClientUploadFetchBackHeaders({
|
|
100
|
-
fallbackContentType: clientUploadMimeType,
|
|
101
|
-
filename,
|
|
102
|
-
upstream: fileResponse.headers
|
|
103
|
-
}) : buildMediaResponseHeaders({
|
|
104
|
-
filename,
|
|
105
|
-
upstream: fileResponse.headers
|
|
106
|
-
});
|
|
107
|
-
return new Response(fileResponse.body, {
|
|
108
|
-
headers: responseHeaders,
|
|
109
|
-
status: fileResponse.status,
|
|
110
|
-
statusText: fileResponse.statusText
|
|
111
|
-
});
|
|
50
|
+
return Response.redirect(data.url, 302);
|
|
112
51
|
} catch (err) {
|
|
113
52
|
req.payload.logger.error({
|
|
114
53
|
err,
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import type { UploadInstructionsAccess } from 'payload';
|
|
1
2
|
import type { AuthMode } from '../db-content-api/utilities/auth.js';
|
|
2
3
|
export type StorageClientConfig = {
|
|
4
|
+
uploadInstructionsAccess?: UploadInstructionsAccess;
|
|
5
|
+
} & ({
|
|
3
6
|
auth: AuthMode;
|
|
4
7
|
baseUrl: string;
|
|
5
8
|
contentSystemId: string;
|
|
6
9
|
} | {
|
|
7
10
|
baseUrl: string;
|
|
8
11
|
contentApiKey: string;
|
|
9
|
-
};
|
|
12
|
+
});
|
|
@@ -39,21 +39,13 @@ export class NextjsAdapter {
|
|
|
39
39
|
}
|
|
40
40
|
async collectPages(projectPath, output) {
|
|
41
41
|
const ssg = await collectSSGAssets(projectPath, output);
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Strip extension to get the base, then derive the route
|
|
46
|
-
const base = key.replace(/\.(?:html|rsc|meta)$/, '');
|
|
47
|
-
const route = base === 'index' ? '/' : `/${base}`;
|
|
48
|
-
if (!routeAssetMap[route]) {
|
|
49
|
-
routeAssetMap[route] = [];
|
|
50
|
-
}
|
|
51
|
-
routeAssetMap[route].push(key);
|
|
52
|
-
}
|
|
42
|
+
// collectSSGAssets records each key's manifest route while scanning the
|
|
43
|
+
// output, preserving the existing mapping for HTML/RSC/meta files while
|
|
44
|
+
// supporting segment keys whose nested names do not encode the page route.
|
|
53
45
|
return {
|
|
54
46
|
pathMap: ssg.pathMap,
|
|
55
|
-
routeAssetMap,
|
|
56
|
-
routes: Object.keys(routeAssetMap),
|
|
47
|
+
routeAssetMap: ssg.routeAssetMap,
|
|
48
|
+
routes: Object.keys(ssg.routeAssetMap),
|
|
57
49
|
uploadKeys: ssg.keys
|
|
58
50
|
};
|
|
59
51
|
}
|