From 2371089f88659fc1183662f5ab1c71281dcee007 Mon Sep 17 00:00:00 2001 From: grey Date: Wed, 1 Nov 2023 20:03:28 +0100 Subject: [PATCH] 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'));