Skip to content

Commit

Permalink
Adds support to sign the request using amazon lambda
Browse files Browse the repository at this point in the history
  • Loading branch information
mcfedr committed May 16, 2015
1 parent a1b05a3 commit 381e5fc
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 9 deletions.
42 changes: 42 additions & 0 deletions README.md
Expand Up @@ -137,3 +137,45 @@ The `supported` property is _Boolean_, and indicates whether the browser has the
## Integration

* [angular-evaporate](https://github.com/uqee/angular-evaporate) — AngularJS module.

## Using AWS Lambda to do the signing

You need to do a couple of things

* Include the Aws SDK for Javascript, either directly, bower, or browserify

<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.28.min.js"></script>

* Create a lambda function see: [`signing_example_lambda.js`](example/signing_example_lambda.js)

* Setup an IAM user with permissions to call your lambda function. This user should be separate from the one that can
upload to S3. Here is a sample policy

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1431709794000",
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws:lambda:...:function:cw-signer"
]
}
]
}

* Pass two options to the Evaporate constructor - `lambda` and `lambdaFunc`, instead of `signerUrl`

var _e_ = new Evaporate({
aws_key: 'your aws_key here',
bucket: 'your s3 bucket name here',
lambda: new AWS.Lambda({
'region': 'lambda region',
'accessKeyId': 'a key that can invoke the lambda function',
'secretAccessKey': 'the secret'
}),
lambdaFunc: 'arn:aws:lambda:...:function:cw-signer' // arn of your lambda function
});
40 changes: 31 additions & 9 deletions evaporate.js
Expand Up @@ -68,9 +68,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
err = 'Missing attribute: name ';
}
else if(con.encodeFilename) {
file.name = encodeURIComponent(file.name); // prevent signature fail in case file name has spaces
}
file.name = encodeURIComponent(file.name); // prevent signature fail in case file name has spaces
}

/*if (!(file.file instanceof File)){
err += '.file attribute must be instanceof File';
}*/
Expand Down Expand Up @@ -325,7 +325,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
var eTag = xhr.getResponseHeader('ETag'), msg;
l.d('uploadPart 200 response for part #' + partNumber + ' ETag: ' + eTag);
if(part.isEmpty || (eTag != ETAG_OF_0_LENGTH_BLOB)) // issue #58
{
{
part.eTag = eTag;
part.status = COMPLETE;
}
Expand Down Expand Up @@ -583,13 +583,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
}
else
{
var xmlHttpRequest = new XMLHttpRequest();
var xmlHttpRequest = new XMLHttpRequest();

xmlHttpRequest.open("GET", con.timeUrl + '?requestTime=' + new Date().getTime(), false);
xmlHttpRequest.send();
requester.dateString = xmlHttpRequest.responseText;
requester.dateString = xmlHttpRequest.responseText;
}

requester.x_amz_headers = extend(requester.x_amz_headers,{
'x-amz-date': requester.dateString
});
Expand Down Expand Up @@ -657,10 +657,14 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
function authorizedSend(authRequester){

l.d('authorizedSend() ' + authRequester.step);
if (con.lambda) {
return authorizedSignWithLambda(authRequester);
}

var xhr = new XMLHttpRequest();
xhrs.push(xhr);
authRequester.authXhr = xhr;
var url = con.signerUrl+'?to_sign='+makeStringToSign(authRequester);
var url = con.signerUrl+'?to_sign='+encodeURIComponent(makeStringToSign(authRequester));
var warnMsg;

for (var param in me.signParams) {
Expand Down Expand Up @@ -714,6 +718,24 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
xhr.send();
}

function authorizedSignWithLambda(authRequester) {
con.lambda.invoke({
FunctionName: con.lambdaFunc,
InvocationType: 'RequestResponse',
Payload: JSON.stringify({"to_sign": makeStringToSign(authRequester)})
}, function (err, data) {
if (err) {
warnMsg = 'failed to get authorization with lambda ' + err;
l.w(warnMsg);
me.warn(warnMsg);
authRequester.onFailedAuth(err);
return;
}
authRequester.auth = JSON.parse(data.Payload);
authRequester.onGotAuth();
})
}

function makeStringToSign(request){

var x_amz_headers = '', to_sign, header_key_array = [];
Expand All @@ -737,7 +759,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
x_amz_headers +
(con.cloudfront ? '/' + con.bucket : '')+
request.path;
return encodeURIComponent(to_sign);
return to_sign;
}

function getPath() {
Expand Down
85 changes: 85 additions & 0 deletions example/evaporate_example_lambda.html
@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>

<title>Evaporate Example</title>

<style>

</style>


<script language="javascript" type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.28.min.js"></script>
<script language="javascript" type="text/javascript" src="../evaporate.js"></script>

</head>
<body>

<div>
<input type="file" id="files" multiple />
<div>


<script language="javascript">

var files;

var _e_ = new Evaporate({
aws_key: 'your aws_key here',
bucket: 'your s3 bucket name here',
lambda: new AWS.Lambda({
'region': 'lambda region',
'accessKeyId': 'a key that can invoke the lambda function',
'secretAccessKey': 'the secret'
}),
lambdaFunc: 'arn:aws:lambda:...:function:cw-signer' // arn of your lambda function
});

$('#files').change(function(evt){
files = evt.target.files;

for (var i = 0; i < files.length; i++){

_e_.add({
name: 'test_' + Math.floor(1000000000*Math.random()),
file: files[i],
notSignedHeadersAtInitiate: {
'Cache-Control': 'max-age=3600'
},
xAmzHeadersAtInitiate : {
'x-amz-acl': 'public-read'
},
signParams: {
foo: 'bar',
fooFunction: function() {
return 'bar';
}
},
signHeaders: {
fooHeaderFunction: function() {
return 'bar'
},
fooHeader: 'bar'
},
beforeSigner: function(xhr) {
var requestDate = (new Date()).toISOString();
xhr.setRequestHeader('Request-Header', requestDate);
},
complete: function(){
console.log('complete................yay!');
},
progress: function(progress){
console.log('making progress: ' + progress);
}
});
}

$(evt.target).val('');

});

</script>

</body>
</html>
14 changes: 14 additions & 0 deletions example/signing_example_lambda.js
@@ -0,0 +1,14 @@
var crypto = require('crypto');
var secret = 'YOUR_AWS_SECRET_KEY';

exports.handler = function(event, context) {
if (!event.to_sign) {
context.fail('Missing to_sign param');
return;
}
context.succeed(
crypto.createHmac('sha1', secret)
.update(event.to_sign)
.digest('base64')
);
};

0 comments on commit 381e5fc

Please sign in to comment.