2020-03-25 16:58:43 +01:00

201 lines
4.8 KiB
JavaScript

/*
* Copyright (C) Sapphirecode - All Rights Reserved
* This file is part of Auth-Server-Helper which is released under MIT.
* See file 'LICENSE' for full license details.
* Created by Timo Hocker <timo@scode.ovh>, March 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
* @param {Array<RegExp>} 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<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;
}
}
/**
* check if a filter matches a request
*
* @param {any} req request
* @param {any} filter filter
* @returns {boolean} true if filter matches
*/
function filter_matches (req, filter) {
if (filter instanceof RegExp && filter.test (req.url))
return true;
return req.method === filter.method
&& filter.regex
&& filter.regex.test (req.url);
}
/**
* 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 ignore of me.ignore_paths) {
if (filter_matches (req, ignore)) {
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;