@humaan/payload-storage-imagekit 2.6.2 → 2.7.0

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/README.md CHANGED
@@ -21,6 +21,9 @@ This plugin integrates `@payloadcms/plugin-cloud-storage` with ImageKit and prov
21
21
  pnpm add @humaan/payload-storage-imagekit
22
22
  ```
23
23
 
24
+ Payload `3.90.1` or newer is required. Use matching versions of `payload` and
25
+ `@payloadcms/plugin-cloud-storage`.
26
+
24
27
  ## Usage
25
28
 
26
29
  ```ts
@@ -59,13 +62,13 @@ export default buildConfig({
59
62
  - `privateKey` (required): ImageKit private API key
60
63
  - `publicKey` (required): ImageKit public key
61
64
  - `urlEndpoint` (required): ImageKit URL endpoint, e.g. `https://ik.imagekit.io/your_id`
62
- - `clientUploads` (optional): when `true`, Payload admin uploads go directly from the browser to ImageKit before Payload fetches the file back for persistence and image-size generation. This helps avoid server request body limits such as Vercel's.
63
- Duplicate filenames still follow Payload's normal safe-name behavior, so a second `pug.jpg` becomes `pug-1.jpg` instead of overwriting the first asset in ImageKit.
65
+ - `clientUploads` (optional): when `true` (or `{ access }`), Payload admin uploads go directly from the browser to ImageKit Upload API V2. Payload checks collection write access before authorization, signs every provider upload option, assigns an unpredictable per-upload object key, and verifies ImageKit metadata before persistence. This helps avoid server request body limits such as Vercel's. No additional ImageKit credentials or environment variables are required.
66
+ Duplicate and concurrent filenames receive distinct object paths and cannot overwrite each other.
64
67
  - `signedUrlExpiresIn` (optional): expiry in seconds for the signed URLs generated for [private](#private-files) collections. Defaults to `900` (15 minutes). Signed URLs are generated fresh on every request, so a short window is safe.
65
68
  - `enabled` (optional): enable / disable plugin
66
69
  - `focalPoint` (optional): broad default for Payload's native focal-point picker (`upload.focalPoint`) on every configured collection. Focal points are disabled by default for configured collections, so prefer the per-collection `collections.<slug>.focalPoint` option for mixed upload collections, e.g. enable it for `media` and leave it off for `documents`. When `true`, editors can place a focal point and `focalX`/`focalY` (0–100) are stored on each doc for focal-aware cropping at render time. A collection's own `upload.focalPoint` still takes precedence.
67
70
  - `alwaysInsertFields` (optional): pass-through to cloud storage plugin
68
- - `disablePayloadAccessControl` (optional): pass-through to cloud storage plugin
71
+ - `disablePayloadAccessControl` (optional): pass-through to cloud storage plugin. It cannot be enabled for private collections or collections that can store SVG/XML, because those files require Payload's file route for signing or Content Security Policy headers. To use this option, every configured collection must have a MIME allow list that excludes XML-family types.
69
72
 
70
73
  ## Exports
71
74
 
@@ -158,6 +161,83 @@ How it works:
158
161
  - **Signed URLs:** because the stored `imagekit.url` is the unsigned URL (which 401s for private files), private documents also expose a virtual `imagekit.signedUrl` field — a freshly signed, time-limited URL computed on each read (never persisted). Use it through Payload's API to reach the file directly on ImageKit. The access-control-gated alternative is the document's top-level `url` (the Payload file route).
159
162
  - **Access control is required.** Because signing happens at request time behind Payload access control, `disablePayloadAccessControl` cannot be combined with private collections — doing so throws at startup. Set per-collection `access.read` rules to control who can read each file.
160
163
 
164
+ ## Payload 3.90 upload security
165
+
166
+ Versions through `2.6.2` do not implement Payload 3.90's hardened client-upload
167
+ contract. Do not use those releases with `clientUploads: true` on Payload 3.90.
168
+ Upgrade to this release, or disable `clientUploads`. Server-side ImageKit uploads
169
+ were not affected by this adapter issue.
170
+
171
+ Client uploads now use ImageKit Upload API V2 and Payload's signed upload receipts.
172
+ Payload chooses the sanitized filename and adds numeric suffixes for duplicates.
173
+ For PNG, JPEG, GIF, and WebP images, the browser handler checks the file signature
174
+ before authorization and corrects a mismatched MIME label or extension. ImageKit
175
+ still verifies the actual content against the signed MIME and size checks.
176
+ Uploads go directly into the collection folder. Authorization binds the collection,
177
+ user, path, filename, MIME type, size, privacy mode, expiry, a per-upload ImageKit tag,
178
+ and create-only upload options. ImageKit metadata is checked on every completed upload,
179
+ including when Payload does not need to fetch file bytes.
180
+
181
+ After ImageKit returns a file ID, the browser sends it and the original receipt back
182
+ to the authorization endpoint with `action: 'complete'`. The server checks the file
183
+ directly by ID against the signed authorization and issues a receipt containing that
184
+ ID. Document saves use this receipt without waiting for ImageKit's asset listings.
185
+ The tag links the initial authorization to the new file; completed receipts use the
186
+ verified file ID. A unique `imagekit.fileId` index prevents receipt reuse.
187
+ ImageKit filename generation stays disabled. If an ImageKit-only file or a concurrent
188
+ upload occupies the selected path, the browser requests a fresh signed authorization
189
+ with the next numeric suffix and retries. Payload checks each new name against its
190
+ database. Up to 10 upload attempts are made; existing ImageKit files are never overwritten.
191
+
192
+ Keep `upload.allowRestrictedFileTypes` unset or `false` for normal Media and Images
193
+ collections, including collections that accept safe SVG files. Payload 3.90's admin
194
+ recognizes SVG and XML-family files and automatically sends them through the normal
195
+ server-side multipart flow instead of the ImageKit client-upload handler. Payload
196
+ then inspects the bytes, accepts safe SVG according to the collection's MIME allow
197
+ list, and rejects unsafe SVG/XML. Custom API clients must follow the same rule and
198
+ send restricted files through Payload rather than requesting direct-upload
199
+ authorization.
200
+
201
+ Setting `upload.allowRestrictedFileTypes: true` is an explicit validation opt-out,
202
+ not the standard way to enable SVG. It permits direct client upload after ImageKit
203
+ MIME, size, path, and privacy verification, but Payload does not sanitize or inspect
204
+ the complete SVG/XML contents. Use it only for files that are already sanitized or
205
+ otherwise fully trusted. Explicitly allowed XML-family files are streamed through
206
+ Payload's file route with Payload's Content Security Policy rather than redirected
207
+ directly to ImageKit. Do not render their stored `imagekit.url` directly from a
208
+ same-origin ImageKit delivery domain.
209
+
210
+ Because that protection requires Payload's file route, `disablePayloadAccessControl`
211
+ cannot be used for a collection that can store SVG/XML. Configure a MIME allow list
212
+ that excludes XML-family types if direct public ImageKit URLs are required.
213
+
214
+ Payload 3.90 adds `_objectKey` to cloud-storage upload collections, and this adapter
215
+ adds a unique index on `imagekit.fileId`. Regenerate Payload types after upgrading.
216
+ SQL users must also create and run a migration:
217
+
218
+ ```bash
219
+ pnpm payload generate:types
220
+ pnpm payload migrate:create imagekit-file-id-receipts
221
+ pnpm payload migrate
222
+ ```
223
+
224
+ Existing files and documents with or without `_objectKey` remain supported in place;
225
+ no ImageKit relocation is required. New client uploads do not set `_objectKey`.
226
+
227
+ Payload 3.90 also caps server-side multipart requests at 50 MiB by default. This
228
+ does not apply to browser-to-ImageKit client uploads, but it does apply when Payload
229
+ routes SVG/XML through server validation. If server-side uploads larger than 50 MiB
230
+ are required, raise Payload's complete-request limit, for example:
231
+
232
+ ```ts
233
+ upload: {
234
+ requestSizeLimit: 75 * 1024 * 1024,
235
+ }
236
+ ```
237
+
238
+ The adapter streams Payload temporary files to ImageKit for server-side uploads,
239
+ rather than loading those files into a second buffer.
240
+
161
241
  ## Folder Migration
162
242
 
163
243
  Use `migrateFolder` to copy uploads from one ImageKit root folder to another (e.g. staging to production).
@@ -218,8 +298,9 @@ DRY_RUN=false pnpx tsx --env-file=../.env imagekit-migration.ts
218
298
 
219
299
  ## Notes
220
300
 
221
- - Files are uploaded under: `<folder>/<collectionSlug>/<filename>`.
222
- - With `clientUploads: true`, the browser uploads the original file directly to ImageKit, then Payload continues its normal document save flow using the uploaded asset.
301
+ - New client and server-side files use: `<folder>/<collectionSlug>/<filename>`.
302
+ - Existing client uploads with `_objectKey` keep their original per-upload folders.
303
+ - With `clientUploads: true`, the browser uploads the original file directly to ImageKit, then Payload verifies the provider object and continues its normal document save flow.
223
304
  - Deletion prefers stored `imagekit.fileId`, with path lookup fallback.
224
305
  - Admin thumbnail behavior:
225
306
  - Images: `<imagekit.url>?tr=w-300,h-300`
@@ -233,3 +314,12 @@ pnpm dev
233
314
  pnpm test:int
234
315
  pnpm build
235
316
  ```
317
+
318
+ The optional credential-backed ImageKit V2 tests use separate environment variables
319
+ so they cannot run accidentally with normal development credentials:
320
+
321
+ ```bash
322
+ IMAGEKIT_INTEGRATION_PRIVATE_KEY=... \
323
+ IMAGEKIT_INTEGRATION_PUBLIC_KEY=... \
324
+ pnpm test:imagekit
325
+ ```
@@ -0,0 +1,5 @@
1
+ /** A browser-side hint only: ImageKit must still verify the actual uploaded content. */
2
+ export declare const getClientImageIdentity: (file: File) => Promise<{
3
+ filename: string;
4
+ mimeType: string;
5
+ }>;
@@ -0,0 +1,86 @@
1
+ /** A browser-side hint only: ImageKit must still verify the actual uploaded content. */ export const getClientImageIdentity = async (file)=>{
2
+ const original = {
3
+ filename: file.name,
4
+ mimeType: file.type
5
+ };
6
+ if (![
7
+ 'image/gif',
8
+ 'image/jpeg',
9
+ 'image/png',
10
+ 'image/webp'
11
+ ].includes(file.type)) {
12
+ return original;
13
+ }
14
+ const header = new Uint8Array(await file.slice(0, 12).arrayBuffer());
15
+ const matches = (bytes, offset = 0)=>bytes.every((byte, index)=>header[offset + index] === byte);
16
+ let detected;
17
+ if (matches([
18
+ 0x89,
19
+ 0x50,
20
+ 0x4e,
21
+ 0x47,
22
+ 0x0d,
23
+ 0x0a,
24
+ 0x1a,
25
+ 0x0a
26
+ ])) {
27
+ detected = {
28
+ extension: 'png',
29
+ mimeType: 'image/png'
30
+ };
31
+ } else if (matches([
32
+ 0xff,
33
+ 0xd8,
34
+ 0xff
35
+ ])) {
36
+ detected = {
37
+ extension: 'jpg',
38
+ mimeType: 'image/jpeg'
39
+ };
40
+ } else if (matches([
41
+ 0x47,
42
+ 0x49,
43
+ 0x46,
44
+ 0x38,
45
+ 0x37,
46
+ 0x61
47
+ ]) || matches([
48
+ 0x47,
49
+ 0x49,
50
+ 0x46,
51
+ 0x38,
52
+ 0x39,
53
+ 0x61
54
+ ])) {
55
+ detected = {
56
+ extension: 'gif',
57
+ mimeType: 'image/gif'
58
+ };
59
+ } else if (matches([
60
+ 0x52,
61
+ 0x49,
62
+ 0x46,
63
+ 0x46
64
+ ]) && matches([
65
+ 0x57,
66
+ 0x45,
67
+ 0x42,
68
+ 0x50
69
+ ], 8)) {
70
+ detected = {
71
+ extension: 'webp',
72
+ mimeType: 'image/webp'
73
+ };
74
+ }
75
+ if (!detected) {
76
+ return original;
77
+ }
78
+ const extension = file.name.split('.').pop()?.toLowerCase();
79
+ const matchingExtension = extension === detected.extension || detected.mimeType === 'image/jpeg' && extension === 'jpeg';
80
+ return {
81
+ filename: matchingExtension ? file.name : `${file.name.replace(/\.[^./\\]+$/, '')}.${detected.extension}`,
82
+ mimeType: detected.mimeType
83
+ };
84
+ };
85
+
86
+ //# sourceMappingURL=clientImageIdentity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/clientImageIdentity.ts"],"sourcesContent":["type ImageIdentity = { extension: string; mimeType: string }\n\n/** A browser-side hint only: ImageKit must still verify the actual uploaded content. */\nexport const getClientImageIdentity = async (file: File): Promise<{\n filename: string\n mimeType: string\n}> => {\n const original = { filename: file.name, mimeType: file.type }\n if (!['image/gif', 'image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {\n return original\n }\n\n const header = new Uint8Array(await file.slice(0, 12).arrayBuffer())\n const matches = (bytes: number[], offset = 0) =>\n bytes.every((byte, index) => header[offset + index] === byte)\n let detected: ImageIdentity | undefined\n if (matches([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {\n detected = { extension: 'png', mimeType: 'image/png' }\n } else if (matches([0xff, 0xd8, 0xff])) {\n detected = { extension: 'jpg', mimeType: 'image/jpeg' }\n } else if (\n matches([0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||\n matches([0x47, 0x49, 0x46, 0x38, 0x39, 0x61])\n ) {\n detected = { extension: 'gif', mimeType: 'image/gif' }\n } else if (matches([0x52, 0x49, 0x46, 0x46]) && matches([0x57, 0x45, 0x42, 0x50], 8)) {\n detected = { extension: 'webp', mimeType: 'image/webp' }\n }\n\n if (!detected) {\n return original\n }\n\n const extension = file.name.split('.').pop()?.toLowerCase()\n const matchingExtension =\n extension === detected.extension ||\n (detected.mimeType === 'image/jpeg' && extension === 'jpeg')\n return {\n filename: matchingExtension\n ? file.name\n : `${file.name.replace(/\\.[^./\\\\]+$/, '')}.${detected.extension}`,\n mimeType: detected.mimeType,\n }\n}\n"],"names":["getClientImageIdentity","file","original","filename","name","mimeType","type","includes","header","Uint8Array","slice","arrayBuffer","matches","bytes","offset","every","byte","index","detected","extension","split","pop","toLowerCase","matchingExtension","replace"],"mappings":"AAEA,sFAAsF,GACtF,OAAO,MAAMA,yBAAyB,OAAOC;IAI3C,MAAMC,WAAW;QAAEC,UAAUF,KAAKG,IAAI;QAAEC,UAAUJ,KAAKK,IAAI;IAAC;IAC5D,IAAI,CAAC;QAAC;QAAa;QAAc;QAAa;KAAa,CAACC,QAAQ,CAACN,KAAKK,IAAI,GAAG;QAC/E,OAAOJ;IACT;IAEA,MAAMM,SAAS,IAAIC,WAAW,MAAMR,KAAKS,KAAK,CAAC,GAAG,IAAIC,WAAW;IACjE,MAAMC,UAAU,CAACC,OAAiBC,SAAS,CAAC,GAC1CD,MAAME,KAAK,CAAC,CAACC,MAAMC,QAAUT,MAAM,CAACM,SAASG,MAAM,KAAKD;IAC1D,IAAIE;IACJ,IAAIN,QAAQ;QAAC;QAAM;QAAM;QAAM;QAAM;QAAM;QAAM;QAAM;KAAK,GAAG;QAC7DM,WAAW;YAAEC,WAAW;YAAOd,UAAU;QAAY;IACvD,OAAO,IAAIO,QAAQ;QAAC;QAAM;QAAM;KAAK,GAAG;QACtCM,WAAW;YAAEC,WAAW;YAAOd,UAAU;QAAa;IACxD,OAAO,IACLO,QAAQ;QAAC;QAAM;QAAM;QAAM;QAAM;QAAM;KAAK,KAC5CA,QAAQ;QAAC;QAAM;QAAM;QAAM;QAAM;QAAM;KAAK,GAC5C;QACAM,WAAW;YAAEC,WAAW;YAAOd,UAAU;QAAY;IACvD,OAAO,IAAIO,QAAQ;QAAC;QAAM;QAAM;QAAM;KAAK,KAAKA,QAAQ;QAAC;QAAM;QAAM;QAAM;KAAK,EAAE,IAAI;QACpFM,WAAW;YAAEC,WAAW;YAAQd,UAAU;QAAa;IACzD;IAEA,IAAI,CAACa,UAAU;QACb,OAAOhB;IACT;IAEA,MAAMiB,YAAYlB,KAAKG,IAAI,CAACgB,KAAK,CAAC,KAAKC,GAAG,IAAIC;IAC9C,MAAMC,oBACJJ,cAAcD,SAASC,SAAS,IAC/BD,SAASb,QAAQ,KAAK,gBAAgBc,cAAc;IACvD,OAAO;QACLhB,UAAUoB,oBACNtB,KAAKG,IAAI,GACT,GAAGH,KAAKG,IAAI,CAACoB,OAAO,CAAC,eAAe,IAAI,CAAC,EAAEN,SAASC,SAAS,EAAE;QACnEd,UAAUa,SAASb,QAAQ;IAC7B;AACF,EAAC"}
@@ -0,0 +1,30 @@
1
+ import type ImageKit from '@imagekit/nodejs';
2
+ import type { CollectionConfig } from 'payload';
3
+ import type { ImageKitReceiptContext } from './clientUploads.js';
4
+ export type VerifiedImageKitUpload = {
5
+ fileId: string;
6
+ filePath: string;
7
+ fileType?: string;
8
+ mimeType: string;
9
+ name: string;
10
+ size: number;
11
+ thumbnailUrl?: string;
12
+ url: string;
13
+ };
14
+ type CollectionSecurityConfig = {
15
+ collection: CollectionConfig;
16
+ isPrivate: boolean;
17
+ prefix: string;
18
+ };
19
+ export declare const getClientUploadStorageFilePath: ({ collectionPrefix, context, }: {
20
+ collectionPrefix: string;
21
+ context: ImageKitReceiptContext;
22
+ }) => string;
23
+ export declare const createImageKitClientUploadVerifier: ({ client, collections, }: {
24
+ client: ImageKit;
25
+ collections: Record<string, CollectionSecurityConfig>;
26
+ }) => ({ collectionSlug, context, }: {
27
+ collectionSlug: string;
28
+ context: unknown;
29
+ }) => Promise<VerifiedImageKitUpload>;
30
+ export {};
@@ -0,0 +1,104 @@
1
+ import { buildStoragePathData } from '@payloadcms/plugin-cloud-storage/utilities';
2
+ import { APIError } from 'payload';
3
+ import { assertClientUploadAllowed } from 'payload/internal';
4
+ import { validateMimeType } from 'payload/shared';
5
+ import { isImageKitReceiptContext } from './clientUploads.js';
6
+ const getMimeTypeEssence = (mimeType)=>(mimeType.split(';', 1)[0] || '').trim().toLowerCase();
7
+ const toImageKitPath = (storageFilePath)=>`/${storageFilePath.replace(/^\/+/, '')}`;
8
+ const assetLookupRetryDelays = [
9
+ 250,
10
+ 500,
11
+ 1000,
12
+ 2000
13
+ ];
14
+ const findFileIDByPath = async (client, filePath)=>{
15
+ const parentPath = `${filePath.slice(0, filePath.lastIndexOf('/') + 1)}`;
16
+ const name = filePath.slice(filePath.lastIndexOf('/') + 1);
17
+ const limit = 100;
18
+ for(let skip = 0;; skip += limit){
19
+ const assets = await client.assets.list({
20
+ type: 'file',
21
+ limit,
22
+ path: parentPath,
23
+ skip
24
+ });
25
+ const found = assets.find((asset)=>'fileId' in asset && asset.filePath === filePath && asset.name === name);
26
+ if (found && 'fileId' in found) {
27
+ return found.fileId;
28
+ }
29
+ if (assets.length < limit) {
30
+ return undefined;
31
+ }
32
+ }
33
+ };
34
+ export const getClientUploadStorageFilePath = ({ collectionPrefix, context })=>buildStoragePathData({
35
+ collectionPrefix,
36
+ docPrefix: [
37
+ context.prefix,
38
+ context._objectKey
39
+ ].filter(Boolean).join('/'),
40
+ filename: context.filename
41
+ }).storageFilePath;
42
+ export const createImageKitClientUploadVerifier = ({ client, collections })=>{
43
+ const verifiedContexts = new WeakMap();
44
+ return async ({ collectionSlug, context })=>{
45
+ if (!isImageKitReceiptContext(context)) {
46
+ throw new APIError('Invalid ImageKit client upload context.', 400);
47
+ }
48
+ const collectionSecurity = collections[collectionSlug];
49
+ if (!collectionSecurity || context.collectionSlug !== collectionSlug || context.isPrivate !== collectionSecurity.isPrivate) {
50
+ throw new APIError('ImageKit client upload reference does not match this collection.', 400);
51
+ }
52
+ const cached = verifiedContexts.get(context);
53
+ if (cached) {
54
+ return cached;
55
+ }
56
+ const storageFilePath = getClientUploadStorageFilePath({
57
+ collectionPrefix: collectionSecurity.prefix,
58
+ context
59
+ });
60
+ const expectedFilePath = toImageKitPath(storageFilePath);
61
+ let fileId = context.fileId;
62
+ // Receipts without a file ID can still be verified by their signed path and binding.
63
+ // The completion handshake avoids depending on asset listing visibility.
64
+ if (!fileId) {
65
+ fileId = await findFileIDByPath(client, expectedFilePath);
66
+ for (const delay of assetLookupRetryDelays){
67
+ if (fileId) {
68
+ break;
69
+ }
70
+ await new Promise((resolve)=>setTimeout(resolve, delay));
71
+ fileId = await findFileIDByPath(client, expectedFilePath);
72
+ }
73
+ }
74
+ if (!fileId) {
75
+ throw new APIError('ImageKit client upload was not found at its authorized path.', 400);
76
+ }
77
+ const uploadedFile = await client.files.get(fileId);
78
+ const uploadedMime = typeof uploadedFile.mime === 'string' ? getMimeTypeEssence(uploadedFile.mime) : '';
79
+ const uploadedSize = uploadedFile.size;
80
+ assertClientUploadAllowed({
81
+ collection: collectionSecurity.collection,
82
+ filename: context.filename,
83
+ mimeType: uploadedMime
84
+ });
85
+ const allowedMimeTypes = typeof collectionSecurity.collection.upload === 'object' && Array.isArray(collectionSecurity.collection.upload.mimeTypes) ? collectionSecurity.collection.upload.mimeTypes : [];
86
+ if (uploadedFile.fileId !== fileId || uploadedFile.filePath !== expectedFilePath || uploadedFile.name !== context.filename || context.uploadTag !== undefined && !uploadedFile.tags?.includes(context.uploadTag) || !uploadedMime || uploadedMime !== context.expectedMimeType || typeof uploadedSize !== 'number' || !Number.isSafeInteger(uploadedSize) || uploadedSize !== context.expectedSize || allowedMimeTypes.length > 0 && !validateMimeType(uploadedMime, allowedMimeTypes) || Boolean(uploadedFile.isPrivateFile) !== collectionSecurity.isPrivate || typeof uploadedFile.url !== 'string' || uploadedFile.url.length === 0) {
87
+ throw new APIError('ImageKit client upload metadata did not match its authorization.', 400);
88
+ }
89
+ const verified = {
90
+ name: uploadedFile.name,
91
+ fileId,
92
+ filePath: expectedFilePath,
93
+ fileType: uploadedFile.fileType,
94
+ mimeType: uploadedMime,
95
+ size: uploadedSize,
96
+ thumbnailUrl: typeof uploadedFile.thumbnail === 'string' ? uploadedFile.thumbnail : undefined,
97
+ url: uploadedFile.url
98
+ };
99
+ verifiedContexts.set(context, verified);
100
+ return verified;
101
+ };
102
+ };
103
+
104
+ //# sourceMappingURL=clientUploadVerification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/clientUploadVerification.ts"],"sourcesContent":["import type ImageKit from '@imagekit/nodejs'\nimport type { CollectionConfig } from 'payload'\n\nimport { buildStoragePathData } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { APIError } from 'payload'\nimport { assertClientUploadAllowed } from 'payload/internal'\nimport { validateMimeType } from 'payload/shared'\n\nimport type { ImageKitReceiptContext } from './clientUploads.js'\n\nimport { isImageKitReceiptContext } from './clientUploads.js'\n\nexport type VerifiedImageKitUpload = {\n fileId: string\n filePath: string\n fileType?: string\n mimeType: string\n name: string\n size: number\n thumbnailUrl?: string\n url: string\n}\n\ntype CollectionSecurityConfig = {\n collection: CollectionConfig\n isPrivate: boolean\n prefix: string\n}\n\nconst getMimeTypeEssence = (mimeType: string): string =>\n (mimeType.split(';', 1)[0] || '').trim().toLowerCase()\n\nconst toImageKitPath = (storageFilePath: string): string =>\n `/${storageFilePath.replace(/^\\/+/, '')}`\n\nconst assetLookupRetryDelays = [250, 500, 1000, 2000]\n\nconst findFileIDByPath = async (\n client: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = `${filePath.slice(0, filePath.lastIndexOf('/') + 1)}`\n const name = filePath.slice(filePath.lastIndexOf('/') + 1)\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await client.assets.list({\n type: 'file',\n limit,\n path: parentPath,\n skip,\n })\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n return undefined\n }\n }\n}\n\nexport const getClientUploadStorageFilePath = ({\n collectionPrefix,\n context,\n}: {\n collectionPrefix: string\n context: ImageKitReceiptContext\n}): string =>\n buildStoragePathData({\n collectionPrefix,\n docPrefix: [context.prefix, context._objectKey].filter(Boolean).join('/'),\n filename: context.filename,\n }).storageFilePath\n\nexport const createImageKitClientUploadVerifier = ({\n client,\n collections,\n}: {\n client: ImageKit\n collections: Record<string, CollectionSecurityConfig>\n}) => {\n const verifiedContexts = new WeakMap<object, VerifiedImageKitUpload>()\n\n return async ({\n collectionSlug,\n context,\n }: {\n collectionSlug: string\n context: unknown\n }): Promise<VerifiedImageKitUpload> => {\n if (!isImageKitReceiptContext(context)) {\n throw new APIError('Invalid ImageKit client upload context.', 400)\n }\n\n const collectionSecurity = collections[collectionSlug]\n if (\n !collectionSecurity ||\n context.collectionSlug !== collectionSlug ||\n context.isPrivate !== collectionSecurity.isPrivate\n ) {\n throw new APIError('ImageKit client upload reference does not match this collection.', 400)\n }\n\n const cached = verifiedContexts.get(context)\n if (cached) {\n return cached\n }\n\n const storageFilePath = getClientUploadStorageFilePath({\n collectionPrefix: collectionSecurity.prefix,\n context,\n })\n const expectedFilePath = toImageKitPath(storageFilePath)\n let fileId = context.fileId\n\n // Receipts without a file ID can still be verified by their signed path and binding.\n // The completion handshake avoids depending on asset listing visibility.\n if (!fileId) {\n fileId = await findFileIDByPath(client, expectedFilePath)\n for (const delay of assetLookupRetryDelays) {\n if (fileId) {\n break\n }\n await new Promise((resolve) => setTimeout(resolve, delay))\n fileId = await findFileIDByPath(client, expectedFilePath)\n }\n }\n\n if (!fileId) {\n throw new APIError('ImageKit client upload was not found at its authorized path.', 400)\n }\n\n const uploadedFile = await client.files.get(fileId)\n const uploadedMime =\n typeof uploadedFile.mime === 'string' ? getMimeTypeEssence(uploadedFile.mime) : ''\n const uploadedSize = uploadedFile.size\n\n assertClientUploadAllowed({\n collection: collectionSecurity.collection,\n filename: context.filename,\n mimeType: uploadedMime,\n })\n const allowedMimeTypes =\n typeof collectionSecurity.collection.upload === 'object' &&\n Array.isArray(collectionSecurity.collection.upload.mimeTypes)\n ? collectionSecurity.collection.upload.mimeTypes\n : []\n\n if (\n uploadedFile.fileId !== fileId ||\n uploadedFile.filePath !== expectedFilePath ||\n uploadedFile.name !== context.filename ||\n (context.uploadTag !== undefined && !uploadedFile.tags?.includes(context.uploadTag)) ||\n !uploadedMime ||\n uploadedMime !== context.expectedMimeType ||\n typeof uploadedSize !== 'number' ||\n !Number.isSafeInteger(uploadedSize) ||\n uploadedSize !== context.expectedSize ||\n (allowedMimeTypes.length > 0 && !validateMimeType(uploadedMime, allowedMimeTypes)) ||\n Boolean(uploadedFile.isPrivateFile) !== collectionSecurity.isPrivate ||\n typeof uploadedFile.url !== 'string' ||\n uploadedFile.url.length === 0\n ) {\n throw new APIError('ImageKit client upload metadata did not match its authorization.', 400)\n }\n\n const verified: VerifiedImageKitUpload = {\n name: uploadedFile.name,\n fileId,\n filePath: expectedFilePath,\n fileType: uploadedFile.fileType,\n mimeType: uploadedMime,\n size: uploadedSize,\n thumbnailUrl: typeof uploadedFile.thumbnail === 'string' ? uploadedFile.thumbnail : undefined,\n url: uploadedFile.url,\n }\n\n verifiedContexts.set(context, verified)\n return verified\n }\n}\n"],"names":["buildStoragePathData","APIError","assertClientUploadAllowed","validateMimeType","isImageKitReceiptContext","getMimeTypeEssence","mimeType","split","trim","toLowerCase","toImageKitPath","storageFilePath","replace","assetLookupRetryDelays","findFileIDByPath","client","filePath","parentPath","slice","lastIndexOf","name","limit","skip","assets","list","type","path","found","find","asset","fileId","length","undefined","getClientUploadStorageFilePath","collectionPrefix","context","docPrefix","prefix","_objectKey","filter","Boolean","join","filename","createImageKitClientUploadVerifier","collections","verifiedContexts","WeakMap","collectionSlug","collectionSecurity","isPrivate","cached","get","expectedFilePath","delay","Promise","resolve","setTimeout","uploadedFile","files","uploadedMime","mime","uploadedSize","size","collection","allowedMimeTypes","upload","Array","isArray","mimeTypes","uploadTag","tags","includes","expectedMimeType","Number","isSafeInteger","expectedSize","isPrivateFile","url","verified","fileType","thumbnailUrl","thumbnail","set"],"mappings":"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"}
@@ -1,5 +1,9 @@
1
- export declare const imageKitUploadAuthBasePath = "/imagekit-upload-auth";
2
- export declare const getImageKitUploadAuthPath: (collectionSlug: string) => `/${string}`;
1
+ import type { ClientUploadContext } from '@payloadcms/plugin-cloud-storage/types';
2
+ export declare const imageKitUploadAuthPath = "/imagekit-upload-auth";
3
+ /**
4
+ * @deprecated Provider metadata is verified server-side and is no longer accepted as upload
5
+ * proof. This backwards-compatible shape remains exported for existing type consumers.
6
+ */
3
7
  export type ImageKitClientUploadContext = {
4
8
  fileId?: string;
5
9
  filePath?: string;
@@ -9,7 +13,8 @@ export type ImageKitClientUploadContext = {
9
13
  size?: number;
10
14
  thumbnailUrl?: string;
11
15
  url?: string;
12
- };
16
+ } & Partial<ClientUploadContext>;
17
+ /** @deprecated The upload handler is configured internally by Payload 3.90.1. */
13
18
  export type ImageKitUploadHandlerProps = {
14
19
  authPath: `/${string}`;
15
20
  collectionSlug: string;
@@ -17,4 +22,36 @@ export type ImageKitUploadHandlerProps = {
17
22
  isPrivate?: boolean;
18
23
  publicKey: string;
19
24
  };
20
- export declare const isImageKitClientUploadContext: (value: unknown) => value is ImageKitClientUploadContext;
25
+ export type ImageKitReceiptContext = {
26
+ /** Legacy uploads stored beneath a per-upload folder. */
27
+ _objectKey?: string;
28
+ collectionSlug: string;
29
+ expectedMimeType: string;
30
+ expectedSize: number;
31
+ /** Present only after the server verifies the completed upload. */
32
+ fileId?: string;
33
+ filename: string;
34
+ isPrivate: boolean;
35
+ prefix: string;
36
+ /** Binds a pending authorization to the newly uploaded provider object. */
37
+ uploadTag?: string;
38
+ };
39
+ export type ImageKitUploadPayload = {
40
+ checks: string;
41
+ fileName: string;
42
+ folder: string;
43
+ isPrivateFile: 'false' | 'true';
44
+ overwriteFile: 'false';
45
+ tags: string;
46
+ useUniqueFileName: 'false';
47
+ };
48
+ export type ImageKitUploadAuthorization = {
49
+ clientUploadContext: ClientUploadContext;
50
+ filename: string;
51
+ token: string;
52
+ uploadPayload: ImageKitUploadPayload;
53
+ };
54
+ export type ImageKitUploadCompletion = {
55
+ clientUploadContext: ClientUploadContext;
56
+ };
57
+ export declare const isImageKitReceiptContext: (value: unknown) => value is ImageKitReceiptContext;
@@ -1,10 +1,10 @@
1
- export const imageKitUploadAuthBasePath = '/imagekit-upload-auth';
2
- export const getImageKitUploadAuthPath = (collectionSlug)=>`${imageKitUploadAuthBasePath}/${collectionSlug}`;
3
- export const isImageKitClientUploadContext = (value)=>{
1
+ export const imageKitUploadAuthPath = '/imagekit-upload-auth';
2
+ export const isImageKitReceiptContext = (value)=>{
4
3
  if (!value || typeof value !== 'object') {
5
4
  return false;
6
5
  }
7
- return 'fileId' in value || 'thumbnailUrl' in value || 'url' in value;
6
+ const context = value;
7
+ return (context._objectKey === undefined || typeof context._objectKey === 'string' && context._objectKey.length > 0) && (context.uploadTag === undefined || typeof context.uploadTag === 'string' && context.uploadTag.length > 0) && Boolean(context._objectKey || context.uploadTag || context.fileId) && typeof context.collectionSlug === 'string' && context.collectionSlug.length > 0 && typeof context.expectedMimeType === 'string' && context.expectedMimeType.length > 0 && typeof context.expectedSize === 'number' && Number.isSafeInteger(context.expectedSize) && context.expectedSize >= 0 && (context.fileId === undefined || typeof context.fileId === 'string' && context.fileId.length > 0) && typeof context.filename === 'string' && context.filename.length > 0 && typeof context.isPrivate === 'boolean' && typeof context.prefix === 'string';
8
8
  };
9
9
 
10
10
  //# sourceMappingURL=clientUploads.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/clientUploads.ts"],"sourcesContent":["export const imageKitUploadAuthBasePath = '/imagekit-upload-auth'\n\nexport const getImageKitUploadAuthPath = (collectionSlug: string): `/${string}` =>\n `${imageKitUploadAuthBasePath}/${collectionSlug}`\n\nexport type ImageKitClientUploadContext = {\n fileId?: string\n filePath?: string\n fileType?: string\n mimeType?: string\n name?: string\n size?: number\n thumbnailUrl?: string\n url?: string\n}\n\nexport type ImageKitUploadHandlerProps = {\n authPath: `/${string}`\n collectionSlug: string\n folder: string\n isPrivate?: boolean\n publicKey: string\n}\n\nexport const isImageKitClientUploadContext = (\n value: unknown,\n): value is ImageKitClientUploadContext => {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n return 'fileId' in value || 'thumbnailUrl' in value || 'url' in value\n}\n"],"names":["imageKitUploadAuthBasePath","getImageKitUploadAuthPath","collectionSlug","isImageKitClientUploadContext","value"],"mappings":"AAAA,OAAO,MAAMA,6BAA6B,wBAAuB;AAEjE,OAAO,MAAMC,4BAA4B,CAACC,iBACxC,GAAGF,2BAA2B,CAAC,EAAEE,gBAAgB,CAAA;AAqBnD,OAAO,MAAMC,gCAAgC,CAC3CC;IAEA,IAAI,CAACA,SAAS,OAAOA,UAAU,UAAU;QACvC,OAAO;IACT;IAEA,OAAO,YAAYA,SAAS,kBAAkBA,SAAS,SAASA;AAClE,EAAC"}
1
+ {"version":3,"sources":["../src/clientUploads.ts"],"sourcesContent":["import type { ClientUploadContext } from '@payloadcms/plugin-cloud-storage/types'\n\nexport const imageKitUploadAuthPath = '/imagekit-upload-auth'\n\n/**\n * @deprecated Provider metadata is verified server-side and is no longer accepted as upload\n * proof. This backwards-compatible shape remains exported for existing type consumers.\n */\nexport type ImageKitClientUploadContext = {\n fileId?: string\n filePath?: string\n fileType?: string\n mimeType?: string\n name?: string\n size?: number\n thumbnailUrl?: string\n url?: string\n} & Partial<ClientUploadContext>\n\n/** @deprecated The upload handler is configured internally by Payload 3.90.1. */\nexport type ImageKitUploadHandlerProps = {\n authPath: `/${string}`\n collectionSlug: string\n folder: string\n isPrivate?: boolean\n publicKey: string\n}\n\nexport type ImageKitReceiptContext = {\n /** Legacy uploads stored beneath a per-upload folder. */\n _objectKey?: string\n collectionSlug: string\n expectedMimeType: string\n expectedSize: number\n /** Present only after the server verifies the completed upload. */\n fileId?: string\n filename: string\n isPrivate: boolean\n prefix: string\n /** Binds a pending authorization to the newly uploaded provider object. */\n uploadTag?: string\n}\n\nexport type ImageKitUploadPayload = {\n checks: string\n fileName: string\n folder: string\n isPrivateFile: 'false' | 'true'\n overwriteFile: 'false'\n tags: string\n useUniqueFileName: 'false'\n}\n\nexport type ImageKitUploadAuthorization = {\n clientUploadContext: ClientUploadContext\n filename: string\n token: string\n uploadPayload: ImageKitUploadPayload\n}\n\nexport type ImageKitUploadCompletion = {\n clientUploadContext: ClientUploadContext\n}\n\nexport const isImageKitReceiptContext = (value: unknown): value is ImageKitReceiptContext => {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n const context = value as Partial<ImageKitReceiptContext>\n\n return (\n (context._objectKey === undefined ||\n (typeof context._objectKey === 'string' && context._objectKey.length > 0)) &&\n (context.uploadTag === undefined ||\n (typeof context.uploadTag === 'string' && context.uploadTag.length > 0)) &&\n Boolean(context._objectKey || context.uploadTag || context.fileId) &&\n typeof context.collectionSlug === 'string' &&\n context.collectionSlug.length > 0 &&\n typeof context.expectedMimeType === 'string' &&\n context.expectedMimeType.length > 0 &&\n typeof context.expectedSize === 'number' &&\n Number.isSafeInteger(context.expectedSize) &&\n context.expectedSize >= 0 &&\n (context.fileId === undefined ||\n (typeof context.fileId === 'string' && context.fileId.length > 0)) &&\n typeof context.filename === 'string' &&\n context.filename.length > 0 &&\n typeof context.isPrivate === 'boolean' &&\n typeof context.prefix === 'string'\n )\n}\n"],"names":["imageKitUploadAuthPath","isImageKitReceiptContext","value","context","_objectKey","undefined","length","uploadTag","Boolean","fileId","collectionSlug","expectedMimeType","expectedSize","Number","isSafeInteger","filename","isPrivate","prefix"],"mappings":"AAEA,OAAO,MAAMA,yBAAyB,wBAAuB;AA8D7D,OAAO,MAAMC,2BAA2B,CAACC;IACvC,IAAI,CAACA,SAAS,OAAOA,UAAU,UAAU;QACvC,OAAO;IACT;IAEA,MAAMC,UAAUD;IAEhB,OACE,AAACC,CAAAA,QAAQC,UAAU,KAAKC,aACrB,OAAOF,QAAQC,UAAU,KAAK,YAAYD,QAAQC,UAAU,CAACE,MAAM,GAAG,CAAC,KACzEH,CAAAA,QAAQI,SAAS,KAAKF,aACpB,OAAOF,QAAQI,SAAS,KAAK,YAAYJ,QAAQI,SAAS,CAACD,MAAM,GAAG,CAAC,KACxEE,QAAQL,QAAQC,UAAU,IAAID,QAAQI,SAAS,IAAIJ,QAAQM,MAAM,KACjE,OAAON,QAAQO,cAAc,KAAK,YAClCP,QAAQO,cAAc,CAACJ,MAAM,GAAG,KAChC,OAAOH,QAAQQ,gBAAgB,KAAK,YACpCR,QAAQQ,gBAAgB,CAACL,MAAM,GAAG,KAClC,OAAOH,QAAQS,YAAY,KAAK,YAChCC,OAAOC,aAAa,CAACX,QAAQS,YAAY,KACzCT,QAAQS,YAAY,IAAI,KACvBT,CAAAA,QAAQM,MAAM,KAAKJ,aACjB,OAAOF,QAAQM,MAAM,KAAK,YAAYN,QAAQM,MAAM,CAACH,MAAM,GAAG,CAAC,KAClE,OAAOH,QAAQY,QAAQ,KAAK,YAC5BZ,QAAQY,QAAQ,CAACT,MAAM,GAAG,KAC1B,OAAOH,QAAQa,SAAS,KAAK,aAC7B,OAAOb,QAAQc,MAAM,KAAK;AAE9B,EAAC"}
@@ -1,2 +1,8 @@
1
- import type { ImageKitUploadHandlerProps } from '../clientUploads.js';
2
- export declare const ImageKitUploadHandler: ({ authPath, collectionSlug, folder, isPrivate, publicKey, }: ImageKitUploadHandlerProps) => null;
1
+ export declare const ImageKitUploadHandler: ({ children, collectionSlug, enabled, extra, prefix, serverHandlerPath, }: {
2
+ children: import("react").ReactNode;
3
+ collectionSlug: import("payload").UploadCollectionSlug;
4
+ enabled?: boolean;
5
+ extra: Record<string, unknown>;
6
+ prefix?: string;
7
+ serverHandlerPath: `/${string}`;
8
+ }) => import("react").JSX.Element;