Merge branch 'main' of https://github.com/JohnXLivingston/peertube-plugin-livechat
This commit is contained in:
@ -38,6 +38,7 @@ async function sanitizeChannelConfigurationOptions (
|
||||
|
||||
const moderationData = data.moderation ?? {} // comes with livechat 10.3.0
|
||||
moderationData.delay ??= 0
|
||||
moderationData.anonymize ??= false // comes with livechat 11.0.0
|
||||
|
||||
// mute not present in livechat <= 10.2.0
|
||||
const mute = data.mute ?? {}
|
||||
@ -73,7 +74,8 @@ async function sanitizeChannelConfigurationOptions (
|
||||
anonymous: _readBoolean(mute, 'anonymous')
|
||||
},
|
||||
moderation: {
|
||||
delay: _readInteger(moderationData, 'delay', 0, 60)
|
||||
delay: _readInteger(moderationData, 'delay', 0, 60),
|
||||
anonymize: _readBoolean(moderationData, 'anonymize')
|
||||
}
|
||||
}
|
||||
if (terms !== undefined) {
|
||||
|
@ -54,7 +54,8 @@ function getDefaultChannelConfigurationOptions (_options: RegisterServerOptions)
|
||||
anonymous: false
|
||||
},
|
||||
moderation: {
|
||||
delay: 0
|
||||
delay: 0,
|
||||
anonymize: false
|
||||
},
|
||||
terms: undefined
|
||||
}
|
||||
|
@ -87,8 +87,8 @@ class ExternalAuthOIDC {
|
||||
private readonly redirectUrl: string
|
||||
private readonly connectUrl: string
|
||||
private readonly externalVirtualhost: string
|
||||
private readonly avatarsDir: string
|
||||
private readonly avatarsFiles: string[]
|
||||
private readonly avatarsDir: string | undefined
|
||||
private readonly avatarsFiles: string[] | undefined
|
||||
|
||||
private readonly encryptionOptions = {
|
||||
algorithm: 'aes256' as string,
|
||||
@ -129,8 +129,8 @@ class ExternalAuthOIDC {
|
||||
connectUrl: string
|
||||
redirectUrl: string
|
||||
externalVirtualhost: string
|
||||
avatarsDir: string
|
||||
avatarsFiles: string[]
|
||||
avatarsDir?: string
|
||||
avatarsFiles?: string[]
|
||||
}) {
|
||||
this.logger = {
|
||||
debug: (s) => params.logger.debug('[ExternalAuthOIDC] ' + s),
|
||||
@ -591,8 +591,8 @@ class ExternalAuthOIDC {
|
||||
*/
|
||||
private async getRandomAvatar (): Promise<undefined | ExternalAccountInfos['avatar']> {
|
||||
try {
|
||||
if (!this.avatarsDir || !this.avatarsFiles.length) {
|
||||
throw new Error('Seems there is no default avatars')
|
||||
if (!this.avatarsDir || !this.avatarsFiles?.length) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const file = this.avatarsFiles[Math.floor(Math.random() * this.avatarsFiles.length)]
|
||||
|
210
server/lib/firewall/config.ts
Normal file
210
server/lib/firewall/config.ts
Normal file
@ -0,0 +1,210 @@
|
||||
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||
import type { AdminFirewallConfiguration } from '../../../shared/lib/types'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import {
|
||||
firewallNameRegexp, maxFirewallFileSize, maxFirewallFiles, maxFirewallNameLength
|
||||
} from '../../../shared/lib/admin-firewall'
|
||||
|
||||
/**
|
||||
* Indicates if the firewall configuration can be changed in the Peertube web interface.
|
||||
* Sys admins can disable this feature by creating a special file in the plugin folder.
|
||||
* @param options Peertube server options
|
||||
*/
|
||||
export async function canEditFirewallConfig (options: RegisterServerOptions): Promise<boolean> {
|
||||
const peertubeHelpers = options.peertubeHelpers
|
||||
const logger = peertubeHelpers.logger
|
||||
if (!peertubeHelpers.plugin) {
|
||||
return false
|
||||
}
|
||||
|
||||
const filepath = path.resolve(peertubeHelpers.plugin.getDataDirectoryPath(), 'disable_mod_firewall_editing')
|
||||
try {
|
||||
// Testing if file exist by reading it.
|
||||
await fs.promises.readFile(filepath)
|
||||
return false
|
||||
} catch (err: any) {
|
||||
if (('code' in err) && err.code === 'ENOENT') {
|
||||
// File does not exist
|
||||
return true
|
||||
}
|
||||
logger.error(err)
|
||||
// Here it is safer to disable the editing...
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of mod_firewall configuration files.
|
||||
* @param options: Peertube server options.
|
||||
* @param dir the path to the directory containing these configuration files.
|
||||
* @param includeDisabled if true, disabled files are included in the results.
|
||||
*/
|
||||
export async function listModFirewallFiles (
|
||||
options: RegisterServerOptions,
|
||||
dir: string,
|
||||
includeDisabled?: boolean
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
const files = (await fs.promises.readdir(dir, { withFileTypes: true })).filter(file => {
|
||||
if (!file.isFile()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
file.name.endsWith('.pfw') &&
|
||||
// we only load valid names, to avoid having files that could not be edited from frontend
|
||||
firewallNameRegexp.test(file.name.substring(0, file.name.length - 4))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
includeDisabled &&
|
||||
file.name.endsWith('.pfw.disabled') &&
|
||||
firewallNameRegexp.test(file.name.substring(0, file.name.length - 13))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
return files.map(f => path.join(dir, f.name)).sort()
|
||||
} catch (err) {
|
||||
// should be that the directory does not exists
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the modFirewall configuration.
|
||||
* @param options Peertube server options
|
||||
* @param dir the path to the directory containing these configuration files.
|
||||
* @throws will throw an error if it can't read any of the configuration file.
|
||||
*/
|
||||
export async function getModFirewallConfig (
|
||||
options: RegisterServerOptions,
|
||||
dir: string
|
||||
): Promise<AdminFirewallConfiguration> {
|
||||
const filePaths = await listModFirewallFiles(options, dir, true)
|
||||
|
||||
const files = []
|
||||
for (const filePath of filePaths) {
|
||||
const content = (await fs.promises.readFile(filePath)).toString()
|
||||
const name = path.basename(filePath).replace(/\.pfw(\.disabled)?$/, '')
|
||||
files.push({
|
||||
name,
|
||||
content,
|
||||
enabled: !filePath.endsWith('.disabled')
|
||||
})
|
||||
}
|
||||
|
||||
const enabled = (await options.settingsManager.getSetting('prosody-firewall-enabled')) === true
|
||||
|
||||
return {
|
||||
enabled,
|
||||
files
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize any data received from the frontend, to store in modFirewall configuration.
|
||||
* Throws an exception if data is invalid.
|
||||
* @param options Peertube server options
|
||||
* @param data Incoming data
|
||||
*/
|
||||
export async function sanitizeModFirewallConfig (
|
||||
options: RegisterServerOptions,
|
||||
data: any
|
||||
): Promise<AdminFirewallConfiguration> {
|
||||
if (typeof data !== 'object') {
|
||||
throw new Error('Invalid data type')
|
||||
}
|
||||
if (!Array.isArray(data.files)) {
|
||||
throw new Error('Invalid data.files')
|
||||
}
|
||||
|
||||
if (data.files.length > maxFirewallFiles) {
|
||||
throw new Error('Too many files')
|
||||
}
|
||||
|
||||
const files: AdminFirewallConfiguration['files'] = []
|
||||
for (const entry of data.files) {
|
||||
if (typeof entry !== 'object') {
|
||||
throw new Error('Invalid data in data.files')
|
||||
}
|
||||
if (typeof entry.enabled !== 'boolean') {
|
||||
throw new Error('Invalid data in data.files (enabled)')
|
||||
}
|
||||
if (typeof entry.name !== 'string') {
|
||||
throw new Error('Invalid data in data.files (name)')
|
||||
}
|
||||
if (typeof entry.content !== 'string') {
|
||||
throw new Error('Invalid data in data.files (content)')
|
||||
}
|
||||
|
||||
if (entry.name.length > maxFirewallNameLength || !firewallNameRegexp.test(entry.name)) {
|
||||
throw new Error('Invalid name in data.files')
|
||||
}
|
||||
if (entry.content.length > maxFirewallFileSize) {
|
||||
throw new Error('File content too big in data.files')
|
||||
}
|
||||
|
||||
files.push({
|
||||
enabled: entry.enabled,
|
||||
name: entry.name,
|
||||
content: entry.content
|
||||
})
|
||||
}
|
||||
|
||||
const result = {
|
||||
enabled: !!data.enabled, // this is not saved, so no need to check type.
|
||||
files
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the modFirewall configuration.
|
||||
* FIXME: currently, if the save fails on one file, remaining files will not be saved. So there is a risk of data loss.
|
||||
* @param options Peertube server options
|
||||
* @param dir the path to the directory containing these configuration files.
|
||||
* @param config the configuration to save
|
||||
* @throws will throw an error if it can't read any of the configuration file.
|
||||
*/
|
||||
export async function saveModFirewallConfig (
|
||||
options: RegisterServerOptions,
|
||||
dir: string,
|
||||
config: AdminFirewallConfiguration
|
||||
): Promise<void> {
|
||||
const logger = options.peertubeHelpers.logger
|
||||
|
||||
const previousFiles = await listModFirewallFiles(options, dir, true)
|
||||
|
||||
logger.debug('[mod-firewall-lib] Creating the ' + dir + ' directory.')
|
||||
await fs.promises.mkdir(dir, { recursive: true })
|
||||
|
||||
const seen = new Map<string, true>()
|
||||
for (const f of config.files) {
|
||||
const filePath = path.join(
|
||||
dir,
|
||||
f.name + '.pfw' + (f.enabled ? '' : '.disabled')
|
||||
)
|
||||
logger.info('[mod-firewall-lib] Saving ' + filePath)
|
||||
await fs.promises.writeFile(filePath, f.content)
|
||||
seen.set(filePath, true)
|
||||
}
|
||||
|
||||
// Removing deprecated files:
|
||||
for (const p of previousFiles) {
|
||||
if (seen.has(p)) { continue }
|
||||
logger.info('[mod-firewall-lib] Deleting deprecated file ' + p)
|
||||
await fs.promises.rm(p)
|
||||
}
|
||||
}
|
31
server/lib/middlewares/is-admin.ts
Normal file
31
server/lib/middlewares/is-admin.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||
import type { Request, Response, NextFunction } from 'express'
|
||||
import type { RequestPromiseHandler } from './async'
|
||||
import { isUserAdmin } from '../helpers'
|
||||
|
||||
/**
|
||||
* Returns a middleware handler to check if advanced configuration is not disabled
|
||||
* @param options Peertube server options
|
||||
* @returns middleware function
|
||||
*/
|
||||
function checkUserIsAdminMiddleware (options: RegisterServerOptions): RequestPromiseHandler {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger = options.peertubeHelpers.logger
|
||||
if (!await isUserAdmin(options, res)) {
|
||||
logger.warn('Current user tries to access a page only allowed for admins, and has no right.')
|
||||
res.sendStatus(403)
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug('User is admin, can access the page..')
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
checkUserIsAdminMiddleware
|
||||
}
|
@ -3,11 +3,77 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||
import { pluginShortName } from '../helpers'
|
||||
|
||||
async function migrateSettings (options: RegisterServerOptions): Promise<void> {
|
||||
const logger = options.peertubeHelpers.logger
|
||||
logger.info('Checking if there is a migration script to launch...')
|
||||
// 2022-10-10: as we removed the «chat-type» settings, there is no migration needed for now.
|
||||
// 2024-09-02: concord theme was removed from ConverseJS, must change if used.
|
||||
await _migrateConverseTheme(options)
|
||||
}
|
||||
|
||||
async function _migrateConverseTheme (options: RegisterServerOptions): Promise<void> {
|
||||
const peertubeHelpers = options.peertubeHelpers
|
||||
const logger = peertubeHelpers.logger
|
||||
// NB: we cant use safely settingsManager.getSetting, because settings are not registered yet.
|
||||
logger.info('Checking if we need to migrate converse-theme')
|
||||
if (!/^[-a-z]+$/.test(pluginShortName)) {
|
||||
// to prevent sql injection... be sure there is no special char here.
|
||||
throw new Error(`Wrong pluginShortName '${pluginShortName}'`)
|
||||
}
|
||||
const [results] = await peertubeHelpers.database.query(
|
||||
'SELECT "settings" FROM "plugin"' +
|
||||
' WHERE "plugin"."name" = :pluginShortName',
|
||||
{
|
||||
replacements: {
|
||||
pluginShortName
|
||||
}
|
||||
}
|
||||
)
|
||||
if (!Array.isArray(results)) {
|
||||
throw new Error('_migrateConverseTheme: query result is not an array.')
|
||||
}
|
||||
if (results.length === 0) {
|
||||
logger.error('Plugin not found in database')
|
||||
return
|
||||
}
|
||||
if (results.length > 1) {
|
||||
logger.error('Multiple lines for plugin in database, dont know which one to migrate... Aborting.')
|
||||
return
|
||||
}
|
||||
const settings = results[0].settings
|
||||
if (!settings) {
|
||||
logger.info('Plugin settings are empty in database, no migration needed.')
|
||||
return
|
||||
}
|
||||
if (typeof settings !== 'object') {
|
||||
logger.error('Plugin settings in database seems to be invalid json')
|
||||
return
|
||||
}
|
||||
if (!('converse-theme' in settings)) {
|
||||
logger.debug('The setting converse-theme is not here, no need to migrate.')
|
||||
return
|
||||
}
|
||||
if (settings['converse-theme'] !== 'concord') {
|
||||
logger.debug('The setting converse-theme is not set to concord, no need to migrate.')
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('The setting converse-theme is set to concord, we must replace by peertube..')
|
||||
await peertubeHelpers.database.query(
|
||||
'UPDATE "plugin" ' +
|
||||
' SET "settings" = "settings" || :value ' +
|
||||
' WHERE "name" = :pluginShortName',
|
||||
{
|
||||
replacements: {
|
||||
pluginShortName,
|
||||
value: JSON.stringify({
|
||||
'converse-theme': 'peertube'
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
|
@ -18,6 +18,7 @@ import { getRemoteServerInfosDir } from '../federation/storage'
|
||||
import { BotConfiguration } from '../configuration/bot'
|
||||
import { debugMucAdmins } from '../debug'
|
||||
import { ExternalAuthOIDC } from '../external-auth/oidc'
|
||||
import { listModFirewallFiles } from '../firewall/config'
|
||||
|
||||
async function getWorkingDir (options: RegisterServerOptions): Promise<string> {
|
||||
const peertubeHelpers = options.peertubeHelpers
|
||||
@ -102,14 +103,23 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise<Pro
|
||||
}
|
||||
|
||||
let avatarSet: AvatarSet = (settings['avatar-set'] ?? 'sepia') as AvatarSet
|
||||
if (!['sepia', 'cat', 'bird', 'fenec', 'abstract', 'legacy', 'nctv'].includes(avatarSet)) {
|
||||
logger.error('Invalid avatar-set setting, using sepia as default')
|
||||
avatarSet = 'sepia'
|
||||
let avatarsDir
|
||||
let avatarsFiles
|
||||
let botAvatarsDir
|
||||
let botAvatarsFiles
|
||||
if (avatarSet === 'none') {
|
||||
botAvatarsDir = path.resolve(__dirname, '../../bot_avatars/', 'sepia') // fallback to default avatars for the bot
|
||||
botAvatarsFiles = await _listAvatars(botAvatarsDir)
|
||||
} else {
|
||||
if (!['sepia', 'cat', 'bird', 'fenec', 'abstract', 'legacy', 'nctv'].includes(avatarSet)) {
|
||||
logger.error('Invalid avatar-set setting, using sepia as default')
|
||||
avatarSet = 'sepia'
|
||||
}
|
||||
avatarsDir = path.resolve(__dirname, '../../avatars/', avatarSet)
|
||||
avatarsFiles = await _listAvatars(avatarsDir)
|
||||
botAvatarsDir = path.resolve(__dirname, '../../bot_avatars/', avatarSet)
|
||||
botAvatarsFiles = await _listAvatars(botAvatarsDir)
|
||||
}
|
||||
const avatarsDir = path.resolve(__dirname, '../../avatars/', avatarSet)
|
||||
const avatarsFiles = await _listAvatars(avatarsDir)
|
||||
const botAvatarsDir = path.resolve(__dirname, '../../bot_avatars/', avatarSet)
|
||||
const botAvatarsFiles = await _listAvatars(botAvatarsDir)
|
||||
|
||||
return {
|
||||
dir: dir,
|
||||
@ -130,7 +140,8 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise<Pro
|
||||
execCtl,
|
||||
execCtlArgs,
|
||||
appImageToExtract,
|
||||
appImageExtractPath
|
||||
appImageExtractPath,
|
||||
modFirewallFiles: path.resolve(dir, 'mod_firewall_config')
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +187,8 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
|
||||
'auto-ban-anonymous-ip',
|
||||
'federation-dont-publish-remotely',
|
||||
'disable-channel-configuration',
|
||||
'chat-terms'
|
||||
'chat-terms',
|
||||
'prosody-firewall-enabled'
|
||||
])
|
||||
|
||||
const valuesToHideInDiagnostic = new Map<string, string>()
|
||||
@ -356,7 +368,9 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
|
||||
|
||||
config.useManageRoomsApi(apikey)
|
||||
config.usePeertubeVCards(basePeertubeUrl)
|
||||
config.useAnonymousRandomVCards(paths.avatars, paths.avatarsFiles)
|
||||
if (paths.avatars && paths.avatarsFiles) {
|
||||
config.useAnonymousRandomVCards(paths.avatars, paths.avatarsFiles)
|
||||
}
|
||||
|
||||
if (useBots) {
|
||||
config.useBotsVirtualHost(paths.botAvatars, paths.botAvatarsFiles)
|
||||
@ -368,6 +382,13 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
|
||||
|
||||
config.usePoll()
|
||||
|
||||
if (settings['prosody-firewall-enabled'] === true) {
|
||||
const modFirewallFiles = await listModFirewallFiles(options, paths.modFirewallFiles)
|
||||
// We load the module, even if there is no configuration file.
|
||||
// So we will be sure that a Prosody reload is enought to take into account any change.
|
||||
config.useModFirewall(modFirewallFiles)
|
||||
}
|
||||
|
||||
config.useTestModule(apikey, testApiUrl)
|
||||
|
||||
const debugMucAdminJids = debugMucAdmins(options)
|
||||
|
@ -255,6 +255,11 @@ class ProsodyConfigContent {
|
||||
|
||||
this.muc.add('modules_enabled', 'muc_moderation_delay')
|
||||
this.muc.set('moderation_delay_form_position', 118)
|
||||
|
||||
this.muc.add('modules_enabled', 'muc_anonymize_moderation_actions')
|
||||
this.muc.set('anonymize_moderation_actions_form_position', 117)
|
||||
|
||||
this.muc.add('modules_enabled', 'muc_mam_search')
|
||||
}
|
||||
|
||||
useAnonymous (autoBanIP: boolean): void {
|
||||
@ -548,6 +553,15 @@ class ProsodyConfigContent {
|
||||
this.muc.set('poll_string_vote_instructions', loc('poll_vote_instructions_xmpp'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable mod_firewall.
|
||||
* @param files file paths to load (ordered)
|
||||
*/
|
||||
useModFirewall (files: string[]): void {
|
||||
this.global.add('modules_enabled', 'firewall')
|
||||
this.global.set('firewall_scripts', files)
|
||||
}
|
||||
|
||||
addMucAdmins (jids: string[]): void {
|
||||
for (const jid of jids) {
|
||||
this.muc.add('admins', jid)
|
||||
|
@ -12,8 +12,8 @@ interface ProsodyFilePaths {
|
||||
certs?: string
|
||||
certsDirIsCustom: boolean
|
||||
modules: string
|
||||
avatars: string
|
||||
avatarsFiles: string[]
|
||||
avatars?: string
|
||||
avatarsFiles?: string[]
|
||||
botAvatars: string
|
||||
botAvatarsFiles: string[]
|
||||
exec?: string
|
||||
@ -22,6 +22,7 @@ interface ProsodyFilePaths {
|
||||
execCtlArgs: string[]
|
||||
appImageToExtract?: string
|
||||
appImageExtractPath: string
|
||||
modFirewallFiles: string
|
||||
}
|
||||
|
||||
export {
|
||||
|
@ -14,6 +14,7 @@ import { initFederationServerInfosApiRouter } from './api/federation-server-info
|
||||
import { initConfigurationApiRouter } from './api/configuration'
|
||||
import { initPromoteApiRouter } from './api/promote'
|
||||
import { initEmojisRouter } from './emojis'
|
||||
import { initAdminFirewallApiRouter } from './api/admin/firewall'
|
||||
|
||||
/**
|
||||
* Initiate API routes
|
||||
@ -45,6 +46,8 @@ async function initApiRouter (options: RegisterServerOptions): Promise<Router> {
|
||||
await initPromoteApiRouter(options, router)
|
||||
await initEmojisRouter(options, router)
|
||||
|
||||
await initAdminFirewallApiRouter(options, router)
|
||||
|
||||
if (isDebugMode(options)) {
|
||||
// Only add this route if the debug mode is enabled at time of the server launch.
|
||||
// Note: the isDebugMode will be tested again when the API is called.
|
||||
|
81
server/lib/routers/api/admin/firewall.ts
Normal file
81
server/lib/routers/api/admin/firewall.ts
Normal file
@ -0,0 +1,81 @@
|
||||
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||
import type { Router, Request, Response, NextFunction } from 'express'
|
||||
import type { AdminFirewallConfiguration } from '../../../../../shared/lib/types'
|
||||
import { asyncMiddleware, RequestPromiseHandler } from '../../../middlewares/async'
|
||||
import { checkUserIsAdminMiddleware } from '../../../middlewares/is-admin'
|
||||
import {
|
||||
getModFirewallConfig, sanitizeModFirewallConfig, saveModFirewallConfig, canEditFirewallConfig
|
||||
} from '../../../firewall/config'
|
||||
import { getProsodyFilePaths, writeProsodyConfig } from '../../../prosody/config'
|
||||
import { reloadProsody } from '../../../prosody/ctl'
|
||||
|
||||
function canEditFirewallConfigMiddleware (options: RegisterServerOptions): RequestPromiseHandler {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!await canEditFirewallConfig(options)) {
|
||||
options.peertubeHelpers.logger.info('Firewall configuration editing is disabled')
|
||||
res.sendStatus(403)
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
async function initAdminFirewallApiRouter (options: RegisterServerOptions, router: Router): Promise<void> {
|
||||
const logger = options.peertubeHelpers.logger
|
||||
|
||||
router.get('/admin/firewall', asyncMiddleware([
|
||||
checkUserIsAdminMiddleware(options),
|
||||
canEditFirewallConfigMiddleware(options),
|
||||
async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
|
||||
try {
|
||||
const prosodyPaths = await getProsodyFilePaths(options)
|
||||
const result: AdminFirewallConfiguration = await getModFirewallConfig(options, prosodyPaths.modFirewallFiles)
|
||||
res.status(200)
|
||||
res.json(result)
|
||||
} catch (err) {
|
||||
options.peertubeHelpers.logger.error(err)
|
||||
res.sendStatus(500)
|
||||
}
|
||||
}
|
||||
]))
|
||||
|
||||
router.post('/admin/firewall', asyncMiddleware([
|
||||
checkUserIsAdminMiddleware(options),
|
||||
canEditFirewallConfigMiddleware(options),
|
||||
async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
|
||||
try {
|
||||
const prosodyPaths = await getProsodyFilePaths(options)
|
||||
|
||||
let data: AdminFirewallConfiguration
|
||||
try {
|
||||
data = await sanitizeModFirewallConfig(options, req.body)
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
|
||||
await saveModFirewallConfig(options, prosodyPaths.modFirewallFiles, data)
|
||||
|
||||
logger.info('Just saved a new mod_firewall const, must rewrite Prosody configuration file, and reload Prosody.')
|
||||
await writeProsodyConfig(options)
|
||||
await reloadProsody(options)
|
||||
|
||||
const result: AdminFirewallConfiguration = await getModFirewallConfig(options, prosodyPaths.modFirewallFiles)
|
||||
res.status(200)
|
||||
res.json(result)
|
||||
} catch (err) {
|
||||
options.peertubeHelpers.logger.error(err)
|
||||
res.sendStatus(500)
|
||||
}
|
||||
}
|
||||
]))
|
||||
}
|
||||
|
||||
export {
|
||||
initAdminFirewallApiRouter
|
||||
}
|
@ -39,6 +39,7 @@ interface RoomDefaults {
|
||||
mute_anonymous?: boolean
|
||||
livechat_muc_terms?: string
|
||||
moderation_delay?: number
|
||||
anonymize_moderation_actions?: boolean
|
||||
}
|
||||
affiliations?: Affiliations
|
||||
}
|
||||
@ -54,7 +55,8 @@ async function _getChannelSpecificOptions (
|
||||
slow_mode_duration: channelOptions.slowMode.duration,
|
||||
mute_anonymous: channelOptions.mute.anonymous,
|
||||
livechat_muc_terms: channelOptions.terms,
|
||||
moderation_delay: channelOptions.moderation.delay
|
||||
moderation_delay: channelOptions.moderation.delay,
|
||||
anonymize_moderation_actions: channelOptions.moderation.anonymize
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,10 @@ import { ExternalAuthOIDC, ExternalAuthOIDCType } from './external-auth/oidc'
|
||||
import { Emojis } from './emojis'
|
||||
import { LivechatProsodyAuth } from './prosody/auth'
|
||||
import { loc } from './loc'
|
||||
import { canEditFirewallConfig } from './firewall/config'
|
||||
const escapeHTML = require('escape-html')
|
||||
|
||||
type AvatarSet = 'sepia' | 'cat' | 'bird' | 'fenec' | 'abstract' | 'legacy' | 'nctv'
|
||||
type AvatarSet = 'sepia' | 'cat' | 'bird' | 'fenec' | 'abstract' | 'legacy' | 'nctv' | 'none'
|
||||
|
||||
async function initSettings (options: RegisterServerOptions): Promise<void> {
|
||||
const { peertubeHelpers, settingsManager } = options
|
||||
@ -27,7 +28,7 @@ async function initSettings (options: RegisterServerOptions): Promise<void> {
|
||||
initAdvancedChannelCustomizationSettings(options)
|
||||
initChatBehaviourSettings(options)
|
||||
initThemingSettings(options)
|
||||
initChatServerAdvancedSettings(options)
|
||||
await initChatServerAdvancedSettings(options)
|
||||
|
||||
await ExternalAuthOIDC.initSingletons(options)
|
||||
const loadOidcs = (): void => {
|
||||
@ -511,7 +512,8 @@ function initThemingSettings ({ registerSetting }: RegisterServerOptions): void
|
||||
{ value: 'bird', label: loc('avatar_set_option_bird') },
|
||||
{ value: 'fenec', label: loc('avatar_set_option_fenec') },
|
||||
{ value: 'abstract', label: loc('avatar_set_option_abstract') },
|
||||
{ value: 'legacy', label: loc('avatar_set_option_legacy') }
|
||||
{ value: 'legacy', label: loc('avatar_set_option_legacy') },
|
||||
{ value: 'none', label: loc('avatar_set_option_none') }
|
||||
] as Array<{
|
||||
value: AvatarSet
|
||||
label: string
|
||||
@ -525,9 +527,9 @@ function initThemingSettings ({ registerSetting }: RegisterServerOptions): void
|
||||
default: 'peertube' as ConverseJSTheme,
|
||||
private: false,
|
||||
options: [
|
||||
{ value: 'peertube', label: loc('peertube') },
|
||||
{ value: 'default', label: loc('default') },
|
||||
{ value: 'concord', label: loc('concord') }
|
||||
{ value: 'peertube', label: loc('converse_theme_option_peertube') },
|
||||
{ value: 'default', label: loc('converse_theme_option_default') },
|
||||
{ value: 'cyberpunk', label: loc('converse_theme_option_cyberpunk') }
|
||||
] as Array<{value: ConverseJSTheme, label: string}>,
|
||||
descriptionHTML: loc('converse_theme_description')
|
||||
})
|
||||
@ -555,7 +557,9 @@ function initThemingSettings ({ registerSetting }: RegisterServerOptions): void
|
||||
* Registers settings related to the "Chat server advanded settings" section.
|
||||
* @param param0 server options
|
||||
*/
|
||||
function initChatServerAdvancedSettings ({ registerSetting }: RegisterServerOptions): void {
|
||||
async function initChatServerAdvancedSettings (options: RegisterServerOptions): Promise<void> {
|
||||
const { registerSetting } = options
|
||||
|
||||
registerSetting({
|
||||
name: 'prosody-advanced',
|
||||
type: 'html',
|
||||
@ -723,6 +727,23 @@ function initChatServerAdvancedSettings ({ registerSetting }: RegisterServerOpti
|
||||
private: true,
|
||||
descriptionHTML: loc('prosody_components_list_description')
|
||||
})
|
||||
|
||||
registerSetting({
|
||||
name: 'prosody-firewall-enabled',
|
||||
label: loc('prosody_firewall_label'),
|
||||
type: 'input-checkbox',
|
||||
default: false,
|
||||
private: true,
|
||||
descriptionHTML: loc('prosody_firewall_description')
|
||||
})
|
||||
if (await canEditFirewallConfig(options)) {
|
||||
registerSetting({
|
||||
type: 'html',
|
||||
name: 'prosody-firewall-configure-button',
|
||||
private: true,
|
||||
descriptionHTML: loc('prosody_firewall_configure_button')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
|
Reference in New Issue
Block a user