Skip to content

Instantly share code, notes, and snippets.

@bschwind
Last active August 27, 2015 01:45
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 bschwind/277a078d59a70d451d0d to your computer and use it in GitHub Desktop.
Save bschwind/277a078d59a70d451d0d to your computer and use it in GitHub Desktop.
"use strict";
var BUCKET_NAME = process.env.BUCKET;
var crypto = require("crypto");
var jwt = require("jsonwebtoken");
var logger = require("services/logger").getLogger("general");
var mime = require("mime-types");
var moment = require("moment");
var uuid = require("node-uuid");
var validation = require("services/validation");
var uploadController = {};
function InvalidFileFormatError() {}
InvalidFileFormatError.prototype = Object.create(Error.prototype);
function generateUploadToken(userID, objectKey) {
var issuedTime = moment();
// An upload token is valid for 10 hours
var expiredTime = issuedTime.clone().add(10, "hours");
var token = jwt.sign({
sub: userID,
key: objectKey,
exp: expiredTime.valueOf(), // exp = Expiration date
iat: issuedTime.valueOf() // iat = Issued At Time
}, process.env.AWS_SECRET);
return token;
}
function checkUploadToken(token) {
var decodedToken;
try {
decodedToken = jwt.verify(token, process.env.AWS_SECRET);
} catch (err) {
return false;
}
if (!decodedToken) {
// Invalid or improperly signed token
return false;
}
if (moment(decodedToken.exp) <= moment()) {
// Token has expired
return false;
}
return decodedToken;
}
var allowedVideoTypes = [
"video/h264", // .h264
"video/webm", // .webm
"video/x-flv", // .flv
"video/quicktime", // .mov, .qt
"video/mpeg", // .mpeg, .mpg
"video/ogg", // .ogv
"video/x-matroska", // .mkv
"video/x-ms-wmv", // .wmv
"video/x-msvideo", // .avi
"video/jpeg" // .jpgv
];
uploadController.initiateUploadRequest = function (req, res) {
var data = {};
validation.run(req, {
file_name: [validation.required]
})
.then(function (fields) {
var extension = fields.file_name.split(".").pop();
var contentType = mime.types[extension];
if (allowedVideoTypes.indexOf(contentType) >= 0) {
var objectKey = "raw_videos/" + uuid.v4() + "." + extension;
var token = generateUploadToken(req.user.id, objectKey);
data.message_code = 200;
data.message = "OK";
data.token = token;
data.object_key = objectKey;
// TODO - We probably want a separate IAM user specifically
// for S3 uploads. Giving out the access key isn't
// dangerous but it's not a great practice.
data.access_key = process.env.AWS_KEY;
res.status(200).json(data);
} else {
// Invalid file format
throw new InvalidFileFormatError();
}
})
.catch(validation.ValidationError, function (err) {
data.message_code = 1;
data.message = "Invalid parameters";
data.errors = err.errors;
res.status(400).json(data);
})
.catch(InvalidFileFormatError, function (err) {
data.message_code = 22;
data.message = "Invalid file type";
data.errors = err.errors;
res.status(400).json(data);
})
.catch(function (err) {
logger.error(err);
data.message_code = 500;
data.message = "Internal server error";
res.status(500).json(data);
});
};
uploadController.signMultipartUploadRequest = function (req, res) {
if (!req.query.to_sign || !req.query.upload_token) {
return res.status(400).send();
}
var decodedToken = checkUploadToken(req.query.upload_token);
if (!decodedToken) {
return res.status(401).send();
}
var toSign = decodeURIComponent(req.query.to_sign);
var clientObjectKey = "/" + BUCKET_NAME + "/" + decodedToken.key;
// The object key the client will use is contained in the last line
// of the string we're signing.
var lastLine = toSign.split("\n").pop();
// If the client tries to upload an S3 key that is different than what
// we issued, we'll smack them with a 403
if (lastLine.indexOf(clientObjectKey) !== 0) {
return res.status(403).send();
}
res.send(crypto
.createHmac("sha1", process.env.AWS_SECRET)
.update(req.query.to_sign)
.digest("base64")
);
};
module.exports = uploadController;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment