added user permissions to rooms
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import crypto from 'crypto';
|
||||
import { closeTunnelIfUnused } from "./tunnel.manager";
|
||||
|
||||
interface RoomPermissions {
|
||||
canControl: boolean;
|
||||
canManageQueue: boolean;
|
||||
}
|
||||
|
||||
interface RoomUser {
|
||||
id: string;
|
||||
username: string;
|
||||
@@ -8,6 +13,8 @@ interface RoomUser {
|
||||
isHost: boolean;
|
||||
isGuest: boolean;
|
||||
userId?: number;
|
||||
permissions?: RoomPermissions;
|
||||
ipAddress?: string;
|
||||
}
|
||||
|
||||
interface SourceContext {
|
||||
@@ -55,8 +62,14 @@ interface RoomData {
|
||||
exposed: boolean;
|
||||
publicUrl?: string;
|
||||
queue: QueueItem[];
|
||||
bannedIPs: Set<string>;
|
||||
}
|
||||
|
||||
export const DEFAULT_GUEST_PERMISSIONS: RoomPermissions = {
|
||||
canControl: false,
|
||||
canManageQueue: false
|
||||
};
|
||||
|
||||
const rooms = new Map<string, RoomData>();
|
||||
|
||||
export function generateRoomId(): string {
|
||||
@@ -77,13 +90,81 @@ export function createRoom(name: string, host: RoomUser, password?: string, expo
|
||||
metadata: null,
|
||||
exposed,
|
||||
publicUrl,
|
||||
queue: []
|
||||
queue: [],
|
||||
bannedIPs: new Set() // NUEVO
|
||||
};
|
||||
|
||||
rooms.set(roomId, room);
|
||||
return room;
|
||||
}
|
||||
|
||||
export function updateUserPermissions(roomId: string, userId: string, permissions: Partial<RoomPermissions>): boolean {
|
||||
const room = rooms.get(roomId);
|
||||
if (!room) return false;
|
||||
|
||||
const user: any = room.users.get(userId);
|
||||
if (!user) return false;
|
||||
|
||||
if (user.isHost) return false;
|
||||
|
||||
user.permissions = {
|
||||
...user.permissions,
|
||||
...permissions
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function banUserIP(roomId: string, ipAddress: string): boolean {
|
||||
const room = rooms.get(roomId);
|
||||
if (!room) return false;
|
||||
|
||||
room.bannedIPs.add(ipAddress);
|
||||
|
||||
// Remover a todos los usuarios con esa IP
|
||||
Array.from(room.users.values()).forEach(user => {
|
||||
if (user.ipAddress === ipAddress) {
|
||||
removeUserFromRoom(roomId, user.id);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function unbanUserIP(roomId: string, ipAddress: string): boolean {
|
||||
const room = rooms.get(roomId);
|
||||
if (!room) return false;
|
||||
|
||||
return room.bannedIPs.delete(ipAddress);
|
||||
}
|
||||
|
||||
export function isIPBanned(roomId: string, ipAddress: string): boolean {
|
||||
const room = rooms.get(roomId);
|
||||
if (!room) return false;
|
||||
return room.bannedIPs.has(ipAddress);
|
||||
}
|
||||
|
||||
export function getBannedIPs(roomId: string): string[] {
|
||||
const room = rooms.get(roomId);
|
||||
if (!room) return [];
|
||||
return Array.from(room.bannedIPs);
|
||||
}
|
||||
|
||||
export function hasPermission(roomId: string, userId: string, permission: keyof RoomPermissions): boolean {
|
||||
const room = rooms.get(roomId);
|
||||
if (!room) return false;
|
||||
|
||||
const user = room.users.get(userId);
|
||||
if (!user) return false;
|
||||
|
||||
// El host siempre tiene todos los permisos
|
||||
if (user.isHost) return true;
|
||||
|
||||
// Si no tiene permisos definidos, usar defaults
|
||||
const userPerms = user.permissions || DEFAULT_GUEST_PERMISSIONS;
|
||||
return userPerms[permission] || false;
|
||||
}
|
||||
|
||||
export function getRoom(roomId: string): RoomData | null {
|
||||
return rooms.get(roomId) || null;
|
||||
}
|
||||
|
||||
@@ -13,14 +13,13 @@ interface WSClient {
|
||||
|
||||
const clients = new Map<string, WSClient>();
|
||||
|
||||
interface WSParams {
|
||||
roomId: string;
|
||||
}
|
||||
|
||||
interface WSQuery {
|
||||
token?: string;
|
||||
guestName?: string;
|
||||
password?: string;
|
||||
function getClientIP(req: any): string {
|
||||
return req.headers['x-forwarded-for']?.split(',')[0].trim() ||
|
||||
req.headers['x-real-ip'] ||
|
||||
req.connection?.remoteAddress ||
|
||||
req.socket?.remoteAddress ||
|
||||
'unknown';
|
||||
}
|
||||
|
||||
export function setupRoomWebSocket(fastify: FastifyInstance) {
|
||||
@@ -44,13 +43,14 @@ async function handleWebSocketConnection(connection: any, req: any) {
|
||||
const guestName = req.query.guestName;
|
||||
const password = req.query.password;
|
||||
|
||||
const clientIP = getClientIP(req); // NUEVO
|
||||
|
||||
let userId: string;
|
||||
let username: string;
|
||||
let avatar: string | undefined;
|
||||
let isGuest = false;
|
||||
let realUserId: any;
|
||||
|
||||
// Verificar si la sala existe
|
||||
const room = roomService.getRoom(roomId);
|
||||
if (!room) {
|
||||
socket.send(JSON.stringify({
|
||||
@@ -61,6 +61,24 @@ async function handleWebSocketConnection(connection: any, req: any) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (roomService.isIPBanned(roomId, clientIP)) {
|
||||
socket.send(JSON.stringify({
|
||||
type: 'error',
|
||||
message: 'You have been banned from this room'
|
||||
}));
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!room) {
|
||||
socket.send(JSON.stringify({
|
||||
type: 'error',
|
||||
message: 'Room not found'
|
||||
}));
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar contraseña si existe
|
||||
if (room.password) {
|
||||
if (!password || !roomService.verifyRoomPassword(roomId, password)) {
|
||||
@@ -134,14 +152,15 @@ async function handleWebSocketConnection(connection: any, req: any) {
|
||||
isHost
|
||||
});
|
||||
|
||||
// Agregar usuario a la sala
|
||||
const userInRoom = {
|
||||
id: userId,
|
||||
username,
|
||||
avatar,
|
||||
isHost: isHost, // ← CORREGIDO: Usar la verificación correcta
|
||||
isHost: isHost,
|
||||
isGuest,
|
||||
userId: realUserId
|
||||
userId: realUserId,
|
||||
ipAddress: clientIP, // NUEVO
|
||||
permissions: isHost ? undefined : { ...roomService.DEFAULT_GUEST_PERMISSIONS }
|
||||
};
|
||||
|
||||
roomService.addUserToRoom(roomId, userInRoom);
|
||||
@@ -160,6 +179,7 @@ async function handleWebSocketConnection(connection: any, req: any) {
|
||||
userId,
|
||||
username,
|
||||
isGuest,
|
||||
isHost, // NUEVO: Enviar explícitamente
|
||||
room: {
|
||||
id: room.id,
|
||||
name: room.name,
|
||||
@@ -168,7 +188,8 @@ async function handleWebSocketConnection(connection: any, req: any) {
|
||||
username: u.username,
|
||||
avatar: u.avatar,
|
||||
isHost: u.isHost,
|
||||
isGuest: u.isGuest
|
||||
isGuest: u.isGuest,
|
||||
permissions: u.permissions // NUEVO
|
||||
})),
|
||||
currentVideo: room.currentVideo,
|
||||
queue: room.queue || []
|
||||
@@ -212,6 +233,9 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
const room = roomService.getRoom(roomId);
|
||||
if (!room) return;
|
||||
|
||||
const user = room.users.get(userId);
|
||||
if (!user) return;
|
||||
|
||||
console.log('Handling message:', data.type, 'from user:', userId, 'isHost:', room.host.id === userId);
|
||||
|
||||
switch (data.type) {
|
||||
@@ -226,8 +250,65 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
});
|
||||
break;
|
||||
|
||||
case 'update_permissions':
|
||||
if (!user.isHost) {
|
||||
console.warn('Non-host attempted to update permissions');
|
||||
return;
|
||||
}
|
||||
|
||||
const success = roomService.updateUserPermissions(
|
||||
roomId,
|
||||
data.targetUserId,
|
||||
data.permissions
|
||||
);
|
||||
|
||||
if (success) {
|
||||
const updatedRoom = roomService.getRoom(roomId);
|
||||
if (updatedRoom) {
|
||||
broadcastToRoom(roomId, {
|
||||
type: 'permissions_updated',
|
||||
users: Array.from(updatedRoom.users.values()).map(u => ({
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
avatar: u.avatar,
|
||||
isHost: u.isHost,
|
||||
isGuest: u.isGuest,
|
||||
permissions: u.permissions
|
||||
}))
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// NUEVO: Baneo de usuarios (solo host)
|
||||
case 'ban_user':
|
||||
if (!user.isHost) {
|
||||
console.warn('Non-host attempted to ban user');
|
||||
return;
|
||||
}
|
||||
|
||||
const targetUser = room.users.get(data.targetUserId);
|
||||
if (targetUser && targetUser.ipAddress) {
|
||||
roomService.banUserIP(roomId, targetUser.ipAddress);
|
||||
|
||||
// Cerrar conexión del usuario baneado
|
||||
const targetClient = clients.get(data.targetUserId);
|
||||
if (targetClient && targetClient.socket) {
|
||||
targetClient.socket.send(JSON.stringify({
|
||||
type: 'banned',
|
||||
message: 'You have been banned from this room'
|
||||
}));
|
||||
targetClient.socket.close();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'queue_play_item':
|
||||
if (room.host.id !== userId) return;
|
||||
if (!user.isHost && !roomService.hasPermission(roomId, userId, 'canManageQueue')) {
|
||||
console.warn('User lacks permission for queue management');
|
||||
return;
|
||||
}
|
||||
|
||||
const itemToPlay = roomService.getAndRemoveQueueItem(roomId, data.itemUid);
|
||||
if (itemToPlay) {
|
||||
@@ -255,7 +336,9 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
break;
|
||||
|
||||
case 'queue_move':
|
||||
if (room.host.id !== userId) return;
|
||||
if (!user.isHost && !roomService.hasPermission(roomId, userId, 'canManageQueue')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const moved = roomService.moveQueueItem(roomId, data.itemUid, data.direction);
|
||||
if (moved) {
|
||||
@@ -295,7 +378,14 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
break;
|
||||
|
||||
case 'video_update':
|
||||
if (room.host.id !== userId) return;
|
||||
const canUpdateVideo = user.isHost ||
|
||||
roomService.hasPermission(roomId, userId, 'canControl') ||
|
||||
roomService.hasPermission(roomId, userId, 'canManageQueue');
|
||||
|
||||
if (!canUpdateVideo) {
|
||||
console.warn('User lacks permissions to update video');
|
||||
return;
|
||||
}
|
||||
|
||||
roomService.updateRoomVideo(roomId, data.video);
|
||||
roomService.updateRoomMetadata(roomId, data.metadata);
|
||||
@@ -308,13 +398,13 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
break;
|
||||
|
||||
case 'queue_add_batch':
|
||||
if (room.host.id !== userId) return;
|
||||
if (!user.isHost && !roomService.hasPermission(roomId, userId, 'canManageQueue')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(data.items)) {
|
||||
// Añadimos el índice (i) al forEach
|
||||
data.items.forEach((item: any, i: number) => {
|
||||
const newItem = {
|
||||
// Añadimos el índice '_${i}' al UID para garantizar unicidad en milisegundos
|
||||
uid: `q_${Date.now()}_${i}_${Math.random().toString(36).substr(2, 5)}`,
|
||||
metadata: item.metadata,
|
||||
videoData: item.video,
|
||||
@@ -365,13 +455,11 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
break;
|
||||
|
||||
case 'play':
|
||||
// Solo el host puede controlar la reproducción
|
||||
if (room.host.id !== userId) {
|
||||
console.warn('Non-host attempted play:', userId);
|
||||
if (!user.isHost && !roomService.hasPermission(roomId, userId, 'canControl')) {
|
||||
console.warn('User lacks control permissions');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Broadcasting play event to room:', roomId);
|
||||
broadcastToRoom(roomId, {
|
||||
type: 'play',
|
||||
currentTime: data.currentTime,
|
||||
@@ -380,8 +468,8 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
break;
|
||||
|
||||
case 'pause':
|
||||
if (room.host.id !== userId) {
|
||||
console.warn('Non-host attempted pause:', userId);
|
||||
if (!user.isHost && !roomService.hasPermission(roomId, userId, 'canControl')) {
|
||||
console.warn('User lacks control permissions for pause');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -394,8 +482,8 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
break;
|
||||
|
||||
case 'seek':
|
||||
if (room.host.id !== userId) {
|
||||
console.warn('Non-host attempted seek:', userId);
|
||||
if (!user.isHost && !roomService.hasPermission(roomId, userId, 'canControl')) {
|
||||
console.warn('User lacks control permissions for seek');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -436,19 +524,19 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
break;
|
||||
|
||||
case 'queue_add':
|
||||
// Solo el host (o todos si quisieras) pueden añadir
|
||||
if (room.host.id !== userId) return;
|
||||
if (!user.isHost && !roomService.hasPermission(roomId, userId, 'canManageQueue')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newItem = {
|
||||
uid: `q_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`,
|
||||
metadata: data.metadata,
|
||||
videoData: data.video, // El objeto que contiene url, type, subtitles
|
||||
videoData: data.video,
|
||||
addedBy: room.users.get(userId)?.username || 'Unknown'
|
||||
};
|
||||
|
||||
roomService.addQueueItem(roomId, newItem);
|
||||
|
||||
// Avisar a todos que la cola cambió
|
||||
broadcastToRoom(roomId, {
|
||||
type: 'queue_update',
|
||||
queue: room.queue
|
||||
@@ -456,7 +544,10 @@ function handleMessage(roomId: string, userId: string, data: any) {
|
||||
break;
|
||||
|
||||
case 'queue_remove':
|
||||
if (room.host.id !== userId) return;
|
||||
if (!user.isHost && !roomService.hasPermission(roomId, userId, 'canManageQueue')) {
|
||||
return;
|
||||
}
|
||||
|
||||
roomService.removeQueueItem(roomId, data.itemUid);
|
||||
broadcastToRoom(roomId, {
|
||||
type: 'queue_update',
|
||||
|
||||
Reference in New Issue
Block a user