Merge branch 'feat-user-roles' into develop
This commit is contained in:
+103
-11
@@ -1,4 +1,5 @@
|
||||
const os = require('os');
|
||||
const userRoles = require('../userRoles');
|
||||
|
||||
module.exports =
|
||||
{
|
||||
@@ -63,18 +64,109 @@ module.exports =
|
||||
// listeningRedirectPort disabled
|
||||
// use case: loadbalancer backend
|
||||
httpOnly : false,
|
||||
// If this is set to true, only signed-in users will be able
|
||||
// to join a room directly. Non-signed-in users (guests) will
|
||||
// always be put in the lobby regardless of room lock status.
|
||||
// If false, there is no difference between guests and signed-in
|
||||
// users when joining.
|
||||
requireSignInToAccess : true,
|
||||
// This flag has no effect when requireSignInToAccess is false
|
||||
// When truthy, the room will be open to all users when the first
|
||||
// authenticated user has already joined the room.
|
||||
activateOnHostJoin : true,
|
||||
// This function will be called on successful login through oidc.
|
||||
// Use this function to map your oidc userinfo to the Peer object.
|
||||
// The roomId is equal to the room name.
|
||||
// See examples below.
|
||||
// Examples:
|
||||
/*
|
||||
// All authenicated users will be MODERATOR and AUTHENTICATED
|
||||
userMapping : async ({ peer, roomId, userinfo }) =>
|
||||
{
|
||||
peer.addRole(userRoles.MODERATOR);
|
||||
peer.addRole(userRoles.AUTHENTICATED);
|
||||
},
|
||||
// All authenicated users will be AUTHENTICATED,
|
||||
// and those with the moderator role set in the userinfo
|
||||
// will also be MODERATOR
|
||||
userMapping : async ({ peer, roomId, userinfo }) =>
|
||||
{
|
||||
if (
|
||||
Array.isArray(userinfo.meet_roles) &&
|
||||
userinfo.meet_roles.includes('moderator')
|
||||
)
|
||||
{
|
||||
peer.addRole(userRoles.MODERATOR);
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(userinfo.meet_roles) &&
|
||||
userinfo.meet_roles.includes('meetingadmin')
|
||||
)
|
||||
{
|
||||
peer.addRole(userRoles.ADMIN);
|
||||
}
|
||||
|
||||
peer.addRole(userRoles.AUTHENTICATED);
|
||||
},
|
||||
// All authenicated users will be AUTHENTICATED,
|
||||
// and those with email ending with @example.com
|
||||
// will also be MODERATOR
|
||||
userMapping : async ({ peer, roomId, userinfo }) =>
|
||||
{
|
||||
if (userinfo.email && userinfo.email.endsWith('@example.com'))
|
||||
{
|
||||
peer.addRole(userRoles.MODERATOR);
|
||||
}
|
||||
|
||||
peer.addRole(userRoles.AUTHENTICATED);
|
||||
}
|
||||
// All authenicated users will be AUTHENTICATED,
|
||||
// and those with email ending with @example.com
|
||||
// will also be MODERATOR
|
||||
userMapping : async ({ peer, roomId, userinfo }) =>
|
||||
{
|
||||
if (userinfo.email && userinfo.email.endsWith('@example.com'))
|
||||
{
|
||||
peer.addRole(userRoles.MODERATOR);
|
||||
}
|
||||
|
||||
peer.addRole(userRoles.AUTHENTICATED);
|
||||
},
|
||||
*/
|
||||
userMapping : async ({ peer, roomId, userinfo }) =>
|
||||
{
|
||||
if (userinfo.picture != null)
|
||||
{
|
||||
if (!userinfo.picture.match(/^http/g))
|
||||
{
|
||||
peer.picture = `data:image/jpeg;base64, ${userinfo.picture}`;
|
||||
}
|
||||
else
|
||||
{
|
||||
peer.picture = userinfo.picture;
|
||||
}
|
||||
}
|
||||
|
||||
if (userinfo.nickname != null)
|
||||
{
|
||||
peer.displayName = userinfo.nickname;
|
||||
}
|
||||
|
||||
if (userinfo.name != null)
|
||||
{
|
||||
peer.displayName = userinfo.name;
|
||||
}
|
||||
|
||||
if (userinfo.email != null)
|
||||
{
|
||||
peer.email = userinfo.email;
|
||||
}
|
||||
},
|
||||
// Required roles for Access. All users have the role "ALL" by default.
|
||||
// Other roles need to be added in the "userMapping" function. This
|
||||
// is an Array of roles. userRoles.ADMIN have all priveleges and access
|
||||
// always.
|
||||
//
|
||||
// Example:
|
||||
// [ userRoles.MODERATOR, userRoles.AUTHENTICATED ]
|
||||
// This will allow all MODERATOR and AUTHENTICATED users access.
|
||||
requiredRolesForAccess : [ userRoles.ALL ],
|
||||
// When truthy, the room will be open to all users when as long as there
|
||||
// are allready users in the room
|
||||
activateOnHostJoin : true,
|
||||
// Mediasoup settings
|
||||
mediasoup :
|
||||
mediasoup :
|
||||
{
|
||||
numWorkers : Object.keys(os.cpus()).length,
|
||||
// mediasoup Worker settings.
|
||||
|
||||
+40
-28
@@ -14,7 +14,7 @@ class Lobby extends EventEmitter
|
||||
// Closed flag.
|
||||
this._closed = false;
|
||||
|
||||
this._peers = new Map();
|
||||
this._peers = {};
|
||||
}
|
||||
|
||||
close()
|
||||
@@ -23,27 +23,28 @@ class Lobby extends EventEmitter
|
||||
|
||||
this._closed = true;
|
||||
|
||||
this._peers.forEach((peer) =>
|
||||
// Close the peers.
|
||||
for (const peer in this._peers)
|
||||
{
|
||||
if (!peer.closed)
|
||||
peer.close();
|
||||
});
|
||||
}
|
||||
|
||||
this._peers.clear();
|
||||
this._peers = null;
|
||||
}
|
||||
|
||||
checkEmpty()
|
||||
{
|
||||
logger.info('checkEmpty()');
|
||||
|
||||
return this._peers.size === 0;
|
||||
return Object.keys(this._peers).length === 0;
|
||||
}
|
||||
|
||||
peerList()
|
||||
{
|
||||
logger.info('peerList()');
|
||||
|
||||
return Array.from(this._peers.values()).map((peer) =>
|
||||
return Object.values(this._peers).map((peer) =>
|
||||
({
|
||||
peerId : peer.id,
|
||||
displayName : peer.displayName
|
||||
@@ -52,38 +53,42 @@ class Lobby extends EventEmitter
|
||||
|
||||
hasPeer(peerId)
|
||||
{
|
||||
return this._peers.has(peerId);
|
||||
return this._peers[peerId] != null;
|
||||
}
|
||||
|
||||
promoteAllPeers()
|
||||
{
|
||||
logger.info('promoteAllPeers()');
|
||||
|
||||
this._peers.forEach((peer) =>
|
||||
for (const peer in this._peers)
|
||||
{
|
||||
if (peer.socket)
|
||||
this.promotePeer(peer.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
promotePeer(peerId)
|
||||
{
|
||||
logger.info('promotePeer() [peer:"%s"]', peerId);
|
||||
|
||||
const peer = this._peers.get(peerId);
|
||||
const peer = this._peers[peerId];
|
||||
|
||||
if (peer)
|
||||
{
|
||||
peer.socket.removeListener('request', peer.socketRequestHandler);
|
||||
peer.removeListener('authenticationChanged', peer.authenticationHandler);
|
||||
peer.removeListener('gotRole', peer.gotRoleHandler);
|
||||
peer.removeListener('displayNameChanged', peer.displayNameChangeHandler);
|
||||
peer.removeListener('pictureChanged', peer.pictureChangeHandler);
|
||||
peer.removeListener('close', peer.closeHandler);
|
||||
|
||||
peer.socketRequestHandler = null;
|
||||
peer.authenticationHandler = null;
|
||||
peer.gotRoleHandler = null;
|
||||
peer.displayNameChangeHandler = null;
|
||||
peer.pictureChangeHandler = null;
|
||||
peer.closeHandler = null;
|
||||
|
||||
this.emit('promotePeer', peer);
|
||||
this._peers.delete(peerId);
|
||||
delete this._peers[peerId];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,16 +117,25 @@ class Lobby extends EventEmitter
|
||||
});
|
||||
};
|
||||
|
||||
peer.authenticationHandler = () =>
|
||||
peer.gotRoleHandler = () =>
|
||||
{
|
||||
logger.info('parkPeer() | authenticationChange [peer:"%s"]', peer.id);
|
||||
logger.info('parkPeer() | rolesChange [peer:"%s"]', peer.id);
|
||||
|
||||
if (peer.authenticated)
|
||||
{
|
||||
this.emit('changeDisplayName', peer);
|
||||
this.emit('changePicture', peer);
|
||||
this.emit('peerAuthenticated', peer);
|
||||
}
|
||||
this.emit('peerRolesChanged', peer);
|
||||
};
|
||||
|
||||
peer.displayNameChangeHandler = () =>
|
||||
{
|
||||
logger.info('parkPeer() | displayNameChange [peer:"%s"]', peer.id);
|
||||
|
||||
this.emit('changeDisplayName', peer);
|
||||
};
|
||||
|
||||
peer.pictureChangeHandler = () =>
|
||||
{
|
||||
logger.info('parkPeer() | pictureChange [peer:"%s"]', peer.id);
|
||||
|
||||
this.emit('changePicture', peer);
|
||||
};
|
||||
|
||||
peer.closeHandler = () =>
|
||||
@@ -133,7 +147,7 @@ class Lobby extends EventEmitter
|
||||
|
||||
this.emit('peerClosed', peer);
|
||||
|
||||
this._peers.delete(peer.id);
|
||||
delete this._peers[peer.id];
|
||||
|
||||
if (this.checkEmpty())
|
||||
this.emit('lobbyEmpty');
|
||||
@@ -141,9 +155,11 @@ class Lobby extends EventEmitter
|
||||
|
||||
this._notification(peer.socket, 'enteredLobby');
|
||||
|
||||
this._peers.set(peer.id, peer);
|
||||
this._peers[peer.id] = peer;
|
||||
|
||||
peer.on('authenticationChanged', peer.authenticationHandler);
|
||||
peer.on('gotRole', peer.gotRoleHandler);
|
||||
peer.on('displayNameChanged', peer.displayNameChangeHandler);
|
||||
peer.on('pictureChanged', peer.pictureChangeHandler);
|
||||
|
||||
peer.socket.on('request', peer.socketRequestHandler);
|
||||
|
||||
@@ -169,8 +185,6 @@ class Lobby extends EventEmitter
|
||||
|
||||
peer.displayName = displayName;
|
||||
|
||||
this.emit('changeDisplayName', peer);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
@@ -181,8 +195,6 @@ class Lobby extends EventEmitter
|
||||
|
||||
peer.picture = picture;
|
||||
|
||||
this.emit('changePicture', peer);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
|
||||
+61
-63
@@ -1,17 +1,20 @@
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const userRoles = require('../userRoles');
|
||||
const Logger = require('./Logger');
|
||||
|
||||
const logger = new Logger('Peer');
|
||||
|
||||
class Peer extends EventEmitter
|
||||
{
|
||||
constructor({ id, socket })
|
||||
constructor({ id, roomId, socket })
|
||||
{
|
||||
logger.info('constructor() [id:"%s", socket:"%s"]', id, socket.id);
|
||||
logger.info('constructor() [id:"%s"]', id);
|
||||
super();
|
||||
|
||||
this._id = id;
|
||||
|
||||
this._roomId = roomId;
|
||||
|
||||
this._authId = null;
|
||||
|
||||
this._socket = socket;
|
||||
@@ -22,7 +25,7 @@ class Peer extends EventEmitter
|
||||
|
||||
this._inLobby = false;
|
||||
|
||||
this._authenticated = false;
|
||||
this._roles = [ userRoles.ALL ];
|
||||
|
||||
this._displayName = false;
|
||||
|
||||
@@ -40,8 +43,6 @@ class Peer extends EventEmitter
|
||||
|
||||
this._consumers = new Map();
|
||||
|
||||
this._checkAuthentication();
|
||||
|
||||
this._handlePeer();
|
||||
}
|
||||
|
||||
@@ -58,56 +59,25 @@ class Peer extends EventEmitter
|
||||
transport.close();
|
||||
});
|
||||
|
||||
if (this._socket)
|
||||
this._socket.disconnect(true);
|
||||
if (this.socket)
|
||||
this.socket.disconnect(true);
|
||||
|
||||
this.emit('close');
|
||||
}
|
||||
|
||||
_handlePeer()
|
||||
{
|
||||
this.socket.use((packet, next) =>
|
||||
if (this.socket)
|
||||
{
|
||||
this._checkAuthentication();
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
this.socket.on('disconnect', () =>
|
||||
{
|
||||
if (this.closed)
|
||||
return;
|
||||
|
||||
logger.debug('"disconnect" event [id:%s]', this.id);
|
||||
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
_checkAuthentication()
|
||||
{
|
||||
if (
|
||||
Boolean(this.socket.handshake.session.passport) &&
|
||||
Boolean(this.socket.handshake.session.passport.user)
|
||||
)
|
||||
{
|
||||
const {
|
||||
id,
|
||||
displayName,
|
||||
picture,
|
||||
email
|
||||
} = this.socket.handshake.session.passport.user;
|
||||
|
||||
id && (this.authId = id);
|
||||
displayName && (this.displayName = displayName);
|
||||
picture && (this.picture = picture);
|
||||
email && (this.email = email);
|
||||
|
||||
this.authenticated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.authenticated = false;
|
||||
this.socket.on('disconnect', () =>
|
||||
{
|
||||
if (this.closed)
|
||||
return;
|
||||
|
||||
logger.debug('"disconnect" event [id:%s]', this.id);
|
||||
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +91,16 @@ class Peer extends EventEmitter
|
||||
this._id = id;
|
||||
}
|
||||
|
||||
get roomId()
|
||||
{
|
||||
return this._roomId;
|
||||
}
|
||||
|
||||
set roomId(roomId)
|
||||
{
|
||||
this._roomId = roomId;
|
||||
}
|
||||
|
||||
get authId()
|
||||
{
|
||||
return this._authId;
|
||||
@@ -166,21 +146,9 @@ class Peer extends EventEmitter
|
||||
this._inLobby = inLobby;
|
||||
}
|
||||
|
||||
get authenticated()
|
||||
get roles()
|
||||
{
|
||||
return this._authenticated;
|
||||
}
|
||||
|
||||
set authenticated(authenticated)
|
||||
{
|
||||
if (authenticated !== this._authenticated)
|
||||
{
|
||||
const oldAuthenticated = this._authenticated;
|
||||
|
||||
this._authenticated = authenticated;
|
||||
|
||||
this.emit('authenticationChanged', { oldAuthenticated });
|
||||
}
|
||||
return this._roles;
|
||||
}
|
||||
|
||||
get displayName()
|
||||
@@ -262,6 +230,35 @@ class Peer extends EventEmitter
|
||||
return this._consumers;
|
||||
}
|
||||
|
||||
addRole(newRole)
|
||||
{
|
||||
if (!this._roles.includes(newRole))
|
||||
{
|
||||
this._roles.push(newRole);
|
||||
|
||||
logger.info('addRole() | [newRole:"%s]"', newRole);
|
||||
|
||||
this.emit('gotRole', { newRole });
|
||||
}
|
||||
}
|
||||
|
||||
removeRole(oldRole)
|
||||
{
|
||||
if (this._roles.includes(oldRole))
|
||||
{
|
||||
this._roles = this._roles.filter((role) => role !== oldRole);
|
||||
|
||||
logger.info('removeRole() | [oldRole:"%s]"', oldRole);
|
||||
|
||||
this.emit('lostRole', { oldRole });
|
||||
}
|
||||
}
|
||||
|
||||
hasRole(role)
|
||||
{
|
||||
return this._roles.includes(role);
|
||||
}
|
||||
|
||||
addTransport(id, transport)
|
||||
{
|
||||
this.transports.set(id, transport);
|
||||
@@ -319,7 +316,8 @@ class Peer extends EventEmitter
|
||||
{
|
||||
id : this.id,
|
||||
displayName : this.displayName,
|
||||
picture : this.picture
|
||||
picture : this.picture,
|
||||
roles : this.roles
|
||||
};
|
||||
|
||||
return peerInfo;
|
||||
|
||||
+167
-69
@@ -2,6 +2,7 @@ const EventEmitter = require('events').EventEmitter;
|
||||
const axios = require('axios');
|
||||
const Logger = require('./Logger');
|
||||
const Lobby = require('./Lobby');
|
||||
const userRoles = require('../userRoles');
|
||||
const config = require('../config/config');
|
||||
|
||||
const logger = new Logger('Room');
|
||||
@@ -54,6 +55,12 @@ class Room extends EventEmitter
|
||||
// Locked flag.
|
||||
this._locked = false;
|
||||
|
||||
// Required roles to access
|
||||
this._requiredRoles = [ userRoles.ALL ];
|
||||
|
||||
if ('requiredRolesForAccess' in config)
|
||||
this._requiredRoles = config.requiredRolesForAccess;
|
||||
|
||||
// if true: accessCode is a possibility to open the room
|
||||
this._joinByAccesCode = true;
|
||||
|
||||
@@ -100,11 +107,8 @@ class Room extends EventEmitter
|
||||
// Close the peers.
|
||||
for (const peer in this._peers)
|
||||
{
|
||||
if (Object.prototype.hasOwnProperty.call(this._peers, peer))
|
||||
{
|
||||
if (!peer.closed)
|
||||
peer.close();
|
||||
}
|
||||
if (!peer.closed)
|
||||
peer.close();
|
||||
}
|
||||
|
||||
this._peers = null;
|
||||
@@ -118,26 +122,27 @@ class Room extends EventEmitter
|
||||
|
||||
handlePeer(peer)
|
||||
{
|
||||
logger.info('handlePeer() [peer:"%s"]', peer.id);
|
||||
logger.info('handlePeer() [peer:"%s", roles:"%s"]', peer.id, peer.roles);
|
||||
|
||||
// This will allow reconnects to join despite lock
|
||||
// Allow reconnections, remove old peer
|
||||
if (this._peers[peer.id])
|
||||
{
|
||||
logger.warn(
|
||||
'handleConnection() | there is already a peer with same peerId [peer:"%s"]',
|
||||
peer.id);
|
||||
|
||||
peer.close();
|
||||
this._peers[peer.id].close();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
// Always let ADMIN in, even if locked
|
||||
if (peer.roles.includes(userRoles.ADMIN))
|
||||
this._peerJoining(peer);
|
||||
else if (this._locked)
|
||||
{
|
||||
this._parkPeer(peer);
|
||||
}
|
||||
else
|
||||
{
|
||||
peer.authenticated ?
|
||||
// If the user has a role in config.requiredRolesForAccess, let them in
|
||||
peer.roles.some((role) => this._requiredRoles.includes(role)) ?
|
||||
this._peerJoining(peer) :
|
||||
this._handleGuest(peer);
|
||||
}
|
||||
@@ -145,21 +150,12 @@ class Room extends EventEmitter
|
||||
|
||||
_handleGuest(peer)
|
||||
{
|
||||
if (config.requireSignInToAccess)
|
||||
{
|
||||
if (config.activateOnHostJoin && !this.checkEmpty())
|
||||
{
|
||||
this._peerJoining(peer);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._parkPeer(peer);
|
||||
this._notification(peer.socket, 'signInRequired');
|
||||
}
|
||||
}
|
||||
if (config.activateOnHostJoin && !this.checkEmpty())
|
||||
this._peerJoining(peer);
|
||||
else
|
||||
{
|
||||
this._peerJoining(peer);
|
||||
this._parkPeer(peer);
|
||||
this._notification(peer.socket, 'signInRequired');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,9 +175,26 @@ class Room extends EventEmitter
|
||||
}
|
||||
});
|
||||
|
||||
this._lobby.on('peerAuthenticated', (peer) =>
|
||||
this._lobby.on('peerRolesChanged', (peer) =>
|
||||
{
|
||||
!this._locked && this._lobby.promotePeer(peer.id);
|
||||
// Always let admin in, even if locked
|
||||
if (peer.roles.includes(userRoles.ADMIN))
|
||||
{
|
||||
this._lobby.promotePeer(peer.id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user has a role in config.requiredRolesForAccess, let them in
|
||||
if (
|
||||
!this._locked &&
|
||||
peer.roles.some((role) => this._requiredRoles.includes(role))
|
||||
)
|
||||
{
|
||||
this._lobby.promotePeer(peer.id);
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
this._lobby.on('changeDisplayName', (changedPeer) =>
|
||||
@@ -304,7 +317,6 @@ class Room extends EventEmitter
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
// checks both room and lobby
|
||||
checkEmpty()
|
||||
{
|
||||
return Object.keys(this._peers).length === 0;
|
||||
@@ -324,12 +336,8 @@ class Room extends EventEmitter
|
||||
{
|
||||
peer.socket.join(this._roomId);
|
||||
|
||||
const index = this._lastN.indexOf(peer.id);
|
||||
|
||||
if (index === -1) // We don't have this peer, add to end
|
||||
{
|
||||
this._lastN.push(peer.id);
|
||||
}
|
||||
// If we don't have this peer, add to end
|
||||
!this._lastN.includes(peer.id) && this._lastN.push(peer.id);
|
||||
|
||||
this._peers[peer.id] = peer;
|
||||
|
||||
@@ -402,25 +410,17 @@ class Room extends EventEmitter
|
||||
|
||||
// If the Peer was joined, notify all Peers.
|
||||
if (peer.joined)
|
||||
{
|
||||
this._notification(peer.socket, 'peerClosed', { peerId: peer.id }, true);
|
||||
}
|
||||
|
||||
const index = this._lastN.indexOf(peer.id);
|
||||
|
||||
if (index > -1) // We have this peer in the list, remove
|
||||
{
|
||||
this._lastN.splice(index, 1);
|
||||
}
|
||||
// Remove from lastN
|
||||
this._lastN = this._lastN.filter((id) => id !== peer.id);
|
||||
|
||||
delete this._peers[peer.id];
|
||||
|
||||
// If this is the last Peer in the room and
|
||||
// lobby is empty, close the room after a while.
|
||||
if (this.checkEmpty() && this._lobby.checkEmpty())
|
||||
{
|
||||
this.selfDestructCountdown();
|
||||
}
|
||||
});
|
||||
|
||||
peer.on('displayNameChanged', ({ oldDisplayName }) =>
|
||||
@@ -449,6 +449,32 @@ class Room extends EventEmitter
|
||||
picture : peer.picture
|
||||
}, true);
|
||||
});
|
||||
|
||||
peer.on('gotRole', ({ newRole }) =>
|
||||
{
|
||||
// Ensure the Peer is joined.
|
||||
if (!peer.joined)
|
||||
return;
|
||||
|
||||
// Spread to others
|
||||
this._notification(peer.socket, 'gotRole', {
|
||||
peerId : peer.id,
|
||||
role : newRole
|
||||
}, true, true);
|
||||
});
|
||||
|
||||
peer.on('lostRole', ({ oldRole }) =>
|
||||
{
|
||||
// Ensure the Peer is joined.
|
||||
if (!peer.joined)
|
||||
return;
|
||||
|
||||
// Spread to others
|
||||
this._notification(peer.socket, 'lostRole', {
|
||||
peerId : peer.id,
|
||||
role : oldRole
|
||||
}, true, true);
|
||||
});
|
||||
}
|
||||
|
||||
async _handleSocketRequest(peer, request, cb)
|
||||
@@ -464,27 +490,6 @@ class Room extends EventEmitter
|
||||
|
||||
case 'join':
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
if (peer.socket.handshake.session.passport.user.displayName)
|
||||
{
|
||||
this._notification(
|
||||
peer.socket,
|
||||
'changeDisplayname',
|
||||
{
|
||||
peerId : peer.id,
|
||||
displayName : peer.socket.handshake.session.passport.user.displayName,
|
||||
oldDisplayName : ''
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error(error);
|
||||
}
|
||||
// Ensure the Peer is not already joined.
|
||||
if (peer.joined)
|
||||
throw new Error('Peer already joined');
|
||||
@@ -512,7 +517,10 @@ class Room extends EventEmitter
|
||||
.filter((joinedPeer) => joinedPeer.id !== peer.id)
|
||||
.map((joinedPeer) => (joinedPeer.peerInfo));
|
||||
|
||||
cb(null, { peers: peerInfos });
|
||||
cb(null, {
|
||||
roles : peer.roles,
|
||||
peers : peerInfos
|
||||
});
|
||||
|
||||
// Mark the new Peer as joined.
|
||||
peer.joined = true;
|
||||
@@ -540,7 +548,8 @@ class Room extends EventEmitter
|
||||
{
|
||||
id : peer.id,
|
||||
displayName : displayName,
|
||||
picture : picture
|
||||
picture : picture,
|
||||
roles : peer.roles
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1106,6 +1115,92 @@ class Room extends EventEmitter
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:muteAll':
|
||||
{
|
||||
if (
|
||||
!peer.hasRole(userRoles.MODERATOR) &&
|
||||
!peer.hasRole(userRoles.ADMIN)
|
||||
)
|
||||
throw new Error('peer does not have moderator priveleges');
|
||||
|
||||
// Spread to others
|
||||
this._notification(peer.socket, 'moderator:mute', {
|
||||
peerId : peer.id
|
||||
}, true);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:stopAllVideo':
|
||||
{
|
||||
if (
|
||||
!peer.hasRole(userRoles.MODERATOR) &&
|
||||
!peer.hasRole(userRoles.ADMIN)
|
||||
)
|
||||
throw new Error('peer does not have moderator priveleges');
|
||||
|
||||
// Spread to others
|
||||
this._notification(peer.socket, 'moderator:stopVideo', {
|
||||
peerId : peer.id
|
||||
}, true);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:closeMeeting':
|
||||
{
|
||||
if (
|
||||
!peer.hasRole(userRoles.MODERATOR) &&
|
||||
!peer.hasRole(userRoles.ADMIN)
|
||||
)
|
||||
throw new Error('peer does not have moderator priveleges');
|
||||
|
||||
this._notification(
|
||||
peer.socket,
|
||||
'moderator:kick',
|
||||
null,
|
||||
true
|
||||
);
|
||||
|
||||
cb();
|
||||
|
||||
// Close the room
|
||||
this.close();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:kickPeer':
|
||||
{
|
||||
if (
|
||||
!peer.hasRole(userRoles.MODERATOR) &&
|
||||
!peer.hasRole(userRoles.ADMIN)
|
||||
)
|
||||
throw new Error('peer does not have moderator priveleges');
|
||||
|
||||
const { peerId } = request.data;
|
||||
|
||||
const kickPeer = this._peers[peerId];
|
||||
|
||||
if (!kickPeer)
|
||||
throw new Error(`peer with id "${peerId}" not found`);
|
||||
|
||||
this._notification(
|
||||
kickPeer.socket,
|
||||
'moderator:kick'
|
||||
);
|
||||
|
||||
kickPeer.close();
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
logger.error('unknown request.method "%s"', request.method);
|
||||
@@ -1319,13 +1414,16 @@ class Room extends EventEmitter
|
||||
});
|
||||
}
|
||||
|
||||
_notification(socket, method, data = {}, broadcast = false)
|
||||
_notification(socket, method, data = {}, broadcast = false, includeSender = false)
|
||||
{
|
||||
if (broadcast)
|
||||
{
|
||||
socket.broadcast.to(this._roomId).emit(
|
||||
'notification', { method, data }
|
||||
);
|
||||
|
||||
if (includeSender)
|
||||
socket.emit('notification', { method, data });
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+63
-69
@@ -18,6 +18,7 @@ const Peer = require('./lib/Peer');
|
||||
const base64 = require('base-64');
|
||||
const helmet = require('helmet');
|
||||
|
||||
const userRoles = require('./userRoles');
|
||||
const {
|
||||
loginHelper,
|
||||
logoutHelper
|
||||
@@ -190,7 +191,7 @@ function setupLTI(ltiConfig)
|
||||
}
|
||||
if (lti.lis_person_name_full)
|
||||
{
|
||||
user.displayName=lti.lis_person_name_full;
|
||||
user.displayName = lti.lis_person_name_full;
|
||||
}
|
||||
|
||||
// Perform local authentication if necessary
|
||||
@@ -241,51 +242,6 @@ function setupOIDC(oidcIssuer)
|
||||
_claims : tokenset.claims
|
||||
};
|
||||
|
||||
if (userinfo.picture != null)
|
||||
{
|
||||
if (!userinfo.picture.match(/^http/g))
|
||||
{
|
||||
user.picture = `data:image/jpeg;base64, ${userinfo.picture}`;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.picture = userinfo.picture;
|
||||
}
|
||||
}
|
||||
|
||||
if (userinfo.nickname != null)
|
||||
{
|
||||
user.displayName = userinfo.nickname;
|
||||
}
|
||||
|
||||
if (userinfo.name != null)
|
||||
{
|
||||
user.displayName = userinfo.name;
|
||||
}
|
||||
|
||||
if (userinfo.email != null)
|
||||
{
|
||||
user.email = userinfo.email;
|
||||
}
|
||||
|
||||
if (userinfo.given_name != null)
|
||||
{
|
||||
user.name={};
|
||||
user.name.givenName = userinfo.given_name;
|
||||
}
|
||||
|
||||
if (userinfo.family_name != null)
|
||||
{
|
||||
if (user.name == null) user.name={};
|
||||
user.name.familyName = userinfo.family_name;
|
||||
}
|
||||
|
||||
if (userinfo.middle_name != null)
|
||||
{
|
||||
if (user.name == null) user.name={};
|
||||
user.name.middleName = userinfo.middle_name;
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
}
|
||||
);
|
||||
@@ -324,7 +280,8 @@ async function setupAuth()
|
||||
{
|
||||
passport.authenticate('oidc', {
|
||||
state : base64.encode(JSON.stringify({
|
||||
id : req.query.id
|
||||
peerId : req.query.peerId,
|
||||
roomId : req.query.roomId
|
||||
}))
|
||||
})(req, res, next);
|
||||
});
|
||||
@@ -341,6 +298,19 @@ async function setupAuth()
|
||||
// logout
|
||||
app.get('/auth/logout', (req, res) =>
|
||||
{
|
||||
const { peerId } = req.session;
|
||||
|
||||
const peer = peers.get(peerId);
|
||||
|
||||
if (peer)
|
||||
{
|
||||
for (const role of peer.roles)
|
||||
{
|
||||
if (role !== userRoles.ALL)
|
||||
peer.removeRole(role);
|
||||
}
|
||||
}
|
||||
|
||||
req.logout();
|
||||
res.send(logoutHelper());
|
||||
});
|
||||
@@ -349,35 +319,35 @@ async function setupAuth()
|
||||
app.get(
|
||||
'/auth/callback',
|
||||
passport.authenticate('oidc', { failureRedirect: '/auth/login' }),
|
||||
(req, res) =>
|
||||
async (req, res) =>
|
||||
{
|
||||
const state = JSON.parse(base64.decode(req.query.state));
|
||||
|
||||
let displayName;
|
||||
let picture;
|
||||
const { peerId, roomId } = state;
|
||||
|
||||
if (req.user != null)
|
||||
req.session.peerId = peerId;
|
||||
req.session.roomId = roomId;
|
||||
|
||||
let peer = peers.get(peerId);
|
||||
|
||||
if (!peer) // User has no socket session yet, make temporary
|
||||
peer = new Peer({ id: peerId, roomId });
|
||||
|
||||
if (peer && peer.roomId !== roomId) // The peer is mischievous
|
||||
throw new Error('peer authenticated with wrong room');
|
||||
|
||||
if (peer && typeof config.userMapping === 'function')
|
||||
{
|
||||
if (req.user.displayName != null)
|
||||
displayName = req.user.displayName;
|
||||
else
|
||||
displayName = '';
|
||||
|
||||
if (req.user.picture != null)
|
||||
picture = req.user.picture;
|
||||
else
|
||||
picture = '/static/media/buddy.403cb9f6.svg';
|
||||
await config.userMapping({
|
||||
peer,
|
||||
roomId,
|
||||
userinfo : req.user._userinfo
|
||||
});
|
||||
}
|
||||
|
||||
const peer = peers.get(state.id);
|
||||
|
||||
peer && (peer.displayName = displayName);
|
||||
peer && (peer.picture = picture);
|
||||
peer && (peer.authenticated = true);
|
||||
|
||||
res.send(loginHelper({
|
||||
displayName,
|
||||
picture
|
||||
displayName : peer.displayName,
|
||||
picture : peer.picture
|
||||
}));
|
||||
}
|
||||
);
|
||||
@@ -495,12 +465,36 @@ async function runWebSocketServer()
|
||||
queue.push(async () =>
|
||||
{
|
||||
const room = await getOrCreateRoom({ roomId });
|
||||
const peer = new Peer({ id: peerId, socket });
|
||||
const peer = new Peer({ id: peerId, roomId, socket });
|
||||
|
||||
peers.set(peerId, peer);
|
||||
|
||||
peer.on('close', () => peers.delete(peerId));
|
||||
|
||||
if (
|
||||
Boolean(socket.handshake.session.passport) &&
|
||||
Boolean(socket.handshake.session.passport.user)
|
||||
)
|
||||
{
|
||||
const {
|
||||
id,
|
||||
displayName,
|
||||
picture,
|
||||
email,
|
||||
_userinfo
|
||||
} = socket.handshake.session.passport.user;
|
||||
|
||||
peer.authId= id;
|
||||
peer.displayName = displayName;
|
||||
peer.picture = picture;
|
||||
peer.email = email;
|
||||
|
||||
if (typeof config.userMapping === 'function')
|
||||
{
|
||||
await config.userMapping({ peer, roomId, userinfo: _userinfo });
|
||||
}
|
||||
}
|
||||
|
||||
room.handlePeer(peer);
|
||||
})
|
||||
.catch((error) =>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
// Allowed to enter locked rooms + all other priveleges
|
||||
ADMIN : 'admin',
|
||||
// Allowed to enter restricted rooms if configured.
|
||||
// Allowed to moderate users in a room (mute all,
|
||||
// spotlight video, kick users)
|
||||
MODERATOR : 'moderator',
|
||||
// Same as MODERATOR, but can't moderate users
|
||||
AUTHENTICATED : 'authenticated',
|
||||
// No priveleges
|
||||
ALL : 'normal'
|
||||
};
|
||||
Reference in New Issue
Block a user