Compare commits
2 Commits
6fa2797903
...
c23b1b306c
Author | SHA1 | Date | |
---|---|---|---|
c23b1b306c | |||
2371089f88 |
@ -1,5 +1,6 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { randomUUID, randomBytes } from 'crypto';
|
||||||
|
|
||||||
export type configObject = Record<any, any>;
|
export type configObject = Record<any, any>;
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ export default class config {
|
|||||||
#configPath: string;
|
#configPath: string;
|
||||||
//global = {[key: string] : string}
|
//global = {[key: string] : string}
|
||||||
global: configObject;
|
global: configObject;
|
||||||
|
replaceSecrets: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of config.
|
* Creates an instance of config.
|
||||||
@ -22,9 +24,10 @@ export default class config {
|
|||||||
* @param {string} configPath Path to config file.
|
* @param {string} configPath Path to config file.
|
||||||
* @param {object} configPreset Default config object with default values.
|
* @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.#configPath = configPath;
|
||||||
this.global = configPreset;
|
this.global = configPreset;
|
||||||
|
this.replaceSecrets = replaceSecrets;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Read config
|
// Read config
|
||||||
@ -52,6 +55,12 @@ export default class config {
|
|||||||
*/
|
*/
|
||||||
save_config() {
|
save_config() {
|
||||||
try {
|
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));
|
fs.writeFileSync(this.#configPath, JSON.stringify(this.global, null, 8));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Could not write config file at ${this.#configPath} due to: ${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}`);
|
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 ****
|
**** Example ****
|
||||||
|
|
||||||
import configHandler from './assets/configHandler.js';
|
import ConfigHandlerNG from './assets/configHandlerNG.js';
|
||||||
|
|
||||||
// Create a new config instance.
|
// 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',
|
test1: 't1',
|
||||||
http_listen_address: '127.0.0.1',
|
test2: 't2',
|
||||||
http_port: 3000,
|
test3: 'gen',
|
||||||
sentry_dsn: 'https://ID@sentry.example.com/PROJECTID',
|
test4: 't4',
|
||||||
debug: false
|
test5: 'gen',
|
||||||
});
|
testObj: {
|
||||||
|
local: {
|
||||||
|
active: true,
|
||||||
|
users: {
|
||||||
|
user1: 'gen',
|
||||||
|
user2: 'gen',
|
||||||
|
user3: 'gen',
|
||||||
|
user4: 'gen',
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
oidc: {
|
||||||
|
active: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
console.log('Base Config:');
|
console.log('Base Config:');
|
||||||
console.log(config.global);
|
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.global.NewKey = 'ThisIsANewKey!'
|
||||||
config.save_config()
|
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('Complete Config:');
|
||||||
console.log(config.global);
|
console.log(config.global);
|
||||||
*/
|
*/
|
||||||
|
23
src/index.ts
23
src/index.ts
@ -7,6 +7,7 @@ import * as eta from 'eta';
|
|||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Sentry
|
// Sentry
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
@ -35,19 +36,18 @@ export const log = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create a new config instance.
|
// 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',
|
db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE',
|
||||||
http_listen_address: '127.0.0.1',
|
http_listen_address: '127.0.0.1',
|
||||||
http_port: 3000,
|
http_port: 3000,
|
||||||
sentry_dsn: 'https://ID@sentry.example.com/PROJECTID',
|
sentry_dsn: 'https://ID@sentry.example.com/PROJECTID',
|
||||||
debug: false,
|
debug: false,
|
||||||
auth: {
|
auth: {
|
||||||
|
cookie_secret: 'gen',
|
||||||
|
cookie_secure: true,
|
||||||
local: {
|
local: {
|
||||||
active: true,
|
active: true,
|
||||||
users: {
|
users: {}
|
||||||
user: 'password',
|
|
||||||
user1: 'password'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
oidc: {
|
oidc: {
|
||||||
active: false
|
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.
|
// TODO: Add errorhandling with some sort of message.
|
||||||
export const prisma = new PrismaClient({
|
export const prisma = new PrismaClient({
|
||||||
datasources: {
|
datasources: {
|
||||||
@ -113,10 +122,10 @@ app.use(bodyParser.json());
|
|||||||
// TODO: Move secret to config -> Autogenerate.
|
// TODO: Move secret to config -> Autogenerate.
|
||||||
app.use(
|
app.use(
|
||||||
session({
|
session({
|
||||||
secret: 'keyboard cat',
|
secret: config.global.auth.cookie_secret,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
cookie: { secure: false }
|
cookie: { secure: config.global.auth.cookie_secure }
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(passport.authenticate('session'));
|
app.use(passport.authenticate('session'));
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
/*
|
export function checkAuthentication(req: any, res: any, next: Function) {
|
||||||
function checkAuthentication(req: any, res: any, next: Function) {
|
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
//req.isAuthenticated() will return true if user is logged in
|
//req.isAuthenticated() will return true if user is logged in
|
||||||
next();
|
next();
|
||||||
@ -8,16 +7,15 @@ function checkAuthentication(req: any, res: any, next: Function) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkIsInRole = (...roles) => (req, res, next) => {
|
// const checkIsInRole = (...roles) => (req, res, next) => {
|
||||||
if (!req.user) {
|
// if (!req.user) {
|
||||||
return res.redirect('/login')
|
// return res.redirect('/login')
|
||||||
}
|
// }
|
||||||
|
|
||||||
const hasRole = roles.find(role => req.user.role === role)
|
// const hasRole = roles.find(role => req.user.role === role)
|
||||||
if (!hasRole) {
|
// if (!hasRole) {
|
||||||
return res.redirect('/login')
|
// return res.redirect('/login')
|
||||||
}
|
// }
|
||||||
|
|
||||||
return next()
|
// return next()
|
||||||
}
|
// }
|
||||||
*/
|
|
||||||
|
@ -3,6 +3,9 @@ import { Strategy as LocalStrategy } from 'passport-local';
|
|||||||
import express, { Request, Response } from 'express';
|
import express, { Request, Response } from 'express';
|
||||||
import { prisma, __path, log, config, app } from '../../index.js';
|
import { prisma, __path, log, config, app } from '../../index.js';
|
||||||
|
|
||||||
|
// Middleware Imports
|
||||||
|
import { checkAuthentication } from '../../middleware/auth.mw.js'
|
||||||
|
|
||||||
/* Configure password authentication strategy.
|
/* Configure password authentication strategy.
|
||||||
*
|
*
|
||||||
* The `LocalStrategy` authenticates users by verifying a username and password.
|
* 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(REQ):', username, password);
|
||||||
//log.auth.debug('Loop(CFG):', user, pass);
|
//log.auth.debug('Loop(CFG):', user, pass);
|
||||||
|
|
||||||
if (user === username && pass === password) {
|
if (user.toLowerCase() === username.toLowerCase() && pass === password) {
|
||||||
log.auth.debug('LocalStrategy: success');
|
log.auth.debug('LocalStrategy: success');
|
||||||
return cb(null, { username: username }); // This is the user object.
|
return cb(null, { username: username }); // This is the user object.
|
||||||
}
|
}
|
||||||
@ -56,8 +59,8 @@ passport.use(
|
|||||||
*/
|
*/
|
||||||
passport.serializeUser(function (user: any, cb) {
|
passport.serializeUser(function (user: any, cb) {
|
||||||
process.nextTick(function () {
|
process.nextTick(function () {
|
||||||
log.auth.debug('Called seriealizeUser');
|
// log.auth.debug('Called seriealizeUser');
|
||||||
log.auth.debug('user:', user);
|
// log.auth.debug('user:', user);
|
||||||
return cb(null, {
|
return cb(null, {
|
||||||
username: user.username
|
username: user.username
|
||||||
});
|
});
|
||||||
@ -66,7 +69,7 @@ passport.serializeUser(function (user: any, cb) {
|
|||||||
|
|
||||||
passport.deserializeUser(function (user, cb) {
|
passport.deserializeUser(function (user, cb) {
|
||||||
process.nextTick(function () {
|
process.nextTick(function () {
|
||||||
log.auth.debug('Called deseriealizeUser');
|
// log.auth.debug('Called deseriealizeUser');
|
||||||
return cb(null, user);
|
return cb(null, user);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -85,12 +88,3 @@ Router.route('/login').post(passport.authenticate('local', { successRedirect: '/
|
|||||||
Router.route('/test').get(checkAuthentication, testRoute.get);
|
Router.route('/test').get(checkAuthentication, testRoute.get);
|
||||||
|
|
||||||
export default Router;
|
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,6 +2,9 @@ import express, { Express } from 'express';
|
|||||||
import { __path, prisma } from '../index.js';
|
import { __path, prisma } from '../index.js';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
|
// Middleware Imports
|
||||||
|
import { checkAuthentication } from '../middleware/auth.mw.js'
|
||||||
|
|
||||||
// Route imports
|
// Route imports
|
||||||
import frontend_routes from './frontend/index.js';
|
import frontend_routes from './frontend/index.js';
|
||||||
import static_routes from './static/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 });
|
const Router = express.Router({ strict: false });
|
||||||
|
|
||||||
Router.use('/static', static_routes);
|
Router.use('/static', static_routes);
|
||||||
Router.use('/api', api_routes);
|
Router.use('/api', checkAuthentication, api_routes);
|
||||||
Router.use('/auth', auth_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
|
// The error handler must be before any other error middleware and after all controllers
|
||||||
Router.use(Sentry.Handlers.errorHandler());
|
Router.use(Sentry.Handlers.errorHandler());
|
||||||
|
Loading…
Reference in New Issue
Block a user