snow-editor

small markdown and org-mode editor
Log | Files | Refs | README

app.js (2020B)


      1 import express from 'express';
      2 import rateLimit from 'express-rate-limit';
      3 import { MSG } from './messages.js';
      4 import {
      5   getAllowedOrigins,
      6   parseAllowedOrigins,
      7   setAllowedOrigins,
      8 } from './originGuard.js';
      9 import documentsRouter from './routes/documents.js';
     10 
     11 export function createApp(options = {}) {
     12   const corsOrigin = options.corsOrigin ?? process.env.CORS_ORIGIN?.trim() ?? '';
     13   const shareAllowedOrigins = parseAllowedOrigins(
     14     options.shareAllowedOrigins ?? process.env.SHARE_ALLOWED_ORIGINS ?? '',
     15   );
     16   setAllowedOrigins(shareAllowedOrigins);
     17 
     18   const app = express();
     19   app.set('trust proxy', 1);
     20 
     21   if (corsOrigin) {
     22     app.use((req, res, next) => {
     23       const origin = req.headers.origin;
     24       if (origin === corsOrigin) {
     25         res.setHeader('Access-Control-Allow-Origin', origin);
     26         res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
     27         res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
     28       }
     29       if (req.method === 'OPTIONS') {
     30         return res.sendStatus(204);
     31       }
     32       next();
     33     });
     34   }
     35 
     36   app.use(express.json({ limit: '1.1mb' }));
     37 
     38   const apiLimiter = rateLimit({
     39     windowMs: 60 * 1000,
     40     max: 60,
     41     standardHeaders: true,
     42     legacyHeaders: false,
     43     message: {
     44       error: 'RATE_LIMIT',
     45       message: MSG.RATE_LIMIT,
     46     },
     47   });
     48 
     49   app.use('/api', apiLimiter, documentsRouter);
     50 
     51   app.use((err, _req, res, next) => {
     52     if (err instanceof SyntaxError && 'body' in err) {
     53       return res.status(400).json({
     54         error: 'INVALID_JSON',
     55         message: MSG.INVALID_JSON,
     56       });
     57     }
     58     next(err);
     59   });
     60 
     61   app.use((err, _req, res, _next) => {
     62     if (err?.type === 'entity.too.large') {
     63       return res.status(413).json({
     64         error: 'CONTENT_TOO_LARGE',
     65         message: MSG.CONTENT_TOO_LARGE,
     66       });
     67     }
     68     console.error(err);
     69     res.status(500).json({
     70       error: 'INTERNAL_ERROR',
     71       message: MSG.INTERNAL_ERROR,
     72     });
     73   });
     74 
     75   return app;
     76 }
     77 
     78 export { getAllowedOrigins };