webserver.class.ts 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import express, { Express } from 'express';
  2. import multiparty from 'multiparty-express';
  3. import { AuthHandler } from './handlers/auth-handler.class';
  4. import { EchoHandler } from './handlers/echo-handler.class';
  5. import { SessionHandler } from './handlers/lib/session-handler.class';
  6. import { PrivateHandler } from './handlers/private-handler.class';
  7. import { PublicHandler } from './handlers/public-handler.class';
  8. import { AuthenticationException } from './model/err/authentication.exception';
  9. import { HttpStatusException } from './model/err/http-status.exception';
  10. const multipartMiddleware = multiparty();
  11. export class Webserver {
  12. private app!: Express;
  13. private sessionHandler: SessionHandler;
  14. constructor(port: number) {
  15. try {
  16. this.app = express();
  17. this.app.set('trust proxy', 1); // Must be set for SessionHandler -> cookie.secure = 'auto' to work
  18. this.app.disable('x-powered-by'); // Prevent Express server to send Header "X-Powered-By: Express" (websecurity issue)
  19. // Parse Bodies containing application/json
  20. this.app.use(express.json());
  21. // Parse Bodies containing application/x-www-form-urlencoded
  22. this.app.use(express.urlencoded({ extended: true }));
  23. // Parse Bodies containing multipart/form-data
  24. this.app.use((req, res, next) => {
  25. if (req.header('content-type')?.startsWith('multipart/')) {
  26. multipartMiddleware(req, res, next);
  27. } else {
  28. next();
  29. }
  30. });
  31. // General Header Settings
  32. this.app.use((req, res, next) => {
  33. res.setHeader('X-Frame-Options', 'SAMEORIGIN');
  34. res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload;');
  35. res.setHeader('Access-Control-Allow-Credentials', 'true');
  36. res.setHeader('Access-Control-Allow-Headers', 'X-CSRF-Token');
  37. res.setHeader('Access-Control-Expose-Headers', 'x-csrf-token');
  38. res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, OPTIONS, HEAD');
  39. next();
  40. });
  41. this.sessionHandler = new SessionHandler();
  42. /** Authentication endpoint /auth/ */
  43. const auth = new AuthHandler(this.sessionHandler);
  44. this.app.use('/auth', auth.router);
  45. /** Send any request to /echo - receive your request data back */
  46. const echo = new EchoHandler();
  47. this.app.use('/echo', echo.router);
  48. /** Serves files in /private via URL /login/~ */
  49. const priv = new PrivateHandler(this.sessionHandler);
  50. this.app.use('/login', priv.router);
  51. /** Global Error Handler - transforms exceptions into the right HTTP response */
  52. this.app.use((err, req, res, next) => {
  53. try {
  54. if (err instanceof AuthenticationException) {
  55. res.status(err.statusCode).send(err.statusText);
  56. } else if (err instanceof HttpStatusException) {
  57. res.status(err.statusCode).send(err.message);
  58. } else if (Object.keys(err).includes('code') && err.code === 'EBADCSRFTOKEN') {
  59. res.status(403).send(err.message);
  60. } else {
  61. console.error(err);
  62. res.status(500).send('INTERNAL SERVER ERROR');
  63. }
  64. } catch (error) {
  65. console.error(error);
  66. res.status(500).send(error);
  67. }
  68. });
  69. /** Serves files in /public via root URL /~ */
  70. const pub = new PublicHandler();
  71. this.app.use('/', pub.router);
  72. this.app.listen(port, () => {
  73. console.log(`Example app listening on http://localhost:${port}`);
  74. });
  75. } catch (error) {
  76. console.error(error);
  77. process.exit(1);
  78. }
  79. }
  80. }