import { AConfigChecker } from './src/AConfigChecker'; import * as express from 'express'; import { AddressInfo } from "net"; import * as path from 'path'; import * as bodyParser from 'body-parser'; const i18next = require('i18next'); // FIXME import from const i18nextMiddleware = require('i18next-http-middleware'); // FIXME import from const resourcesToBackend = require('i18next-resources-to-backend'); // FIXME import from import route_index from './routes/index'; import route_login from './routes/login'; import route_calendar from './routes/calendar'; import route_trombinoscope from './routes/trombinoscope'; import route_accounting from './routes/accounting'; import Security from './src/Security'; import { Session, SessionManager } from './src/Session'; import UserConnectorFactory, { IUserConnector } from './src/UserConnector/UserConnectorFactory'; import DBConnectorFactory from './src/DbConnector/DBConnectorFactory'; const debug = require('debug')('my express app'); const app = express(); declare global { namespace Express { interface Request { mSession: Session mCookies: Map t: (s: string) => string // i18n translation function } interface Response { i18next: any renderWithTranslations: (translationNs: string, renderView: string, args: any) => Promise } } } // Check config (async _ => { await i18next.use(i18nextMiddleware.LanguageDetector) .use(resourcesToBackend((language: string, namespace: string, callback: Function) => { import(`./locales/${language}/${namespace}.json`) .then((resources) => { callback(null, resources) }) .catch((error) => { callback(error, null) }) })) .init({ preload: ['fr', 'en'], nonExplicitSupportedLngs: true, lowerCaseLng: true }); let cc: AConfigChecker[] = [ UserConnectorFactory, await DBConnectorFactory.GetConnector() ]; for (let i of cc) { let err: Promise | Error | null = i.CheckConfig(); let errResult: Error | null = null; try { if (err instanceof Promise) await err; else errResult = err; } catch (_err) { errResult = _err; } if (errResult !== null) { console.error("Configuration error: " + i.constructor.name +": "+ errResult.message); throw "Configuration error"; } } // view engine setup app.set('views', [path.join(__dirname, 'views'), path.join(__dirname, 'views/template')]); app.set('view engine', 'pug'); app.use(express.static(path.join(__dirname, 'public'))); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(i18nextMiddleware.handle(i18next, { ignoreRoutes: [] // or function(req, res, options, i18next) { /* return true to ignore */ } })); // Security and session setup app.use((req: express.Request, res: express.Response, next) => { res.i18next = i18next; res.renderWithTranslations = async (translationNs: string, renderView: string, args: any) => { await i18next.loadNamespaces(translationNs); i18next.setDefaultNamespace(translationNs); args = args || {}; args["t"] = req.t; res.render(renderView, args); }; req.mCookies = new Map(); (req.headers?.cookie || "").split(";").forEach(i => { let keyValue = i.split("=", 2); req.mCookies.set(keyValue[0].trim(), keyValue[1].trim()); }); req.mSession = SessionManager.GetSession(req); if (req.mSession.IsValid()) req.mSession.Ping(); if (req.query["API_KEY"]) { Security.TryLoginApiKey(req.query["API_KEY"].toString()).then(user => { req.mSession.Login(user); }) .catch(e => { let err: any = new Error("Access denied"); err['status'] = 403; next(err); return; }); } next(); }); let HasAccount = await UserConnectorFactory.GetConnector().HasUsers(); // Anonymous pages !HasAccount && app.use(async (req, res, next) => { HasAccount = HasAccount || await UserConnectorFactory.GetConnector().HasUsers(); if (HasAccount) { next(); return; } let connector: IUserConnector = UserConnectorFactory.GetConnector(); if (!connector.CanCreateAccount()) { res.end("No account defined, and cannot create account from Connector Backend"); return; } if (req.method.toUpperCase() === "POST" && (req.body["username"] || "").trim().length && (req.body["password"] || "").trim().length) { let user = await connector.CreateAccount(req.body["username"].trim(), req.body["password"].trim()); req.mSession.Login(user); HasAccount = true; SessionManager.Write(res, req.mSession); res.redirect(302, "/"); return; } if (req.method.toUpperCase() === "GET") { res.render('setup_user'); } }); app.use('/', route_index); app.use('/login', route_login); // Login check app.use((req, res, next) => { if (!req.mSession.IsValid()) res.redirect(302, '/login?redirect=' + encodeURIComponent(req.url)); else next(); }); // Any following routes need login app.use('/calendar', route_calendar); app.use('/trombinoscope', route_trombinoscope); app.use('/accounting', route_accounting); // catch 404 and forward to error handler app.use((_, __, next) => { const err: any = new Error('Not Found'); err['status'] = 404; next(err); }); // error handlers // development error handler // will print stacktrace if (app.get('env') === 'development') { app.use((err: any, req: Express.Request, res: any, next: any) => { // eslint-disable-line @typescript-eslint/no-unused-vars err && console.error(err); res.status(err['status'] || 500); res.render('error', { message: err.message, error: err }); }); } // production error handler // no stacktraces leaked to user app.use((err: any, _: any, res: any, __: any) => { // eslint-disable-line @typescript-eslint/no-unused-vars err && console.error(err); res.status(err.status || 500); res.render('error', { message: err.message, error: {} }); }); app.set('port', process.env.port || 1337); const server = app.listen(app.get('port'), function () { debug(`Express server listening on port ${(server.address() as AddressInfo).port}`); }); })();