Merge branch 'feature-oaut2' into develop

master
Stefan Otto 2018-06-08 13:49:51 +02:00
commit e105670fd9
10 changed files with 343 additions and 29 deletions

View File

@ -206,6 +206,7 @@ gulp.task('livebrowser', (done) =>
{
open : 'external',
host : config.domain,
port : 3000,
server :
{
baseDir : OUTPUT_DIR
@ -226,6 +227,7 @@ gulp.task('browser', (done) =>
{
open : 'external',
host : config.domain,
port : 3000,
server :
{
baseDir : OUTPUT_DIR

View File

@ -20,9 +20,9 @@ const ROOM_OPTIONS =
const VIDEO_CONSTRAINS =
{
qvga : { width: { ideal: 320 }, height: { ideal: 240 } },
vga : { width: { ideal: 640 }, height: { ideal: 480 } },
hd : { width: { ideal: 1280 }, height: { ideal: 720 } }
qvga : { width: { ideal: 320 }, height: { ideal: 240 }, aspectRatio: 1.334 },
vga : { width: { ideal: 640 }, height: { ideal: 480 }, aspectRatio: 1.334 },
hd : { width: { ideal: 800 }, height: { ideal: 600 }, aspectRatio: 1.334 }
};
export default class RoomClient
@ -37,6 +37,9 @@ export default class RoomClient
const protooUrl = getProtooUrl(peerName, roomId);
const protooTransport = new protooClient.WebSocketTransport(protooUrl);
// window element to external login site
this._loginWindow;
// Closed flag.
this._closed = false;
@ -60,6 +63,7 @@ export default class RoomClient
// mediasoup-client Room instance.
this._room = new mediasoupClient.Room(ROOM_OPTIONS);
this._room.roomId = roomId;
// Transport for sending.
this._sendTransport = null;
@ -111,6 +115,18 @@ export default class RoomClient
this._dispatch(stateActions.setRoomState('closed'));
}
login()
{
const url = `/login?roomId=${this._room.roomId}&peerName=${this._peerName}`;
this._loginWindow = window.open(url, 'loginWindow');
}
closeLoginWindow()
{
this._loginWindow.close();
}
changeDisplayName(displayName)
{
logger.debug('changeDisplayName() [displayName:"%s"]', displayName);
@ -750,6 +766,35 @@ export default class RoomClient
break;
}
// This means: server wants to change MY displayName
case 'auth':
{
logger.debug('got auth event from server', request.data);
accept();
if (request.data.verified == true)
{
this.changeDisplayName(request.data.name);
this._dispatch(requestActions.notify(
{
text : `Authenticated successfully: ${request.data}`
}
));
}
else
{
this._dispatch(requestActions.notify(
{
text : `Authentication failed: ${request.data}`
}
));
}
this.closeLoginWindow();
break;
}
case 'raisehand-message':
{
accept();

View File

@ -13,9 +13,8 @@ class Peers extends React.Component
{
super();
this.state = {
ratio : 4 / 3
ratio : 1.334
};
}
updateDimensions()
{

View File

@ -170,7 +170,7 @@
flex: 100 100 auto;
height: 100%;
width: 100%;
object-fit: cover;
object-fit: contain;
user-select: none;
transition-property: opacity;
transition-duration: .15s;

View File

@ -1,5 +1,18 @@
module.exports =
{
// oAuth2 conf
oauth2 :
{
client_id : '',
client_secret : '',
providerID : '',
redirect_uri : 'https://mYDomainName:port/auth-callback',
authorization_endpoint : '',
userinfo_endpoint : '',
token_endpoint : '',
scopes : { request : [ 'openid', 'userid','profile'] },
response_type : 'code'
},
// Listening hostname for `gulp live|open`.
domain : 'localhost',
tls :
@ -7,6 +20,8 @@ module.exports =
cert : `${__dirname}/certs/mediasoup-demo.localhost.cert.pem`,
key : `${__dirname}/certs/mediasoup-demo.localhost.key.pem`
},
// Listening port for https server.
listeningPort : 3443,
mediasoup :
{
// mediasoup Server settings.

View File

@ -0,0 +1,31 @@
'use strict';
const headers = {
"access-control-allow-origin": "*",
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
"access-control-allow-headers": "content-type, accept",
"access-control-max-age": 10,
"Content-Type": "application/json"
};
exports.prepareResponse = function(req, cb) {
var data = "";
req.on('data', function(chunk) { data += chunk; });
req.on('end', function() { cb(data); });
};
exports.respond = function(res, data, status) {
status = status || 200;
res.writeHead(status, headers);
res.end(data);
};
exports.send404 = function(res) {
exports.respond(res, 'Not Found', 404);
};
exports.redirector = function(res, loc, status) {
status = status || 302;
res.writeHead(status, { Location: loc });
res.end();
};

View File

@ -268,7 +268,6 @@ class Room extends EventEmitter
const { mediaPeer } = protooPeer.data;
mediaPeer.appData.raiseHand = request.data.raiseHandState;
// Spread to others via protoo.
this._protooRoom.spread(
'raisehand-message',

194
server/router.js 100644
View File

@ -0,0 +1,194 @@
'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 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
res.statusCode = 200;
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

@ -14,7 +14,9 @@ console.log('- config.mediasoup.logTags:', config.mediasoup.logTags);
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');
@ -77,25 +79,34 @@ mediaServer.on('newroom', (room) =>
});
});
// HTTPS server for the protoo WebSocket server.
// HTTPS server
const tls =
{
cert : fs.readFileSync(config.tls.cert),
key : fs.readFileSync(config.tls.key)
};
const httpsServer = https.createServer(tls, (req, res) =>
const httpsServer = https.createServer(tls, router.handleRequest);
httpsServer.listen(config.listeningPort, '0.0.0.0', () =>
{
res.writeHead(404, 'Not Here');
res.end();
logger.info('Server running, port: ',config.listeningPort);
});
httpsServer.listen(3443, '0.0.0.0', () =>
{
logger.info('protoo WebSocket server running');
});
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.
// Protoo WebSocket server listens to same webserver so everythink is available
// via same port
const webSocketServer = new protooServer.WebSocketServer(httpsServer,
{
maxReceivedFrameSize : 960000, // 960 KBytes.

18
server/util.js 100644
View File

@ -0,0 +1,18 @@
'use strict';
var crypto = require('crypto');
exports.random = function (howMany, chars) {
chars = chars
|| "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
var rnd = crypto.randomBytes(howMany)
, value = new Array(howMany)
, len = len = Math.min(256, chars.length)
, d = 256 / len
for (var i = 0; i < howMany; i++) {
value[i] = chars[Math.floor(rnd[i] / d)]
};
return value.join('');
}