Line data Source code
1 : // @ts-check
2 : const
3 1 : express = require('express'),
4 1 : helmet = require('helmet'),
5 1 : serveStatic = require('express-static-gzip'),
6 1 : path = require('path'),
7 1 : { attempt, noop } = require('lodash'),
8 1 : { makeDeferred } = require('@cern/prom'),
9 1 : ews = require('express-ws'),
10 :
11 1 : { CamService } = require('@cern/cam-express'),
12 1 : logger = require('./httpLogger'),
13 1 : auth = require('./auth');
14 :
15 : /**
16 : * @typedef {import('express').Request} Request
17 : * @typedef {import('express').Response} Response
18 : * @typedef {import('express').NextFunction} NextFunction
19 : */
20 :
21 : class Server {
22 : /**
23 : * @param {AppServer.Config} config
24 : */
25 : constructor(config) {
26 1 : this.config = config;
27 1 : this.app = express();
28 1 : this.camService = null;
29 1 : this._prom = this.prepare(config);
30 : }
31 :
32 : /**
33 : * @param {AppServer.Config} config
34 : */
35 : async prepare(config) {
36 1 : if (process.env.NODE_ENV === 'production') {
37 0 : this.app.use(helmet.contentSecurityPolicy({
38 : useDefaults: true,
39 : directives: {
40 : imgSrc: [ "'self'", "data:", "blob:" ]
41 : }
42 : }));
43 : }
44 1 : logger.register(this.app);
45 1 : ews(this.app);
46 :
47 1 : this.router = express.Router();
48 1 : const dist = path.join(__dirname, '..', 'www', 'dist');
49 1 : this.router.use('/dist', serveStatic(dist, { enableBrotli: true }));
50 :
51 : /* everything after this point is authenticated */
52 1 : if (config.auth) {
53 1 : await auth.register(this.router, config);
54 1 : this.router.use(this.runAuth.bind(this, 'user'));
55 : }
56 :
57 1 : this.app.set('view engine', 'pug');
58 1 : this.app.set('views', path.join(__dirname, 'views'));
59 :
60 1 : this.router.get('/', (req, res) => res.render('index', config));
61 :
62 1 : if (config.auth) {
63 : // protected admin path for admin role only
64 1 : this.router.use('/admin', this.runAuth.bind(this, [ 'admin' ]));
65 : }
66 :
67 : /* NOTE: declare your additional endpoints here */
68 1 : const swaggerOpts = {
69 : definition: {
70 : info: {
71 : title: config.title,
72 : description: 'Web App API to interact with the back-end',
73 : contact: config.contact
74 : },
75 : basePath: config.basePath
76 : }
77 : };
78 1 : this.camService = new CamService(config.camApp, swaggerOpts);
79 :
80 1 : if (config.auth) {
81 1 : this.router?.put("/*",
82 : auth.roleCheck.bind(null, [ "expert", "operator" ]));
83 : }
84 :
85 1 : this.camService.register(this.router);
86 :
87 1 : this.app.use(config.basePath, this.router);
88 :
89 : // default route
90 1 : this.app.use(function(req, res, next) {
91 0 : next({ status: 404, message: 'Not Found' });
92 : });
93 :
94 : // error handler
95 1 : this.app.use(function(
96 : /** @type {any} */ err,
97 : /** @type {Request} */ req,
98 : /** @type {Response} */res,
99 : /** @type {NextFunction} */ next) { /* eslint-disable-line */ /* jshint ignore:line */
100 0 : res.locals.message = err.message;
101 0 : res.locals.error = req.app.get('env') === 'development' ? err : {};
102 0 : res.locals.status = err.status || 500;
103 0 : res.status(err.status || 500);
104 0 : res.render('error', config);
105 : });
106 : }
107 :
108 : close() {
109 1 : if (this.camService) {
110 1 : this.camService.release();
111 1 : this.camService = null;
112 : }
113 1 : if (this.server) {
114 1 : this.server.close();
115 1 : this.server = null;
116 : }
117 : }
118 :
119 : /**
120 : * @param {string|string[]} role
121 : * @param {Request} req
122 : * @param {Response} res
123 : * @param {NextFunction} next
124 : */
125 : runAuth(role, req, res, next) {
126 0 : if (req.path.startsWith('/dist')) {
127 0 : return next();
128 : }
129 0 : return auth.roleCheck(role, req, res, next);
130 : }
131 :
132 : /**
133 : * @param {() => any} cb
134 : */
135 : async listen(cb) {
136 1 : await this._prom;
137 1 : const def = makeDeferred();
138 : /* we're called as a main, let's listen */
139 1 : var server = this.app.listen(this.config.port, () => {
140 1 : this.server = server;
141 1 : def.resolve(undefined);
142 1 : return attempt(cb || noop);
143 : });
144 1 : return def.promise;
145 : }
146 :
147 : address() {
148 1 : if (this.server) {
149 1 : return this.server.address();
150 : }
151 0 : return null;
152 : }
153 : }
154 :
155 1 : module.exports = Server;
|