[mirotalksfu] - improve security, update dep
هذا الالتزام موجود في:
@@ -18,14 +18,14 @@ class HtmlInjector {
|
||||
// Function to get dynamic data for injection (e.g., OG data, title, etc.)
|
||||
getInjectData() {
|
||||
return {
|
||||
OG_TYPE: this.config.og?.type || 'app-webrtc',
|
||||
OG_SITE_NAME: this.config.og?.siteName || 'MiroTalk SFU',
|
||||
OG_TITLE: this.config.og?.title || 'Click the link to make a call.',
|
||||
OG_TYPE: this.config?.og?.type || 'app-webrtc',
|
||||
OG_SITE_NAME: this.config?.og?.siteName || 'MiroTalk SFU',
|
||||
OG_TITLE: this.config?.og?.title || 'Click the link to make a call.',
|
||||
OG_DESCRIPTION:
|
||||
this.config.og?.description ||
|
||||
this.config?.og?.description ||
|
||||
'MiroTalk SFU calling provides real-time video calls, messaging and screen sharing.',
|
||||
OG_IMAGE: this.config.og?.image || 'https://sfu.mirotalk.com/images/mirotalksfu.png',
|
||||
OG_URL: this.config.og?.url || 'https://sfu.mirotalk.com',
|
||||
OG_IMAGE: this.config?.og?.image || 'https://sfu.mirotalk.com/images/mirotalksfu.png',
|
||||
OG_URL: this.config?.og?.url || 'https://sfu.mirotalk.com',
|
||||
// Add more data here as needed with fallbacks
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ dev 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.7.19
|
||||
* @version 1.7.20
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -73,8 +73,10 @@ const axios = require('axios');
|
||||
const ngrok = require('ngrok');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const fs = require('fs');
|
||||
const sanitizeFilename = require('sanitize-filename');
|
||||
const helmet = require('helmet');
|
||||
const config = require('./config');
|
||||
const checkXSS = require('./XSS.js');
|
||||
const checkXSS = require('./XSS');
|
||||
const Host = require('./Host');
|
||||
const Room = require('./Room');
|
||||
const Peer = require('./Peer');
|
||||
@@ -226,16 +228,28 @@ const OIDC = config.oidc ? config.oidc : { enabled: false };
|
||||
const dir = {
|
||||
public: path.join(__dirname, '../../', 'public'),
|
||||
rec: path.join(__dirname, '../', config?.server?.recording?.dir ? config.server.recording.dir + '/' : 'rec/'),
|
||||
rtmp: path.join(__dirname, '../', config?.rtmp?.dir ? config.rtmp.dir + '/' : 'rtmp/'),
|
||||
};
|
||||
|
||||
// rec directory create
|
||||
// Rec directory create and set max file size
|
||||
const recMaxFileSize = config?.server?.recording?.maxFileSize || 1 * 1024 * 1024 * 1024; // 1GB default
|
||||
const serverRecordingEnabled = config?.server?.recording?.enabled;
|
||||
if (serverRecordingEnabled) {
|
||||
log.debug('Server Recording enabled creating dir', dir.rtmp);
|
||||
if (!fs.existsSync(dir.rec)) {
|
||||
fs.mkdirSync(dir.rec, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
// Rtmp directory create
|
||||
const rtmpEnabled = rtmpCfg && rtmpCfg.enabled;
|
||||
if (rtmpEnabled) {
|
||||
log.debug('RTMP enabled creating dir', dir.rtmp);
|
||||
if (!fs.existsSync(dir.rtmp)) {
|
||||
fs.mkdirSync(dir.rtmp, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
// html views
|
||||
const views = {
|
||||
html: path.join(__dirname, '../../public/views'),
|
||||
@@ -340,6 +354,8 @@ function OIDCAuth(req, res, next) {
|
||||
|
||||
function startServer() {
|
||||
// Start the app
|
||||
app.use(helmet.xssFilter()); // Enable XSS protection
|
||||
app.use(helmet.noSniff()); // Enable content type sniffing prevention
|
||||
app.use(express.static(dir.public));
|
||||
app.use(cors(corsOptions));
|
||||
app.use(compression());
|
||||
@@ -769,8 +785,10 @@ function startServer() {
|
||||
return res.status(400).send('Filename not provided');
|
||||
}
|
||||
|
||||
if (!Validator.isValidRecFileNameFormat(fileName)) {
|
||||
log.warn('[RecSync] - Invalid file name', fileName);
|
||||
// Sanitize and validate filename
|
||||
const safeFileName = sanitizeFilename(fileName);
|
||||
if (safeFileName !== fileName || !Validator.isValidRecFileNameFormat(fileName)) {
|
||||
log.warn('[RecSync] - Invalid file name:', fileName);
|
||||
return res.status(400).send('Invalid file name');
|
||||
}
|
||||
|
||||
@@ -782,11 +800,37 @@ function startServer() {
|
||||
return res.status(400).send('Invalid file name');
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
if (!fs.existsSync(dir.rec)) {
|
||||
fs.mkdirSync(dir.rec, { recursive: true });
|
||||
}
|
||||
const filePath = dir.rec + fileName;
|
||||
|
||||
// Resolve and validate file path
|
||||
const filePath = path.resolve(dir.rec, fileName);
|
||||
if (!filePath.startsWith(path.resolve(dir.rec))) {
|
||||
log.warn('[RecSync] - Attempt to save file outside allowed directory:', fileName);
|
||||
return res.status(400).send('Invalid file path');
|
||||
}
|
||||
|
||||
//Validate content type
|
||||
if (!['application/octet-stream'].includes(req.headers['content-type'])) {
|
||||
log.warn('[RecSync] - Invalid content type:', req.headers['content-type']);
|
||||
return res.status(400).send('Invalid content type');
|
||||
}
|
||||
|
||||
// Set up write stream and handle file upload
|
||||
const writeStream = fs.createWriteStream(filePath, { flags: 'a' });
|
||||
let receivedBytes = 0;
|
||||
|
||||
req.on('data', (chunk) => {
|
||||
receivedBytes += chunk.length;
|
||||
if (receivedBytes > recMaxFileSize) {
|
||||
req.destroy(); // Stop receiving data
|
||||
writeStream.destroy(); // Stop writing data
|
||||
log.warn('[RecSync] - File size exceeds limit:', fileName);
|
||||
return res.status(413).send('File too large');
|
||||
}
|
||||
});
|
||||
|
||||
req.pipe(writeStream);
|
||||
|
||||
@@ -841,7 +885,6 @@ function startServer() {
|
||||
});
|
||||
|
||||
app.get('/rtmpEnabled', (req, res) => {
|
||||
const rtmpEnabled = rtmpCfg && rtmpCfg.enabled;
|
||||
log.debug('RTMP enabled', rtmpEnabled);
|
||||
res.json({ enabled: rtmpEnabled });
|
||||
});
|
||||
|
||||
@@ -104,6 +104,7 @@ module.exports = {
|
||||
enabled: false,
|
||||
endpoint: '', // Change the URL if you want to save the recording to a different server or cloud service (http://localhost:8080), otherwise leave it as is (empty).
|
||||
dir: 'rec',
|
||||
maxFileSize: 1 * 1024 * 1024 * 1024, // 1 GB
|
||||
},
|
||||
rtmp: {
|
||||
/*
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
"license": "AGPLv3",
|
||||
"dependencies": {
|
||||
"cors": "2.8.5",
|
||||
"express": "^4.18.2"
|
||||
"express": "^4.21.2",
|
||||
"helmet": "^8.0.0",
|
||||
"sanitize-filename": "^1.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.3"
|
||||
"nodemon": "^3.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ const express = require('express');
|
||||
const cors = require('cors');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const sanitizeFilename = require('sanitize-filename');
|
||||
const helmet = require('helmet');
|
||||
const app = express();
|
||||
const port = process.env.PORT || 8080;
|
||||
|
||||
@@ -14,6 +16,9 @@ const log = {
|
||||
debug: console.log,
|
||||
};
|
||||
|
||||
// Recording max file size
|
||||
const recMaxFileSize = 1 * 1024 * 1024 * 1024; // 1 GB
|
||||
|
||||
// Directory where recordings will be stored
|
||||
const recordingDirectory = path.join(__dirname, 'rec');
|
||||
|
||||
@@ -27,6 +32,7 @@ const corsOptions = {
|
||||
};
|
||||
|
||||
// Middleware
|
||||
app.use(helmet());
|
||||
app.use(express.json());
|
||||
app.use(cors(corsOptions));
|
||||
|
||||
@@ -50,16 +56,39 @@ app.post('/recSync', (req, res) => {
|
||||
return res.status(400).send('Filename not provided');
|
||||
}
|
||||
|
||||
if (!isValidRecFileNameFormat(fileName)) {
|
||||
log.warn('[RecSync] - Invalid file name', fileName);
|
||||
const safeFileName = sanitizeFilename(fileName);
|
||||
if (safeFileName !== fileName || !isValidRecFileNameFormat(fileName)) {
|
||||
log.warn('[RecSync] - Invalid file name:', fileName);
|
||||
return res.status(400).send('Invalid file name');
|
||||
}
|
||||
|
||||
ensureRecordingDirectoryExists();
|
||||
|
||||
const filePath = path.join(recordingDirectory, fileName);
|
||||
const filePath = path.resolve(recordingDirectory, fileName);
|
||||
if (!filePath.startsWith(path.resolve(recordingDirectory))) {
|
||||
log.warn('[RecSync] - Attempt to save file outside allowed directory:', fileName);
|
||||
return res.status(400).send('Invalid file path');
|
||||
}
|
||||
|
||||
if (!['application/octet-stream'].includes(req.headers['content-type'])) {
|
||||
log.warn('[RecSync] - Invalid content type:', req.headers['content-type']);
|
||||
return res.status(400).send('Invalid content type');
|
||||
}
|
||||
|
||||
const writeStream = fs.createWriteStream(filePath, { flags: 'a' });
|
||||
|
||||
let receivedBytes = 0;
|
||||
|
||||
req.on('data', (chunk) => {
|
||||
receivedBytes += chunk.length;
|
||||
if (receivedBytes > recMaxFileSize) {
|
||||
req.destroy(); // Stop receiving data
|
||||
writeStream.destroy(); // Stop writing data
|
||||
log.warn('[RecSync] - File size exceeds limit:', fileName);
|
||||
return res.status(413).send('File too large');
|
||||
}
|
||||
});
|
||||
|
||||
req.pipe(writeStream);
|
||||
|
||||
writeStream.on('error', (err) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mirotalksfu",
|
||||
"version": "1.7.19",
|
||||
"version": "1.7.20",
|
||||
"description": "WebRTC SFU browser-based video calls",
|
||||
"main": "Server.js",
|
||||
"scripts": {
|
||||
@@ -58,7 +58,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mattermost/client": "10.2.0",
|
||||
"@sentry/node": "^8.52.1",
|
||||
"@sentry/node": "^8.53.0",
|
||||
"axios": "^1.7.9",
|
||||
"colors": "1.4.0",
|
||||
"compression": "1.7.5",
|
||||
@@ -70,6 +70,7 @@
|
||||
"express-openid-connect": "^2.17.1",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"he": "^1.2.0",
|
||||
"helmet": "^8.0.0",
|
||||
"httpolyglot": "0.1.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^26.0.0",
|
||||
@@ -80,6 +81,7 @@
|
||||
"nodemailer": "^6.10.0",
|
||||
"openai": "^4.81.0",
|
||||
"qs": "6.14.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"socket.io": "4.8.1",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"uuid": "11.0.5"
|
||||
|
||||
@@ -64,7 +64,7 @@ let BRAND = {
|
||||
},
|
||||
about: {
|
||||
imageUrl: '../images/mirotalk-logo.gif',
|
||||
title: '<strong>WebRTC SFU v1.7.19</strong>',
|
||||
title: '<strong>WebRTC SFU v1.7.20</strong>',
|
||||
html: `
|
||||
<button
|
||||
id="support-button"
|
||||
|
||||
@@ -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.7.19
|
||||
* @version 1.7.20
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -4903,13 +4903,9 @@ function showAbout() {
|
||||
Swal.fire({
|
||||
background: swalBackground,
|
||||
position: 'center',
|
||||
imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== ''
|
||||
? BRAND.about.imageUrl
|
||||
: image.about,
|
||||
imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' ? BRAND.about.imageUrl : image.about,
|
||||
customClass: { image: 'img-about' },
|
||||
title: BRAND.about?.title && BRAND.about.title.trim() !== ''
|
||||
? BRAND.about.title
|
||||
: 'WebRTC SFU v1.7.19',
|
||||
title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.7.20',
|
||||
html: `
|
||||
<br />
|
||||
<div id="about">
|
||||
|
||||
@@ -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.7.19
|
||||
* @version 1.7.20
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم