aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--config/default.yaml3
-rw-r--r--config/production.yaml.example3
-rw-r--r--server/controllers/api/search/search-video-channels.ts5
-rw-r--r--server/controllers/api/search/search-video-playlists.ts5
-rw-r--r--server/controllers/api/search/search-videos.ts5
-rw-r--r--server/helpers/requests.ts16
-rw-r--r--server/initializers/config.ts3
-rw-r--r--server/initializers/constants.ts3
-rw-r--r--server/lib/activitypub/activity.ts50
-rw-r--r--server/lib/activitypub/actors/get.ts4
-rw-r--r--server/lib/activitypub/actors/shared/url-to-object.ts6
-rw-r--r--server/lib/activitypub/crawl.ts8
-rw-r--r--server/lib/activitypub/playlists/shared/url-to-object.ts6
-rw-r--r--server/lib/activitypub/process/process-create.ts4
-rw-r--r--server/lib/activitypub/process/process-undo.ts4
-rw-r--r--server/lib/activitypub/process/process-update.ts4
-rw-r--r--server/lib/activitypub/send/http.ts13
-rw-r--r--server/lib/activitypub/share.ts5
-rw-r--r--server/lib/activitypub/video-comments.ts5
-rw-r--r--server/lib/activitypub/videos/shared/url-to-object.ts4
-rw-r--r--server/lib/activitypub/videos/shared/video-sync-attributes.ts12
-rw-r--r--server/lib/job-queue/handlers/activitypub-cleaner.ts5
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-broadcast.ts2
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-unicast.ts2
-rw-r--r--server/tests/api/activitypub/security.ts4
-rw-r--r--support/docker/production/config/custom-environment-variables.yaml5
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
376federation: 376federation:
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
374federation: 374federation:
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 @@
1import express from 'express' 1import express from 'express'
2import { sanitizeUrl } from '@server/helpers/core-utils' 2import { sanitizeUrl } from '@server/helpers/core-utils'
3import { pickSearchChannelQuery } from '@server/helpers/query' 3import { pickSearchChannelQuery } from '@server/helpers/query'
4import { doJSONRequest, findLatestRedirection } from '@server/helpers/requests' 4import { doJSONRequest } from '@server/helpers/requests'
5import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
6import { WEBSERVER } from '@server/initializers/constants' 6import { WEBSERVER } from '@server/initializers/constants'
7import { findLatestAPRedirection } from '@server/lib/activitypub/activity'
7import { Hooks } from '@server/lib/plugins/hooks' 8import { Hooks } from '@server/lib/plugins/hooks'
8import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' 9import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
9import { getServerActor } from '@server/models/application/application' 10import { 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'
3import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils' 3import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils'
4import { logger } from '@server/helpers/logger' 4import { logger } from '@server/helpers/logger'
5import { pickSearchPlaylistQuery } from '@server/helpers/query' 5import { pickSearchPlaylistQuery } from '@server/helpers/query'
6import { doJSONRequest, findLatestRedirection } from '@server/helpers/requests' 6import { doJSONRequest } from '@server/helpers/requests'
7import { getFormattedObjects } from '@server/helpers/utils' 7import { getFormattedObjects } from '@server/helpers/utils'
8import { CONFIG } from '@server/initializers/config' 8import { CONFIG } from '@server/initializers/config'
9import { WEBSERVER } from '@server/initializers/constants' 9import { WEBSERVER } from '@server/initializers/constants'
10import { findLatestAPRedirection } from '@server/lib/activitypub/activity'
10import { getOrCreateAPVideoPlaylist } from '@server/lib/activitypub/playlists/get' 11import { getOrCreateAPVideoPlaylist } from '@server/lib/activitypub/playlists/get'
11import { Hooks } from '@server/lib/plugins/hooks' 12import { Hooks } from '@server/lib/plugins/hooks'
12import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' 13import { 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 @@
1import express from 'express' 1import express from 'express'
2import { sanitizeUrl } from '@server/helpers/core-utils' 2import { sanitizeUrl } from '@server/helpers/core-utils'
3import { pickSearchVideoQuery } from '@server/helpers/query' 3import { pickSearchVideoQuery } from '@server/helpers/query'
4import { doJSONRequest, findLatestRedirection } from '@server/helpers/requests' 4import { doJSONRequest } from '@server/helpers/requests'
5import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
6import { WEBSERVER } from '@server/initializers/constants' 6import { WEBSERVER } from '@server/initializers/constants'
7import { findLatestAPRedirection } from '@server/lib/activitypub/activity'
7import { getOrCreateAPVideo } from '@server/lib/activitypub/videos' 8import { getOrCreateAPVideo } from '@server/lib/activitypub/videos'
8import { Hooks } from '@server/lib/plugins/hooks' 9import { Hooks } from '@server/lib/plugins/hooks'
9import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search' 10import { 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
34const peertubeGot = got.extend({ 38const 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
183async 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
195export { 189export {
@@ -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 } = {
712const HTTP_SIGNATURE = { 712const 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 @@
1import { doJSONRequest } from '@server/helpers/requests' 1import { doJSONRequest, PeerTubeRequestOptions } from '@server/helpers/requests'
2import { APObjectId, ActivityObject, ActivityPubActor, ActivityType } from '@shared/models' 2import { CONFIG } from '@server/initializers/config'
3import { ActivityObject, ActivityPubActor, ActivityType, APObjectId } from '@shared/models'
4import { buildSignedRequestOptions } from './send'
3 5
4function getAPId (object: string | { id: string }) { 6export 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
10function getActivityStreamDuration (duration: number) { 12export 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
15function getDurationFromActivityStream (duration: string) { 17export function getDurationFromActivityStream (duration: string) {
16 return parseInt(duration.replace(/[^\d]+/, '')) 18 return parseInt(duration.replace(/[^\d]+/, ''))
17} 19}
18 20
19function buildAvailableActivities (): ActivityType[] { 21// ---------------------------------------------------------------------------
22
23export 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
36async function fetchAPObject <T extends (ActivityObject | ActivityPubActor)> (object: APObjectId) { 40// ---------------------------------------------------------------------------
41
42export 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
56export 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
46export { 66export 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'
5import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models' 5import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models'
6import { arrayify } from '@shared/core-utils' 6import { arrayify } from '@shared/core-utils'
7import { ActivityPubActor, APObjectId } from '@shared/models' 7import { ActivityPubActor, APObjectId } from '@shared/models'
8import { fetchAPObject, getAPId } from '../activity' 8import { fetchAPObjectIfNeeded, getAPId } from '../activity'
9import { checkUrlsSameHost } from '../url' 9import { checkUrlsSameHost } from '../url'
10import { refreshActorIfNeeded } from './refresh' 10import { refreshActorIfNeeded } from './refresh'
11import { APActorCreator, fetchRemoteActor } from './shared' 11import { APActorCreator, fetchRemoteActor } from './shared'
@@ -87,7 +87,7 @@ async function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: stri
87 87
88async function findOwner (rootUrl: string, attributedTo: APObjectId[] | APObjectId, type: 'Person' | 'Group') { 88async 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 @@
1import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor' 1import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor'
2import { logger } from '@server/helpers/logger' 2import { logger } from '@server/helpers/logger'
3import { doJSONRequest } from '@server/helpers/requests'
4import { ActivityPubActor, ActivityPubOrderedCollection } from '@shared/models' 3import { ActivityPubActor, ActivityPubOrderedCollection } from '@shared/models'
4import { fetchAP } from '../../activity'
5import { checkUrlsSameHost } from '../../url' 5import { checkUrlsSameHost } from '../../url'
6 6
7async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode: number, actorObject: ActivityPubActor }> { 7async 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
47async function fetchActorTotalItems (url: string) { 47async 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'
3import { retryTransactionWrapper } from '@server/helpers/database-utils' 3import { retryTransactionWrapper } from '@server/helpers/database-utils'
4import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' 4import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
5import { logger } from '../../helpers/logger' 5import { logger } from '../../helpers/logger'
6import { doJSONRequest } from '../../helpers/requests'
7import { ACTIVITY_PUB, WEBSERVER } from '../../initializers/constants' 6import { ACTIVITY_PUB, WEBSERVER } from '../../initializers/constants'
7import { fetchAP } from './activity'
8 8
9type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) 9type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>)
10type CleanerFunction = (startedDate: Date) => Promise<any> 10type 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 @@
1import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '@server/helpers/custom-validators/activitypub/playlist' 1import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '@server/helpers/custom-validators/activitypub/playlist'
2import { isArray } from '@server/helpers/custom-validators/misc' 2import { isArray } from '@server/helpers/custom-validators/misc'
3import { logger, loggerTagsFactory } from '@server/helpers/logger' 3import { logger, loggerTagsFactory } from '@server/helpers/logger'
4import { doJSONRequest } from '@server/helpers/requests'
5import { PlaylistElementObject, PlaylistObject } from '@shared/models' 4import { PlaylistElementObject, PlaylistObject } from '@shared/models'
5import { fetchAP } from '../../activity'
6import { checkUrlsSameHost } from '../../url' 6import { checkUrlsSameHost } from '../../url'
7 7
8async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusCode: number, playlistObject: PlaylistObject }> { 8async 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'
18import { APProcessorOptions } from '../../../types/activitypub-processor.model' 18import { APProcessorOptions } from '../../../types/activitypub-processor.model'
19import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models' 19import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models'
20import { Notifier } from '../../notifier' 20import { Notifier } from '../../notifier'
21import { fetchAPObject } from '../activity' 21import { fetchAPObjectIfNeeded } from '../activity'
22import { createOrUpdateCacheFile } from '../cache-file' 22import { createOrUpdateCacheFile } from '../cache-file'
23import { createOrUpdateLocalVideoViewer } from '../local-video-viewer' 23import { createOrUpdateLocalVideoViewer } from '../local-video-viewer'
24import { createOrUpdateVideoPlaylist } from '../playlists' 24import { 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
19import { VideoShareModel } from '../../../models/video/video-share' 19import { VideoShareModel } from '../../../models/video/video-share'
20import { APProcessorOptions } from '../../../types/activitypub-processor.model' 20import { APProcessorOptions } from '../../../types/activitypub-processor.model'
21import { MActorSignature } from '../../../types/models' 21import { MActorSignature } from '../../../types/models'
22import { fetchAPObject } from '../activity' 22import { fetchAPObjectIfNeeded } from '../activity'
23import { forwardVideoRelatedActivity } from '../send/shared/send-utils' 23import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
24import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos' 24import { 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'
10import { ActorModel } from '../../../models/actor/actor' 10import { ActorModel } from '../../../models/actor/actor'
11import { APProcessorOptions } from '../../../types/activitypub-processor.model' 11import { APProcessorOptions } from '../../../types/activitypub-processor.model'
12import { MActorFull, MActorSignature } from '../../../types/models' 12import { MActorFull, MActorSignature } from '../../../types/models'
13import { fetchAPObject } from '../activity' 13import { fetchAPObjectIfNeeded } from '../activity'
14import { APActorUpdater } from '../actors/updater' 14import { APActorUpdater } from '../actors/updater'
15import { createOrUpdateCacheFile } from '../cache-file' 15import { createOrUpdateCacheFile } from '../cache-file'
16import { createOrUpdateVideoPlaylist } from '../playlists' 16import { createOrUpdateVideoPlaylist } from '../playlists'
@@ -20,7 +20,7 @@ import { APVideoUpdater, getOrCreateAPVideo } from '../videos'
20async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) { 20async 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
26async function buildSignedRequestOptions (payload: Payload<any>) { 26async 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'
2import { Transaction } from 'sequelize' 2import { Transaction } from 'sequelize'
3import { getServerActor } from '@server/models/application/application' 3import { getServerActor } from '@server/models/application/application'
4import { logger, loggerTagsFactory } from '../../helpers/logger' 4import { logger, loggerTagsFactory } from '../../helpers/logger'
5import { doJSONRequest } from '../../helpers/requests'
6import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 5import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
7import { VideoShareModel } from '../../models/video/video-share' 6import { VideoShareModel } from '../../models/video/video-share'
8import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../types/models/video' 7import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../types/models/video'
9import { getAPId } from './activity' 8import { fetchAP, getAPId } from './activity'
10import { getOrCreateAPActor } from './actors' 9import { getOrCreateAPActor } from './actors'
11import { sendUndoAnnounce, sendVideoAnnounce } from './send' 10import { sendUndoAnnounce, sendVideoAnnounce } from './send'
12import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url' 11import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url'
@@ -56,7 +55,7 @@ export {
56// --------------------------------------------------------------------------- 55// ---------------------------------------------------------------------------
57 56
58async function addVideoShare (shareUrl: string, video: MVideoId) { 57async 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 @@
1import { map } from 'bluebird' 1import { map } from 'bluebird'
2
2import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments' 3import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments'
3import { logger } from '../../helpers/logger' 4import { logger } from '../../helpers/logger'
4import { doJSONRequest } from '../../helpers/requests'
5import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 5import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
6import { VideoCommentModel } from '../../models/video/video-comment' 6import { VideoCommentModel } from '../../models/video/video-comment'
7import { MComment, MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video' 7import { MComment, MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video'
8import { isRemoteVideoCommentAccepted } from '../moderation' 8import { isRemoteVideoCommentAccepted } from '../moderation'
9import { Hooks } from '../plugins/hooks' 9import { Hooks } from '../plugins/hooks'
10import { fetchAP } from './activity'
10import { getOrCreateAPActor } from './actors' 11import { getOrCreateAPActor } from './actors'
11import { checkUrlsSameHost } from './url' 12import { checkUrlsSameHost } from './url'
12import { getOrCreateAPVideo } from './videos' 13import { 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 @@
1import { sanitizeAndCheckVideoTorrentObject } from '@server/helpers/custom-validators/activitypub/videos' 1import { sanitizeAndCheckVideoTorrentObject } from '@server/helpers/custom-validators/activitypub/videos'
2import { logger, loggerTagsFactory } from '@server/helpers/logger' 2import { logger, loggerTagsFactory } from '@server/helpers/logger'
3import { doJSONRequest } from '@server/helpers/requests'
4import { VideoObject } from '@shared/models' 3import { VideoObject } from '@shared/models'
4import { fetchAP } from '../../activity'
5import { checkUrlsSameHost } from '../../url' 5import { checkUrlsSameHost } from '../../url'
6 6
7const lTags = loggerTagsFactory('ap', 'video') 7const lTags = loggerTagsFactory('ap', 'video')
@@ -9,7 +9,7 @@ const lTags = loggerTagsFactory('ap', 'video')
9async function fetchRemoteVideo (videoUrl: string): Promise<{ statusCode: number, videoObject: VideoObject }> { 9async 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 @@
1import { runInReadCommittedTransaction } from '@server/helpers/database-utils' 1import { runInReadCommittedTransaction } from '@server/helpers/database-utils'
2import { logger, loggerTagsFactory } from '@server/helpers/logger' 2import { logger, loggerTagsFactory } from '@server/helpers/logger'
3import { doJSONRequest } from '@server/helpers/requests'
4import { JobQueue } from '@server/lib/job-queue' 3import { JobQueue } from '@server/lib/job-queue'
5import { VideoModel } from '@server/models/video/video' 4import { VideoModel } from '@server/models/video/video'
6import { VideoCommentModel } from '@server/models/video/video-comment' 5import { VideoCommentModel } from '@server/models/video/video-comment'
7import { VideoShareModel } from '@server/models/video/video-share' 6import { VideoShareModel } from '@server/models/video/video-share'
8import { MVideo } from '@server/types/models' 7import { MVideo } from '@server/types/models'
9import { ActivitypubHttpFetcherPayload, ActivityPubOrderedCollection, VideoObject } from '@shared/models' 8import { ActivitypubHttpFetcherPayload, ActivityPubOrderedCollection, VideoObject } from '@shared/models'
9import { fetchAP } from '../../activity'
10import { crawlCollectionPage } from '../../crawl' 10import { crawlCollectionPage } from '../../crawl'
11import { addVideoShares } from '../../share' 11import { addVideoShares } from '../../share'
12import { addVideoComments } from '../../video-comments' 12import { 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
79function syncShares (video: MVideo, fetchedVideo: VideoObject, isSync: boolean) { 77function 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'
8import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments' 8import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments'
9import { doJSONRequest, PeerTubeRequestError } from '@server/helpers/requests' 9import { PeerTubeRequestError } from '@server/helpers/requests'
10import { AP_CLEANER } from '@server/initializers/constants' 10import { AP_CLEANER } from '@server/initializers/constants'
11import { fetchAP } from '@server/lib/activitypub/activity'
11import { checkUrlsSameHost } from '@server/lib/activitypub/url' 12import { checkUrlsSameHost } from '@server/lib/activitypub/url'
12import { Redis } from '@server/lib/redis' 13import { Redis } from '@server/lib/redis'
13import { VideoModel } from '@server/models/video/video' 14import { 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
39async function buildRequestOptions (payload: ActivitypubHttpBroadcastPayload) { 39async 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
15federation:
16 sign_federated_fetches:
17 __name: "PEERTUBE_SIGN_FEDERATED_FETCHES"
18 __format: "json"
19
15secrets: 20secrets:
16 peertube: "PEERTUBE_SECRET" 21 peertube: "PEERTUBE_SECRET"
17 22