@strapi/provider-upload-aws-s3 0.0.0-next.c8d6478ec519888ce0cd754886474c3189efc733 → 0.0.0-next.c9130a2ea39288aa524421ae5a00aa87b76bc097
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 +4 -2
- package/dist/index.d.ts +27 -14
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +155 -111
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +162 -0
- package/dist/index.mjs.map +1 -0
- package/dist/utils.d.ts +5 -1
- package/dist/utils.d.ts.map +1 -0
- package/package.json +16 -11
- package/dist/utils.js +0 -71
- package/dist/utils.js.map +0 -1
package/README.md
CHANGED
|
@@ -70,9 +70,11 @@ module.exports = ({ env }) => ({
|
|
|
70
70
|
});
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
### Configuration for a private S3 bucket
|
|
73
|
+
### Configuration for a private S3 bucket and signed URLs
|
|
74
74
|
|
|
75
|
-
If your bucket is configured to be private, you will need to set the `ACL` option to `private` in the `params` object. This will ensure
|
|
75
|
+
If your bucket is configured to be private, you will need to set the `ACL` option to `private` in the `params` object. This will ensure file URLs are signed.
|
|
76
|
+
|
|
77
|
+
**Note:** If you are using a CDN, the URLs will not be signed.
|
|
76
78
|
|
|
77
79
|
You can also define the expiration time of the signed URL by setting the `signedUrlExpires` option in the `params` object. The default value is 15 minutes.
|
|
78
80
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import type { ReadStream } from 'node:fs';
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import { DeleteObjectCommandOutput, CompleteMultipartUploadCommandOutput, AbortMultipartUploadCommandOutput, S3ClientConfig, ObjectCannedACL } from '@aws-sdk/client-s3';
|
|
5
|
+
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
|
6
|
+
export interface File {
|
|
6
7
|
name: string;
|
|
7
8
|
alternativeText?: string;
|
|
8
9
|
caption?: string;
|
|
@@ -21,26 +22,38 @@ interface File {
|
|
|
21
22
|
stream?: ReadStream;
|
|
22
23
|
buffer?: Buffer;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
export type UploadCommandOutput = (CompleteMultipartUploadCommandOutput | AbortMultipartUploadCommandOutput) & {
|
|
26
|
+
Location: string;
|
|
27
|
+
};
|
|
28
|
+
export interface AWSParams {
|
|
29
|
+
Bucket: string;
|
|
30
|
+
ACL?: ObjectCannedACL;
|
|
31
|
+
signedUrlExpires?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface DefaultOptions extends S3ClientConfig {
|
|
34
|
+
accessKeyId?: AwsCredentialIdentity['accessKeyId'];
|
|
35
|
+
secretAccessKey?: AwsCredentialIdentity['secretAccessKey'];
|
|
36
|
+
credentials?: AwsCredentialIdentity;
|
|
37
|
+
params?: AWSParams;
|
|
38
|
+
[k: string]: any;
|
|
39
|
+
}
|
|
40
|
+
export type InitOptions = (DefaultOptions | {
|
|
41
|
+
s3Options: DefaultOptions;
|
|
42
|
+
}) & {
|
|
25
43
|
baseUrl?: string;
|
|
26
44
|
rootPath?: string;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Bucket: string;
|
|
30
|
-
ACL?: string;
|
|
31
|
-
signedUrlExpires?: string;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
}
|
|
45
|
+
[k: string]: any;
|
|
46
|
+
};
|
|
35
47
|
declare const _default: {
|
|
36
48
|
init({ baseUrl, rootPath, s3Options, ...legacyS3Options }: InitOptions): {
|
|
37
49
|
isPrivate(): boolean;
|
|
38
|
-
getSignedUrl(file: File): Promise<{
|
|
50
|
+
getSignedUrl(file: File, customParams: any): Promise<{
|
|
39
51
|
url: string;
|
|
40
52
|
}>;
|
|
41
53
|
uploadStream(file: File, customParams?: {}): Promise<void>;
|
|
42
54
|
upload(file: File, customParams?: {}): Promise<void>;
|
|
43
|
-
delete(file: File, customParams?: {}): Promise<
|
|
55
|
+
delete(file: File, customParams?: {}): Promise<DeleteObjectCommandOutput>;
|
|
44
56
|
};
|
|
45
57
|
};
|
|
46
|
-
export
|
|
58
|
+
export default _default;
|
|
59
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,EAIL,yBAAyB,EAEzB,oCAAoC,EACpC,iCAAiC,EACjC,cAAc,EACd,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAK5D,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAC9B,oCAAoC,GACpC,iCAAiC,CACpC,GAAG;IACF,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAe,SAAQ,cAAc;IAEpD,WAAW,CAAC,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACnD,eAAe,CAAC,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAC;IAE3D,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,cAAc,GAAG;IAAE,SAAS,EAAE,cAAc,CAAA;CAAE,CAAC,GAAG;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClB,CAAC;;+DA0B2D,WAAW;;2BAwCzC,IAAI,gBAAgB,GAAG;iBAAkB,MAAM;;2BAqBrD,IAAI;qBAGV,IAAI;qBAGJ,IAAI,sBAAsB,QAAQ,yBAAyB,CAAC;;;AApE/E,wBA8EE"}
|
package/dist/index.js
CHANGED
|
@@ -1,117 +1,161 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
const fp = require("lodash/fp");
|
|
3
|
+
const clientS3 = require("@aws-sdk/client-s3");
|
|
4
|
+
const s3RequestPresigner = require("@aws-sdk/s3-request-presigner");
|
|
5
|
+
const libStorage = require("@aws-sdk/lib-storage");
|
|
6
|
+
const ENDPOINT_PATTERN = /^(.+\.)?s3[.-]([a-z0-9-]+)\./;
|
|
7
|
+
function isUrlFromBucket(fileUrl, bucketName, baseUrl = "") {
|
|
8
|
+
const url = new URL(fileUrl);
|
|
9
|
+
if (baseUrl) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const { bucket } = getBucketFromAwsUrl(fileUrl);
|
|
13
|
+
if (bucket) {
|
|
14
|
+
return bucket === bucketName;
|
|
15
|
+
}
|
|
16
|
+
return url.host.startsWith(`${bucketName}.`) || url.pathname.includes(`/${bucketName}/`);
|
|
17
|
+
}
|
|
18
|
+
function getBucketFromAwsUrl(fileUrl) {
|
|
19
|
+
const url = new URL(fileUrl);
|
|
20
|
+
if (url.protocol === "s3:") {
|
|
21
|
+
const bucket = url.host;
|
|
22
|
+
if (!bucket) {
|
|
23
|
+
return { err: `Invalid S3 url: no bucket: ${url}` };
|
|
24
|
+
}
|
|
25
|
+
return { bucket };
|
|
26
|
+
}
|
|
27
|
+
if (!url.host) {
|
|
28
|
+
return { err: `Invalid S3 url: no hostname: ${url}` };
|
|
29
|
+
}
|
|
30
|
+
const matches = url.host.match(ENDPOINT_PATTERN);
|
|
31
|
+
if (!matches) {
|
|
32
|
+
return { err: `Invalid S3 url: hostname does not appear to be a valid S3 endpoint: ${url}` };
|
|
33
|
+
}
|
|
34
|
+
const prefix = matches[1];
|
|
35
|
+
if (!prefix) {
|
|
36
|
+
if (url.pathname === "/") {
|
|
37
|
+
return { bucket: null };
|
|
38
|
+
}
|
|
39
|
+
const index2 = url.pathname.indexOf("/", 1);
|
|
40
|
+
if (index2 === -1) {
|
|
41
|
+
return { bucket: url.pathname.substring(1) };
|
|
42
|
+
}
|
|
43
|
+
if (index2 === url.pathname.length - 1) {
|
|
44
|
+
return { bucket: url.pathname.substring(1, index2) };
|
|
45
|
+
}
|
|
46
|
+
return { bucket: url.pathname.substring(1, index2) };
|
|
47
|
+
}
|
|
48
|
+
return { bucket: prefix.substring(0, prefix.length - 1) };
|
|
14
49
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
50
|
+
const extractCredentials = (options) => {
|
|
51
|
+
if (options.accessKeyId && options.secretAccessKey) {
|
|
52
|
+
return {
|
|
53
|
+
accessKeyId: options.accessKeyId,
|
|
54
|
+
secretAccessKey: options.secretAccessKey
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (options.s3Options?.accessKeyId && options.s3Options.secretAccessKey) {
|
|
58
|
+
process.emitWarning(
|
|
59
|
+
"Credentials passed directly to s3Options is deprecated and will be removed in a future release. Please wrap them inside a credentials object."
|
|
60
|
+
);
|
|
61
|
+
return {
|
|
62
|
+
accessKeyId: options.s3Options.accessKeyId,
|
|
63
|
+
secretAccessKey: options.s3Options.secretAccessKey
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (options.s3Options?.credentials) {
|
|
67
|
+
return {
|
|
68
|
+
accessKeyId: options.s3Options.credentials.accessKeyId,
|
|
69
|
+
secretAccessKey: options.s3Options.credentials.secretAccessKey
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
};
|
|
74
|
+
const assertUrlProtocol = (url) => {
|
|
75
|
+
return /^\w*:\/\//.test(url);
|
|
76
|
+
};
|
|
77
|
+
const getConfig = ({ baseUrl, rootPath, s3Options, ...legacyS3Options }) => {
|
|
78
|
+
if (Object.keys(legacyS3Options).length > 0) {
|
|
79
|
+
process.emitWarning(
|
|
80
|
+
"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."
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const credentials = extractCredentials({ s3Options, ...legacyS3Options });
|
|
84
|
+
const config = {
|
|
85
|
+
...s3Options,
|
|
86
|
+
...legacyS3Options,
|
|
87
|
+
...credentials ? { credentials } : {}
|
|
88
|
+
};
|
|
89
|
+
config.params.ACL = fp.getOr(clientS3.ObjectCannedACL.public_read, ["params", "ACL"], config);
|
|
90
|
+
return config;
|
|
91
|
+
};
|
|
92
|
+
const index = {
|
|
93
|
+
init({ baseUrl, rootPath, s3Options, ...legacyS3Options }) {
|
|
94
|
+
const config = getConfig({ baseUrl, rootPath, s3Options, ...legacyS3Options });
|
|
95
|
+
const s3Client = new clientS3.S3Client(config);
|
|
96
|
+
const filePrefix = rootPath ? `${rootPath.replace(/\/+$/, "")}/` : "";
|
|
97
|
+
const getFileKey = (file) => {
|
|
98
|
+
const path = file.path ? `${file.path}/` : "";
|
|
99
|
+
return `${filePrefix}${path}${file.hash}${file.ext}`;
|
|
100
|
+
};
|
|
101
|
+
const upload = async (file, customParams = {}) => {
|
|
102
|
+
const fileKey = getFileKey(file);
|
|
103
|
+
const uploadObj = new libStorage.Upload({
|
|
104
|
+
client: s3Client,
|
|
105
|
+
params: {
|
|
106
|
+
Bucket: config.params.Bucket,
|
|
107
|
+
Key: fileKey,
|
|
108
|
+
Body: file.stream || Buffer.from(file.buffer, "binary"),
|
|
109
|
+
ACL: config.params.ACL,
|
|
110
|
+
ContentType: file.mime,
|
|
111
|
+
...customParams
|
|
19
112
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
113
|
+
});
|
|
114
|
+
const upload2 = await uploadObj.done();
|
|
115
|
+
if (assertUrlProtocol(upload2.Location)) {
|
|
116
|
+
file.url = baseUrl ? `${baseUrl}/${fileKey}` : upload2.Location;
|
|
117
|
+
} else {
|
|
118
|
+
file.url = `https://${upload2.Location}`;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
isPrivate() {
|
|
123
|
+
return config.params.ACL === "private";
|
|
124
|
+
},
|
|
125
|
+
async getSignedUrl(file, customParams) {
|
|
126
|
+
if (!isUrlFromBucket(file.url, config.params.Bucket, baseUrl)) {
|
|
127
|
+
return { url: file.url };
|
|
35
128
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// Some providers like DigitalOcean Spaces return the url without the protocol
|
|
62
|
-
file.url = hasUrlProtocol(data.Location) ? data.Location : `https://${data.Location}`;
|
|
63
|
-
}
|
|
64
|
-
resolve();
|
|
65
|
-
};
|
|
66
|
-
S3.upload(params, onUploaded);
|
|
129
|
+
const fileKey = getFileKey(file);
|
|
130
|
+
const url = await s3RequestPresigner.getSignedUrl(
|
|
131
|
+
s3Client,
|
|
132
|
+
new clientS3.GetObjectCommand({
|
|
133
|
+
Bucket: config.params.Bucket,
|
|
134
|
+
Key: fileKey,
|
|
135
|
+
...customParams
|
|
136
|
+
}),
|
|
137
|
+
{
|
|
138
|
+
expiresIn: fp.getOr(15 * 60, ["params", "signedUrlExpires"], config)
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
return { url };
|
|
142
|
+
},
|
|
143
|
+
uploadStream(file, customParams = {}) {
|
|
144
|
+
return upload(file, customParams);
|
|
145
|
+
},
|
|
146
|
+
upload(file, customParams = {}) {
|
|
147
|
+
return upload(file, customParams);
|
|
148
|
+
},
|
|
149
|
+
delete(file, customParams = {}) {
|
|
150
|
+
const command = new clientS3.DeleteObjectCommand({
|
|
151
|
+
Bucket: config.params.Bucket,
|
|
152
|
+
Key: getFileKey(file),
|
|
153
|
+
...customParams
|
|
67
154
|
});
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
async getSignedUrl(file) {
|
|
73
|
-
// Do not sign the url if it does not come from the same bucket.
|
|
74
|
-
if (!(0, utils_1.isUrlFromBucket)(file.url, config.params.Bucket, baseUrl)) {
|
|
75
|
-
return { url: file.url };
|
|
76
|
-
}
|
|
77
|
-
const signedUrlExpires = (0, fp_1.getOr)(15 * 60, ['params', 'signedUrlExpires'], config); // 15 minutes
|
|
78
|
-
return new Promise((resolve, reject) => {
|
|
79
|
-
const fileKey = getFileKey(file);
|
|
80
|
-
S3.getSignedUrl('getObject', {
|
|
81
|
-
Bucket: config.params.Bucket,
|
|
82
|
-
Key: fileKey,
|
|
83
|
-
Expires: parseInt(signedUrlExpires, 10),
|
|
84
|
-
}, (err, url) => {
|
|
85
|
-
if (err) {
|
|
86
|
-
return reject(err);
|
|
87
|
-
}
|
|
88
|
-
resolve({ url });
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
},
|
|
92
|
-
uploadStream(file, customParams = {}) {
|
|
93
|
-
return upload(file, customParams);
|
|
94
|
-
},
|
|
95
|
-
upload(file, customParams = {}) {
|
|
96
|
-
return upload(file, customParams);
|
|
97
|
-
},
|
|
98
|
-
delete(file, customParams = {}) {
|
|
99
|
-
return new Promise((resolve, reject) => {
|
|
100
|
-
// delete file on S3 bucket
|
|
101
|
-
const fileKey = getFileKey(file);
|
|
102
|
-
S3.deleteObject({
|
|
103
|
-
Key: fileKey,
|
|
104
|
-
Bucket: config.params.Bucket,
|
|
105
|
-
...customParams,
|
|
106
|
-
}, (err) => {
|
|
107
|
-
if (err) {
|
|
108
|
-
return reject(err);
|
|
109
|
-
}
|
|
110
|
-
resolve();
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
},
|
|
155
|
+
return s3Client.send(command);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
116
159
|
};
|
|
117
|
-
|
|
160
|
+
module.exports = index;
|
|
161
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["import type { AwsCredentialIdentity } from '@aws-sdk/types';\nimport type { InitOptions } from '.';\n\nconst ENDPOINT_PATTERN = /^(.+\\.)?s3[.-]([a-z0-9-]+)\\./;\n\ninterface BucketInfo {\n bucket?: string | null;\n err?: string;\n}\n\nexport function isUrlFromBucket(fileUrl: string, bucketName: string, baseUrl = ''): boolean {\n const url = new URL(fileUrl);\n\n // Check if the file URL is using a base URL (e.g. a CDN).\n // In this case do not sign the URL.\n if (baseUrl) {\n return false;\n }\n\n const { bucket } = getBucketFromAwsUrl(fileUrl);\n\n if (bucket) {\n return bucket === bucketName;\n }\n\n // File URL might be of an S3-compatible provider. (or an invalid URL)\n // In this case, check if the bucket name appears in the URL host or path.\n // e.g. https://minio.example.com/bucket-name/object-key\n // e.g. https://bucket.nyc3.digitaloceanspaces.com/folder/img.png\n return url.host.startsWith(`${bucketName}.`) || url.pathname.includes(`/${bucketName}/`);\n}\n\n/**\n * Parse the bucket name from a URL.\n * See all URL formats in https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html\n *\n * @param {string} fileUrl - the URL to parse\n * @returns {object} result\n * @returns {string} result.bucket - the bucket name\n * @returns {string} result.err - if any\n */\nfunction getBucketFromAwsUrl(fileUrl: string): BucketInfo {\n const url = new URL(fileUrl);\n\n // S3://<bucket-name>/<key>\n if (url.protocol === 's3:') {\n const bucket = url.host;\n\n if (!bucket) {\n return { err: `Invalid S3 url: no bucket: ${url}` };\n }\n return { bucket };\n }\n\n if (!url.host) {\n return { err: `Invalid S3 url: no hostname: ${url}` };\n }\n\n const matches = url.host.match(ENDPOINT_PATTERN);\n if (!matches) {\n return { err: `Invalid S3 url: hostname does not appear to be a valid S3 endpoint: ${url}` };\n }\n\n const prefix = matches[1];\n // https://s3.amazonaws.com/<bucket-name>\n if (!prefix) {\n if (url.pathname === '/') {\n return { bucket: null };\n }\n\n const index = url.pathname.indexOf('/', 1);\n\n // https://s3.amazonaws.com/<bucket-name>\n if (index === -1) {\n return { bucket: url.pathname.substring(1) };\n }\n\n // https://s3.amazonaws.com/<bucket-name>/\n if (index === url.pathname.length - 1) {\n return { bucket: url.pathname.substring(1, index) };\n }\n\n // https://s3.amazonaws.com/<bucket-name>/key\n return { bucket: url.pathname.substring(1, index) };\n }\n\n // https://<bucket-name>.s3.amazonaws.com/\n return { bucket: prefix.substring(0, prefix.length - 1) };\n}\n\n// TODO Remove this in V5 since we will only support the new config structure\nexport const extractCredentials = (options: InitOptions): AwsCredentialIdentity | null => {\n // legacy\n if (options.accessKeyId && options.secretAccessKey) {\n return {\n accessKeyId: options.accessKeyId,\n secretAccessKey: options.secretAccessKey,\n };\n }\n // Legacy\n if (options.s3Options?.accessKeyId && options.s3Options.secretAccessKey) {\n process.emitWarning(\n 'Credentials passed directly to s3Options is deprecated and will be removed in a future release. Please wrap them inside a credentials object.'\n );\n return {\n accessKeyId: options.s3Options.accessKeyId,\n secretAccessKey: options.s3Options.secretAccessKey,\n };\n }\n // V5\n if (options.s3Options?.credentials) {\n return {\n accessKeyId: options.s3Options.credentials.accessKeyId,\n secretAccessKey: options.s3Options.credentials.secretAccessKey,\n };\n }\n return null;\n};\n","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 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 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":["index","getOr","ObjectCannedACL","S3Client","Upload","upload","getSignedUrl","GetObjectCommand","DeleteObjectCommand"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { getOr } from "lodash/fp";
|
|
2
|
+
import { S3Client, GetObjectCommand, DeleteObjectCommand, ObjectCannedACL } from "@aws-sdk/client-s3";
|
|
3
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
4
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
|
5
|
+
const ENDPOINT_PATTERN = /^(.+\.)?s3[.-]([a-z0-9-]+)\./;
|
|
6
|
+
function isUrlFromBucket(fileUrl, bucketName, baseUrl = "") {
|
|
7
|
+
const url = new URL(fileUrl);
|
|
8
|
+
if (baseUrl) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const { bucket } = getBucketFromAwsUrl(fileUrl);
|
|
12
|
+
if (bucket) {
|
|
13
|
+
return bucket === bucketName;
|
|
14
|
+
}
|
|
15
|
+
return url.host.startsWith(`${bucketName}.`) || url.pathname.includes(`/${bucketName}/`);
|
|
16
|
+
}
|
|
17
|
+
function getBucketFromAwsUrl(fileUrl) {
|
|
18
|
+
const url = new URL(fileUrl);
|
|
19
|
+
if (url.protocol === "s3:") {
|
|
20
|
+
const bucket = url.host;
|
|
21
|
+
if (!bucket) {
|
|
22
|
+
return { err: `Invalid S3 url: no bucket: ${url}` };
|
|
23
|
+
}
|
|
24
|
+
return { bucket };
|
|
25
|
+
}
|
|
26
|
+
if (!url.host) {
|
|
27
|
+
return { err: `Invalid S3 url: no hostname: ${url}` };
|
|
28
|
+
}
|
|
29
|
+
const matches = url.host.match(ENDPOINT_PATTERN);
|
|
30
|
+
if (!matches) {
|
|
31
|
+
return { err: `Invalid S3 url: hostname does not appear to be a valid S3 endpoint: ${url}` };
|
|
32
|
+
}
|
|
33
|
+
const prefix = matches[1];
|
|
34
|
+
if (!prefix) {
|
|
35
|
+
if (url.pathname === "/") {
|
|
36
|
+
return { bucket: null };
|
|
37
|
+
}
|
|
38
|
+
const index2 = url.pathname.indexOf("/", 1);
|
|
39
|
+
if (index2 === -1) {
|
|
40
|
+
return { bucket: url.pathname.substring(1) };
|
|
41
|
+
}
|
|
42
|
+
if (index2 === url.pathname.length - 1) {
|
|
43
|
+
return { bucket: url.pathname.substring(1, index2) };
|
|
44
|
+
}
|
|
45
|
+
return { bucket: url.pathname.substring(1, index2) };
|
|
46
|
+
}
|
|
47
|
+
return { bucket: prefix.substring(0, prefix.length - 1) };
|
|
48
|
+
}
|
|
49
|
+
const extractCredentials = (options) => {
|
|
50
|
+
if (options.accessKeyId && options.secretAccessKey) {
|
|
51
|
+
return {
|
|
52
|
+
accessKeyId: options.accessKeyId,
|
|
53
|
+
secretAccessKey: options.secretAccessKey
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (options.s3Options?.accessKeyId && options.s3Options.secretAccessKey) {
|
|
57
|
+
process.emitWarning(
|
|
58
|
+
"Credentials passed directly to s3Options is deprecated and will be removed in a future release. Please wrap them inside a credentials object."
|
|
59
|
+
);
|
|
60
|
+
return {
|
|
61
|
+
accessKeyId: options.s3Options.accessKeyId,
|
|
62
|
+
secretAccessKey: options.s3Options.secretAccessKey
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (options.s3Options?.credentials) {
|
|
66
|
+
return {
|
|
67
|
+
accessKeyId: options.s3Options.credentials.accessKeyId,
|
|
68
|
+
secretAccessKey: options.s3Options.credentials.secretAccessKey
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
};
|
|
73
|
+
const assertUrlProtocol = (url) => {
|
|
74
|
+
return /^\w*:\/\//.test(url);
|
|
75
|
+
};
|
|
76
|
+
const getConfig = ({ baseUrl, rootPath, s3Options, ...legacyS3Options }) => {
|
|
77
|
+
if (Object.keys(legacyS3Options).length > 0) {
|
|
78
|
+
process.emitWarning(
|
|
79
|
+
"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."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const credentials = extractCredentials({ s3Options, ...legacyS3Options });
|
|
83
|
+
const config = {
|
|
84
|
+
...s3Options,
|
|
85
|
+
...legacyS3Options,
|
|
86
|
+
...credentials ? { credentials } : {}
|
|
87
|
+
};
|
|
88
|
+
config.params.ACL = getOr(ObjectCannedACL.public_read, ["params", "ACL"], config);
|
|
89
|
+
return config;
|
|
90
|
+
};
|
|
91
|
+
const index = {
|
|
92
|
+
init({ baseUrl, rootPath, s3Options, ...legacyS3Options }) {
|
|
93
|
+
const config = getConfig({ baseUrl, rootPath, s3Options, ...legacyS3Options });
|
|
94
|
+
const s3Client = new S3Client(config);
|
|
95
|
+
const filePrefix = rootPath ? `${rootPath.replace(/\/+$/, "")}/` : "";
|
|
96
|
+
const getFileKey = (file) => {
|
|
97
|
+
const path = file.path ? `${file.path}/` : "";
|
|
98
|
+
return `${filePrefix}${path}${file.hash}${file.ext}`;
|
|
99
|
+
};
|
|
100
|
+
const upload = async (file, customParams = {}) => {
|
|
101
|
+
const fileKey = getFileKey(file);
|
|
102
|
+
const uploadObj = new Upload({
|
|
103
|
+
client: s3Client,
|
|
104
|
+
params: {
|
|
105
|
+
Bucket: config.params.Bucket,
|
|
106
|
+
Key: fileKey,
|
|
107
|
+
Body: file.stream || Buffer.from(file.buffer, "binary"),
|
|
108
|
+
ACL: config.params.ACL,
|
|
109
|
+
ContentType: file.mime,
|
|
110
|
+
...customParams
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const upload2 = await uploadObj.done();
|
|
114
|
+
if (assertUrlProtocol(upload2.Location)) {
|
|
115
|
+
file.url = baseUrl ? `${baseUrl}/${fileKey}` : upload2.Location;
|
|
116
|
+
} else {
|
|
117
|
+
file.url = `https://${upload2.Location}`;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
isPrivate() {
|
|
122
|
+
return config.params.ACL === "private";
|
|
123
|
+
},
|
|
124
|
+
async getSignedUrl(file, customParams) {
|
|
125
|
+
if (!isUrlFromBucket(file.url, config.params.Bucket, baseUrl)) {
|
|
126
|
+
return { url: file.url };
|
|
127
|
+
}
|
|
128
|
+
const fileKey = getFileKey(file);
|
|
129
|
+
const url = await getSignedUrl(
|
|
130
|
+
s3Client,
|
|
131
|
+
new GetObjectCommand({
|
|
132
|
+
Bucket: config.params.Bucket,
|
|
133
|
+
Key: fileKey,
|
|
134
|
+
...customParams
|
|
135
|
+
}),
|
|
136
|
+
{
|
|
137
|
+
expiresIn: getOr(15 * 60, ["params", "signedUrlExpires"], config)
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
return { url };
|
|
141
|
+
},
|
|
142
|
+
uploadStream(file, customParams = {}) {
|
|
143
|
+
return upload(file, customParams);
|
|
144
|
+
},
|
|
145
|
+
upload(file, customParams = {}) {
|
|
146
|
+
return upload(file, customParams);
|
|
147
|
+
},
|
|
148
|
+
delete(file, customParams = {}) {
|
|
149
|
+
const command = new DeleteObjectCommand({
|
|
150
|
+
Bucket: config.params.Bucket,
|
|
151
|
+
Key: getFileKey(file),
|
|
152
|
+
...customParams
|
|
153
|
+
});
|
|
154
|
+
return s3Client.send(command);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
export {
|
|
160
|
+
index as default
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["import type { AwsCredentialIdentity } from '@aws-sdk/types';\nimport type { InitOptions } from '.';\n\nconst ENDPOINT_PATTERN = /^(.+\\.)?s3[.-]([a-z0-9-]+)\\./;\n\ninterface BucketInfo {\n bucket?: string | null;\n err?: string;\n}\n\nexport function isUrlFromBucket(fileUrl: string, bucketName: string, baseUrl = ''): boolean {\n const url = new URL(fileUrl);\n\n // Check if the file URL is using a base URL (e.g. a CDN).\n // In this case do not sign the URL.\n if (baseUrl) {\n return false;\n }\n\n const { bucket } = getBucketFromAwsUrl(fileUrl);\n\n if (bucket) {\n return bucket === bucketName;\n }\n\n // File URL might be of an S3-compatible provider. (or an invalid URL)\n // In this case, check if the bucket name appears in the URL host or path.\n // e.g. https://minio.example.com/bucket-name/object-key\n // e.g. https://bucket.nyc3.digitaloceanspaces.com/folder/img.png\n return url.host.startsWith(`${bucketName}.`) || url.pathname.includes(`/${bucketName}/`);\n}\n\n/**\n * Parse the bucket name from a URL.\n * See all URL formats in https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html\n *\n * @param {string} fileUrl - the URL to parse\n * @returns {object} result\n * @returns {string} result.bucket - the bucket name\n * @returns {string} result.err - if any\n */\nfunction getBucketFromAwsUrl(fileUrl: string): BucketInfo {\n const url = new URL(fileUrl);\n\n // S3://<bucket-name>/<key>\n if (url.protocol === 's3:') {\n const bucket = url.host;\n\n if (!bucket) {\n return { err: `Invalid S3 url: no bucket: ${url}` };\n }\n return { bucket };\n }\n\n if (!url.host) {\n return { err: `Invalid S3 url: no hostname: ${url}` };\n }\n\n const matches = url.host.match(ENDPOINT_PATTERN);\n if (!matches) {\n return { err: `Invalid S3 url: hostname does not appear to be a valid S3 endpoint: ${url}` };\n }\n\n const prefix = matches[1];\n // https://s3.amazonaws.com/<bucket-name>\n if (!prefix) {\n if (url.pathname === '/') {\n return { bucket: null };\n }\n\n const index = url.pathname.indexOf('/', 1);\n\n // https://s3.amazonaws.com/<bucket-name>\n if (index === -1) {\n return { bucket: url.pathname.substring(1) };\n }\n\n // https://s3.amazonaws.com/<bucket-name>/\n if (index === url.pathname.length - 1) {\n return { bucket: url.pathname.substring(1, index) };\n }\n\n // https://s3.amazonaws.com/<bucket-name>/key\n return { bucket: url.pathname.substring(1, index) };\n }\n\n // https://<bucket-name>.s3.amazonaws.com/\n return { bucket: prefix.substring(0, prefix.length - 1) };\n}\n\n// TODO Remove this in V5 since we will only support the new config structure\nexport const extractCredentials = (options: InitOptions): AwsCredentialIdentity | null => {\n // legacy\n if (options.accessKeyId && options.secretAccessKey) {\n return {\n accessKeyId: options.accessKeyId,\n secretAccessKey: options.secretAccessKey,\n };\n }\n // Legacy\n if (options.s3Options?.accessKeyId && options.s3Options.secretAccessKey) {\n process.emitWarning(\n 'Credentials passed directly to s3Options is deprecated and will be removed in a future release. Please wrap them inside a credentials object.'\n );\n return {\n accessKeyId: options.s3Options.accessKeyId,\n secretAccessKey: options.s3Options.secretAccessKey,\n };\n }\n // V5\n if (options.s3Options?.credentials) {\n return {\n accessKeyId: options.s3Options.credentials.accessKeyId,\n secretAccessKey: options.s3Options.credentials.secretAccessKey,\n };\n }\n return null;\n};\n","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 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 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":["index","upload"],"mappings":"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"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
|
2
|
+
import type { InitOptions } from '.';
|
|
3
|
+
export declare function isUrlFromBucket(fileUrl: string, bucketName: string, baseUrl?: string): boolean;
|
|
4
|
+
export declare const extractCredentials: (options: InitOptions) => AwsCredentialIdentity | null;
|
|
5
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,GAAG,CAAC;AASrC,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,OAAO,CAoB1F;AA6DD,eAAO,MAAM,kBAAkB,YAAa,WAAW,KAAG,qBAAqB,GAAG,IA0BjF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/provider-upload-aws-s3",
|
|
3
|
-
"version": "0.0.0-next.
|
|
3
|
+
"version": "0.0.0-next.c9130a2ea39288aa524421ae5a00aa87b76bc097",
|
|
4
4
|
"description": "AWS S3 provider for strapi upload",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"upload",
|
|
@@ -30,32 +30,37 @@
|
|
|
30
30
|
}
|
|
31
31
|
],
|
|
32
32
|
"main": "./dist/index.js",
|
|
33
|
+
"module": "./dist/index.mjs",
|
|
34
|
+
"source": "./src/index.ts",
|
|
33
35
|
"types": "./dist/index.d.ts",
|
|
34
36
|
"files": [
|
|
35
37
|
"./dist"
|
|
36
38
|
],
|
|
37
39
|
"scripts": {
|
|
38
|
-
"build": "
|
|
39
|
-
"build:ts": "run -T tsc",
|
|
40
|
-
"watch": "run -T tsc -w --preserveWatchOutput",
|
|
40
|
+
"build": "pack-up build",
|
|
41
41
|
"clean": "run -T rimraf ./dist",
|
|
42
|
+
"lint": "run -T eslint .",
|
|
42
43
|
"prepublishOnly": "yarn clean && yarn build",
|
|
43
44
|
"test:unit": "run -T jest",
|
|
44
45
|
"test:unit:watch": "run -T jest --watch",
|
|
45
|
-
"
|
|
46
|
+
"watch": "pack-up watch"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"aws-sdk": "
|
|
49
|
+
"@aws-sdk/client-s3": "3.433.0",
|
|
50
|
+
"@aws-sdk/lib-storage": "3.433.0",
|
|
51
|
+
"@aws-sdk/s3-request-presigner": "3.433.0",
|
|
52
|
+
"@aws-sdk/types": "3.433.0",
|
|
49
53
|
"lodash": "4.17.21"
|
|
50
54
|
},
|
|
51
55
|
"devDependencies": {
|
|
52
|
-
"@
|
|
53
|
-
"
|
|
54
|
-
"
|
|
56
|
+
"@strapi/pack-up": "0.0.0-next.c9130a2ea39288aa524421ae5a00aa87b76bc097",
|
|
57
|
+
"@types/jest": "29.5.2",
|
|
58
|
+
"eslint-config-custom": "0.0.0-next.c9130a2ea39288aa524421ae5a00aa87b76bc097",
|
|
59
|
+
"tsconfig": "0.0.0-next.c9130a2ea39288aa524421ae5a00aa87b76bc097"
|
|
55
60
|
},
|
|
56
61
|
"engines": {
|
|
57
|
-
"node": ">=
|
|
62
|
+
"node": ">=18.0.0 <=20.x.x",
|
|
58
63
|
"npm": ">=6.0.0"
|
|
59
64
|
},
|
|
60
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "c9130a2ea39288aa524421ae5a00aa87b76bc097"
|
|
61
66
|
}
|
package/dist/utils.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isUrlFromBucket = void 0;
|
|
4
|
-
const ENDPOINT_PATTERN = /^(.+\.)?s3[.-]([a-z0-9-]+)\./;
|
|
5
|
-
function isUrlFromBucket(fileUrl, bucketName, bucketBaseUrl = '') {
|
|
6
|
-
const url = new URL(fileUrl);
|
|
7
|
-
// Check if the file URL is using a base URL (e.g. a CDN).
|
|
8
|
-
// In this case, check if the file URL starts with the same base URL as the bucket URL.
|
|
9
|
-
if (bucketBaseUrl) {
|
|
10
|
-
const baseUrl = new URL(bucketBaseUrl);
|
|
11
|
-
return url.href.startsWith(baseUrl.href);
|
|
12
|
-
}
|
|
13
|
-
const { bucket } = getBucketFromAwsUrl(fileUrl);
|
|
14
|
-
if (bucket) {
|
|
15
|
-
return bucket === bucketName;
|
|
16
|
-
}
|
|
17
|
-
// File URL might be of an S3-compatible provider. (or an invalid URL)
|
|
18
|
-
// In this case, check if the bucket name appears in the URL host or path.
|
|
19
|
-
// e.g. https://minio.example.com/bucket-name/object-key
|
|
20
|
-
// e.g. https://bucket.nyc3.digitaloceanspaces.com/folder/img.png
|
|
21
|
-
return url.host.startsWith(`${bucketName}.`) || url.pathname.includes(`/${bucketName}/`);
|
|
22
|
-
}
|
|
23
|
-
exports.isUrlFromBucket = isUrlFromBucket;
|
|
24
|
-
/**
|
|
25
|
-
* Parse the bucket name from a URL.
|
|
26
|
-
* See all URL formats in https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html
|
|
27
|
-
*
|
|
28
|
-
* @param {string} fileUrl - the URL to parse
|
|
29
|
-
* @returns {object} result
|
|
30
|
-
* @returns {string} result.bucket - the bucket name
|
|
31
|
-
* @returns {string} result.err - if any
|
|
32
|
-
*/
|
|
33
|
-
function getBucketFromAwsUrl(fileUrl) {
|
|
34
|
-
const url = new URL(fileUrl);
|
|
35
|
-
// S3://<bucket-name>/<key>
|
|
36
|
-
if (url.protocol === 's3:') {
|
|
37
|
-
const bucket = url.host;
|
|
38
|
-
if (!bucket) {
|
|
39
|
-
return { err: `Invalid S3 url: no bucket: ${url}` };
|
|
40
|
-
}
|
|
41
|
-
return { bucket };
|
|
42
|
-
}
|
|
43
|
-
if (!url.host) {
|
|
44
|
-
return { err: `Invalid S3 url: no hostname: ${url}` };
|
|
45
|
-
}
|
|
46
|
-
const matches = url.host.match(ENDPOINT_PATTERN);
|
|
47
|
-
if (!matches) {
|
|
48
|
-
return { err: `Invalid S3 url: hostname does not appear to be a valid S3 endpoint: ${url}` };
|
|
49
|
-
}
|
|
50
|
-
const prefix = matches[1];
|
|
51
|
-
// https://s3.amazonaws.com/<bucket-name>
|
|
52
|
-
if (!prefix) {
|
|
53
|
-
if (url.pathname === '/') {
|
|
54
|
-
return { bucket: null };
|
|
55
|
-
}
|
|
56
|
-
const index = url.pathname.indexOf('/', 1);
|
|
57
|
-
// https://s3.amazonaws.com/<bucket-name>
|
|
58
|
-
if (index === -1) {
|
|
59
|
-
return { bucket: url.pathname.substring(1) };
|
|
60
|
-
}
|
|
61
|
-
// https://s3.amazonaws.com/<bucket-name>/
|
|
62
|
-
if (index === url.pathname.length - 1) {
|
|
63
|
-
return { bucket: url.pathname.substring(1, index) };
|
|
64
|
-
}
|
|
65
|
-
// https://s3.amazonaws.com/<bucket-name>/key
|
|
66
|
-
return { bucket: url.pathname.substring(1, index) };
|
|
67
|
-
}
|
|
68
|
-
// https://<bucket-name>.s3.amazonaws.com/
|
|
69
|
-
return { bucket: prefix.substring(0, prefix.length - 1) };
|
|
70
|
-
}
|
|
71
|
-
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAAA,MAAM,gBAAgB,GAAG,8BAA8B,CAAC;AAOxD,SAAgB,eAAe,CAAC,OAAe,EAAE,UAAkB,EAAE,aAAa,GAAG,EAAE;IACrF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAE7B,0DAA0D;IAC1D,uFAAuF;IACvF,IAAI,aAAa,EAAE;QACjB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC;QACvC,OAAO,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;KAC1C;IAED,MAAM,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAEhD,IAAI,MAAM,EAAE;QACV,OAAO,MAAM,KAAK,UAAU,CAAC;KAC9B;IAED,sEAAsE;IACtE,0EAA0E;IAC1E,wDAAwD;IACxD,iEAAiE;IACjE,OAAO,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,UAAU,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC;AAC3F,CAAC;AArBD,0CAqBC;AAED;;;;;;;;GAQG;AACH,SAAS,mBAAmB,CAAC,OAAe;IAC1C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAE7B,2BAA2B;IAC3B,IAAI,GAAG,CAAC,QAAQ,KAAK,KAAK,EAAE;QAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC;QAExB,IAAI,CAAC,MAAM,EAAE;YACX,OAAO,EAAE,GAAG,EAAE,8BAA8B,GAAG,EAAE,EAAE,CAAC;SACrD;QACD,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB;IAED,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QACb,OAAO,EAAE,GAAG,EAAE,gCAAgC,GAAG,EAAE,EAAE,CAAC;KACvD;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE;QACZ,OAAO,EAAE,GAAG,EAAE,uEAAuE,GAAG,EAAE,EAAE,CAAC;KAC9F;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1B,yCAAyC;IACzC,IAAI,CAAC,MAAM,EAAE;QACX,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,EAAE;YACxB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;SACzB;QAED,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAE3C,yCAAyC;QACzC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE;YAChB,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9C;QAED,0CAA0C;QAC1C,IAAI,KAAK,KAAK,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;YACrC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;SACrD;QAED,6CAA6C;QAC7C,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;KACrD;IAED,0CAA0C;IAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;AAC5D,CAAC"}
|