XMPP external components.
This commit is contained in:
parent
96598f07d1
commit
df3f87e903
@ -2,6 +2,10 @@
|
||||
|
||||
## (unreleased yet)
|
||||
|
||||
### Features
|
||||
|
||||
* Builtin Prosody: you can now allow «external XMPP components» to connect. This can be used for exemple to connect bots or bridges. For now, only connections from localhost will be allowed.
|
||||
|
||||
### Minor changes and fixes
|
||||
|
||||
* Spanish translations (thanks [rnek0](https://github.com/rnek0)).
|
||||
|
@ -211,12 +211,19 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
|
||||
case 'prosody-muc-log-by-default':
|
||||
case 'prosody-muc-expiration':
|
||||
case 'prosody-c2s':
|
||||
case 'prosody-components':
|
||||
return options.formValues['chat-type'] !== ('builtin-prosody' as ChatType)
|
||||
case 'prosody-c2s-port':
|
||||
return !(
|
||||
options.formValues['chat-type'] === ('builtin-prosody' as ChatType) &&
|
||||
options.formValues['prosody-c2s'] === true
|
||||
)
|
||||
case 'prosody-components-port':
|
||||
case 'prosody-components-list':
|
||||
return !(
|
||||
options.formValues['chat-type'] === ('builtin-prosody' as ChatType) &&
|
||||
options.formValues['prosody-components'] === true
|
||||
)
|
||||
case 'chat-server':
|
||||
case 'chat-room':
|
||||
case 'chat-bosh-uri':
|
||||
|
@ -100,6 +100,15 @@ The port that will be used by the c2s module of the builtin Prosody server.
|
||||
XMPP clients shall use this port to connect.
|
||||
Change it if this port is already in use on your server.
|
||||
|
||||
#### Enable external XMPP components
|
||||
|
||||
This settings enable XMPP external components to connect to the server.
|
||||
For now, this option **only allows connections from localhost components**.
|
||||
|
||||
This feature could be used to connect bridges or bots.
|
||||
|
||||
More informations on Prosody external components [here](https://prosody.im/doc/components).
|
||||
|
||||
## Moderation
|
||||
|
||||
You can access rooms settings and moderation tools by opening the chat in a new window,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { ProsodyLogLevel } from './config/content'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { getBaseRouterRoute } from '../helpers'
|
||||
@ -5,7 +6,7 @@ import { ProsodyFilePaths } from './config/paths'
|
||||
import { ConfigLogExpiration, ProsodyConfigContent } from './config/content'
|
||||
import { getProsodyDomain } from './config/domain'
|
||||
import { getAPIKey } from '../apikey'
|
||||
import type { ProsodyLogLevel } from './config/content'
|
||||
import { parseExternalComponents } from './config/components'
|
||||
|
||||
async function getWorkingDir (options: RegisterServerOptions): Promise<string> {
|
||||
const peertubeHelpers = options.peertubeHelpers
|
||||
@ -72,7 +73,7 @@ interface ProsodyConfig {
|
||||
roomType: 'video' | 'channel'
|
||||
logByDefault: boolean
|
||||
logExpiration: ConfigLogExpiration
|
||||
valuesToHideInDiagnostic: {[key: string]: string}
|
||||
valuesToHideInDiagnostic: Map<string, string>
|
||||
}
|
||||
async function getProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
|
||||
const logger = options.peertubeHelpers.logger
|
||||
@ -83,12 +84,15 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
|
||||
'prosody-muc-log-by-default',
|
||||
'prosody-muc-expiration',
|
||||
'prosody-c2s',
|
||||
'prosody-c2s-port',
|
||||
'prosody-room-type',
|
||||
'prosody-peertube-uri',
|
||||
'prosody-c2s-port'
|
||||
'prosody-components',
|
||||
'prosody-components-port',
|
||||
'prosody-components-list'
|
||||
])
|
||||
|
||||
const valuesToHideInDiagnostic: {[key: string]: string} = {}
|
||||
const valuesToHideInDiagnostic = new Map<string, string>()
|
||||
const port = (settings['prosody-port'] as string) || '52800'
|
||||
if (!/^\d+$/.test(port)) {
|
||||
throw new Error('Invalid port')
|
||||
@ -96,12 +100,13 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
|
||||
const logByDefault = (settings['prosody-muc-log-by-default'] as boolean) ?? true
|
||||
const logExpirationSetting = (settings['prosody-muc-expiration'] as string) ?? DEFAULTLOGEXPIRATION
|
||||
const enableC2s = (settings['prosody-c2s'] as boolean) || false
|
||||
const enableComponents = (settings['prosody-c2s'] as boolean) || false
|
||||
const prosodyDomain = await getProsodyDomain(options)
|
||||
const paths = await getProsodyFilePaths(options)
|
||||
const roomType = settings['prosody-room-type'] === 'channel' ? 'channel' : 'video'
|
||||
|
||||
const apikey = await getAPIKey(options)
|
||||
valuesToHideInDiagnostic.APIKey = apikey
|
||||
valuesToHideInDiagnostic.set('APIKey', apikey)
|
||||
|
||||
let baseApiUrl = settings['prosody-peertube-uri'] as string
|
||||
if (baseApiUrl && !/^https?:\/\/[a-z0-9.-_]+(?::\d+)?$/.test(baseApiUrl)) {
|
||||
@ -129,6 +134,18 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
|
||||
config.useC2S(c2sPort)
|
||||
}
|
||||
|
||||
if (enableComponents) {
|
||||
const componentsPort = (settings['prosody-components-port'] as string) || '53470'
|
||||
if (!/^\d+$/.test(componentsPort)) {
|
||||
throw new Error('Invalid external components port')
|
||||
}
|
||||
const components = parseExternalComponents((settings['prosody-components-list'] as string) || '', prosodyDomain)
|
||||
for (const component of components) {
|
||||
valuesToHideInDiagnostic.set('Component ' + component.name + ' secret', component.secret)
|
||||
}
|
||||
config.useExternalComponents(componentsPort, components)
|
||||
}
|
||||
|
||||
const logExpiration = readLogExpiration(options, logExpirationSetting)
|
||||
config.useMam(logByDefault, logExpiration)
|
||||
// TODO: add a settings to choose?
|
||||
@ -238,9 +255,9 @@ function readLogExpiration (options: RegisterServerOptions, logExpiration: strin
|
||||
|
||||
function getProsodyConfigContentForDiagnostic (config: ProsodyConfig, content?: string): string {
|
||||
let r: string = content ?? config.content
|
||||
for (const key in config.valuesToHideInDiagnostic) {
|
||||
for (const [key, value] of config.valuesToHideInDiagnostic.entries()) {
|
||||
// replaceAll not available, using trick:
|
||||
r = r.split(config.valuesToHideInDiagnostic[key]).join(`***${key}***`)
|
||||
r = r.split(value).join(`***${key}***`)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
38
server/lib/prosody/config/components.ts
Normal file
38
server/lib/prosody/config/components.ts
Normal file
@ -0,0 +1,38 @@
|
||||
interface ExternalComponent {
|
||||
name: string
|
||||
secret: string
|
||||
}
|
||||
|
||||
function parseExternalComponents (s: string, prosodyDomain: string): ExternalComponent[] {
|
||||
if (!s) {
|
||||
return []
|
||||
}
|
||||
let lines = s.split('\n')
|
||||
lines = lines.map(line => {
|
||||
return line.replace(/#.*$/, '')
|
||||
.replace(/^\s+/, '')
|
||||
.replace(/\s+$/, '')
|
||||
})
|
||||
lines = lines.filter(line => line !== '')
|
||||
|
||||
const r: ExternalComponent[] = []
|
||||
for (const line of lines) {
|
||||
const matches = line.match(/^([\w.]+)\s*:\s*(\w+)$/)
|
||||
if (matches) {
|
||||
let name = matches[1]
|
||||
if (!name.includes('.')) {
|
||||
name = name + '.' + prosodyDomain
|
||||
}
|
||||
r.push({
|
||||
name,
|
||||
secret: matches[2]
|
||||
})
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
export {
|
||||
ExternalComponent,
|
||||
parseExternalComponents
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import type { ProsodyFilePaths } from './paths'
|
||||
import type { ExternalComponent } from './components'
|
||||
|
||||
type ConfigEntryValue = boolean | number | string | ConfigEntryValue[]
|
||||
|
||||
@ -102,17 +103,20 @@ class ProsodyConfigVirtualHost extends ProsodyConfigBlock {
|
||||
|
||||
class ProsodyConfigComponent extends ProsodyConfigBlock {
|
||||
name: string
|
||||
type: string
|
||||
type?: string
|
||||
|
||||
constructor (type: string, name: string) {
|
||||
constructor (name: string, type?: string) {
|
||||
super(' ')
|
||||
this.type = type
|
||||
this.name = name
|
||||
}
|
||||
|
||||
write (): string {
|
||||
if (this.type !== undefined) {
|
||||
return `Component "${this.name}" "${this.type}"\n` + super.write()
|
||||
}
|
||||
return `Component "${this.name}"\n` + super.write()
|
||||
}
|
||||
}
|
||||
|
||||
type ProsodyLogLevel = 'debug' | 'info' | 'warn' | 'error'
|
||||
@ -123,6 +127,7 @@ class ProsodyConfigContent {
|
||||
authenticated?: ProsodyConfigVirtualHost
|
||||
anon: ProsodyConfigVirtualHost
|
||||
muc: ProsodyConfigComponent
|
||||
externalComponents: ProsodyConfigComponent[] = []
|
||||
log: string
|
||||
prosodyDomain: string
|
||||
|
||||
@ -132,7 +137,7 @@ class ProsodyConfigContent {
|
||||
this.log = ''
|
||||
this.prosodyDomain = prosodyDomain
|
||||
this.anon = new ProsodyConfigVirtualHost('anon.' + prosodyDomain)
|
||||
this.muc = new ProsodyConfigComponent('muc', 'room.' + prosodyDomain)
|
||||
this.muc = new ProsodyConfigComponent('room.' + prosodyDomain, 'muc')
|
||||
|
||||
this.global.set('daemonize', false)
|
||||
this.global.set('allow_registration', false)
|
||||
@ -228,6 +233,17 @@ class ProsodyConfigContent {
|
||||
this.global.set('c2s_ports', [c2sPort])
|
||||
}
|
||||
|
||||
useExternalComponents (componentsPort: string, components: ExternalComponent[]): void {
|
||||
this.global.set('component_ports', [componentsPort])
|
||||
this.global.set('component_interfaces', ['127.0.0.1', '::1'])
|
||||
|
||||
for (const component of components) {
|
||||
const c = new ProsodyConfigComponent(component.name)
|
||||
c.set('component_secret', component.secret)
|
||||
this.externalComponents.push(c)
|
||||
}
|
||||
}
|
||||
|
||||
useMucHttpDefault (url: string): void {
|
||||
this.muc.add('modules_enabled', 'muc_http_defaults')
|
||||
this.muc.add('muc_create_api_url', url)
|
||||
@ -309,6 +325,11 @@ class ProsodyConfigContent {
|
||||
content += '\n\n'
|
||||
content += this.muc.write()
|
||||
content += '\n\n'
|
||||
for (const externalComponent of this.externalComponents) {
|
||||
content += '\n\n'
|
||||
content += externalComponent.write()
|
||||
content += '\n\n'
|
||||
}
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
@ -380,7 +380,53 @@ This option alone only allows connections from localhost clients.`
|
||||
`The port that will be used by the c2s module of the builtin Prosody server.<br>
|
||||
XMPP clients shall use this port to connect.<br>
|
||||
Change it if this port is already in use on your server.<br>
|
||||
Keep it close this port on your firewall for now, it will not be accessed from the outer world.`
|
||||
You can keep this port closed on your firewall for now, it will not be accessed from the outer world.`
|
||||
})
|
||||
|
||||
registerSetting({
|
||||
name: 'prosody-components',
|
||||
label: 'Enable custom Prosody external components',
|
||||
type: 'input-checkbox',
|
||||
default: false,
|
||||
private: true,
|
||||
descriptionHTML:
|
||||
`Enable the use of external XMPP components.<br>
|
||||
This option alone only allows connections from localhost.<br>
|
||||
This feature can for example be used to connect some bots to the chatting rooms.`
|
||||
})
|
||||
|
||||
registerSetting({
|
||||
name: 'prosody-components-port',
|
||||
label: 'Prosody external components port',
|
||||
type: 'input',
|
||||
default: '53470',
|
||||
private: true,
|
||||
descriptionHTML:
|
||||
`The port that will be used by XMPP components to connect to the Prosody server.<br>
|
||||
Change it if this port is already in use on your server.<br>
|
||||
You can keep this port closed on your firewall for now, it will not be accessed from the outer world.`
|
||||
})
|
||||
|
||||
registerSetting({
|
||||
name: 'prosody-components-list',
|
||||
label: 'External components',
|
||||
type: 'input-textarea',
|
||||
default: '',
|
||||
private: true,
|
||||
descriptionHTML:
|
||||
`The external components to create:
|
||||
<ul>
|
||||
<li>One per line.</li>
|
||||
<li>Use the format «component_name:component_secret» (spaces will be trimmed)</li>
|
||||
<li>You can add comments: everything after the # character will be stripped off, and empty lines ignored</li>
|
||||
<li>The name can only contain alphanumeric characters and dots</li>
|
||||
<li>
|
||||
If the name contains only alphanumeric characters, it will be suffixed with the XMPP domain.
|
||||
For exemple «bridge» will become «bridge.your_domain.tld».
|
||||
You can also specify a full domain name, but you have to make sure to configure your DNS correctly.
|
||||
</li>
|
||||
<li>Only use alphanumeric characters in the secret passphrase (use at least 15 characters).</li>
|
||||
</ul>`
|
||||
})
|
||||
|
||||
// ********** settings changes management
|
||||
|
Loading…
Reference in New Issue
Block a user