commit
209653dcc0
|
|
@ -13,6 +13,7 @@
|
|||
"bowser": "^2.7.0",
|
||||
"dompurify": "^2.0.7",
|
||||
"domready": "^1.0.8",
|
||||
"end-of-stream": "1.4.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"hark": "^1.2.3",
|
||||
"is-electron": "^2.2.0",
|
||||
|
|
@ -27,7 +28,7 @@
|
|||
"react-intl": "^3.4.0",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.2.0",
|
||||
"react-scripts": "^3.3.0",
|
||||
"redux": "^4.0.4",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-persist": "^6.0.0",
|
||||
|
|
@ -36,7 +37,6 @@
|
|||
"riek": "^1.1.0",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"source-map-explorer": "^2.1.0",
|
||||
"end-of-stream": "1.4.0",
|
||||
"webtorrent": "^0.107.16"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ export default class RoomClient
|
|||
}
|
||||
|
||||
constructor(
|
||||
{ peerId, accessCode, device, useSimulcast, produce, forceTcp } = {})
|
||||
{ peerId, accessCode, device, useSimulcast, produce, forceTcp, displayName } = {})
|
||||
{
|
||||
if (!peerId)
|
||||
throw new Error('Missing peerId');
|
||||
|
|
@ -114,8 +114,8 @@ export default class RoomClient
|
|||
throw new Error('Missing device');
|
||||
|
||||
logger.debug(
|
||||
'constructor() [peerId: "%s", device: "%s", useSimulcast: "%s", produce: "%s", forceTcp: "%s"]',
|
||||
peerId, device.flag, useSimulcast, produce, forceTcp);
|
||||
'constructor() [peerId: "%s", device: "%s", useSimulcast: "%s", produce: "%s", forceTcp: "%s", displayName ""]',
|
||||
peerId, device.flag, useSimulcast, produce, forceTcp, displayName);
|
||||
|
||||
this._signalingUrl = null;
|
||||
|
||||
|
|
@ -128,6 +128,9 @@ export default class RoomClient
|
|||
// Wheter we force TCP
|
||||
this._forceTcp = forceTcp;
|
||||
|
||||
// Use displayName
|
||||
store.dispatch(settingsActions.setDisplayName(displayName));
|
||||
|
||||
// Torrent support
|
||||
this._torrentSupport = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ function run()
|
|||
const produce = parameters.get('produce') !== 'false';
|
||||
const useSimulcast = parameters.get('simulcast') === 'true';
|
||||
const forceTcp = parameters.get('forceTcp') === 'true';
|
||||
const displayName = parameters.get('displayName');
|
||||
|
||||
// Get current device.
|
||||
const device = deviceInfo();
|
||||
|
|
@ -114,7 +115,7 @@ function run()
|
|||
);
|
||||
|
||||
roomClient = new RoomClient(
|
||||
{ peerId, accessCode, device, useSimulcast, produce, forceTcp });
|
||||
{ peerId, accessCode, device, useSimulcast, produce, forceTcp, displayName });
|
||||
|
||||
global.CLIENT = roomClient;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,25 +2,37 @@ const os = require('os');
|
|||
|
||||
module.exports =
|
||||
{
|
||||
// oAuth2 conf
|
||||
/* auth :
|
||||
{
|
||||
|
||||
// Auth conf
|
||||
/*
|
||||
auth :
|
||||
{
|
||||
lti :
|
||||
{
|
||||
consumerKey : 'key',
|
||||
consumerSecret : 'secret'
|
||||
},
|
||||
oidc:
|
||||
{
|
||||
// The issuer URL for OpenID Connect discovery
|
||||
// The OpenID Provider Configuration Document
|
||||
// could be discovered on:
|
||||
// issuerURL + '/.well-known/openid-configuration'
|
||||
|
||||
// issuerURL : 'https://example.com',
|
||||
// clientOptions :
|
||||
// {
|
||||
issuerURL : 'https://example.com',
|
||||
clientOptions :
|
||||
{
|
||||
client_id : '',
|
||||
client_secret : '',
|
||||
scope : 'openid email profile',
|
||||
// where client.example.com is your multiparty meeting server
|
||||
redirect_uri : 'https://client.example.com/auth/callback'
|
||||
}
|
||||
},*/
|
||||
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
redisOptions: {}
|
||||
// session cookie secret
|
||||
cookieSecret : 'T0P-S3cR3t_cook!e',
|
||||
|
|
|
|||
|
|
@ -405,6 +405,27 @@ 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');
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@
|
|||
"express-session": "^1.17.0",
|
||||
"express-socket.io-session": "^1.3.5",
|
||||
"helmet": "^3.21.2",
|
||||
"ims-lti": "^3.0.2",
|
||||
"mediasoup": "^3.0.12",
|
||||
"openid-client": "^3.7.3",
|
||||
"passport": "^0.4.0",
|
||||
"passport-lti": "0.0.7",
|
||||
"redis": "^2.8.0",
|
||||
"socket.io": "^2.3.0",
|
||||
"spdy": "^4.0.1"
|
||||
|
|
|
|||
191
server/server.js
191
server/server.js
|
|
@ -17,14 +17,17 @@ const Room = require('./lib/Room');
|
|||
const Peer = require('./lib/Peer');
|
||||
const base64 = require('base-64');
|
||||
const helmet = require('helmet');
|
||||
|
||||
const {
|
||||
loginHelper,
|
||||
logoutHelper
|
||||
} = require('./httpHelper');
|
||||
// auth
|
||||
const passport = require('passport');
|
||||
const LTIStrategy = require('passport-lti');
|
||||
const imsLti = require('ims-lti');
|
||||
const redis = require('redis');
|
||||
const client = redis.createClient(config.redisOptions);
|
||||
const redisClient = redis.createClient(config.redisOptions);
|
||||
const { Issuer, Strategy } = require('openid-client');
|
||||
const expressSession = require('express-session');
|
||||
const RedisStore = require('connect-redis')(expressSession);
|
||||
|
|
@ -87,7 +90,7 @@ const session = expressSession({
|
|||
name : config.cookieName,
|
||||
resave : true,
|
||||
saveUninitialized : true,
|
||||
store : new RedisStore({ client }),
|
||||
store : new RedisStore({ client: redisClient }),
|
||||
cookie : {
|
||||
secure : true,
|
||||
httpOnly : true,
|
||||
|
|
@ -112,49 +115,26 @@ let io;
|
|||
let oidcClient;
|
||||
let oidcStrategy;
|
||||
|
||||
const auth = config.auth;
|
||||
|
||||
async function run()
|
||||
{
|
||||
if (
|
||||
typeof(auth) !== 'undefined' &&
|
||||
typeof(auth.issuerURL) !== 'undefined' &&
|
||||
typeof(auth.clientOptions) !== 'undefined'
|
||||
)
|
||||
if (typeof(config.auth) === 'undefined')
|
||||
{
|
||||
Issuer.discover(auth.issuerURL).then(async (oidcIssuer) =>
|
||||
{
|
||||
// Setup authentication
|
||||
await setupAuth(oidcIssuer);
|
||||
|
||||
// Run a mediasoup Worker.
|
||||
await runMediasoupWorkers();
|
||||
|
||||
// Run HTTPS server.
|
||||
await runHttpsServer();
|
||||
|
||||
// Run WebSocketServer.
|
||||
await runWebSocketServer();
|
||||
})
|
||||
.catch((err) =>
|
||||
{
|
||||
logger.error(err);
|
||||
});
|
||||
logger.warn('Auth is not configured properly!');
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error('Auth is not configure properly!');
|
||||
|
||||
// Run a mediasoup Worker.
|
||||
await runMediasoupWorkers();
|
||||
|
||||
// Run HTTPS server.
|
||||
await runHttpsServer();
|
||||
|
||||
// Run WebSocketServer.
|
||||
await runWebSocketServer();
|
||||
await setupAuth();
|
||||
}
|
||||
|
||||
// Run a mediasoup Worker.
|
||||
await runMediasoupWorkers();
|
||||
|
||||
// Run HTTPS server.
|
||||
await runHttpsServer();
|
||||
|
||||
// Run WebSocketServer.
|
||||
await runWebSocketServer();
|
||||
|
||||
// Log rooms status every 30 seconds.
|
||||
setInterval(() =>
|
||||
{
|
||||
|
|
@ -174,16 +154,67 @@ async function run()
|
|||
}, 10000);
|
||||
}
|
||||
|
||||
async function setupAuth(oidcIssuer)
|
||||
function setupLTI(ltiConfig)
|
||||
{
|
||||
oidcClient = new oidcIssuer.Client(auth.clientOptions);
|
||||
|
||||
// Add redis nonce store
|
||||
ltiConfig.nonceStore = new imsLti.Stores.RedisStore(ltiConfig.consumerKey, redisClient);
|
||||
ltiConfig.passReqToCallback= true;
|
||||
|
||||
const ltiStrategy = new LTIStrategy(
|
||||
ltiConfig,
|
||||
function(req, lti, done)
|
||||
{
|
||||
// LTI launch parameters
|
||||
if (lti)
|
||||
{
|
||||
const user = {};
|
||||
|
||||
if (lti.user_id && lti.custom_room)
|
||||
{
|
||||
user.id = lti.user_id;
|
||||
user._lti = lti;
|
||||
}
|
||||
|
||||
if (lti.custom_room)
|
||||
{
|
||||
user.room = lti.custom_room;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.room = '';
|
||||
}
|
||||
if (lti.lis_person_name_full)
|
||||
{
|
||||
user.displayName=lti.lis_person_name_full;
|
||||
}
|
||||
|
||||
// Perform local authentication if necessary
|
||||
return done(null, user);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return done('LTI error');
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
passport.use('lti', ltiStrategy);
|
||||
}
|
||||
|
||||
function setupOIDC(oidcIssuer)
|
||||
{
|
||||
|
||||
oidcClient = new oidcIssuer.Client(config.auth.oidc.clientOptions);
|
||||
|
||||
// ... any authorization request parameters go here
|
||||
// client_id defaults to client.client_id
|
||||
// redirect_uri defaults to client.redirect_uris[0]
|
||||
// response type defaults to client.response_types[0], then 'code'
|
||||
// scope defaults to 'openid'
|
||||
const params = auth.clientOptions;
|
||||
const params = config.auth.oidc.clientOptions;
|
||||
|
||||
// optional, defaults to false, when true req is passed as a first
|
||||
// argument to verify fn
|
||||
|
|
@ -257,6 +288,31 @@ async function setupAuth(oidcIssuer)
|
|||
|
||||
passport.use('oidc', oidcStrategy);
|
||||
|
||||
}
|
||||
|
||||
async function setupAuth()
|
||||
{
|
||||
// LTI
|
||||
if (
|
||||
typeof(config.auth.lti) !== 'undefined' &&
|
||||
typeof(config.auth.lti.consumerKey) !== 'undefined' &&
|
||||
typeof(config.auth.lti.consumerSecret) !== 'undefined'
|
||||
) setupLTI(config.auth.lti);
|
||||
|
||||
// OIDC
|
||||
if (
|
||||
typeof(config.auth.oidc) !== 'undefined' &&
|
||||
typeof(config.auth.oidc.issuerURL) !== 'undefined' &&
|
||||
typeof(config.auth.oidc.clientOptions) !== 'undefined'
|
||||
)
|
||||
{
|
||||
const oidcIssuer = await Issuer.discover(config.auth.oidc.issuerURL);
|
||||
|
||||
// Setup authentication
|
||||
setupOIDC(oidcIssuer);
|
||||
|
||||
}
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
|
|
@ -270,6 +326,15 @@ async function setupAuth(oidcIssuer)
|
|||
})(req, res, next);
|
||||
});
|
||||
|
||||
// lti launch
|
||||
app.post('/auth/lti',
|
||||
passport.authenticate('lti', { failureRedirect: '/' }),
|
||||
function(req, res)
|
||||
{
|
||||
res.redirect(`/${req.user.room}`);
|
||||
}
|
||||
);
|
||||
|
||||
// logout
|
||||
app.get('/auth/logout', (req, res) =>
|
||||
{
|
||||
|
|
@ -321,14 +386,31 @@ async function runHttpsServer()
|
|||
|
||||
app.use('/.well-known/acme-challenge', express.static('public/.well-known/acme-challenge'));
|
||||
|
||||
app.all('*', (req, res, next) =>
|
||||
app.all('*', async (req, res, next) =>
|
||||
{
|
||||
if (req.secure)
|
||||
{
|
||||
return next();
|
||||
}
|
||||
const ltiURL = new URL(`${req.protocol }://${ req.get('host') }${req.originalUrl}`);
|
||||
|
||||
if (
|
||||
req.isAuthenticated &&
|
||||
req.user &&
|
||||
req.user.displayName &&
|
||||
!ltiURL.searchParams.get('displayName') &&
|
||||
!isPathAlreadyTaken(req.url)
|
||||
)
|
||||
{
|
||||
|
||||
ltiURL.searchParams.append('displayName', req.user.displayName);
|
||||
|
||||
res.redirect(ltiURL);
|
||||
}
|
||||
else
|
||||
return next();
|
||||
}
|
||||
else
|
||||
res.redirect(`https://${req.hostname}${req.url}`);
|
||||
|
||||
res.redirect(`https://${req.hostname}${req.url}`);
|
||||
});
|
||||
|
||||
// Serve all files in the public folder as static files.
|
||||
|
|
@ -345,6 +427,27 @@ async function runHttpsServer()
|
|||
httpServer.listen(config.listeningRedirectPort);
|
||||
}
|
||||
|
||||
function isPathAlreadyTaken(url)
|
||||
{
|
||||
const alreadyTakenPath =
|
||||
[
|
||||
'/config/',
|
||||
'/static/',
|
||||
'/images/',
|
||||
'/sounds/',
|
||||
'/favicon.',
|
||||
'/auth/'
|
||||
];
|
||||
|
||||
alreadyTakenPath.forEach((path) =>
|
||||
{
|
||||
if (url.toString.startsWith(path))
|
||||
return true;
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a WebSocketServer to allow WebSocket connections from browsers.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue