@humaan/payload-storage-imagekit 2.6.2 → 2.8.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 +96 -6
- package/dist/clientImageIdentity.d.ts +5 -0
- package/dist/clientImageIdentity.js +86 -0
- package/dist/clientImageIdentity.js.map +1 -0
- package/dist/clientUploadVerification.d.ts +30 -0
- package/dist/clientUploadVerification.js +104 -0
- package/dist/clientUploadVerification.js.map +1 -0
- package/dist/clientUploads.d.ts +41 -4
- package/dist/clientUploads.js +4 -4
- package/dist/clientUploads.js.map +1 -1
- package/dist/components/ImageKitUploadHandler.d.ts +8 -2
- package/dist/components/ImageKitUploadHandler.js +80 -65
- package/dist/components/ImageKitUploadHandler.js.map +1 -1
- package/dist/endpoints/createImageKitUploadAuthHandler.d.ts +12 -4
- package/dist/endpoints/createImageKitUploadAuthHandler.js +162 -29
- package/dist/endpoints/createImageKitUploadAuthHandler.js.map +1 -1
- package/dist/imageKitUploadToken.d.ts +12 -0
- package/dist/imageKitUploadToken.js +25 -0
- package/dist/imageKitUploadToken.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +366 -206
- package/dist/index.js.map +1 -1
- package/package.json +32 -33
|
@@ -1,86 +1,101 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client';
|
|
3
3
|
import { formatAdminURL } from 'payload/shared';
|
|
4
|
-
import {
|
|
5
|
-
const uploadAPIURL = 'https://upload.imagekit.io/api/
|
|
4
|
+
import { getClientImageIdentity } from '../clientImageIdentity.js';
|
|
5
|
+
const uploadAPIURL = 'https://upload.imagekit.io/api/v2/files/upload';
|
|
6
6
|
const readErrorMessage = async (response, fallback)=>{
|
|
7
7
|
try {
|
|
8
8
|
const result = await response.json();
|
|
9
9
|
if (typeof result.message === 'string' && result.message.trim().length > 0) {
|
|
10
10
|
return result.message;
|
|
11
11
|
}
|
|
12
|
+
const messages = result.errors?.map((error)=>error.message).filter((message)=>Boolean(message));
|
|
13
|
+
if (messages?.length) {
|
|
14
|
+
return messages.join(', ');
|
|
15
|
+
}
|
|
12
16
|
} catch {
|
|
13
17
|
// Ignore non-JSON responses and fall back to the default message.
|
|
14
18
|
}
|
|
15
19
|
return fallback;
|
|
16
20
|
};
|
|
17
|
-
export const ImageKitUploadHandler = ({
|
|
18
|
-
|
|
19
|
-
const { setUploadHandler } = useUploadHandlers();
|
|
20
|
-
useEffect(()=>{
|
|
21
|
+
export const ImageKitUploadHandler = createClientUploadHandler({
|
|
22
|
+
handler: async ({ apiRoute, collectionSlug, file, serverHandlerPath, serverURL, updateFilename })=>{
|
|
21
23
|
const authURL = formatAdminURL({
|
|
22
|
-
apiRoute
|
|
23
|
-
path:
|
|
24
|
+
apiRoute,
|
|
25
|
+
path: serverHandlerPath,
|
|
26
|
+
serverURL
|
|
24
27
|
});
|
|
25
|
-
|
|
28
|
+
const identity = await getClientImageIdentity(file);
|
|
29
|
+
let authBody = {
|
|
26
30
|
collectionSlug,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
31
|
+
filename: identity.filename,
|
|
32
|
+
filesize: file.size,
|
|
33
|
+
mimeType: identity.mimeType
|
|
34
|
+
};
|
|
35
|
+
for(let attempt = 0; attempt < 10; attempt++){
|
|
36
|
+
const authResponse = await fetch(authURL, {
|
|
37
|
+
body: JSON.stringify(authBody),
|
|
38
|
+
credentials: 'include',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json'
|
|
41
|
+
},
|
|
42
|
+
method: 'POST'
|
|
43
|
+
});
|
|
44
|
+
if (!authResponse.ok) {
|
|
45
|
+
throw new Error(await readErrorMessage(authResponse, 'Failed to authorize direct ImageKit upload.'));
|
|
46
|
+
}
|
|
47
|
+
const authorization = await authResponse.json();
|
|
48
|
+
if (authorization.filename !== file.name) {
|
|
49
|
+
updateFilename(authorization.filename);
|
|
50
|
+
}
|
|
51
|
+
const formData = new FormData();
|
|
52
|
+
formData.set('file', file);
|
|
53
|
+
formData.set('token', authorization.token);
|
|
54
|
+
for (const [key, value] of Object.entries(authorization.uploadPayload)){
|
|
55
|
+
formData.set(key, value);
|
|
56
|
+
}
|
|
57
|
+
const uploadResponse = await fetch(uploadAPIURL, {
|
|
58
|
+
body: formData,
|
|
59
|
+
method: 'POST'
|
|
60
|
+
});
|
|
61
|
+
if (!uploadResponse.ok) {
|
|
62
|
+
const message = await readErrorMessage(uploadResponse, 'Failed to upload file to ImageKit.');
|
|
63
|
+
const isFilenameConflict = (uploadResponse.status === 400 || uploadResponse.status === 409) && message.includes('A file with the same name already exists at the exact location');
|
|
64
|
+
if (isFilenameConflict) {
|
|
65
|
+
authBody = {
|
|
66
|
+
action: 'retry',
|
|
67
|
+
collectionSlug,
|
|
68
|
+
signedReceipt: authorization.clientUploadContext.signedReceipt
|
|
69
|
+
};
|
|
70
|
+
continue;
|
|
58
71
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
throw new Error(message);
|
|
73
|
+
}
|
|
74
|
+
const uploaded = await uploadResponse.json();
|
|
75
|
+
if (typeof uploaded.fileId !== 'string' || !uploaded.fileId.trim()) {
|
|
76
|
+
throw new Error('ImageKit did not return an uploaded file ID.');
|
|
77
|
+
}
|
|
78
|
+
const completionResponse = await fetch(authURL, {
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
action: 'complete',
|
|
81
|
+
collectionSlug,
|
|
64
82
|
fileId: uploaded.fileId,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
signedReceipt: authorization.clientUploadContext.signedReceipt
|
|
84
|
+
}),
|
|
85
|
+
credentials: 'include',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json'
|
|
88
|
+
},
|
|
89
|
+
method: 'POST'
|
|
90
|
+
});
|
|
91
|
+
if (!completionResponse.ok) {
|
|
92
|
+
throw new Error(await readErrorMessage(completionResponse, 'Failed to verify ImageKit upload.'));
|
|
72
93
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
isPrivate,
|
|
80
|
-
publicKey,
|
|
81
|
-
setUploadHandler
|
|
82
|
-
]);
|
|
83
|
-
return null;
|
|
84
|
-
};
|
|
94
|
+
const completion = await completionResponse.json();
|
|
95
|
+
return completion.clientUploadContext;
|
|
96
|
+
}
|
|
97
|
+
throw new Error('Could not find an available filename. Rename the file and try again.');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
85
100
|
|
|
86
101
|
//# sourceMappingURL=ImageKitUploadHandler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/ImageKitUploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/components/ImageKitUploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\nimport { formatAdminURL } from 'payload/shared'\n\nimport type { ImageKitUploadAuthorization, ImageKitUploadCompletion } from '../clientUploads.js'\n\nimport { getClientImageIdentity } from '../clientImageIdentity.js'\n\nconst uploadAPIURL = 'https://upload.imagekit.io/api/v2/files/upload'\n\nconst readErrorMessage = async (response: Response, fallback: string): Promise<string> => {\n try {\n const result = (await response.json()) as {\n errors?: Array<{ message?: string }>\n message?: string\n }\n\n if (typeof result.message === 'string' && result.message.trim().length > 0) {\n return result.message\n }\n\n const messages = result.errors\n ?.map((error) => error.message)\n .filter((message): message is string => Boolean(message))\n\n if (messages?.length) {\n return messages.join(', ')\n }\n } catch {\n // Ignore non-JSON responses and fall back to the default message.\n }\n\n return fallback\n}\n\nexport const ImageKitUploadHandler = createClientUploadHandler({\n handler: async ({\n apiRoute,\n collectionSlug,\n file,\n serverHandlerPath,\n serverURL,\n updateFilename,\n }) => {\n const authURL = formatAdminURL({\n apiRoute,\n path: serverHandlerPath,\n serverURL,\n })\n const identity = await getClientImageIdentity(file)\n let authBody: Record<string, unknown> = {\n collectionSlug,\n filename: identity.filename,\n filesize: file.size,\n mimeType: identity.mimeType,\n }\n\n for (let attempt = 0; attempt < 10; attempt++) {\n const authResponse = await fetch(authURL, {\n body: JSON.stringify(authBody),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!authResponse.ok) {\n throw new Error(\n await readErrorMessage(authResponse, 'Failed to authorize direct ImageKit upload.'),\n )\n }\n\n const authorization = (await authResponse.json()) as ImageKitUploadAuthorization\n\n if (authorization.filename !== file.name) {\n updateFilename(authorization.filename)\n }\n\n const formData = new FormData()\n\n formData.set('file', file)\n formData.set('token', authorization.token)\n for (const [key, value] of Object.entries(authorization.uploadPayload)) {\n formData.set(key, value)\n }\n\n const uploadResponse = await fetch(uploadAPIURL, {\n body: formData,\n method: 'POST',\n })\n\n if (!uploadResponse.ok) {\n const message = await readErrorMessage(uploadResponse, 'Failed to upload file to ImageKit.')\n const isFilenameConflict =\n (uploadResponse.status === 400 || uploadResponse.status === 409) &&\n message.includes('A file with the same name already exists at the exact location')\n if (isFilenameConflict) {\n authBody = {\n action: 'retry',\n collectionSlug,\n signedReceipt: authorization.clientUploadContext.signedReceipt,\n }\n continue\n }\n throw new Error(message)\n }\n\n const uploaded = (await uploadResponse.json()) as { fileId?: unknown }\n if (typeof uploaded.fileId !== 'string' || !uploaded.fileId.trim()) {\n throw new Error('ImageKit did not return an uploaded file ID.')\n }\n\n const completionResponse = await fetch(authURL, {\n body: JSON.stringify({\n action: 'complete',\n collectionSlug,\n fileId: uploaded.fileId,\n signedReceipt: authorization.clientUploadContext.signedReceipt,\n }),\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n if (!completionResponse.ok) {\n throw new Error(\n await readErrorMessage(completionResponse, 'Failed to verify ImageKit upload.'),\n )\n }\n const completion = (await completionResponse.json()) as ImageKitUploadCompletion\n return completion.clientUploadContext\n }\n throw new Error('Could not find an available filename. Rename the file and try again.')\n },\n})\n"],"names":["createClientUploadHandler","formatAdminURL","getClientImageIdentity","uploadAPIURL","readErrorMessage","response","fallback","result","json","message","trim","length","messages","errors","map","error","filter","Boolean","join","ImageKitUploadHandler","handler","apiRoute","collectionSlug","file","serverHandlerPath","serverURL","updateFilename","authURL","path","identity","authBody","filename","filesize","size","mimeType","attempt","authResponse","fetch","body","JSON","stringify","credentials","headers","method","ok","Error","authorization","name","formData","FormData","set","token","key","value","Object","entries","uploadPayload","uploadResponse","isFilenameConflict","status","includes","action","signedReceipt","clientUploadContext","uploaded","fileId","completionResponse","completion"],"mappings":"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"}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type { ClientUploadsAccess } from '@payloadcms/plugin-cloud-storage/types';
|
|
2
2
|
import type { PayloadHandler, UploadCollectionSlug } from 'payload';
|
|
3
|
+
import type { createImageKitClientUploadVerifier } from '../clientUploadVerification.js';
|
|
4
|
+
type ImageKitUploadCollection = {
|
|
5
|
+
isPrivate: boolean;
|
|
6
|
+
prefix: string;
|
|
7
|
+
};
|
|
3
8
|
type CreateImageKitUploadAuthHandlerArgs = {
|
|
4
|
-
|
|
5
|
-
collections: Partial<Record<UploadCollectionSlug,
|
|
9
|
+
access?: ClientUploadsAccess;
|
|
10
|
+
collections: Partial<Record<UploadCollectionSlug, ImageKitUploadCollection>>;
|
|
11
|
+
privateKey: string;
|
|
12
|
+
publicKey: string;
|
|
13
|
+
verifyClientUpload: ReturnType<typeof createImageKitClientUploadVerifier>;
|
|
6
14
|
};
|
|
7
|
-
export declare const createImageKitUploadAuthHandler: ({
|
|
15
|
+
export declare const createImageKitUploadAuthHandler: ({ access, collections, privateKey, publicKey, verifyClientUpload, }: CreateImageKitUploadAuthHandlerArgs) => PayloadHandler;
|
|
8
16
|
export {};
|
|
@@ -1,42 +1,175 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { buildUploadStoragePathData } from '@payloadcms/plugin-cloud-storage/utilities';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { posix } from 'node:path';
|
|
4
|
+
import { APIError, Forbidden } from 'payload';
|
|
5
|
+
import { assertClientUploadAccess, assertClientUploadAllowed, assertClientUploadFileSize, createClientUploadReceipt, getSafeFileName, verifyClientUploadReceipt } from 'payload/internal';
|
|
6
|
+
import { getSanitizedUploadFilename, validateMimeType } from 'payload/shared';
|
|
7
|
+
import { isImageKitReceiptContext } from '../clientUploads.js';
|
|
8
|
+
import { createImageKitUploadToken } from '../imageKitUploadToken.js';
|
|
9
|
+
const defaultAccess = ({ req })=>Boolean(req.user);
|
|
10
|
+
const bytesToMB = (bytes)=>(bytes / 1024 / 1024).toFixed(2);
|
|
11
|
+
const getMimeTypeEssence = (mimeType)=>(mimeType.split(';', 1)[0] || '').trim().toLowerCase();
|
|
12
|
+
const getImageKitSafeFilename = (filename)=>getSanitizedUploadFilename(filename).replace(/[^\w.-]/g, '_');
|
|
13
|
+
// Continue Payload's numeric suffix convention when the conflict exists only in ImageKit.
|
|
14
|
+
const nextFilename = (filename)=>{
|
|
15
|
+
const { name, ext } = posix.parse(filename);
|
|
16
|
+
const suffix = /^(.*)-(\d+)$/.exec(name);
|
|
17
|
+
return `${suffix ? `${suffix[1]}-${Number(suffix[2]) + 1}` : `${name}-1`}${ext}`;
|
|
18
|
+
};
|
|
19
|
+
export const createImageKitUploadAuthHandler = ({ access = defaultAccess, collections, privateKey, publicKey, verifyClientUpload })=>{
|
|
3
20
|
return async (req)=>{
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return Response.json({
|
|
7
|
-
message: 'Collection not found.'
|
|
8
|
-
}, {
|
|
9
|
-
status: 404
|
|
10
|
-
});
|
|
21
|
+
if (!req.json) {
|
|
22
|
+
throw new APIError('Content-Type expected to be application/json', 400);
|
|
11
23
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
const body = await req.json();
|
|
25
|
+
const { action, collectionSlug, fileId, signedReceipt } = body;
|
|
26
|
+
let { filename, filesize, mimeType } = body;
|
|
27
|
+
if (typeof collectionSlug !== 'string' || !Object.hasOwn(collections, collectionSlug)) {
|
|
28
|
+
throw new APIError('Invalid upload payload.', 400);
|
|
29
|
+
}
|
|
30
|
+
await assertClientUploadAccess({
|
|
31
|
+
collectionSlug,
|
|
32
|
+
req
|
|
33
|
+
});
|
|
34
|
+
const imageKitCollection = collections[collectionSlug];
|
|
35
|
+
if (!imageKitCollection) {
|
|
36
|
+
throw new APIError(`Collection ${collectionSlug} was not found in ImageKit options.`, 400);
|
|
18
37
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
if (!await access({
|
|
39
|
+
collectionSlug,
|
|
40
|
+
req
|
|
41
|
+
})) {
|
|
42
|
+
throw new Forbidden(req.t);
|
|
43
|
+
}
|
|
44
|
+
if (action === 'complete') {
|
|
45
|
+
if (typeof fileId !== 'string' || !fileId.trim() || typeof signedReceipt !== 'string') {
|
|
46
|
+
throw new APIError('Invalid ImageKit upload completion payload.', 400);
|
|
47
|
+
}
|
|
48
|
+
const receipt = verifyClientUploadReceipt({
|
|
49
|
+
collectionSlug,
|
|
50
|
+
req,
|
|
51
|
+
signedReceipt
|
|
52
|
+
});
|
|
53
|
+
if (!isImageKitReceiptContext(receipt.context) || receipt.filename !== receipt.context.filename || receipt.context.fileId !== undefined && receipt.context.fileId !== fileId) {
|
|
54
|
+
throw new APIError('Invalid ImageKit client upload context.', 400);
|
|
55
|
+
}
|
|
56
|
+
// The browser supplies a lookup ID, not proof. Check provider state against the
|
|
57
|
+
// original signed authorization before including that ID in a new signed receipt.
|
|
58
|
+
const context = {
|
|
59
|
+
...receipt.context,
|
|
60
|
+
fileId
|
|
61
|
+
};
|
|
62
|
+
await verifyClientUpload({
|
|
63
|
+
collectionSlug,
|
|
64
|
+
context
|
|
65
|
+
});
|
|
66
|
+
delete context.uploadTag;
|
|
67
|
+
const response = {
|
|
68
|
+
clientUploadContext: {
|
|
69
|
+
_objectKey: context._objectKey,
|
|
70
|
+
prefix: context.prefix,
|
|
71
|
+
signedReceipt: createClientUploadReceipt({
|
|
72
|
+
collectionSlug,
|
|
73
|
+
context,
|
|
74
|
+
filename: context.filename,
|
|
75
|
+
req
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
return Response.json(response, {
|
|
25
80
|
headers: {
|
|
26
81
|
'Cache-Control': 'no-store'
|
|
27
82
|
}
|
|
28
83
|
});
|
|
29
84
|
}
|
|
30
|
-
|
|
85
|
+
if (action === 'retry') {
|
|
86
|
+
if (typeof signedReceipt !== 'string') {
|
|
87
|
+
throw new APIError('Invalid ImageKit upload retry payload.', 400);
|
|
88
|
+
}
|
|
89
|
+
const receipt = verifyClientUploadReceipt({
|
|
90
|
+
collectionSlug,
|
|
91
|
+
req,
|
|
92
|
+
signedReceipt
|
|
93
|
+
});
|
|
94
|
+
if (!isImageKitReceiptContext(receipt.context) || receipt.filename !== receipt.context.filename || receipt.context.fileId !== undefined) {
|
|
95
|
+
throw new APIError('Invalid ImageKit upload retry payload.', 400);
|
|
96
|
+
}
|
|
97
|
+
filename = nextFilename(receipt.context.filename);
|
|
98
|
+
filesize = receipt.context.expectedSize;
|
|
99
|
+
mimeType = receipt.context.expectedMimeType;
|
|
100
|
+
}
|
|
101
|
+
if (action !== undefined && action !== 'retry' || typeof filename !== 'string' || typeof mimeType !== 'string') {
|
|
102
|
+
throw new APIError('Invalid upload payload.', 400);
|
|
103
|
+
}
|
|
104
|
+
assertClientUploadAllowed({
|
|
105
|
+
collection: req.payload.collections[collectionSlug]?.config,
|
|
106
|
+
filename,
|
|
107
|
+
mimeType
|
|
108
|
+
});
|
|
109
|
+
assertClientUploadFileSize(filesize);
|
|
110
|
+
const requestedSize = filesize;
|
|
111
|
+
const expectedMimeType = getMimeTypeEssence(mimeType);
|
|
112
|
+
if (!expectedMimeType) {
|
|
113
|
+
throw new APIError('A valid MIME type is required for ImageKit client uploads.', 400);
|
|
114
|
+
}
|
|
115
|
+
const collectionConfig = req.payload.collections[collectionSlug]?.config;
|
|
116
|
+
const allowedMimeTypes = collectionConfig && typeof collectionConfig.upload === 'object' && Array.isArray(collectionConfig.upload.mimeTypes) ? collectionConfig.upload.mimeTypes : [];
|
|
117
|
+
if (allowedMimeTypes.length > 0 && !validateMimeType(expectedMimeType, allowedMimeTypes)) {
|
|
118
|
+
throw new APIError(`File type ${expectedMimeType} is not allowed.`, 400);
|
|
119
|
+
}
|
|
120
|
+
const filesizeLimit = req.payload.config.upload.limits?.fileSize;
|
|
121
|
+
if (typeof filesizeLimit === 'number' && Number.isFinite(filesizeLimit) && requestedSize > filesizeLimit) {
|
|
122
|
+
throw new APIError(`Exceeded file size limit. Limit: ${bytesToMB(filesizeLimit)}MB, got: ${bytesToMB(requestedSize)}MB`, 400);
|
|
123
|
+
}
|
|
124
|
+
const sanitizedFilename = await getSafeFileName({
|
|
31
125
|
collectionSlug,
|
|
32
|
-
desiredFilename:
|
|
33
|
-
req
|
|
34
|
-
|
|
126
|
+
desiredFilename: getImageKitSafeFilename(filename),
|
|
127
|
+
req
|
|
128
|
+
});
|
|
129
|
+
const resolved = buildUploadStoragePathData({
|
|
130
|
+
collectionPrefix: imageKitCollection.prefix,
|
|
131
|
+
docPrefix: imageKitCollection.prefix,
|
|
132
|
+
filename: sanitizedFilename
|
|
35
133
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
134
|
+
const folder = `/${resolved.storageFilePath.slice(0, -(sanitizedFilename.length + 1))}`;
|
|
135
|
+
const uploadTag = `payload-upload-${randomUUID()}`;
|
|
136
|
+
const uploadPayload = {
|
|
137
|
+
checks: `"file.size" = ${requestedSize} AND "file.mime" = "${expectedMimeType}"`,
|
|
138
|
+
fileName: sanitizedFilename,
|
|
139
|
+
folder,
|
|
140
|
+
isPrivateFile: imageKitCollection.isPrivate ? 'true' : 'false',
|
|
141
|
+
overwriteFile: 'false',
|
|
142
|
+
tags: uploadTag,
|
|
143
|
+
useUniqueFileName: 'false'
|
|
144
|
+
};
|
|
145
|
+
const receiptContext = {
|
|
146
|
+
collectionSlug,
|
|
147
|
+
expectedMimeType,
|
|
148
|
+
expectedSize: requestedSize,
|
|
149
|
+
filename: sanitizedFilename,
|
|
150
|
+
isPrivate: imageKitCollection.isPrivate,
|
|
151
|
+
prefix: resolved.sanitizedDocPrefix,
|
|
152
|
+
uploadTag
|
|
153
|
+
};
|
|
154
|
+
const response = {
|
|
155
|
+
clientUploadContext: {
|
|
156
|
+
prefix: resolved.sanitizedDocPrefix,
|
|
157
|
+
signedReceipt: createClientUploadReceipt({
|
|
158
|
+
collectionSlug,
|
|
159
|
+
context: receiptContext,
|
|
160
|
+
filename: sanitizedFilename,
|
|
161
|
+
req
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
filename: sanitizedFilename,
|
|
165
|
+
token: createImageKitUploadToken({
|
|
166
|
+
privateKey,
|
|
167
|
+
publicKey,
|
|
168
|
+
uploadPayload
|
|
169
|
+
}),
|
|
170
|
+
uploadPayload
|
|
171
|
+
};
|
|
172
|
+
return Response.json(response, {
|
|
40
173
|
headers: {
|
|
41
174
|
'Cache-Control': 'no-store'
|
|
42
175
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/createImageKitUploadAuthHandler.ts"],"sourcesContent":["import type ImageKit from '@imagekit/nodejs'\nimport type { PayloadHandler, UploadCollectionSlug } from 'payload'\n\nimport { getSafeFileName } from 'payload/internal'\n\ntype CreateImageKitUploadAuthHandlerArgs = {\n client: ImageKit\n collections: Partial<Record<UploadCollectionSlug, unknown>>\n}\n\nexport const createImageKitUploadAuthHandler = ({\n client,\n collections,\n}: CreateImageKitUploadAuthHandlerArgs): PayloadHandler => {\n return async (req) => {\n const collectionSlug = req.routeParams?.collection\n\n if (typeof collectionSlug !== 'string' || !(collectionSlug in collections)) {\n return Response.json({ message: 'Collection not found.' }, { status: 404 })\n }\n\n if (!req.user) {\n return Response.json({ message: 'Unauthorized.' }, { status: 401 })\n }\n\n const authenticationParameters = client.helper.getAuthenticationParameters()\n const requestURL = new URL(req.url || 'http://localhost')\n const requestedFilename = requestURL.searchParams.get('filename')?.trim()\n const collection = req.payload.collections[collectionSlug]\n\n if (\n !requestedFilename ||\n typeof collection?.config.upload !== 'object' ||\n typeof collection.config.upload.staticDir !== 'string'\n ) {\n return Response.json(authenticationParameters, {\n headers: {\n 'Cache-Control': 'no-store',\n },\n })\n }\n\n const safeFilename = await getSafeFileName({\n collectionSlug,\n desiredFilename: requestedFilename,\n req,\n staticPath: collection.config.upload.staticDir,\n })\n\n return Response.json(\n { ...authenticationParameters, fileName: safeFilename },\n {\n headers: {\n 'Cache-Control': 'no-store',\n },\n },\n )\n }\n}\n"],"names":["getSafeFileName","createImageKitUploadAuthHandler","client","collections","req","collectionSlug","routeParams","collection","Response","json","message","status","user","authenticationParameters","helper","getAuthenticationParameters","requestURL","URL","url","requestedFilename","searchParams","get","trim","payload","config","upload","staticDir","headers","safeFilename","desiredFilename","staticPath","fileName"],"mappings":"AAGA,SAASA,eAAe,QAAQ,mBAAkB;AAOlD,OAAO,MAAMC,kCAAkC,CAAC,EAC9CC,MAAM,EACNC,WAAW,EACyB;IACpC,OAAO,OAAOC;QACZ,MAAMC,iBAAiBD,IAAIE,WAAW,EAAEC;QAExC,IAAI,OAAOF,mBAAmB,YAAY,CAAEA,CAAAA,kBAAkBF,WAAU,GAAI;YAC1E,OAAOK,SAASC,IAAI,CAAC;gBAAEC,SAAS;YAAwB,GAAG;gBAAEC,QAAQ;YAAI;QAC3E;QAEA,IAAI,CAACP,IAAIQ,IAAI,EAAE;YACb,OAAOJ,SAASC,IAAI,CAAC;gBAAEC,SAAS;YAAgB,GAAG;gBAAEC,QAAQ;YAAI;QACnE;QAEA,MAAME,2BAA2BX,OAAOY,MAAM,CAACC,2BAA2B;QAC1E,MAAMC,aAAa,IAAIC,IAAIb,IAAIc,GAAG,IAAI;QACtC,MAAMC,oBAAoBH,WAAWI,YAAY,CAACC,GAAG,CAAC,aAAaC;QACnE,MAAMf,aAAaH,IAAImB,OAAO,CAACpB,WAAW,CAACE,eAAe;QAE1D,IACE,CAACc,qBACD,OAAOZ,YAAYiB,OAAOC,WAAW,YACrC,OAAOlB,WAAWiB,MAAM,CAACC,MAAM,CAACC,SAAS,KAAK,UAC9C;YACA,OAAOlB,SAASC,IAAI,CAACI,0BAA0B;gBAC7Cc,SAAS;oBACP,iBAAiB;gBACnB;YACF;QACF;QAEA,MAAMC,eAAe,MAAM5B,gBAAgB;YACzCK;YACAwB,iBAAiBV;YACjBf;YACA0B,YAAYvB,WAAWiB,MAAM,CAACC,MAAM,CAACC,SAAS;QAChD;QAEA,OAAOlB,SAASC,IAAI,CAClB;YAAE,GAAGI,wBAAwB;YAAEkB,UAAUH;QAAa,GACtD;YACED,SAAS;gBACP,iBAAiB;YACnB;QACF;IAEJ;AACF,EAAC"}
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/createImageKitUploadAuthHandler.ts"],"sourcesContent":["import type { ClientUploadsAccess } from '@payloadcms/plugin-cloud-storage/types'\nimport type { PayloadHandler, UploadCollectionSlug } from 'payload'\n\nimport { buildUploadStoragePathData } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { randomUUID } from 'node:crypto'\nimport { posix } from 'node:path'\nimport { APIError, Forbidden } from 'payload'\nimport {\n assertClientUploadAccess,\n assertClientUploadAllowed,\n assertClientUploadFileSize,\n createClientUploadReceipt,\n getSafeFileName,\n verifyClientUploadReceipt,\n} from 'payload/internal'\nimport { getSanitizedUploadFilename, validateMimeType } from 'payload/shared'\n\nimport type {\n ImageKitReceiptContext,\n ImageKitUploadAuthorization,\n ImageKitUploadCompletion,\n ImageKitUploadPayload,\n} from '../clientUploads.js'\nimport type { createImageKitClientUploadVerifier } from '../clientUploadVerification.js'\n\nimport { isImageKitReceiptContext } from '../clientUploads.js'\nimport { createImageKitUploadToken } from '../imageKitUploadToken.js'\n\ntype ImageKitUploadCollection = {\n isPrivate: boolean\n prefix: string\n}\n\ntype CreateImageKitUploadAuthHandlerArgs = {\n access?: ClientUploadsAccess\n collections: Partial<Record<UploadCollectionSlug, ImageKitUploadCollection>>\n privateKey: string\n publicKey: string\n verifyClientUpload: ReturnType<typeof createImageKitClientUploadVerifier>\n}\n\nconst defaultAccess: ClientUploadsAccess = ({ req }) => Boolean(req.user)\n\nconst bytesToMB = (bytes: number): string => (bytes / 1024 / 1024).toFixed(2)\n\nconst getMimeTypeEssence = (mimeType: string): string =>\n (mimeType.split(';', 1)[0] || '').trim().toLowerCase()\n\nconst getImageKitSafeFilename = (filename: string): string =>\n getSanitizedUploadFilename(filename).replace(/[^\\w.-]/g, '_')\n\n// Continue Payload's numeric suffix convention when the conflict exists only in ImageKit.\nconst nextFilename = (filename: string): string => {\n const { name, ext } = posix.parse(filename)\n const suffix = /^(.*)-(\\d+)$/.exec(name)\n return `${suffix ? `${suffix[1]}-${Number(suffix[2]) + 1}` : `${name}-1`}${ext}`\n}\n\nexport const createImageKitUploadAuthHandler = ({\n access = defaultAccess,\n collections,\n privateKey,\n publicKey,\n verifyClientUpload,\n}: CreateImageKitUploadAuthHandlerArgs): PayloadHandler => {\n return async (req) => {\n if (!req.json) {\n throw new APIError('Content-Type expected to be application/json', 400)\n }\n\n const body = (await req.json()) as {\n action?: unknown\n collectionSlug?: unknown\n fileId?: unknown\n filename?: unknown\n filesize?: unknown\n mimeType?: unknown\n signedReceipt?: unknown\n }\n const { action, collectionSlug, fileId, signedReceipt } = body\n let { filename, filesize, mimeType } = body\n\n if (typeof collectionSlug !== 'string' || !Object.hasOwn(collections, collectionSlug)) {\n throw new APIError('Invalid upload payload.', 400)\n }\n\n await assertClientUploadAccess({ collectionSlug, req })\n\n const imageKitCollection = collections[collectionSlug]\n if (!imageKitCollection) {\n throw new APIError(`Collection ${collectionSlug} was not found in ImageKit options.`, 400)\n }\n\n if (!(await access({ collectionSlug, req }))) {\n throw new Forbidden(req.t)\n }\n\n if (action === 'complete') {\n if (typeof fileId !== 'string' || !fileId.trim() || typeof signedReceipt !== 'string') {\n throw new APIError('Invalid ImageKit upload completion payload.', 400)\n }\n const receipt = verifyClientUploadReceipt({ collectionSlug, req, signedReceipt })\n if (\n !isImageKitReceiptContext(receipt.context) ||\n receipt.filename !== receipt.context.filename ||\n (receipt.context.fileId !== undefined && receipt.context.fileId !== fileId)\n ) {\n throw new APIError('Invalid ImageKit client upload context.', 400)\n }\n\n // The browser supplies a lookup ID, not proof. Check provider state against the\n // original signed authorization before including that ID in a new signed receipt.\n const context: ImageKitReceiptContext = { ...receipt.context, fileId }\n await verifyClientUpload({ collectionSlug, context })\n delete context.uploadTag\n const response: ImageKitUploadCompletion = {\n clientUploadContext: {\n _objectKey: context._objectKey,\n prefix: context.prefix,\n signedReceipt: createClientUploadReceipt({\n collectionSlug,\n context,\n filename: context.filename,\n req,\n }),\n },\n }\n return Response.json(response, { headers: { 'Cache-Control': 'no-store' } })\n }\n\n if (action === 'retry') {\n if (typeof signedReceipt !== 'string') {\n throw new APIError('Invalid ImageKit upload retry payload.', 400)\n }\n const receipt = verifyClientUploadReceipt({ collectionSlug, req, signedReceipt })\n if (\n !isImageKitReceiptContext(receipt.context) ||\n receipt.filename !== receipt.context.filename ||\n receipt.context.fileId !== undefined\n ) {\n throw new APIError('Invalid ImageKit upload retry payload.', 400)\n }\n filename = nextFilename(receipt.context.filename)\n filesize = receipt.context.expectedSize\n mimeType = receipt.context.expectedMimeType\n }\n\n if (\n (action !== undefined && action !== 'retry') ||\n typeof filename !== 'string' ||\n typeof mimeType !== 'string'\n ) {\n throw new APIError('Invalid upload payload.', 400)\n }\n\n assertClientUploadAllowed({\n collection: req.payload.collections[collectionSlug]?.config,\n filename,\n mimeType,\n })\n assertClientUploadFileSize(filesize)\n\n const requestedSize = filesize as number\n const expectedMimeType = getMimeTypeEssence(mimeType)\n if (!expectedMimeType) {\n throw new APIError('A valid MIME type is required for ImageKit client uploads.', 400)\n }\n\n const collectionConfig = req.payload.collections[collectionSlug]?.config\n const allowedMimeTypes =\n collectionConfig &&\n typeof collectionConfig.upload === 'object' &&\n Array.isArray(collectionConfig.upload.mimeTypes)\n ? collectionConfig.upload.mimeTypes\n : []\n\n if (allowedMimeTypes.length > 0 && !validateMimeType(expectedMimeType, allowedMimeTypes)) {\n throw new APIError(`File type ${expectedMimeType} is not allowed.`, 400)\n }\n\n const filesizeLimit = req.payload.config.upload.limits?.fileSize\n\n if (\n typeof filesizeLimit === 'number' &&\n Number.isFinite(filesizeLimit) &&\n requestedSize > filesizeLimit\n ) {\n throw new APIError(\n `Exceeded file size limit. Limit: ${bytesToMB(filesizeLimit)}MB, got: ${bytesToMB(requestedSize)}MB`,\n 400,\n )\n }\n\n const sanitizedFilename = await getSafeFileName({\n collectionSlug,\n desiredFilename: getImageKitSafeFilename(filename),\n req,\n })\n const resolved = buildUploadStoragePathData({\n collectionPrefix: imageKitCollection.prefix,\n docPrefix: imageKitCollection.prefix,\n filename: sanitizedFilename,\n })\n const folder = `/${resolved.storageFilePath.slice(0, -(sanitizedFilename.length + 1))}`\n const uploadTag = `payload-upload-${randomUUID()}`\n const uploadPayload: ImageKitUploadPayload = {\n checks: `\"file.size\" = ${requestedSize} AND \"file.mime\" = \"${expectedMimeType}\"`,\n fileName: sanitizedFilename,\n folder,\n isPrivateFile: imageKitCollection.isPrivate ? 'true' : 'false',\n overwriteFile: 'false',\n tags: uploadTag,\n useUniqueFileName: 'false',\n }\n const receiptContext: ImageKitReceiptContext = {\n collectionSlug,\n expectedMimeType,\n expectedSize: requestedSize,\n filename: sanitizedFilename,\n isPrivate: imageKitCollection.isPrivate,\n prefix: resolved.sanitizedDocPrefix,\n uploadTag,\n }\n const response: ImageKitUploadAuthorization = {\n clientUploadContext: {\n prefix: resolved.sanitizedDocPrefix,\n signedReceipt: createClientUploadReceipt({\n collectionSlug,\n context: receiptContext,\n filename: sanitizedFilename,\n req,\n }),\n },\n filename: sanitizedFilename,\n token: createImageKitUploadToken({ privateKey, publicKey, uploadPayload }),\n uploadPayload,\n }\n\n return Response.json(response, {\n headers: {\n 'Cache-Control': 'no-store',\n },\n })\n }\n}\n"],"names":["buildUploadStoragePathData","randomUUID","posix","APIError","Forbidden","assertClientUploadAccess","assertClientUploadAllowed","assertClientUploadFileSize","createClientUploadReceipt","getSafeFileName","verifyClientUploadReceipt","getSanitizedUploadFilename","validateMimeType","isImageKitReceiptContext","createImageKitUploadToken","defaultAccess","req","Boolean","user","bytesToMB","bytes","toFixed","getMimeTypeEssence","mimeType","split","trim","toLowerCase","getImageKitSafeFilename","filename","replace","nextFilename","name","ext","parse","suffix","exec","Number","createImageKitUploadAuthHandler","access","collections","privateKey","publicKey","verifyClientUpload","json","body","action","collectionSlug","fileId","signedReceipt","filesize","Object","hasOwn","imageKitCollection","t","receipt","context","undefined","uploadTag","response","clientUploadContext","_objectKey","prefix","Response","headers","expectedSize","expectedMimeType","collection","payload","config","requestedSize","collectionConfig","allowedMimeTypes","upload","Array","isArray","mimeTypes","length","filesizeLimit","limits","fileSize","isFinite","sanitizedFilename","desiredFilename","resolved","collectionPrefix","docPrefix","folder","storageFilePath","slice","uploadPayload","checks","fileName","isPrivateFile","isPrivate","overwriteFile","tags","useUniqueFileName","receiptContext","sanitizedDocPrefix","token"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ImageKitUploadPayload } from './clientUploads.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the ImageKit Upload API V2 JWT. ImageKit authenticates the token with
|
|
4
|
+
* HS256 and compares every non-standard claim with the multipart upload payload.
|
|
5
|
+
* The `file` and `token` fields are the only upload fields intentionally absent.
|
|
6
|
+
*/
|
|
7
|
+
export declare const createImageKitUploadToken: ({ now, privateKey, publicKey, uploadPayload, }: {
|
|
8
|
+
now?: number;
|
|
9
|
+
privateKey: string;
|
|
10
|
+
publicKey: string;
|
|
11
|
+
uploadPayload: ImageKitUploadPayload;
|
|
12
|
+
}) => string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
2
|
+
const IMAGEKIT_UPLOAD_TOKEN_LIFETIME_SECONDS = 10 * 60;
|
|
3
|
+
const encode = (value)=>Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
4
|
+
/**
|
|
5
|
+
* Creates the ImageKit Upload API V2 JWT. ImageKit authenticates the token with
|
|
6
|
+
* HS256 and compares every non-standard claim with the multipart upload payload.
|
|
7
|
+
* The `file` and `token` fields are the only upload fields intentionally absent.
|
|
8
|
+
*/ export const createImageKitUploadToken = ({ now = Date.now(), privateKey, publicKey, uploadPayload })=>{
|
|
9
|
+
const issuedAt = Math.floor(now / 1000);
|
|
10
|
+
const header = encode({
|
|
11
|
+
alg: 'HS256',
|
|
12
|
+
kid: publicKey,
|
|
13
|
+
typ: 'JWT'
|
|
14
|
+
});
|
|
15
|
+
const payload = encode({
|
|
16
|
+
...uploadPayload,
|
|
17
|
+
exp: issuedAt + IMAGEKIT_UPLOAD_TOKEN_LIFETIME_SECONDS,
|
|
18
|
+
iat: issuedAt
|
|
19
|
+
});
|
|
20
|
+
const unsignedToken = `${header}.${payload}`;
|
|
21
|
+
const signature = createHmac('sha256', privateKey).update(unsignedToken).digest('base64url');
|
|
22
|
+
return `${unsignedToken}.${signature}`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=imageKitUploadToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/imageKitUploadToken.ts"],"sourcesContent":["import { createHmac } from 'node:crypto'\n\nimport type { ImageKitUploadPayload } from './clientUploads.js'\n\nconst IMAGEKIT_UPLOAD_TOKEN_LIFETIME_SECONDS = 10 * 60\n\nconst encode = (value: unknown): string =>\n Buffer.from(JSON.stringify(value)).toString('base64url')\n\n/**\n * Creates the ImageKit Upload API V2 JWT. ImageKit authenticates the token with\n * HS256 and compares every non-standard claim with the multipart upload payload.\n * The `file` and `token` fields are the only upload fields intentionally absent.\n */\nexport const createImageKitUploadToken = ({\n now = Date.now(),\n privateKey,\n publicKey,\n uploadPayload,\n}: {\n now?: number\n privateKey: string\n publicKey: string\n uploadPayload: ImageKitUploadPayload\n}): string => {\n const issuedAt = Math.floor(now / 1000)\n const header = encode({ alg: 'HS256', kid: publicKey, typ: 'JWT' })\n const payload = encode({\n ...uploadPayload,\n exp: issuedAt + IMAGEKIT_UPLOAD_TOKEN_LIFETIME_SECONDS,\n iat: issuedAt,\n })\n const unsignedToken = `${header}.${payload}`\n const signature = createHmac('sha256', privateKey).update(unsignedToken).digest('base64url')\n\n return `${unsignedToken}.${signature}`\n}\n"],"names":["createHmac","IMAGEKIT_UPLOAD_TOKEN_LIFETIME_SECONDS","encode","value","Buffer","from","JSON","stringify","toString","createImageKitUploadToken","now","Date","privateKey","publicKey","uploadPayload","issuedAt","Math","floor","header","alg","kid","typ","payload","exp","iat","unsignedToken","signature","update","digest"],"mappings":"AAAA,SAASA,UAAU,QAAQ,cAAa;AAIxC,MAAMC,yCAAyC,KAAK;AAEpD,MAAMC,SAAS,CAACC,QACdC,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACJ,QAAQK,QAAQ,CAAC;AAE9C;;;;CAIC,GACD,OAAO,MAAMC,4BAA4B,CAAC,EACxCC,MAAMC,KAAKD,GAAG,EAAE,EAChBE,UAAU,EACVC,SAAS,EACTC,aAAa,EAMd;IACC,MAAMC,WAAWC,KAAKC,KAAK,CAACP,MAAM;IAClC,MAAMQ,SAAShB,OAAO;QAAEiB,KAAK;QAASC,KAAKP;QAAWQ,KAAK;IAAM;IACjE,MAAMC,UAAUpB,OAAO;QACrB,GAAGY,aAAa;QAChBS,KAAKR,WAAWd;QAChBuB,KAAKT;IACP;IACA,MAAMU,gBAAgB,GAAGP,OAAO,CAAC,EAAEI,SAAS;IAC5C,MAAMI,YAAY1B,WAAW,UAAUY,YAAYe,MAAM,CAACF,eAAeG,MAAM,CAAC;IAEhF,OAAO,GAAGH,cAAc,CAAC,EAAEC,WAAW;AACxC,EAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ClientUploadsConfig } from '@payloadcms/plugin-cloud-storage/types';
|
|
1
2
|
import type { Config, UploadCollectionSlug } from 'payload';
|
|
2
3
|
export type ImageKitCollectionOptions = {
|
|
3
4
|
/**
|
|
@@ -15,7 +16,7 @@ type ImageKitCollectionConfig = ImageKitCollectionOptions | true;
|
|
|
15
16
|
type ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>;
|
|
16
17
|
export type PayloadStorageImageKitConfig = {
|
|
17
18
|
alwaysInsertFields?: boolean;
|
|
18
|
-
clientUploads?:
|
|
19
|
+
clientUploads?: ClientUploadsConfig;
|
|
19
20
|
collections: ImageKitCollectionMap;
|
|
20
21
|
disablePayloadAccessControl?: true;
|
|
21
22
|
enabled?: boolean;
|