3
import path from 'path';
4
import bodyParser from 'body-parser';
5
import compress from 'compression';
6
import cookieParser from 'cookie-parser';
7
import multiparty from 'connect-multiparty';
8
import connectRestreamer from 'connect-restreamer';
9
import engines from 'consolidate';
10
import errorhandler from 'errorhandler';
11
import express from 'express';
12
import expressJwt from 'express-jwt';
13
import session from 'express-session';
15
import i18next from 'i18next';
16
import i18nextBackend from 'i18next-node-fs-backend';
17
import jwt from 'jsonwebtoken';
18
import methodOverride from 'method-override';
19
import morgan from 'morgan';
20
import favicon from 'serve-favicon';
21
import serveStatic from 'serve-static';
22
import sessionFileStore from 'session-file-store';
23
import _get from 'lodash/get';
24
import _noop from 'lodash/noop';
25
import rimraf from 'rimraf';
27
LanguageDetector as i18nextLanguageDetector,
28
handle as i18nextHandle
29
} from 'i18next-express-middleware';
30
import urljoin from './lib/urljoin';
31
import logger from './lib/logger';
32
import settings from './config/settings';
33
import * as api from './api';
34
import errclient from './lib/middleware/errclient';
35
import errlog from './lib/middleware/errlog';
36
import errnotfound from './lib/middleware/errnotfound';
37
import errserver from './lib/middleware/errserver';
38
import config from './services/configstore';
42
} from './access-control';
47
const log = logger('app');
49
const renderPage = (view = 'index', cb = _noop) => (req, res, next) => {
52
res.set({ 'X-UA-Compatible': 'IE=edge' });
54
const locals = { ...cb(req, res) };
55
res.render(view, locals);
58
const appMain = () => {
59
const app = express();
62
if (process.env.NODE_ENV === 'development') {
66
app.use(errorhandler());
69
app.enable('verbose errors');
70
app.disable('view cache');
73
app.disable('verbose errors');
74
app.enable('view cache');
77
app.enable('trust proxy');
78
app.enable('case sensitive routing');
79
app.disable('strict routing');
80
app.disable('x-powered-by');
82
for (let i = 0; i < settings.view.engines.length; ++i) {
83
const extension = settings.view.engines[i].extension;
84
const template = settings.view.engines[i].template;
85
app.engine(extension, engines[template]);
87
app.set('view engine', settings.view.defaultExtension);
89
path.resolve(__dirname, '../app'),
90
path.resolve(__dirname, 'views')
93
log.debug('app.settings: %j', app.settings);
99
.use(i18nextLanguageDetector)
100
.init(settings.i18next);
102
app.use(async (req, res, next) => {
105
const ipaddr = req.ip || req.connection.remoteAddress;
106
await authorizeIPAddress(ipaddr);
109
res.status(ERR_FORBIDDEN).end('Forbidden Access');
117
app.use((req, res, next) => {
118
res.removeHeader('X-Powered-By');
127
const path = settings.middleware.session.path;
132
const FileStore = sessionFileStore(session);
135
secret: settings.secret,
141
saveUninitialized: true,
143
store: new FileStore({
145
logFn: (...args) => {
146
log.debug.apply(log, args);
154
app.use(favicon(path.join(_get(settings, 'assets.app.path', ''), 'favicon.ico')));
155
app.use(cookieParser());
159
app.use(bodyParser.json(settings.middleware['body-parser'].json));
160
app.use(bodyParser.urlencoded(settings.middleware['body-parser'].urlencoded));
165
app.use(multiparty(settings.middleware.multiparty));
170
app.use(connectRestreamer());
173
app.use(methodOverride());
174
if (settings.verbosity > 0) {
177
morgan.token('id', (req, res) => {
178
return req.session.id;
180
app.use(morgan(settings.middleware.morgan.format));
182
app.use(compress(settings.middleware.compression));
184
Object.keys(settings.assets).forEach((name) => {
185
const asset = settings.assets[name];
187
log.debug('assets: name=%s, asset=%s', name, JSON.stringify(asset));
189
log.error('asset path is not defined');
193
asset.routes.forEach((assetRoute) => {
194
const route = urljoin(settings.route || '/', assetRoute || '');
195
log.debug('> route=%s', name, route);
196
app.use(route, serveStatic(asset.path, {
202
app.use(i18nextHandle(i18next, {}));
205
app.use(urljoin(settings.route, 'api'), expressJwt({
206
secret: config.get('secret'),
207
credentialsRequired: true
210
app.use(async (err, req, res, next) => {
211
let bypass = !(err && (err.name === 'UnauthorizedError'));
214
bypass = bypass || (process.env.NODE_ENV === 'development');
219
urljoin(settings.route, 'api/signin')
221
bypass = bypass || whitelist.some(path => {
222
return req.path.indexOf(path) === 0;
227
const token = _get(req, 'query.token') || _get(req, 'body.token');
230
const user = jwt.verify(token, settings.secret) || {};
231
await validateUser(user);
239
const ipaddr = req.ip || req.connection.remoteAddress;
240
log.warn(`Forbidden: ipaddr=${ipaddr}, code="${err.code}", message="${err.message}"`);
241
res.status(ERR_FORBIDDEN).end('Forbidden Access');
251
app.post(urljoin(settings.route, 'api/signin'), api.users.signin);
256
app.get(urljoin(settings.route, 'api/version/latest'), api.version.getLatestVersion);
259
app.get(urljoin(settings.route, 'api/state'), api.state.get);
260
app.post(urljoin(settings.route, 'api/state'), api.state.set);
261
app.delete(urljoin(settings.route, 'api/state'), api.state.unset);
264
app.get(urljoin(settings.route, 'api/gcode'), api.gcode.fetch);
265
app.post(urljoin(settings.route, 'api/gcode'), api.gcode.upload);
266
app.get(urljoin(settings.route, 'api/gcode/download'), api.gcode.download);
267
app.post(urljoin(settings.route, 'api/gcode/download'), api.gcode.download);
270
app.get(urljoin(settings.route, 'api/controllers'), api.controllers.get);
273
app.get(urljoin(settings.route, 'api/commands'), api.commands.fetch);
274
app.post(urljoin(settings.route, 'api/commands'), api.commands.create);
275
app.get(urljoin(settings.route, 'api/commands/:id'), api.commands.read);
276
app.put(urljoin(settings.route, 'api/commands/:id'), api.commands.update);
277
app.delete(urljoin(settings.route, 'api/commands/:id'), api.commands.__delete);
278
app.post(urljoin(settings.route, 'api/commands/run/:id'), api.commands.run);
281
app.get(urljoin(settings.route, 'api/events'), api.events.fetch);
282
app.post(urljoin(settings.route, 'api/events/'), api.events.create);
283
app.get(urljoin(settings.route, 'api/events/:id'), api.events.read);
284
app.put(urljoin(settings.route, 'api/events/:id'), api.events.update);
285
app.delete(urljoin(settings.route, 'api/events/:id'), api.events.__delete);
288
app.get(urljoin(settings.route, 'api/machines'), api.machines.fetch);
289
app.post(urljoin(settings.route, 'api/machines'), api.machines.create);
290
app.get(urljoin(settings.route, 'api/machines/:id'), api.machines.read);
291
app.put(urljoin(settings.route, 'api/machines/:id'), api.machines.update);
292
app.delete(urljoin(settings.route, 'api/machines/:id'), api.machines.__delete);
295
app.get(urljoin(settings.route, 'api/macros'), api.macros.fetch);
296
app.post(urljoin(settings.route, 'api/macros'), api.macros.create);
297
app.get(urljoin(settings.route, 'api/macros/:id'), api.macros.read);
298
app.put(urljoin(settings.route, 'api/macros/:id'), api.macros.update);
299
app.delete(urljoin(settings.route, 'api/macros/:id'), api.macros.__delete);
302
app.get(urljoin(settings.route, 'api/mdi'), api.mdi.fetch);
303
app.post(urljoin(settings.route, 'api/mdi'), api.mdi.create);
304
app.put(urljoin(settings.route, 'api/mdi'), api.mdi.bulkUpdate);
305
app.get(urljoin(settings.route, 'api/mdi/:id'), api.mdi.read);
306
app.put(urljoin(settings.route, 'api/mdi/:id'), api.mdi.update);
307
app.delete(urljoin(settings.route, 'api/mdi/:id'), api.mdi.__delete);
310
app.get(urljoin(settings.route, 'api/users'), api.users.fetch);
311
app.post(urljoin(settings.route, 'api/users/'), api.users.create);
312
app.get(urljoin(settings.route, 'api/users/:id'), api.users.read);
313
app.put(urljoin(settings.route, 'api/users/:id'), api.users.update);
314
app.delete(urljoin(settings.route, 'api/users/:id'), api.users.__delete);
317
app.get(urljoin(settings.route, 'api/watch/files'), api.watch.getFiles);
318
app.post(urljoin(settings.route, 'api/watch/files'), api.watch.getFiles);
319
app.get(urljoin(settings.route, 'api/watch/file'), api.watch.readFile);
320
app.post(urljoin(settings.route, 'api/watch/file'), api.watch.readFile);
324
app.get(urljoin(settings.route, '/'), renderPage('index.hbs', (req, res) => {
325
const webroot = _get(settings, 'assets.app.routes[0]', '');
326
const lng = req.language;
332
title: `${t('title')} ${settings.version}`,
333
loading: t('loading')
342
app.use(errnotfound({
343
view: path.join('common', '404.hogan'),
347
view: path.join('common', '500.hogan'),
348
error: 'Internal server error'
355
export default appMain;