@strapi/provider-upload-aws-s3 4.9.0-beta.2 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -0
- package/.eslintrc.js +4 -0
- package/README.md +41 -0
- package/lib/index.js +45 -5
- package/lib/utils.js +63 -0
- package/package.json +5 -3
package/.eslintignore
ADDED
package/.eslintrc.js
ADDED
package/README.md
CHANGED
|
@@ -25,6 +25,10 @@ npm install @strapi/provider-upload-aws-s3 --save
|
|
|
25
25
|
|
|
26
26
|
- `provider` defines the name of the provider
|
|
27
27
|
- `providerOptions` is passed down during the construction of the provider. (ex: `new AWS.S3(config)`). [Complete list of options](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property)
|
|
28
|
+
- `providerOptions.params` is passed directly to the parameters to each method respectively.
|
|
29
|
+
- `ACL` is the access control list for the object. Defaults to `public-read`.
|
|
30
|
+
- `signedUrlExpires` is the number of seconds before a signed URL expires. (See [how signed URLs work](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html)). Defaults to 15 minutes and URLs are only signed when ACL is set to `private`.
|
|
31
|
+
- `Bucket` is the name of the bucket to upload to.
|
|
28
32
|
- `actionOptions` is passed directly to the parameters to each method respectively. You can find the complete list of [upload/ uploadStream options](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property) and [delete options](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObject-property)
|
|
29
33
|
|
|
30
34
|
See the [documentation about using a provider](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) for information on installing and using a provider. To understand how environment variables are used in Strapi, please refer to the [documentation about environment variables](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.html#environment-variables).
|
|
@@ -49,6 +53,8 @@ module.exports = ({ env }) => ({
|
|
|
49
53
|
secretAccessKey: env('AWS_ACCESS_SECRET'),
|
|
50
54
|
region: env('AWS_REGION'),
|
|
51
55
|
params: {
|
|
56
|
+
ACL: env('AWS_ACL', 'public-read'),
|
|
57
|
+
signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60),
|
|
52
58
|
Bucket: env('AWS_BUCKET'),
|
|
53
59
|
},
|
|
54
60
|
},
|
|
@@ -64,6 +70,41 @@ module.exports = ({ env }) => ({
|
|
|
64
70
|
});
|
|
65
71
|
```
|
|
66
72
|
|
|
73
|
+
### Configuration for a private S3 bucket
|
|
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 that the signed URL is generated with the correct permissions.
|
|
76
|
+
|
|
77
|
+
You can also define the expiration time of the signed URL by setting the `signedUrlExpires` option in the `params` object. The default value is 7 days.
|
|
78
|
+
|
|
79
|
+
`./config/plugins.js`
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
module.exports = ({ env }) => ({
|
|
83
|
+
// ...
|
|
84
|
+
upload: {
|
|
85
|
+
config: {
|
|
86
|
+
provider: 'aws-s3',
|
|
87
|
+
providerOptions: {
|
|
88
|
+
accessKeyId: env('AWS_ACCESS_KEY_ID'),
|
|
89
|
+
secretAccessKey: env('AWS_ACCESS_SECRET'),
|
|
90
|
+
region: env('AWS_REGION'),
|
|
91
|
+
params: {
|
|
92
|
+
ACL: 'private', // <== set ACL to private
|
|
93
|
+
signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 60 * 60 * 24 * 7),
|
|
94
|
+
Bucket: env('AWS_BUCKET'),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
actionOptions: {
|
|
98
|
+
upload: {},
|
|
99
|
+
uploadStream: {},
|
|
100
|
+
delete: {},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
// ...
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
67
108
|
#### Configuration for S3 compatible services
|
|
68
109
|
|
|
69
110
|
This plugin may work with S3 compatible services by using the `endpoint` option instead of `region`. Scaleway example:
|
package/lib/index.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
/* eslint-disable no-unused-vars */
|
|
8
8
|
// Public node modules.
|
|
9
|
-
const
|
|
9
|
+
const { getOr } = require('lodash/fp');
|
|
10
10
|
const AWS = require('aws-sdk');
|
|
11
|
+
const { getBucketFromUrl } = require('./utils');
|
|
11
12
|
|
|
12
13
|
function assertUrlProtocol(url) {
|
|
13
14
|
// Regex to test protocol like "http://", "https://"
|
|
@@ -22,10 +23,11 @@ module.exports = {
|
|
|
22
23
|
);
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
const config = { ...s3Options, ...legacyS3Options };
|
|
27
|
+
|
|
25
28
|
const S3 = new AWS.S3({
|
|
26
29
|
apiVersion: '2006-03-01',
|
|
27
|
-
...
|
|
28
|
-
...legacyS3Options,
|
|
30
|
+
...config,
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
const filePrefix = rootPath ? `${rootPath.replace(/\/+$/, '')}/` : '';
|
|
@@ -36,6 +38,8 @@ module.exports = {
|
|
|
36
38
|
return `${filePrefix}${path}${file.hash}${file.ext}`;
|
|
37
39
|
};
|
|
38
40
|
|
|
41
|
+
const ACL = getOr('public-read', ['params', 'ACL'], config);
|
|
42
|
+
|
|
39
43
|
const upload = (file, customParams = {}) =>
|
|
40
44
|
new Promise((resolve, reject) => {
|
|
41
45
|
// upload file on S3 bucket
|
|
@@ -44,7 +48,7 @@ module.exports = {
|
|
|
44
48
|
{
|
|
45
49
|
Key: fileKey,
|
|
46
50
|
Body: file.stream || Buffer.from(file.buffer, 'binary'),
|
|
47
|
-
ACL
|
|
51
|
+
ACL,
|
|
48
52
|
ContentType: file.mime,
|
|
49
53
|
...customParams,
|
|
50
54
|
},
|
|
@@ -60,13 +64,49 @@ module.exports = {
|
|
|
60
64
|
// Default protocol to https protocol
|
|
61
65
|
file.url = `https://${data.Location}`;
|
|
62
66
|
}
|
|
63
|
-
|
|
64
67
|
resolve();
|
|
65
68
|
}
|
|
66
69
|
);
|
|
67
70
|
});
|
|
68
71
|
|
|
69
72
|
return {
|
|
73
|
+
isPrivate() {
|
|
74
|
+
return ACL === 'private';
|
|
75
|
+
},
|
|
76
|
+
/**
|
|
77
|
+
* @param {Object} file
|
|
78
|
+
* @param {string} file.path
|
|
79
|
+
* @param {string} file.hash
|
|
80
|
+
* @param {string} file.ext
|
|
81
|
+
* @param {Object} customParams
|
|
82
|
+
* @returns {Promise<{url: string}>}
|
|
83
|
+
*/
|
|
84
|
+
getSignedUrl(file, customParams = {}) {
|
|
85
|
+
// Do not sign the url if it does not come from the same bucket.
|
|
86
|
+
const { bucket } = getBucketFromUrl(file.url);
|
|
87
|
+
if (bucket !== config.params.Bucket) {
|
|
88
|
+
return { url: file.url };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const fileKey = getFileKey(file);
|
|
93
|
+
|
|
94
|
+
S3.getSignedUrl(
|
|
95
|
+
'getObject',
|
|
96
|
+
{
|
|
97
|
+
Bucket: config.params.Bucket,
|
|
98
|
+
Key: fileKey,
|
|
99
|
+
Expires: getOr(15 * 60, ['params', 'signedUrlExpires'], config), // 15 minutes
|
|
100
|
+
},
|
|
101
|
+
(err, url) => {
|
|
102
|
+
if (err) {
|
|
103
|
+
return reject(err);
|
|
104
|
+
}
|
|
105
|
+
resolve({ url });
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
},
|
|
70
110
|
uploadStream(file, customParams = {}) {
|
|
71
111
|
return upload(file, customParams);
|
|
72
112
|
},
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ENDPOINT_PATTERN = /^(.+\.)?s3[.-]([a-z0-9-]+)\./;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse the bucket name from a URL.
|
|
7
|
+
* See all URL formats in https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html
|
|
8
|
+
*
|
|
9
|
+
* @param {string} fileUrl - the URL to parse
|
|
10
|
+
* @returns {object} result
|
|
11
|
+
* @returns {string} result.bucket - the bucket name
|
|
12
|
+
* @returns {string} result.error - if any
|
|
13
|
+
*/
|
|
14
|
+
function getBucketFromUrl(fileUrl) {
|
|
15
|
+
const uri = new URL(fileUrl);
|
|
16
|
+
|
|
17
|
+
// S3://<bucket-name>/<key>
|
|
18
|
+
if (uri.protocol === 's3:') {
|
|
19
|
+
const bucket = uri.host;
|
|
20
|
+
|
|
21
|
+
if (!bucket) {
|
|
22
|
+
return { err: `Invalid S3 URI: no bucket: ${uri}` };
|
|
23
|
+
}
|
|
24
|
+
return { bucket };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!uri.host) {
|
|
28
|
+
return { err: `Invalid S3 URI: no hostname: ${uri}` };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const matches = uri.host.match(ENDPOINT_PATTERN);
|
|
32
|
+
if (!matches) {
|
|
33
|
+
return { err: `Invalid S3 URI: hostname does not appear to be a valid S3 endpoint: ${uri}` };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const prefix = matches[1];
|
|
37
|
+
// https://s3.amazonaws.com/<bucket-name>
|
|
38
|
+
if (!prefix) {
|
|
39
|
+
if (uri.pathname === '/') {
|
|
40
|
+
return { bucket: null };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const index = uri.pathname.indexOf('/', 1);
|
|
44
|
+
|
|
45
|
+
// https://s3.amazonaws.com/<bucket-name>
|
|
46
|
+
if (index === -1) {
|
|
47
|
+
return { bucket: uri.pathname.substring(1) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// https://s3.amazonaws.com/<bucket-name>/
|
|
51
|
+
if (index === uri.pathname.length - 1) {
|
|
52
|
+
return { bucket: uri.pathname.substring(1, index) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// https://s3.amazonaws.com/<bucket-name>/key
|
|
56
|
+
return { bucket: uri.pathname.substring(1, index) };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// https://<bucket-name>.s3.amazonaws.com/
|
|
60
|
+
return { bucket: prefix.substring(0, prefix.length - 1) };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { getBucketFromUrl };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/provider-upload-aws-s3",
|
|
3
|
-
"version": "4.9.0
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "AWS S3 provider for strapi upload",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"upload",
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
"lib": "./lib"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"test": "
|
|
37
|
+
"test:unit": "jest",
|
|
38
|
+
"test:unit:watch": "jest --watch",
|
|
39
|
+
"lint": "eslint ."
|
|
38
40
|
},
|
|
39
41
|
"dependencies": {
|
|
40
42
|
"aws-sdk": "2.1287.0",
|
|
@@ -44,5 +46,5 @@
|
|
|
44
46
|
"node": ">=14.19.1 <=18.x.x",
|
|
45
47
|
"npm": ">=6.0.0"
|
|
46
48
|
},
|
|
47
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "ffe3f4621ccc968ce56fda9a8317ec30d4bad205"
|
|
48
50
|
}
|