diff options
26 files changed, 110 insertions, 76 deletions
diff --git a/config/default.yaml b/config/default.yaml index 53d8c45de..9d217b56b 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -374,6 +374,9 @@ plugins: | |||
374 | url: 'https://packages.joinpeertube.org' | 374 | url: 'https://packages.joinpeertube.org' |
375 | 375 | ||
376 | federation: | 376 | federation: |
377 | # Some federated software such as Mastodon may require an HTTP signature to access content | ||
378 | sign_federated_fetches: true | ||
379 | |||
377 | videos: | 380 | videos: |
378 | federate_unlisted: false | 381 | federate_unlisted: false |
379 | 382 | ||
diff --git a/config/production.yaml.example b/config/production.yaml.example index 87ef2b676..2afc5f982 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -372,6 +372,9 @@ plugins: | |||
372 | url: 'https://packages.joinpeertube.org' | 372 | url: 'https://packages.joinpeertube.org' |
373 | 373 | ||
374 | federation: | 374 | federation: |
375 | # Some federated software such as Mastodon may require an HTTP signature to access content | ||
376 | sign_federated_fetches: true | ||
377 | |||
375 | videos: | 378 | videos: |
376 | federate_unlisted: false | 379 | federate_unlisted: false |
377 | 380 | ||
diff --git a/server/controllers/api/search/search-video-channels.ts b/server/controllers/api/search/search-video-channels.ts index d615ff9ed..1d2a9d235 100644 --- a/server/controllers/api/search/search-video-channels.ts +++ b/server/controllers/api/search/search-video-channels.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { sanitizeUrl } from '@server/helpers/core-utils' | 2 | import { sanitizeUrl } from '@server/helpers/core-utils' |
3 | import { pickSearchChannelQuery } from '@server/helpers/query' | 3 | import { pickSearchChannelQuery } from '@server/helpers/query' |
4 | import { doJSONRequest, findLatestRedirection } from '@server/helpers/requests' | 4 | import { doJSONRequest } from '@server/helpers/requests' |
5 | import { CONFIG } from '@server/initializers/config' | 5 | import { CONFIG } from '@server/initializers/config' |
6 | import { WEBSERVER } from '@server/initializers/constants' | 6 | import { WEBSERVER } from '@server/initializers/constants' |
7 | import { findLatestAPRedirection } from '@server/lib/activitypub/activity' | ||
7 | import { Hooks } from '@server/lib/plugins/hooks' | 8 | import { Hooks } from '@server/lib/plugins/hooks' |
8 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' | 9 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' |
9 | import { getServerActor } from '@server/models/application/application' | 10 | import { getServerActor } from '@server/models/application/application' |
@@ -126,7 +127,7 @@ async function searchVideoChannelURI (search: string, res: express.Response) { | |||
126 | 127 | ||
127 | if (isUserAbleToSearchRemoteURI(res)) { | 128 | if (isUserAbleToSearchRemoteURI(res)) { |
128 | try { | 129 | try { |
129 | const latestUri = await findLatestRedirection(uri, { activityPub: true }) | 130 | const latestUri = await findLatestAPRedirection(uri) |
130 | 131 | ||
131 | const actor = await getOrCreateAPActor(latestUri, 'all', true, true) | 132 | const actor = await getOrCreateAPActor(latestUri, 'all', true, true) |
132 | videoChannel = actor.VideoChannel | 133 | videoChannel = actor.VideoChannel |
diff --git a/server/controllers/api/search/search-video-playlists.ts b/server/controllers/api/search/search-video-playlists.ts index e76d65fde..97aeeaba9 100644 --- a/server/controllers/api/search/search-video-playlists.ts +++ b/server/controllers/api/search/search-video-playlists.ts | |||
@@ -3,10 +3,11 @@ import { sanitizeUrl } from '@server/helpers/core-utils' | |||
3 | import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils' | 3 | import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils' |
4 | import { logger } from '@server/helpers/logger' | 4 | import { logger } from '@server/helpers/logger' |
5 | import { pickSearchPlaylistQuery } from '@server/helpers/query' | 5 | import { pickSearchPlaylistQuery } from '@server/helpers/query' |
6 | import { doJSONRequest, findLatestRedirection } from '@server/helpers/requests' | 6 | import { doJSONRequest } from '@server/helpers/requests' |
7 | import { getFormattedObjects } from '@server/helpers/utils' | 7 | import { getFormattedObjects } from '@server/helpers/utils' |
8 | import { CONFIG } from '@server/initializers/config' | 8 | import { CONFIG } from '@server/initializers/config' |
9 | import { WEBSERVER } from '@server/initializers/constants' | 9 | import { WEBSERVER } from '@server/initializers/constants' |
10 | import { findLatestAPRedirection } from '@server/lib/activitypub/activity' | ||
10 | import { getOrCreateAPVideoPlaylist } from '@server/lib/activitypub/playlists/get' | 11 | import { getOrCreateAPVideoPlaylist } from '@server/lib/activitypub/playlists/get' |
11 | import { Hooks } from '@server/lib/plugins/hooks' | 12 | import { Hooks } from '@server/lib/plugins/hooks' |
12 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' | 13 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' |
@@ -105,7 +106,7 @@ async function searchVideoPlaylistsURI (search: string, res: express.Response) { | |||
105 | 106 | ||
106 | if (isUserAbleToSearchRemoteURI(res)) { | 107 | if (isUserAbleToSearchRemoteURI(res)) { |
107 | try { | 108 | try { |
108 | const url = await findLatestRedirection(search, { activityPub: true }) | 109 | const url = await findLatestAPRedirection(search) |
109 | 110 | ||
110 | videoPlaylist = await getOrCreateAPVideoPlaylist(url) | 111 | videoPlaylist = await getOrCreateAPVideoPlaylist(url) |
111 | } catch (err) { | 112 | } catch (err) { |
diff --git a/server/controllers/api/search/search-videos.ts b/server/controllers/api/search/search-videos.ts index d0ab7f12a..b33064335 100644 --- a/server/controllers/api/search/search-videos.ts +++ b/server/controllers/api/search/search-videos.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { sanitizeUrl } from '@server/helpers/core-utils' | 2 | import { sanitizeUrl } from '@server/helpers/core-utils' |
3 | import { pickSearchVideoQuery } from '@server/helpers/query' | 3 | import { pickSearchVideoQuery } from '@server/helpers/query' |
4 | import { doJSONRequest, findLatestRedirection } from '@server/helpers/requests' | 4 | import { doJSONRequest } from '@server/helpers/requests' |
5 | import { CONFIG } from '@server/initializers/config' | 5 | import { CONFIG } from '@server/initializers/config' |
6 | import { WEBSERVER } from '@server/initializers/constants' | 6 | import { WEBSERVER } from '@server/initializers/constants' |
7 | import { findLatestAPRedirection } from '@server/lib/activitypub/activity' | ||
7 | import { getOrCreateAPVideo } from '@server/lib/activitypub/videos' | 8 | import { getOrCreateAPVideo } from '@server/lib/activitypub/videos' |
8 | import { Hooks } from '@server/lib/plugins/hooks' | 9 | import { Hooks } from '@server/lib/plugins/hooks' |
9 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' | 10 | import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' |
@@ -141,7 +142,7 @@ async function searchVideoURI (url: string, res: express.Response) { | |||
141 | } | 142 | } |
142 | 143 | ||
143 | const result = await getOrCreateAPVideo({ | 144 | const result = await getOrCreateAPVideo({ |
144 | videoObject: await findLatestRedirection(url, { activityPub: true }), | 145 | videoObject: await findLatestAPRedirection(url), |
145 | syncParam | 146 | syncParam |
146 | }) | 147 | }) |
147 | video = result ? result.video : undefined | 148 | video = result ? result.video : undefined |
diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index 201a7d7e3..1625d6e49 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts | |||
@@ -21,6 +21,7 @@ type PeerTubeRequestOptions = { | |||
21 | timeout?: number | 21 | timeout?: number |
22 | activityPub?: boolean | 22 | activityPub?: boolean |
23 | bodyKBLimit?: number // 1MB | 23 | bodyKBLimit?: number // 1MB |
24 | |||
24 | httpSignature?: { | 25 | httpSignature?: { |
25 | algorithm: string | 26 | algorithm: string |
26 | authorizationHeaderName: string | 27 | authorizationHeaderName: string |
@@ -28,7 +29,10 @@ type PeerTubeRequestOptions = { | |||
28 | key: string | 29 | key: string |
29 | headers: string[] | 30 | headers: string[] |
30 | } | 31 | } |
32 | |||
31 | jsonResponse?: boolean | 33 | jsonResponse?: boolean |
34 | |||
35 | followRedirect?: boolean | ||
32 | } & Pick<GotOptions, 'headers' | 'json' | 'method' | 'searchParams'> | 36 | } & Pick<GotOptions, 'headers' | 'json' | 'method' | 'searchParams'> |
33 | 37 | ||
34 | const peertubeGot = got.extend({ | 38 | const peertubeGot = got.extend({ |
@@ -180,16 +184,6 @@ function isBinaryResponse (result: Response<any>) { | |||
180 | return BINARY_CONTENT_TYPES.has(result.headers['content-type']) | 184 | return BINARY_CONTENT_TYPES.has(result.headers['content-type']) |
181 | } | 185 | } |
182 | 186 | ||
183 | async function findLatestRedirection (url: string, options: PeerTubeRequestOptions, iteration = 1) { | ||
184 | if (iteration > 10) throw new Error('Too much iterations to find final URL ' + url) | ||
185 | |||
186 | const { headers } = await peertubeGot(url, { followRedirect: false, ...buildGotOptions(options) }) | ||
187 | |||
188 | if (headers.location) return findLatestRedirection(headers.location, options, iteration + 1) | ||
189 | |||
190 | return url | ||
191 | } | ||
192 | |||
193 | // --------------------------------------------------------------------------- | 187 | // --------------------------------------------------------------------------- |
194 | 188 | ||
195 | export { | 189 | export { |
@@ -200,7 +194,6 @@ export { | |||
200 | doRequestAndSaveToFile, | 194 | doRequestAndSaveToFile, |
201 | isBinaryResponse, | 195 | isBinaryResponse, |
202 | getAgent, | 196 | getAgent, |
203 | findLatestRedirection, | ||
204 | peertubeGot | 197 | peertubeGot |
205 | } | 198 | } |
206 | 199 | ||
@@ -227,6 +220,7 @@ function buildGotOptions (options: PeerTubeRequestOptions) { | |||
227 | timeout: options.timeout ?? REQUEST_TIMEOUTS.DEFAULT, | 220 | timeout: options.timeout ?? REQUEST_TIMEOUTS.DEFAULT, |
228 | json: options.json, | 221 | json: options.json, |
229 | searchParams: options.searchParams, | 222 | searchParams: options.searchParams, |
223 | followRedirect: options.followRedirect, | ||
230 | retry: 2, | 224 | retry: 2, |
231 | headers, | 225 | headers, |
232 | context | 226 | context |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 2724990c1..3e3b8ad1f 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -309,7 +309,8 @@ const CONFIG = { | |||
309 | VIDEOS: { | 309 | VIDEOS: { |
310 | FEDERATE_UNLISTED: config.get<boolean>('federation.videos.federate_unlisted'), | 310 | FEDERATE_UNLISTED: config.get<boolean>('federation.videos.federate_unlisted'), |
311 | CLEANUP_REMOTE_INTERACTIONS: config.get<boolean>('federation.videos.cleanup_remote_interactions') | 311 | CLEANUP_REMOTE_INTERACTIONS: config.get<boolean>('federation.videos.cleanup_remote_interactions') |
312 | } | 312 | }, |
313 | SIGN_FEDERATED_FETCHES: config.get<boolean>('federation.sign_federated_fetches') | ||
313 | }, | 314 | }, |
314 | PEERTUBE: { | 315 | PEERTUBE: { |
315 | CHECK_LATEST_VERSION: { | 316 | CHECK_LATEST_VERSION: { |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 9e5a02854..de5f11f8f 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -712,7 +712,8 @@ const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = { | |||
712 | const HTTP_SIGNATURE = { | 712 | const HTTP_SIGNATURE = { |
713 | HEADER_NAME: 'signature', | 713 | HEADER_NAME: 'signature', |
714 | ALGORITHM: 'rsa-sha256', | 714 | ALGORITHM: 'rsa-sha256', |
715 | HEADERS_TO_SIGN: [ '(request-target)', 'host', 'date', 'digest' ], | 715 | HEADERS_TO_SIGN_WITH_PAYLOAD: [ '(request-target)', 'host', 'date', 'digest' ], |
716 | HEADERS_TO_SIGN_WITHOUT_PAYLOAD: [ '(request-target)', 'host', 'date' ], | ||
716 | CLOCK_SKEW_SECONDS: 1800 | 717 | CLOCK_SKEW_SECONDS: 1800 |
717 | } | 718 | } |
718 | 719 | ||
diff --git a/server/lib/activitypub/activity.ts b/server/lib/activitypub/activity.ts index 0fed3e8fd..391bcd9c6 100644 --- a/server/lib/activitypub/activity.ts +++ b/server/lib/activitypub/activity.ts | |||
@@ -1,22 +1,26 @@ | |||
1 | import { doJSONRequest } from '@server/helpers/requests' | 1 | import { doJSONRequest, PeerTubeRequestOptions } from '@server/helpers/requests' |
2 | import { APObjectId, ActivityObject, ActivityPubActor, ActivityType } from '@shared/models' | 2 | import { CONFIG } from '@server/initializers/config' |
3 | import { ActivityObject, ActivityPubActor, ActivityType, APObjectId } from '@shared/models' | ||
4 | import { buildSignedRequestOptions } from './send' | ||
3 | 5 | ||
4 | function getAPId (object: string | { id: string }) { | 6 | export function getAPId (object: string | { id: string }) { |
5 | if (typeof object === 'string') return object | 7 | if (typeof object === 'string') return object |
6 | 8 | ||
7 | return object.id | 9 | return object.id |
8 | } | 10 | } |
9 | 11 | ||
10 | function getActivityStreamDuration (duration: number) { | 12 | export function getActivityStreamDuration (duration: number) { |
11 | // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration | 13 | // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration |
12 | return 'PT' + duration + 'S' | 14 | return 'PT' + duration + 'S' |
13 | } | 15 | } |
14 | 16 | ||
15 | function getDurationFromActivityStream (duration: string) { | 17 | export function getDurationFromActivityStream (duration: string) { |
16 | return parseInt(duration.replace(/[^\d]+/, '')) | 18 | return parseInt(duration.replace(/[^\d]+/, '')) |
17 | } | 19 | } |
18 | 20 | ||
19 | function buildAvailableActivities (): ActivityType[] { | 21 | // --------------------------------------------------------------------------- |
22 | |||
23 | export function buildAvailableActivities (): ActivityType[] { | ||
20 | return [ | 24 | return [ |
21 | 'Create', | 25 | 'Create', |
22 | 'Update', | 26 | 'Update', |
@@ -33,9 +37,25 @@ function buildAvailableActivities (): ActivityType[] { | |||
33 | ] | 37 | ] |
34 | } | 38 | } |
35 | 39 | ||
36 | async function fetchAPObject <T extends (ActivityObject | ActivityPubActor)> (object: APObjectId) { | 40 | // --------------------------------------------------------------------------- |
41 | |||
42 | export async function fetchAP <T> (url: string, moreOptions: PeerTubeRequestOptions = {}) { | ||
43 | const options = { | ||
44 | activityPub: true, | ||
45 | |||
46 | httpSignature: CONFIG.FEDERATION.SIGN_FEDERATED_FETCHES | ||
47 | ? await buildSignedRequestOptions({ hasPayload: false }) | ||
48 | : undefined, | ||
49 | |||
50 | ...moreOptions | ||
51 | } | ||
52 | |||
53 | return doJSONRequest<T>(url, options) | ||
54 | } | ||
55 | |||
56 | export async function fetchAPObjectIfNeeded <T extends (ActivityObject | ActivityPubActor)> (object: APObjectId) { | ||
37 | if (typeof object === 'string') { | 57 | if (typeof object === 'string') { |
38 | const { body } = await doJSONRequest<Exclude<T, string>>(object, { activityPub: true }) | 58 | const { body } = await fetchAP<Exclude<T, string>>(object) |
39 | 59 | ||
40 | return body | 60 | return body |
41 | } | 61 | } |
@@ -43,10 +63,12 @@ async function fetchAPObject <T extends (ActivityObject | ActivityPubActor)> (ob | |||
43 | return object as Exclude<T, string> | 63 | return object as Exclude<T, string> |
44 | } | 64 | } |
45 | 65 | ||
46 | export { | 66 | export async function findLatestAPRedirection (url: string, iteration = 1) { |
47 | getAPId, | 67 | if (iteration > 10) throw new Error('Too much iterations to find final URL ' + url) |
48 | fetchAPObject, | 68 | |
49 | getActivityStreamDuration, | 69 | const { headers } = await fetchAP(url, { followRedirect: false }) |
50 | buildAvailableActivities, | 70 | |
51 | getDurationFromActivityStream | 71 | if (headers.location) return findLatestAPRedirection(headers.location, iteration + 1) |
72 | |||
73 | return url | ||
52 | } | 74 | } |
diff --git a/server/lib/activitypub/actors/get.ts b/server/lib/activitypub/actors/get.ts index b2be3f5fb..dd2bc9f03 100644 --- a/server/lib/activitypub/actors/get.ts +++ b/server/lib/activitypub/actors/get.ts | |||
@@ -5,7 +5,7 @@ import { ActorLoadByUrlType, loadActorByUrl } from '@server/lib/model-loaders' | |||
5 | import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models' | 5 | import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models' |
6 | import { arrayify } from '@shared/core-utils' | 6 | import { arrayify } from '@shared/core-utils' |
7 | import { ActivityPubActor, APObjectId } from '@shared/models' | 7 | import { ActivityPubActor, APObjectId } from '@shared/models' |
8 | import { fetchAPObject, getAPId } from '../activity' | 8 | import { fetchAPObjectIfNeeded, getAPId } from '../activity' |
9 | import { checkUrlsSameHost } from '../url' | 9 | import { checkUrlsSameHost } from '../url' |
10 | import { refreshActorIfNeeded } from './refresh' | 10 | import { refreshActorIfNeeded } from './refresh' |
11 | import { APActorCreator, fetchRemoteActor } from './shared' | 11 | import { APActorCreator, fetchRemoteActor } from './shared' |
@@ -87,7 +87,7 @@ async function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: stri | |||
87 | 87 | ||
88 | async function findOwner (rootUrl: string, attributedTo: APObjectId[] | APObjectId, type: 'Person' | 'Group') { | 88 | async function findOwner (rootUrl: string, attributedTo: APObjectId[] | APObjectId, type: 'Person' | 'Group') { |
89 | for (const actorToCheck of arrayify(attributedTo)) { | 89 | for (const actorToCheck of arrayify(attributedTo)) { |
90 | const actorObject = await fetchAPObject<ActivityPubActor>(getAPId(actorToCheck)) | 90 | const actorObject = await fetchAPObjectIfNeeded<ActivityPubActor>(getAPId(actorToCheck)) |
91 | 91 | ||
92 | if (!actorObject) { | 92 | if (!actorObject) { |
93 | logger.warn('Unknown attributed to actor %s for owner %s', actorToCheck, rootUrl) | 93 | logger.warn('Unknown attributed to actor %s for owner %s', actorToCheck, rootUrl) |
diff --git a/server/lib/activitypub/actors/shared/url-to-object.ts b/server/lib/activitypub/actors/shared/url-to-object.ts index 208d108ee..73766bd50 100644 --- a/server/lib/activitypub/actors/shared/url-to-object.ts +++ b/server/lib/activitypub/actors/shared/url-to-object.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor' | 1 | import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor' |
2 | import { logger } from '@server/helpers/logger' | 2 | import { logger } from '@server/helpers/logger' |
3 | import { doJSONRequest } from '@server/helpers/requests' | ||
4 | import { ActivityPubActor, ActivityPubOrderedCollection } from '@shared/models' | 3 | import { ActivityPubActor, ActivityPubOrderedCollection } from '@shared/models' |
4 | import { fetchAP } from '../../activity' | ||
5 | import { checkUrlsSameHost } from '../../url' | 5 | import { checkUrlsSameHost } from '../../url' |
6 | 6 | ||
7 | async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode: number, actorObject: ActivityPubActor }> { | 7 | async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode: number, actorObject: ActivityPubActor }> { |
8 | logger.info('Fetching remote actor %s.', actorUrl) | 8 | logger.info('Fetching remote actor %s.', actorUrl) |
9 | 9 | ||
10 | const { body, statusCode } = await doJSONRequest<ActivityPubActor>(actorUrl, { activityPub: true }) | 10 | const { body, statusCode } = await fetchAP<ActivityPubActor>(actorUrl) |
11 | 11 | ||
12 | if (sanitizeAndCheckActorObject(body) === false) { | 12 | if (sanitizeAndCheckActorObject(body) === false) { |
13 | logger.debug('Remote actor JSON is not valid.', { actorJSON: body }) | 13 | logger.debug('Remote actor JSON is not valid.', { actorJSON: body }) |
@@ -46,7 +46,7 @@ export { | |||
46 | 46 | ||
47 | async function fetchActorTotalItems (url: string) { | 47 | async function fetchActorTotalItems (url: string) { |
48 | try { | 48 | try { |
49 | const { body } = await doJSONRequest<ActivityPubOrderedCollection<unknown>>(url, { activityPub: true }) | 49 | const { body } = await fetchAP<ActivityPubOrderedCollection<unknown>>(url) |
50 | 50 | ||
51 | return body.totalItems || 0 | 51 | return body.totalItems || 0 |
52 | } catch (err) { | 52 | } catch (err) { |
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts index 336129b82..b8348e8cf 100644 --- a/server/lib/activitypub/crawl.ts +++ b/server/lib/activitypub/crawl.ts | |||
@@ -3,8 +3,8 @@ import { URL } from 'url' | |||
3 | import { retryTransactionWrapper } from '@server/helpers/database-utils' | 3 | import { retryTransactionWrapper } from '@server/helpers/database-utils' |
4 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' | 4 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' |
5 | import { logger } from '../../helpers/logger' | 5 | import { logger } from '../../helpers/logger' |
6 | import { doJSONRequest } from '../../helpers/requests' | ||
7 | import { ACTIVITY_PUB, WEBSERVER } from '../../initializers/constants' | 6 | import { ACTIVITY_PUB, WEBSERVER } from '../../initializers/constants' |
7 | import { fetchAP } from './activity' | ||
8 | 8 | ||
9 | type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) | 9 | type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) |
10 | type CleanerFunction = (startedDate: Date) => Promise<any> | 10 | type CleanerFunction = (startedDate: Date) => Promise<any> |
@@ -14,11 +14,9 @@ async function crawlCollectionPage <T> (argUrl: string, handler: HandlerFunction | |||
14 | 14 | ||
15 | logger.info('Crawling ActivityPub data on %s.', url) | 15 | logger.info('Crawling ActivityPub data on %s.', url) |
16 | 16 | ||
17 | const options = { activityPub: true } | ||
18 | |||
19 | const startDate = new Date() | 17 | const startDate = new Date() |
20 | 18 | ||
21 | const response = await doJSONRequest<ActivityPubOrderedCollection<T>>(url, options) | 19 | const response = await fetchAP<ActivityPubOrderedCollection<T>>(url) |
22 | const firstBody = response.body | 20 | const firstBody = response.body |
23 | 21 | ||
24 | const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT | 22 | const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT |
@@ -34,7 +32,7 @@ async function crawlCollectionPage <T> (argUrl: string, handler: HandlerFunction | |||
34 | 32 | ||
35 | url = nextLink | 33 | url = nextLink |
36 | 34 | ||
37 | const res = await doJSONRequest<ActivityPubOrderedCollection<T>>(url, options) | 35 | const res = await fetchAP<ActivityPubOrderedCollection<T>>(url) |
38 | body = res.body | 36 | body = res.body |
39 | } else { | 37 | } else { |
40 | // nextLink is already the object we want | 38 | // nextLink is already the object we want |
diff --git a/server/lib/activitypub/playlists/shared/url-to-object.ts b/server/lib/activitypub/playlists/shared/url-to-object.ts index 41bee3752..fd9fe5558 100644 --- a/server/lib/activitypub/playlists/shared/url-to-object.ts +++ b/server/lib/activitypub/playlists/shared/url-to-object.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '@server/helpers/custom-validators/activitypub/playlist' | 1 | import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '@server/helpers/custom-validators/activitypub/playlist' |
2 | import { isArray } from '@server/helpers/custom-validators/misc' | 2 | import { isArray } from '@server/helpers/custom-validators/misc' |
3 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 3 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
4 | import { doJSONRequest } from '@server/helpers/requests' | ||
5 | import { PlaylistElementObject, PlaylistObject } from '@shared/models' | 4 | import { PlaylistElementObject, PlaylistObject } from '@shared/models' |
5 | import { fetchAP } from '../../activity' | ||
6 | import { checkUrlsSameHost } from '../../url' | 6 | import { checkUrlsSameHost } from '../../url' |
7 | 7 | ||
8 | async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusCode: number, playlistObject: PlaylistObject }> { | 8 | async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusCode: number, playlistObject: PlaylistObject }> { |
@@ -10,7 +10,7 @@ async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusC | |||
10 | 10 | ||
11 | logger.info('Fetching remote playlist %s.', playlistUrl, lTags()) | 11 | logger.info('Fetching remote playlist %s.', playlistUrl, lTags()) |
12 | 12 | ||
13 | const { body, statusCode } = await doJSONRequest<any>(playlistUrl, { activityPub: true }) | 13 | const { body, statusCode } = await fetchAP<any>(playlistUrl) |
14 | 14 | ||
15 | if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) { | 15 | if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) { |
16 | logger.debug('Remote video playlist JSON is not valid.', { body, ...lTags() }) | 16 | logger.debug('Remote video playlist JSON is not valid.', { body, ...lTags() }) |
@@ -30,7 +30,7 @@ async function fetchRemotePlaylistElement (elementUrl: string): Promise<{ status | |||
30 | 30 | ||
31 | logger.debug('Fetching remote playlist element %s.', elementUrl, lTags()) | 31 | logger.debug('Fetching remote playlist element %s.', elementUrl, lTags()) |
32 | 32 | ||
33 | const { body, statusCode } = await doJSONRequest<PlaylistElementObject>(elementUrl, { activityPub: true }) | 33 | const { body, statusCode } = await fetchAP<PlaylistElementObject>(elementUrl) |
34 | 34 | ||
35 | if (!isPlaylistElementObjectValid(body)) throw new Error(`Invalid body in fetch playlist element ${elementUrl}`) | 35 | if (!isPlaylistElementObjectValid(body)) throw new Error(`Invalid body in fetch playlist element ${elementUrl}`) |
36 | 36 | ||
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 2e64d981e..5f980de65 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -18,7 +18,7 @@ import { sequelizeTypescript } from '../../../initializers/database' | |||
18 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 18 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
19 | import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models' | 19 | import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models' |
20 | import { Notifier } from '../../notifier' | 20 | import { Notifier } from '../../notifier' |
21 | import { fetchAPObject } from '../activity' | 21 | import { fetchAPObjectIfNeeded } from '../activity' |
22 | import { createOrUpdateCacheFile } from '../cache-file' | 22 | import { createOrUpdateCacheFile } from '../cache-file' |
23 | import { createOrUpdateLocalVideoViewer } from '../local-video-viewer' | 23 | import { createOrUpdateLocalVideoViewer } from '../local-video-viewer' |
24 | import { createOrUpdateVideoPlaylist } from '../playlists' | 24 | import { createOrUpdateVideoPlaylist } from '../playlists' |
@@ -31,7 +31,7 @@ async function processCreateActivity (options: APProcessorOptions<ActivityCreate | |||
31 | 31 | ||
32 | // Only notify if it is not from a fetcher job | 32 | // Only notify if it is not from a fetcher job |
33 | const notify = options.fromFetch !== true | 33 | const notify = options.fromFetch !== true |
34 | const activityObject = await fetchAPObject<Exclude<ActivityObject, AbuseObject>>(activity.object) | 34 | const activityObject = await fetchAPObjectIfNeeded<Exclude<ActivityObject, AbuseObject>>(activity.object) |
35 | const activityType = activityObject.type | 35 | const activityType = activityObject.type |
36 | 36 | ||
37 | if (activityType === 'Video') { | 37 | if (activityType === 'Video') { |
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index 25f68724d..a9d8199de 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts | |||
@@ -19,7 +19,7 @@ import { VideoRedundancyModel } from '../../../models/redundancy/video-redundanc | |||
19 | import { VideoShareModel } from '../../../models/video/video-share' | 19 | import { VideoShareModel } from '../../../models/video/video-share' |
20 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 20 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
21 | import { MActorSignature } from '../../../types/models' | 21 | import { MActorSignature } from '../../../types/models' |
22 | import { fetchAPObject } from '../activity' | 22 | import { fetchAPObjectIfNeeded } from '../activity' |
23 | import { forwardVideoRelatedActivity } from '../send/shared/send-utils' | 23 | import { forwardVideoRelatedActivity } from '../send/shared/send-utils' |
24 | import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos' | 24 | import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos' |
25 | 25 | ||
@@ -32,7 +32,7 @@ async function processUndoActivity (options: APProcessorOptions<ActivityUndo<Act | |||
32 | } | 32 | } |
33 | 33 | ||
34 | if (activityToUndo.type === 'Create') { | 34 | if (activityToUndo.type === 'Create') { |
35 | const objectToUndo = await fetchAPObject<CacheFileObject>(activityToUndo.object) | 35 | const objectToUndo = await fetchAPObjectIfNeeded<CacheFileObject>(activityToUndo.object) |
36 | 36 | ||
37 | if (objectToUndo.type === 'CacheFile') { | 37 | if (objectToUndo.type === 'CacheFile') { |
38 | return retryTransactionWrapper(processUndoCacheFile, byActor, activity, objectToUndo) | 38 | return retryTransactionWrapper(processUndoCacheFile, byActor, activity, objectToUndo) |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 9caa74e04..304ed9de6 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -10,7 +10,7 @@ import { sequelizeTypescript } from '../../../initializers/database' | |||
10 | import { ActorModel } from '../../../models/actor/actor' | 10 | import { ActorModel } from '../../../models/actor/actor' |
11 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 11 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
12 | import { MActorFull, MActorSignature } from '../../../types/models' | 12 | import { MActorFull, MActorSignature } from '../../../types/models' |
13 | import { fetchAPObject } from '../activity' | 13 | import { fetchAPObjectIfNeeded } from '../activity' |
14 | import { APActorUpdater } from '../actors/updater' | 14 | import { APActorUpdater } from '../actors/updater' |
15 | import { createOrUpdateCacheFile } from '../cache-file' | 15 | import { createOrUpdateCacheFile } from '../cache-file' |
16 | import { createOrUpdateVideoPlaylist } from '../playlists' | 16 | import { createOrUpdateVideoPlaylist } from '../playlists' |
@@ -20,7 +20,7 @@ import { APVideoUpdater, getOrCreateAPVideo } from '../videos' | |||
20 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) { | 20 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) { |
21 | const { activity, byActor } = options | 21 | const { activity, byActor } = options |
22 | 22 | ||
23 | const object = await fetchAPObject(activity.object) | 23 | const object = await fetchAPObjectIfNeeded(activity.object) |
24 | const objectType = object.type | 24 | const objectType = object.type |
25 | 25 | ||
26 | if (objectType === 'Video') { | 26 | if (objectType === 'Video') { |
diff --git a/server/lib/activitypub/send/http.ts b/server/lib/activitypub/send/http.ts index ad7869853..b461aa55d 100644 --- a/server/lib/activitypub/send/http.ts +++ b/server/lib/activitypub/send/http.ts | |||
@@ -23,11 +23,14 @@ async function computeBody <T> ( | |||
23 | return body | 23 | return body |
24 | } | 24 | } |
25 | 25 | ||
26 | async function buildSignedRequestOptions (payload: Payload<any>) { | 26 | async function buildSignedRequestOptions (options: { |
27 | signatureActorId?: number | ||
28 | hasPayload: boolean | ||
29 | }) { | ||
27 | let actor: MActor | null | 30 | let actor: MActor | null |
28 | 31 | ||
29 | if (payload.signatureActorId) { | 32 | if (options.signatureActorId) { |
30 | actor = await ActorModel.load(payload.signatureActorId) | 33 | actor = await ActorModel.load(options.signatureActorId) |
31 | if (!actor) throw new Error('Unknown signature actor id.') | 34 | if (!actor) throw new Error('Unknown signature actor id.') |
32 | } else { | 35 | } else { |
33 | // We need to sign the request, so use the server | 36 | // We need to sign the request, so use the server |
@@ -40,7 +43,9 @@ async function buildSignedRequestOptions (payload: Payload<any>) { | |||
40 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, | 43 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, |
41 | keyId, | 44 | keyId, |
42 | key: actor.privateKey, | 45 | key: actor.privateKey, |
43 | headers: HTTP_SIGNATURE.HEADERS_TO_SIGN | 46 | headers: options.hasPayload |
47 | ? HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD | ||
48 | : HTTP_SIGNATURE.HEADERS_TO_SIGN_WITHOUT_PAYLOAD | ||
44 | } | 49 | } |
45 | } | 50 | } |
46 | 51 | ||
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index af0dd510a..792a73f2a 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts | |||
@@ -2,11 +2,10 @@ import { map } from 'bluebird' | |||
2 | import { Transaction } from 'sequelize' | 2 | import { Transaction } from 'sequelize' |
3 | import { getServerActor } from '@server/models/application/application' | 3 | import { getServerActor } from '@server/models/application/application' |
4 | import { logger, loggerTagsFactory } from '../../helpers/logger' | 4 | import { logger, loggerTagsFactory } from '../../helpers/logger' |
5 | import { doJSONRequest } from '../../helpers/requests' | ||
6 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 5 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
7 | import { VideoShareModel } from '../../models/video/video-share' | 6 | import { VideoShareModel } from '../../models/video/video-share' |
8 | import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../types/models/video' | 7 | import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../types/models/video' |
9 | import { getAPId } from './activity' | 8 | import { fetchAP, getAPId } from './activity' |
10 | import { getOrCreateAPActor } from './actors' | 9 | import { getOrCreateAPActor } from './actors' |
11 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' | 10 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' |
12 | import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url' | 11 | import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url' |
@@ -56,7 +55,7 @@ export { | |||
56 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
57 | 56 | ||
58 | async function addVideoShare (shareUrl: string, video: MVideoId) { | 57 | async function addVideoShare (shareUrl: string, video: MVideoId) { |
59 | const { body } = await doJSONRequest<any>(shareUrl, { activityPub: true }) | 58 | const { body } = await fetchAP<any>(shareUrl) |
60 | if (!body?.actor) throw new Error('Body or body actor is invalid') | 59 | if (!body?.actor) throw new Error('Body or body actor is invalid') |
61 | 60 | ||
62 | const actorUrl = getAPId(body.actor) | 61 | const actorUrl = getAPId(body.actor) |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index 4fdb4e0b7..b861be5bd 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -1,12 +1,13 @@ | |||
1 | import { map } from 'bluebird' | 1 | import { map } from 'bluebird' |
2 | |||
2 | import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments' | 3 | import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments' |
3 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
4 | import { doJSONRequest } from '../../helpers/requests' | ||
5 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 5 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
6 | import { VideoCommentModel } from '../../models/video/video-comment' | 6 | import { VideoCommentModel } from '../../models/video/video-comment' |
7 | import { MComment, MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video' | 7 | import { MComment, MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video' |
8 | import { isRemoteVideoCommentAccepted } from '../moderation' | 8 | import { isRemoteVideoCommentAccepted } from '../moderation' |
9 | import { Hooks } from '../plugins/hooks' | 9 | import { Hooks } from '../plugins/hooks' |
10 | import { fetchAP } from './activity' | ||
10 | import { getOrCreateAPActor } from './actors' | 11 | import { getOrCreateAPActor } from './actors' |
11 | import { checkUrlsSameHost } from './url' | 12 | import { checkUrlsSameHost } from './url' |
12 | import { getOrCreateAPVideo } from './videos' | 13 | import { getOrCreateAPVideo } from './videos' |
@@ -139,7 +140,7 @@ async function resolveRemoteParentComment (params: ResolveThreadParams) { | |||
139 | throw new Error('Recursion limit reached when resolving a thread') | 140 | throw new Error('Recursion limit reached when resolving a thread') |
140 | } | 141 | } |
141 | 142 | ||
142 | const { body } = await doJSONRequest<any>(url, { activityPub: true }) | 143 | const { body } = await fetchAP<any>(url) |
143 | 144 | ||
144 | if (sanitizeAndCheckVideoCommentObject(body) === false) { | 145 | if (sanitizeAndCheckVideoCommentObject(body) === false) { |
145 | throw new Error(`Remote video comment JSON ${url} is not valid:` + JSON.stringify(body)) | 146 | throw new Error(`Remote video comment JSON ${url} is not valid:` + JSON.stringify(body)) |
diff --git a/server/lib/activitypub/videos/shared/url-to-object.ts b/server/lib/activitypub/videos/shared/url-to-object.ts index 5b7007530..7fe008419 100644 --- a/server/lib/activitypub/videos/shared/url-to-object.ts +++ b/server/lib/activitypub/videos/shared/url-to-object.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { sanitizeAndCheckVideoTorrentObject } from '@server/helpers/custom-validators/activitypub/videos' | 1 | import { sanitizeAndCheckVideoTorrentObject } from '@server/helpers/custom-validators/activitypub/videos' |
2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
3 | import { doJSONRequest } from '@server/helpers/requests' | ||
4 | import { VideoObject } from '@shared/models' | 3 | import { VideoObject } from '@shared/models' |
4 | import { fetchAP } from '../../activity' | ||
5 | import { checkUrlsSameHost } from '../../url' | 5 | import { checkUrlsSameHost } from '../../url' |
6 | 6 | ||
7 | const lTags = loggerTagsFactory('ap', 'video') | 7 | const lTags = loggerTagsFactory('ap', 'video') |
@@ -9,7 +9,7 @@ const lTags = loggerTagsFactory('ap', 'video') | |||
9 | async function fetchRemoteVideo (videoUrl: string): Promise<{ statusCode: number, videoObject: VideoObject }> { | 9 | async function fetchRemoteVideo (videoUrl: string): Promise<{ statusCode: number, videoObject: VideoObject }> { |
10 | logger.info('Fetching remote video %s.', videoUrl, lTags(videoUrl)) | 10 | logger.info('Fetching remote video %s.', videoUrl, lTags(videoUrl)) |
11 | 11 | ||
12 | const { statusCode, body } = await doJSONRequest<any>(videoUrl, { activityPub: true }) | 12 | const { statusCode, body } = await fetchAP<any>(videoUrl) |
13 | 13 | ||
14 | if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) { | 14 | if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) { |
15 | logger.debug('Remote video JSON is not valid.', { body, ...lTags(videoUrl) }) | 15 | logger.debug('Remote video JSON is not valid.', { body, ...lTags(videoUrl) }) |
diff --git a/server/lib/activitypub/videos/shared/video-sync-attributes.ts b/server/lib/activitypub/videos/shared/video-sync-attributes.ts index aa37f3d34..7fb933559 100644 --- a/server/lib/activitypub/videos/shared/video-sync-attributes.ts +++ b/server/lib/activitypub/videos/shared/video-sync-attributes.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import { runInReadCommittedTransaction } from '@server/helpers/database-utils' | 1 | import { runInReadCommittedTransaction } from '@server/helpers/database-utils' |
2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
3 | import { doJSONRequest } from '@server/helpers/requests' | ||
4 | import { JobQueue } from '@server/lib/job-queue' | 3 | import { JobQueue } from '@server/lib/job-queue' |
5 | import { VideoModel } from '@server/models/video/video' | 4 | import { VideoModel } from '@server/models/video/video' |
6 | import { VideoCommentModel } from '@server/models/video/video-comment' | 5 | import { VideoCommentModel } from '@server/models/video/video-comment' |
7 | import { VideoShareModel } from '@server/models/video/video-share' | 6 | import { VideoShareModel } from '@server/models/video/video-share' |
8 | import { MVideo } from '@server/types/models' | 7 | import { MVideo } from '@server/types/models' |
9 | import { ActivitypubHttpFetcherPayload, ActivityPubOrderedCollection, VideoObject } from '@shared/models' | 8 | import { ActivitypubHttpFetcherPayload, ActivityPubOrderedCollection, VideoObject } from '@shared/models' |
9 | import { fetchAP } from '../../activity' | ||
10 | import { crawlCollectionPage } from '../../crawl' | 10 | import { crawlCollectionPage } from '../../crawl' |
11 | import { addVideoShares } from '../../share' | 11 | import { addVideoShares } from '../../share' |
12 | import { addVideoComments } from '../../video-comments' | 12 | import { addVideoComments } from '../../video-comments' |
@@ -63,17 +63,15 @@ async function getRatesCount (type: 'like' | 'dislike', video: MVideo, fetchedVi | |||
63 | : fetchedVideo.dislikes | 63 | : fetchedVideo.dislikes |
64 | 64 | ||
65 | logger.info('Sync %s of video %s', type, video.url) | 65 | logger.info('Sync %s of video %s', type, video.url) |
66 | const options = { activityPub: true } | ||
67 | 66 | ||
68 | const response = await doJSONRequest<ActivityPubOrderedCollection<any>>(uri, options) | 67 | const { body } = await fetchAP<ActivityPubOrderedCollection<any>>(uri) |
69 | const totalItems = response.body.totalItems | ||
70 | 68 | ||
71 | if (isNaN(totalItems)) { | 69 | if (isNaN(body.totalItems)) { |
72 | logger.error('Cannot sync %s of video %s, totalItems is not a number', type, video.url, { body: response.body }) | 70 | logger.error('Cannot sync %s of video %s, totalItems is not a number', type, video.url, { body }) |
73 | return | 71 | return |
74 | } | 72 | } |
75 | 73 | ||
76 | return totalItems | 74 | return body.totalItems |
77 | } | 75 | } |
78 | 76 | ||
79 | function syncShares (video: MVideo, fetchedVideo: VideoObject, isSync: boolean) { | 77 | function syncShares (video: MVideo, fetchedVideo: VideoObject, isSync: boolean) { |
diff --git a/server/lib/job-queue/handlers/activitypub-cleaner.ts b/server/lib/job-queue/handlers/activitypub-cleaner.ts index a25f00b0a..6ee9e2429 100644 --- a/server/lib/job-queue/handlers/activitypub-cleaner.ts +++ b/server/lib/job-queue/handlers/activitypub-cleaner.ts | |||
@@ -6,8 +6,9 @@ import { | |||
6 | isLikeActivityValid | 6 | isLikeActivityValid |
7 | } from '@server/helpers/custom-validators/activitypub/activity' | 7 | } from '@server/helpers/custom-validators/activitypub/activity' |
8 | import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments' | 8 | import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments' |
9 | import { doJSONRequest, PeerTubeRequestError } from '@server/helpers/requests' | 9 | import { PeerTubeRequestError } from '@server/helpers/requests' |
10 | import { AP_CLEANER } from '@server/initializers/constants' | 10 | import { AP_CLEANER } from '@server/initializers/constants' |
11 | import { fetchAP } from '@server/lib/activitypub/activity' | ||
11 | import { checkUrlsSameHost } from '@server/lib/activitypub/url' | 12 | import { checkUrlsSameHost } from '@server/lib/activitypub/url' |
12 | import { Redis } from '@server/lib/redis' | 13 | import { Redis } from '@server/lib/redis' |
13 | import { VideoModel } from '@server/models/video/video' | 14 | import { VideoModel } from '@server/models/video/video' |
@@ -85,7 +86,7 @@ async function updateObjectIfNeeded <T> (options: { | |||
85 | } | 86 | } |
86 | 87 | ||
87 | try { | 88 | try { |
88 | const { body } = await doJSONRequest<any>(url, { activityPub: true }) | 89 | const { body } = await fetchAP<any>(url) |
89 | 90 | ||
90 | // If not same id, check same host and update | 91 | // If not same id, check same host and update |
91 | if (!body?.id || !bodyValidator(body)) throw new Error(`Body or body id of ${url} is invalid`) | 92 | if (!body?.id || !bodyValidator(body)) throw new Error(`Body or body id of ${url} is invalid`) |
diff --git a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts index 57ecf0acc..8904d086f 100644 --- a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts | |||
@@ -38,7 +38,7 @@ export { | |||
38 | 38 | ||
39 | async function buildRequestOptions (payload: ActivitypubHttpBroadcastPayload) { | 39 | async function buildRequestOptions (payload: ActivitypubHttpBroadcastPayload) { |
40 | const body = await computeBody(payload) | 40 | const body = await computeBody(payload) |
41 | const httpSignatureOptions = await buildSignedRequestOptions(payload) | 41 | const httpSignatureOptions = await buildSignedRequestOptions({ signatureActorId: payload.signatureActorId, hasPayload: true }) |
42 | 42 | ||
43 | return { | 43 | return { |
44 | method: 'POST' as 'POST', | 44 | method: 'POST' as 'POST', |
diff --git a/server/lib/job-queue/handlers/activitypub-http-unicast.ts b/server/lib/job-queue/handlers/activitypub-http-unicast.ts index 9e4e84002..50fca3f94 100644 --- a/server/lib/job-queue/handlers/activitypub-http-unicast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-unicast.ts | |||
@@ -12,7 +12,7 @@ async function processActivityPubHttpUnicast (job: Job) { | |||
12 | const uri = payload.uri | 12 | const uri = payload.uri |
13 | 13 | ||
14 | const body = await computeBody(payload) | 14 | const body = await computeBody(payload) |
15 | const httpSignatureOptions = await buildSignedRequestOptions(payload) | 15 | const httpSignatureOptions = await buildSignedRequestOptions({ signatureActorId: payload.signatureActorId, hasPayload: true }) |
16 | 16 | ||
17 | const options = { | 17 | const options = { |
18 | method: 'POST' as 'POST', | 18 | method: 'POST' as 'POST', |
diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts index 31ebc89b4..8e87361a9 100644 --- a/server/tests/api/activitypub/security.ts +++ b/server/tests/api/activitypub/security.ts | |||
@@ -58,7 +58,7 @@ async function makeFollowRequest (to: { url: string }, by: { url: string, privat | |||
58 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, | 58 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, |
59 | keyId: by.url, | 59 | keyId: by.url, |
60 | key: by.privateKey, | 60 | key: by.privateKey, |
61 | headers: HTTP_SIGNATURE.HEADERS_TO_SIGN | 61 | headers: HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD |
62 | } | 62 | } |
63 | const headers = { | 63 | const headers = { |
64 | 'digest': buildDigest(body), | 64 | 'digest': buildDigest(body), |
@@ -82,7 +82,7 @@ describe('Test ActivityPub security', function () { | |||
82 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, | 82 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, |
83 | keyId: 'acct:peertube@' + servers[1].host, | 83 | keyId: 'acct:peertube@' + servers[1].host, |
84 | key: keys.privateKey, | 84 | key: keys.privateKey, |
85 | headers: HTTP_SIGNATURE.HEADERS_TO_SIGN | 85 | headers: HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD |
86 | }) | 86 | }) |
87 | 87 | ||
88 | // --------------------------------------------------------------- | 88 | // --------------------------------------------------------------- |
diff --git a/support/docker/production/config/custom-environment-variables.yaml b/support/docker/production/config/custom-environment-variables.yaml index 0058cbd64..7ac794909 100644 --- a/support/docker/production/config/custom-environment-variables.yaml +++ b/support/docker/production/config/custom-environment-variables.yaml | |||
@@ -12,6 +12,11 @@ webserver: | |||
12 | __name: "PEERTUBE_WEBSERVER_HTTPS" | 12 | __name: "PEERTUBE_WEBSERVER_HTTPS" |
13 | __format: "json" | 13 | __format: "json" |
14 | 14 | ||
15 | federation: | ||
16 | sign_federated_fetches: | ||
17 | __name: "PEERTUBE_SIGN_FEDERATED_FETCHES" | ||
18 | __format: "json" | ||
19 | |||
15 | secrets: | 20 | secrets: |
16 | peertube: "PEERTUBE_SECRET" | 21 | peertube: "PEERTUBE_SECRET" |
17 | 22 | ||