diff --git a/.env.template b/.env.template index 0a529608..f059198e 100644 --- a/.env.template +++ b/.env.template @@ -43,11 +43,13 @@ OIDC_ISSUER_BASE_URL='https://server.example.com' OIDC_BASE_URL='http://localhost:3000' # https://p2p.mirotalk.com OIDC_CLIENT_ID='ClientID' OIDC_CLIENT_SECRET='ClientSecret' +OIDC_AUTH_REUIRED=false # set to true if authentication is required for all routes SESSION_SECRET='mirotalk-p2p-oidc-secret' # 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 for joining the room. +# HOST_PROTECTED: +# - When set to true, it requires a valid username and password from the HOST_USERS list to initialize or join a room. +# - When OIDC_ENABLED is utilized alongside host protection, the authenticated user will be recognized as valid.# HOST_USER_AUTH: When set to true, it also requires a valid username and password for joining the room. # HOST_USERS: This is the list of valid users along with their credentials. HOST_PROTECTED=false # true or false diff --git a/app/src/host.js b/app/src/host.js index 840755e4..f660028e 100644 --- a/app/src/host.js +++ b/app/src/host.js @@ -3,6 +3,16 @@ module.exports = class Host { constructor() { this.authorizedIPs = new Map(); + this.roomActive = false; + } + + /** + * Get IP from req + * @param {object} req + * @returns string IP + */ + getIP(req) { + return req.headers['x-forwarded-for'] || req.headers['X-Forwarded-For'] || req.socket.remoteAddress || req.ip; } /** @@ -20,6 +30,7 @@ module.exports = class Host { */ setAuthorizedIP(ip, authorized) { this.authorizedIPs.set(ip, authorized); + this.setRoomActive(); } /** @@ -31,12 +42,37 @@ module.exports = class Host { return this.authorizedIPs.has(ip); } + /** + * Host room status + * @returns boolean + */ + isRoomActive() { + return this.roomActive; + } + + /** + * Set host room activate + */ + setRoomActive() { + this.roomActive = true; + } + + /** + * Set host room deactivate + */ + setRoomDeactivate() { + this.roomActive = false; + } + /** * Delete ip from authorized IPs * @param {string} ip * @returns boolean */ deleteIP(ip) { + if (this.isAuthorizedIP(ip)) { + this.setRoomDeactivate(); + } return this.authorizedIPs.delete(ip); } }; diff --git a/app/src/server.js b/app/src/server.js index 0fea602d..d9e17513 100755 --- a/app/src/server.js +++ b/app/src/server.js @@ -40,7 +40,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.3.28 + * @version 1.3.29 * */ @@ -295,7 +295,7 @@ const OIDC = { response_type: 'code', scope: 'openid profile email', }, - authRequired: false, // Set to true if authentication is required for all routes + authRequired: process.env.OIDC_AUTH_REQUIRED ? getEnvBoolean(process.env.OIDC_AUTH_REQUIRED) : false, // Set to true if authentication is required for all routes auth0Logout: true, // Set to true to enable logout with Auth0 routes: { callback: '/auth/callback', // Indicating the endpoint where your application will handle the callback from the authentication provider after a user has been authenticated. @@ -306,13 +306,36 @@ const OIDC = { }; // Custom middleware function for OIDC authentication -const OIDCAuth = function (req, res, next) { +function OIDCAuth(req, res, next) { if (OIDC.enabled) { - requiresAuth()(req, res, next); // Apply requiresAuth() middleware conditionally + // Apply requiresAuth() middleware conditionally + requiresAuth()(req, res, function () { + log.debug('[OIDC] ------> requiresAuth'); + // Check if user is authenticated + if (req.oidc.isAuthenticated()) { + log.debug('[OIDC] ------> User isAuthenticated'); + // User is authenticated + if (hostCfg.protected) { + const ip = authHost.getIP(req); + hostCfg.authenticated = true; + authHost.setAuthorizedIP(ip, true); + // Check... + log.debug('[OIDC] ------> Host protected', { + authenticated: hostCfg.authenticated, + authorizedIPs: authHost.getAuthorizedIPs(), + activeRoom: authHost.isRoomActive(), + }); + } + next(); + } else { + // User is not authenticated + res.status(401).send('Unauthorized'); + } + }); } else { next(); } -}; +} // stats configuration const statsData = { @@ -426,16 +449,33 @@ app.get('/auth/callback', (req, res, next) => { // Logout Route app.get('/logout', (req, res) => { - if (OIDC.enabled) req.logout(); + if (OIDC.enabled) { + // + if (hostCfg.protected) { + const ip = authHost.getIP(req); + if (authHost.isAuthorizedIP(ip)) { + authHost.deleteIP(ip); + } + hostCfg.authenticated = false; + // + log.debug('[OIDC] ------> Logout', { + authenticated: hostCfg.authenticated, + authorizedIPs: authHost.getAuthorizedIPs(), + activeRoom: authHost.isRoomActive(), + }); + } + req.logout(); // Logout user + } res.redirect('/'); // Redirect to the home page after logout }); // main page app.get(['/'], OIDCAuth, (req, res) => { - if (hostCfg.protected && !hostCfg.authenticated) { + if ((!OIDC.enabled && hostCfg.protected && !hostCfg.authenticated) || authHost.isRoomActive()) { const ip = getIP(req); if (allowedIP(ip)) { res.sendFile(views.landing); + hostCfg.authenticated = true; } else { hostCfg.authenticated = false; res.sendFile(views.login); @@ -447,10 +487,11 @@ app.get(['/'], OIDCAuth, (req, res) => { // set new room name and join app.get(['/newcall'], OIDCAuth, (req, res) => { - if (hostCfg.protected && !hostCfg.authenticated) { + if ((!OIDC.enabled && hostCfg.protected && !hostCfg.authenticated) || authHost.isRoomActive()) { const ip = getIP(req); if (allowedIP(ip)) { res.sendFile(views.newCall); + hostCfg.authenticated = true; } else { hostCfg.authenticated = false; res.sendFile(views.login); @@ -485,7 +526,7 @@ app.get(['/test'], (req, res) => { }); // Handle Direct join room with params -app.get('/join/', OIDCAuth, async (req, res) => { +app.get('/join/', async (req, res) => { if (Object.keys(req.query).length > 0) { log.debug('Request Query', req.query); /* @@ -495,6 +536,14 @@ app.get('/join/', OIDCAuth, async (req, res) => { */ const { room, name, audio, video, screen, notify, hide, token } = checkXSS(req.query); + const OIDCUserAuthenticated = OIDC.enabled && req.oidc.isAuthenticated(); + + log.debug('Direct Join', { + OIDCUserAuthenticated: OIDCUserAuthenticated, + authenticated: hostCfg.authenticated, + host_protected: hostCfg.protected, + }); + let peerUsername, peerPassword = ''; let isPeerValid = false; @@ -526,7 +575,7 @@ app.get('/join/', OIDCAuth, async (req, res) => { } // Peer valid going to auth as host - if (hostCfg.protected && isPeerValid && isPeerPresenter && !hostCfg.authenticated) { + if ((hostCfg.protected && isPeerValid && isPeerPresenter && !hostCfg.authenticated) || OIDCUserAuthenticated) { const ip = getIP(req); hostCfg.authenticated = true; authHost.setAuthorizedIP(ip, true); @@ -548,12 +597,23 @@ app.get('/join/', OIDCAuth, async (req, res) => { }); // Join Room by id -app.get('/join/:roomId', OIDCAuth, function (req, res) { +app.get('/join/:roomId', function (req, res) { // log.debug('Join to room', { roomId: req.params.roomId }); - if (hostCfg.authenticated) { + const OIDCUserAuthenticated = OIDC.enabled && req.oidc.isAuthenticated(); + + if (OIDCUserAuthenticated || hostCfg.authenticated || authHost.isRoomActive()) { + log.debug('/join/room', { + OIDCUserAuthenticated: OIDCUserAuthenticated, + authenticated: hostCfg.authenticated, + host_protected: hostCfg.protected, + activeRoom: authHost.isRoomActive(), + }); + + if (hostCfg.protected) authHost.setRoomActive(); + res.sendFile(views.client); } else { - if (hostCfg.protected) { + if (!OIDC.enabled && hostCfg.protected) { return res.sendFile(views.login); } res.redirect('/'); @@ -908,8 +968,8 @@ io.sockets.on('connect', async (socket) => { */ socket.on('disconnect', async (reason) => { for (let channel in socket.channels) { - await removePeerFrom(channel); removeIP(socket); + await removePeerFrom(channel); } log.debug('[' + socket.id + '] disconnected', { reason: reason }); delete sockets[socket.id]; diff --git a/package.json b/package.json index 23fe3890..4a8cbfce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalk", - "version": "1.3.28", + "version": "1.3.29", "description": "A free WebRTC browser-based video call", "main": "server.js", "scripts": { @@ -51,7 +51,7 @@ "jsonwebtoken": "^9.0.2", "ngrok": "^5.0.0-beta.2", "nodemailer": "^6.9.13", - "openai": "^4.40.2", + "openai": "^4.41.1", "qs": "^6.12.1", "socket.io": "^4.7.5", "swagger-ui-express": "^5.0.0", diff --git a/public/js/client.js b/public/js/client.js index eee52257..66ffd327 100644 --- a/public/js/client.js +++ b/public/js/client.js @@ -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.3.28 + * @version 1.3.29 * */