From e307ff97ace6fc6ca43e80ceb85b0d0d224a19ca Mon Sep 17 00:00:00 2001 From: Spacelord Date: Sat, 26 Aug 2023 20:56:03 +0200 Subject: [PATCH 1/8] Add passport.js dependencies --- package-lock.json | 139 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 ++ 2 files changed, 145 insertions(+) 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", From 56cbebb36b42f4ca0dda3661da05bdfbf0c234a6 Mon Sep 17 00:00:00 2001 From: Spacelord Date: Sat, 26 Aug 2023 20:56:26 +0200 Subject: [PATCH 2/8] Add form to login page --- src/frontend/auth/login.eta.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/frontend/auth/login.eta.html b/src/frontend/auth/login.eta.html index e112807..c6a05c8 100644 --- a/src/frontend/auth/login.eta.html +++ b/src/frontend/auth/login.eta.html @@ -6,6 +6,18 @@

Log into AssetFlow

+
+
+ + + +
+
+ + +
+ +
<%~ E.includeFile("../partials/foot.eta.html") %> From ddfdfc30922019d5fcbb9263b9ac58cd7a224503 Mon Sep 17 00:00:00 2001 From: Spacelord Date: Sat, 26 Aug 2023 20:56:48 +0200 Subject: [PATCH 3/8] Remove old demo login page --- src/routes/frontend/manage/demo.ts | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 src/routes/frontend/manage/demo.ts diff --git a/src/routes/frontend/manage/demo.ts b/src/routes/frontend/manage/demo.ts deleted file mode 100644 index 010c59f..0000000 --- a/src/routes/frontend/manage/demo.ts +++ /dev/null @@ -1,8 +0,0 @@ -import express, { Request, Response } from 'express'; -import { prisma, __path } from '../../../index.js'; - -function get(req: Request, res: Response) { - res.render(__path + '/src/frontend/auth/login.eta.html'); //, { items: items }); -} - -export default { get }; From 347979bb106d28b74ec92a3bc0b8f7cbf7181819 Mon Sep 17 00:00:00 2001 From: Spacelord Date: Sat, 26 Aug 2023 20:57:16 +0200 Subject: [PATCH 4/8] Remove login demo page route --- src/routes/frontend/manage/index.ts | 2 -- 1 file changed, 2 deletions(-) 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; From af896a668815be9888be88dbc0dc36ee9f7338a3 Mon Sep 17 00:00:00 2001 From: Spacelord Date: Sat, 26 Aug 2023 20:59:46 +0200 Subject: [PATCH 5/8] Added local authentication (AFLOW-32) --- src/index.ts | 53 +++++++++++++++------ src/middleware/auth.mw.ts | 23 +++++++++ src/routes/api/v1/index.ts | 1 + src/routes/auth/index.ts | 96 ++++++++++++++++++++++++++++++++++++++ src/routes/auth/login.ts | 10 ++++ src/routes/auth/test.ts | 7 +++ src/routes/index.ts | 7 +++ 7 files changed, 183 insertions(+), 14 deletions(-) create mode 100644 src/middleware/auth.mw.ts create mode 100644 src/routes/auth/index.ts create mode 100644 src/routes/auth/login.ts create mode 100644 src/routes/auth/test.ts diff --git a/src/index.ts b/src/index.ts index ba664cd..a50c072 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,8 @@ 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'; // Sentry import * as Sentry from '@sentry/node'; @@ -28,6 +30,7 @@ export const log = { core: coreLogger, db: coreLogger.scope('DB'), web: coreLogger.scope('WEB'), + auth: coreLogger.scope('AUTH'), helper: coreLogger.scope('HELPER') }; @@ -37,9 +40,22 @@ export const config = new ConfigHandler(__path + '/config.json', { http_listen_address: '127.0.0.1', http_port: 3000, sentry_dsn: 'https://ID@sentry.example.com/PROJECTID', - debug: false + debug: false, + auth: { + local: { + active: true, + users: { + user: 'password', + user1: 'password' + } + }, + oidc: { + active: false + } + } }); +// TODO: Add errorhandling with some sort of message. export const prisma = new PrismaClient({ datasources: { db: { @@ -67,16 +83,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 +109,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: 'keyboard cat', + resave: false, + saveUninitialized: false, + cookie: { secure: false } + }) +); +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..6e9cd26 --- /dev/null +++ b/src/middleware/auth.mw.ts @@ -0,0 +1,23 @@ +/* +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..26dc027 --- /dev/null +++ b/src/routes/auth/index.ts @@ -0,0 +1,96 @@ +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'; + +/* 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 === username && 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; + +function checkAuthentication(req: Request, res: Response, next: Function) { + if (req.isAuthenticated()) { + //req.isAuthenticated() will return true if user is logged in + next(); + } else { + res.redirect('/auth/login'); + } +} diff --git a/src/routes/auth/login.ts b/src/routes/auth/login.ts new file mode 100644 index 0000000..0007b26 --- /dev/null +++ b/src/routes/auth/login.ts @@ -0,0 +1,10 @@ +import passport from 'passport'; + +import express, { Request, Response } from 'express'; +import { prisma, __path, log } from '../../index.js'; + +function get(req: Request, res: Response) { + res.render(__path + '/src/frontend/auth/login.eta.html'); //, { items: items }); +} + +export default { get }; 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/index.ts b/src/routes/index.ts index 1ef45b3..47d23a2 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,17 +1,23 @@ import express, { Express } from 'express'; import { __path, prisma } from '../index.js'; +import * as Sentry from '@sentry/node'; // 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('/auth', auth_routes); Router.use('/', 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) { // TODO: Respond based on content-type (with req.is('application/json')) @@ -23,3 +29,4 @@ Router.all('*', function (req, res) { }); export default Router; + From 6fa27979032be09025fa176a17b5cc12b7873c5f Mon Sep 17 00:00:00 2001 From: grey Date: Sun, 27 Aug 2023 19:04:55 +0200 Subject: [PATCH 6/8] improved login screen (fixed layout, added error msg) --- src/frontend/auth/login.eta.html | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/frontend/auth/login.eta.html b/src/frontend/auth/login.eta.html index c6a05c8..17f8100 100644 --- a/src/frontend/auth/login.eta.html +++ b/src/frontend/auth/login.eta.html @@ -4,8 +4,12 @@
-
+

Log into AssetFlow

+ +
@@ -20,5 +24,13 @@
+ <%~ E.includeFile("../partials/foot.eta.html") %>
From 2371089f88659fc1183662f5ab1c71281dcee007 Mon Sep 17 00:00:00 2001 From: grey Date: Wed, 1 Nov 2023 20:03:28 +0100 Subject: [PATCH 7/8] updated config handler to autogenerate secrets and default user structure Co-authored-by: Spacelord --- src/assets/configHandler.ts | 71 +++++++++++++++++++++++++++++++------ src/index.ts | 23 ++++++++---- 2 files changed, 77 insertions(+), 17 deletions(-) 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/index.ts b/src/index.ts index a50c072..91d517b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ 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'; @@ -35,19 +36,18 @@ export const log = { }; // 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, auth: { + cookie_secret: 'gen', + cookie_secure: true, local: { active: true, - users: { - user: 'password', - user1: 'password' - } + users: {} }, oidc: { active: false @@ -55,6 +55,15 @@ export const config = new ConfigHandler(__path + '/config.json', { } }); +// 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: { @@ -113,10 +122,10 @@ app.use(bodyParser.json()); // TODO: Move secret to config -> Autogenerate. app.use( session({ - secret: 'keyboard cat', + secret: config.global.auth.cookie_secret, resave: false, saveUninitialized: false, - cookie: { secure: false } + cookie: { secure: config.global.auth.cookie_secure } }) ); app.use(passport.authenticate('session')); From c23b1b306c04c36c930c3e2e2e901def51bbc163 Mon Sep 17 00:00:00 2001 From: grey Date: Wed, 1 Nov 2023 20:04:19 +0100 Subject: [PATCH 8/8] updated and properly implemented auth middleware AFLOW-32 Co-authored-by: Spacelord --- src/middleware/auth.mw.ts | 24 +++++++++++------------- src/routes/auth/index.ts | 20 +++++++------------- src/routes/index.ts | 7 +++++-- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/middleware/auth.mw.ts b/src/middleware/auth.mw.ts index 6e9cd26..7f3d831 100644 --- a/src/middleware/auth.mw.ts +++ b/src/middleware/auth.mw.ts @@ -1,5 +1,4 @@ -/* -function checkAuthentication(req: any, res: any, next: Function) { +export function checkAuthentication(req: any, res: any, next: Function) { if (req.isAuthenticated()) { //req.isAuthenticated() will return true if user is logged in next(); @@ -8,16 +7,15 @@ function checkAuthentication(req: any, res: any, next: Function) { } } -const checkIsInRole = (...roles) => (req, res, next) => { - if (!req.user) { - return res.redirect('/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') - } +// const hasRole = roles.find(role => req.user.role === role) +// if (!hasRole) { +// return res.redirect('/login') +// } - return next() -} -*/ +// return next() +// } diff --git a/src/routes/auth/index.ts b/src/routes/auth/index.ts index 26dc027..45b760e 100644 --- a/src/routes/auth/index.ts +++ b/src/routes/auth/index.ts @@ -3,6 +3,9 @@ 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. @@ -22,7 +25,7 @@ passport.use( //log.auth.debug('Loop(REQ):', username, password); //log.auth.debug('Loop(CFG):', user, pass); - if (user === username && pass === password) { + if (user.toLowerCase() === username.toLowerCase() && pass === password) { log.auth.debug('LocalStrategy: success'); return cb(null, { username: username }); // This is the user object. } @@ -56,8 +59,8 @@ passport.use( */ passport.serializeUser(function (user: any, cb) { process.nextTick(function () { - log.auth.debug('Called seriealizeUser'); - log.auth.debug('user:', user); + // log.auth.debug('Called seriealizeUser'); + // log.auth.debug('user:', user); return cb(null, { username: user.username }); @@ -66,7 +69,7 @@ passport.serializeUser(function (user: any, cb) { passport.deserializeUser(function (user, cb) { process.nextTick(function () { - log.auth.debug('Called deseriealizeUser'); + // log.auth.debug('Called deseriealizeUser'); return cb(null, user); }); }); @@ -85,12 +88,3 @@ Router.route('/login').post(passport.authenticate('local', { successRedirect: '/ Router.route('/test').get(checkAuthentication, testRoute.get); export default Router; - -function checkAuthentication(req: Request, res: Response, next: Function) { - if (req.isAuthenticated()) { - //req.isAuthenticated() will return true if user is logged in - next(); - } else { - res.redirect('/auth/login'); - } -} diff --git a/src/routes/index.ts b/src/routes/index.ts index 47d23a2..fc11c3f 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -2,6 +2,9 @@ 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'; @@ -11,9 +14,9 @@ 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('/api', checkAuthentication, api_routes); Router.use('/auth', auth_routes); -Router.use('/', frontend_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());