@payloadcms/plugin-cloud-storage 3.89.0 → 3.90.1
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 +3 -2
- package/dist/exports/utilities.d.ts.map +1 -1
- package/dist/exports/utilities.js +3 -2
- package/dist/exports/utilities.js.map +1 -1
- package/dist/fields/getFields.d.ts.map +1 -1
- package/dist/fields/getFields.js +28 -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 +56 -9
- package/dist/hooks/afterChange.js.map +1 -1
- package/dist/hooks/afterChange.spec.js +150 -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 +13 -2
- package/dist/hooks/afterRead.js.map +1 -1
- package/dist/hooks/beforeChange.d.ts.map +1 -1
- package/dist/hooks/beforeChange.js +10 -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 +15 -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 +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utilities/assertReceiptMatchesFileKey.d.ts +15 -0
- package/dist/utilities/assertReceiptMatchesFileKey.d.ts.map +1 -0
- package/dist/utilities/assertReceiptMatchesFileKey.js +31 -0
- package/dist/utilities/assertReceiptMatchesFileKey.js.map +1 -0
- 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 +55 -0
- package/dist/utilities/buildStoragePathData.d.ts.map +1 -0
- package/dist/utilities/buildStoragePathData.js +69 -0
- package/dist/utilities/buildStoragePathData.js.map +1 -0
- package/dist/utilities/{getFileKey.spec.js → buildStoragePathData.spec.js} +116 -36
- package/dist/utilities/buildStoragePathData.spec.js.map +1 -0
- package/dist/utilities/getFilePrefix.d.ts +27 -11
- package/dist/utilities/getFilePrefix.d.ts.map +1 -1
- package/dist/utilities/getFilePrefix.js +75 -31
- package/dist/utilities/getFilePrefix.js.map +1 -1
- package/dist/utilities/getFilePrefix.spec.js +221 -35
- package/dist/utilities/getFilePrefix.spec.js.map +1 -1
- package/dist/utilities/resolveSignedURLKey.d.ts +11 -0
- package/dist/utilities/resolveSignedURLKey.d.ts.map +1 -1
- package/dist/utilities/resolveSignedURLKey.js +43 -8
- package/dist/utilities/resolveSignedURLKey.js.map +1 -1
- package/dist/utilities/resolveSignedURLKey.spec.js +56 -0
- package/dist/utilities/resolveSignedURLKey.spec.js.map +1 -0
- package/dist/utilities/sanitizePrefix.d.ts +2 -2
- package/dist/utilities/sanitizePrefix.js +4 -4
- package/dist/utilities/sanitizePrefix.js.map +1 -1
- package/dist/utilities/verifyClientUploadReceiptForFileKey.d.ts +20 -0
- package/dist/utilities/verifyClientUploadReceiptForFileKey.d.ts.map +1 -0
- package/dist/utilities/verifyClientUploadReceiptForFileKey.js +24 -0
- package/dist/utilities/verifyClientUploadReceiptForFileKey.js.map +1 -0
- package/dist/utilities/verifyClientUploadReceiptForFileKey.spec.js +108 -0
- package/dist/utilities/verifyClientUploadReceiptForFileKey.spec.js.map +1 -0
- 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.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/buildStoragePathData.spec.ts"],"sourcesContent":["import {\n buildStoragePathData,\n buildUploadStoragePathData,\n isStoragePathWithinCollectionPrefix,\n} from './buildStoragePathData.js'\nimport { describe, expect, it } from 'vitest'\n\ndescribe('buildStoragePathData', () => {\n describe('non-composite mode (useCompositePrefixes: false)', () => {\n it('should preserve the key of a legacy document outside the collection prefix', () => {\n const result = buildStoragePathData({\n collectionPrefix: 'media/',\n docPrefix: 'media-archive',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n\n expect(result).toEqual({\n fileKey: 'media-archive/test.png',\n sanitizedCollectionPrefix: 'media',\n sanitizedDocPrefix: 'media-archive',\n sanitizedFilename: 'test.png',\n storageFilePath: 'media-archive/test.png',\n })\n })\n\n it('should fallback to collectionPrefix when docPrefix is empty', () => {\n const result = buildStoragePathData({\n collectionPrefix: 'collection',\n docPrefix: '',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'collection/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n storageFilePath: 'collection/test.png',\n })\n })\n\n it('should fallback to collectionPrefix when docPrefix is undefined', () => {\n const result = buildStoragePathData({\n collectionPrefix: 'collection',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'collection/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n storageFilePath: 'collection/test.png',\n })\n })\n\n it('should return only filename when both prefixes are empty', () => {\n const result = buildStoragePathData({\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'test.png',\n sanitizedCollectionPrefix: '',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n storageFilePath: 'test.png',\n })\n })\n })\n\n describe('composite mode (useCompositePrefixes: true)', () => {\n it('should combine collectionPrefix and docPrefix', () => {\n const result = buildStoragePathData({\n collectionPrefix: 'collection',\n docPrefix: 'document',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'collection/document/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: 'document',\n sanitizedFilename: 'test.png',\n storageFilePath: 'collection/document/test.png',\n })\n })\n\n it('should work with only collectionPrefix', () => {\n const result = buildStoragePathData({\n collectionPrefix: 'collection',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'collection/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n storageFilePath: 'collection/test.png',\n })\n })\n\n it('should work with only docPrefix', () => {\n const result = buildStoragePathData({\n docPrefix: 'document',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'document/test.png',\n sanitizedCollectionPrefix: '',\n sanitizedDocPrefix: 'document',\n sanitizedFilename: 'test.png',\n storageFilePath: 'document/test.png',\n })\n })\n\n it('should return only filename when both prefixes are empty', () => {\n const result = buildStoragePathData({\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'test.png',\n sanitizedCollectionPrefix: '',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n storageFilePath: 'test.png',\n })\n })\n })\n\n describe('collection prefix boundaries', () => {\n it('should accept a file key within a slash-terminated collection prefix', () => {\n expect(\n isStoragePathWithinCollectionPrefix({\n collectionPrefix: 'media/',\n docPrefix: 'media/documents/test.png',\n }),\n ).toBe(true)\n })\n })\n\n describe('sanitization', () => {\n it.each(['./media/./images', 'media\\\\images', '/media//images'])(\n 'should canonicalize equivalent prefix paths',\n (docPrefix) => {\n const result = buildStoragePathData({ docPrefix, filename: 'test.png' })\n\n expect(result).toEqual({\n fileKey: 'media/images/test.png',\n sanitizedCollectionPrefix: '',\n sanitizedDocPrefix: 'media/images',\n sanitizedFilename: 'test.png',\n storageFilePath: 'media/images/test.png',\n })\n },\n )\n\n it('should remove path traversal segments from collectionPrefix', () => {\n const result = buildStoragePathData({\n collectionPrefix: '../../../etc',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'etc/test.png',\n sanitizedCollectionPrefix: 'etc',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n storageFilePath: 'etc/test.png',\n })\n expect(result.fileKey).not.toContain('..')\n })\n\n it('should remove path traversal segments from docPrefix', () => {\n const result = buildStoragePathData({\n docPrefix: 'a/../../outside',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'a/outside/test.png',\n sanitizedCollectionPrefix: '',\n sanitizedDocPrefix: 'a/outside',\n sanitizedFilename: 'test.png',\n storageFilePath: 'a/outside/test.png',\n })\n expect(result.fileKey).not.toContain('..')\n })\n\n it('should remove control characters from prefixes', () => {\n const result = buildStoragePathData({\n collectionPrefix: 'test\\x00\\x1fprefix',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'testprefix/test.png',\n sanitizedCollectionPrefix: 'testprefix',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n storageFilePath: 'testprefix/test.png',\n })\n expect(result.fileKey).not.toMatch(/[\\x00-\\x1f]/)\n })\n\n it('should normalize both prefixes after removing control characters in composite mode', () => {\n const result = buildStoragePathData({\n collectionPrefix: '.\\x00./collection',\n docPrefix: '.\\x00./doc',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'collection/doc/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: 'doc',\n sanitizedFilename: 'test.png',\n storageFilePath: 'collection/doc/test.png',\n })\n expect(result.fileKey).not.toContain('..')\n })\n })\n})\n\ndescribe('buildUploadStoragePathData', () => {\n it.each([\n ['invoices', false, 'media/invoices/file.png', 'media/invoices'],\n ['media/invoices', false, 'media/invoices/file.png', 'media/invoices'],\n ['', false, 'media/file.png', ''],\n ['invoices', true, 'media/invoices/file.png', 'invoices'],\n ] as const)(\n 'should persist a readable destination for %s (composite %s)',\n (docPrefix, useCompositePrefixes, fileKey, storedPrefix) => {\n const result = buildUploadStoragePathData({\n collectionPrefix: 'media',\n docPrefix,\n filename: 'file.png',\n useCompositePrefixes,\n })\n\n expect(result.storageFilePath).toBe(fileKey)\n expect(result.sanitizedDocPrefix).toBe(storedPrefix)\n expect(\n buildStoragePathData({\n collectionPrefix: 'media',\n docPrefix: result.sanitizedDocPrefix,\n filename: 'file.png',\n useCompositePrefixes,\n }).fileKey,\n ).toBe(fileKey)\n },\n )\n})\n"],"names":["buildStoragePathData","buildUploadStoragePathData","isStoragePathWithinCollectionPrefix","describe","expect","it","result","collectionPrefix","docPrefix","filename","useCompositePrefixes","toEqual","fileKey","sanitizedCollectionPrefix","sanitizedDocPrefix","sanitizedFilename","storageFilePath","toBe","each","not","toContain","toMatch","storedPrefix"],"mappings":"AAAA,SACEA,oBAAoB,EACpBC,0BAA0B,EAC1BC,mCAAmC,QAC9B,4BAA2B;AAClC,SAASC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7CF,SAAS,wBAAwB;IAC/BA,SAAS,oDAAoD;QAC3DE,GAAG,8EAA8E;YAC/E,MAAMC,SAASN,qBAAqB;gBAClCO,kBAAkB;gBAClBC,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YAEAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;QACF;QAEAX,GAAG,+DAA+D;YAChE,MAAMC,SAASN,qBAAqB;gBAClCO,kBAAkB;gBAClBC,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;QACF;QAEAX,GAAG,mEAAmE;YACpE,MAAMC,SAASN,qBAAqB;gBAClCO,kBAAkB;gBAClBE,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;QACF;QAEAX,GAAG,4DAA4D;YAC7D,MAAMC,SAASN,qBAAqB;gBAClCS,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;QACF;IACF;IAEAb,SAAS,+CAA+C;QACtDE,GAAG,iDAAiD;YAClD,MAAMC,SAASN,qBAAqB;gBAClCO,kBAAkB;gBAClBC,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;QACF;QAEAX,GAAG,0CAA0C;YAC3C,MAAMC,SAASN,qBAAqB;gBAClCO,kBAAkB;gBAClBE,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;QACF;QAEAX,GAAG,mCAAmC;YACpC,MAAMC,SAASN,qBAAqB;gBAClCQ,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;QACF;QAEAX,GAAG,4DAA4D;YAC7D,MAAMC,SAASN,qBAAqB;gBAClCS,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;QACF;IACF;IAEAb,SAAS,gCAAgC;QACvCE,GAAG,wEAAwE;YACzED,OACEF,oCAAoC;gBAClCK,kBAAkB;gBAClBC,WAAW;YACb,IACAS,IAAI,CAAC;QACT;IACF;IAEAd,SAAS,gBAAgB;QACvBE,GAAGa,IAAI,CAAC;YAAC;YAAoB;YAAiB;SAAiB,EAC7D,+CACA,CAACV;YACC,MAAMF,SAASN,qBAAqB;gBAAEQ;gBAAWC,UAAU;YAAW;YAEtEL,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;QACF;QAGFX,GAAG,+DAA+D;YAChE,MAAMC,SAASN,qBAAqB;gBAClCO,kBAAkB;gBAClBE,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;YACAZ,OAAOE,OAAOM,OAAO,EAAEO,GAAG,CAACC,SAAS,CAAC;QACvC;QAEAf,GAAG,wDAAwD;YACzD,MAAMC,SAASN,qBAAqB;gBAClCQ,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;YACAZ,OAAOE,OAAOM,OAAO,EAAEO,GAAG,CAACC,SAAS,CAAC;QACvC;QAEAf,GAAG,kDAAkD;YACnD,MAAMC,SAASN,qBAAqB;gBAClCO,kBAAkB;gBAClBE,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;YACAZ,OAAOE,OAAOM,OAAO,EAAEO,GAAG,CAACE,OAAO,CAAC;QACrC;QAEAhB,GAAG,sFAAsF;YACvF,MAAMC,SAASN,qBAAqB;gBAClCO,kBAAkB;gBAClBC,WAAW;gBACXC,UAAU;gBACVC,sBAAsB;YACxB;YACAN,OAAOE,QAAQK,OAAO,CAAC;gBACrBC,SAAS;gBACTC,2BAA2B;gBAC3BC,oBAAoB;gBACpBC,mBAAmB;gBACnBC,iBAAiB;YACnB;YACAZ,OAAOE,OAAOM,OAAO,EAAEO,GAAG,CAACC,SAAS,CAAC;QACvC;IACF;AACF;AAEAjB,SAAS,8BAA8B;IACrCE,GAAGa,IAAI,CAAC;QACN;YAAC;YAAY;YAAO;YAA2B;SAAiB;QAChE;YAAC;YAAkB;YAAO;YAA2B;SAAiB;QACtE;YAAC;YAAI;YAAO;YAAkB;SAAG;QACjC;YAAC;YAAY;YAAM;YAA2B;SAAW;KAC1D,EACC,+DACA,CAACV,WAAWE,sBAAsBE,SAASU;QACzC,MAAMhB,SAASL,2BAA2B;YACxCM,kBAAkB;YAClBC;YACAC,UAAU;YACVC;QACF;QAEAN,OAAOE,OAAOU,eAAe,EAAEC,IAAI,CAACL;QACpCR,OAAOE,OAAOQ,kBAAkB,EAAEG,IAAI,CAACK;QACvClB,OACEJ,qBAAqB;YACnBO,kBAAkB;YAClBC,WAAWF,OAAOQ,kBAAkB;YACpCL,UAAU;YACVC;QACF,GAAGE,OAAO,EACVK,IAAI,CAACL;IACT;AAEJ"}
|
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
import type { CollectionConfig, PayloadRequest } from 'payload';
|
|
1
|
+
import type { CollectionConfig, PayloadRequest, TypeWithID } from 'payload';
|
|
2
2
|
/**
|
|
3
|
-
* Resolves the
|
|
4
|
-
* always returns a sanitized value.
|
|
3
|
+
* Resolves the folder a stored object lives under (semantic `prefix` plus `_objectKey`).
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Resolved values are passed through `sanitizePrefix`.
|
|
5
|
+
* An already-authorized `doc` wins outright. Otherwise a verified `clientUploadContext` is used
|
|
6
|
+
* (its `prefix` re-contained with {@link buildUploadStoragePathData}, since it hasn't been verified against
|
|
7
|
+
* a signed receipt by every adapter). Otherwise the document is resolved with
|
|
8
|
+
* `overrideAccess: false`, any query-param prefix only narrows that lookup, and `_objectKey` is
|
|
9
|
+
* read from the resolved document.
|
|
12
10
|
*/
|
|
13
|
-
|
|
11
|
+
type GetFilePrefixArgs = {
|
|
14
12
|
clientUploadContext?: unknown;
|
|
15
13
|
collection: CollectionConfig;
|
|
14
|
+
collectionPrefix?: string;
|
|
15
|
+
doc?: {
|
|
16
|
+
_objectKey?: string;
|
|
17
|
+
prefix?: string;
|
|
18
|
+
} & TypeWithID;
|
|
16
19
|
filename: string;
|
|
20
|
+
/**
|
|
21
|
+
* Only narrows the access-controlled fallback lookup below; never trusted or
|
|
22
|
+
* returned directly. Prefer passing the access-checked document as `doc` instead.
|
|
23
|
+
*/
|
|
17
24
|
prefixQueryParam?: string;
|
|
18
25
|
req: PayloadRequest;
|
|
19
|
-
|
|
26
|
+
useCompositePrefixes?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export declare function getFilePrefix({ clientUploadContext, collection, collectionPrefix, doc, filename, prefixQueryParam, req, useCompositePrefixes, }: GetFilePrefixArgs): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Serve-path helper: resolves the authorized folder with {@link getFilePrefix}, then returns the
|
|
31
|
+
* full object key via {@link buildStoragePathData}. Read handlers use this so a key is never built from an
|
|
32
|
+
* unresolved prefix.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getStorageFilePath(args: GetFilePrefixArgs): Promise<string>;
|
|
35
|
+
export {};
|
|
20
36
|
//# sourceMappingURL=getFilePrefix.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getFilePrefix.d.ts","sourceRoot":"","sources":["../../src/utilities/getFilePrefix.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAgB,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"getFilePrefix.d.ts","sourceRoot":"","sources":["../../src/utilities/getFilePrefix.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,UAAU,EAAgB,MAAM,SAAS,CAAA;AAMzF;;;;;;;;GAQG;AAEH,KAAK,iBAAiB,GAAG;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,UAAU,EAAE,gBAAgB,CAAA;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,GAAG,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,UAAU,CAAA;IAC3D,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,GAAG,EAAE,cAAc,CAAA;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AACD,wBAAsB,aAAa,CAAC,EAClC,mBAAmB,EACnB,UAAU,EACV,gBAAgB,EAChB,GAAG,EACH,QAAQ,EACR,gBAAgB,EAChB,GAAG,EACH,oBAA4B,GAC7B,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuErC;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CASjF"}
|
|
@@ -1,46 +1,90 @@
|
|
|
1
|
+
import { buildPrefixWithObjectKey } from './buildPrefixWithObjectKey.js';
|
|
2
|
+
import { buildStoragePathData, buildUploadStoragePathData } from './buildStoragePathData.js';
|
|
1
3
|
import { sanitizePrefix } from './sanitizePrefix.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
* Resolved values are passed through `sanitizePrefix`.
|
|
12
|
-
*/ export async function getFilePrefix({ clientUploadContext, collection, filename, prefixQueryParam, req }) {
|
|
13
|
-
if (typeof prefixQueryParam === 'string') {
|
|
14
|
-
return sanitizePrefix(prefixQueryParam);
|
|
4
|
+
export async function getFilePrefix({ clientUploadContext, collection, collectionPrefix, doc, filename, prefixQueryParam, req, useCompositePrefixes = false }) {
|
|
5
|
+
// The serve path already loaded and authorized this document — trust it over any
|
|
6
|
+
// client-supplied upload context or query prefix.
|
|
7
|
+
if (doc) {
|
|
8
|
+
return buildPrefixWithObjectKey({
|
|
9
|
+
objectKey: doc._objectKey,
|
|
10
|
+
prefix: doc.prefix
|
|
11
|
+
});
|
|
15
12
|
}
|
|
16
|
-
//
|
|
13
|
+
// Client uploads call handlers without a document yet. Re-contain the claimed
|
|
14
|
+
// prefix — it hasn't been verified against a signed receipt by every adapter.
|
|
17
15
|
if (clientUploadContext && typeof clientUploadContext === 'object' && 'prefix' in clientUploadContext && typeof clientUploadContext.prefix === 'string') {
|
|
18
|
-
|
|
16
|
+
const contextObjectKey = '_objectKey' in clientUploadContext ? clientUploadContext._objectKey : undefined;
|
|
17
|
+
const containedPrefix = buildUploadStoragePathData({
|
|
18
|
+
collectionPrefix,
|
|
19
|
+
docPrefix: clientUploadContext.prefix,
|
|
20
|
+
filename,
|
|
21
|
+
useCompositePrefixes
|
|
22
|
+
}).sanitizedDocPrefix;
|
|
23
|
+
return buildPrefixWithObjectKey({
|
|
24
|
+
objectKey: contextObjectKey,
|
|
25
|
+
prefix: containedPrefix
|
|
26
|
+
});
|
|
19
27
|
}
|
|
28
|
+
// Reads without a query prefix or read-access constraints skip the endpoint's document lookup.
|
|
20
29
|
const imageSizes = collection?.upload?.imageSizes || [];
|
|
30
|
+
const filenameClause = {
|
|
31
|
+
or: [
|
|
32
|
+
{
|
|
33
|
+
filename: {
|
|
34
|
+
equals: filename
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
...imageSizes.map((imageSize)=>({
|
|
38
|
+
[`sizes.${imageSize.name}.filename`]: {
|
|
39
|
+
equals: filename
|
|
40
|
+
}
|
|
41
|
+
}))
|
|
42
|
+
]
|
|
43
|
+
};
|
|
44
|
+
// Only filter by prefix when the collection persists a `prefix` field (querying a missing field throws).
|
|
45
|
+
const hasPrefixField = collection.fields?.some((field)=>'name' in field && field.name === 'prefix');
|
|
46
|
+
const where = typeof prefixQueryParam === 'string' && hasPrefixField ? {
|
|
47
|
+
and: [
|
|
48
|
+
filenameClause,
|
|
49
|
+
{
|
|
50
|
+
prefix: {
|
|
51
|
+
equals: sanitizePrefix(prefixQueryParam)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
} : filenameClause;
|
|
21
56
|
const files = await req.payload.find({
|
|
22
57
|
collection: collection.slug,
|
|
23
58
|
depth: 0,
|
|
24
59
|
draft: true,
|
|
25
60
|
limit: 1,
|
|
61
|
+
overrideAccess: false,
|
|
26
62
|
pagination: false,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}))
|
|
39
|
-
]
|
|
40
|
-
}
|
|
63
|
+
req,
|
|
64
|
+
select: {
|
|
65
|
+
_objectKey: true,
|
|
66
|
+
prefix: true
|
|
67
|
+
},
|
|
68
|
+
where
|
|
69
|
+
});
|
|
70
|
+
const found = files?.docs?.[0];
|
|
71
|
+
return buildPrefixWithObjectKey({
|
|
72
|
+
objectKey: found?._objectKey,
|
|
73
|
+
prefix: found?.prefix
|
|
41
74
|
});
|
|
42
|
-
|
|
43
|
-
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Serve-path helper: resolves the authorized folder with {@link getFilePrefix}, then returns the
|
|
78
|
+
* full object key via {@link buildStoragePathData}. Read handlers use this so a key is never built from an
|
|
79
|
+
* unresolved prefix.
|
|
80
|
+
*/ export async function getStorageFilePath(args) {
|
|
81
|
+
const docPrefix = await getFilePrefix(args);
|
|
82
|
+
return buildStoragePathData({
|
|
83
|
+
collectionPrefix: args.collectionPrefix,
|
|
84
|
+
docPrefix,
|
|
85
|
+
filename: args.filename,
|
|
86
|
+
useCompositePrefixes: args.useCompositePrefixes
|
|
87
|
+
}).storageFilePath;
|
|
44
88
|
}
|
|
45
89
|
|
|
46
90
|
//# 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 { buildStoragePathData, 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 a verified `clientUploadContext` is used\n * (its `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` is\n * read from the resolved document.\n */\n\ntype GetFilePrefixArgs = {\n clientUploadContext?: unknown\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 useCompositePrefixes?: boolean\n}\nexport async function getFilePrefix({\n clientUploadContext,\n collection,\n collectionPrefix,\n doc,\n filename,\n prefixQueryParam,\n req,\n useCompositePrefixes = false,\n}: GetFilePrefixArgs): Promise<string> {\n // The serve path already loaded and authorized this document — trust it over any\n // client-supplied upload context or query prefix.\n if (doc) {\n return buildPrefixWithObjectKey({ objectKey: doc._objectKey, prefix: doc.prefix })\n }\n\n // Client uploads 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 clientUploadContext &&\n typeof clientUploadContext === 'object' &&\n 'prefix' in clientUploadContext &&\n typeof clientUploadContext.prefix === 'string'\n ) {\n const contextObjectKey: string | undefined =\n '_objectKey' in clientUploadContext\n ? ((clientUploadContext as { _objectKey?: unknown })._objectKey as string)\n : undefined\n const containedPrefix = buildUploadStoragePathData({\n collectionPrefix,\n docPrefix: clientUploadContext.prefix,\n filename,\n useCompositePrefixes,\n }).sanitizedDocPrefix\n return buildPrefixWithObjectKey({\n objectKey: contextObjectKey,\n prefix: containedPrefix,\n })\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 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\n/**\n * Serve-path helper: resolves the authorized folder with {@link getFilePrefix}, then returns the\n * full object key via {@link buildStoragePathData}. Read handlers use this so a key is never built from an\n * unresolved prefix.\n */\nexport async function getStorageFilePath(args: GetFilePrefixArgs): Promise<string> {\n const docPrefix = await getFilePrefix(args)\n\n return buildStoragePathData({\n collectionPrefix: args.collectionPrefix,\n docPrefix,\n filename: args.filename,\n useCompositePrefixes: args.useCompositePrefixes,\n }).storageFilePath\n}\n"],"names":["buildPrefixWithObjectKey","buildStoragePathData","buildUploadStoragePathData","sanitizePrefix","getFilePrefix","clientUploadContext","collection","collectionPrefix","doc","filename","prefixQueryParam","req","useCompositePrefixes","objectKey","_objectKey","prefix","contextObjectKey","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","found","docs","getStorageFilePath","args","storageFilePath"],"mappings":"AAEA,SAASA,wBAAwB,QAAQ,gCAA+B;AACxE,SAASC,oBAAoB,EAAEC,0BAA0B,QAAQ,4BAA2B;AAC5F,SAASC,cAAc,QAAQ,sBAAqB;AA0BpD,OAAO,eAAeC,cAAc,EAClCC,mBAAmB,EACnBC,UAAU,EACVC,gBAAgB,EAChBC,GAAG,EACHC,QAAQ,EACRC,gBAAgB,EAChBC,GAAG,EACHC,uBAAuB,KAAK,EACV;IAClB,iFAAiF;IACjF,kDAAkD;IAClD,IAAIJ,KAAK;QACP,OAAOR,yBAAyB;YAAEa,WAAWL,IAAIM,UAAU;YAAEC,QAAQP,IAAIO,MAAM;QAAC;IAClF;IAEA,8EAA8E;IAC9E,8EAA8E;IAC9E,IACEV,uBACA,OAAOA,wBAAwB,YAC/B,YAAYA,uBACZ,OAAOA,oBAAoBU,MAAM,KAAK,UACtC;QACA,MAAMC,mBACJ,gBAAgBX,sBACX,AAACA,oBAAiDS,UAAU,GAC7DG;QACN,MAAMC,kBAAkBhB,2BAA2B;YACjDK;YACAY,WAAWd,oBAAoBU,MAAM;YACrCN;YACAG;QACF,GAAGQ,kBAAkB;QACrB,OAAOpB,yBAAyB;YAC9Ba,WAAWG;YACXD,QAAQG;QACV;IACF;IAEA,+FAA+F;IAC/F,MAAMG,aAAa,AAACf,YAAYgB,QAAyBD,cAAc,EAAE;IAEzE,MAAME,iBAAiB;QACrBC,IAAI;YACF;gBACEf,UAAU;oBAAEgB,QAAQhB;gBAAS;YAC/B;eACGY,WAAWK,GAAG,CAAC,CAACC,YAAe,CAAA;oBAChC,CAAC,CAAC,MAAM,EAAEA,UAAUC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE;wBAAEH,QAAQhB;oBAAS;gBAC3D,CAAA;SACD;IACH;IAEA,yGAAyG;IACzG,MAAMoB,iBAAiBvB,WAAWwB,MAAM,EAAEC,KACxC,CAACC,QAAU,UAAUA,SAASA,MAAMJ,IAAI,KAAK;IAE/C,MAAMK,QACJ,OAAOvB,qBAAqB,YAAYmB,iBACpC;QAAEK,KAAK;YAACX;YAAgB;gBAAER,QAAQ;oBAAEU,QAAQtB,eAAeO;gBAAkB;YAAE;SAAE;IAAC,IAClFa;IAEN,MAAMY,QAAQ,MAAMxB,IAAIyB,OAAO,CAACC,IAAI,CAAC;QACnC/B,YAAYA,WAAWgC,IAAI;QAC3BC,OAAO;QACPC,OAAO;QACPC,OAAO;QACPC,gBAAgB;QAChBC,YAAY;QACZhC;QACAiC,QAAQ;YACN9B,YAAY;YACZC,QAAQ;QACV;QACAkB;IACF;IAEA,MAAMY,QAAQV,OAAOW,MAAM,CAAC,EAAE;IAC9B,OAAO9C,yBAAyB;QAAEa,WAAWgC,OAAO/B;QAAYC,QAAQ8B,OAAO9B;IAAO;AACxF;AAEA;;;;CAIC,GACD,OAAO,eAAegC,mBAAmBC,IAAuB;IAC9D,MAAM7B,YAAY,MAAMf,cAAc4C;IAEtC,OAAO/C,qBAAqB;QAC1BM,kBAAkByC,KAAKzC,gBAAgB;QACvCY;QACAV,UAAUuC,KAAKvC,QAAQ;QACvBG,sBAAsBoC,KAAKpC,oBAAoB;IACjD,GAAGqC,eAAe;AACpB"}
|
|
@@ -1,80 +1,226 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { getFilePrefix } from './getFilePrefix.js';
|
|
3
|
-
const makeReq = (docs = [])=>({
|
|
3
|
+
const makeReq = ({ docs = [] } = {})=>({
|
|
4
4
|
payload: {
|
|
5
5
|
find: vi.fn().mockResolvedValue({
|
|
6
6
|
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
|
-
|
|
17
|
-
|
|
22
|
+
it('prefers an already-authorized document over a client upload context or query prefix', async ()=>{
|
|
23
|
+
const req = makeReq({
|
|
24
|
+
docs: [
|
|
25
|
+
{
|
|
26
|
+
prefix: 'db-prefix'
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
const doc = {
|
|
31
|
+
id: 1,
|
|
32
|
+
prefix: 'invoices'
|
|
33
|
+
};
|
|
34
|
+
const result = await getFilePrefix({
|
|
35
|
+
clientUploadContext: {
|
|
36
|
+
prefix: 'reference-prefix'
|
|
37
|
+
},
|
|
38
|
+
collection: makeCollection(),
|
|
39
|
+
collectionPrefix: 'media',
|
|
40
|
+
doc,
|
|
41
|
+
filename: 'logo.png',
|
|
42
|
+
prefixQueryParam: 'query-prefix',
|
|
43
|
+
req
|
|
44
|
+
});
|
|
45
|
+
expect(result).toBe('invoices');
|
|
46
|
+
expect(req.payload.find).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
it('does not fall back to a lookup when the checked document has no prefix', async ()=>{
|
|
49
|
+
const req = makeReq({
|
|
50
|
+
docs: [
|
|
51
|
+
{
|
|
52
|
+
prefix: 'db-prefix'
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
const result = await getFilePrefix({
|
|
57
|
+
collection: makeCollection(),
|
|
58
|
+
collectionPrefix: 'media',
|
|
59
|
+
doc: {
|
|
60
|
+
id: 1
|
|
61
|
+
},
|
|
62
|
+
filename: 'logo.png',
|
|
63
|
+
req
|
|
64
|
+
});
|
|
65
|
+
expect(result).toBe('');
|
|
66
|
+
expect(req.payload.find).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
it('contains a client upload context prefix beneath the collection prefix', async ()=>{
|
|
69
|
+
const req = makeReq({
|
|
70
|
+
docs: [
|
|
71
|
+
{
|
|
72
|
+
prefix: 'invoices'
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
const result = await getFilePrefix({
|
|
77
|
+
clientUploadContext: {
|
|
78
|
+
prefix: 'invoices'
|
|
79
|
+
},
|
|
80
|
+
collection: makeCollection(),
|
|
81
|
+
collectionPrefix: 'media',
|
|
82
|
+
filename: 'file.png',
|
|
83
|
+
req
|
|
84
|
+
});
|
|
85
|
+
expect(result).toBe('media/invoices');
|
|
86
|
+
expect(req.payload.find).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
describe('authorized document (trusted)', ()=>{
|
|
89
|
+
it('folds the object key into the document prefix and skips the database', async ()=>{
|
|
90
|
+
const req = makeReq({
|
|
91
|
+
docs: [
|
|
92
|
+
{
|
|
93
|
+
prefix: 'db-prefix'
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
});
|
|
18
97
|
const result = await getFilePrefix({
|
|
19
98
|
collection: makeCollection(),
|
|
20
|
-
|
|
99
|
+
doc: {
|
|
100
|
+
id: 1,
|
|
101
|
+
_objectKey: 'abc123',
|
|
102
|
+
prefix: 'doc-prefix'
|
|
103
|
+
},
|
|
21
104
|
filename: 'logo.png',
|
|
22
105
|
req
|
|
23
106
|
});
|
|
24
|
-
expect(result).toBe('
|
|
107
|
+
expect(result).toBe('doc-prefix/abc123');
|
|
25
108
|
expect(req.payload.find).not.toHaveBeenCalled();
|
|
26
109
|
});
|
|
27
|
-
it('
|
|
110
|
+
it('returns the document prefix alone when it has no object key', async ()=>{
|
|
28
111
|
const req = makeReq();
|
|
29
112
|
const result = await getFilePrefix({
|
|
30
113
|
collection: makeCollection(),
|
|
31
|
-
|
|
114
|
+
doc: {
|
|
115
|
+
id: 1,
|
|
116
|
+
prefix: 'doc-prefix'
|
|
117
|
+
},
|
|
32
118
|
filename: 'logo.png',
|
|
33
119
|
req
|
|
34
120
|
});
|
|
35
|
-
expect(result).toBe('');
|
|
36
|
-
expect(req.payload.find).not.toHaveBeenCalled();
|
|
121
|
+
expect(result).toBe('doc-prefix');
|
|
37
122
|
});
|
|
38
|
-
it('
|
|
123
|
+
it('sanitizes the document prefix', async ()=>{
|
|
39
124
|
const req = makeReq();
|
|
40
125
|
const result = await getFilePrefix({
|
|
41
126
|
collection: makeCollection(),
|
|
42
|
-
|
|
127
|
+
doc: {
|
|
128
|
+
id: 1,
|
|
129
|
+
prefix: '../secret'
|
|
130
|
+
},
|
|
43
131
|
filename: 'logo.png',
|
|
44
132
|
req
|
|
45
133
|
});
|
|
46
|
-
expect(result).toBe('');
|
|
47
|
-
expect(req.payload.find).not.toHaveBeenCalled();
|
|
134
|
+
expect(result).toBe('secret');
|
|
48
135
|
});
|
|
49
|
-
|
|
136
|
+
});
|
|
137
|
+
describe('verified clientUploadContext (trusted)', ()=>{
|
|
138
|
+
it('folds the server-owned object key into the prefix and skips the database', async ()=>{
|
|
50
139
|
const req = makeReq();
|
|
51
140
|
const result = await getFilePrefix({
|
|
141
|
+
clientUploadContext: {
|
|
142
|
+
_objectKey: 'abc123',
|
|
143
|
+
prefix: 'documents'
|
|
144
|
+
},
|
|
52
145
|
collection: makeCollection(),
|
|
53
|
-
prefixQueryParam: '%E0%A4%A',
|
|
54
146
|
filename: 'logo.png',
|
|
55
147
|
req
|
|
56
148
|
});
|
|
57
|
-
expect(result).toBe('');
|
|
149
|
+
expect(result).toBe('documents/abc123');
|
|
58
150
|
expect(req.payload.find).not.toHaveBeenCalled();
|
|
59
151
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
it('should query the database when prefixQueryParam is not provided', async ()=>{
|
|
63
|
-
const req = makeReq([
|
|
64
|
-
{
|
|
65
|
-
prefix: 'db-prefix'
|
|
66
|
-
}
|
|
67
|
-
]);
|
|
152
|
+
it('returns the prefix alone when the context has no object key', async ()=>{
|
|
153
|
+
const req = makeReq();
|
|
68
154
|
const result = await getFilePrefix({
|
|
155
|
+
clientUploadContext: {
|
|
156
|
+
prefix: 'documents'
|
|
157
|
+
},
|
|
69
158
|
collection: makeCollection(),
|
|
70
159
|
filename: 'logo.png',
|
|
71
160
|
req
|
|
72
161
|
});
|
|
73
|
-
expect(result).toBe('
|
|
162
|
+
expect(result).toBe('documents');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
describe('access-controlled database resolution', ()=>{
|
|
166
|
+
it('does NOT short-circuit on a client-supplied prefix — the access-controlled query still runs', async ()=>{
|
|
167
|
+
const req = makeReq({
|
|
168
|
+
docs: [
|
|
169
|
+
{
|
|
170
|
+
_objectKey: 'abc123',
|
|
171
|
+
prefix: 'documents'
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
await getFilePrefix({
|
|
176
|
+
collection: makeCollection({
|
|
177
|
+
hasPrefixField: true
|
|
178
|
+
}),
|
|
179
|
+
filename: 'logo.png',
|
|
180
|
+
prefixQueryParam: 'documents',
|
|
181
|
+
req
|
|
182
|
+
});
|
|
183
|
+
// A client-supplied prefix must not bypass the access-controlled lookup.
|
|
74
184
|
expect(req.payload.find).toHaveBeenCalledOnce();
|
|
75
185
|
});
|
|
76
|
-
it('
|
|
77
|
-
const req = makeReq(
|
|
186
|
+
it('queries with access control enabled', async ()=>{
|
|
187
|
+
const req = makeReq({
|
|
188
|
+
docs: [
|
|
189
|
+
{
|
|
190
|
+
prefix: 'db-prefix'
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
await getFilePrefix({
|
|
195
|
+
collection: makeCollection(),
|
|
196
|
+
filename: 'logo.png',
|
|
197
|
+
req
|
|
198
|
+
});
|
|
199
|
+
expect(req.payload.find).toHaveBeenCalledWith(expect.objectContaining({
|
|
200
|
+
overrideAccess: false,
|
|
201
|
+
req
|
|
202
|
+
}));
|
|
203
|
+
});
|
|
204
|
+
it('folds the stored object key into the resolved prefix', async ()=>{
|
|
205
|
+
const req = makeReq({
|
|
206
|
+
docs: [
|
|
207
|
+
{
|
|
208
|
+
_objectKey: 'abc123',
|
|
209
|
+
prefix: 'db-prefix'
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
});
|
|
213
|
+
const result = await getFilePrefix({
|
|
214
|
+
collection: makeCollection(),
|
|
215
|
+
filename: 'logo.png',
|
|
216
|
+
req
|
|
217
|
+
});
|
|
218
|
+
expect(result).toBe('db-prefix/abc123');
|
|
219
|
+
});
|
|
220
|
+
it('returns an empty string when no document is found', async ()=>{
|
|
221
|
+
const req = makeReq({
|
|
222
|
+
docs: []
|
|
223
|
+
});
|
|
78
224
|
const result = await getFilePrefix({
|
|
79
225
|
collection: makeCollection(),
|
|
80
226
|
filename: 'logo.png',
|
|
@@ -82,12 +228,14 @@ describe('getFilePrefix', ()=>{
|
|
|
82
228
|
});
|
|
83
229
|
expect(result).toBe('');
|
|
84
230
|
});
|
|
85
|
-
it('
|
|
86
|
-
const req = makeReq(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
231
|
+
it('prioritizes a verified clientUploadContext over the database query', async ()=>{
|
|
232
|
+
const req = makeReq({
|
|
233
|
+
docs: [
|
|
234
|
+
{
|
|
235
|
+
prefix: 'db-prefix'
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
});
|
|
91
239
|
const result = await getFilePrefix({
|
|
92
240
|
clientUploadContext: {
|
|
93
241
|
prefix: 'context-prefix'
|
|
@@ -100,6 +248,44 @@ describe('getFilePrefix', ()=>{
|
|
|
100
248
|
expect(req.payload.find).not.toHaveBeenCalled();
|
|
101
249
|
});
|
|
102
250
|
});
|
|
251
|
+
describe('client prefix as a disambiguation filter only', ()=>{
|
|
252
|
+
it('adds the prefix filter when the collection persists a prefix field', async ()=>{
|
|
253
|
+
const req = makeReq({
|
|
254
|
+
docs: [
|
|
255
|
+
{
|
|
256
|
+
prefix: 'documents'
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
});
|
|
260
|
+
await getFilePrefix({
|
|
261
|
+
collection: makeCollection({
|
|
262
|
+
hasPrefixField: true
|
|
263
|
+
}),
|
|
264
|
+
filename: 'logo.png',
|
|
265
|
+
prefixQueryParam: 'documents',
|
|
266
|
+
req
|
|
267
|
+
});
|
|
268
|
+
expect(whereOf(req)).toContain('prefix');
|
|
269
|
+
});
|
|
270
|
+
it('omits the prefix filter when the collection has no prefix field (avoids querying a missing field)', async ()=>{
|
|
271
|
+
const req = makeReq({
|
|
272
|
+
docs: [
|
|
273
|
+
{
|
|
274
|
+
prefix: ''
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
});
|
|
278
|
+
await getFilePrefix({
|
|
279
|
+
collection: makeCollection({
|
|
280
|
+
hasPrefixField: false
|
|
281
|
+
}),
|
|
282
|
+
filename: 'logo.png',
|
|
283
|
+
prefixQueryParam: 'anything',
|
|
284
|
+
req
|
|
285
|
+
});
|
|
286
|
+
expect(whereOf(req)).not.toContain('prefix');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
103
289
|
});
|
|
104
290
|
|
|
105
291
|
//# sourceMappingURL=getFilePrefix.spec.js.map
|
|
@@ -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 clientUploadContext prefix over database query', async () => {\n const req = makeReq([{ prefix: 'db-prefix' }])\n\n const result = await getFilePrefix({\n clientUploadContext: { prefix: 'context-prefix' },\n collection: makeCollection(),\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","clientUploadContext"],"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,oEAAoE;YACrE,MAAMY,MAAMT,QAAQ;gBAAC;oBAAEiB,QAAQ;gBAAY;aAAE;YAE7C,MAAMP,SAAS,MAAMX,cAAc;gBACjCoB,qBAAqB;oBAAEF,QAAQ;gBAAiB;gBAChDN,YAAYL;gBACZO,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 = [] }: { 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 a client upload context or query prefix', async () => {\n const req = makeReq({ docs: [{ prefix: 'db-prefix' }] })\n const doc = { id: 1, prefix: 'invoices' }\n\n const result = await getFilePrefix({\n clientUploadContext: { prefix: 'reference-prefix' },\n collection: makeCollection(),\n collectionPrefix: 'media',\n doc,\n filename: 'logo.png',\n prefixQueryParam: 'query-prefix',\n req,\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({ docs: [{ 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 a client upload context prefix beneath the collection prefix', async () => {\n const req = makeReq({ docs: [{ prefix: 'invoices' }] })\n\n const result = await getFilePrefix({\n clientUploadContext: { prefix: 'invoices' },\n collection: makeCollection(),\n collectionPrefix: 'media',\n filename: 'file.png',\n req,\n })\n\n expect(result).toBe('media/invoices')\n expect(req.payload.find).not.toHaveBeenCalled()\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({ docs: [{ 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('verified clientUploadContext (trusted)', () => {\n it('folds the server-owned object key into the prefix and skips the database', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n clientUploadContext: { _objectKey: 'abc123', prefix: 'documents' },\n collection: makeCollection(),\n filename: 'logo.png',\n req,\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 context has no object key', async () => {\n const req = makeReq()\n\n const result = await getFilePrefix({\n clientUploadContext: { prefix: 'documents' },\n collection: makeCollection(),\n filename: 'logo.png',\n req,\n })\n\n expect(result).toBe('documents')\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({ docs: [{ _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 // A client-supplied prefix must not bypass the access-controlled lookup.\n expect(req.payload.find).toHaveBeenCalledOnce()\n })\n\n it('queries with access control enabled', async () => {\n const req = makeReq({ docs: [{ 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 }),\n )\n })\n\n it('folds the stored object key into the resolved prefix', async () => {\n const req = makeReq({ docs: [{ _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({ docs: [] })\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('prioritizes a verified clientUploadContext over the database query', async () => {\n const req = makeReq({ docs: [{ prefix: 'db-prefix' }] })\n\n const result = await getFilePrefix({\n clientUploadContext: { prefix: 'context-prefix' },\n collection: makeCollection(),\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 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({ docs: [{ 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 (avoids querying a missing field)', async () => {\n const req = makeReq({ docs: [{ 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","clientUploadContext","collection","collectionPrefix","filename","prefixQueryParam","toBe","not","toHaveBeenCalled","_objectKey","toHaveBeenCalledOnce","toHaveBeenCalledWith","objectContaining","overrideAccess","toContain"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { ClientUploadContext } 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 per-upload segment is persisted in `_objectKey` (not `prefix`), so the stored `prefix`
|
|
17
|
+
* and `filename` stay clean.
|
|
18
|
+
*
|
|
15
19
|
* The resolved `sanitizedFilename` is returned so the browser-side handler
|
|
16
20
|
* can update the form via `updateFilename`.
|
|
21
|
+
*
|
|
22
|
+
* The semantic prefix is resolved with {@link buildUploadPrefix} and the final key with
|
|
23
|
+
* {@link buildUploadStoragePathData}, so the receipt only ever signs a key beneath the configured
|
|
24
|
+
* collection prefix.
|
|
17
25
|
*/
|
|
18
26
|
export declare function resolveSignedURLKey({ collectionPrefix, collectionSlug, docPrefix, filename, req, useCompositePrefixes, }: Args): Promise<{
|
|
27
|
+
_objectKey: `${string}-${string}-${string}-${string}-${string}`;
|
|
28
|
+
clientUploadContext: ClientUploadContext;
|
|
19
29
|
fileKey: string;
|
|
20
30
|
sanitizedDocPrefix: string;
|
|
21
31
|
sanitizedFilename: string;
|
|
32
|
+
storageFilePath: string;
|
|
22
33
|
}>;
|
|
23
34
|
export {};
|
|
24
35
|
//# sourceMappingURL=resolveSignedURLKey.d.ts.map
|