From 4f3fb4788401233d3985ff4128af850da3863638 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 27 Dec 2023 15:44:09 +0100 Subject: [PATCH] Implement FEP-2677 Better way to find and follow remote instance actor --- packages/models/src/server/job.model.ts | 2 +- server/core/controllers/api/server/follows.ts | 2 -- server/core/controllers/api/videos/upload.ts | 8 +++++++- server/core/controllers/well-known.ts | 4 ++++ server/core/helpers/activity-pub-utils.ts | 15 ++++++++++++++- server/core/initializers/config.ts | 2 +- server/core/lib/activitypub/url.ts | 4 ++-- .../lib/activitypub/videos/shared/trackers.ts | 6 +++--- .../lib/job-queue/handlers/activitypub-follow.ts | 12 ++++++++++-- server/core/models/video/video-file.ts | 4 ++-- 10 files changed, 44 insertions(+), 15 deletions(-) diff --git a/packages/models/src/server/job.model.ts b/packages/models/src/server/job.model.ts index ac90231ef2e..913686d729f 100644 --- a/packages/models/src/server/job.model.ts +++ b/packages/models/src/server/job.model.ts @@ -58,7 +58,7 @@ export type ActivitypubHttpBroadcastPayload = { export type ActivitypubFollowPayload = { followerActorId: number - name: string + name?: string host: string isAutoFollow?: boolean assertIsChannel?: boolean diff --git a/server/core/controllers/api/server/follows.ts b/server/core/controllers/api/server/follows.ts index d7f84053a35..3a796229f7c 100644 --- a/server/core/controllers/api/server/follows.ts +++ b/server/core/controllers/api/server/follows.ts @@ -3,7 +3,6 @@ import { HttpStatusCode, ServerFollowCreate, UserRight } from '@peertube/peertub import { getServerActor } from '@server/models/application/application.js' import { logger } from '../../../helpers/logger.js' import { getFormattedObjects } from '../../../helpers/utils.js' -import { SERVER_ACTOR_NAME } from '../../../initializers/constants.js' import { sequelizeTypescript } from '../../../initializers/database.js' import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow.js' import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send/index.js' @@ -132,7 +131,6 @@ async function addFollow (req: express.Request, res: express.Response) { for (const host of hosts) { const payload = { host, - name: SERVER_ACTOR_NAME, followerActorId: follower.id } diff --git a/server/core/controllers/api/videos/upload.ts b/server/core/controllers/api/videos/upload.ts index 1d51094c82e..a0353b0abba 100644 --- a/server/core/controllers/api/videos/upload.ts +++ b/server/core/controllers/api/videos/upload.ts @@ -6,7 +6,13 @@ import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url.js' import { CreateJobArgument, CreateJobOptions, JobQueue } from '@server/lib/job-queue/index.js' import { Redis } from '@server/lib/redis.js' import { uploadx } from '@server/lib/uploadx.js' -import { buildLocalVideoFromReq, buildMoveJob, buildStoryboardJobIfNeeded, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video.js' +import { + buildLocalVideoFromReq, + buildMoveJob, + buildStoryboardJobIfNeeded, + buildVideoThumbnailsFromReq, + setVideoTags +} from '@server/lib/video.js' import { buildNewFile } from '@server/lib/video-file.js' import { VideoPathManager } from '@server/lib/video-path-manager.js' import { buildNextVideoState } from '@server/lib/video-state.js' diff --git a/server/core/controllers/well-known.ts b/server/core/controllers/well-known.ts index 73652bb030e..a9d077228ce 100644 --- a/server/core/controllers/well-known.ts +++ b/server/core/controllers/well-known.ts @@ -41,6 +41,10 @@ wellKnownRouter.use('/.well-known/nodeinfo', { rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', href: WEBSERVER.URL + '/nodeinfo/2.0.json' + }, + { + rel: 'https://www.w3.org/ns/activitystreams#Application', + href: WEBSERVER.URL + '/accounts/peertube' } ] }) diff --git a/server/core/helpers/activity-pub-utils.ts b/server/core/helpers/activity-pub-utils.ts index fb9dda336a8..aa05e603172 100644 --- a/server/core/helpers/activity-pub-utils.ts +++ b/server/core/helpers/activity-pub-utils.ts @@ -1,7 +1,9 @@ import { ContextType } from '@peertube/peertube-models' -import { ACTIVITY_PUB } from '@server/initializers/constants.js' +import { ACTIVITY_PUB, REMOTE_SCHEME } from '@server/initializers/constants.js' import { buildDigest } from './peertube-crypto.js' import type { signJsonLDObject } from './peertube-jsonld.js' +import { doJSONRequest } from './requests.js' +import { isArray } from './custom-validators/misc.js' export type ContextFilter = (arg: T) => Promise @@ -36,6 +38,17 @@ export async function signAndContextify (options: { return signerFunction({ byActor, data: activity }) } +export async function getApplicationActorOfHost (host: string) { + const url = REMOTE_SCHEME.HTTP + '://' + host + '/.well-known/nodeinfo' + const { body } = await doJSONRequest<{ links: { rel: string, href: string }[] }>(url) + + if (!isArray(body.links)) return undefined + + const found = body.links.find(l => l.rel === 'https://www.w3.org/ns/activitystreams#Application') + + return found?.href || undefined +} + // --------------------------------------------------------------------------- // Private // --------------------------------------------------------------------------- diff --git a/server/core/initializers/config.ts b/server/core/initializers/config.ts index 1780c197177..d435efc0027 100644 --- a/server/core/initializers/config.ts +++ b/server/core/initializers/config.ts @@ -684,7 +684,7 @@ export function reloadConfig () { return process.env.NODE_CONFIG_DIR.split(':') } - return [join(root(), 'config')] + return [ join(root(), 'config') ] } function purge () { diff --git a/server/core/lib/activitypub/url.ts b/server/core/lib/activitypub/url.ts index aff104804b4..4bbd1c57303 100644 --- a/server/core/lib/activitypub/url.ts +++ b/server/core/lib/activitypub/url.ts @@ -130,7 +130,7 @@ function getAbuseTargetUrl (abuse: MAbuseFull) { // --------------------------------------------------------------------------- -function buildRemoteVideoBaseUrl (video: MVideoWithHost, path: string, scheme?: string) { +function buildRemoteUrl (video: MVideoWithHost, path: string, scheme?: string) { if (!scheme) scheme = REMOTE_SCHEME.HTTP const host = video.VideoChannel.Actor.Server.host @@ -178,5 +178,5 @@ export { getAbuseTargetUrl, checkUrlsSameHost, - buildRemoteVideoBaseUrl + buildRemoteUrl } diff --git a/server/core/lib/activitypub/videos/shared/trackers.ts b/server/core/lib/activitypub/videos/shared/trackers.ts index a7ce6f4adb0..ac8425de5e9 100644 --- a/server/core/lib/activitypub/videos/shared/trackers.ts +++ b/server/core/lib/activitypub/videos/shared/trackers.ts @@ -5,7 +5,7 @@ import { REMOTE_SCHEME } from '@server/initializers/constants.js' import { TrackerModel } from '@server/models/server/tracker.js' import { MVideo, MVideoWithHost } from '@server/types/models/index.js' import { ActivityTrackerUrlObject, VideoObject } from '@peertube/peertube-models' -import { buildRemoteVideoBaseUrl } from '../../url.js' +import { buildRemoteUrl } from '../../url.js' function getTrackerUrls (object: VideoObject, video: MVideoWithHost) { let wsFound = false @@ -20,8 +20,8 @@ function getTrackerUrls (object: VideoObject, video: MVideoWithHost) { if (wsFound) return trackers return [ - buildRemoteVideoBaseUrl(video, '/tracker/socket', REMOTE_SCHEME.WS), - buildRemoteVideoBaseUrl(video, '/tracker/announce') + buildRemoteUrl(video, '/tracker/socket', REMOTE_SCHEME.WS), + buildRemoteUrl(video, '/tracker/announce') ] } diff --git a/server/core/lib/job-queue/handlers/activitypub-follow.ts b/server/core/lib/job-queue/handlers/activitypub-follow.ts index a5b3a4fd465..0fb32234528 100644 --- a/server/core/lib/job-queue/handlers/activitypub-follow.ts +++ b/server/core/lib/job-queue/handlers/activitypub-follow.ts @@ -4,7 +4,7 @@ import { ActivitypubFollowPayload } from '@peertube/peertube-models' import { sanitizeHost } from '../../../helpers/core-utils.js' import { retryTransactionWrapper } from '../../../helpers/database-utils.js' import { logger } from '../../../helpers/logger.js' -import { REMOTE_SCHEME, WEBSERVER } from '../../../initializers/constants.js' +import { REMOTE_SCHEME, SERVER_ACTOR_NAME, WEBSERVER } from '../../../initializers/constants.js' import { sequelizeTypescript } from '../../../initializers/database.js' import { ActorModel } from '../../../models/actor/actor.js' import { ActorFollowModel } from '../../../models/actor/actor-follow.js' @@ -12,6 +12,7 @@ import { MActor, MActorFull } from '../../../types/models/index.js' import { getOrCreateAPActor, loadActorUrlOrGetFromWebfinger } from '../../activitypub/actors/index.js' import { sendFollow } from '../../activitypub/send/index.js' import { Notifier } from '../../notifier/index.js' +import { getApplicationActorOfHost } from '@server/helpers/activity-pub-utils.js' async function processActivityPubFollow (job: Job) { const payload = job.data as ActivitypubFollowPayload @@ -21,10 +22,17 @@ async function processActivityPubFollow (job: Job) { let targetActor: MActorFull if (!host || host === WEBSERVER.HOST) { + if (!payload.name) throw new Error('Payload name is mandatory for local follow') + targetActor = await ActorModel.loadLocalByName(payload.name) } else { const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) - const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) + + let actorUrl: string + + if (!payload.name) actorUrl = await getApplicationActorOfHost(sanitizedHost) + if (!actorUrl) actorUrl = await loadActorUrlOrGetFromWebfinger((payload.name || SERVER_ACTOR_NAME) + '@' + sanitizedHost) + targetActor = await getOrCreateAPActor(actorUrl, 'all') } diff --git a/server/core/models/video/video-file.ts b/server/core/models/video/video-file.ts index 735bda2d544..ee6e11ca37e 100644 --- a/server/core/models/video/video-file.ts +++ b/server/core/models/video/video-file.ts @@ -3,7 +3,7 @@ import { AttributesOnly } from '@peertube/peertube-typescript-utils' import { logger } from '@server/helpers/logger.js' import { extractVideo } from '@server/helpers/video.js' import { CONFIG } from '@server/initializers/config.js' -import { buildRemoteVideoBaseUrl } from '@server/lib/activitypub/url.js' +import { buildRemoteUrl } from '@server/lib/activitypub/url.js' import { getHLSPrivateFileUrl, getHLSPublicFileUrl, @@ -582,7 +582,7 @@ export class VideoFileModel extends Model if (video.isOwned()) return WEBSERVER.URL + path // FIXME: don't guess remote URL - return buildRemoteVideoBaseUrl(video, path) + return buildRemoteUrl(video, path) } getRemoteTorrentUrl (video: MVideo) {