/**
* @module authentication-controller
*/
const csrfProtection = require( 'csurf' )( {
cookie: true
} );
const jwt = require( 'jwt-simple' );
const express = require( 'express' );
const router = express.Router();
// var debug = require( 'debug' )( 'authentication-controller' );
module.exports = app => {
app.use( `${app.get( 'base path' )}/`, router );
};
router
.get( '/login', csrfProtection, login )
.get( '/logout', logout )
.post( '/login', csrfProtection, setToken );
/**
* @param {module:api-controller~ExpressRequest} req - HTTP request
* @param {module:api-controller~ExpressResponse} res - HTTP response
* @param {Function} next - Express callback
*/
function login( req, res, next ) {
let error;
const authSettings = req.app.get( 'linked form and data server' ).authentication;
const returnUrl = req.query.return_url || '';
if ( authSettings.type.toLowerCase() !== 'basic' ) {
if ( authSettings.url ) {
// the url is expected to:
// - authenticate the user,
// - set a session cookie (cross-domain if necessary) or add a token as query parameter to the return URL,
// - and return the user back to Enketo
// - enketo will then pass the cookie or token along when requesting resources, or submitting data
// Though returnUrl was encoded with encodeURIComponent, for some reason it appears to have been automatically decoded here.
res.redirect( authSettings.url.replace( '{RETURNURL}', encodeURIComponent( returnUrl ) ) );
} else {
error = new Error( 'Enketo configuration error. External authentication URL is missing.' );
error.status = 500;
next( error );
}
} else if ( req.app.get( 'env' ) !== 'production' || req.protocol === 'https' || req.headers[ 'x-forwarded-proto' ] === 'https' || req.app.get( 'linked form and data server' ).authentication[ 'allow insecure transport' ] ) {
res.render( 'surveys/login', {
csrfToken: req.csrfToken(),
server: req.app.get( 'linked form and data server' ).name
} );
} else {
error = new Error( 'Forbidden. Enketo needs to use https in production mode to enable authentication.' );
error.status = 405;
next( error );
}
}
/**
* @param {module:api-controller~ExpressRequest} req - HTTP request
* @param {module:api-controller~ExpressResponse} res - HTTP response
*/
function logout( req, res ) {
res
.clearCookie( req.app.get( 'authentication cookie name' ) )
.clearCookie( '__enketo_meta_username' )
.clearCookie( '__enketo_logout' )
.render( 'surveys/logout' );
}
/**
* @param {module:api-controller~ExpressRequest} req - HTTP request
* @param {module:api-controller~ExpressResponse} res - HTTP response
*/
function setToken( req, res ) {
const username = req.body.username.trim();
const maxAge = 30 * 24 * 60 * 60 * 1000;
const returnUrl = req.query.return_url || '';
const token = jwt.encode( {
user: username,
pass: req.body.password
}, req.app.get( 'encryption key' ) );
// Do not allow authentication cookies to be saved if enketo runs on http, unless 'allow insecure transport' is set to true
// This is double because the check in login() already ensures the login screen isn't even shown.
const secure = ( req.protocol === 'production' && !req.app.get( 'linked form and data server' ).authentication[ 'allow insecure transport' ] );
const authOptions = {
secure,
signed: true,
httpOnly: true,
path: '/'
};
const uidOptions = {
signed: true,
maxAge: 30 * 24 * 60 * 60 * 1000,
path: '/'
};
if ( req.body.remember ) {
authOptions.maxAge = maxAge;
uidOptions.maxAge = maxAge;
}
// store the token in a cookie on the client
res
.cookie( req.app.get( 'authentication cookie name' ), token, authOptions )
.cookie( '__enketo_logout', true )
.cookie( '__enketo_meta_username', username, uidOptions );
if ( returnUrl ) {
res.redirect( returnUrl );
} else {
res.send( 'Username and password are stored. You can close this page now.' );
}
}