Files
MultiRoombaRover/server/src/services/videoAuthService/policy.js
T
2026-07-18 03:22:01 -04:00

129 lines
3.6 KiB
JavaScript

// Video Auth Policy
// Purpose: Encapsulates mode, role, and stream-specific authorization decisions for MediaMTX auth checks.
// Scope: Evaluates viewer/publisher eligibility from normalized request context and socket/session state.
const {
shouldUseSnapshotsForNonTurnVideo,
shouldUseSnapshotsForExternalSpectatorVideo,
} = require('../../helpers/bandwidthSavings');
function createVideoAuthPolicy(deps) {
const {
getMode,
MODES,
isAdmin,
isLockdownAdmin,
getRole,
isVerified,
turnService,
roverManager,
ptzCameraService,
getSocketIp,
isLocalNetwork,
io,
} = deps;
function countControllableUsers() {
const ids = new Set();
io.sockets.sockets.forEach((candidate) => {
if (!candidate?.id || getRole(candidate) === 'spectator') return;
/*
MediaMTX can ask for authorization after a browser has already received
a token, so this count intentionally mirrors videoSocketService instead
of trusting the client-visible session policy snapshot.
*/
if (roverManager.getRoversForSocket(candidate.id).length > 0) {
ids.add(candidate.id);
}
});
if (typeof ptzCameraService.getParticipantSocketIds === 'function') {
ptzCameraService.getParticipantSocketIds().forEach((socketId) => {
const socket = io.sockets.sockets.get(socketId);
if (socket && getRole(socket) !== 'spectator') ids.add(socketId);
});
}
return ids.size;
}
function canView(socket) {
const mode = getMode();
if (!socket) return false;
if (mode === MODES.LOCKDOWN) {
return isLockdownAdmin(socket);
}
if (mode === MODES.ADMIN) {
const role = getRole(socket);
return role === 'spectator' || isAdmin(socket);
}
return true;
}
function canAccessStream({ socket, streamInfo, action, sourceType }) {
if (!canView(socket)) {
return false;
}
if (streamInfo.type === 'rover') {
const roverId = streamInfo.baseId || streamInfo.id;
if (!roverManager.canSeeRover(roverId, socket)) {
return false;
}
}
if (streamInfo.type === 'ptz') {
return ptzCameraService.canRequestLiveVideo(socket);
}
if (sourceType === 'roverMic' && action === 'publish') {
const roverId = streamInfo.baseId || streamInfo.id;
if (!isVerified(socket)) {
return false;
}
if (!roverManager.isDriver(roverId, socket)) {
return false;
}
if (!turnService.canDrive(roverId, socket)) {
return false;
}
return true;
}
const role = getRole(socket);
const isAudio = streamInfo.id?.endsWith('-audio');
if (role === 'spectator' && !isAdmin(socket) && !isAudio) {
const socketIp = getSocketIp(socket);
if (!isLocalNetwork(socketIp) && shouldUseSnapshotsForExternalSpectatorVideo()) {
return false;
}
}
if (streamInfo.type === 'rover' && role !== 'spectator' && !isAdmin(socket)) {
const roverId = streamInfo.baseId || streamInfo.id;
if (!roverManager.isDriver(roverId, socket)) {
return false;
}
if (
!isAudio &&
shouldUseSnapshotsForNonTurnVideo({ controllableUserCount: countControllableUsers() }) &&
!turnService.canRequestLiveVideo(roverId, socket)
) {
/*
This mirrors videoSocketService's token gate. MediaMTX can ask auth
after a token has been issued, so the same "must belong to this rover's
driver queue" rule has to be evaluated here too.
*/
return false;
}
}
return true;
}
return {
canAccessStream,
};
}
module.exports = {
createVideoAuthPolicy,
};