Sfoglia il codice sorgente

examples/aws: client-side signing (#4463)

Antoine du Hamel 1 anno fa
parent
commit
961f11f375

+ 56 - 0
examples/aws-nodejs/index.js

@@ -21,12 +21,37 @@ const {
   UploadPartCommand,
 } = require('@aws-sdk/client-s3')
 const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
+const {
+  STSClient,
+  GetFederationTokenCommand,
+} = require('@aws-sdk/client-sts')
+
+const policy = {
+  Version: '2012-10-17',
+  Statement: [
+    {
+      Effect: 'Allow',
+      Action: [
+        's3:PutObject',
+      ],
+      Resource: [
+        `arn:aws:s3:::${process.env.COMPANION_AWS_BUCKET}/*`,
+        `arn:aws:s3:::${process.env.COMPANION_AWS_BUCKET}`,
+      ],
+    },
+  ],
+}
 
 /**
  * @type {S3Client}
  */
 let s3Client
 
+/**
+ * @type {STSClient}
+ */
+let stsClient
+
 const expiresIn = 900 // Define how long until a S3 signature expires.
 
 function getS3Client () {
@@ -40,6 +65,17 @@ function getS3Client () {
   return s3Client
 }
 
+function getSTSClient () {
+  stsClient ??= new STSClient({
+    region: process.env.COMPANION_AWS_REGION,
+    credentials : {
+      accessKeyId: process.env.COMPANION_AWS_KEY,
+      secretAccessKey: process.env.COMPANION_AWS_SECRET,
+    },
+  })
+  return stsClient
+}
+
 app.use(bodyParser.urlencoded({ extended: true }), bodyParser.json())
 
 app.get('/', (req, res) => {
@@ -52,6 +88,26 @@ app.get('/drag', (req, res) => {
   res.sendFile(htmlPath)
 })
 
+app.get('/sts', (req, res, next) => {
+  getSTSClient().send(new GetFederationTokenCommand({
+    Name: '123user',
+    // The duration, in seconds, of the role session. The value specified
+    // can range from 900 seconds (15 minutes) up to the maximum session
+    // duration set for the role.
+    DurationSeconds: expiresIn,
+    Policy: JSON.stringify(policy),
+  })).then(response => {
+    // Test creating multipart upload from the server — it works
+    // createMultipartUploadYo(response)
+    res.setHeader('Access-Control-Allow-Origin', '*')
+    res.setHeader('Cache-Control', `public,max-age=${expiresIn}`)
+    res.json({
+      credentials: response.Credentials,
+      bucket: process.env.COMPANION_AWS_BUCKET,
+      region: process.env.COMPANION_AWS_REGION,
+    })
+  }, next)
+})
 app.post('/sign-s3', (req, res, next) => {
   const Key = `${crypto.randomUUID()}-${req.body.filename}`
   const { contentType } = req.body

+ 1 - 0
examples/aws-nodejs/package.json

@@ -11,6 +11,7 @@
   "license": "MIT",
   "dependencies": {
     "@aws-sdk/client-s3": "^3.338.0",
+    "@aws-sdk/client-sts": "^3.338.0",
     "@aws-sdk/s3-request-presigner": "^3.338.0",
     "body-parser": "^1.20.0",
     "dotenv": "^16.0.0",

+ 27 - 2
examples/aws-nodejs/public/index.html

@@ -45,9 +45,21 @@
             target: '#uppy',
           })
           .use(AwsS3, {
+            id: 'myAWSPlugin',
+
             // Files that are more than 100MiB should be uploaded in multiple parts.
             shouldUseMultipart: (file) => file.size > 100 * MiB,
 
+            /**
+             * This method tells Uppy how to retrieve a temporary token for signing on the client.
+             * Signing on the client is optional, you can also do the signing from the server.
+             */
+            async getTemporarySecurityCredentials({signal}) {
+                const response = await fetch('/sts', { signal })
+                if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
+                return response.json()
+            },
+
             // ========== Non-Multipart Uploads ==========
 
             /**
@@ -55,7 +67,12 @@
              * If for some reason you want to only support multipart uploads,
              * you don't need to implement it.
              */
-            async getUploadParameters (file) {
+            async getUploadParameters (file, options) {
+              if (typeof crypto?.subtle === 'object') {
+                // If WebCrypto is available, let's do signing from the client.
+                return uppy.getPlugin('myAWSPlugin').createSignedURL(file, options)
+              }
+
               // Send a request to our Express.js signing endpoint.
               const response = await fetch('/sign-s3', {
                 method: 'POST',
@@ -66,6 +83,7 @@
                   filename: file.name,
                   contentType: file.type,
                 }),
+                signal: options.signal,
               })
 
               if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
@@ -139,7 +157,14 @@
               if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
             },
 
-            async signPart (file, { uploadId, key, partNumber, signal }) {
+            async signPart (file, options) {
+              if (typeof crypto?.subtle === 'object') {
+                // If WebCrypto, let's do signing from the client.
+                return uppy.getPlugin('myAWSPlugin').createSignedURL(file, options)
+              }
+
+              const { uploadId, key, partNumber, signal } = options
+
               if (signal?.aborted) {
                 const err = new DOMException('The operation was aborted', 'AbortError')
                 Object.defineProperty(err, 'cause', { __proto__: null, configurable: true, writable: true, value: signal.reason })

+ 1 - 0
yarn.lock

@@ -10679,6 +10679,7 @@ __metadata:
   resolution: "@uppy-example/aws-nodejs@workspace:examples/aws-nodejs"
   dependencies:
     "@aws-sdk/client-s3": ^3.338.0
+    "@aws-sdk/client-sts": ^3.338.0
     "@aws-sdk/s3-request-presigner": ^3.338.0
     body-parser: ^1.20.0
     dotenv: ^16.0.0