Skip to content

Commit

Permalink
[mirotalk] - add host protection functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
miroslavpejic85 committed Mar 5, 2023
1 parent 72a563c commit 285c926
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .env.template
Expand Up @@ -5,6 +5,9 @@ HTTPS=false
# Domain

HOST=localhost
HOST_PROTECTED=true|false
HOST_USERNAME='username'
HOST_PASSWORD='password'

# Signaling Server listen port

Expand Down
18 changes: 18 additions & 0 deletions app/src/host.js
@@ -0,0 +1,18 @@
'use strict';

const Logs = require('./logs');
const log = new Logs('Host');

module.exports = class Host {
constructor(ip, authorized) {
this.auth = new Map();
this.auth.set(ip, authorized);
//log.debug('AUTH ---> ', this.auth.get(ip));
}
isAuthorized(ip) {
return this.auth.has(ip);
}
deleteIP(ip) {
return this.auth.delete(ip);
}
};
104 changes: 97 additions & 7 deletions app/src/server.js
Expand Up @@ -36,7 +36,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.0.2
* @version 1.0.3
*
*/

Expand All @@ -53,7 +53,7 @@ const cors = require('cors');
const path = require('path');
const checkXSS = require('./xss.js');
const app = express();

const Host = require('./host');
const Logs = require('./logs');
const log = new Logs('server');

Expand All @@ -62,7 +62,7 @@ const isHttps = process.env.HTTPS == 'true';
const port = process.env.PORT || 3000; // must be the same to client.js signalingServerPort
const host = `http${isHttps ? 's' : ''}://${domain}:${port}`;

let io, server;
let io, server, authHost;

