diff --git a/CHANGELOG.md b/CHANGELOG.md index 1acc5b2..03bc10d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ Slingshot Changelog =================== + +## Version 0.7.0 + +### Enhancements + + * Added `Slingshot.S3Storage.TempCredentials`. Thanks @jossoco + +### Bug Fixes + + * Fixed character encoding for content-disposition for AWS-S3 based directives ([#95](https://github.com/CulturalMe/meteor-slingshot/issues/95)). Thanks @timtch. + ## Version 0.6.2 Removed debugging log. diff --git a/README.md b/README.md index 106b88f..6913899 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,53 @@ Slingshot.createDirective("aws-s3-example", Slingshot.S3Storage, { }); ``` +#### S3 with temporary AWS Credentials (Advanced) + +For extra security you can use +[temporary credentials](http://docs.aws.amazon.com/STS/latest/UsingSTS/CreatingSessionTokens.html) to sign upload requests. + +```JavaScript +var sts = new AWS.STS(); // Using the AWS SDK to retrieve temporary credentials + +Slingshot.createDirective('myUploads', Slingshot.S3Storage.TempCredentials, { + bucket: 'myBucket', + temporaryCredentials: Meteor.wrapAsync(function (expire, callback) { + //AWS dictates that the minimum duration must be 900 seconds: + var duration = Math.max(Math.round(expire / 1000), 900); + + sts.getSessionToken({ + DurationSeconds: duration + }, function (error, result) { + callback(error, result && result.Credentials); + }); + }) +}); +``` + +If you are running slingshot on an EC2 instance, you can conveniantly retreive +your access keys with [`AWS.EC2MetadataCredentials`](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EC2MetadataCredentials.html): + +```JavaScript +var credentials = new AWS.EC2MetadataCredentials(); + +var updateCredentials = Meteor.wrapAsync(credentials.get, credentials); + +Slingshot.createDirective('myUploads', Slingshot.S3Storage.TempCredentials, { + bucket: 'myBucket', + temporaryCredentials: function () { + if (credentials.needsRefresh()) { + updateCredentials(); + } + + return { + AccessKeyId: credentials.accessKeyId, + SecretAccessKey: credentials.secretAccessKey, + SessionToken: credentials.sessionToken + }; + } +}); +``` + ### Google Cloud [Generate a private key](http://goo.gl/kxt5qz) and convert it to a `.pem` file @@ -457,10 +504,7 @@ i.e. `"https://d111111abcdef8.cloudfront.net"` `expire` Number (optional) - Number of milliseconds in which an upload authorization will expire after the request was made. Default is 5 minutes. -#### AWS S3 - -`bucket` String (**required**) - Name of bucket to use. The default is -`Meteor.settings.S3Bucket`. +#### AWS S3 (`Slingshot.S3Storage`) `region` String (optional) - Default is `Meteor.settings.AWSRegion` or "us-east-1". [See AWS Regions](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region) @@ -469,7 +513,17 @@ authorization will expire after the request was made. Default is 5 minutes. `AWSSecretAccessKey` String (**required**) - Can also be set in `Meteor.settings`. -#### Google Cloud Storage +#### AWS S3 with Temporary Credentials (`Slingshot.S3Storage.TempCredentials`) + +`region` String (optional) - Default is `Meteor.settings.AWSRegion` or +"us-east-1". [See AWS Regions](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region) + +`temporaryCredentials` Function (**required**) - Function that generates temporary +credentials. It takes a signle argument, which is the minumum desired expiration +time in milli-seconds and it returns an object that contains `AccessKeyId`, +`SecretAccessKey` and `SessionToken`. + +#### Google Cloud Storage (`Slingshot.GoogleCloud`) `bucket` String (**required**) - Name of bucket to use. The default is `Meteor.settings.GoogleCloudBucket`. @@ -500,7 +554,7 @@ the second is the meta-information that can be passed by the client. `contentDisposition` String (optional) - RFC 2616 Content-Disposition directive. Default is the uploaded file's name (inline). Use null to disable. -#### Rackspace Cloud +#### Rackspace Cloud (`Slingshot.RackspaceFiles`) `RackspaceAccountId` String (**required**) - Can also be set in `Meteor.settings`. @@ -508,11 +562,15 @@ Default is the uploaded file's name (inline). Use null to disable. `container` String (**required**) - Name of container to use. -`region` String (optional) - Data Center region. The default is `"iad3"`. [See other regions](http://docs.rackspace.com/files/api/v1/cf-devguide/content/Service-Access-Endpoints-d1e003.html) +`region` String (optional) - Data Center region. The default is `"iad3"`. +[See other regions](http://docs.rackspace.com/files/api/v1/cf-devguide/content/Service-Access-Endpoints-d1e003.html) -`pathPrefix` String or Function (**required**) - Simlar to `key` for S3, but will always be appended by `file.name` that is provided by the client. +`pathPrefix` String or Function (**required**) - Similar to `key` for S3, but +will always be appended by `file.name` that is provided by the client. -`deleteAt` Date (optional) - Absolute time when the uploaded file is to be deleted. _This attribute is not enforced at all. It can be easily altered by the client_ +`deleteAt` Date (optional) - Absolute time when the uploaded file is to be +deleted. _This attribute is not enforced at all. It can be easily altered by the +client_ `deleteAfter` Number (optional) - Same as `deleteAt`, but relative. diff --git a/package.js b/package.js index 696278d..c8f3db7 100644 --- a/package.js +++ b/package.js @@ -1,7 +1,7 @@ Package.describe({ name: "edgee:slingshot", summary: "Directly post files to cloud storage services, such as AWS-S3.", - version: "0.6.2", + version: "0.7.0", git: "https://github.com/CulturalMe/meteor-slingshot" }); diff --git a/services/aws-s3.js b/services/aws-s3.js index 01c6b98..55d2ac5 100644 --- a/services/aws-s3.js +++ b/services/aws-s3.js @@ -72,9 +72,7 @@ Slingshot.S3Storage = { */ upload: function (method, directive, file, meta) { - var url = Npm.require("url"), - - policy = new Slingshot.StoragePolicy() + var policy = new Slingshot.StoragePolicy() .expireIn(directive.expire) .contentLength(0, Math.min(file.size, directive.maxSize || Infinity)), @@ -89,7 +87,8 @@ Slingshot.S3Storage = { "Cache-Control": directive.cacheControl, "Content-Disposition": directive.contentDisposition || file.name && - "inline; filename=" + quoteString(file.name, '"') + "inline; filename=" + quoteString(file.name, '"') + + "; filename*=utf-8''" + encodeURIComponent(file.name) }, bucketUrl = _.isFunction(directive.bucketUrl) ? @@ -170,6 +169,37 @@ Slingshot.S3Storage = { } }; +Slingshot.S3Storage.TempCredentials = _.defaults({ + + directiveMatch: _.chain(Slingshot.S3Storage.directiveMatch) + .omit("AWSAccessKeyId", "AWSSecretAccessKey") + .extend({ + temporaryCredentials: Function + }) + .value(), + + directiveDefault: _.omit(Slingshot.S3Storage.directiveDefault, + "AWSAccessKeyId", "AWSSecretAccessKey"), + + applySignature: function (payload, policy, directive) { + var credentials = directive.temporaryCredentials(directive.expire); + + check(credentials, Match.ObjectIncluding({ + AccessKeyId: Slingshot.S3Storage.directiveMatch.AWSAccessKeyId, + SecretAccessKey: Slingshot.S3Storage.directiveMatch.AWSSecretAccessKey, + SessionToken: String + })); + + payload["x-amz-security-token"] = credentials.SessionToken; + + return Slingshot.S3Storage.applySignature + .call(this, payload, policy, _.defaults({ + AWSAccessKeyId: credentials.AccessKeyId, + AWSSecretAccessKey: credentials.SecretAccessKey + }, directive)); + } +}, Slingshot.S3Storage); + function quoteString(string, quotes) { return quotes + string.replace(quotes, '\\' + quotes) + quotes; }