Files
hydrationhub/src/libs/configManager.ts

147 lines
4.0 KiB
TypeScript

import fs from 'node:fs';
import _ from 'lodash';
import { randomBytes } from 'crypto';
import { Logger } from 'tslog';
export type configObject = Record<any, any>;
/**
* This class is responsible to save/edit config files.
*
* @export
* @class config
* @typedef {config}
*/
export default class config {
#configPath: string;
//global = {[key: string] : string}
global: configObject;
replaceSecrets: boolean;
#logger: Logger<unknown> | typeof console;
/**
* Creates an instance of config.
*
* @constructor
* @param {string} configPath Path to config file.
* @param {boolean} replaceSecrets Whether to replace secrets with generated values.
* @param {object} configPreset Default config object with default values.
* @param {Logger<unknown> | typeof console} [logger] Optional (tslog) logger.
*/
constructor(configPath: string, replaceSecrets: boolean, configPreset: object, logger?: Logger<unknown> | typeof console) {
this.#configPath = configPath;
this.global = configPreset;
this.replaceSecrets = replaceSecrets;
this.#logger = logger ?? console;
this.#logger.info(`Initializing config manager with path: ${this.#configPath}`);
try {
// Read config
const data = fs.readFileSync(this.#configPath, 'utf8');
// Extend config with missing parameters from configPreset.
this.global = _.defaultsDeep(JSON.parse(data), this.global);
// Save config.
this.save_config();
} catch (err: any) {
// If file does not exist, create it.
if (err.code === 'ENOENT') {
this.#logger.info(`Config file does not exist. Creating it at ${this.#configPath} now.`);
this.save_config();
return;
}
this.#logger.error(`Could not read config file at ${this.#configPath} due to: ${err}`);
// Exit process.
process.exit(1);
}
}
/**
* Saves the jsonified config object to the config file.
*/
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) {
this.#logger.error(`Could not write config file at ${this.#configPath} due to: ${err}`);
return;
}
this.#logger.info(`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: any = stack.pop();
Object.keys(currentObj).forEach((key) => {
if (currentObj[key] === placeholder) {
this.#logger.info('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 ConfigHandlerNG from './assets/configHandlerNG.js';
// Create a new config instance.
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().');
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);
*/