server.ts 7.5 KB

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