Fix #48: Proper 404 and 403 pages when trying to open non-existant chatroom (WIP).

This commit is contained in:
John Livingston 2024-04-04 10:58:16 +02:00
parent 972306aa3e
commit 0719d25f35
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
12 changed files with 198 additions and 104 deletions

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -26,8 +26,12 @@ async function registerRoom (clientOptions: RegisterClientOptions): Promise<void
await displayConverseJS(clientOptions, container, roomKey, 'peertube-fullpage', forceType)
} catch (err) {
console.error('[peertube-plugin-livechat] ' + (err as string))
// FIXME: do a better error page.
rootEl.innerText = await peertubeHelpers.translate(LOC_NOT_FOUND)
// Displaying an error page.
rootEl.innerHTML = ''
const message = document.createElement('div')
message.classList.add('peertube-plugin-livechat-error-message')
message.innerText = await peertubeHelpers.translate(LOC_CHATROOM_NOT_ACCESSIBLE)
rootEl.append(message)
}
}
})

View File

@ -155,7 +155,7 @@ async function displayConverseJS (
}
)
if (!response.ok) {
throw new Error('Can\'t get channel configuration options.')
throw new Error('Can\'t get room configuration.')
}
const converseJSParams: InitConverseJSParams = await (response).json()

View File

@ -214,7 +214,15 @@ function register (registerOptions: RegisterClientOptions): void {
// Loading converseJS...
await displayConverseJS(registerOptions, container, roomkey, 'peertube-video', false)
} catch (err) {
// Displaying an error page.
if (container) {
const message = document.createElement('div')
message.classList.add('peertube-plugin-livechat-error-message')
message.innerText = await peertubeHelpers.translate(LOC_CHATROOM_NOT_ACCESSIBLE)
container.append(message)
}
hackStyles(false)
}
}
@ -229,7 +237,9 @@ function register (registerOptions: RegisterClientOptions): void {
if (window.converse?.livechatDisconnect) { window.converse.livechatDisconnect() }
// Removing from the DOM
container.querySelectorAll('converse-root, .livechat-spinner').forEach(dom => dom.remove())
container.querySelectorAll(
'converse-root, .livechat-spinner, .peertube-plugin-livechat-error-message'
).forEach(dom => dom.remove())
container.setAttribute('peertube-plugin-livechat-state', 'closed')

View File

@ -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."

20
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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<InitConverseJSParams | InitConverseJSParamsError> {
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
}

View File

@ -18,9 +18,17 @@ async function initConfigurationApiRouter (options: RegisterServerOptions, route
router.get('/configuration/room/:roomKey', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
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

View File

@ -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<typeof createProxyServer> | null = null
let currentWebsocketProxy: ReturnType<typeof createProxyServer> | null = null
let currentS2SWebsocketProxy: ReturnType<typeof createProxyServer> | null = null
class LivechatError extends Error {
livechatError: InitConverseJSParamsError
constructor (e: InitConverseJSParamsError) {
super(e.message)
this.livechatError = e
}
}
async function initWebchatRouter (options: RegisterServerOptionsV5): Promise<Router> {
const {
getRouter,
@ -33,100 +46,113 @@ async function initWebchatRouter (options: RegisterServerOptionsV5): Promise<Rou
const router: Router = getRouter()
// eslint-disable-next-line @typescript-eslint/no-misused-promises
router.get('/room/:roomKey', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
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<void> => {
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 = `
<style>
:root {
--peertube-main-foreground: ${autocolors.mainForeground};
--peertube-main-background: ${autocolors.mainBackground};
--peertube-grey-foreground: ${autocolors.greyForeground};
--peertube-grey-background: ${autocolors.greyBackground};
--peertube-menu-foreground: ${autocolors.menuForeground};
--peertube-menu-background: ${autocolors.menuBackground};
--peertube-input-foreground: ${autocolors.inputForeground};
--peertube-input-background: ${autocolors.inputBackground};
--peertube-button-foreground: ${autocolors.buttonForeground};
--peertube-button-background: ${autocolors.buttonBackground};
--peertube-link: ${autocolors.link};
--peertube-link-hover: ${autocolors.linkHover};
}
</style>
`
} 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 = `
<style>
:root {
--peertube-main-foreground: ${autocolors.mainForeground};
--peertube-main-background: ${autocolors.mainBackground};
--peertube-grey-foreground: ${autocolors.greyForeground};
--peertube-grey-background: ${autocolors.greyBackground};
--peertube-menu-foreground: ${autocolors.menuForeground};
--peertube-menu-background: ${autocolors.menuBackground};
--peertube-input-foreground: ${autocolors.inputForeground};
--peertube-input-background: ${autocolors.inputBackground};
--peertube-button-foreground: ${autocolors.buttonForeground};
--peertube-button-background: ${autocolors.buttonBackground};
--peertube-link: ${autocolors.link};
--peertube-link-hover: ${autocolors.linkHover};
}
</style>
`
} 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(`<!DOCTYPE html PUBLIC "-//IETF//DTD HTML 2.0//EN"><html>
<head><title>${message}</title></head>
<body>
<h1>${message}</h1>
<p>${additionnalMessage}</p>
</body>
</html>`)
}
// ... 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',