From e719dc307998650a32be474bd391637c38122403 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Wed, 24 May 2023 16:09:55 +0200 Subject: [PATCH] Chat Federation: fetch remote server information when missing. --- .../{remote-infos.ts => fetch-infos.ts} | 25 +++++--- server/lib/federation/incoming.ts | 4 +- server/lib/federation/sanitize.ts | 62 ++++++++++++++----- server/lib/federation/storage.ts | 54 +++++++++++----- server/lib/routers/api.ts | 16 ++--- server/lib/routers/webchat.ts | 10 +-- 6 files changed, 118 insertions(+), 53 deletions(-) rename server/lib/federation/{remote-infos.ts => fetch-infos.ts} (83%) diff --git a/server/lib/federation/remote-infos.ts b/server/lib/federation/fetch-infos.ts similarity index 83% rename from server/lib/federation/remote-infos.ts rename to server/lib/federation/fetch-infos.ts index ae54230d..9b5f75f8 100644 --- a/server/lib/federation/remote-infos.ts +++ b/server/lib/federation/fetch-infos.ts @@ -1,6 +1,8 @@ import type { RegisterServerOptions } from '@peertube/peertube-types' +import { hasRemoteServerInfos, storeRemoteServerInfos } from './storage' import { getBaseRouterRoute } from '../helpers' import { canonicalizePluginUri } from '../uri/canonicalize' +import { sanitizePeertubeLiveChatServerInfos } from './sanitize' import { URL } from 'url' const got = require('got') @@ -33,13 +35,18 @@ const got = require('got') * @param remoteInstanceUrl remote instance url to check (as readed in the request header) * @returns true if the remote instance is ok */ -async function remoteServerInfos ( +async function fetchMissingRemoteServerInfos ( options: RegisterServerOptions, remoteInstanceUrl: string -): Promise { +): Promise { const logger = options.peertubeHelpers.logger logger.debug(`remoteServerInfos: checking if we have remote server infos for host ${remoteInstanceUrl}.`) + // FIXME: add a max age. + if (await hasRemoteServerInfos(options, remoteInstanceUrl)) { + return + } + let url: string try { const u = new URL(remoteInstanceUrl) @@ -53,7 +60,7 @@ async function remoteServerInfos ( }) } catch (_err) { logger.info('remoteServerInfos: Invalid remote instance url provided: ' + remoteInstanceUrl) - return false + return } try { @@ -66,18 +73,18 @@ async function remoteServerInfos ( if (!response) { logger.info('remoteServerInfos: Invalid remote server options') - return false + return } - // FIXME/TODO - - return true + const serverInfos = sanitizePeertubeLiveChatServerInfos(options, response) + if (serverInfos) { + await storeRemoteServerInfos(options, serverInfos) + } } catch (_err) { logger.info('remoteServerInfos: Can\'t get remote instance informations using url ' + url) - return false } } export { - remoteServerInfos + fetchMissingRemoteServerInfos } diff --git a/server/lib/federation/incoming.ts b/server/lib/federation/incoming.ts index f8e322de..404e3ee8 100644 --- a/server/lib/federation/incoming.ts +++ b/server/lib/federation/incoming.ts @@ -20,7 +20,9 @@ async function readIncomingAPVideo ( await storeVideoLiveChatInfos(options, video, peertubeLiveChat) if (video.remote) { - await storeRemoteServerInfos(options, peertubeLiveChat) + if (peertubeLiveChat !== false && peertubeLiveChat.xmppserver) { + await storeRemoteServerInfos(options, peertubeLiveChat.xmppserver) + } } } diff --git a/server/lib/federation/sanitize.ts b/server/lib/federation/sanitize.ts index 7d2a4c7f..fa141c8c 100644 --- a/server/lib/federation/sanitize.ts +++ b/server/lib/federation/sanitize.ts @@ -1,5 +1,5 @@ import type { RegisterServerOptions } from '@peertube/peertube-types' -import type { LiveChatJSONLDAttributeV1 } from './types' +import type { LiveChatJSONLDAttributeV1, PeertubeXMPPServerInfos } from './types' import { URL } from 'url' /** @@ -28,7 +28,24 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo if (!chatInfos.xmppserver || (typeof chatInfos.xmppserver !== 'object')) { return false } - const xmppserver = chatInfos.xmppserver + const xmppserver = sanitizePeertubeLiveChatServerInfos(options, chatInfos.xmppserver) + if (!xmppserver) { return false } + + const r: LiveChatJSONLDAttributeV1 = { + type: chatInfos.type, + jid: chatInfos.jid, + xmppserver + } + + return r +} + +function sanitizePeertubeLiveChatServerInfos ( + options: RegisterServerOptions, xmppserver: any +): PeertubeXMPPServerInfos | false { + if (!xmppserver || (typeof xmppserver !== 'object')) { + return false + } if ((typeof xmppserver.host) !== 'string') { return false } const host = _validateHost(xmppserver.host) @@ -37,20 +54,16 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo const muc = _validateHost(xmppserver.muc) if (!muc) { return false } - const r: LiveChatJSONLDAttributeV1 = { - type: chatInfos.type, - jid: chatInfos.jid, - xmppserver: { - host, - muc - } + const r: PeertubeXMPPServerInfos = { + host, + muc } if (xmppserver.directs2s) { if ((typeof xmppserver.directs2s) === 'object') { const port = xmppserver.directs2s.port if ((typeof port === 'string') && /^\d+$/.test(port)) { - r.xmppserver.directs2s = { + r.directs2s = { port } } @@ -63,7 +76,7 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo noSearchParams: true, protocol: 'ws.' })) { - r.xmppserver.websockets2s = { + r.websockets2s = { url } } @@ -72,7 +85,7 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo if (xmppserver.anonymous) { const virtualhost = _validateHost(xmppserver.anonymous.virtualhost) if (virtualhost) { - r.xmppserver.anonymous = { + r.anonymous = { virtualhost } @@ -81,7 +94,7 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo noSearchParams: true, protocol: 'http.' })) { - r.xmppserver.anonymous.bosh = bosh + r.anonymous.bosh = bosh } const websocket = xmppserver.anonymous.websocket @@ -89,7 +102,7 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo noSearchParams: true, protocol: 'ws.' })) { - r.xmppserver.anonymous.websocket = websocket + r.anonymous.websocket = websocket } } } @@ -206,6 +219,23 @@ function _sanitizePeertubeLiveChatInfosV0 (options: RegisterServerOptions, chatI return r } -export { - sanitizePeertubeLiveChatInfos +function sanitizeXMPPHost (options: RegisterServerOptions, host: any): false | string { + return _validateHost(host) +} + +function sanitizeXMPPHostFromInstanceUrl (_options: RegisterServerOptions, s: any): false | string { + try { + if (typeof s !== 'string') { return false } + const url = new URL(s) + return url.hostname + } catch (_err) { + return false + } +} + +export { + sanitizePeertubeLiveChatInfos, + sanitizePeertubeLiveChatServerInfos, + sanitizeXMPPHost, + sanitizeXMPPHostFromInstanceUrl } diff --git a/server/lib/federation/storage.ts b/server/lib/federation/storage.ts index f2aa79c8..3c24ec75 100644 --- a/server/lib/federation/storage.ts +++ b/server/lib/federation/storage.ts @@ -1,6 +1,6 @@ import type { RegisterServerOptions, MVideoFullLight, MVideoAP, Video, MVideoThumbnail } from '@peertube/peertube-types' -import type { LiveChatJSONLDAttribute, LiveChatJSONLDAttributeV1 } from './types' -import { sanitizePeertubeLiveChatInfos } from './sanitize' +import type { LiveChatJSONLDAttribute, LiveChatJSONLDAttributeV1, PeertubeXMPPServerInfos } from './types' +import { sanitizePeertubeLiveChatInfos, sanitizeXMPPHostFromInstanceUrl } from './sanitize' import { URL } from 'url' import * as fs from 'fs' import * as path from 'path' @@ -111,21 +111,18 @@ async function getVideoLiveChatInfos ( * kind of urls. * * @param options server optiosn - * @param liveChatInfos livechat stored data + * @param xmppserver remote server informations */ async function storeRemoteServerInfos ( options: RegisterServerOptions, - liveChatInfos: LiveChatJSONLDAttributeV1 + xmppserver: PeertubeXMPPServerInfos ): Promise { - if (!liveChatInfos) { return } - if (!liveChatInfos.xmppserver) { return } - const logger = options.peertubeHelpers.logger - const mainHost = liveChatInfos.xmppserver.host + const mainHost = xmppserver.host const hosts = [ - liveChatInfos.xmppserver.host, - liveChatInfos.xmppserver.muc + xmppserver.host, + xmppserver.muc ] for (const host of hosts) { @@ -144,27 +141,55 @@ async function storeRemoteServerInfos ( ) const s2sFilePath = path.resolve(dir, 's2s') const wsS2SFilePath = path.resolve(dir, 'ws-s2s') + const timestampFilePath = path.resolve(dir, 'last-update') - if (liveChatInfos.xmppserver.directs2s?.port) { + if (xmppserver.directs2s?.port) { await _store(options, s2sFilePath, { host: mainHost, - port: liveChatInfos.xmppserver.directs2s.port + port: xmppserver.directs2s.port }) } else { await _del(options, s2sFilePath) } - if (liveChatInfos.xmppserver.websockets2s?.url) { + if (xmppserver.websockets2s?.url) { await _store(options, wsS2SFilePath, { host: mainHost, - url: liveChatInfos.xmppserver.websockets2s.url + url: xmppserver.websockets2s.url }) } else { await _del(options, wsS2SFilePath) } + + await _store(options, timestampFilePath, { + timestamp: (new Date()).getTime() + }) } } +/** + * Indicate if we have the remote hosts informations. + * @param options server options + * @param host host domain + */ +async function hasRemoteServerInfos (options: RegisterServerOptions, hostParam: any): Promise { + const host = sanitizeXMPPHostFromInstanceUrl(options, hostParam) + if (!host) { + return false + } + if (host.includes('..')) { + options.peertubeHelpers.logger.error(`Host seems not correct, contains ..: ${host}`) + return false + } + const filePath = path.resolve( + options.peertubeHelpers.plugin.getDataDirectoryPath(), + 'serverInfos', + host, + 'last-update' + ) + return fs.existsSync(filePath) +} + async function _getFilePath ( options: RegisterServerOptions, remote: boolean, @@ -269,6 +294,7 @@ function getRemoteServerInfosDir (options: RegisterServerOptions): string { export { storeVideoLiveChatInfos, storeRemoteServerInfos, + hasRemoteServerInfos, getVideoLiveChatInfos, getRemoteServerInfosDir } diff --git a/server/lib/routers/api.ts b/server/lib/routers/api.ts index bb88216b..d667f487 100644 --- a/server/lib/routers/api.ts +++ b/server/lib/routers/api.ts @@ -10,6 +10,7 @@ import { getProsodyDomain } from '../prosody/config/domain' import { fillVideoCustomFields } from '../custom-fields' import { getChannelInfosById } from '../database/channel' import { ensureProsodyRunning } from '../prosody/ctl' +import { serverBuildInfos } from '../federation/outgoing' import { isDebugMode } from '../debug' // See here for description: https://modules.prosody.im/mod_muc_http_defaults.html @@ -224,14 +225,13 @@ async function initApiRouter (options: RegisterServerOptions): Promise { } )) - // router.get('/federation_server_infos', asyncMiddleware( - // async (req: Request, res: Response, _next: NextFunction) => { - // logger.info('federation_server_infos api call') - // // TODO/FIXME: return server infos. - // // TODO/FIXME: store these informations on the other side. - // res.json({ ok: true }) - // } - // )) + router.get('/federation_server_infos', asyncMiddleware( + async (req: Request, res: Response, _next: NextFunction) => { + logger.info('federation_server_infos api call') + const result = await serverBuildInfos(options) + res.json(result) + } + )) if (isDebugMode(options)) { // Only add this route if the debug mode is enabled at time of the server launch. diff --git a/server/lib/routers/webchat.ts b/server/lib/routers/webchat.ts index 9cd5a072..071f49d6 100644 --- a/server/lib/routers/webchat.ts +++ b/server/lib/routers/webchat.ts @@ -18,7 +18,7 @@ import { LiveChatJSONLDAttributeV1 } from '../federation/types' import { anonymousConnectionInfos, compatibleRemoteAuthenticatedConnectionEnabled } from '../federation/connection-infos' -// import { remoteServerInfos } from '../federation/remote-infos' +import { fetchMissingRemoteServerInfos } from '../federation/fetch-infos' import * as path from 'path' const got = require('got') @@ -290,11 +290,11 @@ async function initWebchatRouter (options: RegisterServerOptionsV5): Promise