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. * Some code refactoring.
* New translations: Galician. * 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. * 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 ## 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_LIVECHAT_CONFIGURATION_CHANNEL_FOR_MORE_INFO: string
declare const LOC_INVALID_VALUE: 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) await displayConverseJS(clientOptions, container, roomKey, 'peertube-fullpage', forceType)
} catch (err) { } catch (err) {
console.error('[peertube-plugin-livechat] ' + (err as string)) console.error('[peertube-plugin-livechat] ' + (err as string))
// FIXME: do a better error page. // Displaying an error page.
rootEl.innerText = await peertubeHelpers.translate(LOC_NOT_FOUND) 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) { 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() const converseJSParams: InitConverseJSParams = await (response).json()

View File

@ -214,7 +214,15 @@ function register (registerOptions: RegisterClientOptions): void {
// Loading converseJS... // Loading converseJS...
await displayConverseJS(registerOptions, container, roomkey, 'peertube-video', false) await displayConverseJS(registerOptions, container, roomkey, 'peertube-video', false)
} catch (err) { } 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() } if (window.converse?.livechatDisconnect) { window.converse.livechatDisconnect() }
// Removing from the DOM // 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') container.setAttribute('peertube-plugin-livechat-state', 'closed')

View File

@ -390,3 +390,5 @@ livechat_configuration_channel_bot_nickname: "Bot nickname"
invalid_value: "Invalid value." invalid_value: "Invalid value."
slow_mode_info: "Slow mode is enabled, users can send a message every %1$s seconds." 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": { "dependencies": {
"async": "^3.2.2", "async": "^3.2.2",
"decache": "^4.6.0", "decache": "^4.6.0",
"escape-html": "^1.0.3",
"got": "^11.8.2", "got": "^11.8.2",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"log-rotate": "^0.2.8", "log-rotate": "^0.2.8",
@ -22,6 +23,7 @@
"@peertube/peertube-types": "^5.2.0", "@peertube/peertube-types": "^5.2.0",
"@tsconfig/node12": "^1.0.9", "@tsconfig/node12": "^1.0.9",
"@types/async": "^3.2.9", "@types/async": "^3.2.9",
"@types/escape-html": "^1.0.4",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/got": "^9.6.12", "@types/got": "^9.6.12",
"@types/http-proxy": "^1.17.9", "@types/http-proxy": "^1.17.9",
@ -3896,6 +3898,12 @@
"@types/ms": "*" "@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": { "node_modules/@types/express": {
"version": "4.17.13", "version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
@ -6441,8 +6449,7 @@
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
"dev": true
}, },
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",
@ -15300,6 +15307,12 @@
"@types/ms": "*" "@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": { "@types/express": {
"version": "4.17.13", "version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
@ -17251,8 +17264,7 @@
"escape-html": { "escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
"dev": true
}, },
"escape-string-regexp": { "escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",

View File

@ -35,6 +35,7 @@
"dependencies": { "dependencies": {
"async": "^3.2.2", "async": "^3.2.2",
"decache": "^4.6.0", "decache": "^4.6.0",
"escape-html": "^1.0.3",
"got": "^11.8.2", "got": "^11.8.2",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"log-rotate": "^0.2.8", "log-rotate": "^0.2.8",
@ -46,6 +47,7 @@
"@peertube/peertube-types": "^5.2.0", "@peertube/peertube-types": "^5.2.0",
"@tsconfig/node12": "^1.0.9", "@tsconfig/node12": "^1.0.9",
"@types/async": "^3.2.9", "@types/async": "^3.2.9",
"@types/escape-html": "^1.0.4",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/got": "^9.6.12", "@types/got": "^9.6.12",
"@types/http-proxy": "^1.17.9", "@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. * Returns an object describing the error if access can not be granted.
* @param options server options * @param options server options
* @param roomKey chat room key: video UUID (or channel id when forcetype is true) * @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 ( async function getConverseJSParams (
options: RegisterServerOptionsV5, options: RegisterServerOptionsV5,
roomKey: string, roomKey: string,
params: GetConverseJSParamsParams params: GetConverseJSParamsParams,
userIsConnected?: boolean
): Promise<InitConverseJSParams | InitConverseJSParamsError> { ): Promise<InitConverseJSParams | InitConverseJSParamsError> {
const settings = await options.settingsManager.getSettings([ const settings = await options.settingsManager.getSettings([
'prosody-room-type', 'prosody-room-type',
'disable-websocket', 'disable-websocket',
'converse-theme', 'converse-theme',
'federation-no-remote-chat', '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 { const {
autoViewerMode, forceReadonly, transparent, converseJSTheme autoViewerMode, forceReadonly, transparent, converseJSTheme
} = _interfaceParams(options, settings, params) } = _interfaceParams(options, settings, params)
@ -337,5 +349,6 @@ async function _localRoomJID (
} }
export { export {
getConverseJSParams getConverseJSParams,
InitConverseJSParamsError
} }

View File

@ -18,9 +18,17 @@ async function initConfigurationApiRouter (options: RegisterServerOptions, route
router.get('/configuration/room/:roomKey', asyncMiddleware( router.get('/configuration/room/:roomKey', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction): Promise<void> => { async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
const roomKey = req.params.roomKey const roomKey = req.params.roomKey
const initConverseJSParam = await getConverseJSParams(options, roomKey, {
const user = await options.peertubeHelpers.user.getAuthUser(res)
const initConverseJSParam = await getConverseJSParams(
options,
roomKey,
{
forcetype: req.query.forcetype === '1' forcetype: req.query.forcetype === '1'
}) },
!!user
)
if (('isError' in initConverseJSParam) && initConverseJSParam.isError) { if (('isError' in initConverseJSParam) && initConverseJSParam.isError) {
res.sendStatus(initConverseJSParam.code) res.sendStatus(initConverseJSParam.code)
return return

View File

@ -1,6 +1,8 @@
import type { RegisterServerOptions } from '@peertube/peertube-types' import type { RegisterServerOptions } from '@peertube/peertube-types'
import type { Router, Request, Response, NextFunction } from 'express' 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 { createProxyServer } from 'http-proxy'
import { RegisterServerOptionsV5, isUserAdmin } from '../helpers' import { RegisterServerOptionsV5, isUserAdmin } from '../helpers'
import { asyncMiddleware } from '../middlewares/async' import { asyncMiddleware } from '../middlewares/async'
@ -10,8 +12,11 @@ import { getConverseJSParams } from '../conversejs/params'
import { setCurrentProsody, delCurrentProsody } from '../prosody/api/host' import { setCurrentProsody, delCurrentProsody } from '../prosody/api/host'
import { getChannelInfosById } from '../database/channel' import { getChannelInfosById } from '../database/channel'
import { listProsodyRooms } from '../prosody/api/manage-rooms' import { listProsodyRooms } from '../prosody/api/manage-rooms'
import { loc } from '../loc'
import * as path from 'path' import * as path from 'path'
const escapeHTML = require('escape-html')
const fs = require('fs').promises const fs = require('fs').promises
interface ProsodyProxyInfo { interface ProsodyProxyInfo {
@ -22,6 +27,14 @@ let currentHttpBindProxy: ReturnType<typeof createProxyServer> | null = null
let currentWebsocketProxy: ReturnType<typeof createProxyServer> | null = null let currentWebsocketProxy: ReturnType<typeof createProxyServer> | null = null
let currentS2SWebsocketProxy: 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> { async function initWebchatRouter (options: RegisterServerOptionsV5): Promise<Router> {
const { const {
getRouter, getRouter,
@ -33,8 +46,9 @@ async function initWebchatRouter (options: RegisterServerOptionsV5): Promise<Rou
const router: Router = getRouter() const router: Router = getRouter()
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
router.get('/room/:roomKey', asyncMiddleware( router.get('/room/:roomKey',
async (req: Request, res: Response, _next: NextFunction): Promise<void> => { asyncMiddleware(async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
try {
res.removeHeader('X-Frame-Options') // this route can be opened in an iframe res.removeHeader('X-Frame-Options') // this route can be opened in an iframe
const roomKey = req.params.roomKey const roomKey = req.params.roomKey
@ -53,9 +67,7 @@ async function initWebchatRouter (options: RegisterServerOptionsV5): Promise<Rou
}) })
if (('isError' in initConverseJSParam)) { if (('isError' in initConverseJSParam)) {
res.status(initConverseJSParam.code) throw new LivechatError(initConverseJSParam)
res.send(initConverseJSParam.message)
return
} }
let page = '' + (converseJSIndex as string) let page = '' + (converseJSIndex as string)
@ -125,8 +137,22 @@ async function initWebchatRouter (options: RegisterServerOptionsV5): Promise<Rou
res.status(200) res.status(200)
res.type('html') res.type('html')
res.send(page) 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>`)
} }
)) })
)
await disableProxyRoute(options) await disableProxyRoute(options)
router.post('/http-bind', router.post('/http-bind',