[mirotalk] - #285 refactoring
This commit is contained in:
+101
-54
@@ -6,6 +6,7 @@ const TokenManager = require('./tokenManager');
|
||||
const Logger = require('./logs');
|
||||
const log = new Logger('Mattermost');
|
||||
|
||||
// Token operations
|
||||
class TokenService {
|
||||
constructor(secret, expiresIn, encryptionKey) {
|
||||
this.tokenManager = new TokenManager(secret, expiresIn, encryptionKey);
|
||||
@@ -20,33 +21,16 @@ class TokenService {
|
||||
}
|
||||
}
|
||||
|
||||
class MattermostService {
|
||||
constructor(config) {
|
||||
this.validateConfig(config);
|
||||
|
||||
this.token = config.token;
|
||||
this.serverUrl = config.server_url;
|
||||
this.username = config.username;
|
||||
this.password = config.password;
|
||||
this.disabledEndpoints = config.api_disabled || [];
|
||||
|
||||
// Mattermost authentication
|
||||
class MattermostAuthService {
|
||||
constructor({ serverUrl, username, password }) {
|
||||
this.client = new Client4();
|
||||
this.client.setUrl(this.serverUrl);
|
||||
|
||||
this.tokenService = new TokenService(
|
||||
this.token || 'fallback-secret-at-least-32-chars',
|
||||
config.roomTokenExpire || '15m',
|
||||
config.encryptionKey || 'fallback-encryption-key-32chars'
|
||||
);
|
||||
this.client.setUrl(serverUrl);
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
validateConfig(config) {
|
||||
if (!config.enabled || !config.server_url || !config.token || !config.username || !config.password) {
|
||||
throw new Error('Invalid Mattermost configuration');
|
||||
}
|
||||
}
|
||||
|
||||
async authenticate() {
|
||||
async login() {
|
||||
try {
|
||||
const user = await this.client.login(this.username, this.password);
|
||||
log.debug('Logged into Mattermost as', user.username);
|
||||
@@ -54,77 +38,122 @@ class MattermostService {
|
||||
log.error('Failed to log into Mattermost:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Meeting-related operations
|
||||
class MeetingService {
|
||||
constructor(tokenService, serverUrl, disabledEndpoints = [], secure = false) {
|
||||
this.tokenService = tokenService;
|
||||
this.serverUrl = serverUrl;
|
||||
this.disabledEndpoints = disabledEndpoints;
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
isEndpointDisabled(endpoint) {
|
||||
return this.disabledEndpoints.includes(endpoint);
|
||||
}
|
||||
|
||||
createMeetingToken(userId) {
|
||||
const payload = {
|
||||
createTokenPayload(userId) {
|
||||
return {
|
||||
userId,
|
||||
roomId: uuidV4(),
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
const token = this.tokenService.createToken(payload);
|
||||
return { token, payload };
|
||||
}
|
||||
|
||||
validateToken(token) {
|
||||
createMeetingURL(req) {
|
||||
return `${this.getBaseURL(req)}/join/${uuidV4()}`;
|
||||
}
|
||||
|
||||
createSecureMeetingURL(req, userId) {
|
||||
const payload = this.createTokenPayload(userId);
|
||||
const token = this.tokenService.createToken(payload);
|
||||
return {
|
||||
url: `${this.getBaseURL(req)}/mattermost/join/${encodeURIComponent(token)}`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
decodeToken(token) {
|
||||
return this.tokenService.decodeToken(token);
|
||||
}
|
||||
|
||||
getMeetingURL(req, roomToken) {
|
||||
getBaseURL(req) {
|
||||
const host = req.headers.host;
|
||||
const protocol = host.includes('localhost') ? 'http' : 'https';
|
||||
return `${protocol}://${host}/mattermost/join/${encodeURIComponent(roomToken)}`;
|
||||
return `${protocol}://${host}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Just handles routing and delegates everything
|
||||
class MattermostController {
|
||||
constructor(app, mattermostCfg, htmlInjector, clientHtml) {
|
||||
constructor(app, config, htmlInjector, clientHtml) {
|
||||
try {
|
||||
this.service = new MattermostService(mattermostCfg);
|
||||
this.validateConfig(config);
|
||||
|
||||
const tokenService = new TokenService(
|
||||
config.token || 'fallback-secret-at-least-32-chars',
|
||||
config.roomTokenExpire || '15m',
|
||||
config.encryptionKey || 'fallback-encryption-key-32chars'
|
||||
);
|
||||
|
||||
this.authService = new MattermostAuthService({
|
||||
serverUrl: config.server_url,
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
});
|
||||
|
||||
this.meetingService = new MeetingService(
|
||||
tokenService,
|
||||
config.server_url,
|
||||
config.api_disabled,
|
||||
config.security
|
||||
);
|
||||
|
||||
this.token = config.token;
|
||||
this.app = app;
|
||||
this.htmlInjector = htmlInjector;
|
||||
this.clientHtml = clientHtml;
|
||||
|
||||
this.authService.login();
|
||||
this.setupRoutes();
|
||||
|
||||
} catch (error) {
|
||||
log.error('MattermostController disabled due to config error:', error.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.htmlInjector = htmlInjector;
|
||||
this.clientHtml = clientHtml;
|
||||
this.app = app;
|
||||
this.token = mattermostCfg.token;
|
||||
|
||||
this.service.authenticate();
|
||||
this.setupRoutes();
|
||||
validateConfig(cfg) {
|
||||
if (!cfg.enabled || !cfg.server_url || !cfg.token || !cfg.username || !cfg.password) {
|
||||
throw new Error('Invalid Mattermost configuration');
|
||||
}
|
||||
}
|
||||
|
||||
setupRoutes() {
|
||||
this.app.post('/mattermost', (req, res) => {
|
||||
if (this.service.isEndpointDisabled('mattermost')) {
|
||||
if (this.meetingService.isEndpointDisabled('mattermost')) {
|
||||
return res.end('`This endpoint has been disabled`. Please contact the administrator.');
|
||||
}
|
||||
|
||||
const { token, text, command, channel_id, user_id } = req.body;
|
||||
|
||||
if (token !== this.token) {
|
||||
log.error('Invalid token attempt', { token });
|
||||
return res.status(403).send('Invalid token');
|
||||
}
|
||||
|
||||
if (command?.trim() === '/p2p' || text?.trim() === '/p2p') {
|
||||
if (this.isP2PCommand(command, text)) {
|
||||
try {
|
||||
const { token: roomToken } = this.service.createMeetingToken(user_id);
|
||||
const meetingUrl = this.service.getMeetingURL(req, roomToken);
|
||||
const meetingUrl = this.generateMeetingUrl(req, user_id);
|
||||
const message = this.getMeetingResponseMessage(meetingUrl);
|
||||
|
||||
return res.json({
|
||||
response_type: 'in_channel',
|
||||
text: `🔗 [Click here to join your private meeting](${meetingUrl})`,
|
||||
text: message,
|
||||
channel_id,
|
||||
});
|
||||
} catch (error) {
|
||||
log.error('Token creation failed', error);
|
||||
return res.status(500).send('Error creating meeting');
|
||||
log.error('Meeting creation failed', { error, user_id });
|
||||
return res.status(500).json({ error: 'Failed to create meeting' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +161,7 @@ class MattermostController {
|
||||
});
|
||||
|
||||
this.app.get('/mattermost/join/:roomToken', (req, res) => {
|
||||
if (this.service.isEndpointDisabled('mattermost')) {
|
||||
if (this.meetingService.isEndpointDisabled('mattermost')) {
|
||||
return res.end('This endpoint has been disabled');
|
||||
}
|
||||
|
||||
@@ -143,14 +172,13 @@ class MattermostController {
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = this.service.validateToken(roomToken);
|
||||
log.debug('Decoded payload', payload);
|
||||
|
||||
const payload = this.meetingService.decodeToken(roomToken);
|
||||
if (!payload || !payload.userId || !payload.roomId) {
|
||||
log.error('Invalid or malformed token payload', payload);
|
||||
return res.status(400).send('Invalid token');
|
||||
}
|
||||
|
||||
log.debug('Decoded payload', payload);
|
||||
return this.htmlInjector.injectHtml(this.clientHtml, res);
|
||||
} catch (error) {
|
||||
log.error('Token processing error', {
|
||||
@@ -161,6 +189,25 @@ class MattermostController {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isP2PCommand(command, text) {
|
||||
const normalizedCommand = command?.trim();
|
||||
const normalizedText = text?.trim();
|
||||
return normalizedCommand === '/p2p' || normalizedText === '/p2p';
|
||||
}
|
||||
|
||||
generateMeetingUrl(req, userId) {
|
||||
if (this.meetingService.secure) {
|
||||
return this.meetingService.createSecureMeetingURL(req, userId).url;
|
||||
}
|
||||
return this.meetingService.createMeetingURL(req);
|
||||
}
|
||||
|
||||
getMeetingResponseMessage(meetingUrl) {
|
||||
return this.meetingService.secure
|
||||
? `🔒 [Join your secure private meeting](${meetingUrl})`
|
||||
: `🌐 Join meeting: ${meetingUrl}`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MattermostController;
|
||||
|
||||
+14
-13
@@ -45,7 +45,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.5.04
|
||||
* @version 1.5.05
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -265,18 +265,6 @@ if (configChatGPT.enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mattermost config
|
||||
const mattermostCfg = {
|
||||
enabled: getEnvBoolean(process.env.MATTERMOST_ENABLED),
|
||||
server_url: process.env.MATTERMOST_SERVER_URL,
|
||||
username: process.env.MATTERMOST_USERNAME,
|
||||
password: process.env.MATTERMOST_PASSWORD,
|
||||
token: process.env.MATTERMOST_TOKEN,
|
||||
roomTokenExpire: process.env.MATTERMOST_ROOM_TOKEN_EXPIRE,
|
||||
encryptionKey: process.env.JWT_KEY,
|
||||
api_disabled: api_disabled,
|
||||
};
|
||||
|
||||
// IP Whitelist
|
||||
const ipWhitelist = {
|
||||
enabled: getEnvBoolean(process.env.IP_WHITELIST_ENABLED),
|
||||
@@ -350,6 +338,19 @@ function OIDCAuth(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mattermost config
|
||||
const mattermostCfg = {
|
||||
enabled: getEnvBoolean(process.env.MATTERMOST_ENABLED),
|
||||
server_url: process.env.MATTERMOST_SERVER_URL,
|
||||
username: process.env.MATTERMOST_USERNAME,
|
||||
password: process.env.MATTERMOST_PASSWORD,
|
||||
token: process.env.MATTERMOST_TOKEN,
|
||||
roomTokenExpire: process.env.MATTERMOST_ROOM_TOKEN_EXPIRE,
|
||||
encryptionKey: process.env.JWT_KEY,
|
||||
security: (hostCfg.protected || OIDC.enabled),
|
||||
api_disabled: api_disabled,
|
||||
};
|
||||
|
||||
// stats configuration
|
||||
const statsData = {
|
||||
enabled: process.env.STATS_ENABLED ? getEnvBoolean(process.env.STATS_ENABLED) : true,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mirotalk",
|
||||
"version": "1.5.04",
|
||||
"version": "1.5.05",
|
||||
"description": "A free WebRTC browser-based video call",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ let brand = {
|
||||
},
|
||||
about: {
|
||||
imageUrl: '../images/mirotalk-logo.gif',
|
||||
title: 'WebRTC P2P v1.5.04',
|
||||
title: 'WebRTC P2P v1.5.05',
|
||||
html: `
|
||||
<button
|
||||
id="support-button"
|
||||
|
||||
+2
-2
@@ -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.5.04
|
||||
* @version 1.5.05
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -11158,7 +11158,7 @@ function showAbout() {
|
||||
Swal.fire({
|
||||
background: swBg,
|
||||
position: 'center',
|
||||
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.5.04',
|
||||
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.5.05',
|
||||
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
|
||||
customClass: { image: 'img-about' },
|
||||
html: `
|
||||
|
||||
Reference in New Issue
Block a user