'use strict';
/**
* Some helper functions/utilities for authentication.
* @module utils/auth
* @license MIT
* @author Kai KRETSCHMANN <kai@kretschmann.consulting>
*/
const UsersService = require('../service/UsersService');
const jwt = require('jsonwebtoken');
require('custom-env').env(true);
const sharedSecret = process.env.JWT_SECRET;
const issuer = process.env.JWT_ISSUER;
const log4js = require('log4js');
const logger = log4js.getLogger();
logger.level = process.env.LOGLEVEL || /* istanbul ignore next */ 'warn';
/**
* Verify a given token from bearer http header request.
* @method verifyToken
* @public
* @async
* @param {object} req The complete web request
* @param {string} _scopes Not used here
* @param {object} schema Secruity schema used for this API method
* @returns {boolean} true if valid token found in request
*/
exports.verifyToken = async function (req, _scopes, schema) {
logger.info('In verifyToken');
const currentReqScopes = req.openapi.schema['x-security-scopes']
logger.info(`currentReqScopes: ${currentReqScopes}`);
logger.info('schema:');
logger.info(schema);
const token = req.headers.authorization;
logger.info(`token header: ${token}`);
// validate the 'Authorization' header. it should have the following format:
// 'Bearer tokenString'
if (token && token.indexOf('Bearer ') === 0) {
const tokenString = token.split(' ')[1];
const untrustedDecodedToken = jwt.decode(tokenString);
let trustedDecodedToken = '';
try {
trustedDecodedToken = jwt.verify(tokenString, sharedSecret);
} catch (err) {
logger.error(`JWT verify failed: ${err}`);
logger.error(err.message);
logger.error(`Token data, user=${untrustedDecodedToken.sub}, IP=${req.realip}`);
if (err.name === 'TokenExpiredError') {
await UsersService.logAuthExpired(untrustedDecodedToken.sub, req.realip, err.expiredAt);
} else {
await UsersService.logAuthFailure(untrustedDecodedToken.sub, req.realip);
}
if (req.sentry) {
req.sentry.setUser({ username: untrustedDecodedToken.sub, ip_address: req.realip });
req.sentry.setContext('JWT', {
msg: err.message
});
req.sentry.captureException(err);
}
throw (err);
}
logger.debug('Decoded token:');
logger.debug(trustedDecodedToken);
if (!trustedDecodedToken) {
logger.error('Decode failed');
await UsersService.logAuthFailure(untrustedDecodedToken.sub, req.realip);
return false;
}
// check if the JWT was verified correctly
if (trustedDecodedToken.role) {
logger.info(`User has role ${trustedDecodedToken.role} in JWT`);
// check if the issuer matches
const issuerMatch = trustedDecodedToken.iss === issuer;
if (!issuerMatch) {
await UsersService.logAuthFailure(untrustedDecodedToken.sub, req.realip);
logger.error('issuer doesn\'t match');
return false;
}
// Check if users role matches api precondition, will return if OK, otherwise jump out
logger.debug('Check active users role matching API requirements');
await UsersService.isActiveHasRole(trustedDecodedToken.sub, currentReqScopes);
req.auth = trustedDecodedToken;
logger.debug('Added AUTH to request object');
// Add user data to sentry
req.sentry.setUser({ username: trustedDecodedToken.sub, ip_address: req.realip });
// store success
await UsersService.logAuthSuccess(trustedDecodedToken.sub, req.realip, trustedDecodedToken.exp);
return true;
}
} else { // not reached if API filters right before
logger.error('Token without Bearer keyword');
logger.debug(req.headers);
logger.debug(req);
return false;
}
};
/**
* Helper function for issuing a web token
* @method doIssue
* @private
* @param {string} username The textual user name
* @param {string} therole The textual user role
* @param {string} exprange The textual length in time
* @returns {object} JWT token
*/
function doIssue (username, therole, exprange) {
logger.info(`user ${username}, ${therole} role`);
return jwt.sign(
{
sub: username,
iss: issuer,
role: therole
},
sharedSecret, {
expiresIn: exprange
}
);
}
/**
* Issue a valid web token for a given user and role.
* Make it default valid duration.
* @method issueToken
* @public
* @param {string} username The textual user name
* @param {string} role The textual user role
* @returns {object} JWT token
*/
exports.issueToken = function (username, role) {
return doIssue(username, role, '365d');
};
/**
* Issue a short valid web token for a given user and role.
* Make it short testing valid duration.
* @method issueShortToken
* @public
* @param {string} username The textual user name
* @param {string} role The textual user role
* @returns {object} JWT token
*/
exports.issueShortToken = function (username, role) {
return doIssue(username, role, '1s');
};