diff --git a/README.md b/README.md index f4a8a43..6c4e574 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,11 @@ function getSignedUrl(file, callback) { Server-Side ----------- -### Bundled router +### Bundled routers You can use the Express router that is bundled with this module to answer calls to `/s3/sign` +#### Express + ```js app.use('/s3', require('react-s3-uploader/s3router')({ bucket: "MyS3Bucket", @@ -111,7 +113,19 @@ app.use('/s3', require('react-s3-uploader/s3router')({ })); ``` -This also provides another endpoint: `GET /s3/img/(.*)` and `GET /s3/uploads/(.*)`. This will create a temporary URL +# Koa 1.0 + +```js +import s3router from 'react-s3-uploader/s3router'; + +app.use(require('react-s3-uploader/s3router')({ + /* Same as above with these additional options: */ + endpoint: 'https://rest.s3alternative.com', // optional. useful for s3-compatible APIs + prefix: '/v1/s3' // optional. default is /s3. useful if you version your API endpoints +})); +``` + +These also provide another endpoint: `GET /s3/img/(.*)` and `GET /s3/uploads/(.*)`. This will create a temporary URL that provides access to the uploaded file (which are uploaded privately by default). The request is then redirected to the URL, so that the image is served to the client. diff --git a/package.json b/package.json index 8f56468..8c1bb23 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,10 @@ "homepage": "https://github.com/odysseyscience/react-s3-uploader", "dependencies": { "aws-sdk": "2.x", + "koa-router": "^5.4.0", + "latinize": "0.2.x", "node-uuid": "1.x", "object-assign": "^2.0.0", - "latinize": "0.2.x", "unorm": "1.4.x" }, "peerDependencies": { diff --git a/s3router-koa.js b/s3router-koa.js new file mode 100644 index 0000000..eba3cce --- /dev/null +++ b/s3router-koa.js @@ -0,0 +1,111 @@ +import Router from 'koa-router'; +import uuid from 'node-uuid'; +import AWS from 'aws-sdk'; + +const { S3_ACCESS_KEY, S3_SECRET_ACCESS_KEY, S3_ENDPOINT } = process.env; + +function checkTrailingSlash (path) { + let newPath; + + if (path && path[path.length - 1] !== '/') { + newPath = path + '/'; + } + + return newPath; +} + +export default function S3Router (options) { + const S3_BUCKET = options.bucket; + + if (!S3_BUCKET) { + throw new Error('S3_BUCKET is required.'); + } + + const s3Options = {}; + if (options.region) { + s3Options.region = options.region; + } + if (options.signatureVersion) { + s3Options.signatureVersion = options.signatureVersion; + } + if (options.endpoint) { + s3Options.endpoint = options.endpoint; + } + + const s3 = new AWS.S3({ + ...s3Options, + accessKeyId: S3_ACCESS_KEY, + secretAccessKey: S3_SECRET_ACCESS_KEY, + endpoint: S3_ENDPOINT + }); + + const router = new Router({ + prefix: options.prefix || 's3' + }); + + /** + * Redirects image requests with a temporary signed URL, giving access + * to GET an upload. + */ + function * tempRedirect() { + const self = this; + + const params = { + Bucket: S3_BUCKET, + Key: options.key + }; + + s3.getSignedUrl('getObject', params, function (err, url) { + self.redirect(url); + }); + } + + /** + * Image specific route. + */ + router.get(/\/img\/(.*)/, tempRedirect); + + /** + * Other file type(s) route. + */ + router.get(/\/uploads\/(.*)/, tempRedirect); + + /** + * Returns an object with `signedUrl` and `publicUrl` properties that + * give temporary access to PUT an object in an S3 bucket. + */ + router.get('/sign', function * () { + const self = this; + const filename = uuid.v4() + '_' + this.query.objectName; + const mimeType = this.query.contentType; + + // Set any custom headers + if (options.headers) { + this.set(options.headers); + } + + const params = { + Bucket: S3_BUCKET, + Key: checkTrailingSlash(options.key) + filename, + Expires: 60, + ContentType: mimeType, + ACL: options.ACL || 'private' + }; + + s3.getSignedUrl('putObject', params, function (err, data) { + if (err) { + console.log(err); + self.status = 500; + self.body = 'Cannot create S3 signed URL'; + } + + self.body = { + signedUrl: data, + publicUrl: '/s3/uploads/' + filename, + filename + }; + }); + }); + + return router.routes(); +}