@@ -11,7 +11,7 @@ const passport = require('passport'); | |||
const canonicalizeRequest = require('../../util/canonicalize').canonicalizeRequest; | |||
const requireAuth = require('../../util/auth').requireAuth; | |||
const wrap = require('../../util/wrap.js'); | |||
const verifyBody = require('../../util/verifyBody'); | |||
const bodyVerifier = require('../../util/verifyBody').bodyVerifier; | |||
// Wraps passport.authenticate to return a promise | |||
const authenticate = (req, res, next) => { | |||
@@ -68,7 +68,7 @@ const registerProps = [ | |||
{name: 'password', type: 'string'}, | |||
{name: 'invite', type: 'string'}]; | |||
router.post('/register', | |||
verifyBody(registerProps), canonicalizeRequest, | |||
bodyVerifier(registerProps), canonicalizeRequest, | |||
validateInvite, validateUsername, | |||
wrap(async (req, res, next) => { | |||
// Update the database | |||
@@ -89,7 +89,7 @@ const loginProps = [ | |||
{name: 'username', type: 'string', optional: true}, | |||
{name: 'displayname', type: 'string', optional: true}, | |||
{name: 'password', type: 'string'}]; | |||
router.post('/login', verifyBody(loginProps), canonicalizeRequest, wrap(async (req, res, next) => { | |||
router.post('/login', bodyVerifier(loginProps), canonicalizeRequest, wrap(async (req, res, next) => { | |||
// Authenticate | |||
const user = await authenticate(req, res, next); | |||
if (!user) | |||
@@ -9,13 +9,12 @@ const User = require(ModelPath + 'User.js'); | |||
const wrap = require('../../util/wrap.js'); | |||
const requireAuth = require('../../util/auth').requireAuth; | |||
const verifyScope = require('../../util/verifyScope'); | |||
const verifyBody = require('../../util/verifyBody'); | |||
const bodyVerifier = require('../../util/verifyBody').bodyVerifier; | |||
const createParams = [{name: 'scope', instance: Array}]; | |||
router.post('/create', requireAuth('invite.create'), verifyBody(createParams), wrap(async (req, res, next) => { | |||
router.post('/create', requireAuth('invite.create'), bodyVerifier(createParams), wrap(async (req, res, next) => { | |||
const scope = req.body.scope; | |||
const hasPermission = scope.every(scope => verifyScope(req.scope, scope)); | |||
if (!hasPermission) | |||
if (!scope.every(scope => verifyScope(req.scope, scope))) | |||
return res.status(403).json({message: 'Requested scope exceeds own scope.'}); | |||
const invite = { | |||
@@ -38,7 +37,7 @@ router.post('/create', requireAuth('invite.create'), verifyBody(createParams), w | |||
})); | |||
const deleteParams = [{name: 'code', type: 'string'}]; | |||
router.post('/delete', requireAuth('invite.delete'), verifyBody(deleteParams), wrap(async (req, res, next) => { | |||
router.post('/delete', requireAuth('invite.delete'), bodyVerifier(deleteParams), wrap(async (req, res, next) => { | |||
let query = {code: req.body.code}; | |||
// Users need a permission to delete invites other than their own | |||
@@ -48,7 +47,7 @@ router.post('/delete', requireAuth('invite.delete'), verifyBody(deleteParams), w | |||
// Find the invite | |||
const invite = await Invite.findOne(query).catch(next); | |||
if (!invite) | |||
return res.status(404).json({message: 'Invite not found.'}); | |||
return res.status(422).json({message: 'Invite not found.'}); | |||
// Users need a permission to delete invites that have been used | |||
if (!verifyScope(req.scope, 'invite.delete.used') && invite.used != null && invite.recipient != null) | |||
@@ -58,13 +57,15 @@ router.post('/delete', requireAuth('invite.delete'), verifyBody(deleteParams), w | |||
res.status(200).json({message: 'Invite deleted.'}); | |||
})); | |||
const getParams = [{name: 'code', type: 'string', optional: true}]; | |||
router.get('/get', requireAuth('invite.get'), verifyBody(getParams), wrap(async (req, res, next) => { | |||
const getParams = [{name: 'code', type: 'string', optional: true}, {name: 'issuer', type: 'string', optional: true}]; | |||
router.get('/get', requireAuth('invite.get'), bodyVerifier(getParams), wrap(async (req, res, next) => { | |||
let query = {}; | |||
// Users need a permission to list invites other than their own | |||
if (!verifyScope(req.scope, 'invite.get.others')) | |||
query.issuer = req.username; | |||
else if (req.body.issuer) | |||
query.issuer = req.body.issuer; | |||
// Narrow down the query by code if specified | |||
if (req.body.code) | |||
@@ -1,7 +1,7 @@ | |||
// Verifies a single property is well formed | |||
const verifyProp = (req, expected) => new Promise((resolve, reject) => { | |||
const prop = req.body[expected.name]; | |||
const sanitizer = require('sanitizer'); | |||
// Verifies a single property is well formed | |||
const verifyProp = (prop, expected) => new Promise((resolve, reject) => { | |||
if (!expected.optional && !prop) | |||
return reject({code: 400, message: expected.name + ' not specified.'}); | |||
@@ -12,13 +12,13 @@ const verifyProp = (req, expected) => new Promise((resolve, reject) => { | |||
return reject({code: 400, message: expected.name + ' malformed.'}); | |||
if (prop && expected.maxLength && prop.length > expected.maxLength) | |||
return reject({code: 422, message: expected.name + ' too long.'}); | |||
return reject({code: 400, message: expected.name + ' too long.'}); | |||
if (prop && expected.sanitize && req.sanitize(prop) !== prop) | |||
return reject({code: 422, message: expected.name + ' contains invalid characters.'}); | |||
if (prop && expected.sanitize && sanitizer.sanitize(prop) !== prop) | |||
return reject({code: 400, message: expected.name + ' contains invalid characters.'}); | |||
if (prop && expected.restrict && prop.replace(expected.restrict, '') !== prop) | |||
return reject({code: 422, message: expected.name + ' contains invalid characters.'}); | |||
return reject({code: 400, message: expected.name + ' contains invalid characters.'}); | |||
resolve(); | |||
}); | |||
@@ -26,11 +26,15 @@ const verifyProp = (req, expected) => new Promise((resolve, reject) => { | |||
// Verifies the entire request body is well formed | |||
// expectedProps follows the format: | |||
// [{name: 'myList', instance: 'Array'}, {name: 'myVar', type: 'string', optional: true}, etc.] | |||
const verifyBody = expectedProps => | |||
const verifyBody = (body, expectedProps) => | |||
Promise.all(expectedProps.map(expected => verifyProp(body[expected.name], expected))); | |||
const bodyVerifier = expectedProps => | |||
(req, res, next) => { | |||
Promise.all(expectedProps.map(expected => verifyProp(req, expected))) | |||
verifyBody(req.body, expectedProps) | |||
.then(() => next()) | |||
.catch(err => res.status(err.code).json({message: err.message})); | |||
}; | |||
module.exports = verifyBody; | |||
exports.verifyBody = verifyBody; | |||
exports.bodyVerifier = bodyVerifier; |
@@ -23,6 +23,7 @@ | |||
"passport": "^0.4.0", | |||
"passport-local": "^1.0.0", | |||
"passport-local-mongoose": "^5.0.1", | |||
"sanitizer": "^0.1.3", | |||
"type-is": "^1.6.16", | |||
"vinyl-source-stream": "^2.0.0" | |||
}, | |||
@@ -42,7 +43,8 @@ | |||
"minimatch": "^3.0.4", | |||
"mocha": "^5.2.0", | |||
"nodemon": "^1.18.3", | |||
"npx": "^10.2.0" | |||
"npx": "^10.2.0", | |||
"nyc": "^12.0.2" | |||
}, | |||
"author": "Jack Foltz", | |||
"license": "LICENSE", | |||