Newer
Older
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Lloyd Hilaiel
committed
// a teensy tinsy module to do parameter validation. A good candiate for future
// librification.
//
// usage:
//
// const validate = require('validate.js');
//
// app.post('/wsapi/foo', validate([ "email", "site" ]), function(req, resp) {
// });
const
logger = require('./logging.js').logger,
Zachary Carter
committed
httputils = require('./httputils.js'),
check = require('validator').check;
var types = {
email: function(x) {
check(x).isEmail();
},
password: function(x) {
check(x).len(8,80);
},
boolean: function(x) {
if (typeof x !== 'boolean') throw "boolean required";
},
token: function(x) {
check(x).len(48,48).isAlphanumeric();
},
assertion: function(x) {
check(x).len(50,10240).regex(/[0-9a-zA-Z~_-]+/);
},
pubkey: function(x) {
check(x).len(50,10240);
JSON.parse(x);
},
origin: function(x) {
/* origin regex
/^ // beginning
https?:\/\/ // starts with http:// or https://
(?=.{1,254}(?::|$)) // hostname must be within 1-254 characters
(?: // match hostname part (<part>.<part>...)
Lloyd Hilaiel
committed
(?!-) // cannot start with a dash (allow it to start with a digit re issue #2042)
(?![a-z0-9\-]{1,62}- // part cannot end with a dash
(?:\.|:|$)) // (end of part will be '.', ':', or end of str)
[a-z0-9\-]{1,63}\b // part will be 1-63 letters, numbers, or dashes
(?!\.$) // final part cannot end with a '.'
\.? // part followed by '.' unless final part
)+ // one or more hostname parts
(:\d+)? // optional port
$/i; // end; case-insensitive
*/
Lloyd Hilaiel
committed
var regex = /^https?:\/\/(?=.{1,254}(?::|$))(?:(?!-)(?![a-z0-9\-]{1,62}-(?:\.|:|$))[a-z0-9\-]{1,63}\b(?!\.$)\.?)+(:\d+)?$/i;
if (typeof x !== 'string' || !x.match(regex)) {
throw new Error("not a valid origin");
Zachary Carter
committed
}
}
};
Lloyd Hilaiel
committed
module.exports = function (params) {
Zachary Carter
committed
// normalize the parameters description, verify all specified types are present
if (Array.isArray(params) || typeof params !== 'object' || typeof params === null) {
throw "argument to validate must be an object, not a " + (typeof params);
}
Object.keys(params).forEach(function(p) {
var v = params[p];
if (typeof v === 'string') {
v = { type: v };
}
if (typeof v.required === "undefined") v.required = true;
if (!types[v.type]) throw "unknown type specified in WSAPI:" + v.type;
params[p] = v;
});
Lloyd Hilaiel
committed
return function(req, resp, next) {
Zachary Carter
committed
var reqParams = null;
Lloyd Hilaiel
committed
if (req.method === "POST") {
Zachary Carter
committed
reqParams = req.body;
Lloyd Hilaiel
committed
} else {
Zachary Carter
committed
reqParams = req.query;
Lloyd Hilaiel
committed
}
Zachary Carter
committed
// clear body and query to prevent wsapi handlers from accessing
// un-validated input parameters
req.body = {};
req.query = {};
req.params = {};
// now validate
Lloyd Hilaiel
committed
try {
Zachary Carter
committed
// allow csrf through
if (reqParams.csrf) {
req.params.csrf = reqParams.csrf;
delete reqParams.csrf;
}
Object.keys(params).forEach(function(p) {
if (params[p].required && !reqParams.hasOwnProperty(p)) throw "missing required parameter: '" + p + "'";
if (reqParams[p] === undefined) return;
// validate
try {
types[params[p].type](reqParams[p]);
} catch (e) {
throw p + ": " + e.toString();
Lloyd Hilaiel
committed
}
Zachary Carter
committed
req.params[p] = reqParams[p];
delete reqParams[p];
Lloyd Hilaiel
committed
});
Zachary Carter
committed
// if there are any keys left in reqParams, they're not allowable!
var extra = Object.keys(reqParams);
if (extra.length) throw "extra parameters are not allowed: " + extra.join(', ');
Lloyd Hilaiel
committed
} catch(e) {
Zachary Carter
committed
var msg = {
success: false,
reason: e.toString()
};
logger.warn("bad request received: " + msg.reason);
resp.statusCode = 400;
return resp.json(msg);
Lloyd Hilaiel
committed
}
Ben Adida
committed
Zachary Carter
committed
Ben Adida
committed
// this is called outside the try/catch because errors
// in the handling of the request should be caught separately
next();