@payloadcms/plugin-cloud-storage 4.0.0-canary.32 → 4.0.0-canary.34
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 +2 -0
- package/dist/exports/client.d.ts +1 -1
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +1 -1
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/utilities.d.ts +1 -1
- package/dist/exports/utilities.d.ts.map +1 -1
- package/dist/exports/utilities.js +1 -1
- package/dist/exports/utilities.js.map +1 -1
- package/dist/fields/getFields.d.ts.map +1 -1
- package/dist/fields/getFields.js +25 -1
- package/dist/fields/getFields.js.map +1 -1
- package/dist/hooks/afterChange.d.ts +3 -1
- package/dist/hooks/afterChange.d.ts.map +1 -1
- package/dist/hooks/afterChange.js +51 -8
- package/dist/hooks/afterChange.js.map +1 -1
- package/dist/hooks/afterChange.spec.js +95 -0
- package/dist/hooks/afterChange.spec.js.map +1 -0
- package/dist/hooks/afterDelete.d.ts +3 -1
- package/dist/hooks/afterDelete.d.ts.map +1 -1
- package/dist/hooks/afterDelete.js +16 -2
- package/dist/hooks/afterDelete.js.map +1 -1
- package/dist/hooks/afterRead.d.ts.map +1 -1
- package/dist/hooks/afterRead.js +15 -2
- package/dist/hooks/afterRead.js.map +1 -1
- package/dist/hooks/beforeChange.d.ts.map +1 -1
- package/dist/hooks/beforeChange.js +12 -1
- package/dist/hooks/beforeChange.js.map +1 -1
- package/dist/hooks/normalizeUploadPrefix.d.ts +11 -0
- package/dist/hooks/normalizeUploadPrefix.d.ts.map +1 -0
- package/dist/hooks/normalizeUploadPrefix.js +42 -0
- package/dist/hooks/normalizeUploadPrefix.js.map +1 -0
- package/dist/hooks/normalizeUploadPrefix.spec.js +154 -0
- package/dist/hooks/normalizeUploadPrefix.spec.js.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +14 -6
- package/dist/plugin.js.map +1 -1
- package/dist/plugin.spec.js +166 -0
- package/dist/plugin.spec.js.map +1 -0
- package/dist/types.d.ts +19 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utilities/buildPrefixWithObjectKey.d.ts +8 -0
- package/dist/utilities/buildPrefixWithObjectKey.d.ts.map +1 -0
- package/dist/utilities/buildPrefixWithObjectKey.js +13 -0
- package/dist/utilities/buildPrefixWithObjectKey.js.map +1 -0
- package/dist/utilities/buildStoragePathData.d.ts +49 -0
- package/dist/utilities/buildStoragePathData.d.ts.map +1 -0
- package/dist/utilities/buildStoragePathData.js +67 -0
- package/dist/utilities/buildStoragePathData.js.map +1 -0
- package/dist/utilities/buildStoragePathData.spec.js +241 -0
- package/dist/utilities/buildStoragePathData.spec.js.map +1 -0
- package/dist/utilities/getFilePrefix.d.ts +18 -10
- package/dist/utilities/getFilePrefix.d.ts.map +1 -1
- package/dist/utilities/getFilePrefix.js +69 -29
- package/dist/utilities/getFilePrefix.js.map +1 -1
- package/dist/utilities/getFilePrefix.spec.js +174 -31
- package/dist/utilities/getFilePrefix.spec.js.map +1 -1
- package/dist/utilities/resolveSignedURLKey.d.ts +12 -1
- package/dist/utilities/resolveSignedURLKey.d.ts.map +1 -1
- package/dist/utilities/resolveSignedURLKey.js +42 -8
- package/dist/utilities/resolveSignedURLKey.js.map +1 -1
- package/dist/utilities/resolveSignedURLKey.spec.js +74 -0
- package/dist/utilities/resolveSignedURLKey.spec.js.map +1 -0
- package/dist/utilities/sanitizePrefix.d.ts +1 -9
- package/dist/utilities/sanitizePrefix.d.ts.map +1 -1
- package/dist/utilities/sanitizePrefix.js +1 -21
- package/dist/utilities/sanitizePrefix.js.map +1 -1
- package/package.json +4 -4
- package/dist/utilities/getFileKey.d.ts +0 -22
- package/dist/utilities/getFileKey.d.ts.map +0 -1
- package/dist/utilities/getFileKey.js +0 -23
- package/dist/utilities/getFileKey.js.map +0 -1
- package/dist/utilities/getFileKey.spec.js +0 -174
- package/dist/utilities/getFileKey.spec.js.map +0 -1
|
@@ -1,46 +1,86 @@
|
|
|
1
|
+
import { buildPrefixWithObjectKey } from './buildPrefixWithObjectKey.js';
|
|
2
|
+
import { buildUploadStoragePathData } from './buildStoragePathData.js';
|
|
1
3
|
import { sanitizePrefix } from './sanitizePrefix.js';
|
|
2
4
|
/**
|
|
3
|
-
* Resolves the
|
|
4
|
-
* always returns a sanitized value.
|
|
5
|
+
* Resolves the folder a stored object lives under (semantic `prefix` plus `_objectKey`).
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
* An already-authorized `doc` wins outright. Otherwise an `uploadReference` is used (its
|
|
8
|
+
* `prefix` re-contained with {@link buildUploadStoragePathData}, since it hasn't been verified against
|
|
9
|
+
* a signed receipt by every adapter). Otherwise the document is resolved with
|
|
10
|
+
* `overrideAccess: false`, any query-param prefix only narrows that lookup, and `_objectKey`
|
|
11
|
+
* is read from the resolved document via `showHiddenFields`.
|
|
12
|
+
*/ export async function getFilePrefix({ collection, collectionPrefix, doc, filename, prefixQueryParam, req, uploadReference, useCompositePrefixes = false }) {
|
|
13
|
+
// The serve path already loaded and authorized this document — trust it over any
|
|
14
|
+
// client-supplied upload reference or query prefix.
|
|
15
|
+
if (doc) {
|
|
16
|
+
return buildPrefixWithObjectKey({
|
|
17
|
+
objectKey: doc._objectKey,
|
|
18
|
+
prefix: doc.prefix
|
|
19
|
+
});
|
|
15
20
|
}
|
|
16
|
-
//
|
|
21
|
+
// Upload instructions call handlers without a document yet. Re-contain the claimed
|
|
22
|
+
// prefix — it hasn't been verified against a signed receipt by every adapter.
|
|
17
23
|
if (uploadReference && typeof uploadReference === 'object' && 'prefix' in uploadReference && typeof uploadReference.prefix === 'string') {
|
|
18
|
-
|
|
24
|
+
const referenceObjectKey = '_objectKey' in uploadReference ? uploadReference._objectKey : undefined;
|
|
25
|
+
const containedPrefix = buildUploadStoragePathData({
|
|
26
|
+
collectionPrefix,
|
|
27
|
+
docPrefix: uploadReference.prefix,
|
|
28
|
+
filename,
|
|
29
|
+
useCompositePrefixes
|
|
30
|
+
}).sanitizedDocPrefix;
|
|
31
|
+
return buildPrefixWithObjectKey({
|
|
32
|
+
objectKey: referenceObjectKey,
|
|
33
|
+
prefix: containedPrefix
|
|
34
|
+
});
|
|
19
35
|
}
|
|
36
|
+
// Reads without a query prefix or read-access constraints skip the endpoint's document lookup.
|
|
20
37
|
const imageSizes = collection?.upload?.imageSizes || [];
|
|
38
|
+
const filenameClause = {
|
|
39
|
+
or: [
|
|
40
|
+
{
|
|
41
|
+
filename: {
|
|
42
|
+
equals: filename
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
...imageSizes.map((imageSize)=>({
|
|
46
|
+
[`sizes.${imageSize.name}.filename`]: {
|
|
47
|
+
equals: filename
|
|
48
|
+
}
|
|
49
|
+
}))
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
// Only filter by prefix when the collection persists a `prefix` field (querying a missing field throws).
|
|
53
|
+
const hasPrefixField = collection.fields?.some((field)=>'name' in field && field.name === 'prefix');
|
|
54
|
+
const where = typeof prefixQueryParam === 'string' && hasPrefixField ? {
|
|
55
|
+
and: [
|
|
56
|
+
filenameClause,
|
|
57
|
+
{
|
|
58
|
+
prefix: {
|
|
59
|
+
equals: sanitizePrefix(prefixQueryParam)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
} : filenameClause;
|
|
21
64
|
const files = await req.payload.find({
|
|
22
65
|
collection: collection.slug,
|
|
23
66
|
depth: 0,
|
|
24
67
|
draft: true,
|
|
25
68
|
limit: 1,
|
|
69
|
+
overrideAccess: false,
|
|
26
70
|
pagination: false,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
]
|
|
40
|
-
}
|
|
71
|
+
req,
|
|
72
|
+
select: {
|
|
73
|
+
_objectKey: true,
|
|
74
|
+
prefix: true
|
|
75
|
+
},
|
|
76
|
+
showHiddenFields: true,
|
|
77
|
+
where
|
|
78
|
+
});
|
|
79
|
+
const found = files?.docs?.[0];
|
|
80
|
+
return buildPrefixWithObjectKey({
|
|
81
|
+
objectKey: found?._objectKey,
|
|
82
|
+
prefix: found?.prefix
|
|
41
83
|
});
|
|
42
|
-
const prefix = files?.docs?.[0]?.prefix;
|
|
43
|
-
return prefix ? sanitizePrefix(prefix) : '';
|
|
44
84
|
}
|
|
45
85
|
|
|
46
86
|
//# sourceMappingURL=getFilePrefix.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/getFilePrefix.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest, UploadConfig } from 'payload'\n\nimport { sanitizePrefix } from './sanitizePrefix.js'\n\n/**\n * Resolves the
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/getFilePrefix.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest, TypeWithID, UploadConfig } from 'payload'\n\nimport { buildPrefixWithObjectKey } from './buildPrefixWithObjectKey.js'\nimport { buildUploadStoragePathData } from './buildStoragePathData.js'\nimport { sanitizePrefix } from './sanitizePrefix.js'\n\n/**\n * Resolves the folder a stored object lives under (semantic `prefix` plus `_objectKey`).\n *\n * An already-authorized `doc` wins outright. Otherwise an `uploadReference` is used (its\n * `prefix` re-contained with {@link buildUploadStoragePathData}, since it hasn't been verified against\n * a signed receipt by every adapter). Otherwise the document is resolved with\n * `overrideAccess: false`, any query-param prefix only narrows that lookup, and `_objectKey`\n * is read from the resolved document via `showHiddenFields`.\n */\nexport async function getFilePrefix({\n collection,\n collectionPrefix,\n doc,\n filename,\n prefixQueryParam,\n req,\n uploadReference,\n useCompositePrefixes = false,\n}: {\n collection: CollectionConfig\n collectionPrefix?: string\n doc?: { _objectKey?: string; prefix?: string } & TypeWithID\n filename: string\n /**\n * Only narrows the access-controlled fallback lookup below; never trusted or\n * returned directly. Prefer passing the access-checked document as `doc` instead.\n */\n prefixQueryParam?: string\n req: PayloadRequest\n uploadReference?: unknown\n useCompositePrefixes?: boolean\n}): Promise<string> {\n // The serve path already loaded and authorized this document — trust it over any\n // client-supplied upload reference or query prefix.\n if (doc) {\n return buildPrefixWithObjectKey({ objectKey: doc._objectKey, prefix: doc.prefix })\n }\n\n // Upload instructions call handlers without a document yet. Re-contain the claimed\n // prefix — it hasn't been verified against a signed receipt by every adapter.\n if (\n uploadReference &&\n typeof uploadReference === 'object' &&\n 'prefix' in uploadReference &&\n typeof uploadReference.prefix === 'string'\n ) {\n const referenceObjectKey =\n '_objectKey' in uploadReference\n ? (uploadReference as { _objectKey?: string })._objectKey\n : undefined\n const containedPrefix = buildUploadStoragePathData({\n collectionPrefix,\n docPrefix: uploadReference.prefix,\n filename,\n useCompositePrefixes,\n }).sanitizedDocPrefix\n return buildPrefixWithObjectKey({ objectKey: referenceObjectKey, prefix: containedPrefix })\n }\n\n // Reads without a query prefix or read-access constraints skip the endpoint's document lookup.\n const imageSizes = (collection?.upload as UploadConfig)?.imageSizes || []\n\n const filenameClause = {\n or: [\n {\n filename: { equals: filename },\n },\n ...imageSizes.map((imageSize) => ({\n [`sizes.${imageSize.name}.filename`]: { equals: filename },\n })),\n ],\n }\n\n // Only filter by prefix when the collection persists a `prefix` field (querying a missing field throws).\n const hasPrefixField = collection.fields?.some(\n (field) => 'name' in field && field.name === 'prefix',\n )\n const where =\n typeof prefixQueryParam === 'string' && hasPrefixField\n ? { and: [filenameClause, { prefix: { equals: sanitizePrefix(prefixQueryParam) } }] }\n : filenameClause\n\n const files = await req.payload.find({\n collection: collection.slug,\n depth: 0,\n draft: true,\n limit: 1,\n overrideAccess: false,\n pagination: false,\n req,\n select: {\n _objectKey: true,\n prefix: true,\n },\n showHiddenFields: true,\n where,\n })\n\n const found = files?.docs?.[0] as { _objectKey?: string; prefix?: string } | undefined\n return buildPrefixWithObjectKey({ objectKey: found?._objectKey, prefix: found?.prefix })\n}\n"],"names":["buildPrefixWithObjectKey","buildUploadStoragePathData","sanitizePrefix","getFilePrefix","collection","collectionPrefix","doc","filename","prefixQueryParam","req","uploadReference","useCompositePrefixes","objectKey","_objectKey","prefix","referenceObjectKey","undefined","containedPrefix","docPrefix","sanitizedDocPrefix","imageSizes","upload","filenameClause","or","equals","map","imageSize","name","hasPrefixField","fields","some","field","where","and","files","payload","find","slug","depth","draft","limit","overrideAccess","pagination","select","showHiddenFields","found","docs"],"mappings":"AAEA,SAASA,wBAAwB,QAAQ,gCAA+B;AACxE,SAASC,0BAA0B,QAAQ,4BAA2B;AACtE,SAASC,cAAc,QAAQ,sBAAqB;AAEpD;;;;;;;;CAQC,GACD,OAAO,eAAeC,cAAc,EAClCC,UAAU,EACVC,gBAAgB,EAChBC,GAAG,EACHC,QAAQ,EACRC,gBAAgB,EAChBC,GAAG,EACHC,eAAe,EACfC,uBAAuB,KAAK,EAc7B;IACC,iFAAiF;IACjF,oDAAoD;IACpD,IAAIL,KAAK;QACP,OAAON,yBAAyB;YAAEY,WAAWN,IAAIO,UAAU;YAAEC,QAAQR,IAAIQ,MAAM;QAAC;IAClF;IAEA,mFAAmF;IACnF,8EAA8E;IAC9E,IACEJ,mBACA,OAAOA,oBAAoB,YAC3B,YAAYA,mBACZ,OAAOA,gBAAgBI,MAAM,KAAK,UAClC;QACA,MAAMC,qBACJ,gBAAgBL,kBACZ,AAACA,gBAA4CG,UAAU,GACvDG;QACN,MAAMC,kBAAkBhB,2BAA2B;YACjDI;YACAa,WAAWR,gBAAgBI,MAAM;YACjCP;YACAI;QACF,GAAGQ,kBAAkB;QACrB,OAAOnB,yBAAyB;YAAEY,WAAWG;YAAoBD,QAAQG;QAAgB;IAC3F;IAEA,+FAA+F;IAC/F,MAAMG,aAAa,AAAChB,YAAYiB,QAAyBD,cAAc,EAAE;IAEzE,MAAME,iBAAiB;QACrBC,IAAI;YACF;gBACEhB,UAAU;oBAAEiB,QAAQjB;gBAAS;YAC/B;eACGa,WAAWK,GAAG,CAAC,CAACC,YAAe,CAAA;oBAChC,CAAC,CAAC,MAAM,EAAEA,UAAUC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE;wBAAEH,QAAQjB;oBAAS;gBAC3D,CAAA;SACD;IACH;IAEA,yGAAyG;IACzG,MAAMqB,iBAAiBxB,WAAWyB,MAAM,EAAEC,KACxC,CAACC,QAAU,UAAUA,SAASA,MAAMJ,IAAI,KAAK;IAE/C,MAAMK,QACJ,OAAOxB,qBAAqB,YAAYoB,iBACpC;QAAEK,KAAK;YAACX;YAAgB;gBAAER,QAAQ;oBAAEU,QAAQtB,eAAeM;gBAAkB;YAAE;SAAE;IAAC,IAClFc;IAEN,MAAMY,QAAQ,MAAMzB,IAAI0B,OAAO,CAACC,IAAI,CAAC;QACnChC,YAAYA,WAAWiC,IAAI;QAC3BC,OAAO;QACPC,OAAO;QACPC,OAAO;QACPC,gBAAgB;QAChBC,YAAY;QACZjC;QACAkC,QAAQ;YACN9B,YAAY;YACZC,QAAQ;QACV;QACA8B,kBAAkB;QAClBZ;IACF;IAEA,MAAMa,QAAQX,OAAOY,MAAM,CAAC,EAAE;IAC9B,OAAO9C,yBAAyB;QAAEY,WAAWiC,OAAOhC;QAAYC,QAAQ+B,OAAO/B;IAAO;AACxF"}
|
|
@@ -7,61 +7,188 @@ const makeReq = (docs = [])=>({
|
|
|
7
7
|
})
|
|
8
8
|
}
|
|
9
9
|
});
|
|
10
|
-
const makeCollection = ()=>({
|
|
10
|
+
const makeCollection = ({ hasPrefixField = false } = {})=>({
|
|
11
11
|
slug: 'media',
|
|
12
|
+
fields: hasPrefixField ? [
|
|
13
|
+
{
|
|
14
|
+
name: 'prefix',
|
|
15
|
+
type: 'text'
|
|
16
|
+
}
|
|
17
|
+
] : [],
|
|
12
18
|
upload: {}
|
|
13
19
|
});
|
|
20
|
+
const whereOf = (req)=>JSON.stringify(req.payload.find.mock.calls[0][0].where);
|
|
14
21
|
describe('getFilePrefix', ()=>{
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
it('prefers an already-authorized document over an upload reference or query prefix', async ()=>{
|
|
23
|
+
const req = makeReq([
|
|
24
|
+
{
|
|
25
|
+
prefix: 'db-prefix'
|
|
26
|
+
}
|
|
27
|
+
]);
|
|
28
|
+
const doc = {
|
|
29
|
+
id: '1',
|
|
30
|
+
prefix: 'invoices'
|
|
31
|
+
};
|
|
32
|
+
const result = await getFilePrefix({
|
|
33
|
+
collection: makeCollection(),
|
|
34
|
+
collectionPrefix: 'media',
|
|
35
|
+
doc,
|
|
36
|
+
filename: 'logo.png',
|
|
37
|
+
prefixQueryParam: 'query-prefix',
|
|
38
|
+
req,
|
|
39
|
+
uploadReference: {
|
|
40
|
+
prefix: 'reference-prefix'
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
expect(result).toBe('invoices');
|
|
44
|
+
expect(req.payload.find).not.toHaveBeenCalled();
|
|
45
|
+
});
|
|
46
|
+
it('does not fall back to a lookup when the checked document has no prefix', async ()=>{
|
|
47
|
+
const req = makeReq([
|
|
48
|
+
{
|
|
49
|
+
prefix: 'db-prefix'
|
|
50
|
+
}
|
|
51
|
+
]);
|
|
52
|
+
const result = await getFilePrefix({
|
|
53
|
+
collection: makeCollection(),
|
|
54
|
+
collectionPrefix: 'media',
|
|
55
|
+
doc: {
|
|
56
|
+
id: '1'
|
|
57
|
+
},
|
|
58
|
+
filename: 'logo.png',
|
|
59
|
+
req
|
|
60
|
+
});
|
|
61
|
+
expect(result).toBe('');
|
|
62
|
+
expect(req.payload.find).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
it('contains an upload reference prefix beneath the collection prefix', async ()=>{
|
|
65
|
+
const req = makeReq();
|
|
66
|
+
const result = await getFilePrefix({
|
|
67
|
+
collection: makeCollection(),
|
|
68
|
+
collectionPrefix: 'media',
|
|
69
|
+
filename: 'file.png',
|
|
70
|
+
req,
|
|
71
|
+
uploadReference: {
|
|
72
|
+
prefix: 'invoices'
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
expect(result).toBe('media/invoices');
|
|
76
|
+
expect(req.payload.find).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
describe('verified upload reference (trusted)', ()=>{
|
|
79
|
+
it('folds the object key into the prefix and skips the database', async ()=>{
|
|
17
80
|
const req = makeReq();
|
|
18
81
|
const result = await getFilePrefix({
|
|
19
82
|
collection: makeCollection(),
|
|
20
|
-
prefixQueryParam: 'uuid-prefix',
|
|
21
83
|
filename: 'logo.png',
|
|
22
|
-
req
|
|
84
|
+
req,
|
|
85
|
+
uploadReference: {
|
|
86
|
+
_objectKey: 'abc123',
|
|
87
|
+
prefix: 'documents'
|
|
88
|
+
}
|
|
23
89
|
});
|
|
24
|
-
expect(result).toBe('
|
|
90
|
+
expect(result).toBe('documents/abc123');
|
|
25
91
|
expect(req.payload.find).not.toHaveBeenCalled();
|
|
26
92
|
});
|
|
27
|
-
it('
|
|
93
|
+
it('returns the prefix alone when the reference has no object key', async ()=>{
|
|
28
94
|
const req = makeReq();
|
|
29
95
|
const result = await getFilePrefix({
|
|
30
96
|
collection: makeCollection(),
|
|
31
|
-
|
|
97
|
+
filename: 'logo.png',
|
|
98
|
+
req,
|
|
99
|
+
uploadReference: {
|
|
100
|
+
prefix: 'documents'
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
expect(result).toBe('documents');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('authorized document (trusted)', ()=>{
|
|
107
|
+
it('folds the object key into the document prefix and skips the database', async ()=>{
|
|
108
|
+
const req = makeReq([
|
|
109
|
+
{
|
|
110
|
+
prefix: 'db-prefix'
|
|
111
|
+
}
|
|
112
|
+
]);
|
|
113
|
+
const result = await getFilePrefix({
|
|
114
|
+
collection: makeCollection(),
|
|
115
|
+
doc: {
|
|
116
|
+
id: '1',
|
|
117
|
+
_objectKey: 'abc123',
|
|
118
|
+
prefix: 'doc-prefix'
|
|
119
|
+
},
|
|
32
120
|
filename: 'logo.png',
|
|
33
121
|
req
|
|
34
122
|
});
|
|
35
|
-
expect(result).toBe('');
|
|
123
|
+
expect(result).toBe('doc-prefix/abc123');
|
|
36
124
|
expect(req.payload.find).not.toHaveBeenCalled();
|
|
37
125
|
});
|
|
38
|
-
it('
|
|
126
|
+
it('returns the document prefix alone when it has no object key', async ()=>{
|
|
39
127
|
const req = makeReq();
|
|
40
128
|
const result = await getFilePrefix({
|
|
41
129
|
collection: makeCollection(),
|
|
42
|
-
|
|
130
|
+
doc: {
|
|
131
|
+
id: '1',
|
|
132
|
+
prefix: 'doc-prefix'
|
|
133
|
+
},
|
|
43
134
|
filename: 'logo.png',
|
|
44
135
|
req
|
|
45
136
|
});
|
|
46
|
-
expect(result).toBe('');
|
|
47
|
-
expect(req.payload.find).not.toHaveBeenCalled();
|
|
137
|
+
expect(result).toBe('doc-prefix');
|
|
48
138
|
});
|
|
49
|
-
it('
|
|
139
|
+
it('sanitizes the document prefix', async ()=>{
|
|
50
140
|
const req = makeReq();
|
|
51
141
|
const result = await getFilePrefix({
|
|
52
142
|
collection: makeCollection(),
|
|
53
|
-
|
|
143
|
+
doc: {
|
|
144
|
+
id: '1',
|
|
145
|
+
prefix: '../secret'
|
|
146
|
+
},
|
|
54
147
|
filename: 'logo.png',
|
|
55
148
|
req
|
|
56
149
|
});
|
|
57
|
-
expect(result).toBe('');
|
|
58
|
-
expect(req.payload.find).not.toHaveBeenCalled();
|
|
150
|
+
expect(result).toBe('secret');
|
|
59
151
|
});
|
|
60
152
|
});
|
|
61
|
-
describe('database
|
|
62
|
-
it('
|
|
153
|
+
describe('access-controlled database resolution', ()=>{
|
|
154
|
+
it('does NOT short-circuit on a client-supplied prefix — the access-controlled query still runs', async ()=>{
|
|
63
155
|
const req = makeReq([
|
|
64
156
|
{
|
|
157
|
+
_objectKey: 'abc123',
|
|
158
|
+
prefix: 'documents'
|
|
159
|
+
}
|
|
160
|
+
]);
|
|
161
|
+
await getFilePrefix({
|
|
162
|
+
collection: makeCollection({
|
|
163
|
+
hasPrefixField: true
|
|
164
|
+
}),
|
|
165
|
+
filename: 'logo.png',
|
|
166
|
+
prefixQueryParam: 'documents',
|
|
167
|
+
req
|
|
168
|
+
});
|
|
169
|
+
expect(req.payload.find).toHaveBeenCalledOnce();
|
|
170
|
+
});
|
|
171
|
+
it('queries with access control enabled and hidden fields', async ()=>{
|
|
172
|
+
const req = makeReq([
|
|
173
|
+
{
|
|
174
|
+
prefix: 'db-prefix'
|
|
175
|
+
}
|
|
176
|
+
]);
|
|
177
|
+
await getFilePrefix({
|
|
178
|
+
collection: makeCollection(),
|
|
179
|
+
filename: 'logo.png',
|
|
180
|
+
req
|
|
181
|
+
});
|
|
182
|
+
expect(req.payload.find).toHaveBeenCalledWith(expect.objectContaining({
|
|
183
|
+
overrideAccess: false,
|
|
184
|
+
req,
|
|
185
|
+
showHiddenFields: true
|
|
186
|
+
}));
|
|
187
|
+
});
|
|
188
|
+
it('folds the stored object key into the resolved prefix', async ()=>{
|
|
189
|
+
const req = makeReq([
|
|
190
|
+
{
|
|
191
|
+
_objectKey: 'abc123',
|
|
65
192
|
prefix: 'db-prefix'
|
|
66
193
|
}
|
|
67
194
|
]);
|
|
@@ -70,10 +197,9 @@ describe('getFilePrefix', ()=>{
|
|
|
70
197
|
filename: 'logo.png',
|
|
71
198
|
req
|
|
72
199
|
});
|
|
73
|
-
expect(result).toBe('db-prefix');
|
|
74
|
-
expect(req.payload.find).toHaveBeenCalledOnce();
|
|
200
|
+
expect(result).toBe('db-prefix/abc123');
|
|
75
201
|
});
|
|
76
|
-
it('
|
|
202
|
+
it('returns an empty string when no document is found', async ()=>{
|
|
77
203
|
const req = makeReq([]);
|
|
78
204
|
const result = await getFilePrefix({
|
|
79
205
|
collection: makeCollection(),
|
|
@@ -82,22 +208,39 @@ describe('getFilePrefix', ()=>{
|
|
|
82
208
|
});
|
|
83
209
|
expect(result).toBe('');
|
|
84
210
|
});
|
|
85
|
-
|
|
211
|
+
});
|
|
212
|
+
describe('client prefix as a disambiguation filter only', ()=>{
|
|
213
|
+
it('adds the prefix filter when the collection persists a prefix field', async ()=>{
|
|
86
214
|
const req = makeReq([
|
|
87
215
|
{
|
|
88
|
-
prefix: '
|
|
216
|
+
prefix: 'documents'
|
|
89
217
|
}
|
|
90
218
|
]);
|
|
91
|
-
|
|
92
|
-
collection: makeCollection(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
},
|
|
219
|
+
await getFilePrefix({
|
|
220
|
+
collection: makeCollection({
|
|
221
|
+
hasPrefixField: true
|
|
222
|
+
}),
|
|
96
223
|
filename: 'logo.png',
|
|
224
|
+
prefixQueryParam: 'documents',
|
|
97
225
|
req
|
|
98
226
|
});
|
|
99
|
-
expect(
|
|
100
|
-
|
|
227
|
+
expect(whereOf(req)).toContain('prefix');
|
|
228
|
+
});
|
|
229
|
+
it('omits the prefix filter when the collection has no prefix field', async ()=>{
|
|
230
|
+
const req = makeReq([
|
|
231
|
+
{
|
|
232
|
+
prefix: ''
|
|
233
|
+
}
|
|
234
|
+
]);
|
|
235
|
+
await getFilePrefix({
|
|
236
|
+
collection: makeCollection({
|
|
237
|
+
hasPrefixField: false
|
|
238
|
+
}),
|
|
239
|
+
filename: 'logo.png',
|
|
240
|
+
prefixQueryParam: 'anything',
|
|
241
|
+
req
|
|
242
|
+
});
|
|
243
|
+
expect(whereOf(req)).not.toContain('prefix');
|
|
101
244
|
});
|
|
102
245
|
});
|
|
103
246
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/getFilePrefix.spec.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest'\n\nimport type { CollectionConfig, PayloadRequest } from 'payload'\n\nimport { getFilePrefix } from './getFilePrefix.js'\n\nconst makeReq = (docs: unknown[] = []) =>\n ({\n payload: {\n find: vi.fn().mockResolvedValue({ docs }),\n },\n }) as unknown as PayloadRequest\n\nconst makeCollection = (): CollectionConfig =>\n ({ slug: 'media', upload: {} }) as unknown as CollectionConfig\n\ndescribe('getFilePrefix', () => {\n describe('prefixQueryParam shortcut', () => {\n it('should return prefixQueryParam immediately without querying the database', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n prefixQueryParam: 'uuid-prefix',\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('uuid-prefix')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n\n it('should return an empty string when prefixQueryParam is an empty string', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n prefixQueryParam: '',\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n\n it('should reject multi-encoded values', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n prefixQueryParam: '%252e%252e%252fsecret%252f..%252fok',\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n\n it('should reject malformed percent-encoding', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n prefixQueryParam: '%E0%A4%A',\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n })\n\n describe('database fallback', () => {\n it('should query the database when prefixQueryParam is not provided', async () => {\n const req = makeReq([{ prefix: 'db-prefix' }])\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('db-prefix')\n expect(req.payload.find).toHaveBeenCalledOnce()\n })\n\n it('should return empty string when no document is found', async () => {\n const req = makeReq([])\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('')\n })\n\n it('should prioritize the upload reference prefix over the database query', async () => {\n const req = makeReq([{ prefix: 'db-prefix' }])\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n uploadReference: { prefix: 'context-prefix' },\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('context-prefix')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n })\n})\n"],"names":["describe","expect","it","vi","getFilePrefix","makeReq","docs","payload","find","fn","mockResolvedValue","makeCollection","slug","upload","req","result","collection","prefixQueryParam","filename","toBe","not","toHaveBeenCalled","prefix","toHaveBeenCalledOnce","uploadReference"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAIjD,SAASC,aAAa,QAAQ,qBAAoB;AAElD,MAAMC,UAAU,CAACC,OAAkB,EAAE,GAClC,CAAA;QACCC,SAAS;YACPC,MAAML,GAAGM,EAAE,GAAGC,iBAAiB,CAAC;gBAAEJ;YAAK;QACzC;IACF,CAAA;AAEF,MAAMK,iBAAiB,IACpB,CAAA;QAAEC,MAAM;QAASC,QAAQ,CAAC;IAAE,CAAA;AAE/Bb,SAAS,iBAAiB;IACxBA,SAAS,6BAA6B;QACpCE,GAAG,4EAA4E;YAC7E,MAAMY,MAAMT;YAEZ,MAAMU,SAAS,MAAMX,cAAc;gBACjCY,YAAYL;gBACZM,kBAAkB;gBAClBC,UAAU;gBACVJ;YACF;YAEAb,OAAOc,QAAQI,IAAI,CAAC;YACpBlB,OAAOa,IAAIP,OAAO,CAACC,IAAI,EAAEY,GAAG,CAACC,gBAAgB;QAC/C;QAEAnB,GAAG,0EAA0E;YAC3E,MAAMY,MAAMT;YAEZ,MAAMU,SAAS,MAAMX,cAAc;gBACjCY,YAAYL;gBACZM,kBAAkB;gBAClBC,UAAU;gBACVJ;YACF;YAEAb,OAAOc,QAAQI,IAAI,CAAC;YACpBlB,OAAOa,IAAIP,OAAO,CAACC,IAAI,EAAEY,GAAG,CAACC,gBAAgB;QAC/C;QAEAnB,GAAG,sCAAsC;YACvC,MAAMY,MAAMT;YAEZ,MAAMU,SAAS,MAAMX,cAAc;gBACjCY,YAAYL;gBACZM,kBAAkB;gBAClBC,UAAU;gBACVJ;YACF;YAEAb,OAAOc,QAAQI,IAAI,CAAC;YACpBlB,OAAOa,IAAIP,OAAO,CAACC,IAAI,EAAEY,GAAG,CAACC,gBAAgB;QAC/C;QAEAnB,GAAG,4CAA4C;YAC7C,MAAMY,MAAMT;YAEZ,MAAMU,SAAS,MAAMX,cAAc;gBACjCY,YAAYL;gBACZM,kBAAkB;gBAClBC,UAAU;gBACVJ;YACF;YAEAb,OAAOc,QAAQI,IAAI,CAAC;YACpBlB,OAAOa,IAAIP,OAAO,CAACC,IAAI,EAAEY,GAAG,CAACC,gBAAgB;QAC/C;IACF;IAEArB,SAAS,qBAAqB;QAC5BE,GAAG,mEAAmE;YACpE,MAAMY,MAAMT,QAAQ;gBAAC;oBAAEiB,QAAQ;gBAAY;aAAE;YAE7C,MAAMP,SAAS,MAAMX,cAAc;gBACjCY,YAAYL;gBACZO,UAAU;gBACVJ;YACF;YAEAb,OAAOc,QAAQI,IAAI,CAAC;YACpBlB,OAAOa,IAAIP,OAAO,CAACC,IAAI,EAAEe,oBAAoB;QAC/C;QAEArB,GAAG,wDAAwD;YACzD,MAAMY,MAAMT,QAAQ,EAAE;YAEtB,MAAMU,SAAS,MAAMX,cAAc;gBACjCY,YAAYL;gBACZO,UAAU;gBACVJ;YACF;YAEAb,OAAOc,QAAQI,IAAI,CAAC;QACtB;QAEAjB,GAAG,yEAAyE;YAC1E,MAAMY,MAAMT,QAAQ;gBAAC;oBAAEiB,QAAQ;gBAAY;aAAE;YAE7C,MAAMP,SAAS,MAAMX,cAAc;gBACjCY,YAAYL;gBACZa,iBAAiB;oBAAEF,QAAQ;gBAAiB;gBAC5CJ,UAAU;gBACVJ;YACF;YAEAb,OAAOc,QAAQI,IAAI,CAAC;YACpBlB,OAAOa,IAAIP,OAAO,CAACC,IAAI,EAAEY,GAAG,CAACC,gBAAgB;QAC/C;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/getFilePrefix.spec.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest } from 'payload'\n\nimport { describe, expect, it, vi } from 'vitest'\n\nimport { getFilePrefix } from './getFilePrefix.js'\n\nconst makeReq = (docs: unknown[] = []) =>\n ({\n payload: {\n find: vi.fn().mockResolvedValue({ docs }),\n },\n }) as unknown as PayloadRequest\n\nconst makeCollection = ({\n hasPrefixField = false,\n}: { hasPrefixField?: boolean } = {}): CollectionConfig =>\n ({\n slug: 'media',\n fields: hasPrefixField ? [{ name: 'prefix', type: 'text' }] : [],\n upload: {},\n }) as unknown as CollectionConfig\n\nconst whereOf = (req: PayloadRequest) =>\n JSON.stringify(\n (req.payload.find as unknown as { mock: { calls: [{ where: unknown }][] } }).mock.calls[0][0]\n .where,\n )\n\ndescribe('getFilePrefix', () => {\n it('prefers an already-authorized document over an upload reference or query prefix', async () => {\n const req = makeReq([{ prefix: 'db-prefix' }])\n const doc = { id: '1', prefix: 'invoices' }\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n collectionPrefix: 'media',\n doc,\n filename: 'logo.png',\n prefixQueryParam: 'query-prefix',\n req,\n uploadReference: { prefix: 'reference-prefix' },\n })\n\n expect(result).toBe('invoices')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n\n it('does not fall back to a lookup when the checked document has no prefix', async () => {\n const req = makeReq([{ prefix: 'db-prefix' }])\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n collectionPrefix: 'media',\n doc: { id: '1' },\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n\n it('contains an upload reference prefix beneath the collection prefix', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n collectionPrefix: 'media',\n filename: 'file.png',\n req,\n uploadReference: { prefix: 'invoices' },\n })\n\n expect(result).toBe('media/invoices')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n\n describe('verified upload reference (trusted)', () => {\n it('folds the object key into the prefix and skips the database', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n filename: 'logo.png',\n req,\n uploadReference: { _objectKey: 'abc123', prefix: 'documents' },\n })\n\n expect(result).toBe('documents/abc123')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n\n it('returns the prefix alone when the reference has no object key', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n filename: 'logo.png',\n req,\n uploadReference: { prefix: 'documents' },\n })\n\n expect(result).toBe('documents')\n })\n })\n\n describe('authorized document (trusted)', () => {\n it('folds the object key into the document prefix and skips the database', async () => {\n const req = makeReq([{ prefix: 'db-prefix' }])\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n doc: { id: '1', _objectKey: 'abc123', prefix: 'doc-prefix' },\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('doc-prefix/abc123')\n expect(req.payload.find).not.toHaveBeenCalled()\n })\n\n it('returns the document prefix alone when it has no object key', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n doc: { id: '1', prefix: 'doc-prefix' },\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('doc-prefix')\n })\n\n it('sanitizes the document prefix', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n doc: { id: '1', prefix: '../secret' },\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('secret')\n })\n })\n\n describe('access-controlled database resolution', () => {\n it('does NOT short-circuit on a client-supplied prefix — the access-controlled query still runs', async () => {\n const req = makeReq([{ _objectKey: 'abc123', prefix: 'documents' }])\n\n await getFilePrefix({\n collection: makeCollection({ hasPrefixField: true }),\n filename: 'logo.png',\n prefixQueryParam: 'documents',\n req,\n })\n\n expect(req.payload.find).toHaveBeenCalledOnce()\n })\n\n it('queries with access control enabled and hidden fields', async () => {\n const req = makeReq([{ prefix: 'db-prefix' }])\n\n await getFilePrefix({\n collection: makeCollection(),\n filename: 'logo.png',\n req,\n })\n\n expect(req.payload.find).toHaveBeenCalledWith(\n expect.objectContaining({ overrideAccess: false, req, showHiddenFields: true }),\n )\n })\n\n it('folds the stored object key into the resolved prefix', async () => {\n const req = makeReq([{ _objectKey: 'abc123', prefix: 'db-prefix' }])\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('db-prefix/abc123')\n })\n\n it('returns an empty string when no document is found', async () => {\n const req = makeReq([])\n\n const result = await getFilePrefix({\n collection: makeCollection(),\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('')\n })\n })\n\n describe('client prefix as a disambiguation filter only', () => {\n it('adds the prefix filter when the collection persists a prefix field', async () => {\n const req = makeReq([{ prefix: 'documents' }])\n\n await getFilePrefix({\n collection: makeCollection({ hasPrefixField: true }),\n filename: 'logo.png',\n prefixQueryParam: 'documents',\n req,\n })\n\n expect(whereOf(req)).toContain('prefix')\n })\n\n it('omits the prefix filter when the collection has no prefix field', async () => {\n const req = makeReq([{ prefix: '' }])\n\n await getFilePrefix({\n collection: makeCollection({ hasPrefixField: false }),\n filename: 'logo.png',\n prefixQueryParam: 'anything',\n req,\n })\n\n expect(whereOf(req)).not.toContain('prefix')\n })\n })\n})\n"],"names":["describe","expect","it","vi","getFilePrefix","makeReq","docs","payload","find","fn","mockResolvedValue","makeCollection","hasPrefixField","slug","fields","name","type","upload","whereOf","req","JSON","stringify","mock","calls","where","prefix","doc","id","result","collection","collectionPrefix","filename","prefixQueryParam","uploadReference","toBe","not","toHaveBeenCalled","_objectKey","toHaveBeenCalledOnce","toHaveBeenCalledWith","objectContaining","overrideAccess","showHiddenFields","toContain"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { UploadReference } from '../types.js';
|
|
2
3
|
type Args = {
|
|
3
4
|
collectionPrefix?: string;
|
|
4
5
|
collectionSlug: string;
|
|
@@ -12,13 +13,23 @@ type Args = {
|
|
|
12
13
|
* the filename via {@link getSafeFileName} so a duplicate upload does not
|
|
13
14
|
* overwrite an existing blob.
|
|
14
15
|
*
|
|
16
|
+
* A unique per-upload segment is included in the storage key so each issued
|
|
17
|
+
* key is distinct. The stored `filename` stays clean and the semantic `prefix`
|
|
18
|
+
* stays exactly what the collection configured — the per-upload entropy is
|
|
19
|
+
* persisted separately in `_objectKey`.
|
|
20
|
+
*
|
|
15
21
|
* The resolved `sanitizedFilename` is returned so the browser-side handler
|
|
16
22
|
* can update the form via `updateFilename`.
|
|
23
|
+
*
|
|
24
|
+
* The semantic prefix is resolved with {@link buildUploadPrefix} and the final
|
|
25
|
+
* key with {@link buildUploadStoragePathData}, so the receipt only ever signs a key
|
|
26
|
+
* beneath the configured collection prefix.
|
|
17
27
|
*/
|
|
18
28
|
export declare function resolveSignedURLKey({ collectionPrefix, collectionSlug, docPrefix, filename, req, useCompositePrefixes, }: Args): Promise<{
|
|
19
|
-
fileKey: string;
|
|
20
29
|
sanitizedDocPrefix: string;
|
|
21
30
|
sanitizedFilename: string;
|
|
31
|
+
storageFilePath: string;
|
|
32
|
+
uploadReference: UploadReference;
|
|
22
33
|
}>;
|
|
23
34
|
export {};
|
|
24
35
|
//# sourceMappingURL=resolveSignedURLKey.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveSignedURLKey.d.ts","sourceRoot":"","sources":["../../src/utilities/resolveSignedURLKey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAM7C,KAAK,IAAI,GAAG;IACV,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,cAAc,CAAA;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"resolveSignedURLKey.d.ts","sourceRoot":"","sources":["../../src/utilities/resolveSignedURLKey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAM7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIlD,KAAK,IAAI,GAAG;IACV,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,cAAc,CAAA;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,gBAAqB,EACrB,cAAc,EACd,SAAS,EACT,QAAQ,EACR,GAAG,EACH,oBAA4B,GAC7B,EAAE,IAAI,GAAG,OAAO,CAAC;IAChB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IACzB,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,eAAe,CAAA;CACjC,CAAC,CAyCD"}
|
|
@@ -1,29 +1,63 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createClientUploadReceipt, getSafeFileName } from 'payload/internal';
|
|
3
|
+
import { getSanitizedUploadFilename } from 'payload/shared';
|
|
4
|
+
import { buildUploadPrefix, buildUploadStoragePathData } from './buildStoragePathData.js';
|
|
3
5
|
/**
|
|
4
6
|
* Resolves the storage key for an upload-instructions request, deduping
|
|
5
7
|
* the filename via {@link getSafeFileName} so a duplicate upload does not
|
|
6
8
|
* overwrite an existing blob.
|
|
7
9
|
*
|
|
10
|
+
* A unique per-upload segment is included in the storage key so each issued
|
|
11
|
+
* key is distinct. The stored `filename` stays clean and the semantic `prefix`
|
|
12
|
+
* stays exactly what the collection configured — the per-upload entropy is
|
|
13
|
+
* persisted separately in `_objectKey`.
|
|
14
|
+
*
|
|
8
15
|
* The resolved `sanitizedFilename` is returned so the browser-side handler
|
|
9
16
|
* can update the form via `updateFilename`.
|
|
17
|
+
*
|
|
18
|
+
* The semantic prefix is resolved with {@link buildUploadPrefix} and the final
|
|
19
|
+
* key with {@link buildUploadStoragePathData}, so the receipt only ever signs a key
|
|
20
|
+
* beneath the configured collection prefix.
|
|
10
21
|
*/ export async function resolveSignedURLKey({ collectionPrefix = '', collectionSlug, docPrefix, filename, req, useCompositePrefixes = false }) {
|
|
22
|
+
// Sanitize with the same helper generateFileData uses for the DB filename so the storage key
|
|
23
|
+
// and doc.filename stay in sync (#16694).
|
|
11
24
|
const sanitizedFilename = await getSafeFileName({
|
|
12
25
|
collectionSlug,
|
|
13
|
-
desiredFilename: filename,
|
|
14
|
-
prefix: docPrefix,
|
|
26
|
+
desiredFilename: getSanitizedUploadFilename(filename),
|
|
15
27
|
req
|
|
16
28
|
});
|
|
17
|
-
const
|
|
29
|
+
const rawBaseDocPrefix = useCompositePrefixes ? docPrefix : docPrefix || collectionPrefix;
|
|
30
|
+
const { sanitizedDocPrefix } = buildUploadPrefix({
|
|
31
|
+
collectionPrefix,
|
|
32
|
+
docPrefix: rawBaseDocPrefix,
|
|
33
|
+
useCompositePrefixes
|
|
34
|
+
});
|
|
35
|
+
const _objectKey = randomUUID();
|
|
36
|
+
// Per-upload segment lives in the key; it is persisted in `_objectKey`, keeping `prefix` semantic.
|
|
37
|
+
const keyedDocPrefix = sanitizedDocPrefix ? `${sanitizedDocPrefix}/${_objectKey}` : _objectKey;
|
|
38
|
+
const { storageFilePath } = buildUploadStoragePathData({
|
|
18
39
|
collectionPrefix,
|
|
19
|
-
docPrefix,
|
|
40
|
+
docPrefix: keyedDocPrefix,
|
|
20
41
|
filename: sanitizedFilename,
|
|
21
42
|
useCompositePrefixes
|
|
22
43
|
});
|
|
44
|
+
const uploadReference = {
|
|
45
|
+
_objectKey,
|
|
46
|
+
prefix: sanitizedDocPrefix,
|
|
47
|
+
signedReceipt: createClientUploadReceipt({
|
|
48
|
+
_objectKey,
|
|
49
|
+
collectionSlug,
|
|
50
|
+
filename: sanitizedFilename,
|
|
51
|
+
filePrefix: sanitizedDocPrefix,
|
|
52
|
+
req,
|
|
53
|
+
storageFilePath
|
|
54
|
+
})
|
|
55
|
+
};
|
|
23
56
|
return {
|
|
24
|
-
fileKey,
|
|
25
57
|
sanitizedDocPrefix,
|
|
26
|
-
sanitizedFilename
|
|
58
|
+
sanitizedFilename,
|
|
59
|
+
storageFilePath,
|
|
60
|
+
uploadReference
|
|
27
61
|
};
|
|
28
62
|
}
|
|
29
63
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/resolveSignedURLKey.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { getSafeFileName } from 'payload/internal'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/resolveSignedURLKey.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { randomUUID } from 'node:crypto'\nimport { createClientUploadReceipt, getSafeFileName } from 'payload/internal'\nimport { getSanitizedUploadFilename } from 'payload/shared'\n\nimport type { UploadReference } from '../types.js'\n\nimport { buildUploadPrefix, buildUploadStoragePathData } from './buildStoragePathData.js'\n\ntype Args = {\n collectionPrefix?: string\n collectionSlug: string\n docPrefix?: string\n filename: string\n req: PayloadRequest\n useCompositePrefixes?: boolean\n}\n\n/**\n * Resolves the storage key for an upload-instructions request, deduping\n * the filename via {@link getSafeFileName} so a duplicate upload does not\n * overwrite an existing blob.\n *\n * A unique per-upload segment is included in the storage key so each issued\n * key is distinct. The stored `filename` stays clean and the semantic `prefix`\n * stays exactly what the collection configured — the per-upload entropy is\n * persisted separately in `_objectKey`.\n *\n * The resolved `sanitizedFilename` is returned so the browser-side handler\n * can update the form via `updateFilename`.\n *\n * The semantic prefix is resolved with {@link buildUploadPrefix} and the final\n * key with {@link buildUploadStoragePathData}, so the receipt only ever signs a key\n * beneath the configured collection prefix.\n */\nexport async function resolveSignedURLKey({\n collectionPrefix = '',\n collectionSlug,\n docPrefix,\n filename,\n req,\n useCompositePrefixes = false,\n}: Args): Promise<{\n sanitizedDocPrefix: string\n sanitizedFilename: string\n storageFilePath: string\n uploadReference: UploadReference\n}> {\n // Sanitize with the same helper generateFileData uses for the DB filename so the storage key\n // and doc.filename stay in sync (#16694).\n const sanitizedFilename = await getSafeFileName({\n collectionSlug,\n desiredFilename: getSanitizedUploadFilename(filename),\n req,\n })\n\n const rawBaseDocPrefix = useCompositePrefixes ? docPrefix : docPrefix || collectionPrefix\n const { sanitizedDocPrefix } = buildUploadPrefix({\n collectionPrefix,\n docPrefix: rawBaseDocPrefix,\n useCompositePrefixes,\n })\n\n const _objectKey = randomUUID()\n // Per-upload segment lives in the key; it is persisted in `_objectKey`, keeping `prefix` semantic.\n const keyedDocPrefix = sanitizedDocPrefix ? `${sanitizedDocPrefix}/${_objectKey}` : _objectKey\n\n const { storageFilePath } = buildUploadStoragePathData({\n collectionPrefix,\n docPrefix: keyedDocPrefix,\n filename: sanitizedFilename,\n useCompositePrefixes,\n })\n\n const uploadReference = {\n _objectKey,\n prefix: sanitizedDocPrefix,\n signedReceipt: createClientUploadReceipt({\n _objectKey,\n collectionSlug,\n filename: sanitizedFilename,\n filePrefix: sanitizedDocPrefix,\n req,\n storageFilePath,\n }),\n }\n\n return { sanitizedDocPrefix, sanitizedFilename, storageFilePath, uploadReference }\n}\n"],"names":["randomUUID","createClientUploadReceipt","getSafeFileName","getSanitizedUploadFilename","buildUploadPrefix","buildUploadStoragePathData","resolveSignedURLKey","collectionPrefix","collectionSlug","docPrefix","filename","req","useCompositePrefixes","sanitizedFilename","desiredFilename","rawBaseDocPrefix","sanitizedDocPrefix","_objectKey","keyedDocPrefix","storageFilePath","uploadReference","prefix","signedReceipt","filePrefix"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AACxC,SAASC,yBAAyB,EAAEC,eAAe,QAAQ,mBAAkB;AAC7E,SAASC,0BAA0B,QAAQ,iBAAgB;AAI3D,SAASC,iBAAiB,EAAEC,0BAA0B,QAAQ,4BAA2B;AAWzF;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,eAAeC,oBAAoB,EACxCC,mBAAmB,EAAE,EACrBC,cAAc,EACdC,SAAS,EACTC,QAAQ,EACRC,GAAG,EACHC,uBAAuB,KAAK,EACvB;IAML,6FAA6F;IAC7F,0CAA0C;IAC1C,MAAMC,oBAAoB,MAAMX,gBAAgB;QAC9CM;QACAM,iBAAiBX,2BAA2BO;QAC5CC;IACF;IAEA,MAAMI,mBAAmBH,uBAAuBH,YAAYA,aAAaF;IACzE,MAAM,EAAES,kBAAkB,EAAE,GAAGZ,kBAAkB;QAC/CG;QACAE,WAAWM;QACXH;IACF;IAEA,MAAMK,aAAajB;IACnB,mGAAmG;IACnG,MAAMkB,iBAAiBF,qBAAqB,GAAGA,mBAAmB,CAAC,EAAEC,YAAY,GAAGA;IAEpF,MAAM,EAAEE,eAAe,EAAE,GAAGd,2BAA2B;QACrDE;QACAE,WAAWS;QACXR,UAAUG;QACVD;IACF;IAEA,MAAMQ,kBAAkB;QACtBH;QACAI,QAAQL;QACRM,eAAerB,0BAA0B;YACvCgB;YACAT;YACAE,UAAUG;YACVU,YAAYP;YACZL;YACAQ;QACF;IACF;IAEA,OAAO;QAAEH;QAAoBH;QAAmBM;QAAiBC;IAAgB;AACnF"}
|