@strapi/provider-upload-aws-s3 5.35.0 → 5.36.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/dist/index.js CHANGED
@@ -6,11 +6,107 @@ var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
6
6
  var libStorage = require('@aws-sdk/lib-storage');
7
7
  var utils = require('./utils.js');
8
8
 
9
- const assertUrlProtocol = (url)=>{
10
- // Regex to test protocol like "http://", "https://"
11
- return /^\w*:\/\//.test(url);
9
+ /**
10
+ * Validates that a URL uses HTTP or HTTPS protocol.
11
+ * Rejects dangerous protocols like file://, javascript:, data:, etc.
12
+ */ const assertUrlProtocol = (url)=>{
13
+ return /^https?:\/\//.test(url);
12
14
  };
13
- const getConfig = ({ baseUrl, rootPath, s3Options, ...legacyS3Options })=>{
15
+ /**
16
+ * Sanitizes a path component to prevent path traversal attacks.
17
+ * Removes directory traversal sequences and normalizes the path.
18
+ */ const sanitizePathComponent = (component)=>{
19
+ if (!component) return '';
20
+ return component.replace(/\.\./g, '').replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/');
21
+ };
22
+ /**
23
+ * Maps the provider checksum algorithm to the AWS SDK checksum algorithm.
24
+ */ const mapChecksumAlgorithm = (algorithm)=>{
25
+ if (!algorithm) return undefined;
26
+ const mapping = {
27
+ CRC32: clientS3.ChecksumAlgorithm.CRC32,
28
+ CRC32C: clientS3.ChecksumAlgorithm.CRC32C,
29
+ SHA1: clientS3.ChecksumAlgorithm.SHA1,
30
+ SHA256: clientS3.ChecksumAlgorithm.SHA256,
31
+ CRC64NVME: clientS3.ChecksumAlgorithm.CRC64NVME
32
+ };
33
+ return mapping[algorithm];
34
+ };
35
+ /**
36
+ * Maps the provider storage class to the AWS SDK storage class.
37
+ */ const mapStorageClass = (storageClass)=>{
38
+ if (!storageClass) return undefined;
39
+ const mapping = {
40
+ STANDARD: clientS3.StorageClass.STANDARD,
41
+ REDUCED_REDUNDANCY: clientS3.StorageClass.REDUCED_REDUNDANCY,
42
+ STANDARD_IA: clientS3.StorageClass.STANDARD_IA,
43
+ ONEZONE_IA: clientS3.StorageClass.ONEZONE_IA,
44
+ INTELLIGENT_TIERING: clientS3.StorageClass.INTELLIGENT_TIERING,
45
+ GLACIER: clientS3.StorageClass.GLACIER,
46
+ DEEP_ARCHIVE: clientS3.StorageClass.DEEP_ARCHIVE,
47
+ GLACIER_IR: clientS3.StorageClass.GLACIER_IR
48
+ };
49
+ return mapping[storageClass];
50
+ };
51
+ /**
52
+ * Maps the provider encryption type to the AWS SDK server-side encryption.
53
+ */ const mapServerSideEncryption = (type)=>{
54
+ if (!type) return undefined;
55
+ const mapping = {
56
+ AES256: clientS3.ServerSideEncryption.AES256,
57
+ 'aws:kms': clientS3.ServerSideEncryption.aws_kms,
58
+ 'aws:kms:dsse': clientS3.ServerSideEncryption.aws_kms_dsse
59
+ };
60
+ return mapping[type];
61
+ };
62
+ /**
63
+ * Converts a tags object to the S3 Tagging header format.
64
+ * Format: key1=value1&key2=value2
65
+ */ const formatTagsForHeader = (tags)=>{
66
+ if (!tags || Object.keys(tags).length === 0) return undefined;
67
+ return Object.entries(tags).map(([key, value])=>`${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&');
68
+ };
69
+ /**
70
+ * Checks if the endpoint appears to be a non-AWS S3-compatible provider.
71
+ */ const isNonAwsEndpoint = (endpoint)=>{
72
+ if (!endpoint) return false;
73
+ const awsPatterns = [
74
+ /\.amazonaws\.com$/i,
75
+ /\.amazonaws\.com\.cn$/i
76
+ ];
77
+ return !awsPatterns.some((pattern)=>pattern.test(endpoint));
78
+ };
79
+ /**
80
+ * Validates provider configuration and emits warnings for potential compatibility issues.
81
+ */ const validateProviderConfig = (providerConfig, s3Options)=>{
82
+ if (!providerConfig) return;
83
+ const endpoint = s3Options?.endpoint?.toString() || '';
84
+ const isNonAws = isNonAwsEndpoint(endpoint);
85
+ // Warn about AWS-specific features when using non-AWS endpoints
86
+ if (isNonAws) {
87
+ if (providerConfig.storageClass) {
88
+ process.emitWarning(`Storage class '${providerConfig.storageClass}' is AWS S3-specific and may be ignored by your S3-compatible provider.`);
89
+ }
90
+ if (providerConfig.encryption?.type && providerConfig.encryption.type !== 'AES256') {
91
+ process.emitWarning(`Encryption type '${providerConfig.encryption.type}' is AWS S3-specific. Consider using 'AES256' for better compatibility.`);
92
+ }
93
+ }
94
+ // Validate multipart configuration
95
+ if (providerConfig.multipart?.partSize) {
96
+ const minPartSize = 5 * 1024 * 1024; // 5MB
97
+ const maxPartSize = 5 * 1024 * 1024 * 1024; // 5GB
98
+ if (providerConfig.multipart.partSize < minPartSize) {
99
+ process.emitWarning(`Multipart partSize ${providerConfig.multipart.partSize} is below the minimum of 5MB. This may cause upload failures.`);
100
+ }
101
+ if (providerConfig.multipart.partSize > maxPartSize) {
102
+ process.emitWarning(`Multipart partSize ${providerConfig.multipart.partSize} exceeds the maximum of 5GB. This may cause upload failures.`);
103
+ }
104
+ }
105
+ if (providerConfig.multipart?.queueSize && providerConfig.multipart.queueSize > 16) {
106
+ process.emitWarning(`Multipart queueSize ${providerConfig.multipart.queueSize} is high and may cause memory issues. Consider using 4-8.`);
107
+ }
108
+ };
109
+ const getConfig = ({ s3Options, legacyS3Options })=>{
14
110
  if (Object.keys(legacyS3Options).length > 0) {
15
111
  process.emitWarning("S3 configuration options passed at root level of the plugin's providerOptions is deprecated and will be removed in a future release. Please wrap them inside the 's3Options:{}' property.");
16
112
  }
@@ -25,65 +121,219 @@ const getConfig = ({ baseUrl, rootPath, s3Options, ...legacyS3Options })=>{
25
121
  credentials
26
122
  } : {}
27
123
  };
28
- config.params.ACL = fp.getOr(clientS3.ObjectCannedACL.public_read, [
29
- 'params',
30
- 'ACL'
31
- ], config);
124
+ if (config.params !== undefined) {
125
+ // Only set default ACL when ACL is not explicitly present in params.
126
+ // Since April 2023, new AWS S3 buckets have ACLs disabled by default
127
+ // ("Bucket owner enforced"). Sending an ACL header to such buckets
128
+ // throws AccessControlListNotSupported. To disable ACLs, users should
129
+ // simply not include ACL in their params configuration.
130
+ if (!('ACL' in config.params)) {
131
+ config.params.ACL = clientS3.ObjectCannedACL.public_read;
132
+ }
133
+ } else {
134
+ throw new Error('Upload AWS S3 provider: `params` are required in the config object');
135
+ }
32
136
  return config;
33
137
  };
34
138
  var index = {
35
- init ({ baseUrl, rootPath, s3Options, ...legacyS3Options }) {
139
+ init ({ baseUrl, rootPath, s3Options, providerConfig, ...legacyS3Options }) {
140
+ // Validate configuration and emit warnings for potential issues
141
+ validateProviderConfig(providerConfig, s3Options);
36
142
  // TODO V5 change config structure to avoid having to do this
37
143
  const config = getConfig({
38
- baseUrl,
39
- rootPath,
40
- s3Options,
41
- ...legacyS3Options
144
+ s3Options: s3Options,
145
+ legacyS3Options
42
146
  });
43
147
  const s3Client = new clientS3.S3Client(config);
44
148
  const filePrefix = rootPath ? `${rootPath.replace(/\/+$/, '')}/` : '';
45
149
  const getFileKey = (file)=>{
46
- const path = file.path ? `${file.path}/` : '';
47
- return `${filePrefix}${path}${file.hash}${file.ext}`;
150
+ const sanitizedPath = sanitizePathComponent(file.path);
151
+ const path = sanitizedPath ? `${sanitizedPath}/` : '';
152
+ const sanitizedHash = sanitizePathComponent(file.hash);
153
+ const sanitizedExt = file.ext ? file.ext.replace(/[^a-zA-Z0-9.]/g, '') : '';
154
+ return `${filePrefix}${path}${sanitizedHash}${sanitizedExt}`;
155
+ };
156
+ /**
157
+ * Builds the upload parameters including all configured features.
158
+ */ const buildUploadParams = (file, fileKey, customParams = {})=>{
159
+ const params = {
160
+ Bucket: config.params.Bucket,
161
+ Key: fileKey,
162
+ Body: file.stream || Buffer.from(file.buffer, 'binary'),
163
+ // ACL is optional to support providers like Cloudflare R2 that don't support ACLs.
164
+ // Set params.ACL to undefined or omit it entirely to disable ACL headers.
165
+ ...config.params.ACL ? {
166
+ ACL: config.params.ACL
167
+ } : {},
168
+ ContentType: file.mime
169
+ };
170
+ // Checksum validation
171
+ const checksumAlgorithm = mapChecksumAlgorithm(providerConfig?.checksumAlgorithm);
172
+ if (checksumAlgorithm) {
173
+ params.ChecksumAlgorithm = checksumAlgorithm;
174
+ }
175
+ // Conditional writes - prevent overwrite
176
+ if (providerConfig?.preventOverwrite) {
177
+ params.IfNoneMatch = '*';
178
+ }
179
+ // Storage class
180
+ const storageClass = mapStorageClass(providerConfig?.storageClass);
181
+ if (storageClass) {
182
+ params.StorageClass = storageClass;
183
+ }
184
+ // Server-side encryption
185
+ if (providerConfig?.encryption) {
186
+ const sse = mapServerSideEncryption(providerConfig.encryption.type);
187
+ if (sse) {
188
+ params.ServerSideEncryption = sse;
189
+ if (providerConfig.encryption.kmsKeyId) {
190
+ params.SSEKMSKeyId = providerConfig.encryption.kmsKeyId;
191
+ }
192
+ }
193
+ }
194
+ // Object tagging
195
+ const tagging = formatTagsForHeader(providerConfig?.tags);
196
+ if (tagging) {
197
+ params.Tagging = tagging;
198
+ }
199
+ // Merge customParams but preserve critical security parameters
200
+ // Bucket, Key, and Body must not be overridden by customParams
201
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
202
+ const { Bucket, Key, Body, ...safeCustomParams } = customParams;
203
+ return {
204
+ ...params,
205
+ ...safeCustomParams
206
+ };
207
+ };
208
+ /**
209
+ * Constructs the correct file URL.
210
+ * Handles S3-compatible providers that return incorrect Location formats.
211
+ */ const constructFileUrl = (fileKey, uploadLocation)=>{
212
+ // Priority 1: Use baseUrl if configured (CDN or custom domain)
213
+ if (baseUrl) {
214
+ const cleanBase = baseUrl.replace(/\/+$/, '');
215
+ return `${cleanBase}/${fileKey}`;
216
+ }
217
+ // Priority 2: Construct URL from endpoint if configured
218
+ // This fixes issues with S3-compatible providers (IONOS, MinIO, etc.)
219
+ // that return Location in incorrect format for multipart uploads
220
+ const endpoint = config.endpoint?.toString();
221
+ if (endpoint) {
222
+ const endpointUrl = endpoint.startsWith('http') ? endpoint : `https://${endpoint}`;
223
+ const cleanEndpoint = endpointUrl.replace(/\/+$/, '');
224
+ return `${cleanEndpoint}/${config.params.Bucket}/${fileKey}`;
225
+ }
226
+ // Priority 3: Use the Location from S3 response
227
+ if (assertUrlProtocol(uploadLocation)) {
228
+ return uploadLocation;
229
+ }
230
+ // Priority 4: Prepend https if protocol is missing
231
+ return `https://${uploadLocation}`;
48
232
  };
49
233
  const upload = async (file, customParams = {})=>{
50
234
  const fileKey = getFileKey(file);
51
- const uploadObj = new libStorage.Upload({
235
+ const params = buildUploadParams(file, fileKey, customParams);
236
+ const uploadOptions = {
52
237
  client: s3Client,
53
- params: {
54
- Bucket: config.params.Bucket,
55
- Key: fileKey,
56
- Body: file.stream || Buffer.from(file.buffer, 'binary'),
57
- ACL: config.params.ACL,
58
- ContentType: file.mime,
59
- ...customParams
238
+ params
239
+ };
240
+ // Multipart configuration
241
+ if (providerConfig?.multipart) {
242
+ if (providerConfig.multipart.partSize) {
243
+ uploadOptions.partSize = providerConfig.multipart.partSize;
244
+ }
245
+ if (providerConfig.multipart.queueSize) {
246
+ uploadOptions.queueSize = providerConfig.multipart.queueSize;
247
+ }
248
+ if (providerConfig.multipart.leavePartsOnError !== undefined) {
249
+ uploadOptions.leavePartsOnError = providerConfig.multipart.leavePartsOnError;
60
250
  }
251
+ }
252
+ const uploadObj = new libStorage.Upload(uploadOptions);
253
+ const result = await uploadObj.done();
254
+ // Construct the correct URL (handles S3-compatible provider quirks)
255
+ file.url = constructFileUrl(fileKey, result.Location);
256
+ // Store ETag for potential future conditional updates
257
+ if (result.ETag) {
258
+ file.etag = result.ETag.replace(/"/g, '');
259
+ }
260
+ };
261
+ /**
262
+ * Uploads a file only if the existing object matches the expected ETag.
263
+ * This implements optimistic locking to prevent lost updates.
264
+ */ const uploadIfMatch = async (file, expectedETag, customParams = {})=>{
265
+ const fileKey = getFileKey(file);
266
+ const params = buildUploadParams(file, fileKey, {
267
+ ...customParams,
268
+ IfMatch: expectedETag
269
+ });
270
+ const uploadObj = new libStorage.Upload({
271
+ client: s3Client,
272
+ params
61
273
  });
62
- const upload = await uploadObj.done();
63
- if (assertUrlProtocol(upload.Location)) {
64
- file.url = baseUrl ? `${baseUrl}/${fileKey}` : upload.Location;
65
- } else {
66
- // Default protocol to https protocol
67
- file.url = `https://${upload.Location}`;
274
+ const result = await uploadObj.done();
275
+ // Construct the correct URL (handles S3-compatible provider quirks)
276
+ file.url = constructFileUrl(fileKey, result.Location);
277
+ if (result.ETag) {
278
+ file.etag = result.ETag.replace(/"/g, '');
279
+ }
280
+ };
281
+ /**
282
+ * Retrieves metadata for an object including its ETag.
283
+ */ const getObjectMetadata = async (file)=>{
284
+ const command = new clientS3.HeadObjectCommand({
285
+ Bucket: config.params.Bucket,
286
+ Key: getFileKey(file)
287
+ });
288
+ const response = await s3Client.send(command);
289
+ return {
290
+ etag: response.ETag?.replace(/"/g, ''),
291
+ contentLength: response.ContentLength,
292
+ contentType: response.ContentType,
293
+ lastModified: response.LastModified,
294
+ storageClass: response.StorageClass,
295
+ serverSideEncryption: response.ServerSideEncryption
296
+ };
297
+ };
298
+ /**
299
+ * Checks if an object exists in the bucket.
300
+ */ const objectExists = async (file)=>{
301
+ try {
302
+ await getObjectMetadata(file);
303
+ return true;
304
+ } catch (error) {
305
+ if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
306
+ return false;
307
+ }
308
+ throw error;
68
309
  }
69
310
  };
70
311
  return {
71
- isPrivate () {
312
+ /**
313
+ * Returns whether the bucket is configured with private ACL.
314
+ */ isPrivate () {
72
315
  return config.params.ACL === 'private';
73
316
  },
74
- async getSignedUrl (file, customParams) {
75
- // Do not sign the url if it does not come from the same bucket.
317
+ /**
318
+ * Returns the current provider configuration.
319
+ */ getProviderConfig () {
320
+ return providerConfig;
321
+ },
322
+ /**
323
+ * Generates a signed URL for accessing a private object.
324
+ */ async getSignedUrl (file, customParams) {
76
325
  if (!utils.isUrlFromBucket(file.url, config.params.Bucket, baseUrl)) {
77
326
  return {
78
327
  url: file.url
79
328
  };
80
329
  }
81
330
  const fileKey = getFileKey(file);
82
- const url = await s3RequestPresigner.getSignedUrl(// @ts-expect-error - TODO fix client type
83
- s3Client, new clientS3.GetObjectCommand({
331
+ // Spread customParams first, then override with secure values
332
+ // This prevents malicious override of Bucket and Key
333
+ const url = await s3RequestPresigner.getSignedUrl(s3Client, new clientS3.GetObjectCommand({
334
+ ...customParams,
84
335
  Bucket: config.params.Bucket,
85
- Key: fileKey,
86
- ...customParams
336
+ Key: fileKey
87
337
  }), {
88
338
  expiresIn: fp.getOr(15 * 60, [
89
339
  'params',
@@ -94,17 +344,42 @@ var index = {
94
344
  url
95
345
  };
96
346
  },
97
- uploadStream (file, customParams = {}) {
347
+ /**
348
+ * Uploads a file using streaming.
349
+ */ uploadStream (file, customParams = {}) {
98
350
  return upload(file, customParams);
99
351
  },
100
- upload (file, customParams = {}) {
352
+ /**
353
+ * Uploads a file to S3.
354
+ */ upload (file, customParams = {}) {
101
355
  return upload(file, customParams);
102
356
  },
103
- delete (file, customParams = {}) {
357
+ /**
358
+ * Uploads a file only if it matches the expected ETag (optimistic locking).
359
+ * Throws PreconditionFailed error if ETag does not match.
360
+ */ uploadIfMatch (file, expectedETag, customParams = {}) {
361
+ return uploadIfMatch(file, expectedETag, customParams);
362
+ },
363
+ /**
364
+ * Retrieves object metadata including ETag.
365
+ */ getObjectMetadata (file) {
366
+ return getObjectMetadata(file);
367
+ },
368
+ /**
369
+ * Checks if an object exists in the bucket.
370
+ */ objectExists (file) {
371
+ return objectExists(file);
372
+ },
373
+ /**
374
+ * Deletes an object from S3.
375
+ */ delete (file, customParams = {}) {
376
+ // Spread customParams first, then override with secure values
377
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
378
+ const { Bucket, Key, ...safeParams } = customParams;
104
379
  const command = new clientS3.DeleteObjectCommand({
380
+ ...safeParams,
105
381
  Bucket: config.params.Bucket,
106
- Key: getFileKey(file),
107
- ...customParams
382
+ Key: getFileKey(file)
108
383
  });
109
384
  return s3Client.send(command);
110
385
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { ReadStream } from 'node:fs';\nimport { getOr } from 'lodash/fp';\nimport {\n S3Client,\n GetObjectCommand,\n DeleteObjectCommand,\n DeleteObjectCommandOutput,\n PutObjectCommandInput,\n CompleteMultipartUploadCommandOutput,\n AbortMultipartUploadCommandOutput,\n S3ClientConfig,\n ObjectCannedACL,\n} from '@aws-sdk/client-s3';\nimport type { AwsCredentialIdentity } from '@aws-sdk/types';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { Upload } from '@aws-sdk/lib-storage';\nimport { extractCredentials, isUrlFromBucket } from './utils';\n\nexport interface File {\n name: string;\n alternativeText?: string;\n caption?: string;\n width?: number;\n height?: number;\n formats?: Record<string, unknown>;\n hash: string;\n ext?: string;\n mime: string;\n size: number;\n sizeInBytes: number;\n url: string;\n previewUrl?: string;\n path?: string;\n provider?: string;\n provider_metadata?: Record<string, unknown>;\n stream?: ReadStream;\n buffer?: Buffer;\n}\n\nexport type UploadCommandOutput = (\n | CompleteMultipartUploadCommandOutput\n | AbortMultipartUploadCommandOutput\n) & {\n Location: string;\n};\n\nexport interface AWSParams {\n Bucket: string; // making it required\n ACL?: ObjectCannedACL;\n signedUrlExpires?: number;\n}\n\nexport interface DefaultOptions extends S3ClientConfig {\n // TODO Remove this in V5\n accessKeyId?: AwsCredentialIdentity['accessKeyId'];\n secretAccessKey?: AwsCredentialIdentity['secretAccessKey'];\n // Keep this for V5\n credentials?: AwsCredentialIdentity;\n params?: AWSParams;\n [k: string]: any;\n}\n\nexport type InitOptions = (DefaultOptions | { s3Options: DefaultOptions }) & {\n baseUrl?: string;\n rootPath?: string;\n [k: string]: any;\n};\n\nconst assertUrlProtocol = (url: string) => {\n // Regex to test protocol like \"http://\", \"https://\"\n return /^\\w*:\\/\\//.test(url);\n};\n\nconst getConfig = ({ baseUrl, rootPath, s3Options, ...legacyS3Options }: InitOptions) => {\n if (Object.keys(legacyS3Options).length > 0) {\n process.emitWarning(\n \"S3 configuration options passed at root level of the plugin's providerOptions is deprecated and will be removed in a future release. Please wrap them inside the 's3Options:{}' property.\"\n );\n }\n const credentials = extractCredentials({ s3Options, ...legacyS3Options });\n const config = {\n ...s3Options,\n ...legacyS3Options,\n ...(credentials ? { credentials } : {}),\n };\n\n config.params.ACL = getOr(ObjectCannedACL.public_read, ['params', 'ACL'], config);\n\n return config;\n};\n\nexport default {\n init({ baseUrl, rootPath, s3Options, ...legacyS3Options }: InitOptions) {\n // TODO V5 change config structure to avoid having to do this\n const config = getConfig({ baseUrl, rootPath, s3Options, ...legacyS3Options });\n const s3Client = new S3Client(config);\n const filePrefix = rootPath ? `${rootPath.replace(/\\/+$/, '')}/` : '';\n\n const getFileKey = (file: File) => {\n const path = file.path ? `${file.path}/` : '';\n return `${filePrefix}${path}${file.hash}${file.ext}`;\n };\n\n const upload = async (file: File, customParams: Partial<PutObjectCommandInput> = {}) => {\n const fileKey = getFileKey(file);\n const uploadObj = new Upload({\n client: s3Client,\n params: {\n Bucket: config.params.Bucket,\n Key: fileKey,\n Body: file.stream || Buffer.from(file.buffer as any, 'binary'),\n ACL: config.params.ACL,\n ContentType: file.mime,\n ...customParams,\n },\n });\n\n const upload = (await uploadObj.done()) as UploadCommandOutput;\n\n if (assertUrlProtocol(upload.Location)) {\n file.url = baseUrl ? `${baseUrl}/${fileKey}` : upload.Location;\n } else {\n // Default protocol to https protocol\n file.url = `https://${upload.Location}`;\n }\n };\n\n return {\n isPrivate() {\n return config.params.ACL === 'private';\n },\n\n async getSignedUrl(file: File, customParams: any): Promise<{ url: string }> {\n // Do not sign the url if it does not come from the same bucket.\n if (!isUrlFromBucket(file.url, config.params.Bucket, baseUrl)) {\n return { url: file.url };\n }\n const fileKey = getFileKey(file);\n\n const url = await getSignedUrl(\n // @ts-expect-error - TODO fix client type\n s3Client,\n new GetObjectCommand({\n Bucket: config.params.Bucket,\n Key: fileKey,\n ...customParams,\n }),\n {\n expiresIn: getOr(15 * 60, ['params', 'signedUrlExpires'], config),\n }\n );\n\n return { url };\n },\n uploadStream(file: File, customParams = {}) {\n return upload(file, customParams);\n },\n upload(file: File, customParams = {}) {\n return upload(file, customParams);\n },\n delete(file: File, customParams = {}): Promise<DeleteObjectCommandOutput> {\n const command = new DeleteObjectCommand({\n Bucket: config.params.Bucket,\n Key: getFileKey(file),\n ...customParams,\n });\n return s3Client.send(command);\n },\n };\n },\n};\n"],"names":["assertUrlProtocol","url","test","getConfig","baseUrl","rootPath","s3Options","legacyS3Options","Object","keys","length","process","emitWarning","credentials","extractCredentials","config","params","ACL","getOr","ObjectCannedACL","public_read","init","s3Client","S3Client","filePrefix","replace","getFileKey","file","path","hash","ext","upload","customParams","fileKey","uploadObj","Upload","client","Bucket","Key","Body","stream","Buffer","from","buffer","ContentType","mime","done","Location","isPrivate","getSignedUrl","isUrlFromBucket","GetObjectCommand","expiresIn","uploadStream","delete","command","DeleteObjectCommand","send"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { ReadStream } from 'node:fs';\nimport { getOr } from 'lodash/fp';\nimport {\n S3Client,\n GetObjectCommand,\n DeleteObjectCommand,\n DeleteObjectCommandOutput,\n HeadObjectCommand,\n PutObjectCommandInput,\n CompleteMultipartUploadCommandOutput,\n AbortMultipartUploadCommandOutput,\n S3ClientConfig,\n ObjectCannedACL,\n ChecksumAlgorithm,\n StorageClass,\n ServerSideEncryption,\n} from '@aws-sdk/client-s3';\nimport type { AwsCredentialIdentity } from '@aws-sdk/types';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { Upload } from '@aws-sdk/lib-storage';\nimport { extractCredentials, isUrlFromBucket } from './utils';\n\n/**\n * Supported checksum algorithms for data integrity validation.\n * CRC64NVME is recommended for best performance on modern hardware.\n */\nexport type SupportedChecksumAlgorithm = 'CRC32' | 'CRC32C' | 'SHA1' | 'SHA256' | 'CRC64NVME';\n\n/**\n * Supported S3 storage classes for cost optimization.\n */\nexport type SupportedStorageClass =\n | 'STANDARD'\n | 'REDUCED_REDUNDANCY'\n | 'STANDARD_IA'\n | 'ONEZONE_IA'\n | 'INTELLIGENT_TIERING'\n | 'GLACIER'\n | 'DEEP_ARCHIVE'\n | 'GLACIER_IR';\n\n/**\n * Server-side encryption types.\n */\nexport type EncryptionType = 'AES256' | 'aws:kms' | 'aws:kms:dsse';\n\n/**\n * Encryption configuration for server-side encryption.\n */\nexport interface EncryptionConfig {\n type: EncryptionType;\n kmsKeyId?: string;\n}\n\n/**\n * Multipart upload configuration for large files.\n */\nexport interface MultipartConfig {\n partSize?: number;\n queueSize?: number;\n leavePartsOnError?: boolean;\n}\n\nexport interface File {\n name: string;\n alternativeText?: string;\n caption?: string;\n width?: number;\n height?: number;\n formats?: Record<string, unknown>;\n hash: string;\n ext?: string;\n mime: string;\n size: number;\n sizeInBytes: number;\n url: string;\n previewUrl?: string;\n path?: string;\n provider?: string;\n provider_metadata?: Record<string, unknown>;\n stream?: ReadStream;\n buffer?: Buffer;\n etag?: string;\n}\n\nexport type UploadCommandOutput = (\n | CompleteMultipartUploadCommandOutput\n | AbortMultipartUploadCommandOutput\n) & {\n Location: string;\n ETag?: string;\n};\n\nexport interface AWSParams {\n Bucket: string;\n ACL?: ObjectCannedACL;\n signedUrlExpires?: number;\n}\n\n/**\n * Extended configuration options for the S3 provider.\n */\nexport interface ProviderConfig {\n /**\n * Checksum algorithm for data integrity validation during upload.\n * When enabled, the SDK calculates a checksum and S3 validates it server-side.\n */\n checksumAlgorithm?: SupportedChecksumAlgorithm;\n\n /**\n * When true, uploads will fail if an object with the same key already exists.\n * This prevents accidental overwrites due to race conditions.\n */\n preventOverwrite?: boolean;\n\n /**\n * S3 storage class for uploaded objects.\n * Use lower-cost classes for infrequently accessed data.\n */\n storageClass?: SupportedStorageClass;\n\n /**\n * Server-side encryption configuration.\n */\n encryption?: EncryptionConfig;\n\n /**\n * Tags to apply to uploaded objects.\n * Useful for cost allocation and lifecycle policies.\n */\n tags?: Record<string, string>;\n\n /**\n * Multipart upload configuration for large files.\n */\n multipart?: MultipartConfig;\n}\n\nexport interface DefaultOptions extends S3ClientConfig {\n // TODO Remove this in V5\n accessKeyId?: AwsCredentialIdentity['accessKeyId'];\n secretAccessKey?: AwsCredentialIdentity['secretAccessKey'];\n // Keep this for V5\n credentials?: AwsCredentialIdentity;\n params?: AWSParams;\n [k: string]: unknown;\n}\n\nexport type InitOptions = (DefaultOptions | { s3Options: DefaultOptions }) & {\n baseUrl?: string;\n rootPath?: string;\n providerConfig?: ProviderConfig;\n [k: string]: unknown;\n};\n\n/**\n * Validates that a URL uses HTTP or HTTPS protocol.\n * Rejects dangerous protocols like file://, javascript:, data:, etc.\n */\nconst assertUrlProtocol = (url: string) => {\n return /^https?:\\/\\//.test(url);\n};\n\n/**\n * Sanitizes a path component to prevent path traversal attacks.\n * Removes directory traversal sequences and normalizes the path.\n */\nconst sanitizePathComponent = (component: string | undefined): string => {\n if (!component) return '';\n return component\n .replace(/\\.\\./g, '')\n .replace(/^\\/+|\\/+$/g, '')\n .replace(/\\/+/g, '/');\n};\n\n/**\n * Maps the provider checksum algorithm to the AWS SDK checksum algorithm.\n */\nconst mapChecksumAlgorithm = (\n algorithm?: SupportedChecksumAlgorithm\n): ChecksumAlgorithm | undefined => {\n if (!algorithm) return undefined;\n const mapping: Record<SupportedChecksumAlgorithm, ChecksumAlgorithm> = {\n CRC32: ChecksumAlgorithm.CRC32,\n CRC32C: ChecksumAlgorithm.CRC32C,\n SHA1: ChecksumAlgorithm.SHA1,\n SHA256: ChecksumAlgorithm.SHA256,\n CRC64NVME: ChecksumAlgorithm.CRC64NVME,\n };\n return mapping[algorithm];\n};\n\n/**\n * Maps the provider storage class to the AWS SDK storage class.\n */\nconst mapStorageClass = (storageClass?: SupportedStorageClass): StorageClass | undefined => {\n if (!storageClass) return undefined;\n const mapping: Record<SupportedStorageClass, StorageClass> = {\n STANDARD: StorageClass.STANDARD,\n REDUCED_REDUNDANCY: StorageClass.REDUCED_REDUNDANCY,\n STANDARD_IA: StorageClass.STANDARD_IA,\n ONEZONE_IA: StorageClass.ONEZONE_IA,\n INTELLIGENT_TIERING: StorageClass.INTELLIGENT_TIERING,\n GLACIER: StorageClass.GLACIER,\n DEEP_ARCHIVE: StorageClass.DEEP_ARCHIVE,\n GLACIER_IR: StorageClass.GLACIER_IR,\n };\n return mapping[storageClass];\n};\n\n/**\n * Maps the provider encryption type to the AWS SDK server-side encryption.\n */\nconst mapServerSideEncryption = (type?: EncryptionType): ServerSideEncryption | undefined => {\n if (!type) return undefined;\n const mapping: Record<EncryptionType, ServerSideEncryption> = {\n AES256: ServerSideEncryption.AES256,\n 'aws:kms': ServerSideEncryption.aws_kms,\n 'aws:kms:dsse': ServerSideEncryption.aws_kms_dsse,\n };\n return mapping[type];\n};\n\n/**\n * Converts a tags object to the S3 Tagging header format.\n * Format: key1=value1&key2=value2\n */\nconst formatTagsForHeader = (tags?: Record<string, string>): string | undefined => {\n if (!tags || Object.keys(tags).length === 0) return undefined;\n return Object.entries(tags)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join('&');\n};\n\n/**\n * Checks if the endpoint appears to be a non-AWS S3-compatible provider.\n */\nconst isNonAwsEndpoint = (endpoint?: string): boolean => {\n if (!endpoint) return false;\n const awsPatterns = [/\\.amazonaws\\.com$/i, /\\.amazonaws\\.com\\.cn$/i];\n return !awsPatterns.some((pattern) => pattern.test(endpoint));\n};\n\n/**\n * Validates provider configuration and emits warnings for potential compatibility issues.\n */\nconst validateProviderConfig = (\n providerConfig: ProviderConfig | undefined,\n s3Options: DefaultOptions | undefined\n): void => {\n if (!providerConfig) return;\n\n const endpoint = s3Options?.endpoint?.toString() || '';\n const isNonAws = isNonAwsEndpoint(endpoint);\n\n // Warn about AWS-specific features when using non-AWS endpoints\n if (isNonAws) {\n if (providerConfig.storageClass) {\n process.emitWarning(\n `Storage class '${providerConfig.storageClass}' is AWS S3-specific and may be ignored by your S3-compatible provider.`\n );\n }\n\n if (providerConfig.encryption?.type && providerConfig.encryption.type !== 'AES256') {\n process.emitWarning(\n `Encryption type '${providerConfig.encryption.type}' is AWS S3-specific. Consider using 'AES256' for better compatibility.`\n );\n }\n }\n\n // Validate multipart configuration\n if (providerConfig.multipart?.partSize) {\n const minPartSize = 5 * 1024 * 1024; // 5MB\n const maxPartSize = 5 * 1024 * 1024 * 1024; // 5GB\n\n if (providerConfig.multipart.partSize < minPartSize) {\n process.emitWarning(\n `Multipart partSize ${providerConfig.multipart.partSize} is below the minimum of 5MB. This may cause upload failures.`\n );\n }\n\n if (providerConfig.multipart.partSize > maxPartSize) {\n process.emitWarning(\n `Multipart partSize ${providerConfig.multipart.partSize} exceeds the maximum of 5GB. This may cause upload failures.`\n );\n }\n }\n\n if (providerConfig.multipart?.queueSize && providerConfig.multipart.queueSize > 16) {\n process.emitWarning(\n `Multipart queueSize ${providerConfig.multipart.queueSize} is high and may cause memory issues. Consider using 4-8.`\n );\n }\n};\n\nconst getConfig = ({\n s3Options,\n legacyS3Options,\n}: {\n s3Options: DefaultOptions;\n legacyS3Options: Record<string, unknown>;\n}) => {\n if (Object.keys(legacyS3Options).length > 0) {\n process.emitWarning(\n \"S3 configuration options passed at root level of the plugin's providerOptions is deprecated and will be removed in a future release. Please wrap them inside the 's3Options:{}' property.\"\n );\n }\n const credentials = extractCredentials({ s3Options, ...legacyS3Options });\n const config = {\n ...s3Options,\n ...legacyS3Options,\n ...(credentials ? { credentials } : {}),\n };\n\n if (config.params !== undefined) {\n // Only set default ACL when ACL is not explicitly present in params.\n // Since April 2023, new AWS S3 buckets have ACLs disabled by default\n // (\"Bucket owner enforced\"). Sending an ACL header to such buckets\n // throws AccessControlListNotSupported. To disable ACLs, users should\n // simply not include ACL in their params configuration.\n if (!('ACL' in config.params)) {\n config.params.ACL = ObjectCannedACL.public_read;\n }\n } else {\n throw new Error('Upload AWS S3 provider: `params` are required in the config object');\n }\n\n return config as DefaultOptions & {\n params: AWSParams;\n };\n};\n\nexport default {\n init({ baseUrl, rootPath, s3Options, providerConfig, ...legacyS3Options }: InitOptions) {\n // Validate configuration and emit warnings for potential issues\n validateProviderConfig(providerConfig, s3Options as DefaultOptions);\n\n // TODO V5 change config structure to avoid having to do this\n const config = getConfig({ s3Options: s3Options as DefaultOptions, legacyS3Options });\n const s3Client = new S3Client(config);\n const filePrefix = rootPath ? `${rootPath.replace(/\\/+$/, '')}/` : '';\n\n const getFileKey = (file: File) => {\n const sanitizedPath = sanitizePathComponent(file.path);\n const path = sanitizedPath ? `${sanitizedPath}/` : '';\n const sanitizedHash = sanitizePathComponent(file.hash);\n const sanitizedExt = file.ext ? file.ext.replace(/[^a-zA-Z0-9.]/g, '') : '';\n return `${filePrefix}${path}${sanitizedHash}${sanitizedExt}`;\n };\n\n /**\n * Builds the upload parameters including all configured features.\n */\n const buildUploadParams = (\n file: File,\n fileKey: string,\n customParams: Partial<PutObjectCommandInput> = {}\n ): PutObjectCommandInput => {\n const params: PutObjectCommandInput = {\n Bucket: config.params.Bucket,\n Key: fileKey,\n Body: file.stream || Buffer.from(file.buffer as any, 'binary'),\n // ACL is optional to support providers like Cloudflare R2 that don't support ACLs.\n // Set params.ACL to undefined or omit it entirely to disable ACL headers.\n ...(config.params.ACL ? { ACL: config.params.ACL } : {}),\n ContentType: file.mime,\n };\n\n // Checksum validation\n const checksumAlgorithm = mapChecksumAlgorithm(providerConfig?.checksumAlgorithm);\n if (checksumAlgorithm) {\n params.ChecksumAlgorithm = checksumAlgorithm;\n }\n\n // Conditional writes - prevent overwrite\n if (providerConfig?.preventOverwrite) {\n params.IfNoneMatch = '*';\n }\n\n // Storage class\n const storageClass = mapStorageClass(providerConfig?.storageClass);\n if (storageClass) {\n params.StorageClass = storageClass;\n }\n\n // Server-side encryption\n if (providerConfig?.encryption) {\n const sse = mapServerSideEncryption(providerConfig.encryption.type);\n if (sse) {\n params.ServerSideEncryption = sse;\n if (providerConfig.encryption.kmsKeyId) {\n params.SSEKMSKeyId = providerConfig.encryption.kmsKeyId;\n }\n }\n }\n\n // Object tagging\n const tagging = formatTagsForHeader(providerConfig?.tags);\n if (tagging) {\n params.Tagging = tagging;\n }\n\n // Merge customParams but preserve critical security parameters\n // Bucket, Key, and Body must not be overridden by customParams\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { Bucket, Key, Body, ...safeCustomParams } = customParams as any;\n return { ...params, ...safeCustomParams };\n };\n\n /**\n * Constructs the correct file URL.\n * Handles S3-compatible providers that return incorrect Location formats.\n */\n const constructFileUrl = (fileKey: string, uploadLocation: string): string => {\n // Priority 1: Use baseUrl if configured (CDN or custom domain)\n if (baseUrl) {\n const cleanBase = baseUrl.replace(/\\/+$/, '');\n return `${cleanBase}/${fileKey}`;\n }\n\n // Priority 2: Construct URL from endpoint if configured\n // This fixes issues with S3-compatible providers (IONOS, MinIO, etc.)\n // that return Location in incorrect format for multipart uploads\n const endpoint = config.endpoint?.toString();\n if (endpoint) {\n const endpointUrl = endpoint.startsWith('http') ? endpoint : `https://${endpoint}`;\n const cleanEndpoint = endpointUrl.replace(/\\/+$/, '');\n return `${cleanEndpoint}/${config.params.Bucket}/${fileKey}`;\n }\n\n // Priority 3: Use the Location from S3 response\n if (assertUrlProtocol(uploadLocation)) {\n return uploadLocation;\n }\n\n // Priority 4: Prepend https if protocol is missing\n return `https://${uploadLocation}`;\n };\n\n const upload = async (file: File, customParams: Partial<PutObjectCommandInput> = {}) => {\n const fileKey = getFileKey(file);\n const params = buildUploadParams(file, fileKey, customParams);\n\n const uploadOptions: {\n client: S3Client;\n params: PutObjectCommandInput;\n partSize?: number;\n queueSize?: number;\n leavePartsOnError?: boolean;\n } = {\n client: s3Client,\n params,\n };\n\n // Multipart configuration\n if (providerConfig?.multipart) {\n if (providerConfig.multipart.partSize) {\n uploadOptions.partSize = providerConfig.multipart.partSize;\n }\n if (providerConfig.multipart.queueSize) {\n uploadOptions.queueSize = providerConfig.multipart.queueSize;\n }\n if (providerConfig.multipart.leavePartsOnError !== undefined) {\n uploadOptions.leavePartsOnError = providerConfig.multipart.leavePartsOnError;\n }\n }\n\n const uploadObj = new Upload(uploadOptions);\n const result = (await uploadObj.done()) as UploadCommandOutput;\n\n // Construct the correct URL (handles S3-compatible provider quirks)\n file.url = constructFileUrl(fileKey, result.Location);\n\n // Store ETag for potential future conditional updates\n if (result.ETag) {\n file.etag = result.ETag.replace(/\"/g, '');\n }\n };\n\n /**\n * Uploads a file only if the existing object matches the expected ETag.\n * This implements optimistic locking to prevent lost updates.\n */\n const uploadIfMatch = async (\n file: File,\n expectedETag: string,\n customParams: Partial<PutObjectCommandInput> = {}\n ) => {\n const fileKey = getFileKey(file);\n const params = buildUploadParams(file, fileKey, {\n ...customParams,\n IfMatch: expectedETag,\n });\n\n const uploadObj = new Upload({\n client: s3Client,\n params,\n });\n\n const result = (await uploadObj.done()) as UploadCommandOutput;\n\n // Construct the correct URL (handles S3-compatible provider quirks)\n file.url = constructFileUrl(fileKey, result.Location);\n\n if (result.ETag) {\n file.etag = result.ETag.replace(/\"/g, '');\n }\n };\n\n /**\n * Retrieves metadata for an object including its ETag.\n */\n const getObjectMetadata = async (file: File) => {\n const command = new HeadObjectCommand({\n Bucket: config.params.Bucket,\n Key: getFileKey(file),\n });\n\n const response = await s3Client.send(command);\n\n return {\n etag: response.ETag?.replace(/\"/g, ''),\n contentLength: response.ContentLength,\n contentType: response.ContentType,\n lastModified: response.LastModified,\n storageClass: response.StorageClass,\n serverSideEncryption: response.ServerSideEncryption,\n };\n };\n\n /**\n * Checks if an object exists in the bucket.\n */\n const objectExists = async (file: File): Promise<boolean> => {\n try {\n await getObjectMetadata(file);\n return true;\n } catch (error: any) {\n if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {\n return false;\n }\n throw error;\n }\n };\n\n return {\n /**\n * Returns whether the bucket is configured with private ACL.\n */\n isPrivate() {\n return config.params.ACL === 'private';\n },\n\n /**\n * Returns the current provider configuration.\n */\n getProviderConfig(): ProviderConfig | undefined {\n return providerConfig;\n },\n\n /**\n * Generates a signed URL for accessing a private object.\n */\n async getSignedUrl(file: File, customParams: any): Promise<{ url: string }> {\n if (!isUrlFromBucket(file.url, config.params.Bucket, baseUrl)) {\n return { url: file.url };\n }\n const fileKey = getFileKey(file);\n\n // Spread customParams first, then override with secure values\n // This prevents malicious override of Bucket and Key\n const url = await getSignedUrl(\n s3Client,\n new GetObjectCommand({\n ...customParams,\n Bucket: config.params.Bucket,\n Key: fileKey,\n }),\n {\n expiresIn: getOr(15 * 60, ['params', 'signedUrlExpires'], config),\n }\n );\n\n return { url };\n },\n\n /**\n * Uploads a file using streaming.\n */\n uploadStream(file: File, customParams = {}) {\n return upload(file, customParams);\n },\n\n /**\n * Uploads a file to S3.\n */\n upload(file: File, customParams = {}) {\n return upload(file, customParams);\n },\n\n /**\n * Uploads a file only if it matches the expected ETag (optimistic locking).\n * Throws PreconditionFailed error if ETag does not match.\n */\n uploadIfMatch(file: File, expectedETag: string, customParams = {}) {\n return uploadIfMatch(file, expectedETag, customParams);\n },\n\n /**\n * Retrieves object metadata including ETag.\n */\n getObjectMetadata(file: File) {\n return getObjectMetadata(file);\n },\n\n /**\n * Checks if an object exists in the bucket.\n */\n objectExists(file: File) {\n return objectExists(file);\n },\n\n /**\n * Deletes an object from S3.\n */\n delete(file: File, customParams = {}): Promise<DeleteObjectCommandOutput> {\n // Spread customParams first, then override with secure values\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { Bucket, Key, ...safeParams } = customParams as any;\n const command = new DeleteObjectCommand({\n ...safeParams,\n Bucket: config.params.Bucket,\n Key: getFileKey(file),\n });\n return s3Client.send(command);\n },\n };\n },\n};\n"],"names":["assertUrlProtocol","url","test","sanitizePathComponent","component","replace","mapChecksumAlgorithm","algorithm","undefined","mapping","CRC32","ChecksumAlgorithm","CRC32C","SHA1","SHA256","CRC64NVME","mapStorageClass","storageClass","STANDARD","StorageClass","REDUCED_REDUNDANCY","STANDARD_IA","ONEZONE_IA","INTELLIGENT_TIERING","GLACIER","DEEP_ARCHIVE","GLACIER_IR","mapServerSideEncryption","type","AES256","ServerSideEncryption","aws_kms","aws_kms_dsse","formatTagsForHeader","tags","Object","keys","length","entries","map","key","value","encodeURIComponent","join","isNonAwsEndpoint","endpoint","awsPatterns","some","pattern","validateProviderConfig","providerConfig","s3Options","toString","isNonAws","process","emitWarning","encryption","multipart","partSize","minPartSize","maxPartSize","queueSize","getConfig","legacyS3Options","credentials","extractCredentials","config","params","ACL","ObjectCannedACL","public_read","Error","init","baseUrl","rootPath","s3Client","S3Client","filePrefix","getFileKey","file","sanitizedPath","path","sanitizedHash","hash","sanitizedExt","ext","buildUploadParams","fileKey","customParams","Bucket","Key","Body","stream","Buffer","from","buffer","ContentType","mime","checksumAlgorithm","preventOverwrite","IfNoneMatch","sse","kmsKeyId","SSEKMSKeyId","tagging","Tagging","safeCustomParams","constructFileUrl","uploadLocation","cleanBase","endpointUrl","startsWith","cleanEndpoint","upload","uploadOptions","client","leavePartsOnError","uploadObj","Upload","result","done","Location","ETag","etag","uploadIfMatch","expectedETag","IfMatch","getObjectMetadata","command","HeadObjectCommand","response","send","contentLength","ContentLength","contentType","lastModified","LastModified","serverSideEncryption","objectExists","error","name","$metadata","httpStatusCode","isPrivate","getProviderConfig","getSignedUrl","isUrlFromBucket","GetObjectCommand","expiresIn","getOr","uploadStream","delete","safeParams","DeleteObjectCommand"],"mappings":"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"}