if (isHttps) {
const fs = require('fs');
Expand All @@ -85,6 +85,15 @@ io = new Server({

// console.log(io);

// Host protection (disabled by default)
const hostProtected = process.env.HOST_PROTECTED == 'true' ? true : false;
const hostCfg = {
protected: hostProtected,
username: process.env.HOST_USERNAME,
password: process.env.HOST_PASSWORD,
authenticated: !hostProtected,
};

// Swagger config
const yamlJS = require('yamljs');
const swaggerUi = require('swagger-ui-express');
Expand Down Expand Up @@ -155,6 +164,7 @@ const views = {
about: path.join(__dirname, '../../', 'public/views/about.html'),
client: path.join(__dirname, '../../', 'public/views/client.html'),
landing: path.join(__dirname, '../../', 'public/views/landing.html'),
login: path.join(__dirname, '../../', 'public/views/login.html'),
newCall: path.join(__dirname, '../../', 'public/views/newcall.html'),
notFound: path.join(__dirname, '../../', 'public/views/404.html'),
permission: path.join(__dirname, '../../', 'public/views/permission.html'),
Expand Down Expand Up @@ -198,7 +208,33 @@ app.use((err, req, res, next) => {

// main page
app.get(['/'], (req, res) => {
res.sendFile(views.landing);
if (hostCfg.protected == true) {
hostCfg.authenticated = false;
res.sendFile(views.login);
} else {
res.sendFile(views.landing);
}
});

// handle login on host protected
app.get(['/login'], (req, res) => {
if (hostCfg.protected == true) {
let ip = getIP(req);
log.debug(`Request login to host from: ${ip}`, req.query);
const { username, password } = req.query;
if (username == hostCfg.username && password == hostCfg.password) {
hostCfg.authenticated = true;
authHost = new Host(ip, true);
log.debug('LOGIN OK', { ip: ip, authorized: authHost.isAuthorized(ip) });
res.sendFile(views.landing);
} else {
log.debug('LOGIN KO', { ip: ip, authorized: false });
hostCfg.authenticated = false;
res.sendFile(views.login);
}
} else {
res.redirect('/');
}
});

// mirotalk about
Expand All @@ -208,7 +244,17 @@ app.get(['/about'], (req, res) => {

// set new room name and join
app.get(['/newcall'], (req, res) => {
res.sendFile(views.newCall);
if (hostCfg.protected == true) {
let ip = getIP(req);
if (allowedIP(ip)) {
res.sendFile(views.newCall);
} else {
hostCfg.authenticated = false;
res.sendFile(views.login);
}
} else {
res.sendFile(views.newCall);
}
});

// if not allow video/audio
Expand Down Expand Up @@ -237,7 +283,7 @@ app.get(['/test'], (req, res) => {

// no room name specified to join
app.get('/join/', (req, res) => {
if (Object.keys(req.query).length > 0) {
if (hostCfg.authenticated && 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=1&notify=1
Expand All @@ -257,7 +303,11 @@ app.get('/join/', (req, res) => {
// Join Room by id
app.get('/join/:roomId', function (req, res) {
// log.debug('Join to room', { roomId: req.params.roomId });
res.sendFile(views.client);
if (hostCfg.authenticated) {
res.sendFile(views.client);
} else {
res.redirect('/');
}
});

// Not specified correctly the room id
Expand Down Expand Up @@ -398,6 +448,9 @@ async function ngrokStart() {
const tunnelHttps = pu0.startsWith('https') ? pu0 : pu1;
// server settings
log.debug('settings', {
host_protected: hostCfg.protected,
host_username: hostCfg.username,
host_password: hostCfg.password,
iceServers: iceServers,
ngrok: {
ngrok_enabled: ngrokEnabled,
Expand Down Expand Up @@ -446,6 +499,9 @@ server.listen(port, null, () => {
} else {
// server settings
log.debug('settings', {
host_protected: hostCfg.protected,
host_username: hostCfg.username,
host_password: hostCfg.password,
iceServers: iceServers,
server: host,
test_ice_servers: testStunTurn,
Expand Down Expand Up @@ -496,6 +552,7 @@ io.sockets.on('connect', async (socket) => {
socket.on('disconnect', async (reason) => {
for (let channel in socket.channels) {
await removePeerFrom(channel);
removeIP(socket);
}
log.debug('[' + socket.id + '] disconnected', { reason: reason });
delete sockets[socket.id];
Expand Down Expand Up @@ -996,3 +1053,36 @@ io.sockets.on('connect', async (socket) => {
}
}
}); // end [sockets.on-connect]

/**
* Get ip
* @param {object} req
* @returns string ip
*/
function getIP(req) {
return req.headers['x-forwarded-for'] || req.socket.remoteAddress;
}

/**
* Check if auth ip
* @param {string} ip
* @returns boolean
*/
function allowedIP(ip) {
return authHost != null && authHost.isAuthorized(ip);
}

/**
* Remove hosts auth ip on socket disconnect
* @param {object} socket
*/
function removeIP(socket) {
if (hostCfg.protected == true) {
let ip = socket.handshake.address;
if (ip && allowedIP(ip)) {
authHost.deleteIP(ip);
hostCfg.authenticated = false;
log.debug('Remove IP from auth', { ip: ip });
}
}
}
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "mirotalk",
"version": "1.0.2",
"version": "1.0.3",
"description": "A free WebRTC browser-based video call",
"main": "server.js",
"scripts": {
Expand Down
11 changes: 11 additions & 0 deletions public/css/landing.css
Expand Up @@ -10,6 +10,17 @@ body {
margin: 0;
}

/*--------------------------------------------------------------
# Login
--------------------------------------------------------------*/

#loginForm {
padding: 40px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
background: #000000;
}

article,
aside,
footer,
Expand Down
2 changes: 1 addition & 1 deletion public/js/client.js
Expand Up @@ -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.0.2
* @version 1.0.3
*
*/

Expand Down

1 comment on commit 285c926

@miroslavpejic85
Copy link
Owner Author

@miroslavpejic85 miroslavpejic85 commented on 285c926 Mar 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Host protection

Like in MiroTalk SFU

Just edit .env file to enable it (on default disabled)

HOST_PROTECTED=true
HOST_USERNAME=username
HOST_PASSWORD=password

Update 22.11.2023

Commit: ec14f5e#commitcomment-133251188

We introduced 2 new variables:

  • HOST_USER_AUTH: When set to true, users are required to provide a valid username and password in the URL parameters to initialize or join a room.
  • HOST_USERS: This parameter represents the list of valid host users.

.env file

  1. Remove the old variables:
HOST_USERNAME=username
HOST_PASSWORD=password
  1. Add the new variables:
# Host protection
# HOST_PROTECTED: When set to true, it requires a valid username and password from the HOST_USERS list to initialize or join a room.
# HOST_USER_AUTH: When set to true, it also requires a valid username and password, but these need to be provided in the URL parameters.
# HOST_USERS: This is the list of valid host users along with their credentials.

HOST_PROTECTED=true # true or false

HOST_USER_AUTH=false # true or false

HOST_USERS='[{"username": "username", "password": "password"},{"username": "username2", "password": "password2"}]'

If HOST_PROTECTED is set to true, the logic remains the same as before.
If HOST_USER_AUTH is set to true, user login is required.


mirotalk-p2p-host-protection-desktop

The Current Host protection logic if enabled

  1. Host Login (username/password required).
  2. Host Login successfully (the IP will be save as valid auth IP).
  3. You can Create Room - Join Room - Share Room.
  4. All Guest can join ... until you are Logged in.
  5. When you leave room or exit the browser, your IP will be removed from valid auth IP, to prevent point(4).
  6. To access again you need to provide (username/password).

Please sign in to comment.