Add support for user roles
parent
743b9e4869
commit
c70740f5c7
|
|
@ -1,4 +1,5 @@
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
const userRoles = require('../userRoles');
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
{
|
{
|
||||||
|
|
@ -50,18 +51,95 @@ module.exports =
|
||||||
// listeningRedirectPort disabled
|
// listeningRedirectPort disabled
|
||||||
// use case: loadbalancer backend
|
// use case: loadbalancer backend
|
||||||
httpOnly : false,
|
httpOnly : false,
|
||||||
// If this is set to true, only signed-in users will be able
|
// This function will be called on successful login through oidc.
|
||||||
// to join a room directly. Non-signed-in users (guests) will
|
// Use this function to map your oidc userinfo to the Peer object,
|
||||||
// always be put in the lobby regardless of room lock status.
|
// see examples below.
|
||||||
// If false, there is no difference between guests and signed-in
|
// Examples:
|
||||||
// users when joining.
|
/*
|
||||||
requireSignInToAccess : true,
|
// All authenicated users will be MODERATOR and AUTHENTICATED
|
||||||
// This flag has no effect when requireSignInToAccess is false
|
userMapping : async ({ peer, 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, 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, userinfo }) =>
|
||||||
|
{
|
||||||
|
if (userinfo.email && userinfo.email.endsWith('@example.com'))
|
||||||
|
{
|
||||||
|
peer.addRole(userRoles.MODERATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.addRole(userRoles.AUTHENTICATED);
|
||||||
|
},*/
|
||||||
|
userMapping : async ({ peer, 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 the first
|
// When truthy, the room will be open to all users when the first
|
||||||
// authenticated user has already joined the room.
|
// AUTHENTICATED or MODERATOR user joins the room.
|
||||||
activateOnHostJoin : true,
|
activateOnHostJoin : true,
|
||||||
// Mediasoup settings
|
// Mediasoup settings
|
||||||
mediasoup :
|
mediasoup :
|
||||||
{
|
{
|
||||||
numWorkers : Object.keys(os.cpus()).length,
|
numWorkers : Object.keys(os.cpus()).length,
|
||||||
// mediasoup Worker settings.
|
// mediasoup Worker settings.
|
||||||
|
|
|
||||||
|
|
@ -75,11 +75,15 @@ class Lobby extends EventEmitter
|
||||||
if (peer)
|
if (peer)
|
||||||
{
|
{
|
||||||
peer.socket.removeListener('request', peer.socketRequestHandler);
|
peer.socket.removeListener('request', peer.socketRequestHandler);
|
||||||
peer.removeListener('authenticationChanged', peer.authenticationHandler);
|
peer.removeListener('rolesChange', peer.roleChangeHandler);
|
||||||
|
peer.removeListener('displayNameChanged', peer.displayNameChangeHandler);
|
||||||
|
peer.removeListener('pictureChanged', peer.pictureChangeHandler);
|
||||||
peer.removeListener('close', peer.closeHandler);
|
peer.removeListener('close', peer.closeHandler);
|
||||||
|
|
||||||
peer.socketRequestHandler = null;
|
peer.socketRequestHandler = null;
|
||||||
peer.authenticationHandler = null;
|
peer.roleChangeHandler = null;
|
||||||
|
peer.displayNameChangeHandler = null;
|
||||||
|
peer.pictureChangeHandler = null;
|
||||||
peer.closeHandler = null;
|
peer.closeHandler = null;
|
||||||
|
|
||||||
this.emit('promotePeer', peer);
|
this.emit('promotePeer', peer);
|
||||||
|
|
@ -112,16 +116,25 @@ class Lobby extends EventEmitter
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
peer.authenticationHandler = () =>
|
peer.roleChangeHandler = () =>
|
||||||
{
|
{
|
||||||
logger.info('parkPeer() | authenticationChange [peer:"%s"]', peer.id);
|
logger.info('parkPeer() | rolesChange [peer:"%s"]', peer.id);
|
||||||
|
|
||||||
if (peer.authenticated)
|
this.emit('peerRolesChanged', peer);
|
||||||
{
|
};
|
||||||
this.emit('changeDisplayName', peer);
|
|
||||||
this.emit('changePicture', peer);
|
peer.displayNameChangeHandler = () =>
|
||||||
this.emit('peerAuthenticated', peer);
|
{
|
||||||
}
|
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 = () =>
|
peer.closeHandler = () =>
|
||||||
|
|
@ -143,7 +156,9 @@ class Lobby extends EventEmitter
|
||||||
|
|
||||||
this._peers.set(peer.id, peer);
|
this._peers.set(peer.id, peer);
|
||||||
|
|
||||||
peer.on('authenticationChanged', peer.authenticationHandler);
|
peer.on('rolesChange', peer.roleChangeHandler);
|
||||||
|
peer.on('displayNameChanged', peer.displayNameChangeHandler);
|
||||||
|
peer.on('pictureChanged', peer.pictureChangeHandler);
|
||||||
|
|
||||||
peer.socket.on('request', peer.socketRequestHandler);
|
peer.socket.on('request', peer.socketRequestHandler);
|
||||||
|
|
||||||
|
|
@ -169,8 +184,6 @@ class Lobby extends EventEmitter
|
||||||
|
|
||||||
peer.displayName = displayName;
|
peer.displayName = displayName;
|
||||||
|
|
||||||
this.emit('changeDisplayName', peer);
|
|
||||||
|
|
||||||
cb();
|
cb();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -181,8 +194,6 @@ class Lobby extends EventEmitter
|
||||||
|
|
||||||
peer.picture = picture;
|
peer.picture = picture;
|
||||||
|
|
||||||
this.emit('changePicture', peer);
|
|
||||||
|
|
||||||
cb();
|
cb();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
const EventEmitter = require('events').EventEmitter;
|
const EventEmitter = require('events').EventEmitter;
|
||||||
|
const userRoles = require('../userRoles');
|
||||||
|
const config = require('../config/config');
|
||||||
const Logger = require('./Logger');
|
const Logger = require('./Logger');
|
||||||
|
|
||||||
const logger = new Logger('Peer');
|
const logger = new Logger('Peer');
|
||||||
|
|
@ -22,7 +24,7 @@ class Peer extends EventEmitter
|
||||||
|
|
||||||
this._inLobby = false;
|
this._inLobby = false;
|
||||||
|
|
||||||
this._authenticated = false;
|
this._roles = [ userRoles.ALL ];
|
||||||
|
|
||||||
this._displayName = false;
|
this._displayName = false;
|
||||||
|
|
||||||
|
|
@ -40,8 +42,6 @@ class Peer extends EventEmitter
|
||||||
|
|
||||||
this._consumers = new Map();
|
this._consumers = new Map();
|
||||||
|
|
||||||
this._checkAuthentication();
|
|
||||||
|
|
||||||
this._handlePeer();
|
this._handlePeer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,13 +66,6 @@ class Peer extends EventEmitter
|
||||||
|
|
||||||
_handlePeer()
|
_handlePeer()
|
||||||
{
|
{
|
||||||
this.socket.use((packet, next) =>
|
|
||||||
{
|
|
||||||
this._checkAuthentication();
|
|
||||||
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('disconnect', () =>
|
this.socket.on('disconnect', () =>
|
||||||
{
|
{
|
||||||
if (this.closed)
|
if (this.closed)
|
||||||
|
|
@ -84,33 +77,6 @@ class Peer extends EventEmitter
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get id()
|
get id()
|
||||||
{
|
{
|
||||||
return this._id;
|
return this._id;
|
||||||
|
|
@ -166,21 +132,9 @@ class Peer extends EventEmitter
|
||||||
this._inLobby = inLobby;
|
this._inLobby = inLobby;
|
||||||
}
|
}
|
||||||
|
|
||||||
get authenticated()
|
get roles()
|
||||||
{
|
{
|
||||||
return this._authenticated;
|
return this._roles;
|
||||||
}
|
|
||||||
|
|
||||||
set authenticated(authenticated)
|
|
||||||
{
|
|
||||||
if (authenticated !== this._authenticated)
|
|
||||||
{
|
|
||||||
const oldAuthenticated = this._authenticated;
|
|
||||||
|
|
||||||
this._authenticated = authenticated;
|
|
||||||
|
|
||||||
this.emit('authenticationChanged', { oldAuthenticated });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get displayName()
|
get displayName()
|
||||||
|
|
@ -262,6 +216,35 @@ class Peer extends EventEmitter
|
||||||
return this._consumers;
|
return this._consumers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addRole(newRole)
|
||||||
|
{
|
||||||
|
if (!this._roles.includes(newRole))
|
||||||
|
{
|
||||||
|
this._roles.push(newRole);
|
||||||
|
|
||||||
|
logger.info('addRole() | [newRole:"%s]"', newRole);
|
||||||
|
|
||||||
|
this.emit('rolesChange', { newRole });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeRole(oldRole)
|
||||||
|
{
|
||||||
|
if (this._roles.includes(oldRole))
|
||||||
|
{
|
||||||
|
this._roles = this._roles.filter((role) => role !== oldRole);
|
||||||
|
|
||||||
|
logger.info('removeRole() | [oldRole:"%s]"', oldRole);
|
||||||
|
|
||||||
|
this.emit('rolesChange', { oldRole });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRole(role)
|
||||||
|
{
|
||||||
|
return this._roles.includes(role);
|
||||||
|
}
|
||||||
|
|
||||||
addTransport(id, transport)
|
addTransport(id, transport)
|
||||||
{
|
{
|
||||||
this.transports.set(id, transport);
|
this.transports.set(id, transport);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const EventEmitter = require('events').EventEmitter;
|
const EventEmitter = require('events').EventEmitter;
|
||||||
const Logger = require('./Logger');
|
const Logger = require('./Logger');
|
||||||
const Lobby = require('./Lobby');
|
const Lobby = require('./Lobby');
|
||||||
|
const userRoles = require('../userRoles');
|
||||||
const config = require('../config/config');
|
const config = require('../config/config');
|
||||||
|
|
||||||
const logger = new Logger('Room');
|
const logger = new Logger('Room');
|
||||||
|
|
@ -117,9 +118,8 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
handlePeer(peer)
|
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
|
|
||||||
if (this._peers[peer.id])
|
if (this._peers[peer.id])
|
||||||
{
|
{
|
||||||
logger.warn(
|
logger.warn(
|
||||||
|
|
@ -130,13 +130,16 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always let ADMIN in, even if locked
|
||||||
|
if (peer.roles.includes(userRoles.ADMIN))
|
||||||
|
this._peerJoining(peer);
|
||||||
else if (this._locked)
|
else if (this._locked)
|
||||||
{
|
|
||||||
this._parkPeer(peer);
|
this._parkPeer(peer);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
peer.authenticated ?
|
// If the user has a role in config.requiredRolesForAccess, let them in
|
||||||
|
peer.roles.some((role) => config.requiredRolesForAccess.includes(role)) ?
|
||||||
this._peerJoining(peer) :
|
this._peerJoining(peer) :
|
||||||
this._handleGuest(peer);
|
this._handleGuest(peer);
|
||||||
}
|
}
|
||||||
|
|
@ -144,21 +147,12 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
_handleGuest(peer)
|
_handleGuest(peer)
|
||||||
{
|
{
|
||||||
if (config.requireSignInToAccess)
|
if (config.activateOnHostJoin && !this.checkEmpty())
|
||||||
{
|
this._peerJoining(peer);
|
||||||
if (config.activateOnHostJoin && !this.checkEmpty())
|
|
||||||
{
|
|
||||||
this._peerJoining(peer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this._parkPeer(peer);
|
|
||||||
this._notification(peer.socket, 'signInRequired');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this._peerJoining(peer);
|
this._parkPeer(peer);
|
||||||
|
this._notification(peer.socket, 'signInRequired');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,9 +172,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) => config.requiredRolesForAccess.includes(role))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this._lobby.promotePeer(peer.id);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._lobby.on('changeDisplayName', (changedPeer) =>
|
this._lobby.on('changeDisplayName', (changedPeer) =>
|
||||||
|
|
|
||||||
|
|
@ -241,51 +241,6 @@ function setupOIDC(oidcIssuer)
|
||||||
_claims : tokenset.claims
|
_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);
|
return done(null, user);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -349,7 +304,7 @@ async function setupAuth()
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/callback',
|
'/auth/callback',
|
||||||
passport.authenticate('oidc', { failureRedirect: '/auth/login' }),
|
passport.authenticate('oidc', { failureRedirect: '/auth/login' }),
|
||||||
(req, res) =>
|
async (req, res) =>
|
||||||
{
|
{
|
||||||
const state = JSON.parse(base64.decode(req.query.state));
|
const state = JSON.parse(base64.decode(req.query.state));
|
||||||
|
|
||||||
|
|
@ -373,7 +328,11 @@ async function setupAuth()
|
||||||
|
|
||||||
peer && (peer.displayName = displayName);
|
peer && (peer.displayName = displayName);
|
||||||
peer && (peer.picture = picture);
|
peer && (peer.picture = picture);
|
||||||
peer && (peer.authenticated = true);
|
|
||||||
|
if (peer && typeof config.userMapping === 'function')
|
||||||
|
{
|
||||||
|
await config.userMapping({ peer, userinfo: req.user._userinfo });
|
||||||
|
}
|
||||||
|
|
||||||
res.send(loginHelper({
|
res.send(loginHelper({
|
||||||
displayName,
|
displayName,
|
||||||
|
|
@ -501,6 +460,30 @@ async function runWebSocketServer()
|
||||||
|
|
||||||
peer.on('close', () => peers.delete(peerId));
|
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, userinfo: _userinfo });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
room.handlePeer(peer);
|
room.handlePeer(peer);
|
||||||
})
|
})
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue