/* */
const mach = require("../index");
const makeToken = require("../utils/makeToken");
const {is, not, isNil} = require("ramda");
mach.extend(
require("../extensions/server")
);
/**
* The set of HTTP request methods that are considered safe because they
* do not alter server data.
*/
const SAFE_METHODS = {
GET: true,
HEAD: true,
OPTIONS: true,
TRACE: true
};
/**
* A middleware that helps to prevent Cross-site Request Forgery attacks by
* requiring the client to include an authentication token in all form
* submissions that matches a value stored in the session cookie. See
* http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html
*
* If the session does not already have an authentication token one is
* automatically generated and stored in the session. The default session key
* is "_token". All form submissions need to include this value in the "_token"
* parameter, like this:
*
* <form method="POST" action="/">
* <input type="hidden" name="_token" value="{{session._token}}">
* </form>
*
* On the backend, you need to put both mach.session and mach.params in front of
* mach.token in order for it to be able to retrieve values from the request session
* and parameters, like this:
*
* app.use(mach.session);
* app.use(mach.params);
* app.use(mach.token);
* app.run(function (conn) {
* // The connection authenticated successfully
* });
*
* Options may be any of the following:
*
* - paramName The name of the request parameter that contains the token
* (i.e. the value of the "name" attribute on your <input>).
* Defaults to "_token"
* - sessionKey The name of the session variable to use to store the token.
* Defaults to "_token"
* - byteLength The length of the token in bytes. Defaults to 32
*
* Note: Non-POST requests are always forwarded to the downstream app regardless of
* whether or not they contain the token since it is assumed they are not modifying
* anything and are safe.
*/
function verifyToken(app, options) {
options = options || {};
Iif (is(String, options)) {
options = {paramName: options};
}
const paramName = options.paramName || "_token";
const sessionKey = options.sessionKey || "_token";
const byteLength = options.byteLength || 32;
return function (conn) {
const session = conn.session, params = conn.params;
Iif (isNil(session)) {
conn.onError(new Error("No session! Use mach.session in front of mach.token"));
} else Iif (isNil(params)) {
conn.onError(new Error("No params! Use mach.params in front of mach.token"));
} else {
let token = session[sessionKey];
// Create a new session token if needed.
if (!token) {
token = session[sessionKey] = makeToken(byteLength);
}
if (params[paramName] && params[paramName] === token) {
return conn.run(app);
}
}
// If the request is not a POST we assume it's not a form submission
// and therefore not modifying anything. Pass it downstream.
if (SAFE_METHODS[conn.method] === true) {
return conn.run(app);
}
conn.text(403, "Forbidden");
};
}
module.exports = verifyToken;
|