Use Express instead of custom server, passport for auth and refactor server

master
Torjus 2018-07-23 12:09:15 +02:00
parent 4431a40297
commit f44bde4ef4
6 changed files with 2947 additions and 3200 deletions

View File

@ -1079,8 +1079,6 @@ export default class RoomClient
logger.debug('got auth event from server', request.data);
accept();
if (request.data.verified == true)
{
this.changeDisplayName(request.data.name);
this.changeProfilePicture(request.data.picture);
@ -1092,20 +1090,6 @@ export default class RoomClient
}
));
}
else
{
this._dispatch(requestActions.notify(
{
text : `Authentication failed: ${request.data}`
}
));
}
this.closeLoginWindow();
this._dispatch(stateActions.setLoginInProgress(false));
break;
}
case 'raisehand-message':
{

319
server/mediasoup.js 100644
View File

@ -0,0 +1,319 @@
const mediasoup = require('mediasoup');
const readline = require('readline');
const colors = require('colors/safe');
const repl = require('repl');
const homer = require('./lib/homer');
const config = require('./config');
// mediasoup server.
const mediaServer = mediasoup.Server(
{
numWorkers : 1,
logLevel : config.mediasoup.logLevel,
logTags : config.mediasoup.logTags,
rtcIPv4 : config.mediasoup.rtcIPv4,
rtcIPv6 : config.mediasoup.rtcIPv6,
rtcAnnouncedIPv4 : config.mediasoup.rtcAnnouncedIPv4,
rtcAnnouncedIPv6 : config.mediasoup.rtcAnnouncedIPv6,
rtcMinPort : config.mediasoup.rtcMinPort,
rtcMaxPort : config.mediasoup.rtcMaxPort
});
// Do Homer stuff.
if (process.env.MEDIASOUP_HOMER_OUTPUT)
homer(mediaServer);
global.SERVER = mediaServer;
mediaServer.on('newroom', (room) =>
{
global.ROOM = room;
room.on('newpeer', (peer) =>
{
global.PEER = peer;
if (peer.consumers.length > 0)
global.CONSUMER = peer.consumers[peer.consumers.length - 1];
peer.on('newtransport', (transport) =>
{
global.TRANSPORT = transport;
});
peer.on('newproducer', (producer) =>
{
global.PRODUCER = producer;
});
peer.on('newconsumer', (consumer) =>
{
global.CONSUMER = consumer;
});
});
});
// Listen for keyboard input.
let cmd;
let terminal;
openCommandConsole();
function openCommandConsole()
{
stdinLog('[opening Readline Command Console...]');
closeCommandConsole();
closeTerminal();
cmd = readline.createInterface(
{
input : process.stdin,
output : process.stdout
});
cmd.on('SIGINT', () =>
{
process.exit();
});
readStdin();
function readStdin()
{
cmd.question('cmd> ', (answer) =>
{
switch (answer)
{
case '':
{
readStdin();
break;
}
case 'h':
case 'help':
{
stdinLog('');
stdinLog('available commands:');
stdinLog('- h, help : show this message');
stdinLog('- sd, serverdump : execute server.dump()');
stdinLog('- rd, roomdump : execute room.dump() for the latest created mediasoup Room');
stdinLog('- pd, peerdump : execute peer.dump() for the latest created mediasoup Peer');
stdinLog('- td, transportdump : execute transport.dump() for the latest created mediasoup Transport');
stdinLog('- prd, producerdump : execute producer.dump() for the latest created mediasoup Producer');
stdinLog('- cd, consumerdump : execute consumer.dump() for the latest created mediasoup Consumer');
stdinLog('- t, terminal : open REPL Terminal');
stdinLog('');
readStdin();
break;
}
case 'sd':
case 'serverdump':
{
mediaServer.dump()
.then((data) =>
{
stdinLog(`server.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`mediaServer.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'rd':
case 'roomdump':
{
if (!global.ROOM)
{
readStdin();
break;
}
global.ROOM.dump()
.then((data) =>
{
stdinLog(`room.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`room.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'pd':
case 'peerdump':
{
if (!global.PEER)
{
readStdin();
break;
}
global.PEER.dump()
.then((data) =>
{
stdinLog(`peer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`peer.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'td':
case 'transportdump':
{
if (!global.TRANSPORT)
{
readStdin();
break;
}
global.TRANSPORT.dump()
.then((data) =>
{
stdinLog(`transport.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`transport.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'prd':
case 'producerdump':
{
if (!global.PRODUCER)
{
readStdin();
break;
}
global.PRODUCER.dump()
.then((data) =>
{
stdinLog(`producer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`producer.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'cd':
case 'consumerdump':
{
if (!global.CONSUMER)
{
readStdin();
break;
}
global.CONSUMER.dump()
.then((data) =>
{
stdinLog(`consumer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`consumer.dump() failed: ${error}`);
readStdin();
});
break;
}
case 't':
case 'terminal':
{
openTerminal();
break;
}
default:
{
stdinError(`unknown command: ${answer}`);
stdinLog('press \'h\' or \'help\' to get the list of available commands');
readStdin();
}
}
});
}
}
function openTerminal()
{
stdinLog('[opening REPL Terminal...]');
closeCommandConsole();
closeTerminal();
terminal = repl.start(
{
prompt : 'terminal> ',
useColors : true,
useGlobal : true,
ignoreUndefined : false
});
terminal.on('exit', () => openCommandConsole());
}
function closeCommandConsole()
{
if (cmd)
{
cmd.close();
cmd = undefined;
}
}
function closeTerminal()
{
if (terminal)
{
terminal.removeAllListeners('exit');
terminal.close();
terminal = undefined;
}
}
function stdinLog(msg)
{
// eslint-disable-next-line no-console
console.log(colors.green(msg));
}
function stdinError(msg)
{
// eslint-disable-next-line no-console
console.error(colors.red.bold('ERROR: ') + colors.red(msg));
}

5200
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,8 +9,9 @@
"dependencies": {
"colors": "^1.1.2",
"debug": "^3.1.0",
"express": "^4.16.2",
"express": "^4.16.3",
"mediasoup": "^2.1.0",
"passport-dataporten": "^1.3.0",
"protoo-server": "^2.0.7"
},
"devDependencies": {

View File

@ -1,196 +0,0 @@
'use strict';
const EventEmitter = require( 'events' );
const eventEmitter = new EventEmitter();
const path = require('path');
const url = require('url');
const httpHelpers = require('./http-helpers');
const fs = require('fs');
const config = require('./config');
const utils = require('./util');
const querystring = require('querystring');
const https = require('https')
const Logger = require('./lib/Logger');
const logger = new Logger();
let authRequests = {}; // ongoing auth requests :
/*
{
state:
{
peerName:'peerName'
code:'oauth2 code',
roomId: 'romid',
}
}
*/
const actions = {
'GET': function(req, res) {
var parsedUrl = url.parse(req.url,true);
if ( parsedUrl.pathname === '/auth-callback' )
{
if ( typeof(authRequests[parsedUrl.query.state]) != 'undefined' )
{
console.log('got authorization code for access token: ',parsedUrl.query,authRequests[parsedUrl.query.state]);
const auth = "Basic " + new Buffer(config.oauth2.client_id + ":" + config.oauth2.client_secret).toString("base64");
const postUrl = url.parse(config.oauth2.token_endpoint);
let postData = querystring.stringify({
"grant_type":"authorization_code",
"code":parsedUrl.query.code,
"redirect_uri":config.oauth2.redirect_uri
});
let request = https.request( {
host : postUrl.hostname,
path : postUrl.pathname,
port : postUrl.port,
method : 'POST',
headers :
{
'Content-Type' : 'application/x-www-form-urlencoded',
'Authorization' : auth,
'Content-Length': Buffer.byteLength(postData)
}
}, function(res)
{
res.setEncoding("utf8");
let body = "";
res.on("data", data => {
body += data;
});
res.on("end", () => {
if ( res.statusCode == 200 )
{
console.log('We\'ve got an access token!', body);
body = JSON.parse(body);
authRequests[parsedUrl.query.state].access_token =
body.access_token;
const auth = "Bearer " + body.access_token;
const getUrl = url.parse(config.oauth2.userinfo_endpoint);
let request = https.request( {
host : getUrl.hostname,
path : getUrl.pathname,
port : getUrl.port,
method : 'GET',
headers :
{
'Authorization' : auth,
}
}, function(res)
{
res.setEncoding("utf8");
let body = '';
res.on("data", data => {
body += data;
});
res.on("end", () => {
// we don't need this any longer:
delete authRequests[parsedUrl.query.state].access_token;
body = JSON.parse(body);
console.log(body);
if ( res.statusCode == 200 )
{
authRequests[parsedUrl.query.state].verified = true;
if ( typeof(body.sub) != 'undefined')
{
authRequests[parsedUrl.query.state].sub = body.sub;
}
if ( typeof(body.name) != 'undefined')
{
authRequests[parsedUrl.query.state].name = body.name;
}
if ( typeof(body.picture) != 'undefined')
{
authRequests[parsedUrl.query.state].picture = body.picture;
}
} else {
{
authRequests[parsedUrl.query.state].verified = false;
}
}
eventEmitter.emit('auth',
authRequests[parsedUrl.query.state]);
delete authRequests[parsedUrl.query.state];
});
});
request.write(' ');
request.end;
}
else
{
console.log('access_token denied',body);
authRequests[parsedUrl.query.state].verified = false;
delete authRequests[parsedUrl.query.state].access_token;
eventEmitter.emit('auth',
authRequests[parsedUrl.query.state]);
}
});
});
request.write(postData);
request.end;
}
else
{
logger.warn('Got authorization_code for unseen state:', parsedUrl)
}
}
else if (parsedUrl.pathname === '/login') {
const state = utils.random(10);
httpHelpers.redirector(res, config.oauth2.authorization_endpoint
+ '?client_id=' + config.oauth2.client_id
+ '&redirect_uri=' + config.oauth2.redirect_uri
+ '&state=' + state
+ '&scopes=' + config.oauth2.scopes.request.join('+')
+ '&response_type=' + config.oauth2.response_type);
authRequests[state] =
{
'roomId' : parsedUrl.query.roomId,
'peerName' : parsedUrl.query.peerName
};
console.log('Started authorization process: ', parsedUrl.query);
}
else
{
console.log('requested url:', parsedUrl.pathname);
var resolvedBase = path.resolve('./public');
var safeSuffix = path.normalize(req.url).replace(/^(\.\.[\/\\])+/, '');
var fileLoc = path.join(resolvedBase, safeSuffix);
var headers = {};
var stream = fs.createReadStream(fileLoc);
// Handle non-existent file -> delivering index.html
stream.on('error', function(error) {
stream = fs.createReadStream(path.resolve('./public/index.html'));
res.statusCode = 200;
stream.pipe(res);
});
// File exists, stream it to user
if (parsedUrl.pathname.indexOf('svg') === parsedUrl.pathname.length -3) {headers = {'Content-Type': 'image/svg+xml'}};
res.writeHead(200, headers);
stream.pipe(res);
}
},
'POST': function(req, res) {
httpHelpers.prepareResponse(req, function(data) {
// Do something with the data that was just collected by the helper
// e.g., validate and save to db
// either redirect or respond
// should be based on result of the operation performed in response to the POST request intent
// e.g., if user wants to save, and save fails, throw error
httpHelpers.redirector(res, /* redirect path , optional status code - defaults to 302 */);
});
}
};
module.exports = eventEmitter;
module.exports.handleRequest = function(req, res) {
var action = actions[req.method];
action ? action(req, res) : httpHelpers.send404(res);
};

View File

@ -5,6 +5,14 @@
process.title = 'multiparty-meeting-server';
const config = require('./config');
const fs = require('fs');
const https = require('https');
const express = require('express');
const url = require('url');
const protooServer = require('protoo-server');
const Logger = require('./lib/Logger');
const Room = require('./lib/Room');
const Dataporten = require('passport-dataporten');
/* eslint-disable no-console */
console.log('- process.env.DEBUG:', process.env.DEBUG);
@ -12,100 +20,66 @@ console.log('- config.mediasoup.logLevel:', config.mediasoup.logLevel);
console.log('- config.mediasoup.logTags:', config.mediasoup.logTags);
/* eslint-enable no-console */
const fs = require('fs');
const https = require('https');
const router = require('./router');
const url = require('url');
const path = require('path');
const protooServer = require('protoo-server');
const mediasoup = require('mediasoup');
const readline = require('readline');
const colors = require('colors/safe');
const repl = require('repl');
const Logger = require('./lib/Logger');
const Room = require('./lib/Room');
const homer = require('./lib/homer');
// Start the mediasoup server.
require('./mediasoup');
const logger = new Logger();
// Map of Room instances indexed by roomId.
const rooms = new Map();
// mediasoup server.
const mediaServer = mediasoup.Server(
{
numWorkers : 1,
logLevel : config.mediasoup.logLevel,
logTags : config.mediasoup.logTags,
rtcIPv4 : config.mediasoup.rtcIPv4,
rtcIPv6 : config.mediasoup.rtcIPv6,
rtcAnnouncedIPv4 : config.mediasoup.rtcAnnouncedIPv4,
rtcAnnouncedIPv6 : config.mediasoup.rtcAnnouncedIPv6,
rtcMinPort : config.mediasoup.rtcMinPort,
rtcMaxPort : config.mediasoup.rtcMaxPort
});
// Do Homer stuff.
if (process.env.MEDIASOUP_HOMER_OUTPUT)
homer(mediaServer);
global.SERVER = mediaServer;
mediaServer.on('newroom', (room) =>
{
global.ROOM = room;
room.on('newpeer', (peer) =>
{
global.PEER = peer;
if (peer.consumers.length > 0)
global.CONSUMER = peer.consumers[peer.consumers.length - 1];
peer.on('newtransport', (transport) =>
{
global.TRANSPORT = transport;
});
peer.on('newproducer', (producer) =>
{
global.PRODUCER = producer;
});
peer.on('newconsumer', (consumer) =>
{
global.CONSUMER = consumer;
});
});
});
// HTTPS server
// TLS server configuration.
const tls =
{
cert : fs.readFileSync(config.tls.cert),
key : fs.readFileSync(config.tls.key)
};
const httpsServer = https.createServer(tls, router.handleRequest);
const app = express();
const dataporten = new Dataporten.Setup(config.oauth2);
app.use(dataporten.passport.initialize());
app.use(dataporten.passport.session());
dataporten.setupAuthenticate(app, '/login');
dataporten.setupLogout(app, '/logout');
dataporten.setupCallback(app);
app.get(
'/auth-callback',
dataporten.passport.authenticate('dataporten', { failureRedirect: '/login' }),
(req, res) =>
{
res.redirect(req.session.redirectToAfterLogin || '/');
if (rooms.has(req.query.roomId))
{
const room = rooms.get(req.query.roomId)._protooRoom;
if ( room.hasPeer(req.query.peerName) )
{
const peer = room.getPeer(req.query.peerName);
peer.send('auth', {
name: req.user.displayName,
picture: req.user.photos[0]
});
}
}
}
)
// Serve all files in the public folder as static files.
app.use(express.static('public'));
const httpsServer = https.createServer(tls, app);
httpsServer.listen(config.listeningPort, '0.0.0.0', () =>
{
logger.info('Server running, port: ',config.listeningPort);
logger.info('Server running on port: ', config.listeningPort);
});
router.on('auth',function(event){
console.log('router: Got an event: ',event)
if ( rooms.has(event.roomId) )
{
const room = rooms.get(event.roomId)._protooRoom;
if ( room.hasPeer(event.peerName) )
{
const peer = room.getPeer(event.peerName);
peer.send('auth', event)
}
}
})
// Protoo WebSocket server listens to same webserver so everythink is available
// Protoo WebSocket server listens to same webserver so everything is available
// via same port
const webSocketServer = new protooServer.WebSocketServer(httpsServer,
{
@ -179,268 +153,3 @@ webSocketServer.on('connectionrequest', (info, accept, reject) =>
room.handleConnection(peerName, transport);
});
// Listen for keyboard input.
let cmd;
let terminal;
openCommandConsole();
function openCommandConsole()
{
stdinLog('[opening Readline Command Console...]');
closeCommandConsole();
closeTerminal();
cmd = readline.createInterface(
{
input : process.stdin,
output : process.stdout
});
cmd.on('SIGINT', () =>
{
process.exit();
});
readStdin();
function readStdin()
{
cmd.question('cmd> ', (answer) =>
{
switch (answer)
{
case '':
{
readStdin();
break;
}
case 'h':
case 'help':
{
stdinLog('');
stdinLog('available commands:');
stdinLog('- h, help : show this message');
stdinLog('- sd, serverdump : execute server.dump()');
stdinLog('- rd, roomdump : execute room.dump() for the latest created mediasoup Room');
stdinLog('- pd, peerdump : execute peer.dump() for the latest created mediasoup Peer');
stdinLog('- td, transportdump : execute transport.dump() for the latest created mediasoup Transport');
stdinLog('- prd, producerdump : execute producer.dump() for the latest created mediasoup Producer');
stdinLog('- cd, consumerdump : execute consumer.dump() for the latest created mediasoup Consumer');
stdinLog('- t, terminal : open REPL Terminal');
stdinLog('');
readStdin();
break;
}
case 'sd':
case 'serverdump':
{
mediaServer.dump()
.then((data) =>
{
stdinLog(`server.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`mediaServer.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'rd':
case 'roomdump':
{
if (!global.ROOM)
{
readStdin();
break;
}
global.ROOM.dump()
.then((data) =>
{
stdinLog(`room.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`room.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'pd':
case 'peerdump':
{
if (!global.PEER)
{
readStdin();
break;
}
global.PEER.dump()
.then((data) =>
{
stdinLog(`peer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`peer.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'td':
case 'transportdump':
{
if (!global.TRANSPORT)
{
readStdin();
break;
}
global.TRANSPORT.dump()
.then((data) =>
{
stdinLog(`transport.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`transport.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'prd':
case 'producerdump':
{
if (!global.PRODUCER)
{
readStdin();
break;
}
global.PRODUCER.dump()
.then((data) =>
{
stdinLog(`producer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`producer.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'cd':
case 'consumerdump':
{
if (!global.CONSUMER)
{
readStdin();
break;
}
global.CONSUMER.dump()
.then((data) =>
{
stdinLog(`consumer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`consumer.dump() failed: ${error}`);
readStdin();
});
break;
}
case 't':
case 'terminal':
{
openTerminal();
break;
}
default:
{
stdinError(`unknown command: ${answer}`);
stdinLog('press \'h\' or \'help\' to get the list of available commands');
readStdin();
}
}
});
}
}
function openTerminal()
{
stdinLog('[opening REPL Terminal...]');
closeCommandConsole();
closeTerminal();
terminal = repl.start(
{
prompt : 'terminal> ',
useColors : true,
useGlobal : true,
ignoreUndefined : false
});
terminal.on('exit', () => openCommandConsole());
}
function closeCommandConsole()
{
if (cmd)
{
cmd.close();
cmd = undefined;
}
}
function closeTerminal()
{
if (terminal)
{
terminal.removeAllListeners('exit');
terminal.close();
terminal = undefined;
}
}
function stdinLog(msg)
{
// eslint-disable-next-line no-console
console.log(colors.green(msg));
}
function stdinError(msg)
{
// eslint-disable-next-line no-console
console.error(colors.red.bold('ERROR: ') + colors.red(msg));
}