Skip to content

Commit

Permalink
examples/aws: client-side signing (#4463)
Browse files Browse the repository at this point in the history
  • Loading branch information
aduh95 committed Jul 20, 2023
1 parent c3aec25 commit 961f11f
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 2 deletions.
56 changes: 56 additions & 0 deletions examples/aws-nodejs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand All @@ -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) => {
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions examples/aws-nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
29 changes: 27 additions & 2 deletions examples/aws-nodejs/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,34 @@ <h1>AWS upload example</h1>
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 ==========

/**
* This method tells Uppy how to handle non-multipart uploads.
* 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',
Expand All @@ -66,6 +83,7 @@ <h1>AWS upload example</h1>
filename: file.name,
contentType: file.type,
}),
signal: options.signal,
})

if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
Expand Down Expand Up @@ -139,7 +157,14 @@ <h1>AWS upload example</h1>
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 })
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 961f11f

Please sign in to comment.