2020-03-06 18:02:08 +01:00
|
|
|
/*
|
|
|
|
* Copyright (C) Sapphirecode - All Rights Reserved
|
|
|
|
* Created by Timo Hocker <timo@scode.ovh>, 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<Promise|object>} get_user
|
|
|
|
* function that returns {id:number, password:string, salt:string}
|
|
|
|
* for a given user identifier
|
2020-03-07 17:56:41 +01:00
|
|
|
* @param {Array<RegExp>} ignore_paths array of regex to skip auth
|
|
|
|
* @returns {Function} request handler
|
2020-03-06 18:02:08 +01:00
|
|
|
*/
|
2020-03-07 17:56:41 +01:00
|
|
|
function init (get_user, ignore_paths = []) {
|
2020-03-06 18:02:08 +01:00
|
|
|
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 ();
|
2020-03-07 17:56:41 +01:00
|
|
|
return request_handler;
|
2020-03-06 18:02:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* tries to authenticate a user
|
|
|
|
*
|
|
|
|
* @param {string} user name or email of the given user
|
|
|
|
* @param {string} password hashed password
|
|
|
|
* @returns {Promise<string>} 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<boolean>} 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 ();
|
|
|
|
}
|
|
|
|
|
2020-03-07 17:56:41 +01:00
|
|
|
module.exports = init;
|