mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
fixins
This commit is contained in:
@@ -7,7 +7,7 @@ const logger = require('../../globals/logger').child('liftService');
|
|||||||
const { loadConfig } = require('../../helpers/configLoader');
|
const { loadConfig } = require('../../helpers/configLoader');
|
||||||
const { isFeatureEnabled } = require('../../helpers/features');
|
const { isFeatureEnabled } = require('../../helpers/features');
|
||||||
const { getMode, MODES } = require('../modeManager');
|
const { getMode, MODES } = require('../modeManager');
|
||||||
const { isLockdownAdmin } = require('../roleService');
|
const { isAdmin, isLockdownAdmin } = require('../roleService');
|
||||||
const {
|
const {
|
||||||
homeAssistantEvents,
|
homeAssistantEvents,
|
||||||
getRawEntitySnapshot,
|
getRawEntitySnapshot,
|
||||||
@@ -186,13 +186,20 @@ if (featureEnabled) {
|
|||||||
homeAssistantEvents.on('status', emitUpdate);
|
homeAssistantEvents.on('status', emitUpdate);
|
||||||
|
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
|
function assertFeatureAccess() {
|
||||||
|
const mode = getMode();
|
||||||
|
// Lift is a public activity feature in open and turns modes. Restricted
|
||||||
|
// access modes mirror the rest of the server: admin mode admits normal
|
||||||
|
// admins, while lockdown admits only the explicitly stronger lockdown
|
||||||
|
// role. Enforcing this in the owning service keeps UI buttons and text
|
||||||
|
// command behavior aligned instead of trusting individual callers.
|
||||||
|
if (mode === MODES.ADMIN && !isAdmin(socket)) throw new Error('Admin mode: admins only');
|
||||||
|
if (mode === MODES.LOCKDOWN && !isLockdownAdmin(socket)) throw new Error('Server in lockdown');
|
||||||
|
}
|
||||||
|
|
||||||
socket.on('lift:up', async (_, cb = () => {}) => {
|
socket.on('lift:up', async (_, cb = () => {}) => {
|
||||||
try {
|
try {
|
||||||
if (getMode() === MODES.LOCKDOWN && !isLockdownAdmin(socket)) {
|
assertFeatureAccess();
|
||||||
throw new Error('Server in lockdown');
|
|
||||||
}
|
|
||||||
// Lift movement is now a public activity feature. Lockdown still wins
|
|
||||||
// above because that mode is the global safety/admin gate for the room.
|
|
||||||
const resp = await moveUp(socket.id || 'socket');
|
const resp = await moveUp(socket.id || 'socket');
|
||||||
cb({ success: true, ...resp });
|
cb({ success: true, ...resp });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -202,11 +209,7 @@ if (featureEnabled) {
|
|||||||
|
|
||||||
socket.on('lift:down', async (_, cb = () => {}) => {
|
socket.on('lift:down', async (_, cb = () => {}) => {
|
||||||
try {
|
try {
|
||||||
if (getMode() === MODES.LOCKDOWN && !isLockdownAdmin(socket)) {
|
assertFeatureAccess();
|
||||||
throw new Error('Server in lockdown');
|
|
||||||
}
|
|
||||||
// Public access intentionally mirrors lift:up so both directions share
|
|
||||||
// the same policy and cannot drift into different permission behavior.
|
|
||||||
const resp = await moveDown(socket.id || 'socket');
|
const resp = await moveDown(socket.id || 'socket');
|
||||||
cb({ success: true, ...resp });
|
cb({ success: true, ...resp });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const { loadConfig } = require('../../helpers/configLoader');
|
|||||||
const { isFeatureEnabled } = require('../../helpers/features');
|
const { isFeatureEnabled } = require('../../helpers/features');
|
||||||
const { isVerified } = require('../verificationService');
|
const { isVerified } = require('../verificationService');
|
||||||
const { getMode, MODES } = require('../modeManager');
|
const { getMode, MODES } = require('../modeManager');
|
||||||
const { isLockdownAdmin } = require('../roleService');
|
const { isAdmin, isLockdownAdmin } = require('../roleService');
|
||||||
const {
|
const {
|
||||||
homeAssistantEvents,
|
homeAssistantEvents,
|
||||||
getRawEntitySnapshot,
|
getRawEntitySnapshot,
|
||||||
@@ -269,17 +269,19 @@ function hasVerifiedSockets() {
|
|||||||
|
|
||||||
if (featureEnabled) {
|
if (featureEnabled) {
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
function assertLockdownAccess() {
|
function assertFeatureAccess() {
|
||||||
if (getMode() === MODES.LOCKDOWN && !isLockdownAdmin(socket)) {
|
const mode = getMode();
|
||||||
throw new Error('Server in lockdown');
|
// Neato shares the same public-activity policy as lift: everyone may use
|
||||||
}
|
// it in open/turns modes, admin mode requires an admin, and lockdown
|
||||||
|
// requires a lockdown admin. This service-level gate protects every socket
|
||||||
|
// action even if a future client bypasses the current UI presentation.
|
||||||
|
if (mode === MODES.ADMIN && !isAdmin(socket)) throw new Error('Admin mode: admins only');
|
||||||
|
if (mode === MODES.LOCKDOWN && !isLockdownAdmin(socket)) throw new Error('Server in lockdown');
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on('neato:start', async (_, cb = () => {}) => {
|
socket.on('neato:start', async (_, cb = () => {}) => {
|
||||||
try {
|
try {
|
||||||
assertLockdownAccess();
|
assertFeatureAccess();
|
||||||
// Neato commands are public activity features. The lockdown check above
|
|
||||||
// remains the room-wide safety/admin gate when the server is restricted.
|
|
||||||
await startCleaning();
|
await startCleaning();
|
||||||
cb({ success: true });
|
cb({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -289,8 +291,7 @@ if (featureEnabled) {
|
|||||||
|
|
||||||
socket.on('neato:sendHome', async (_, cb = () => {}) => {
|
socket.on('neato:sendHome', async (_, cb = () => {}) => {
|
||||||
try {
|
try {
|
||||||
assertLockdownAccess();
|
assertFeatureAccess();
|
||||||
// Keep send-home public for consistency with the rest of the Neato card.
|
|
||||||
await sendHome();
|
await sendHome();
|
||||||
cb({ success: true });
|
cb({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -300,8 +301,7 @@ if (featureEnabled) {
|
|||||||
|
|
||||||
socket.on('neato:locate', async (_, cb = () => {}) => {
|
socket.on('neato:locate', async (_, cb = () => {}) => {
|
||||||
try {
|
try {
|
||||||
assertLockdownAccess();
|
assertFeatureAccess();
|
||||||
// Locate is a public activity action; lockdown still blocks it above.
|
|
||||||
await locateRobot();
|
await locateRobot();
|
||||||
cb({ success: true });
|
cb({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -311,9 +311,7 @@ if (featureEnabled) {
|
|||||||
|
|
||||||
socket.on('neato:clearErrors', async (_, cb = () => {}) => {
|
socket.on('neato:clearErrors', async (_, cb = () => {}) => {
|
||||||
try {
|
try {
|
||||||
assertLockdownAccess();
|
assertFeatureAccess();
|
||||||
// Error clearing is grouped with the public Neato controls so the UI does
|
|
||||||
// not show a button that only some public users can actually run.
|
|
||||||
await clearErrors();
|
await clearErrors();
|
||||||
cb({ success: true });
|
cb({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -323,9 +321,7 @@ if (featureEnabled) {
|
|||||||
|
|
||||||
socket.on('neato:powerCycle', async (_, cb = () => {}) => {
|
socket.on('neato:powerCycle', async (_, cb = () => {}) => {
|
||||||
try {
|
try {
|
||||||
assertLockdownAccess();
|
assertFeatureAccess();
|
||||||
// Power cycle follows the same public policy as the rest of the card;
|
|
||||||
// operational safety remains controlled by lockdown mode.
|
|
||||||
await powerCycle();
|
await powerCycle();
|
||||||
cb({ success: true });
|
cb({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -97,8 +97,19 @@ function createCommandHandlers(deps) {
|
|||||||
// light locking belongs here because it can force the physical room lights
|
// light locking belongs here because it can force the physical room lights
|
||||||
// on and disables ordinary Home Assistant room controls for everyone else.
|
// on and disables ordinary Home Assistant room controls for everyone else.
|
||||||
const moderationActions = new Set(['lock', 'unlock', 'mode', 'goal', 'reason', 'verify', 'deter', 'lights', 'kick', 'lift', 'neato']);
|
const moderationActions = new Set(['lock', 'unlock', 'mode', 'goal', 'reason', 'verify', 'deter', 'lights', 'kick', 'lift', 'neato']);
|
||||||
|
const isAccessModeCommand = commandDefinition?.permission === 'access-mode';
|
||||||
|
|
||||||
if (!isAdmin && action !== '' && action !== 'status' && action !== 'help' && action !== 'replay' && action !== 'bridge' && action !== 'goal' && action !== 'reason' && action !== 'verify' && action !== 'deter') {
|
// Feature commands are public activities while access is open or managed
|
||||||
|
// by turns. In admin mode they follow the same admin-only boundary as rover
|
||||||
|
// access, and lockdown continues to require the stricter lockdown role.
|
||||||
|
// Keeping this policy in the shared dispatcher makes web chat and Discord
|
||||||
|
// behave identically instead of each transport interpreting modes itself.
|
||||||
|
if (isAccessModeCommand && mode === MODES.ADMIN && !isAdmin) {
|
||||||
|
await request.reply({ content: 'Admin mode: only admins can run feature commands.', allowedMentions: { parse: [], repliedUser: false } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAccessModeCommand && !isAdmin && action !== '' && action !== 'status' && action !== 'help' && action !== 'replay' && action !== 'bridge' && action !== 'goal' && action !== 'reason' && action !== 'verify' && action !== 'deter') {
|
||||||
await request.reply({ content: 'Only admins can run that command.', allowedMentions: { parse: [], repliedUser: false } });
|
await request.reply({ content: 'Only admins can run that command.', allowedMentions: { parse: [], repliedUser: false } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ function buildCommandRegistry(prefix, timeCommand) {
|
|||||||
kick: { category: 'admin', summary: 'Remove a user from their current rover.', usage: [`${prefix} kick <user> [reason]`], access: 'Admin', permission: 'admin' },
|
kick: { category: 'admin', summary: 'Remove a user from their current rover.', usage: [`${prefix} kick <user> [reason]`], access: 'Admin', permission: 'admin' },
|
||||||
verify: { category: 'admin', summary: 'List or remove verified identities.', usage: [`${prefix} verify list`, `${prefix} verify remove <identity>`], access: 'Lockdown admin', permission: 'lockdown-admin' },
|
verify: { category: 'admin', summary: 'List or remove verified identities.', usage: [`${prefix} verify list`, `${prefix} verify remove <identity>`], access: 'Lockdown admin', permission: 'lockdown-admin' },
|
||||||
deter: { category: 'admin', summary: 'List, add, or remove identity deterrence.', usage: [`${prefix} deter list`, `${prefix} deter ban <identity>`, `${prefix} deter unban <identity>`], access: 'Lockdown admin', permission: 'lockdown-admin' },
|
deter: { category: 'admin', summary: 'List, add, or remove identity deterrence.', usage: [`${prefix} deter list`, `${prefix} deter ban <identity>`, `${prefix} deter unban <identity>`], access: 'Lockdown admin', permission: 'lockdown-admin' },
|
||||||
lift: { category: 'features', summary: 'Show or move the lift.', usage: [`${prefix} lift <status|up|down>`], access: 'Admin', permission: 'admin', requiredFeature: 'lift', unavailableLabel: 'Lift' },
|
lift: { category: 'features', summary: 'Show or move the lift.', usage: [`${prefix} lift <status|up|down>`], access: 'Public unless server access is restricted', permission: 'access-mode', requiredFeature: 'lift', unavailableLabel: 'Lift' },
|
||||||
neato: { category: 'features', summary: 'Show or control Neato.', usage: [`${prefix} neato <status|start|home|locate|clear-errors>`], access: 'Admin', permission: 'admin', requiredFeature: 'neato', unavailableLabel: 'Neato' },
|
neato: { category: 'features', summary: 'Show or control Neato.', usage: [`${prefix} neato <status|start|home|locate|clear-errors>`], access: 'Public unless server access is restricted', permission: 'access-mode', requiredFeature: 'neato', unavailableLabel: 'Neato' },
|
||||||
bridge: { category: 'discord', summary: 'Configure this Discord server chat bridge.', usage: [`${prefix} bridge`, `${prefix} bridge here <global|private>`, `${prefix} bridge mode <global|private>`, `${prefix} bridge off`], access: 'Discord server manager' },
|
bridge: { category: 'discord', summary: 'Configure this Discord server chat bridge.', usage: [`${prefix} bridge`, `${prefix} bridge here <global|private>`, `${prefix} bridge mode <global|private>`, `${prefix} bridge off`], access: 'Discord server manager' },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
1. assign rovers based on battery percentage, give people highest one
|
1. assign rovers based on battery percentage, give people highest one
|
||||||
2. add admin ui for VIP and private requests instead of only through discord
|
2. add admin ui for VIP and private requests instead of only through discord
|
||||||
3. make overcurrent limiter speed sensitive, slower fill at lower speeds
|
3. make overcurrent limiter speed sensitive, slower fill at lower speeds
|
||||||
4. add feature chat commands, like /neato start, /lift up, etc
|
4. add more background gap themes
|
||||||
5. add more background gap themes
|
5. fix this:
|
||||||
6. fix this:
|
|
||||||
`Jun 18 15:14:18 roombaserver.local node[216731]: /home/daniel/MultiRoombaRover/server/src/services/roverManager/socketHandlers.js:92
|
`Jun 18 15:14:18 roombaserver.local node[216731]: /home/daniel/MultiRoombaRover/server/src/services/roverManager/socketHandlers.js:92
|
||||||
Jun 18 15:14:18 roombaserver.local node[216731]: cb({ error: err.message });
|
Jun 18 15:14:18 roombaserver.local node[216731]: cb({ error: err.message });
|
||||||
Jun 18 15:14:18 roombaserver.local node[216731]: ^
|
Jun 18 15:14:18 roombaserver.local node[216731]: ^
|
||||||
|
|||||||
Reference in New Issue
Block a user