From 0719d25f35f62f22d2131b1ab48750848bd13e8c Mon Sep 17 00:00:00 2001 From: John Livingston Date: Thu, 4 Apr 2024 10:58:16 +0200 Subject: [PATCH] Fix #48: Proper 404 and 403 pages when trying to open non-existant chatroom (WIP). --- CHANGELOG.md | 1 + assets/styles/style.scss | 15 ++ client/@types/global.d.ts | 1 + client/common/room/register.ts | 8 +- client/utils/conversejs.ts | 2 +- client/videowatch-client-plugin.ts | 12 +- languages/en.yml | 2 + package-lock.json | 20 ++- package.json | 2 + server/lib/conversejs/params.ts | 19 ++- server/lib/routers/api/configuration.ts | 14 +- server/lib/routers/webchat.ts | 206 +++++++++++++----------- 12 files changed, 198 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d570e140..b17234ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ TODO: https://github.com/JohnXLivingston/peertube-plugin-livechat/issues/48 * Some code refactoring. * New translations: Galician. * Fix slow mode: focus was lost when textarea got disabled, so it could trigger some Peertube events if the user type some text. +* #48: Proper 404 and 403 pages when trying to open non-existant chatroom. ## 8.4.0 diff --git a/assets/styles/style.scss b/assets/styles/style.scss index c02a48ed..fa7c0161 100644 --- a/assets/styles/style.scss +++ b/assets/styles/style.scss @@ -207,3 +207,18 @@ table.peertube-plugin-livechat-prosody-list-rooms td { } } } + +.peertube-plugin-livechat-error-message { + /* display an error block (page not found, ...) */ + display: block; + font-size: 20px; + padding-top: 50px; + text-align: center; + width: 100%; +} + +.peertube-plugin-livechat-container { + .peertube-plugin-livechat-error-message { + max-width: 30vw; + } +} diff --git a/client/@types/global.d.ts b/client/@types/global.d.ts index 1a60f3e2..1a5cc358 100644 --- a/client/@types/global.d.ts +++ b/client/@types/global.d.ts @@ -76,3 +76,4 @@ declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_BOT_NICKNAME: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FOR_MORE_INFO: string declare const LOC_INVALID_VALUE: string +declare const LOC_CHATROOM_NOT_ACCESSIBLE: string diff --git a/client/common/room/register.ts b/client/common/room/register.ts index 9dc006c8..257716b7 100644 --- a/client/common/room/register.ts +++ b/client/common/room/register.ts @@ -26,8 +26,12 @@ async function registerRoom (clientOptions: RegisterClientOptions): Promise dom.remove()) + container.querySelectorAll( + 'converse-root, .livechat-spinner, .peertube-plugin-livechat-error-message' + ).forEach(dom => dom.remove()) container.setAttribute('peertube-plugin-livechat-state', 'closed') diff --git a/languages/en.yml b/languages/en.yml index b7832c37..9c4dc2ad 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -390,3 +390,5 @@ livechat_configuration_channel_bot_nickname: "Bot nickname" invalid_value: "Invalid value." slow_mode_info: "Slow mode is enabled, users can send a message every %1$s seconds." + +chatroom_not_accessible: "This chatroom does not exist, or is not accessible to you." diff --git a/package-lock.json b/package-lock.json index 0fe2a9a1..c3a2ab8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "async": "^3.2.2", "decache": "^4.6.0", + "escape-html": "^1.0.3", "got": "^11.8.2", "http-proxy": "^1.18.1", "log-rotate": "^0.2.8", @@ -22,6 +23,7 @@ "@peertube/peertube-types": "^5.2.0", "@tsconfig/node12": "^1.0.9", "@types/async": "^3.2.9", + "@types/escape-html": "^1.0.4", "@types/express": "^4.17.13", "@types/got": "^9.6.12", "@types/http-proxy": "^1.17.9", @@ -3896,6 +3898,12 @@ "@types/ms": "*" } }, + "node_modules/@types/escape-html": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", + "integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==", + "dev": true + }, "node_modules/@types/express": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", @@ -6441,8 +6449,7 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -15300,6 +15307,12 @@ "@types/ms": "*" } }, + "@types/escape-html": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", + "integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==", + "dev": true + }, "@types/express": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", @@ -17251,8 +17264,7 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "escape-string-regexp": { "version": "1.0.5", diff --git a/package.json b/package.json index 0c534aeb..9f0b1890 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "dependencies": { "async": "^3.2.2", "decache": "^4.6.0", + "escape-html": "^1.0.3", "got": "^11.8.2", "http-proxy": "^1.18.1", "log-rotate": "^0.2.8", @@ -46,6 +47,7 @@ "@peertube/peertube-types": "^5.2.0", "@tsconfig/node12": "^1.0.9", "@types/async": "^3.2.9", + "@types/escape-html": "^1.0.4", "@types/express": "^4.17.13", "@types/got": "^9.6.12", "@types/http-proxy": "^1.17.9", diff --git a/server/lib/conversejs/params.ts b/server/lib/conversejs/params.ts index e3dfaae7..ed48595d 100644 --- a/server/lib/conversejs/params.ts +++ b/server/lib/conversejs/params.ts @@ -23,20 +23,32 @@ interface GetConverseJSParamsParams { * Returns an object describing the error if access can not be granted. * @param options server options * @param roomKey chat room key: video UUID (or channel id when forcetype is true) + * @param params various parameters + * @param userIsConnected true if user is connected. If undefined, bypass access tests. */ async function getConverseJSParams ( options: RegisterServerOptionsV5, roomKey: string, - params: GetConverseJSParamsParams + params: GetConverseJSParamsParams, + userIsConnected?: boolean ): Promise { const settings = await options.settingsManager.getSettings([ 'prosody-room-type', 'disable-websocket', 'converse-theme', 'federation-no-remote-chat', - 'prosody-room-allow-s2s' + 'prosody-room-allow-s2s', + 'chat-no-anonymous' ]) + if (settings['chat-no-anonymous'] && userIsConnected === false) { + return { + isError: true, + code: 403, + message: 'You must be connected' + } + } + const { autoViewerMode, forceReadonly, transparent, converseJSTheme } = _interfaceParams(options, settings, params) @@ -337,5 +349,6 @@ async function _localRoomJID ( } export { - getConverseJSParams + getConverseJSParams, + InitConverseJSParamsError } diff --git a/server/lib/routers/api/configuration.ts b/server/lib/routers/api/configuration.ts index 7b56a124..c49ffdc0 100644 --- a/server/lib/routers/api/configuration.ts +++ b/server/lib/routers/api/configuration.ts @@ -18,9 +18,17 @@ async function initConfigurationApiRouter (options: RegisterServerOptions, route router.get('/configuration/room/:roomKey', asyncMiddleware( async (req: Request, res: Response, _next: NextFunction): Promise => { const roomKey = req.params.roomKey - const initConverseJSParam = await getConverseJSParams(options, roomKey, { - forcetype: req.query.forcetype === '1' - }) + + const user = await options.peertubeHelpers.user.getAuthUser(res) + + const initConverseJSParam = await getConverseJSParams( + options, + roomKey, + { + forcetype: req.query.forcetype === '1' + }, + !!user + ) if (('isError' in initConverseJSParam) && initConverseJSParam.isError) { res.sendStatus(initConverseJSParam.code) return diff --git a/server/lib/routers/webchat.ts b/server/lib/routers/webchat.ts index 2c622b32..97607eeb 100644 --- a/server/lib/routers/webchat.ts +++ b/server/lib/routers/webchat.ts @@ -1,6 +1,8 @@ import type { RegisterServerOptions } from '@peertube/peertube-types' import type { Router, Request, Response, NextFunction } from 'express' -import type { ProsodyListRoomsResult, ProsodyListRoomsResultRoom } from '../../../shared/lib/types' +import type { + InitConverseJSParamsError, ProsodyListRoomsResult, ProsodyListRoomsResultRoom +} from '../../../shared/lib/types' import { createProxyServer } from 'http-proxy' import { RegisterServerOptionsV5, isUserAdmin } from '../helpers' import { asyncMiddleware } from '../middlewares/async' @@ -10,8 +12,11 @@ import { getConverseJSParams } from '../conversejs/params' import { setCurrentProsody, delCurrentProsody } from '../prosody/api/host' import { getChannelInfosById } from '../database/channel' import { listProsodyRooms } from '../prosody/api/manage-rooms' +import { loc } from '../loc' import * as path from 'path' +const escapeHTML = require('escape-html') + const fs = require('fs').promises interface ProsodyProxyInfo { @@ -22,6 +27,14 @@ let currentHttpBindProxy: ReturnType | null = null let currentWebsocketProxy: ReturnType | null = null let currentS2SWebsocketProxy: ReturnType | null = null +class LivechatError extends Error { + livechatError: InitConverseJSParamsError + constructor (e: InitConverseJSParamsError) { + super(e.message) + this.livechatError = e + } +} + async function initWebchatRouter (options: RegisterServerOptionsV5): Promise { const { getRouter, @@ -33,100 +46,113 @@ async function initWebchatRouter (options: RegisterServerOptionsV5): Promise => { - res.removeHeader('X-Frame-Options') // this route can be opened in an iframe + router.get('/room/:roomKey', + asyncMiddleware(async (req: Request, res: Response, _next: NextFunction): Promise => { + try { + res.removeHeader('X-Frame-Options') // this route can be opened in an iframe - const roomKey = req.params.roomKey - let readonly: boolean | 'noscroll' = false - if (req.query._readonly === 'true') { - readonly = true - } else if (req.query._readonly === 'noscroll') { - readonly = 'noscroll' - } - - const initConverseJSParam = await getConverseJSParams(options, roomKey, { - readonly, - transparent: req.query._transparent === 'true', - forcetype: req.query.forcetype === '1', - forceDefaultHideMucParticipants: req.query.force_default_hide_muc_participants === '1' - }) - - if (('isError' in initConverseJSParam)) { - res.status(initConverseJSParam.code) - res.send(initConverseJSParam.message) - return - } - - let page = '' + (converseJSIndex as string) - page = page.replace(/{{BASE_STATIC_URL}}/g, initConverseJSParam.staticBaseUrl) - - const settings = await options.settingsManager.getSettings([ - 'converse-theme', 'converse-autocolors' - ]) - - // Adding some custom CSS if relevant... - let autocolorsStyles = '' - if ( - settings['converse-autocolors'] && - isAutoColorsAvailable(settings['converse-theme'] as string) - ) { - peertubeHelpers.logger.debug('Trying to load AutoColors...') - const autocolors: AutoColors = { - mainForeground: req.query._ac_mainForeground?.toString() ?? '', - mainBackground: req.query._ac_mainBackground?.toString() ?? '', - greyForeground: req.query._ac_greyForeground?.toString() ?? '', - greyBackground: req.query._ac_greyBackground?.toString() ?? '', - menuForeground: req.query._ac_menuForeground?.toString() ?? '', - menuBackground: req.query._ac_menuBackground?.toString() ?? '', - inputForeground: req.query._ac_inputForeground?.toString() ?? '', - inputBackground: req.query._ac_inputBackground?.toString() ?? '', - buttonForeground: req.query._ac_buttonForeground?.toString() ?? '', - buttonBackground: req.query._ac_buttonBackground?.toString() ?? '', - link: req.query._ac_link?.toString() ?? '', - linkHover: req.query._ac_linkHover?.toString() ?? '' + const roomKey = req.params.roomKey + let readonly: boolean | 'noscroll' = false + if (req.query._readonly === 'true') { + readonly = true + } else if (req.query._readonly === 'noscroll') { + readonly = 'noscroll' } - if (!Object.values(autocolors).find(c => c !== '')) { - peertubeHelpers.logger.debug('All AutoColors are empty.') - } else { - const autoColorsTest = areAutoColorsValid(autocolors) - if (autoColorsTest === true) { - autocolorsStyles = ` - - ` - } else { - peertubeHelpers.logger.error('Provided AutoColors are invalid.', autoColorsTest) + + const initConverseJSParam = await getConverseJSParams(options, roomKey, { + readonly, + transparent: req.query._transparent === 'true', + forcetype: req.query.forcetype === '1', + forceDefaultHideMucParticipants: req.query.force_default_hide_muc_participants === '1' + }) + + if (('isError' in initConverseJSParam)) { + throw new LivechatError(initConverseJSParam) + } + + let page = '' + (converseJSIndex as string) + page = page.replace(/{{BASE_STATIC_URL}}/g, initConverseJSParam.staticBaseUrl) + + const settings = await options.settingsManager.getSettings([ + 'converse-theme', 'converse-autocolors' + ]) + + // Adding some custom CSS if relevant... + let autocolorsStyles = '' + if ( + settings['converse-autocolors'] && + isAutoColorsAvailable(settings['converse-theme'] as string) + ) { + peertubeHelpers.logger.debug('Trying to load AutoColors...') + const autocolors: AutoColors = { + mainForeground: req.query._ac_mainForeground?.toString() ?? '', + mainBackground: req.query._ac_mainBackground?.toString() ?? '', + greyForeground: req.query._ac_greyForeground?.toString() ?? '', + greyBackground: req.query._ac_greyBackground?.toString() ?? '', + menuForeground: req.query._ac_menuForeground?.toString() ?? '', + menuBackground: req.query._ac_menuBackground?.toString() ?? '', + inputForeground: req.query._ac_inputForeground?.toString() ?? '', + inputBackground: req.query._ac_inputBackground?.toString() ?? '', + buttonForeground: req.query._ac_buttonForeground?.toString() ?? '', + buttonBackground: req.query._ac_buttonBackground?.toString() ?? '', + link: req.query._ac_link?.toString() ?? '', + linkHover: req.query._ac_linkHover?.toString() ?? '' } + if (!Object.values(autocolors).find(c => c !== '')) { + peertubeHelpers.logger.debug('All AutoColors are empty.') + } else { + const autoColorsTest = areAutoColorsValid(autocolors) + if (autoColorsTest === true) { + autocolorsStyles = ` + + ` + } else { + peertubeHelpers.logger.error('Provided AutoColors are invalid.', autoColorsTest) + } + } + } else { + peertubeHelpers.logger.debug('No AutoColors.') } - } else { - peertubeHelpers.logger.debug('No AutoColors.') + + // ... then insert CSS in the page. + page = page.replace(/{{CONVERSEJS_AUTOCOLORS}}/g, autocolorsStyles) + + // ... and finaly inject all other parameters + page = page.replace('{INIT_CONVERSE_PARAMS}', JSON.stringify(initConverseJSParam)) + res.status(200) + res.type('html') + res.send(page) + } catch (err: LivechatError | any) { + const code = err.livechatError?.code ?? 500 + const additionnalMessage: string = escapeHTML(err.livechatError?.message as string ?? '') + const message: string = escapeHTML(loc('chatroom_not_accessible')) + + res.status(code) + res.send(` + ${message} + +

${message}

+

${additionnalMessage}

+ + `) } - - // ... then insert CSS in the page. - page = page.replace(/{{CONVERSEJS_AUTOCOLORS}}/g, autocolorsStyles) - - // ... and finaly inject all other parameters - page = page.replace('{INIT_CONVERSE_PARAMS}', JSON.stringify(initConverseJSParam)) - res.status(200) - res.type('html') - res.send(page) - } - )) + }) + ) await disableProxyRoute(options) router.post('/http-bind',