/* * Copyright (C) Sapphirecode - All Rights Reserved * Created by Timo Hocker , January 2020 */ // @ts-nocheck /* eslint-disable no-magic-numbers */ 'use strict'; const password_helper = require ('@scode/password-helper'); const crypto = require ('@scode/crypto-helper'); const consts = require ('@scode/consts'); const me = {}; /** * initializes the module * * @param {Function} get_user * function that returns {id:number, password:string, salt:string} * for a given user identifier * @param {Array} ignore_paths array of regex to skip auth * @returns {Function} request handler */ function init (get_user, ignore_paths = []) { me.get_user = get_user; me.session_timeout_milliseconds = 300000; me.ignore_paths = ignore_paths; me.jwt_secret = crypto.create_salt (); me.app_id = crypto.create_salt (); return request_handler; } /** * tries to authenticate a user * * @param {string} user name or email of the given user * @param {string} password hashed password * @returns {Promise} session key if successful */ async function authenticate (user, password) { const user_entry = await new Promise ((res) => res (me.get_user (user))); if (!user_entry) return null; if (!await password_helper.verify (user_entry.password, password)) return null; const session_key = crypto.sign_object ( { id: user_entry.id }, me.jwt_secret ); return session_key; } /** * gets the correct salt for a given user * * @param {string} user user name or email to query */ async function salt (user) { const user_entry = await new Promise ((res) => res (me.get_user (user))); if (!user_entry) return null; return user_entry.salt; } /** * block if no auth header found * * @param {string} session session key * @param {string} user user name * @param {any} res response object * @returns {boolean} true if handler blocked request */ function request_handler_block (session, user, res) { if (typeof session === 'undefined' && typeof user === 'undefined') { res.status (consts.http.status_unauthorized); res.end (); return true; } return false; } /** * handle authentication * * @param {string} session session key * @param {string} user user name * @param {string} key user hash * @param {any} res response object * @param {any} next next handler * @returns {Promise} true if handler authenticated */ async function request_handler_authenticate (session, user, key, res, next) { if (typeof session === 'undefined' && typeof user !== 'undefined') { if (typeof key === 'undefined') { const user_salt = await salt (user); res.status ( user_salt === null ? consts.http.status_forbidden : consts.http.status_ok ); res.end (user_salt); return true; } const session_key = await authenticate (user, key); res.status ( session_key === null ? consts.http.status_forbidden : consts.http.status_ok ) .cookie (me.app_id, session_key, { maxAge: 900000, httpOnly: true }) .end (session_key); return true; } try { const jwt = crypto.verify_signature ( session, me.jwt_secret, me.session_timeout_milliseconds ); res.locals.user_id = jwt.id; const new_user_token = crypto.sign_object ( { id: jwt.id }, me.jwt_secret ); res.cookie ( me.app_id, new_user_token, { maxAge: 900000, httpOnly: true } ) .header ('session', new_user_token); next (); return true; } catch (err) { return false; } } /** * handles http requests * * @param {any} req request * @param {any} res response * @param {any} next next handler */ async function request_handler (req, res, next) { if (Array.isArray (me.ignore_paths)) { for (const regex of me.ignore_paths) { if (regex.test (req.url)) { next (); return; } } } const { user, key, session: header_session } = req.headers; const cookie_session = typeof req.cookies === 'undefined' ? null : req.cookies[me.app_id]; const session = cookie_session || header_session; if (request_handler_block (session, user, res)) return; if (await request_handler_authenticate (session, user, key, res, next)) return; res.status (consts.http.status_forbidden); res.end (); } module.exports = init;