@payloadcms/storage-vercel-blob 3.91.0-internal.c1569dc → 4.0.0-beta.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/LICENSE.md +1 -1
- package/README.md +11 -9
- package/dist/adapter.d.ts +3 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +109 -10
- package/dist/adapter.js.map +1 -1
- package/dist/authorizeFileOverwrite.d.ts +8 -6
- package/dist/authorizeFileOverwrite.d.ts.map +1 -1
- package/dist/authorizeFileOverwrite.js +9 -8
- package/dist/authorizeFileOverwrite.js.map +1 -1
- package/dist/client/VercelBlobClientUploadHandler.d.ts +3 -8
- package/dist/client/VercelBlobClientUploadHandler.d.ts.map +1 -1
- package/dist/client/VercelBlobClientUploadHandler.js +9 -37
- package/dist/client/VercelBlobClientUploadHandler.js.map +1 -1
- package/dist/copyFile.d.ts +12 -0
- package/dist/copyFile.d.ts.map +1 -0
- package/dist/copyFile.js +86 -0
- package/dist/copyFile.js.map +1 -0
- package/dist/generateURL.d.ts +1 -1
- package/dist/generateURL.d.ts.map +1 -1
- package/dist/generateURL.js +6 -6
- package/dist/generateURL.js.map +1 -1
- package/dist/getCollectionSources.d.ts +20 -0
- package/dist/getCollectionSources.d.ts.map +1 -0
- package/dist/getCollectionSources.js +27 -0
- package/dist/getCollectionSources.js.map +1 -0
- package/dist/getFile.d.ts +4 -3
- package/dist/getFile.d.ts.map +1 -1
- package/dist/getFile.js +16 -10
- package/dist/getFile.js.map +1 -1
- package/dist/index.d.ts +6 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +72 -78
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/authorizeFileOverwrite.spec.js +0 -87
- package/dist/authorizeFileOverwrite.spec.js.map +0 -1
- package/dist/getClientUploadRoute.d.ts +0 -16
- package/dist/getClientUploadRoute.d.ts.map +0 -1
- package/dist/getClientUploadRoute.js +0 -149
- package/dist/getClientUploadRoute.js.map +0 -1
- package/dist/staticHandler.spec.js +0 -71
- package/dist/staticHandler.spec.js.map +0 -1
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { authorizeClientOverwrite } from './authorizeFileOverwrite.js';
|
|
3
|
-
const owner = (id)=>({
|
|
4
|
-
filename: 'image.png',
|
|
5
|
-
id,
|
|
6
|
-
prefix: 'shared'
|
|
7
|
-
});
|
|
8
|
-
const createRequest = ({ protectedOwner = false } = {})=>({
|
|
9
|
-
payload: {
|
|
10
|
-
collections: {
|
|
11
|
-
media: {
|
|
12
|
-
config: {
|
|
13
|
-
access: {
|
|
14
|
-
update: async ()=>true
|
|
15
|
-
},
|
|
16
|
-
upload: true
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
protected: {
|
|
20
|
-
config: {
|
|
21
|
-
access: {
|
|
22
|
-
update: async ()=>false
|
|
23
|
-
},
|
|
24
|
-
upload: true
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
find: vi.fn(async ({ collection })=>({
|
|
29
|
-
docs: collection === 'media' ? [
|
|
30
|
-
owner(1)
|
|
31
|
-
] : protectedOwner ? [
|
|
32
|
-
owner(2)
|
|
33
|
-
] : []
|
|
34
|
-
}))
|
|
35
|
-
},
|
|
36
|
-
t: vi.fn()
|
|
37
|
-
});
|
|
38
|
-
describe('authorizeClientOverwrite', ()=>{
|
|
39
|
-
it('should reject a new key outside the requested collection prefix', async ()=>{
|
|
40
|
-
await expect(authorizeClientOverwrite({
|
|
41
|
-
collections: {
|
|
42
|
-
media: {
|
|
43
|
-
prefix: 'media'
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
requestedFilename: 'image.png',
|
|
47
|
-
requestedStorageFilePath: 'media-archive/image.png',
|
|
48
|
-
req: createRequest(),
|
|
49
|
-
requestedCollectionSlug: 'media',
|
|
50
|
-
collectionPrefix: 'media'
|
|
51
|
-
})).rejects.toMatchObject({
|
|
52
|
-
status: 403
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
it('allows an update when the requested collection is the only owner', async ()=>{
|
|
56
|
-
await expect(authorizeClientOverwrite({
|
|
57
|
-
collections: {
|
|
58
|
-
media: true,
|
|
59
|
-
protected: true
|
|
60
|
-
},
|
|
61
|
-
requestedFilename: 'image.png',
|
|
62
|
-
requestedStorageFilePath: 'shared/image.png',
|
|
63
|
-
req: createRequest(),
|
|
64
|
-
requestedCollectionSlug: 'media',
|
|
65
|
-
collectionPrefix: ''
|
|
66
|
-
})).resolves.toBe(true);
|
|
67
|
-
});
|
|
68
|
-
it('rejects an update when another collection owns the same key', async ()=>{
|
|
69
|
-
await expect(authorizeClientOverwrite({
|
|
70
|
-
collections: {
|
|
71
|
-
media: true,
|
|
72
|
-
protected: true
|
|
73
|
-
},
|
|
74
|
-
requestedFilename: 'image.png',
|
|
75
|
-
requestedStorageFilePath: 'shared/image.png',
|
|
76
|
-
req: createRequest({
|
|
77
|
-
protectedOwner: true
|
|
78
|
-
}),
|
|
79
|
-
requestedCollectionSlug: 'media',
|
|
80
|
-
collectionPrefix: ''
|
|
81
|
-
})).rejects.toMatchObject({
|
|
82
|
-
status: 403
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
//# sourceMappingURL=authorizeFileOverwrite.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/authorizeFileOverwrite.spec.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { describe, expect, it, vi } from 'vitest'\n\nimport { authorizeClientOverwrite } from './authorizeFileOverwrite.js'\n\nconst owner = (id: number) => ({ filename: 'image.png', id, prefix: 'shared' })\n\nconst createRequest = ({ protectedOwner = false }: { protectedOwner?: boolean } = {}) =>\n ({\n payload: {\n collections: {\n media: {\n config: { access: { update: async () => true }, upload: true },\n },\n protected: {\n config: { access: { update: async () => false }, upload: true },\n },\n },\n find: vi.fn(async ({ collection }: { collection: string }) => ({\n docs: collection === 'media' ? [owner(1)] : protectedOwner ? [owner(2)] : [],\n })),\n },\n t: vi.fn(),\n }) as unknown as PayloadRequest\n\ndescribe('authorizeClientOverwrite', () => {\n it('should reject a new key outside the requested collection prefix', async () => {\n await expect(\n authorizeClientOverwrite({\n collections: { media: { prefix: 'media' } },\n requestedFilename: 'image.png',\n requestedStorageFilePath: 'media-archive/image.png',\n req: createRequest(),\n requestedCollectionSlug: 'media',\n collectionPrefix: 'media',\n }),\n ).rejects.toMatchObject({ status: 403 })\n })\n\n it('allows an update when the requested collection is the only owner', async () => {\n await expect(\n authorizeClientOverwrite({\n collections: { media: true, protected: true },\n requestedFilename: 'image.png',\n requestedStorageFilePath: 'shared/image.png',\n req: createRequest(),\n requestedCollectionSlug: 'media',\n collectionPrefix: '',\n }),\n ).resolves.toBe(true)\n })\n\n it('rejects an update when another collection owns the same key', async () => {\n await expect(\n authorizeClientOverwrite({\n collections: { media: true, protected: true },\n requestedFilename: 'image.png',\n requestedStorageFilePath: 'shared/image.png',\n req: createRequest({ protectedOwner: true }),\n requestedCollectionSlug: 'media',\n collectionPrefix: '',\n }),\n ).rejects.toMatchObject({ status: 403 })\n })\n})\n"],"names":["describe","expect","it","vi","authorizeClientOverwrite","owner","id","filename","prefix","createRequest","protectedOwner","payload","collections","media","config","access","update","upload","protected","find","fn","collection","docs","t","requestedFilename","requestedStorageFilePath","req","requestedCollectionSlug","collectionPrefix","rejects","toMatchObject","status","resolves","toBe"],"mappings":"AAEA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAEjD,SAASC,wBAAwB,QAAQ,8BAA6B;AAEtE,MAAMC,QAAQ,CAACC,KAAgB,CAAA;QAAEC,UAAU;QAAaD;QAAIE,QAAQ;IAAS,CAAA;AAE7E,MAAMC,gBAAgB,CAAC,EAAEC,iBAAiB,KAAK,EAAgC,GAAG,CAAC,CAAC,GACjF,CAAA;QACCC,SAAS;YACPC,aAAa;gBACXC,OAAO;oBACLC,QAAQ;wBAAEC,QAAQ;4BAAEC,QAAQ,UAAY;wBAAK;wBAAGC,QAAQ;oBAAK;gBAC/D;gBACAC,WAAW;oBACTJ,QAAQ;wBAAEC,QAAQ;4BAAEC,QAAQ,UAAY;wBAAM;wBAAGC,QAAQ;oBAAK;gBAChE;YACF;YACAE,MAAMhB,GAAGiB,EAAE,CAAC,OAAO,EAAEC,UAAU,EAA0B,GAAM,CAAA;oBAC7DC,MAAMD,eAAe,UAAU;wBAAChB,MAAM;qBAAG,GAAGK,iBAAiB;wBAACL,MAAM;qBAAG,GAAG,EAAE;gBAC9E,CAAA;QACF;QACAkB,GAAGpB,GAAGiB,EAAE;IACV,CAAA;AAEFpB,SAAS,4BAA4B;IACnCE,GAAG,mEAAmE;QACpE,MAAMD,OACJG,yBAAyB;YACvBQ,aAAa;gBAAEC,OAAO;oBAAEL,QAAQ;gBAAQ;YAAE;YAC1CgB,mBAAmB;YACnBC,0BAA0B;YAC1BC,KAAKjB;YACLkB,yBAAyB;YACzBC,kBAAkB;QACpB,IACAC,OAAO,CAACC,aAAa,CAAC;YAAEC,QAAQ;QAAI;IACxC;IAEA7B,GAAG,oEAAoE;QACrE,MAAMD,OACJG,yBAAyB;YACvBQ,aAAa;gBAAEC,OAAO;gBAAMK,WAAW;YAAK;YAC5CM,mBAAmB;YACnBC,0BAA0B;YAC1BC,KAAKjB;YACLkB,yBAAyB;YACzBC,kBAAkB;QACpB,IACAI,QAAQ,CAACC,IAAI,CAAC;IAClB;IAEA/B,GAAG,+DAA+D;QAChE,MAAMD,OACJG,yBAAyB;YACvBQ,aAAa;gBAAEC,OAAO;gBAAMK,WAAW;YAAK;YAC5CM,mBAAmB;YACnBC,0BAA0B;YAC1BC,KAAKjB,cAAc;gBAAEC,gBAAgB;YAAK;YAC1CiB,yBAAyB;YACzBC,kBAAkB;QACpB,IACAC,OAAO,CAACC,aAAa,CAAC;YAAEC,QAAQ;QAAI;IACxC;AACF"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { PayloadHandler, PayloadRequest, UploadCollectionSlug } from 'payload';
|
|
2
|
-
import type { VercelBlobCollectionOptions } from './authorizeFileOverwrite.js';
|
|
3
|
-
type Args = {
|
|
4
|
-
access?: (args: {
|
|
5
|
-
collectionSlug: UploadCollectionSlug;
|
|
6
|
-
req: PayloadRequest;
|
|
7
|
-
}) => boolean | Promise<boolean>;
|
|
8
|
-
addRandomSuffix?: boolean;
|
|
9
|
-
cacheControlMaxAge?: number;
|
|
10
|
-
collections: VercelBlobCollectionOptions;
|
|
11
|
-
token: string;
|
|
12
|
-
useCompositePrefixes?: boolean;
|
|
13
|
-
};
|
|
14
|
-
export declare const getClientUploadRoute: ({ access, cacheControlMaxAge, collections, token, useCompositePrefixes, }: Args) => PayloadHandler;
|
|
15
|
-
export {};
|
|
16
|
-
//# sourceMappingURL=getClientUploadRoute.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getClientUploadRoute.d.ts","sourceRoot":"","sources":["../src/getClientUploadRoute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAenF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAI9E,KAAK,IAAI,GAAG;IACV,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE;QACd,cAAc,EAAE,oBAAoB,CAAA;QACpC,GAAG,EAAE,cAAc,CAAA;KACpB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAChC,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,2BAA2B,CAAA;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAID,eAAO,MAAM,oBAAoB,8EAO5B,IAAI,KAAG,cA8JT,CAAA"}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { buildStoragePathData, resolveSignedURLKey, verifyClientUploadReceiptForFileKey } from '@payloadcms/plugin-cloud-storage/utilities';
|
|
2
|
-
import { handleUpload } from '@vercel/blob/client';
|
|
3
|
-
import { APIError, Forbidden } from 'payload';
|
|
4
|
-
import { assertClientUploadAccess, assertClientUploadAllowed, createClientUploadReceipt } from 'payload/internal';
|
|
5
|
-
import { authorizeClientOverwrite } from './authorizeFileOverwrite.js';
|
|
6
|
-
const defaultAccess = ({ req })=>!!req.user;
|
|
7
|
-
export const getClientUploadRoute = ({ access = defaultAccess, cacheControlMaxAge, collections, token, useCompositePrefixes })=>async (req)=>{
|
|
8
|
-
try {
|
|
9
|
-
if (req.searchParams.get('issue-client-upload') === '1') {
|
|
10
|
-
const { collectionSlug, docPrefix, filename, mimeType } = await req.json();
|
|
11
|
-
if (typeof collectionSlug !== 'string' || typeof filename !== 'string' || typeof mimeType !== 'string' || docPrefix !== undefined && typeof docPrefix !== 'string' || !Object.hasOwn(collections, collectionSlug)) {
|
|
12
|
-
throw new APIError('Invalid upload payload', 400);
|
|
13
|
-
}
|
|
14
|
-
await assertClientUploadAccess({
|
|
15
|
-
collectionSlug,
|
|
16
|
-
req
|
|
17
|
-
});
|
|
18
|
-
if (!await access({
|
|
19
|
-
collectionSlug,
|
|
20
|
-
req
|
|
21
|
-
})) {
|
|
22
|
-
throw new Forbidden();
|
|
23
|
-
}
|
|
24
|
-
assertClientUploadAllowed({
|
|
25
|
-
collection: req.payload.collections[collectionSlug]?.config,
|
|
26
|
-
filename,
|
|
27
|
-
mimeType
|
|
28
|
-
});
|
|
29
|
-
const collectionConfig = collections[collectionSlug];
|
|
30
|
-
const collectionPrefix = typeof collectionConfig === 'object' && collectionConfig.prefix || '';
|
|
31
|
-
const requested = buildStoragePathData({
|
|
32
|
-
collectionPrefix,
|
|
33
|
-
docPrefix,
|
|
34
|
-
filename,
|
|
35
|
-
useCompositePrefixes
|
|
36
|
-
});
|
|
37
|
-
const allowOverwrite = await authorizeClientOverwrite({
|
|
38
|
-
collectionPrefix,
|
|
39
|
-
collections,
|
|
40
|
-
req,
|
|
41
|
-
requestedCollectionSlug: collectionSlug,
|
|
42
|
-
requestedFilename: requested.sanitizedFilename,
|
|
43
|
-
requestedStorageFilePath: requested.storageFilePath,
|
|
44
|
-
useCompositePrefixes
|
|
45
|
-
});
|
|
46
|
-
const resolved = allowOverwrite ? {
|
|
47
|
-
...requested,
|
|
48
|
-
clientUploadContext: {
|
|
49
|
-
allowOverwrite: true,
|
|
50
|
-
prefix: requested.sanitizedDocPrefix,
|
|
51
|
-
signedReceipt: createClientUploadReceipt({
|
|
52
|
-
collectionSlug,
|
|
53
|
-
context: {
|
|
54
|
-
allowOverwrite: true,
|
|
55
|
-
prefix: requested.sanitizedDocPrefix
|
|
56
|
-
},
|
|
57
|
-
filename: requested.sanitizedFilename,
|
|
58
|
-
req
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
} : await resolveSignedURLKey({
|
|
62
|
-
collectionPrefix,
|
|
63
|
-
collectionSlug,
|
|
64
|
-
docPrefix,
|
|
65
|
-
filename,
|
|
66
|
-
req,
|
|
67
|
-
useCompositePrefixes
|
|
68
|
-
});
|
|
69
|
-
return Response.json({
|
|
70
|
-
clientUploadContext: resolved.clientUploadContext,
|
|
71
|
-
filename: resolved.sanitizedFilename,
|
|
72
|
-
pathname: resolved.storageFilePath
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
const body = await req.json();
|
|
76
|
-
const jsonResponse = await handleUpload({
|
|
77
|
-
body,
|
|
78
|
-
onBeforeGenerateToken: async (pathname, clientPayload)=>{
|
|
79
|
-
if (!clientPayload) {
|
|
80
|
-
throw new APIError('No payload was provided');
|
|
81
|
-
}
|
|
82
|
-
let parsed;
|
|
83
|
-
try {
|
|
84
|
-
parsed = JSON.parse(clientPayload);
|
|
85
|
-
} catch {
|
|
86
|
-
parsed = {
|
|
87
|
-
collectionSlug: clientPayload
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
if (typeof parsed.collectionSlug !== 'string' || !parsed.collectionSlug || parsed.mimeType !== undefined && typeof parsed.mimeType !== 'string' || typeof parsed.signedReceipt !== 'string') {
|
|
91
|
-
throw new APIError('Invalid upload payload', 400);
|
|
92
|
-
}
|
|
93
|
-
const { collectionSlug, mimeType, signedReceipt } = parsed;
|
|
94
|
-
await assertClientUploadAccess({
|
|
95
|
-
collectionSlug,
|
|
96
|
-
req
|
|
97
|
-
});
|
|
98
|
-
if (!Object.hasOwn(collections, collectionSlug)) {
|
|
99
|
-
throw new APIError('Invalid upload payload', 400);
|
|
100
|
-
}
|
|
101
|
-
if (!await access({
|
|
102
|
-
collectionSlug,
|
|
103
|
-
req
|
|
104
|
-
})) {
|
|
105
|
-
throw new Forbidden();
|
|
106
|
-
}
|
|
107
|
-
const collectionConfig = collections[collectionSlug];
|
|
108
|
-
const collectionPrefix = typeof collectionConfig === 'object' && collectionConfig.prefix || '';
|
|
109
|
-
const receipt = verifyClientUploadReceiptForFileKey({
|
|
110
|
-
collectionPrefix,
|
|
111
|
-
collectionSlug,
|
|
112
|
-
expectedFileKey: pathname,
|
|
113
|
-
req,
|
|
114
|
-
signedReceipt,
|
|
115
|
-
useCompositePrefixes
|
|
116
|
-
});
|
|
117
|
-
assertClientUploadAllowed({
|
|
118
|
-
collection: req.payload.collections[collectionSlug]?.config,
|
|
119
|
-
filename: pathname,
|
|
120
|
-
mimeType
|
|
121
|
-
});
|
|
122
|
-
return {
|
|
123
|
-
addRandomSuffix: false,
|
|
124
|
-
...mimeType ? {
|
|
125
|
-
allowedContentTypes: [
|
|
126
|
-
mimeType
|
|
127
|
-
]
|
|
128
|
-
} : {},
|
|
129
|
-
...receipt.context.allowOverwrite === true && {
|
|
130
|
-
allowOverwrite: true
|
|
131
|
-
},
|
|
132
|
-
cacheControlMaxAge
|
|
133
|
-
};
|
|
134
|
-
},
|
|
135
|
-
onUploadCompleted: async ()=>{},
|
|
136
|
-
request: req,
|
|
137
|
-
token
|
|
138
|
-
});
|
|
139
|
-
return Response.json(jsonResponse);
|
|
140
|
-
} catch (error) {
|
|
141
|
-
if (error instanceof APIError) {
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
144
|
-
req.payload.logger.error(error);
|
|
145
|
-
throw new APIError('storage-vercel-blob client upload route error');
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
//# sourceMappingURL=getClientUploadRoute.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/getClientUploadRoute.ts"],"sourcesContent":["import type { PayloadHandler, PayloadRequest, UploadCollectionSlug } from 'payload'\n\nimport {\n buildStoragePathData,\n resolveSignedURLKey,\n verifyClientUploadReceiptForFileKey,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { handleUpload, type HandleUploadBody } from '@vercel/blob/client'\nimport { APIError, Forbidden } from 'payload'\nimport {\n assertClientUploadAccess,\n assertClientUploadAllowed,\n createClientUploadReceipt,\n} from 'payload/internal'\n\nimport type { VercelBlobCollectionOptions } from './authorizeFileOverwrite.js'\n\nimport { authorizeClientOverwrite } from './authorizeFileOverwrite.js'\n\ntype Args = {\n access?: (args: {\n collectionSlug: UploadCollectionSlug\n req: PayloadRequest\n }) => boolean | Promise<boolean>\n addRandomSuffix?: boolean\n cacheControlMaxAge?: number\n collections: VercelBlobCollectionOptions\n token: string\n useCompositePrefixes?: boolean\n}\n\nconst defaultAccess: Args['access'] = ({ req }) => !!req.user\n\nexport const getClientUploadRoute =\n ({\n access = defaultAccess,\n cacheControlMaxAge,\n collections,\n token,\n useCompositePrefixes,\n }: Args): PayloadHandler =>\n async (req) => {\n try {\n if (req.searchParams.get('issue-client-upload') === '1') {\n const { collectionSlug, docPrefix, filename, mimeType } = (await req.json!()) as {\n collectionSlug?: unknown\n docPrefix?: unknown\n filename?: unknown\n mimeType?: unknown\n }\n if (\n typeof collectionSlug !== 'string' ||\n typeof filename !== 'string' ||\n typeof mimeType !== 'string' ||\n (docPrefix !== undefined && typeof docPrefix !== 'string') ||\n !Object.hasOwn(collections, collectionSlug)\n ) {\n throw new APIError('Invalid upload payload', 400)\n }\n\n await assertClientUploadAccess({ collectionSlug, req })\n\n if (!(await access({ collectionSlug, req }))) {\n throw new Forbidden()\n }\n assertClientUploadAllowed({\n collection: req.payload.collections[collectionSlug]?.config,\n filename,\n mimeType,\n })\n const collectionConfig = collections[collectionSlug]\n const collectionPrefix =\n (typeof collectionConfig === 'object' && collectionConfig.prefix) || ''\n const requested = buildStoragePathData({\n collectionPrefix,\n docPrefix,\n filename,\n useCompositePrefixes,\n })\n const allowOverwrite = await authorizeClientOverwrite({\n collectionPrefix,\n collections,\n req,\n requestedCollectionSlug: collectionSlug,\n requestedFilename: requested.sanitizedFilename,\n requestedStorageFilePath: requested.storageFilePath,\n useCompositePrefixes,\n })\n const resolved = allowOverwrite\n ? {\n ...requested,\n clientUploadContext: {\n allowOverwrite: true,\n prefix: requested.sanitizedDocPrefix,\n signedReceipt: createClientUploadReceipt({\n collectionSlug,\n context: {\n allowOverwrite: true,\n prefix: requested.sanitizedDocPrefix,\n },\n filename: requested.sanitizedFilename,\n req,\n }),\n },\n }\n : await resolveSignedURLKey({\n collectionPrefix,\n collectionSlug,\n docPrefix,\n filename,\n req,\n useCompositePrefixes,\n })\n return Response.json({\n clientUploadContext: resolved.clientUploadContext,\n filename: resolved.sanitizedFilename,\n pathname: resolved.storageFilePath,\n })\n }\n\n const body = (await req.json!()) as HandleUploadBody\n const jsonResponse = await handleUpload({\n body,\n onBeforeGenerateToken: async (pathname: string, clientPayload: null | string) => {\n if (!clientPayload) {\n throw new APIError('No payload was provided')\n }\n\n let parsed: { collectionSlug?: unknown; mimeType?: unknown; signedReceipt?: unknown }\n try {\n parsed = JSON.parse(clientPayload) as {\n collectionSlug?: unknown\n mimeType?: unknown\n signedReceipt?: unknown\n }\n } catch {\n parsed = { collectionSlug: clientPayload }\n }\n\n if (\n typeof parsed.collectionSlug !== 'string' ||\n !parsed.collectionSlug ||\n (parsed.mimeType !== undefined && typeof parsed.mimeType !== 'string') ||\n typeof parsed.signedReceipt !== 'string'\n ) {\n throw new APIError('Invalid upload payload', 400)\n }\n\n const { collectionSlug, mimeType, signedReceipt } = parsed\n\n await assertClientUploadAccess({ collectionSlug, req })\n\n if (!Object.hasOwn(collections, collectionSlug)) {\n throw new APIError('Invalid upload payload', 400)\n }\n\n if (!(await access({ collectionSlug, req }))) {\n throw new Forbidden()\n }\n const collectionConfig = collections[collectionSlug]\n const collectionPrefix =\n (typeof collectionConfig === 'object' && collectionConfig.prefix) || ''\n const receipt = verifyClientUploadReceiptForFileKey({\n collectionPrefix,\n collectionSlug,\n expectedFileKey: pathname,\n req,\n signedReceipt,\n useCompositePrefixes,\n })\n\n assertClientUploadAllowed({\n collection: req.payload.collections[collectionSlug]?.config,\n filename: pathname,\n mimeType,\n })\n\n return {\n addRandomSuffix: false,\n ...(mimeType ? { allowedContentTypes: [mimeType] } : {}),\n ...(receipt.context.allowOverwrite === true && { allowOverwrite: true }),\n cacheControlMaxAge,\n }\n },\n onUploadCompleted: async () => {},\n request: req as Request,\n token,\n })\n\n return Response.json(jsonResponse)\n } catch (error) {\n if (error instanceof APIError) {\n throw error\n }\n\n req.payload.logger.error(error)\n throw new APIError('storage-vercel-blob client upload route error')\n }\n }\n"],"names":["buildStoragePathData","resolveSignedURLKey","verifyClientUploadReceiptForFileKey","handleUpload","APIError","Forbidden","assertClientUploadAccess","assertClientUploadAllowed","createClientUploadReceipt","authorizeClientOverwrite","defaultAccess","req","user","getClientUploadRoute","access","cacheControlMaxAge","collections","token","useCompositePrefixes","searchParams","get","collectionSlug","docPrefix","filename","mimeType","json","undefined","Object","hasOwn","collection","payload","config","collectionConfig","collectionPrefix","prefix","requested","allowOverwrite","requestedCollectionSlug","requestedFilename","sanitizedFilename","requestedStorageFilePath","storageFilePath","resolved","clientUploadContext","sanitizedDocPrefix","signedReceipt","context","Response","pathname","body","jsonResponse","onBeforeGenerateToken","clientPayload","parsed","JSON","parse","receipt","expectedFileKey","addRandomSuffix","allowedContentTypes","onUploadCompleted","request","error","logger"],"mappings":"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"}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { head } from '@vercel/blob';
|
|
3
|
-
vi.mock('@vercel/blob', async (importOriginal)=>({
|
|
4
|
-
...await importOriginal(),
|
|
5
|
-
head: vi.fn(async ()=>({
|
|
6
|
-
contentDisposition: 'inline',
|
|
7
|
-
contentType: 'image/png',
|
|
8
|
-
size: 4,
|
|
9
|
-
uploadedAt: new Date('2026-01-01')
|
|
10
|
-
}))
|
|
11
|
-
}));
|
|
12
|
-
afterEach(()=>{
|
|
13
|
-
vi.unstubAllGlobals();
|
|
14
|
-
vi.clearAllMocks();
|
|
15
|
-
});
|
|
16
|
-
import { createVercelBlobAdapter } from './adapter.js';
|
|
17
|
-
describe('static handler document locations', ()=>{
|
|
18
|
-
it('should read the checked document without another lookup', async ()=>{
|
|
19
|
-
const fetchMock = vi.fn(async ()=>new Response('file'));
|
|
20
|
-
vi.stubGlobal('fetch', fetchMock);
|
|
21
|
-
const adapter = createVercelBlobAdapter({
|
|
22
|
-
access: 'public',
|
|
23
|
-
baseUrl: 'https://example.com',
|
|
24
|
-
cacheControlMaxAge: 60,
|
|
25
|
-
token: 'token'
|
|
26
|
-
});
|
|
27
|
-
const find = vi.fn(async ()=>({
|
|
28
|
-
docs: [
|
|
29
|
-
{
|
|
30
|
-
prefix: 'database'
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
}));
|
|
34
|
-
const generatedAdapter = adapter({
|
|
35
|
-
collection: {
|
|
36
|
-
fields: [],
|
|
37
|
-
slug: 'media',
|
|
38
|
-
upload: {}
|
|
39
|
-
},
|
|
40
|
-
prefix: 'media'
|
|
41
|
-
});
|
|
42
|
-
const doc = {
|
|
43
|
-
id: 'authorized',
|
|
44
|
-
prefix: 'legacy'
|
|
45
|
-
};
|
|
46
|
-
const response = await generatedAdapter.staticHandler({
|
|
47
|
-
headers: new Headers(),
|
|
48
|
-
payload: {
|
|
49
|
-
find,
|
|
50
|
-
logger: {
|
|
51
|
-
error: vi.fn()
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}, {
|
|
55
|
-
doc,
|
|
56
|
-
params: {
|
|
57
|
-
collection: 'media',
|
|
58
|
-
filename: 'original.png'
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
expect(response.status).toBe(200);
|
|
62
|
-
expect(await response.text()).toBe('file');
|
|
63
|
-
expect(head).toHaveBeenCalledWith(`https://example.com/legacy/original.png`, {
|
|
64
|
-
token: 'token'
|
|
65
|
-
});
|
|
66
|
-
expect(fetchMock).toHaveBeenCalledWith(`https://example.com/legacy/original.png?2026-01-01T00:00:00.000Z`, expect.any(Object));
|
|
67
|
-
expect(find).not.toHaveBeenCalled();
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
//# sourceMappingURL=staticHandler.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/staticHandler.spec.ts"],"sourcesContent":["import { afterEach, describe, expect, it, vi } from 'vitest'\n\nimport { head } from '@vercel/blob'\n\nvi.mock('@vercel/blob', async (importOriginal) => ({\n ...(await importOriginal<typeof import('@vercel/blob')>()),\n head: vi.fn(async () => ({\n contentDisposition: 'inline',\n contentType: 'image/png',\n size: 4,\n uploadedAt: new Date('2026-01-01'),\n })),\n}))\n\nafterEach(() => {\n vi.unstubAllGlobals()\n vi.clearAllMocks()\n})\n\nimport { createVercelBlobAdapter } from './adapter.js'\n\ndescribe('static handler document locations', () => {\n it('should read the checked document without another lookup', async () => {\n const fetchMock = vi.fn(async () => new Response('file'))\n\n vi.stubGlobal('fetch', fetchMock)\n const adapter = createVercelBlobAdapter({\n access: 'public',\n baseUrl: 'https://example.com',\n cacheControlMaxAge: 60,\n token: 'token',\n })\n const find = vi.fn(async () => ({ docs: [{ prefix: 'database' }] }))\n const generatedAdapter = adapter({\n collection: {\n fields: [],\n slug: 'media',\n upload: {},\n },\n prefix: 'media',\n })\n const doc = {\n id: 'authorized',\n prefix: 'legacy',\n }\n const response = await generatedAdapter.staticHandler(\n {\n headers: new Headers(),\n payload: { find, logger: { error: vi.fn() } },\n } as never,\n {\n doc,\n params: { collection: 'media', filename: 'original.png' },\n },\n )\n\n expect(response.status).toBe(200)\n expect(await response.text()).toBe('file')\n expect(head).toHaveBeenCalledWith(`https://example.com/legacy/original.png`, { token: 'token' })\n expect(fetchMock).toHaveBeenCalledWith(\n `https://example.com/legacy/original.png?2026-01-01T00:00:00.000Z`,\n expect.any(Object),\n )\n expect(find).not.toHaveBeenCalled()\n })\n})\n"],"names":["afterEach","describe","expect","it","vi","head","mock","importOriginal","fn","contentDisposition","contentType","size","uploadedAt","Date","unstubAllGlobals","clearAllMocks","createVercelBlobAdapter","fetchMock","Response","stubGlobal","adapter","access","baseUrl","cacheControlMaxAge","token","find","docs","prefix","generatedAdapter","collection","fields","slug","upload","doc","id","response","staticHandler","headers","Headers","payload","logger","error","params","filename","status","toBe","text","toHaveBeenCalledWith","any","Object","not","toHaveBeenCalled"],"mappings":"AAAA,SAASA,SAAS,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAE5D,SAASC,IAAI,QAAQ,eAAc;AAEnCD,GAAGE,IAAI,CAAC,gBAAgB,OAAOC,iBAAoB,CAAA;QACjD,GAAI,MAAMA,gBAA+C;QACzDF,MAAMD,GAAGI,EAAE,CAAC,UAAa,CAAA;gBACvBC,oBAAoB;gBACpBC,aAAa;gBACbC,MAAM;gBACNC,YAAY,IAAIC,KAAK;YACvB,CAAA;IACF,CAAA;AAEAb,UAAU;IACRI,GAAGU,gBAAgB;IACnBV,GAAGW,aAAa;AAClB;AAEA,SAASC,uBAAuB,QAAQ,eAAc;AAEtDf,SAAS,qCAAqC;IAC5CE,GAAG,2DAA2D;QAC5D,MAAMc,YAAYb,GAAGI,EAAE,CAAC,UAAY,IAAIU,SAAS;QAEjDd,GAAGe,UAAU,CAAC,SAASF;QACvB,MAAMG,UAAUJ,wBAAwB;YACtCK,QAAQ;YACRC,SAAS;YACTC,oBAAoB;YACpBC,OAAO;QACT;QACA,MAAMC,OAAOrB,GAAGI,EAAE,CAAC,UAAa,CAAA;gBAAEkB,MAAM;oBAAC;wBAAEC,QAAQ;oBAAW;iBAAE;YAAC,CAAA;QACjE,MAAMC,mBAAmBR,QAAQ;YAC/BS,YAAY;gBACVC,QAAQ,EAAE;gBACVC,MAAM;gBACNC,QAAQ,CAAC;YACX;YACAL,QAAQ;QACV;QACA,MAAMM,MAAM;YACVC,IAAI;YACJP,QAAQ;QACV;QACA,MAAMQ,WAAW,MAAMP,iBAAiBQ,aAAa,CACnD;YACEC,SAAS,IAAIC;YACbC,SAAS;gBAAEd;gBAAMe,QAAQ;oBAAEC,OAAOrC,GAAGI,EAAE;gBAAG;YAAE;QAC9C,GACA;YACEyB;YACAS,QAAQ;gBAAEb,YAAY;gBAASc,UAAU;YAAe;QAC1D;QAGFzC,OAAOiC,SAASS,MAAM,EAAEC,IAAI,CAAC;QAC7B3C,OAAO,MAAMiC,SAASW,IAAI,IAAID,IAAI,CAAC;QACnC3C,OAAOG,MAAM0C,oBAAoB,CAAC,CAAC,uCAAuC,CAAC,EAAE;YAAEvB,OAAO;QAAQ;QAC9FtB,OAAOe,WAAW8B,oBAAoB,CACpC,CAAC,gEAAgE,CAAC,EAClE7C,OAAO8C,GAAG,CAACC;QAEb/C,OAAOuB,MAAMyB,GAAG,CAACC,gBAAgB;IACnC;AACF"}
|