import csurf from 'csurf'; import { createHash } from 'crypto'; import { NextFunction, Request, Response, Router, RouterOptions, json as jsonBodyParser } from 'express'; import moment from 'moment'; import { ChatController } from '../../controllers/chat-controller.class'; import { ControllerPool } from '../../controllers/lib/controller-pool.interface'; import { AuthenticationException } from '../../model/err/authentication.exception'; import { SessionHandler } from './session-handler.class'; const STATIC_USERS = { testuser: 'bc2d5cc456b81caa403661411cc72a309c39677d035b74b713a5ba02412d9eff' // pass1234 }; export abstract class HandlerBase implements ControllerPool { private _router: Router; private _chatCtrl?: ChatController; constructor(private sessionHandler?: SessionHandler, auth?: boolean, options?: RouterOptions) { this._router = Router(options); if (this.sessionHandler) { this._router.use(this.sessionHandler.handler); if (auth) { this._router.use(this.authHandler.bind(this)); } } } public get router(): Router { return this._router; } public get chat(): ChatController { if (!this._chatCtrl) { this._chatCtrl = new ChatController(this); } return this._chatCtrl; } protected avoidCache = (req, res, next) => { res.setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT'); res.setHeader('Last-Modified', `${moment().format('ddd, DD MMM YYYY HH:mm:ss')} CEST`); res.setHeader('Cache-Control', 'no-cache, max-age=0, must-revalidate, no-store'); res.setHeader('Pragma', 'no-cache'); next(); }; protected csrf(options?: { ignorePath: string[] }) { options = { ignorePath: [], ...options }; return (req, res, next) => { if (options.ignorePath.includes(req.path)) { return next(); } csurf({ ignoreMethods: ['GET', 'HEAD', 'OPTIONS'] })(req, res, (err?: any) => { if (err) return next(err); const proto = req.get('x-forwarded-proto') || req.protocol; res.cookie('XSRF-TOKEN', req.csrfToken(), { httpOnly: false, secure: proto === 'https', sameSite: 'strict' }); next(); }); }; } private async authHandler( req: Request>, res: Response>, next: NextFunction ): Promise { // Is there already a recovered session available -> skip auth handling if (req.session && req.session.user) { return next(); } // Login Requests Handling let loginUser, loginPass; if (req.method === 'POST' && req.body && req.body.user && req.body.password) { // JSON Post Body Login loginUser = req.body.user; loginPass = req.body.password; } else if (process.env.ENABLE_BASIC_AUTH && req.header('Authorization')?.substring(0, 5).toLowerCase() === 'basic') { // Basic Auth Login ( ^- Enable only for DEV) [loginUser, loginPass] = Buffer.from(req.header('Authorization').substring(6), 'base64').toString().split(':'); if (!process.env.UNIT_TEST_MODE) console.log('[INFO]', 'Authenticating via Basic Auth: ', loginUser); } if (loginUser && loginPass) { try { // --------------------------------------------- // // TODO: Implement your "real" login here. // This is just an example implementation based // on a STATIC_USERS array defined above ;) // --------------------------------------------- // const pass = STATIC_USERS[loginUser]; if (pass && pass === HandlerBase.hashPassword(loginPass)) { req.session.user = loginUser; // Hint: you can even store complex object types in a session, not just a string req.session.save(); return next(); } } catch (e) { return next(e); } } next(new AuthenticationException('No Session / Session Expired')); } public static hashPassword(password: string, salt?: string): string { if (!salt) { salt = process.env.PASSWORD_SALT; } return createHash('sha256').update(`${salt}${password}`).digest('hex'); } }