[mirotalk] - #209 add JWT, update dep

This commit is contained in:
Miroslav Pejic
2024-02-11 13:12:30 +01:00
parent 41373591c9
commit ceb459b98f
11 changed files with 167 additions and 84 deletions
+5 -3
View File
@@ -10,7 +10,6 @@ HOST=localhost
# Access to the instance is restricted to only the specified IP addresses in the allowed list. This feature is disabled by default.
IP_WHITELIST_ENABLED=false # true or false
IP_WHITELIST_ALLOWED='["127.0.0.1", "::1"]'
# Host protection
@@ -19,11 +18,14 @@ IP_WHITELIST_ALLOWED='["127.0.0.1", "::1"]'
# HOST_USERS: This is the list of valid host users along with their credentials.
HOST_PROTECTED=false # true or false
HOST_USER_AUTH=false # true or false
HOST_USERS='[{"username": "username", "password": "password"},{"username": "username2", "password": "password2"}]'
# JWT token config
JWT_KEY=mirotalk_jwt_secret
JWT_EXP=1h
# Presenters list
# In our virtual room, the first participant to join will assume the role of the presenter.
# Additionally, we have the option to include more presenters and co-presenters, each identified by their username.
+15 -11
View File
@@ -36,6 +36,7 @@
- Host protection to prevent unauthorized access.
- User auth to prevent unauthorized access.
- Room password protection.
- JWT.io securely manages credentials for host configurations and user authentication, enhancing security and streamlining processes.
- Compatible with desktop and mobile devices.
- Optimized mobile room URL sharing.
- Webcam streaming with front and rear camera support for mobile devices.
@@ -95,17 +96,16 @@
- https://p2p.mirotalk.com/join?room=test&name=mirotalk&audio=0&video=0&screen=0&hide=0&notify=0
- https://mirotalk.up.railway.app/join?room=test&name=mirotalk&audio=0&video=0&screen=0&hide=0&notify=0
| Params | Type | Description |
| -------- | ------- | --------------- |
| room | string | Room Id |
| name | string | User name |
| audio | boolean | Audio stream |
| video | boolean | Video stream |
| screen | boolean | Screen stream |
| hide | boolean | Hide myself |
| notify | boolean | Welcome message |
| username | string | auth username |
| password | string | auth password |
| Params | Type | Description |
| ------ | ------- | --------------- |
| room | string | Room Id |
| name | string | User name |
| audio | boolean | Audio stream |
| video | boolean | Video stream |
| screen | boolean | Screen stream |
| hide | boolean | Hide myself |
| notify | boolean | Welcome message |
| token | string | jwt token |
> **Note**
>
@@ -224,6 +224,10 @@ $ docker-compose down
$ curl -X POST "http://localhost:3000/api/v1/join" -H "authorization: mirotalk_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"mirotalk","audio":"true","video":"true","screen":"false","hide":"false","notify":"true"}'
$ curl -X POST "https://p2p.mirotalk.com/api/v1/join" -H "authorization: mirotalk_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"mirotalk","audio":"true","video":"true","screen":"false","hide":"false","notify":"true"}'
$ curl -X POST "https://mirotalk.up.railway.app/api/v1/join" -H "authorization: mirotalk_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"mirotalk","audio":"true","video":"true","screen":"false","hide":"false","notify":"true"}'
# The response will give you an entry point/URL for direct joining to the meeting with a token.
$ curl -X POST "http://localhost:3000/api/v1/join" -H "authorization: mirotalk_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"mirotalk","audio":"true","video":"true","screen":"false","hide":"false","notify":"true","token":{"username":"username","password":"password","presenter":"true", "expire":"1h"}}'
$ curl -X POST "https://p2p.mirotalk.com/api/v1/join" -H "authorization: mirotalk_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"mirotalk","audio":"true","video":"true","screen":"false","hide":"false","notify":"true","token":{"username":"username","password":"password","presenter":"true", "expire":"1h"}}'
$ curl -X POST "https://mirotalk.up.railway.app/api/v1/join" -H "authorization: mirotalk_default_secret" -H "Content-Type: application/json" --data '{"room":"test","name":"mirotalk","audio":"true","video":"true","screen":"false","hide":"false","notify":"true","token":{"username":"username","password":"password","presenter":"true", "expire":"1h"}}'
```
</details>
+6
View File
@@ -26,6 +26,12 @@ async function getJoin() {
screen: false,
hide: false,
notify: true,
token: {
username: 'username',
password: 'password',
presenter: true,
expire: '1h',
},
}),
});
const data = await response.json();
+6
View File
@@ -25,6 +25,12 @@ $data = array(
"screen" => false,
"hide" => false,
"notify" => true,
"token" => array(
"username" => "username",
"password" => "password",
"presenter" => true,
"expire" => "1h",
),
);
$data_string = json_encode($data);
+6
View File
@@ -20,6 +20,12 @@ data = {
"screen": "false",
"hide": "false",
"notify": "true",
"token": {
"username": "username",
"password": "password",
"presenter": "true",
"expire": "1h",
}
}
response = requests.post(
+1 -1
View File
@@ -8,5 +8,5 @@ MIROTALK_URL="https://p2p.mirotalk.com/api/v1/join"
curl $MIROTALK_URL \
--header "authorization: $API_KEY_SECRET" \
--header "Content-Type: application/json" \
--data '{"room":"test","name":"mirotalk","audio":"true","video":"true","screen":"false","hide":"false","notify":"true"}' \
--data '{"room":"test","name":"mirotalk","audio":"true","video":"true","screen":"false","hide":"false","notify":"true","token":{"username":"username","password":"password","presenter":"true", "expire":"1h"}}' \
--request POST
+25 -7
View File
@@ -1,7 +1,11 @@
'use strict';
const jwt = require('jsonwebtoken');
const { v4: uuidV4 } = require('uuid');
const JWT_KEY = process.env.JWT_KEY || 'mirotalk_jwt_secret';
const JWT_EXP = process.env.JWT_EXP || '1h';
module.exports = class ServerApi {
constructor(host = null, authorization = null, api_key_secret = null) {
this._host = host;
@@ -19,23 +23,37 @@ module.exports = class ServerApi {
}
getJoinURL(data) {
// Get data...
const { room, name, audio, video, screen, hide, notify, token } = data;
let jwtToken = '';
if (token) {
const { username, password, presenter, expire } = token;
jwtToken =
'&token=' +
jwt.sign({ username: username, password: password, presenter: presenter }, JWT_KEY, {
expiresIn: expire ? expire : JWT_EXP,
});
}
return (
this.getProtocol() +
this._host +
'/join?room=' +
data.room +
room +
'&name=' +
data.name +
name +
'&audio=' +
data.audio +
audio +
'&video=' +
data.video +
video +
'&screen=' +
data.screen +
screen +
'&hide=' +
data.hide +
hide +
'&notify=' +
data.notify
notify +
jwtToken
);
}
+76 -23
View File
@@ -18,6 +18,7 @@ dependencies: {
crypto-js : https://www.npmjs.com/package/crypto-js
dotenv : https://www.npmjs.com/package/dotenv
express : https://www.npmjs.com/package/express
jsonwebtoken : https://www.npmjs.com/package/jsonwebtoken
ngrok : https://www.npmjs.com/package/ngrok
qs : https://www.npmjs.com/package/qs
openai : https://www.npmjs.com/package/openai
@@ -38,7 +39,7 @@ dependencies: {
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.2.84
* @version 1.2.85
*
*/
@@ -54,6 +55,7 @@ const express = require('express');
const cors = require('cors');
const path = require('path');
const axios = require('axios');
const jwt = require('jsonwebtoken');
const app = express();
const checkXSS = require('./xss.js');
const ServerApi = require('./api');
@@ -102,6 +104,12 @@ const hostCfg = {
authenticated: !hostProtected,
};
// JWT config
const jwtCfg = {
JWT_KEY: process.env.JWT_KEY || 'mirotalk_jwt_secret',
JWT_EXP: process.env.JWT_EXP || '1h',
};
// Room presenters
const roomPresentersString = process.env.PRESENTERS || '["MiroTalk P2P"]';
const roomPresenters = JSON.parse(roomPresentersString);
@@ -355,24 +363,43 @@ app.get('/join/', (req, res) => {
if (Object.keys(req.query).length > 0) {
log.debug('Request Query', req.query);
/*
http://localhost:3000/join?room=test&name=mirotalk&audio=1&video=1&screen=0&notify=0&hide=1&username=username&password=password
http://localhost:3000/join?room=test&name=mirotalk&audio=1&video=1&screen=0&notify=0&hide=1&token=token
https://p2p.mirotalk.com/join?room=test&name=mirotalk&audio=1&video=1&screen=0&notify=0&hide=0
https://mirotalk.up.railway.app/join?room=test&name=mirotalk&audio=1&video=1&screen=0&notify=0&hide=0
*/
const { room, name, audio, video, screen, notify, hide, username, password } = checkXSS(req.query);
const { room, name, audio, video, screen, notify, hide, token } = checkXSS(req.query);
// check if valid peer
const isPeerValid = isAuthPeer(username, password);
let peerUsername,
peerPassword = '';
let isPeerValid = false;
let isPeerPresenter = false;
if (token) {
try {
const { username, password, presenter } = checkXSS(jwt.verify(token, jwtCfg.JWT_KEY));
// Peer credentials
peerUsername = username;
peerPassword = password;
// Check if valid peer
isPeerValid = isAuthPeer(username, password);
// Check if presenter
isPeerPresenter = presenter === '1' || presenter === 'true';
} catch (err) {
// Invalid token
log.error('Direct Join JWT error', err.message);
return hostCfg.protected || hostCfg.user_auth ? res.sendFile(views.login) : res.sendFile(views.landing);
}
}
// Peer valid going to auth as host
if (hostCfg.protected && isPeerValid && !hostCfg.authenticated) {
if (hostCfg.protected && isPeerValid && isPeerPresenter && !hostCfg.authenticated) {
const ip = getIP(req);
hostCfg.authenticated = true;
authHost = new Host(ip, true);
log.debug('Direct Join user auth as host done', {
ip: ip,
username: username,
password: password,
username: peerUsername,
password: peerPassword,
});
}
@@ -442,13 +469,19 @@ app.post(['/login'], (req, res) => {
hostCfg.authenticated = true;
authHost = new Host(ip, true);
log.debug('HOST LOGIN OK', { ip: ip, authorized: authHost.isAuthorized(ip) });
return res.status(200).json({ message: 'authorized' });
const token = jwt.sign({ username: username, password: password, presenter: true }, jwtCfg.JWT_KEY, {
expiresIn: jwtCfg.JWT_EXP,
});
return res.status(200).json({ message: token });
}
// Peer auth valid
if (isPeerValid) {
log.debug('PEER LOGIN OK', { ip: ip, authorized: true });
return res.status(200).json({ message: 'authorized' });
const token = jwt.sign({ username: username, password: password, presenter: false }, jwtCfg.JWT_KEY, {
expiresIn: jwtCfg.JWT_EXP,
});
return res.status(200).json({ message: token });
} else {
return res.status(401).json({ message: 'unauthorized' });
}
@@ -573,6 +606,7 @@ async function ngrokStart() {
iceServers: iceServers,
stats: statsData,
host: hostCfg,
jwtCfg: jwtCfg,
presenters: roomPresenters,
ip_whitelist: ipWhitelist,
ngrok: {
@@ -630,6 +664,7 @@ server.listen(port, null, () => {
iceServers: iceServers,
stats: statsData,
host: hostCfg,
jwtCfg: jwtCfg,
presenters: roomPresenters,
ip_whitelist: ipWhitelist,
server: host,
@@ -784,8 +819,7 @@ io.sockets.on('connect', async (socket) => {
channel_password,
peer_uuid,
peer_name,
peer_username,
peer_password,
peer_token,
peer_video,
peer_audio,
peer_video_status,
@@ -808,18 +842,37 @@ io.sockets.on('connect', async (socket) => {
// no presenter aka host in presenters init
if (!(channel in presenters)) presenters[channel] = {};
let is_presenter = true;
// User Auth required, we check if peer valid
if (hostCfg.user_auth) {
const isPeerValid = isAuthPeer(peer_username, peer_password);
// Check JWT
if (peer_token) {
try {
const { username, password, presenter } = checkXSS(jwt.verify(peer_token, jwtCfg.JWT_KEY));
log.debug('[' + socket.id + '] JOIN ROOM - HOST PROTECTED - USER AUTH check peer', {
ip: peer_ip,
peer_username: peer_username,
peer_password: peer_password,
peer_valid: isPeerValid,
});
const isPeerValid = isAuthPeer(username, password);
if (!isPeerValid) {
is_presenter = presenter === '1' || presenter === 'true';
log.debug('[' + socket.id + '] JOIN ROOM - USER AUTH check peer', {
ip: peer_ip,
peer_username: username,
peer_password: password,
peer_valid: isPeerValid,
peer_presenter: is_presenter,
});
if (!isPeerValid) {
// redirect peer to login page
return socket.emit('unauthorized');
}
} catch (err) {
// redirect peer to login page
log.error('[' + socket.id + '] [Warning] Join Room JWT error', err.message);
return socket.emit('unauthorized');
}
} else {
// redirect peer to login page
return socket.emit('unauthorized');
}
@@ -836,7 +889,7 @@ io.sockets.on('connect', async (socket) => {
peer_ip: peer_ip,
peer_name: peer_name,
peer_uuid: peer_uuid,
is_presenter: true,
is_presenter: is_presenter,
};
// first we check if the username match the presenters username
if (roomPresenters && roomPresenters.includes(peer_name)) {
@@ -848,8 +901,8 @@ io.sockets.on('connect', async (socket) => {
}
}
// Check if peer is presenter
const isPresenter = await isPeerPresenter(channel, socket.id, peer_name, peer_uuid);
// Check if peer is presenter, if token check the presenter key
const isPresenter = peer_token ? is_presenter : await isPeerPresenter(channel, socket.id, peer_name, peer_uuid);
// collect peers info grp by channels
peers[channel][socket.id] = {
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "mirotalk",
"version": "1.2.84",
"version": "1.2.85",
"description": "A free WebRTC browser-based video call",
"main": "server.js",
"scripts": {
@@ -45,10 +45,11 @@
"compression": "^1.7.4",
"cors": "^2.8.5",
"crypto-js": "^4.2.0",
"dotenv": "^16.4.1",
"dotenv": "^16.4.2",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"ngrok": "^4.3.3",
"openai": "^4.26.1",
"openai": "^4.27.0",
"qs": "^6.11.2",
"socket.io": "^4.7.4",
"swagger-ui-express": "^5.0.0",
+15 -33
View File
@@ -15,7 +15,7 @@
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.2.84
* @version 1.2.85
*
*/
@@ -562,8 +562,7 @@ let isDocumentOnFullScreen = false;
let myPeerId; // This socket.id
let myPeerUUID = getUUID(); // Unique peer id
let myPeerName = getPeerName();
let myUsername = getPeerUsername(); // default false if not passed by query params
let myPassword = getPeerPassword(); // default false if not passed by query params
let myToken = getPeerToken(); // peer JWT
let isPresenter = false; // True Who init the room (aka first peer joined)
let myHandStatus = false;
let myVideoStatus = false;
@@ -972,35 +971,19 @@ function getNotify() {
}
/**
* Get Peer username
* @returns {mixed} boolean false or username string
* Get Peer JWT
* @returns {mixed} boolean false or token string
*/
function getPeerUsername() {
if (window.sessionStorage.peer_username) return window.sessionStorage.peer_username;
function getPeerToken() {
if (window.sessionStorage.peer_token) return window.sessionStorage.peer_token;
let qs = new URLSearchParams(window.location.search);
let username = filterXSS(qs.get('username'));
let queryUsername = false;
if (username) {
queryUsername = username;
let token = filterXSS(qs.get('token'));
let queryToken = false;
if (token) {
queryToken = token;
}
console.log('Direct join', { username: queryUsername });
return queryUsername;
}
/**
* Get Peer password
* @returns {mixed} boolean false or password string
*/
function getPeerPassword() {
if (window.sessionStorage.peer_password) return window.sessionStorage.peer_password;
let qs = new URLSearchParams(window.location.search);
let password = filterXSS(qs.get('password'));
let queryPassword = false;
if (password) {
queryPassword = password;
}
console.log('Direct join', { password: queryPassword });
return queryPassword;
console.log('Direct join', { token: queryToken });
return queryToken;
}
/**
@@ -1368,7 +1351,7 @@ async function whoAreYou() {
console.log(`11.1 Check if ${myPeerName} exist in the room`, roomId);
if (await checkUserName()) {
return userNameAlreadyInRoom();
if (!myToken) return userNameAlreadyInRoom(); // #209 Hack...
}
checkPeerAudioVideo();
@@ -1777,8 +1760,7 @@ async function joinToChannel() {
peer_info: peerInfo,
peer_uuid: myPeerUUID,
peer_name: myPeerName,
peer_username: myUsername,
peer_password: myPassword,
peer_token: myToken,
peer_video: useVideo,
peer_audio: useAudio,
peer_video_status: myVideoStatus,
@@ -5458,7 +5440,7 @@ function shareRoomByEmail() {
function getRoomURL() {
return myRoomUrl;
// return isHostProtected && isPeerAuthEnabled
// ? window.location.origin + '/join/?room=' + roomId + '&username=' + myUsername + '&password=' + myPassword
// ? window.location.origin + '/join/?room=' + roomId + '&token=' + myToken
// : myRoomUrl;
}
+8 -3
View File
@@ -163,16 +163,21 @@
console.log(response);
// Store in session
window.sessionStorage.peer_username = username;
window.sessionStorage.peer_password = password;
const token = response.data.message;
window.sessionStorage.peer_token = token;
if (room) {
return (window.location.href =
'/join/' + window.location.search);
/*
return (window.location.href =
'/join/?room=' + room + '&token=' + token); */
}
if (roomPath) {
return (window.location.href = '/join/' + roomPath);
/*
return (window.location.href =
'/join/?room=' + roomPath + '&token=' + token); */
}
return (window.location.href = '/logged');