Skip to content

Instantly share code, notes, and snippets.

@fdb
Last active January 30, 2017 20:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fdb/8d673525b97c0f9dfabad9bd02284124 to your computer and use it in GitHub Desktop.
Save fdb/8d673525b97c0f9dfabad9bd02284124 to your computer and use it in GitHub Desktop.
Uploading to S3 from the browser
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.rawgit.com/Caligatio/jsSHA/v2.2.0/src/sha256.js"></script>
<title>S3 Post Upload Example</title>
<style>
body { font: 14px arial; padding: 20px; margin: 80px auto; width: 40rem; }
input { display: block; font: 16px arial; padding: 5px; margin: 5px; width: 40rem;}
</style>
</head>
<body>
<h1>S3 POST Uploader</h1>
<form method="post" enctype="multipart/form-data" id="form">
<div id="hidden_fields"></div>
<input type="file" name="file" /> <br />
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
<script>
const AWS_POST_URL = 'https://enigmeta.s3.amazonaws.com/';
const AWS_ACCESS_KEY = 'YOUR_ACCESS_KEY_HERE';
const AWS_SECRET_KEY = 'YOUR_SECRET_KEY_HERE';
const AWS_REGION = 'us-east-1';
const AWS_BUCKET = 'enigmeta';
function prefixZeroes(s, length=2) {
while (s.length < length) {
s = '0' + s;
}
return s;
}
function hmacSha256(data, key, output='BYTES') {
const shaObj = new jsSHA('SHA-256', 'BYTES');
shaObj.setHMACKey(key, 'BYTES');
shaObj.update(data);
return shaObj.getHMAC(output);
}
function setHiddenFormField(name, value) {
const $form = document.getElementById('hidden_fields');
const $field = document.createElement('input');
$field.setAttribute('type', 'hidden');
$field.setAttribute('name', name);
$field.setAttribute('value', value);
$form.appendChild($field);
}
function dateToIso8601(d) {
const sYear = d.getFullYear();
const sMonth = prefixZeroes('' + (d.getMonth() + 1));
const sDate = prefixZeroes('' + d.getDate());
return sYear + sMonth + sDate + 'T000000Z';
}
const $form = document.getElementById('form');
$form.setAttribute('action', AWS_POST_URL);
const d = new Date();
const sYear = d.getFullYear();
const sMonth = prefixZeroes('' + (d.getMonth() + 1));
const sDate = prefixZeroes('' + d.getDate());
const sFullDate = sYear + sMonth + sDate;
const isoDate = dateToIso8601(d);
const credential = `${AWS_ACCESS_KEY}/${sFullDate}/${AWS_REGION}/s3/aws4_request`;
const MINUTES = 60 * 1000;
const HOURS = 60 * MINUTES;
const MEGABYTES = 1024 * 1024;
const expiration = new Date(Date.now() + 1 * HOURS);
const policy = {
expiration: expiration.toISOString(),
conditions: [
['starts-with', '$key', 'uploads'],
{acl: 'public-read'},
{bucket: AWS_BUCKET},
['content-length-range', 0, 1 * MEGABYTES],
{'success_action_status': '201'},
{'x-amz-algorithm': 'AWS4-HMAC-SHA256'},
{'x-amz-credential': credential},
{'x-amz-date': isoDate},
]
};
const stringToSign = btoa(JSON.stringify(policy));
const kDate = hmacSha256(sFullDate, 'AWS4' + AWS_SECRET_KEY);
const kRegion = hmacSha256(AWS_REGION, kDate);
const kService = hmacSha256('s3', kRegion);
const kSigning = hmacSha256('aws4_request', kService);
const signature = hmacSha256(stringToSign, kSigning, 'HEX');
setHiddenFormField('key', 'uploads/\${filename}');
setHiddenFormField('acl', 'public-read');
setHiddenFormField('success_action_status', '201');
setHiddenFormField('X-Amz-Algorithm', 'AWS4-HMAC-SHA256');
setHiddenFormField('X-Amz-Credential', credential);
setHiddenFormField('X-Amz-Date', isoDate);
setHiddenFormField('Policy', btoa(JSON.stringify(policy)));
setHiddenFormField('X-Amz-Signature', signature);
</script>
</body>
</html>
@fdb
Copy link
Author

fdb commented Jan 30, 2017

This Gist is based on Amazon's Browser-Based Uploads Using POST document.

It's a full example: all you need is to set the constants in the beginning: your access key, secret key, bucket and upload URL.

The library includes the jsSHA crypto library for HMAC signing.

How you protect the secret key is up to you. It's a bad idea to write it in the web page. You probably want to request the signature from a server, in which case part of this code will end up in node.js.

@fdb
Copy link
Author

fdb commented Jan 30, 2017

Version 1 uses a redirect URL, while version 2 uses a status of 201 which returns a XML response with additional information (such as the ETag).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment