diff --git a/package-lock.json b/package-lock.json index 2bd8769..2f1c4cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,8 +21,11 @@ "eta": "^2.0.1", "express": "^4.18.2", "express-fileupload": "^1.4.0", + "express-session": "^1.17.3", "jquery": "^3.6.4", "lodash": "^4.17.21", + "passport": "^0.6.0", + "passport-local": "^1.0.0", "signale": "^1.4.0", "tsparticles-confetti": "^2.9.3" }, @@ -30,7 +33,10 @@ "@loancrate/prisma-schema-parser": "^2.0.0", "@types/express": "^4.17.17", "@types/express-fileupload": "^1.4.1", + "@types/express-session": "^1.17.7", "@types/lodash": "^4.14.194", + "@types/passport": "^1.0.12", + "@types/passport-local": "^1.0.35", "@types/signale": "^1.4.4", "eslint": "^8.39.0", "eslint-config-prettier": "^8.8.0", @@ -832,6 +838,15 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.7.tgz", + "integrity": "sha512-L25080PBYoRLu472HY/HNCxaXY8AaGgqGC8/p/8+BYMhG0RDOLQ1wpXOpAzr4Gi5TGozTKyJv5BVODM5UNyVMw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/lodash": { "version": "4.14.194", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.194.tgz", @@ -868,6 +883,36 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "node_modules/@types/passport": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.12.tgz", + "integrity": "sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-local": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.35.tgz", + "integrity": "sha512-K4eLTJ8R0yYW8TvCqkjB0pTKoqfUSdl5PfZdidTjV2ETV3604fQxtY6BHKjQWAx50WUS0lqzBvKv3LoI1ZBPeA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -2337,6 +2382,32 @@ "node": ">=12.0.0" } }, + "node_modules/express-session": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", + "dependencies": { + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -4211,6 +4282,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4422,6 +4501,42 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4469,6 +4584,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -5116,6 +5236,14 @@ "node": ">=8" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -6369,6 +6497,17 @@ "node": ">=12.20" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index 67990e5..4022837 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,11 @@ "eta": "^2.0.1", "express": "^4.18.2", "express-fileupload": "^1.4.0", + "express-session": "^1.17.3", "jquery": "^3.6.4", "lodash": "^4.17.21", + "passport": "^0.6.0", + "passport-local": "^1.0.0", "signale": "^1.4.0", "tsparticles-confetti": "^2.9.3" }, @@ -38,7 +41,10 @@ "@loancrate/prisma-schema-parser": "^2.0.0", "@types/express": "^4.17.17", "@types/express-fileupload": "^1.4.1", + "@types/express-session": "^1.17.7", "@types/lodash": "^4.14.194", + "@types/passport": "^1.0.12", + "@types/passport-local": "^1.0.35", "@types/signale": "^1.4.4", "eslint": "^8.39.0", "eslint-config-prettier": "^8.8.0", diff --git a/src/assets/configHandler.ts b/src/assets/configHandler.ts index 4cfb8b0..2e1f527 100644 --- a/src/assets/configHandler.ts +++ b/src/assets/configHandler.ts @@ -1,5 +1,6 @@ import fs from 'node:fs'; import _ from 'lodash'; +import { randomUUID, randomBytes } from 'crypto'; export type configObject = Record; @@ -14,6 +15,7 @@ export default class config { #configPath: string; //global = {[key: string] : string} global: configObject; + replaceSecrets: boolean; /** * Creates an instance of config. @@ -22,9 +24,10 @@ export default class config { * @param {string} configPath Path to config file. * @param {object} configPreset Default config object with default values. */ - constructor(configPath: string, configPreset: object) { + constructor(configPath: string, replaceSecrets: boolean, configPreset: object) { this.#configPath = configPath; this.global = configPreset; + this.replaceSecrets = replaceSecrets; try { // Read config @@ -52,6 +55,12 @@ export default class config { */ save_config() { try { + // If enabled replace tokens defines as "gen" with random token + if (this.replaceSecrets) { + // Replace tokens with value "gen" + this.generate_secrets(this.global, 'gen') + } + fs.writeFileSync(this.#configPath, JSON.stringify(this.global, null, 8)); } catch (err) { console.error(`Could not write config file at ${this.#configPath} due to: ${err}`); @@ -59,31 +68,73 @@ export default class config { } console.log(`Successfully written config file to ${this.#configPath}`); } + + /** + * Replaces each item matching the value of placeholder with a random UUID. + * Thanks to https://stackoverflow.com/questions/8085004/iterate-through-nested-javascript-objects + * @param {configObject} obj + */ + generate_secrets(obj: configObject, placeholder: string) { + const stack = [obj]; + while (stack?.length > 0) { + const currentObj = stack.pop(); + Object.keys(currentObj).forEach((key) => { + + if (currentObj[key] === placeholder) { + console.log('Generating secret: ' + key); + currentObj[key] = randomBytes(48).toString('base64').replace(/\W/g, ''); + } + + if (typeof currentObj[key] === 'object' && currentObj[key] !== null) { + stack.push(currentObj[key]); + } + }); + } + } } + /* **** Example **** -import configHandler from './assets/configHandler.js'; +import ConfigHandlerNG from './assets/configHandlerNG.js'; // Create a new config instance. -export const config = new ConfigHandler(__path + '/config.json', { - db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE', - http_listen_address: '127.0.0.1', - http_port: 3000, - sentry_dsn: 'https://ID@sentry.example.com/PROJECTID', - debug: false -}); +export const config = new ConfigHandler(__path + '/config.json', true, { + test1: 't1', + test2: 't2', + test3: 'gen', + test4: 't4', + test5: 'gen', + testObj: { + local: { + active: true, + users: { + user1: 'gen', + user2: 'gen', + user3: 'gen', + user4: 'gen', + } + }, + oidc: { + active: false + } + } +}); console.log('Base Config:'); console.log(config.global); -console.log('Add some new key to config and call save_config.'); +console.log('Add some new key to config and call save_config().'); config.global.NewKey = 'ThisIsANewKey!' config.save_config() +console.log('This will add a new key with value gen, but gen gets replaced with a random UUID when save_config() is called.'); +config.global.someSecret = 'gen' +config.save_config() // global.someSecret is getting replaced with some random UUID since it was set to 'gen'. + console.log('Complete Config:'); console.log(config.global); */ diff --git a/src/frontend/auth/login.eta.html b/src/frontend/auth/login.eta.html index e112807..17f8100 100644 --- a/src/frontend/auth/login.eta.html +++ b/src/frontend/auth/login.eta.html @@ -4,9 +4,33 @@
-
+

Log into AssetFlow

+ + +
+
+ + + +
+
+ + +
+ +
+ <%~ E.includeFile("../partials/foot.eta.html") %>
diff --git a/src/index.ts b/src/index.ts index ba664cd..91d517b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,9 @@ import fileUpload from 'express-fileupload'; import { PrismaClient } from '@prisma/client'; import * as eta from 'eta'; import bodyParser from 'body-parser'; +import session from 'express-session'; +import passport from 'passport'; +import _ from 'lodash'; // Sentry import * as Sentry from '@sentry/node'; @@ -28,18 +31,40 @@ export const log = { core: coreLogger, db: coreLogger.scope('DB'), web: coreLogger.scope('WEB'), + auth: coreLogger.scope('AUTH'), helper: coreLogger.scope('HELPER') }; // Create a new config instance. -export const config = new ConfigHandler(__path + '/config.json', { +export const config = new ConfigHandler(__path + '/config.json', true, { db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE', http_listen_address: '127.0.0.1', http_port: 3000, sentry_dsn: 'https://ID@sentry.example.com/PROJECTID', - debug: false + debug: false, + auth: { + cookie_secret: 'gen', + cookie_secure: true, + local: { + active: true, + users: {} + }, + oidc: { + active: false + } + } }); +// If no local User exists, create the default with a generated password +if (_.isEqual(config.global.auth.local.users, {})) { + config.global.auth.local.users = { + 'flowAdmin': 'gen', + }; + config.save_config(); +} + + +// TODO: Add errorhandling with some sort of message. export const prisma = new PrismaClient({ datasources: { db: { @@ -67,16 +92,16 @@ Sentry.init({ environment: config.global.debug ? 'development' : 'production' }); -app.locals.versionRevLong = require('child_process') -.execSync('git rev-parse HEAD') -.toString().trim() -app.locals.versionRev = require('child_process') -.execSync('git rev-parse --short HEAD') -.toString().trim() -app.locals.versionRevLatest = require('child_process') -.execSync('git ls-remote --refs -q') -.toString().trim().split("\t")[0] -log.core.info(`Running revision ${app.locals.versionRevLong} (${app.locals.versionRevLatest} latest)`); +// TODO: Version check need to be rewritten. +app.locals.versionRevLong = require('child_process').execSync('git rev-parse HEAD').toString().trim(); +app.locals.versionRev = require('child_process').execSync('git rev-parse --short HEAD').toString().trim(); +app.locals.versionRevLatest = require('child_process').execSync('git ls-remote --refs -q').toString().trim().split('\t')[0]; + +if (app.locals.versionRevLong === app.locals.versionRevLatest) { + log.core.info(`Running Latest Version (${app.locals.versionRevLong})`); +} else { + log.core.info(`Running Version: ${app.locals.versionRevLong} (Latest: ${app.locals.versionRevLatest})`); +} // RequestHandler creates a separate execution context using domains, so that every // transaction/span/breadcrumb is attached to its own Hub instance @@ -93,14 +118,23 @@ app.use(bodyParser.urlencoded({ extended: false })); // Using bodyParser to parse JSON bodies into JS objects app.use(bodyParser.json()); +// Session store +// TODO: Move secret to config -> Autogenerate. +app.use( + session({ + secret: config.global.auth.cookie_secret, + resave: false, + saveUninitialized: false, + cookie: { secure: config.global.auth.cookie_secure } + }) +); +app.use(passport.authenticate('session')); + app.use(fileUpload()); app.use(express.static(__path + '/static')); app.use(routes); -// The error handler must be before any other error middleware and after all controllers -app.use(Sentry.Handlers.errorHandler()); - app.listen(config.global.http_port, config.global.http_listen_address, () => { log.web.info(`Listening at http://${config.global.http_listen_address}:${config.global.http_port}`); }); diff --git a/src/middleware/auth.mw.ts b/src/middleware/auth.mw.ts new file mode 100644 index 0000000..7f3d831 --- /dev/null +++ b/src/middleware/auth.mw.ts @@ -0,0 +1,21 @@ +export function checkAuthentication(req: any, res: any, next: Function) { + if (req.isAuthenticated()) { + //req.isAuthenticated() will return true if user is logged in + next(); + } else { + res.redirect('/auth/login'); + } +} + +// const checkIsInRole = (...roles) => (req, res, next) => { +// if (!req.user) { +// return res.redirect('/login') +// } + +// const hasRole = roles.find(role => req.user.role === role) +// if (!hasRole) { +// return res.redirect('/login') +// } + +// return next() +// } diff --git a/src/routes/api/v1/index.ts b/src/routes/api/v1/index.ts index b978e1d..3e1943a 100644 --- a/src/routes/api/v1/index.ts +++ b/src/routes/api/v1/index.ts @@ -1,4 +1,5 @@ import express from 'express'; +import passport from 'passport'; // Route imports import testRoute from './test.js'; diff --git a/src/routes/auth/index.ts b/src/routes/auth/index.ts new file mode 100644 index 0000000..45b760e --- /dev/null +++ b/src/routes/auth/index.ts @@ -0,0 +1,90 @@ +import passport from 'passport'; +import { Strategy as LocalStrategy } from 'passport-local'; +import express, { Request, Response } from 'express'; +import { prisma, __path, log, config, app } from '../../index.js'; + +// Middleware Imports +import { checkAuthentication } from '../../middleware/auth.mw.js' + +/* Configure password authentication strategy. + * + * The `LocalStrategy` authenticates users by verifying a username and password. + * The strategy parses the username and password from the request and calls the + * `verify` function. + * + * The `verify` function queries the database for the user record and verifies + * the password by hashing the password supplied by the user and comparing it to + * the hashed password stored in the database. If the comparison succeeds, the + * user is authenticated; otherwise, not. + */ +passport.use( + new LocalStrategy(function verify(username, password, cb) { + //log.auth.debug('LocalStrategy:', username, password); + + for (const [user, pass] of Object.entries(config.global.auth.local.users)) { + //log.auth.debug('Loop(REQ):', username, password); + //log.auth.debug('Loop(CFG):', user, pass); + + if (user.toLowerCase() === username.toLowerCase() && pass === password) { + log.auth.debug('LocalStrategy: success'); + return cb(null, { username: username }); // This is the user object. + } + } + log.auth.debug('LocalStrategy: failed'); + return cb(null, false, { message: 'Incorrect username or password.' }); + }) + /* + 1. If the user not found in DB, + done (null, false) + + 2. If the user found in DB, but password does not match, + done (null, false) + + 3. If user found in DB and password match, + done (null, {authenticated_user}) + */ +); + +/* Configure session management. + * + * When a login session is established, information about the user will be + * stored in the session. This information is supplied by the `serializeUser` + * function, which is yielding the user ID and username. + * + * As the user interacts with the app, subsequent requests will be authenticated + * by verifying the session. The same user information that was serialized at + * session establishment will be restored when the session is authenticated by + * the `deserializeUser` function. + * + */ +passport.serializeUser(function (user: any, cb) { + process.nextTick(function () { + // log.auth.debug('Called seriealizeUser'); + // log.auth.debug('user:', user); + return cb(null, { + username: user.username + }); + }); +}); + +passport.deserializeUser(function (user, cb) { + process.nextTick(function () { + // log.auth.debug('Called deseriealizeUser'); + return cb(null, user); + }); +}); + +// Route imports +import testRoute from './test.js'; +import loginRoute from './login.js'; +//import logoutRoute from './login.js' + +// Router base is '/auth' +const Router = express.Router({ strict: false }); + +Router.route('/login').get(loginRoute.get); +Router.route('/login').post(passport.authenticate('local', { successRedirect: '/', failureRedirect: '/auth/login?failed' })); + +Router.route('/test').get(checkAuthentication, testRoute.get); + +export default Router; diff --git a/src/routes/frontend/manage/demo.ts b/src/routes/auth/login.ts similarity index 70% rename from src/routes/frontend/manage/demo.ts rename to src/routes/auth/login.ts index 010c59f..0007b26 100644 --- a/src/routes/frontend/manage/demo.ts +++ b/src/routes/auth/login.ts @@ -1,5 +1,7 @@ +import passport from 'passport'; + import express, { Request, Response } from 'express'; -import { prisma, __path } from '../../../index.js'; +import { prisma, __path, log } from '../../index.js'; function get(req: Request, res: Response) { res.render(__path + '/src/frontend/auth/login.eta.html'); //, { items: items }); diff --git a/src/routes/auth/test.ts b/src/routes/auth/test.ts new file mode 100644 index 0000000..e6bdfa8 --- /dev/null +++ b/src/routes/auth/test.ts @@ -0,0 +1,7 @@ +import express, { Request, Response } from 'express'; + +function get(req: Request, res: Response) { + res.status(200).send('Auth Test Successful!'); +}; + +export default { get }; diff --git a/src/routes/frontend/manage/index.ts b/src/routes/frontend/manage/index.ts index 99ffe43..ff41564 100644 --- a/src/routes/frontend/manage/index.ts +++ b/src/routes/frontend/manage/index.ts @@ -7,7 +7,6 @@ import jsonImportRoute from './import/jsonImport.js'; import categoryManager from './categoryManager.js'; import storageManager from './storageManager.js'; import startpageRoute from './startpage.js'; -import demoLoginRoute from './demo.js' // Router base is '/manage' const Router = express.Router({ strict: false }); @@ -17,7 +16,6 @@ Router.route('/categories').get(categoryManager.get); Router.route('/storages').get(storageManager.get); Router.route('/import/csv').get(csvImportRoute.get).post(csvImportRoute.post); Router.route('/import/json').get(jsonImportRoute.get).post(jsonImportRoute.post); -Router.route('/demo/login').get(demoLoginRoute.get); Router.route('/').get(startpageRoute.get); export default Router; diff --git a/src/routes/index.ts b/src/routes/index.ts index 1ef45b3..fc11c3f 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,16 +1,25 @@ import express, { Express } from 'express'; import { __path, prisma } from '../index.js'; +import * as Sentry from '@sentry/node'; + +// Middleware Imports +import { checkAuthentication } from '../middleware/auth.mw.js' // Route imports import frontend_routes from './frontend/index.js'; import static_routes from './static/index.js'; import api_routes from './api/index.js'; +import auth_routes from './auth/index.js'; const Router = express.Router({ strict: false }); Router.use('/static', static_routes); -Router.use('/api', api_routes); -Router.use('/', frontend_routes); +Router.use('/api', checkAuthentication, api_routes); +Router.use('/auth', auth_routes); +Router.use('/', checkAuthentication, frontend_routes); + +// The error handler must be before any other error middleware and after all controllers +Router.use(Sentry.Handlers.errorHandler()); // Default route. Router.all('*', function (req, res) { @@ -23,3 +32,4 @@ Router.all('*', function (req, res) { }); export default Router; +