|
|
@@ -0,0 +1,116 @@
|
|
|
+import csurf from 'csurf';
|
|
|
+import { createHash } from 'crypto';
|
|
|
+import { NextFunction, Request, Response, Router, RouterOptions, json as jsonBodyParser } from 'express';
|
|
|
+import moment from 'moment';
|
|
|
+
|
|
|
+import { AuthenticationException } from '../model/err/authentication.exception';
|
|
|
+import { SessionHandler } from './helpers/session-handler.class';
|
|
|
+
|
|
|
+const STATIC_USERS = {
|
|
|
+ testuser: 'bc2d5cc456b81caa403661411cc72a309c39677d035b74b713a5ba02412d9eff' // pass1234
|
|
|
+};
|
|
|
+
|
|
|
+export abstract class HandlerBase {
|
|
|
+ private _router: Router;
|
|
|
+
|
|
|
+ constructor(private sessionHandler?: SessionHandler, auth?: boolean, options?: RouterOptions) {
|
|
|
+ this._router = Router(options);
|
|
|
+
|
|
|
+ this.router.use(jsonBodyParser());
|
|
|
+
|
|
|
+ if (this.sessionHandler) {
|
|
|
+ this._router.use(this.sessionHandler.handler);
|
|
|
+ if (auth) {
|
|
|
+ this._router.use(this.authHandler.bind(this));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected useCsrfMiddleware(options?: { ignorePath: string[] }) {
|
|
|
+ options = {
|
|
|
+ ignorePath: [],
|
|
|
+ ...options
|
|
|
+ };
|
|
|
+ this.router.use((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();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public get router(): Router {
|
|
|
+ return this._router;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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();
|
|
|
+ };
|
|
|
+
|
|
|
+ private async authHandler(
|
|
|
+ req: Request<any, any, any, any, Record<string, any>>,
|
|
|
+ res: Response<any, Record<string, any>>,
|
|
|
+ next: NextFunction
|
|
|
+ ): Promise<void> {
|
|
|
+ // 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');
|
|
|
+ }
|
|
|
+}
|