[mirotalksfu] - add host.auth_user
هذا الالتزام موجود في:
69
README.md
69
README.md
@@ -35,6 +35,7 @@
|
||||
- Unlimited conference rooms with no time limitations.
|
||||
- Translated into 133 languages.
|
||||
- Host protection to prevent unauthorized access.
|
||||
- User auth to prevent unauthorized access.
|
||||
- Room password protection.
|
||||
- Compatible with desktop and mobile devices.
|
||||
- Optimized mobile room URL sharing.
|
||||
@@ -79,26 +80,76 @@
|
||||
<br/>
|
||||
|
||||
- You can `directly join a room` by using link like:
|
||||
- https://sfu.mirotalk.com/join?room=test&password=0&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0
|
||||
- https://sfu.mirotalk.com/join?room=test&roomPassword=0&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0
|
||||
|
||||
| Params | Type | Description |
|
||||
| -------- | -------------- | --------------- |
|
||||
| ------------ | -------------- | --------------- |
|
||||
| room | string | Room Id |
|
||||
| password | string/boolean | Room password |
|
||||
| roomPassword | string/boolean | Room password |
|
||||
| name | string | User name |
|
||||
| audio | boolean | Audio stream |
|
||||
| video | boolean | Video stream |
|
||||
| screen | boolean | Screen stream |
|
||||
| notify | boolean | Welcome message |
|
||||
| username | string | auth username |
|
||||
| password | string | auth password |
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> When [host protection is enabled](https://github.com/miroslavpejic85/mirotalksfu/commit/ab21686e9ad4b75e14c3ee020141d61b33111dde#commitcomment-62398736) the host needs to provide a valid username and password as specified in the `app/src/config.js`.
|
||||
>
|
||||
> After host authentication, participants can join the room using any of the following URL formats:
|
||||
>
|
||||
> - https://sfu.mirotalk.com/join/test (URL path)
|
||||
> - https://sfu.mirotalk.com/join/?room=test&password=0&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0 (URL with query parameters for direct join)
|
||||
> The `username` and `password` parameters are required when either `host.protected` or `host.user_auth` is set to `true` in the `app/src/config.js` file. The valid list of users is defined in the `host.users` configuration.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Host Protection Configuration</summary>
|
||||
|
||||
<br/>
|
||||
|
||||
When [host.protected](https://github.com/miroslavpejic85/mirotalksfu/commit/ab21686e9ad4b75e14c3ee020141d61b33111dde#commitcomment-62398736) or `host.user_auth` is enabled, the host/users must provide a valid username and password as specified in the `app/src/config.js` file.
|
||||
|
||||
| Params | Value | Description |
|
||||
| ---------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| `host.protected` | `true` if protection is enabled, `false` if not (default false) | Requires the host to provide a valid username and password during room initialization. |
|
||||
| `host.user_auth` | `true` if user authentication is required, `false` if not (default false). | Determines whether host authentication is required. |
|
||||
| `host.users` | JSON array with user objects: `{"username": "username", "password": "password"}` | List of valid host users with their credentials. |
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
host: {
|
||||
protected: true,
|
||||
user_auth: true,
|
||||
users: [
|
||||
{
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
},
|
||||
{
|
||||
username: 'username2',
|
||||
password: 'password2',
|
||||
},
|
||||
//...
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
### Room Initialization
|
||||
|
||||
To bypass the login page, join the room with URL parameters:
|
||||
|
||||
- [https://sfu.mirotalk.com/join/?room=test&username=username&password=password](https://sfu.mirotalk.com/join/?room=test&username=username&password=password)
|
||||
|
||||
### Participant Room Entry
|
||||
|
||||
If `host.protected` is enabled, participants can join after host authentication using:
|
||||
|
||||
- [https://sfu.mirotalk.com/join/test](https://sfu.mirotalk.com/join/test) (URL path)
|
||||
- [https://sfu.mirotalk.com/join/?room=test&roomPassword=0&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0](https://sfu.mirotalk.com/join/?room=test&roomPassword=0&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0) (URL with query parameters)
|
||||
|
||||
If `host.user_auth` is enabled, participants can join with mandatory credentials:
|
||||
|
||||
- [https://sfu.mirotalk.com/join/?room=test&username=username&password=password](https://sfu.mirotalk.com/join/?room=test&username=username&password=password) (URL path)
|
||||
- [https://sfu.mirotalk.com/join/?room=test&name=mirotalk&audio=0&video=0&screen=0¬ify=0&username=username&password=password](https://sfu.mirotalk.com/join/?room=test&name=mirotalk&audio=0&video=0&screen=0&¬ify=0&username=username&password=password) (URL with query parameters)
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ async function getJoin() {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
room: 'test',
|
||||
password: false,
|
||||
roomPassword: false,
|
||||
name: 'mirotalksfu',
|
||||
audio: true,
|
||||
video: true,
|
||||
|
||||
@@ -18,7 +18,7 @@ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
$data = array(
|
||||
"room" => "test",
|
||||
"password" => false,
|
||||
"roomPassword" => false,
|
||||
"name" => "mirotalksfu",
|
||||
"audio" => true,
|
||||
"video" => true,
|
||||
|
||||
@@ -13,7 +13,7 @@ headers = {
|
||||
|
||||
data = {
|
||||
"room": "test",
|
||||
"password": "false",
|
||||
"roomPassword": "false",
|
||||
"name": "mirotalksfu",
|
||||
"audio": "true",
|
||||
"video": "true",
|
||||
|
||||
@@ -7,5 +7,5 @@ MIROTALK_URL="https://sfu.mirotalk.com/api/v1/join"
|
||||
curl $MIROTALK_URL \
|
||||
--header "authorization: $API_KEY_SECRET" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data '{"room":"test","password":"false","name":"mirotalksfu","audio":"true","video":"true","screen":"false","notify":"true"}' \
|
||||
--data '{"room":"test","roomPassword":"false","name":"mirotalksfu","audio":"true","video":"true","screen":"false","notify":"true"}' \
|
||||
--request POST
|
||||
@@ -40,7 +40,7 @@ dependencies: {
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.2.7
|
||||
* @version 1.2.8
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -92,6 +92,7 @@ const host = 'https://' + 'localhost' + ':' + config.server.listen.port; // conf
|
||||
|
||||
const hostCfg = {
|
||||
protected: config.host.protected,
|
||||
user_auth: config.host.user_auth,
|
||||
users: config.host.users,
|
||||
authenticated: !config.host.protected,
|
||||
};
|
||||
@@ -269,13 +270,30 @@ function startServer() {
|
||||
|
||||
// no room name specified to join || direct join
|
||||
app.get('/join/', (req, res) => {
|
||||
if (hostCfg.authenticated && Object.keys(req.query).length > 0) {
|
||||
if (Object.keys(req.query).length > 0) {
|
||||
log.debug('Direct Join', req.query);
|
||||
// http://localhost:3010/join?room=test&password=0&name=mirotalksfu&audio=1&video=1&screen=1¬ify=1
|
||||
const { room, password, name, audio, video, screen, notify, isPresenter } = checkXSS(req.query);
|
||||
// if (room && password && name && audio && video && screen && notify) {
|
||||
if (room) {
|
||||
// http://localhost:3010/join?room=test&roomPassword=0&name=mirotalksfu&audio=1&video=1&screen=0¬ify=1
|
||||
// http://localhost:3010/join?room=test&roomPassword=0&name=mirotalksfu&audio=1&video=1&screen=0¬ify=0&username=username&password=password
|
||||
const { room, roomPassword, name, audio, video, screen, notify, username, password, isPresenter } =
|
||||
checkXSS(req.query);
|
||||
|
||||
const isPeerValid = isAuthPeer(username, password);
|
||||
|
||||
if (hostCfg.protected && isPeerValid && !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,
|
||||
});
|
||||
}
|
||||
|
||||
if (room && (hostCfg.authenticated || isPeerValid)) {
|
||||
return res.sendFile(views.room);
|
||||
} else {
|
||||
return res.sendFile(views.login);
|
||||
}
|
||||
}
|
||||
if (hostCfg.protected) {
|
||||
@@ -323,6 +341,11 @@ function startServer() {
|
||||
res.send(stats);
|
||||
});
|
||||
|
||||
// handle login if user_auth enabled
|
||||
app.get(['/login'], (req, res) => {
|
||||
res.sendFile(views.login);
|
||||
});
|
||||
|
||||
// handle logged on host protected
|
||||
app.get(['/logged'], (req, res) => {
|
||||
const ip = getIP(req);
|
||||
@@ -340,24 +363,26 @@ function startServer() {
|
||||
|
||||
// handle login on host protected
|
||||
app.post(['/login'], (req, res) => {
|
||||
if (hostCfg.protected) {
|
||||
let ip = getIP(req);
|
||||
const ip = getIP(req);
|
||||
log.debug(`Request login to host from: ${ip}`, req.body);
|
||||
|
||||
const { username, password } = checkXSS(req.body);
|
||||
const isValidUser =
|
||||
hostCfg.users && hostCfg.users.some((user) => user.username === username && user.password === password);
|
||||
if (isValidUser) {
|
||||
|
||||
const isPeerValid = isAuthPeer(username, password);
|
||||
|
||||
if (hostCfg.protected && isPeerValid && !hostCfg.authenticated) {
|
||||
const ip = getIP(req);
|
||||
hostCfg.authenticated = true;
|
||||
authHost = new Host(ip, true);
|
||||
log.debug('LOGIN OK', { ip: ip, authorized: authHost.isAuthorized(ip) });
|
||||
res.status(200).json({ message: 'authorized' });
|
||||
} else {
|
||||
log.debug('LOGIN KO', { ip: ip, authorized: false });
|
||||
hostCfg.authenticated = false;
|
||||
res.status(401).json({ message: 'unauthorized' });
|
||||
log.debug('HOST LOGIN OK', { ip: ip, authorized: authHost.isAuthorized(ip) });
|
||||
return res.status(200).json({ message: 'authorized' });
|
||||
}
|
||||
|
||||
if (isPeerValid) {
|
||||
log.debug('PEER LOGIN OK', { ip: ip, authorized: true });
|
||||
return res.status(200).json({ message: 'authorized' });
|
||||
} else {
|
||||
res.redirect('/');
|
||||
return res.status(401).json({ message: 'unauthorized' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -896,6 +921,26 @@ function startServer() {
|
||||
|
||||
log.debug('User joined', data);
|
||||
|
||||
// User Auth required, we check if peer valid
|
||||
if (hostCfg.user_auth) {
|
||||
const peer_username = data.peer_info.peer_username;
|
||||
const peer_password = data.peer_info.peer_password;
|
||||
|
||||
const isPeerValid = isAuthPeer(peer_username, peer_password);
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
if (!isPeerValid) {
|
||||
// redirect peer to login page
|
||||
return cb('unauthorized');
|
||||
}
|
||||
}
|
||||
|
||||
const room = roomList.get(socket.room_id);
|
||||
|
||||
room.addPeer(new Peer(socket.id, data));
|
||||
@@ -1348,6 +1393,10 @@ function startServer() {
|
||||
}
|
||||
}
|
||||
|
||||
function isAuthPeer(username, password) {
|
||||
return hostCfg.users && hostCfg.users.some((user) => user.username === username && user.password === password);
|
||||
}
|
||||
|
||||
async function getPeerGeoLocation(ip) {
|
||||
const endpoint = config.IPLookup.getEndpoint(ip);
|
||||
log.debug('Get peer geo', { ip: ip, endpoint: endpoint });
|
||||
|
||||
@@ -25,8 +25,8 @@ module.exports = class ServerApi {
|
||||
this._host +
|
||||
'/join?room=' +
|
||||
data.room +
|
||||
'&password=' +
|
||||
data.password +
|
||||
'&roomPassword=' +
|
||||
data.roomPassword +
|
||||
'&name=' +
|
||||
data.name +
|
||||
'&audio=' +
|
||||
|
||||
@@ -39,10 +39,11 @@ module.exports = {
|
||||
host: {
|
||||
/*
|
||||
Host Protection (default: false)
|
||||
To enhance host security, enable host protection and provide valid
|
||||
To enhance host security, enable host protection - user auth and provide valid
|
||||
usernames and passwords in the users array.
|
||||
*/
|
||||
protected: false,
|
||||
user_auth: false,
|
||||
users: [
|
||||
{
|
||||
username: 'username',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mirotalksfu",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.8",
|
||||
"description": "WebRTC SFU browser-based video calls",
|
||||
"main": "Server.js",
|
||||
"scripts": {
|
||||
|
||||
ثنائية
public/images/forbidden.png
Normal file
ثنائية
public/images/forbidden.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 3.6 KiB |
@@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.2.7
|
||||
* @version 1.2.8
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -86,6 +86,8 @@ let room_id = getRoomId();
|
||||
let room_password = getRoomPassword();
|
||||
let peer_name = getPeerName();
|
||||
let peer_uuid = getPeerUUID();
|
||||
let peer_username = getPeerUsername();
|
||||
let peer_password = getPeerPassword();
|
||||
let isScreenAllowed = getScreen();
|
||||
let notify = getNotify();
|
||||
isPresenter = isPeerPresenter();
|
||||
@@ -568,9 +570,33 @@ function getPeerUUID() {
|
||||
return peer_uuid;
|
||||
}
|
||||
|
||||
function getPeerUsername() {
|
||||
if (window.sessionStorage.peer_username) return window.sessionStorage.peer_username;
|
||||
let qs = new URLSearchParams(window.location.search);
|
||||
let username = filterXSS(qs.get('username'));
|
||||
let queryUsername = false;
|
||||
if (username) {
|
||||
queryUsername = username;
|
||||
}
|
||||
console.log('Direct join', { username: queryUsername });
|
||||
return queryUsername;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function getRoomPassword() {
|
||||
let qs = new URLSearchParams(window.location.search);
|
||||
let roomPassword = filterXSS(qs.get('password'));
|
||||
let roomPassword = filterXSS(qs.get('roomPassword'));
|
||||
if (roomPassword) {
|
||||
let queryNoRoomPassword = roomPassword === '0' || roomPassword === 'false';
|
||||
if (queryNoRoomPassword) {
|
||||
@@ -609,6 +635,8 @@ function getPeerInfo() {
|
||||
peer_uuid: peer_uuid,
|
||||
peer_id: socket.id,
|
||||
peer_name: peer_name,
|
||||
peer_username: peer_username,
|
||||
peer_password: peer_password,
|
||||
peer_presenter: isPresenter,
|
||||
peer_audio: isAudioAllowed,
|
||||
peer_video: isVideoAllowed,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.2.7
|
||||
* @version 1.2.8
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -83,6 +83,7 @@ const image = {
|
||||
email: '../images/email.png',
|
||||
chatgpt: '../images/chatgpt.png',
|
||||
all: '../images/all.png',
|
||||
forbidden: '../images/forbidden.png',
|
||||
};
|
||||
|
||||
const mediaType = {
|
||||
@@ -318,6 +319,12 @@ class RoomClient {
|
||||
.then(
|
||||
async function (room) {
|
||||
console.log('##### JOIN ROOM #####', room);
|
||||
if (room === 'unauthorized') {
|
||||
console.log(
|
||||
'00-WARNING ----> Room is Unauthorized for current user, please provide a valid username and password',
|
||||
);
|
||||
return this.userUnauthorized();
|
||||
}
|
||||
if (room === 'isLocked') {
|
||||
this.event(_EVENTS.roomLock);
|
||||
console.log('00-WARNING ----> Room is Locked, Try to unlock by the password');
|
||||
@@ -4829,6 +4836,24 @@ class RoomClient {
|
||||
// HANDLE ROOM ACTION
|
||||
// ####################################################
|
||||
|
||||
userUnauthorized() {
|
||||
this.sound('alert');
|
||||
Swal.fire({
|
||||
allowOutsideClick: false,
|
||||
allowEscapeKey: false,
|
||||
background: swalBackground,
|
||||
imageUrl: image.forbidden,
|
||||
title: 'Oops, Unauthorized',
|
||||
text: 'The host has user authentication enabled',
|
||||
confirmButtonText: `Login`,
|
||||
showClass: { popup: 'animate__animated animate__fadeInDown' },
|
||||
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
|
||||
}).then(() => {
|
||||
// Login required to join room
|
||||
openURL(`/login/?room=${this.room_id}`);
|
||||
});
|
||||
}
|
||||
|
||||
unlockTheRoom() {
|
||||
if (room_password) {
|
||||
this.RoomPassword = room_password;
|
||||
|
||||
@@ -133,7 +133,9 @@
|
||||
const password = filterXSS(document.getElementById('password').value);
|
||||
|
||||
// http://localhost:3010/join/?room=test
|
||||
// http://localhost:3010/join/?room=test&password=0&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0
|
||||
// http://localhost:3010/join/?room=test&roomPassword=0&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0
|
||||
// http://localhost:3010/join/?room=test&roomPassword=0&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0&username=username&password=password
|
||||
|
||||
const qs = new URLSearchParams(window.location.search);
|
||||
const room = filterXSS(qs.get('room'));
|
||||
|
||||
@@ -150,6 +152,10 @@
|
||||
.then(function (response) {
|
||||
console.log(response);
|
||||
|
||||
// Store in session
|
||||
window.sessionStorage.peer_username = username;
|
||||
window.sessionStorage.peer_password = password;
|
||||
|
||||
if (room) {
|
||||
return (window.location.href =
|
||||
'/join/' + window.location.search);
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم