From 0c63c5fb1a1b26d76b3790006de8b028a61c98f0 Mon Sep 17 00:00:00 2001 From: goynov Date: Sat, 24 Jan 2026 11:35:09 +0200 Subject: [PATCH] access management and telemetrics #12 --- backend/app/AccessManager.js | 133 +++++++++++++++++++++++++++++++++++ backend/app/App.js | 9 +++ backend/app/Db.js | 4 ++ backend/app/WebServer.js | 6 ++ backend/main.js | 21 +++++- 5 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 backend/app/AccessManager.js diff --git a/backend/app/AccessManager.js b/backend/app/AccessManager.js new file mode 100644 index 0000000..66c0a5d --- /dev/null +++ b/backend/app/AccessManager.js @@ -0,0 +1,133 @@ +class AccessManager { + + name = 'am' + init(app){ + + } + + start(app){ + + } + + async audit(req, action, objectId, custom){ + let data = { + t: Math.floor(Date.now() / 1000), + s: req.session?.id, + i: this.getIp(req), + r: req.headers.referer, + ua: req.headers['user-agent'], + l: req?.lang?.code, + u: req.user?._id && this.app.db.ObjectId(req.user._id), + a: action, + o: objectId && this.app.db.ObjectId(objectId), + c: custom + } + await this.app.db.create('log', data); + } + + async addToHistory(id, collection, action){ + let o; + if (typeof id == 'string') o = await this.app.db.get(collection, {'_id': this.app.db.ObjectId(id)}); + else o = id; + o._oid = this.app.db.ObjectId(o._id); + o._from = collection; + o._action = action; + await this.app.db.create('history', o); + return o; + } + + async getObjectHistory(collection, match){ + let object = await this.app.db.aggregate( collection, [ + { $match: match }, + { $lookup: {from: 'users', foreignField: '_id', localField: '_meta.user', as: '_user'} }, + { $project: {action:'$_meta.action', time:'$_meta.time', user:"$_user.email", type:'current'} }, + { $unwind: {path:'$user', preserveNullAndEmptyArrays:true}} + ]); + let history = await this.app.db.aggregate('history',[ + { $match: {_oid:object._id, _from:collection}}, + { $lookup: {from: 'users', foreignField: '_id', localField: '_meta.user', as: '_user'} }, + { $project: {action:'$_meta.action', time:'$_meta.time', user:"$_user.email", type:'history'} }, + { $sort: { time:-1 }}, + { $unwind: {path:'$user', preserveNullAndEmptyArrays:true}} + ]); + return {object, history}; + } + + setMeta(m, action, user, time, custom){ + delete m.revert; + m.user = user && user._id && this.app.db.ObjectId(user._id); + m.creator = m.creator || m.user; + m.time = time || Math.floor(Date.now() / 1000); + m.ctime = m.ctime || m.time; + m.action = action; + custom && Object.assign(m, custom); + } + + async processSocialLogin(p1, p2, profile, done){ + //console.log(p1, p2, profile); + let externalProfile = { + loginProvider: profile.provider, + providerKey: profile.id + }; + let user; + let dbUser = await this.app.db.get(collection, {social: externalProfile}); + if (dbUser){ + user = this.getUserProfile(dbUser); + }else{ + let dbUser = { + email: (profile.emails && profile.emails[0].value) || ((profile.username || profile.id) + "@" + profile.provider), + firstName: (profile.name && profile.name.givenName) || profile.displayName, + lastName: profile.name && profile.name.familyName, + displayName: profile.displayName || (profile.name && profile.name.givenName), + profilePicture: + (profile.photos && profile.photos[0] && profile.photos[0].value) + || (profile._json.data && profile._json.data.profile_picture), + status: 1, + social:[ + externalProfile + ], + roles:['user'] + }; + let r = await this.app.db.create(collection, dbUser); + user = this.getUserProfile(dbUser); + user._id = r.insertedId; + } + done(null, user); + } + + getUserProfile(dbUser){ + return { + _id: dbUser._id, + email: dbUser.email, + roles: dbUser.roles || ['user'], + groups: dbUser.groups || [], + firstName: dbUser.firstName, + lastName: dbUser.lastName, + displayName: dbUser.displayName, + profilePicture: dbUser.profilePicture, + status: dbUser.status + } + } + + getSocialCallback(provider){ + const am = this; + return function(req, res, next) { + passport.authenticate(provider, function(err, user, info) { + if (err) { return next(err); } + let lang = req.cookies.lang || am.app.config.langs[0].code; + if (!user) { return res.redirect(`/${lang}/user/signin`); } + req.logIn(user, function(err) { + if (err) { return next(err); } + return res.redirect(`/${lang}`); + }); + })(req, res, next); + } + } + + is(user, role){ + return user && user.roles.indexOf(role) > -1; + } + +} + +export { AccessManager } \ No newline at end of file diff --git a/backend/app/App.js b/backend/app/App.js index 5da6596..d5d6e4c 100644 --- a/backend/app/App.js +++ b/backend/app/App.js @@ -72,6 +72,15 @@ class App{ if(p.start) await p.start(this); } } + + async stop(){ + for (let p of this.plugins){ + if(p.stop) { + console.debug('Stopping', p.name) + await p.stop(this); + } + } + } } diff --git a/backend/app/Db.js b/backend/app/Db.js index 672c22a..fa34aad 100644 --- a/backend/app/Db.js +++ b/backend/app/Db.js @@ -263,6 +263,10 @@ class Db { ]); return ag.max || 0; } + + async stop(){ + await db.close(); + } } export { Db }; \ No newline at end of file diff --git a/backend/app/WebServer.js b/backend/app/WebServer.js index 365adbc..e4d2c51 100644 --- a/backend/app/WebServer.js +++ b/backend/app/WebServer.js @@ -105,6 +105,12 @@ class WebServer { } } + async stop(){ + if (this.server) { + this.server.close(); + } + } + } export { WebServer }; \ No newline at end of file diff --git a/backend/main.js b/backend/main.js index ad5d182..9230ac6 100644 --- a/backend/main.js +++ b/backend/main.js @@ -1,4 +1,5 @@ import 'dotenv/config'; +import { spawn } from "child_process"; console.debug = function(){ if (process.env.debug){ @@ -11,6 +12,7 @@ import App from './app/App.js'; const modules = [ {name: 'Config', path:'app/Config.js'}, {name: 'Db', path:'app/Db.js'}, + {name: 'AccessManager', path:'app/AccessManager.js'}, {name: 'GameObjectsManager', path:'app/bl/GameObjectsManager.js'}, {name: 'ScenariosManager', path:'app/bl/ScenariosManager.js'}, @@ -30,10 +32,25 @@ process.on('uncaughtException', err => { console.error(reason, 'Unhandled Rejection at Promise', p); }); +if (process.env.NODE_ENV == 'development'){ + console.log('Running in development mode'); + process.stdin.resume(); + process.stdin.on('data', async (data) => { + const input = data.toString().trim(); + if (input === 'r') { + process.stdin.pause(); + await app.stop(); + console.log('Restarting...'); + spawn(process.argv.shift(), process.argv, { + cwd: process.cwd(), + stdio: "inherit" + }); + } + }); +} + const app = new App(); - await app.importModules(modules); - await app.init(); console.log(`Starting...`);