/**
* @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.'
);
}
}