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