farmcontrol-ws/src/socket/socketuser.js
Tom Butcher 9416a94a23
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good
Refactor database utility functions and enhance audit logging
- Removed unnecessary console logging in the `SocketUser` class for cleaner output.
- Updated the `distributeNew` function to accept the entire object instead of just the ID, improving flexibility in handling new entries.
- Enhanced the `newAuditLog` and `editAuditLog` functions to filter out excluded models and changes, ensuring sensitive data is not logged.
- Introduced new utility functions for omitting specific paths and excluded changes from audit logs, enhancing data privacy and integrity.
2026-09-01 12:34:31 +01:00

378 lines
11 KiB
JavaScript

import log4js from 'log4js';
// Load configuration
import { loadConfig } from '../config.js';
import { createAuthMiddleware, KeycloakAuth } from '../auth/auth.js';
import { generateHostOTP } from '../utils.js';
import { ActivityManager } from '../activity/activitymanager.js';
import { UpdateManager } from '../updates/updatemanager.js';
import { ActionManager } from '../actions/actionmanager.js';
import { EventManager } from '../events/eventmanager.js';
import { StatsManager } from '../stats/statsmanager.js';
import { NotificationManager } from '../notification/notificationmanager.js';
import { ServerManager } from '../server/servermanager.js';
import { UserSettingsManager } from '../usersettings/usersettingsmanager.js';
const config = loadConfig();
const logger = log4js.getLogger('Socket User');
logger.level = config.server.logLevel;
export class SocketUser {
constructor(socket, socketManager) {
this.socket = socket;
this.authenticated = false;
this.socketId = socket.id;
this.id = null;
this.user = null;
this.socketManager = socketManager;
this.activityManager = new ActivityManager(this);
this.trackedActivities = new Set();
this.updateManager = new UpdateManager(this);
this.actionManager = new ActionManager(this);
this.eventManager = new EventManager(this);
this.statsManager = new StatsManager(this);
this.notificationManager = new NotificationManager(this);
this.serverManager = new ServerManager(this);
this.userSettingsManager = new UserSettingsManager(this);
this.keycloakAuth = new KeycloakAuth();
this.setupSocketEventHandlers();
}
setupSocketEventHandlers() {
this.socket.use(createAuthMiddleware(this));
this.socket.on('authenticate', this.handleAuthenticateEvent.bind(this));
this.socket.on('setActivity', this.handleSetActivityEvent.bind(this));
this.socket.on('clearActivity', this.handleClearActivityEvent.bind(this));
this.socket.on('getActivities', this.handleGetActivitiesEvent.bind(this));
this.socket.on(
'subscribeToObjectActivity',
this.handleSubscribeToObjectActivityEvent.bind(this)
);
this.socket.on(
'unsubscribeObjectActivity',
this.handleUnsubscribeObjectActivityEvent.bind(this)
);
this.socket.on(
'subscribeToObjectTypeUpdate',
this.handleSubscribeToObjectTypeUpdateEvent.bind(this)
);
this.socket.on(
'subscribeToObjectUpdate',
this.handleSubscribeToObjectUpdateEvent.bind(this)
);
this.socket.on(
'subscribeToObjectEvent',
this.handleSubscribeToObjectEventEvent.bind(this)
);
this.socket.on(
'unsubscribeObjectTypeUpdate',
this.handleUnsubscribeToObjectTypeUpdateEvent.bind(this)
);
this.socket.on(
'unsubscribeObjectUpdate',
this.handleUnsubscribeToObjectUpdateEvent.bind(this)
);
this.socket.on(
'unsubscribeObjectEvent',
this.handleUnsubscribeObjectEventEvent.bind(this)
);
this.socket.on(
'subscribeToModelStats',
this.handleSubscribeToStatsEvent.bind(this)
);
this.socket.on(
'unsubscribeModelStats',
this.handleUnsubscribeToStatsEvent.bind(this)
);
this.socket.on(
'generateHostOtp',
this.handleGenerateHostOtpEvent.bind(this)
);
this.socket.on(
'getServerVersion',
this.handleGetServerVersionEvent.bind(this)
);
this.socket.on(
'getUserSettings',
this.handleGetUserSettingsEvent.bind(this)
);
this.socket.on(
'updateUserSettings',
this.handleUpdateUserSettingsEvent.bind(this)
);
this.socket.on('objectAction', this.handleObjectActionEvent.bind(this));
this.socket.on('disconnect', this.handleDisconnect.bind(this));
}
async handleAuthenticateEvent(data, callback) {
const token = data.token || undefined;
logger.info('Authenticating user with token...');
if (token) {
const result = await this.keycloakAuth.verifyToken(token);
if (result.valid == true) {
logger.info('User authenticated and valid.');
this.user = result.user;
this.id = this.user._id.toString();
this.authenticated = true;
await this.notificationManager.subscribe();
} else {
logger.warn('User is not authenticated.');
}
callback(result);
}
}
trackActivity(type, id) {
this.trackedActivities.add(`${type}|${id}`);
}
untrackActivity(type, id) {
this.trackedActivities.delete(`${type}|${id}`);
}
async handleSetActivityEvent(data, callback) {
const respond = result => {
if (typeof callback === 'function') {
callback(result);
} else {
this.socket.emit('setActivity_result', result);
}
};
if (!data || !data._id || !data.type || !data.mode) {
respond({
success: false,
error: 'Invalid setActivity event data'
});
return;
}
data = { ...data, user: this.user._id.toString() };
try {
const result = await this.activityManager.setActivity(data);
if (result?.success === false) {
respond(result);
return;
}
this.trackActivity(data.type, data._id);
respond(result);
} catch (err) {
logger.error('Set activity event error:', err);
respond({
success: false,
error: err.message
});
}
}
async handleClearActivityEvent(data) {
if (!data || !data._id || !data.type) {
this.socket.emit('clearActivity_result', {
success: false,
error: 'Invalid clearActivity event data'
});
return;
}
data = { ...data, user: this.user._id.toString() };
try {
await this.activityManager.clearActivity(data);
this.untrackActivity(data.type, data._id);
} catch (err) {
logger.error('Clear activity event error:', err);
this.socket.emit('clearActivity_result', {
success: false,
error: err.message
});
}
}
async handleGetActivitiesEvent(data, callback) {
if (!data || !data._id || !data.type) {
callback({
error: 'Invalid getActivities event data'
});
return;
}
try {
const activities = await this.activityManager.getObjectActivities(data);
callback({ activities });
} catch (err) {
logger.error('GetActivities event error:', err);
callback({
error: err.message
});
}
}
async handleSubscribeToObjectActivityEvent(data, callback) {
if (!data || !data._id || !data.objectType) {
if (typeof callback === 'function') {
callback({
success: false,
error: 'Invalid subscribeToObjectActivity event data'
});
}
return;
}
const result = await this.activityManager.subscribeToObjectActivity(
data._id,
data.objectType
);
if (typeof callback === 'function') {
callback(result);
}
}
async handleUnsubscribeObjectActivityEvent(data) {
await this.activityManager.removeObjectActivityListener(
data._id,
data.objectType
);
}
async handleSubscribeToObjectTypeUpdateEvent(data, callback) {
await this.updateManager.subscribeToObjectNew(data.objectType, data.filter);
await this.updateManager.subscribeToObjectDelete(
data.objectType,
data.filter
);
if (typeof callback === 'function') {
callback({ success: true });
}
}
async handleSubscribeToObjectUpdateEvent(data, callback) {
const result = await this.updateManager.subscribeToObjectUpdate(
data._id,
data.objectType
);
if (typeof callback === 'function') {
callback(result);
}
}
async handleSubscribeToObjectEventEvent(data) {
await this.eventManager.subscribeToObjectEvent(
data._id,
data.objectType,
data.eventType
);
}
async handleUnsubscribeToObjectTypeUpdateEvent(data) {
await this.updateManager.removeObjectNewListener(
data.objectType,
data.filter
);
await this.updateManager.removeObjectDeleteListener(
data.objectType,
data.filter
);
}
async handleUnsubscribeToObjectUpdateEvent(data) {
await this.updateManager.removeObjectUpdateListener(
data._id,
data.objectType
);
}
async handleUnsubscribeObjectEventEvent(data) {
await this.eventManager.removeObjectEventsListener(
data._id,
data.objectType,
data.eventType
);
}
async handleSubscribeToStatsEvent(data) {
await this.statsManager.subscribeToStats(data.objectType);
}
async handleUnsubscribeToStatsEvent(data) {
await this.statsManager.removeStatsListener(data.objectType);
}
async handleGenerateHostOtpEvent(data, callback) {
const result = await generateHostOTP(data._id);
callback(result);
}
async handleGetServerVersionEvent(data, callback) {
const responseCallback = typeof callback === 'function' ? callback : data;
responseCallback(this.serverManager.getServerVersion());
}
async handleGetUserSettingsEvent(data, callback) {
const responseCallback = typeof callback === 'function' ? callback : data;
try {
const settings = await this.userSettingsManager.getUserSettings();
responseCallback({ success: true, settings });
} catch (error) {
logger.error('Get user settings error:', error);
responseCallback({ success: false, error: error.message });
}
}
async handleUpdateUserSettingsEvent(data, callback) {
try {
const settings = await this.userSettingsManager.updateUserSettings(data);
if (typeof callback === 'function') {
callback({ success: true, settings });
}
} catch (error) {
logger.error('Update user settings error:', error);
if (typeof callback === 'function') {
callback({ success: false, error: error.message });
}
}
}
async handleObjectActionEvent(data, callback) {
await this.actionManager.sendObjectAction(
data._id,
data.objectType,
data.action,
callback
);
}
async handleDisconnect() {
if (this.user?._id) {
const userId = this.user._id.toString();
for (const activityKey of this.trackedActivities) {
const separatorIndex = activityKey.indexOf('|');
const type = activityKey.slice(0, separatorIndex);
const id = activityKey.slice(separatorIndex + 1);
try {
await this.activityManager.clearActivity({
_id: id,
type,
user: userId
});
} catch (err) {
logger.error('Error clearing activity on disconnect:', err);
}
}
this.trackedActivities.clear();
}
await this.activityManager.removeAllListeners();
await this.actionManager.removeAllListeners();
await this.updateManager.removeAllListeners();
await this.eventManager.removeAllListeners();
await this.statsManager.removeAllListeners();
await this.notificationManager.removeAllListeners();
if (this.user || this.socket.user) {
logger.info(
'External user disconnected:',
this.user.username || this.socket.user?.username
);
} else {
logger.info('External user disconnected.');
}
}
}