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/nodash'),
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 : license: config.license
75 : },
76 : basePath: config.basePath
77 : }
78 : };
79 1 : this.camService = new CamService(config.camApp, swaggerOpts);
80 :
81 1 : if (config.auth) {
82 1 : this.router?.put("/*",
83 : auth.roleCheck.bind(null, [ "expert", "operator" ]));
84 : }
85 :
86 1 : this.camService.register(this.router);
87 :
88 1 : this.app.use(config.basePath, this.router);
89 :
90 : // default route
91 1 : this.app.use(function(req, res, next) {
92 0 : next({ status: 404, message: 'Not Found' });
93 : });
94 :
95 : // error handler
96 1 : this.app.use(function(
97 : /** @type {any} */ err,
98 : /** @type {Request} */ req,
99 : /** @type {Response} */res,
100 : /** @type {NextFunction} */ next) { /* eslint-disable-line */ /* jshint ignore:line */
101 0 : res.locals.message = err.message;
102 0 : res.locals.error = req.app.get('env') === 'development' ? err : {};
103 0 : res.locals.status = err.status || 500;
104 0 : res.status(err.status || 500);
105 0 : res.render('error', config);
106 : });
107 : }
108 :
109 : close() {
110 1 : if (this.camService) {
111 1 : this.camService.release();
112 1 : this.camService = null;
113 : }
114 1 : if (this.server) {
115 1 : this.server.close();
116 1 : this.server = null;
117 : }
118 : }
119 :
120 : /**
121 : * @param {string|string[]} role
122 : * @param {Request} req
123 : * @param {Response} res
124 : * @param {NextFunction} next
125 : */
126 : runAuth(role, req, res, next) {
127 0 : if (req.path.startsWith('/dist')) {
128 0 : return next();
129 : }
130 0 : return auth.roleCheck(role, req, res, next);
131 : }
132 :
133 : /**
134 : * @param {() => any} cb
135 : */
136 : async listen(cb) {
137 1 : await this._prom;
138 1 : const def = makeDeferred();
139 : /* we're called as a main, let's listen */
140 1 : var server = this.app.listen(this.config.port, () => {
141 1 : this.server = server;
142 1 : def.resolve(undefined);
143 1 : return attempt(cb || noop);
144 : });
145 1 : return def.promise;
146 : }
147 :
148 : address() {
149 1 : if (this.server) {
150 1 : return this.server.address();
151 : }
152 0 : return null;
153 : }
154 : }
155 :
156 1 : module.exports = Server;
|