import express, { Express } from 'express'; import multiparty from 'multiparty-express'; import { AuthHandler } from './handlers/auth-handler.class'; import { EchoHandler } from './handlers/echo-handler.class'; import { SessionHandler } from './handlers/lib/session-handler.class'; import { PrivateHandler } from './handlers/private-handler.class'; import { PublicHandler } from './handlers/public-handler.class'; import { AuthenticationException } from './model/err/authentication.exception'; import { HttpStatusException } from './model/err/http-status.exception'; const multipartMiddleware = multiparty(); export class Webserver { private app!: Express; private sessionHandler: SessionHandler; constructor(port: number) { try { this.app = express(); this.app.set('trust proxy', 1); // Must be set for SessionHandler -> cookie.secure = 'auto' to work this.app.disable('x-powered-by'); // Prevent Express server to send Header "X-Powered-By: Express" (websecurity issue) // Parse Bodies containing application/json this.app.use(express.json()); // Parse Bodies containing application/x-www-form-urlencoded this.app.use(express.urlencoded({ extended: true })); // Parse Bodies containing multipart/form-data this.app.use((req, res, next) => { if (req.header('content-type')?.startsWith('multipart/')) { multipartMiddleware(req, res, next); } else { next(); } }); // General Header Settings this.app.use((req, res, next) => { res.setHeader('X-Frame-Options', 'SAMEORIGIN'); res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload;'); res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader('Access-Control-Allow-Headers', 'X-CSRF-Token'); res.setHeader('Access-Control-Expose-Headers', 'x-csrf-token'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, OPTIONS, HEAD'); next(); }); this.sessionHandler = new SessionHandler(); /** Authentication endpoint /auth/ */ const auth = new AuthHandler(this.sessionHandler); this.app.use('/auth', auth.router); /** Send any request to /echo - receive your request data back */ const echo = new EchoHandler(); this.app.use('/echo', echo.router); /** Serves files in /private via URL /login/~ */ const priv = new PrivateHandler(this.sessionHandler); this.app.use('/login', priv.router); /** Global Error Handler - transforms exceptions into the right HTTP response */ this.app.use((err, req, res, next) => { try { if (err instanceof AuthenticationException) { res.status(err.statusCode).send(err.statusText); } else if (err instanceof HttpStatusException) { res.status(err.statusCode).send(err.message); } else if (Object.keys(err).includes('code') && err.code === 'EBADCSRFTOKEN') { res.status(403).send(err.message); } else { console.error(err); res.status(500).send('INTERNAL SERVER ERROR'); } } catch (error) { console.error(error); res.status(500).send(error); } }); /** Serves files in /public via root URL /~ */ const pub = new PublicHandler(); this.app.use('/', pub.router); this.app.listen(port, () => { console.log(`Example app listening on http://localhost:${port}`); }); } catch (error) { console.error(error); process.exit(1); } } }