diff --git a/package.json b/package.json index 2c5d0f0..56c200b 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,12 @@ "aws-sdk": "^2.978.0", "bcrypt": "^5.0.0", "colors": "^1.4.0", + "dicer": "^0.2.5", "express": "^4.17.1", + "express-form-data": "^2.0.17", + "express-rate-limit": "^5.3.0", "express-subdomain": "^1.0.5", + "formidable": "^1.2.2", "fs-extra": "^8.1.0", "got": "^11.8.2", "image-pixels": "^2.2.2", @@ -34,6 +38,7 @@ "mongoose": "^5.8.3", "mongoose-unique-validator": "^2.0.3", "morgan": "^1.9.1", + "multer": "^1.4.3", "nodemailer": "^6.4.2", "tga": "^1.0.4", "xmlbuilder": "^13.0.2", diff --git a/src/server.js b/src/server.js index ef672dc..560844d 100644 --- a/src/server.js +++ b/src/server.js @@ -13,6 +13,7 @@ const app = express(); const accountWiiU = require('./services/wiiu'); //const account3DS = require('./services/3ds'); +const datastore = require('./services/datastore'); // START APPLICATION app.set('etag', false); @@ -30,6 +31,7 @@ app.use(xmlparser); // import the servers into one app.use(accountWiiU); //app.use(account3DS); +app.use(datastore); // 404 handler logger.info('Creating 404 status handler'); diff --git a/src/services/datastore/index.js b/src/services/datastore/index.js new file mode 100644 index 0000000..0c0e09e --- /dev/null +++ b/src/services/datastore/index.js @@ -0,0 +1,20 @@ +const express = require('express'); +const subdomain = require('express-subdomain'); +const logger = require('../../../logger'); +const routes = require('./routes'); + +// Router to handle the subdomain +const datastore = express.Router(); + +// Setup routes +logger.info('[CDN] Applying imported routes'); +datastore.use(routes.UPLOAD); + +// Main router for endpoints +const router = express.Router(); + +// Create subdomains +logger.info('[CDN] Creating \'datastore\' subdomain'); +router.use(subdomain('datastore', datastore)); + +module.exports = router; \ No newline at end of file diff --git a/src/services/datastore/routes/index.js b/src/services/datastore/routes/index.js new file mode 100644 index 0000000..34215ff --- /dev/null +++ b/src/services/datastore/routes/index.js @@ -0,0 +1,3 @@ +module.exports = { + UPLOAD: require('./upload.js'), +}; \ No newline at end of file diff --git a/src/services/datastore/routes/upload.js b/src/services/datastore/routes/upload.js new file mode 100644 index 0000000..18f13b6 --- /dev/null +++ b/src/services/datastore/routes/upload.js @@ -0,0 +1,74 @@ +const crypto = require('crypto'); +const router = require('express').Router(); +const Dicer = require('dicer'); +const fs = require('fs'); +const util = require('../../../util'); + +const signatureSecret = fs.readFileSync(`${__dirname}/../../../../certs/nex/datastore/secret.key`); + +function multipartParser(request, response, next) { + const RE_BOUNDARY = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i; + const RE_FILE_NAME = /name="(.*)"/; + const boundary = RE_BOUNDARY.exec(request.header('content-type')); + const dicer = new Dicer({ boundary: boundary[1] || boundary[2] }); + const files = {}; + + dicer.on('part', part => { + let fileBuffer = Buffer.alloc(0); + let fileName = ''; + + part.on('header', header => { + fileName = RE_FILE_NAME.exec(header['content-disposition'][0])[1]; + }); + + part.on('data', data => { + fileBuffer = Buffer.concat([fileBuffer, data]); + }); + + part.on('end', () => { + files[fileName] = fileBuffer; + }); + }); + + dicer.on('finish', function () { + request.files = files; + return next(); + }); + + request.pipe(dicer); +} + +router.post('/upload', multipartParser, async (request, response) => { + const { + bucket, // Space name + key, // path + file, // the file content + acl, // S3 ACL + pid, // uploading user PID + date, // upload time + signature, // data signature + } = request.files; + + // Signatures only good for 1 minute + const minute = 1000 * 60; + const minuteAgo = Date.now() - minute; + + if (Number(date) < Math.floor(minuteAgo / 1000)) { + return response.sendStatus(400); + } + + const data = pid.toString() + bucket.toString() + key.toString() + date.toString(); + + const hmac = crypto.createHmac('sha256', signatureSecret).update(data).digest('hex'); + + console.log(hmac, signature.toString()); + + if (hmac !== signature.toString()) { + return response.sendStatus(400); + } + + await util.uploadCDNAsset(bucket.toString(), key.toString(), file, acl.toString()); + response.sendStatus(200); +}); + +module.exports = router; \ No newline at end of file