server.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import { AConfigChecker } from './src/AConfigChecker';
  2. import * as express from 'express';
  3. import { AddressInfo } from "net";
  4. import * as path from 'path';
  5. import * as bodyParser from 'body-parser';
  6. const i18next = require('i18next'); // FIXME import from
  7. const i18nextMiddleware = require('i18next-http-middleware'); // FIXME import from
  8. const resourcesToBackend = require('i18next-resources-to-backend'); // FIXME import from
  9. import route_index from './routes/index';
  10. import route_login from './routes/login';
  11. import route_calendar from './routes/calendar';
  12. import route_trombinoscope from './routes/trombinoscope';
  13. import route_accounting from './routes/accounting';
  14. import Security from './src/Security';
  15. import { Session, SessionManager } from './src/Session';
  16. import UserConnectorFactory, { IUserConnector } from './src/UserConnector/UserConnectorFactory';
  17. import DBConnectorFactory from './src/DbConnector/DBConnectorFactory';
  18. const debug = require('debug')('my express app');
  19. const app = express();
  20. declare global {
  21. namespace Express {
  22. interface Request {
  23. mSession: Session
  24. mCookies: Map<string, string>
  25. t: (s: string) => string // i18n translation function
  26. }
  27. interface Response {
  28. i18next: any
  29. renderWithTranslations: (translationNs: string, renderView: string, args: any) => Promise<void>
  30. }
  31. }
  32. }
  33. // Check config
  34. (async _ => {
  35. await i18next.use(i18nextMiddleware.LanguageDetector)
  36. .use(resourcesToBackend((language: string, namespace: string, callback: Function) => {
  37. import(`./locales/${language}/${namespace}.json`)
  38. .then((resources) => {
  39. callback(null, resources)
  40. })
  41. .catch((error) => {
  42. callback(error, null)
  43. })
  44. }))
  45. .init({
  46. preload: ['fr', 'en'],
  47. nonExplicitSupportedLngs: true,
  48. lowerCaseLng: true
  49. });
  50. let cc: AConfigChecker[] = [
  51. UserConnectorFactory,
  52. await DBConnectorFactory.GetConnector()
  53. ];
  54. for (let i of cc) {
  55. let err: Promise<void> | Error | null = i.CheckConfig();
  56. let errResult: Error | null = null;
  57. try {
  58. if (err instanceof Promise)
  59. await err;
  60. else
  61. errResult = err;
  62. }
  63. catch (_err) {
  64. errResult = _err;
  65. }
  66. if (errResult !== null) {
  67. console.error("Configuration error: " + i.constructor.name +": "+ errResult.message);
  68. throw "Configuration error";
  69. }
  70. }
  71. // view engine setup
  72. app.set('views', [path.join(__dirname, 'views'), path.join(__dirname, 'views/template')]);
  73. app.set('view engine', 'pug');
  74. app.use(express.static(path.join(__dirname, 'public')));
  75. app.use(bodyParser.json());
  76. app.use(bodyParser.urlencoded({ extended: true }));
  77. app.use(i18nextMiddleware.handle(i18next, {
  78. ignoreRoutes: [] // or function(req, res, options, i18next) { /* return true to ignore */ }
  79. }));
  80. // Security and session setup
  81. app.use((req: express.Request, res: express.Response, next) => {
  82. res.i18next = i18next;
  83. res.renderWithTranslations = async (translationNs: string, renderView: string, args: any) => {
  84. await i18next.loadNamespaces(translationNs);
  85. i18next.setDefaultNamespace(translationNs);
  86. args = args || {};
  87. args["t"] = req.t;
  88. res.render(renderView, args);
  89. };
  90. req.mCookies = new Map();
  91. (req.headers?.cookie || "").split(";").forEach(i => {
  92. let keyValue = i.split("=", 2);
  93. req.mCookies.set(keyValue[0].trim(), keyValue[1].trim());
  94. });
  95. req.mSession = SessionManager.GetSession(req);
  96. if (req.mSession.IsValid())
  97. req.mSession.Ping();
  98. if (req.query["API_KEY"]) {
  99. Security.TryLoginApiKey(req.query["API_KEY"].toString()).then(user => {
  100. req.mSession.Login(user);
  101. })
  102. .catch(e => {
  103. let err: any = new Error("Access denied");
  104. err['status'] = 403;
  105. next(err);
  106. return;
  107. });
  108. }
  109. next();
  110. });
  111. let HasAccount = await UserConnectorFactory.GetConnector().HasUsers();
  112. // Anonymous pages
  113. !HasAccount && app.use(async (req, res, next) => {
  114. HasAccount = HasAccount || await UserConnectorFactory.GetConnector().HasUsers();
  115. if (HasAccount) {
  116. next();
  117. return;
  118. }
  119. let connector: IUserConnector = UserConnectorFactory.GetConnector();
  120. if (!connector.CanCreateAccount()) {
  121. res.end("No account defined, and cannot create account from Connector Backend");
  122. return;
  123. }
  124. if (req.method.toUpperCase() === "POST" && (req.body["username"] || "").trim().length && (req.body["password"] || "").trim().length) {
  125. let user = await connector.CreateAccount(req.body["username"].trim(), req.body["password"].trim());
  126. req.mSession.Login(user);
  127. HasAccount = true;
  128. SessionManager.Write(res, req.mSession);
  129. res.redirect(302, "/");
  130. return;
  131. }
  132. if (req.method.toUpperCase() === "GET") {
  133. res.render('setup_user');
  134. }
  135. });
  136. app.use('/', route_index);
  137. app.use('/login', route_login);
  138. // Login check
  139. app.use((req, res, next) => {
  140. if (!req.mSession.IsValid())
  141. res.redirect(302, '/login?redirect=' + encodeURIComponent(req.url));
  142. else
  143. next();
  144. });
  145. // Any following routes need login
  146. app.use('/calendar', route_calendar);
  147. app.use('/trombinoscope', route_trombinoscope);
  148. app.use('/accounting', route_accounting);
  149. // catch 404 and forward to error handler
  150. app.use((_, __, next) => {
  151. const err: any = new Error('Not Found');
  152. err['status'] = 404;
  153. next(err);
  154. });
  155. // error handlers
  156. // development error handler
  157. // will print stacktrace
  158. if (app.get('env') === 'development') {
  159. app.use((err: any, req: Express.Request, res: any, next: any) => { // eslint-disable-line @typescript-eslint/no-unused-vars
  160. err && console.error(err);
  161. res.status(err['status'] || 500);
  162. res.render('error', {
  163. message: err.message,
  164. error: err
  165. });
  166. });
  167. }
  168. // production error handler
  169. // no stacktraces leaked to user
  170. app.use((err: any, _: any, res: any, __: any) => { // eslint-disable-line @typescript-eslint/no-unused-vars
  171. err && console.error(err);
  172. res.status(err.status || 500);
  173. res.render('error', {
  174. message: err.message,
  175. error: {}
  176. });
  177. });
  178. app.set('port', process.env.port || 1337);
  179. const server = app.listen(app.get('port'), function () {
  180. debug(`Express server listening on port ${(server.address() as AddressInfo).port}`);
  181. });
  182. })();