Add an option to sign federated fetches for mastodon compatibility (#5898)
* Fix player error modal Not hidden when we change the video * Correctly dispose player components * Sign cross-server fetch requests for mastodon AUTHORIZED_FETCH compatibilty * Add a remote fetch sign configuration knob * Federated fetches refactoring --------- Co-authored-by: Chocobozzz <me@florianbigard.com> Co-authored-by: ira <ira@foxgirl.space>
This commit is contained in:
parent
787d822cd4
commit
f862be2749
|
@ -374,6 +374,9 @@ plugins:
|
|||
url: 'https://packages.joinpeertube.org'
|
||||
|
||||
federation:
|
||||
# Some federated software such as Mastodon may require an HTTP signature to access content
|
||||
sign_federated_fetches: true
|
||||
|
||||
videos:
|
||||
federate_unlisted: false
|
||||
|
||||
|
|
|
@ -372,6 +372,9 @@ plugins:
|
|||
url: 'https://packages.joinpeertube.org'
|
||||
|
||||
federation:
|
||||
# Some federated software such as Mastodon may require an HTTP signature to access content
|
||||
sign_federated_fetches: true
|
||||
|
||||
videos:
|
||||
federate_unlisted: false
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import express from 'express'
|
||||
import { sanitizeUrl } from '@server/helpers/core-utils'
|
||||
import { pickSearchChannelQuery } from '@server/helpers/query'
|
||||
import { doJSONRequest, findLatestRedirection } from '@server/helpers/requests'
|
||||
import { doJSONRequest } from '@server/helpers/requests'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import { WEBSERVER } from '@server/initializers/constants'
|
||||
import { findLatestAPRedirection } from '@server/lib/activitypub/activity'
|
||||
import { Hooks } from '@server/lib/plugins/hooks'
|
||||
import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
|
||||
import { getServerActor } from '@server/models/application/application'
|
||||
|
@ -126,7 +127,7 @@ async function searchVideoChannelURI (search: string, res: express.Response) {
|
|||
|
||||
if (isUserAbleToSearchRemoteURI(res)) {
|
||||
try {
|
||||
const latestUri = await findLatestRedirection(uri, { activityPub: true })
|
||||
const latestUri = await findLatestAPRedirection(uri)
|
||||
|
||||
const actor = await getOrCreateAPActor(latestUri, 'all', true, true)
|
||||
videoChannel = actor.VideoChannel
|
||||
|
|
|
@ -3,10 +3,11 @@ import { sanitizeUrl } from '@server/helpers/core-utils'
|
|||
import { isUserAbleToSearchRemoteURI } from '@server/helpers/express-utils'
|
||||
import { logger } from '@server/helpers/logger'
|
||||
import { pickSearchPlaylistQuery } from '@server/helpers/query'
|
||||
import { doJSONRequest, findLatestRedirection } from '@server/helpers/requests'
|
||||
import { doJSONRequest } from '@server/helpers/requests'
|
||||
import { getFormattedObjects } from '@server/helpers/utils'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import { WEBSERVER } from '@server/initializers/constants'
|
||||
import { findLatestAPRedirection } from '@server/lib/activitypub/activity'
|
||||
import { getOrCreateAPVideoPlaylist } from '@server/lib/activitypub/playlists/get'
|
||||
import { Hooks } from '@server/lib/plugins/hooks'
|
||||
import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
|
||||
|
@ -105,7 +106,7 @@ async function searchVideoPlaylistsURI (search: string, res: express.Response) {
|
|||
|
||||
if (isUserAbleToSearchRemoteURI(res)) {
|
||||
try {
|
||||
const url = await findLatestRedirection(search, { activityPub: true })
|
||||
const url = await findLatestAPRedirection(search)
|
||||
|
||||
videoPlaylist = await getOrCreateAPVideoPlaylist(url)
|
||||
} catch (err) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import express from 'express'
|
||||
import { sanitizeUrl } from '@server/helpers/core-utils'
|
||||
import { pickSearchVideoQuery } from '@server/helpers/query'
|
||||
import { doJSONRequest, findLatestRedirection } from '@server/helpers/requests'
|
||||
import { doJSONRequest } from '@server/helpers/requests'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import { WEBSERVER } from '@server/initializers/constants'
|
||||
import { findLatestAPRedirection } from '@server/lib/activitypub/activity'
|
||||
import { getOrCreateAPVideo } from '@server/lib/activitypub/videos'
|
||||
import { Hooks } from '@server/lib/plugins/hooks'
|
||||
import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
|
||||
|
@ -141,7 +142,7 @@ async function searchVideoURI (url: string, res: express.Response) {
|
|||
}
|
||||
|
||||
const result = await getOrCreateAPVideo({
|
||||
videoObject: await findLatestRedirection(url, { activityPub: true }),
|
||||
videoObject: await findLatestAPRedirection(url),
|
||||
syncParam
|
||||
})
|
||||
video = result ? result.video : undefined
|
||||
|
|
|
@ -21,6 +21,7 @@ type PeerTubeRequestOptions = {
|
|||
timeout?: number
|
||||
activityPub?: boolean
|
||||
bodyKBLimit?: number // 1MB
|
||||
|
||||
httpSignature?: {
|
||||
algorithm: string
|
||||
authorizationHeaderName: string
|
||||
|
@ -28,7 +29,10 @@ type PeerTubeRequestOptions = {
|
|||
key: string
|
||||
headers: string[]
|
||||
}
|
||||
|
||||
jsonResponse?: boolean
|
||||
|
||||
followRedirect?: boolean
|
||||
} & Pick<GotOptions, 'headers' | 'json' | 'method' | 'searchParams'>
|
||||
|
||||
const peertubeGot = got.extend({
|
||||
|
@ -180,16 +184,6 @@ function isBinaryResponse (result: Response<any>) {
|
|||
return BINARY_CONTENT_TYPES.has(result.headers['content-type'])
|
||||
}
|
||||
|
||||
async function findLatestRedirection (url: string, options: PeerTubeRequestOptions, iteration = 1) {
|
||||
if (iteration > 10) throw new Error('Too much iterations to find final URL ' + url)
|
||||
|
||||
const { headers } = await peertubeGot(url, { followRedirect: false, ...buildGotOptions(options) })
|
||||
|
||||
if (headers.location) return findLatestRedirection(headers.location, options, iteration + 1)
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -200,7 +194,6 @@ export {
|
|||
doRequestAndSaveToFile,
|
||||
isBinaryResponse,
|
||||
getAgent,
|
||||
findLatestRedirection,
|
||||
peertubeGot
|
||||
}
|
||||
|
||||
|
@ -227,6 +220,7 @@ function buildGotOptions (options: PeerTubeRequestOptions) {
|
|||
timeout: options.timeout ?? REQUEST_TIMEOUTS.DEFAULT,
|
||||
json: options.json,
|
||||
searchParams: options.searchParams,
|
||||
followRedirect: options.followRedirect,
|
||||
retry: 2,
|
||||
headers,
|
||||
context
|
||||
|
|
|
@ -309,7 +309,8 @@ const CONFIG = {
|
|||
VIDEOS: {
|
||||
FEDERATE_UNLISTED: config.get<boolean>('federation.videos.federate_unlisted'),
|
||||
CLEANUP_REMOTE_INTERACTIONS: config.get<boolean>('federation.videos.cleanup_remote_interactions')
|
||||
}
|
||||
},
|
||||
SIGN_FEDERATED_FETCHES: config.get<boolean>('federation.sign_federated_fetches')
|
||||
},
|
||||
PEERTUBE: {
|
||||
CHECK_LATEST_VERSION: {
|
||||
|
|
|
@ -712,7 +712,8 @@ const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
|
|||
const HTTP_SIGNATURE = {
|
||||
HEADER_NAME: 'signature',
|
||||
ALGORITHM: 'rsa-sha256',
|
||||
HEADERS_TO_SIGN: [ '(request-target)', 'host', 'date', 'digest' ],
|
||||
HEADERS_TO_SIGN_WITH_PAYLOAD: [ '(request-target)', 'host', 'date', 'digest' ],
|
||||
HEADERS_TO_SIGN_WITHOUT_PAYLOAD: [ '(request-target)', 'host', 'date' ],
|
||||
CLOCK_SKEW_SECONDS: 1800
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
import { doJSONRequest } from '@server/helpers/requests'
|
||||
import { APObjectId, ActivityObject, ActivityPubActor, ActivityType } from '@shared/models'
|
||||
import { doJSONRequest, PeerTubeRequestOptions } from '@server/helpers/requests'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import { ActivityObject, ActivityPubActor, ActivityType, APObjectId } from '@shared/models'
|
||||
import { buildSignedRequestOptions } from './send'
|
||||
|
||||
function getAPId (object: string | { id: string }) {
|
||||
export function getAPId (object: string | { id: string }) {
|
||||
if (typeof object === 'string') return object
|
||||
|
||||
return object.id
|
||||
}
|
||||
|
||||
function getActivityStreamDuration (duration: number) {
|
||||
export function getActivityStreamDuration (duration: number) {
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
|
||||
return 'PT' + duration + 'S'
|
||||
}
|
||||
|
||||
function getDurationFromActivityStream (duration: string) {
|
||||
export function getDurationFromActivityStream (duration: string) {
|
||||
return parseInt(duration.replace(/[^\d]+/, ''))
|
||||
}
|
||||
|
||||
function buildAvailableActivities (): ActivityType[] {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function buildAvailableActivities (): ActivityType[] {
|
||||
return [
|
||||
'Create',
|
||||
'Update',
|
||||
|
@ -33,9 +37,25 @@ function buildAvailableActivities (): ActivityType[] {
|
|||
]
|
||||
}
|
||||
|
||||
async function fetchAPObject <T extends (ActivityObject | ActivityPubActor)> (object: APObjectId) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function fetchAP <T> (url: string, moreOptions: PeerTubeRequestOptions = {}) {
|
||||
const options = {
|
||||
activityPub: true,
|
||||
|
||||
httpSignature: CONFIG.FEDERATION.SIGN_FEDERATED_FETCHES
|
||||
? await buildSignedRequestOptions({ hasPayload: false })
|
||||
: undefined,
|
||||
|
||||
...moreOptions
|
||||
}
|
||||
|
||||
return doJSONRequest<T>(url, options)
|
||||
}
|
||||
|
||||
export async function fetchAPObjectIfNeeded <T extends (ActivityObject | ActivityPubActor)> (object: APObjectId) {
|
||||
if (typeof object === 'string') {
|
||||
const { body } = await doJSONRequest<Exclude<T, string>>(object, { activityPub: true })
|
||||
const { body } = await fetchAP<Exclude<T, string>>(object)
|
||||
|
||||
return body
|
||||
}
|
||||
|
@ -43,10 +63,12 @@ async function fetchAPObject <T extends (ActivityObject | ActivityPubActor)> (ob
|
|||
return object as Exclude<T, string>
|
||||
}
|
||||
|
||||
export {
|
||||
getAPId,
|
||||
fetchAPObject,
|
||||
getActivityStreamDuration,
|
||||
buildAvailableActivities,
|
||||
getDurationFromActivityStream
|
||||
export async function findLatestAPRedirection (url: string, iteration = 1) {
|
||||
if (iteration > 10) throw new Error('Too much iterations to find final URL ' + url)
|
||||
|
||||
const { headers } = await fetchAP(url, { followRedirect: false })
|
||||
|
||||
if (headers.location) return findLatestAPRedirection(headers.location, iteration + 1)
|
||||
|
||||
return url
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ActorLoadByUrlType, loadActorByUrl } from '@server/lib/model-loaders'
|
|||
import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models'
|
||||
import { arrayify } from '@shared/core-utils'
|
||||
import { ActivityPubActor, APObjectId } from '@shared/models'
|
||||
import { fetchAPObject, getAPId } from '../activity'
|
||||
import { fetchAPObjectIfNeeded, getAPId } from '../activity'
|
||||
import { checkUrlsSameHost } from '../url'
|
||||
import { refreshActorIfNeeded } from './refresh'
|
||||
import { APActorCreator, fetchRemoteActor } from './shared'
|
||||
|
@ -87,7 +87,7 @@ async function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: stri
|
|||
|
||||
async function findOwner (rootUrl: string, attributedTo: APObjectId[] | APObjectId, type: 'Person' | 'Group') {
|
||||
for (const actorToCheck of arrayify(attributedTo)) {
|
||||
const actorObject = await fetchAPObject<ActivityPubActor>(getAPId(actorToCheck))
|
||||
const actorObject = await fetchAPObjectIfNeeded<ActivityPubActor>(getAPId(actorToCheck))
|
||||
|
||||
if (!actorObject) {
|
||||
logger.warn('Unknown attributed to actor %s for owner %s', actorToCheck, rootUrl)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor'
|
||||
import { logger } from '@server/helpers/logger'
|
||||
import { doJSONRequest } from '@server/helpers/requests'
|
||||
import { ActivityPubActor, ActivityPubOrderedCollection } from '@shared/models'
|
||||
import { fetchAP } from '../../activity'
|
||||
import { checkUrlsSameHost } from '../../url'
|
||||
|
||||
async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode: number, actorObject: ActivityPubActor }> {
|
||||
logger.info('Fetching remote actor %s.', actorUrl)
|
||||
|
||||
const { body, statusCode } = await doJSONRequest<ActivityPubActor>(actorUrl, { activityPub: true })
|
||||
const { body, statusCode } = await fetchAP<ActivityPubActor>(actorUrl)
|
||||
|
||||
if (sanitizeAndCheckActorObject(body) === false) {
|
||||
logger.debug('Remote actor JSON is not valid.', { actorJSON: body })
|
||||
|
@ -46,7 +46,7 @@ export {
|
|||
|
||||
async function fetchActorTotalItems (url: string) {
|
||||
try {
|
||||
const { body } = await doJSONRequest<ActivityPubOrderedCollection<unknown>>(url, { activityPub: true })
|
||||
const { body } = await fetchAP<ActivityPubOrderedCollection<unknown>>(url)
|
||||
|
||||
return body.totalItems || 0
|
||||
} catch (err) {
|
||||
|
|
|
@ -3,8 +3,8 @@ import { URL } from 'url'
|
|||
import { retryTransactionWrapper } from '@server/helpers/database-utils'
|
||||
import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { doJSONRequest } from '../../helpers/requests'
|
||||
import { ACTIVITY_PUB, WEBSERVER } from '../../initializers/constants'
|
||||
import { fetchAP } from './activity'
|
||||
|
||||
type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>)
|
||||
type CleanerFunction = (startedDate: Date) => Promise<any>
|
||||
|
@ -14,11 +14,9 @@ async function crawlCollectionPage <T> (argUrl: string, handler: HandlerFunction
|
|||
|
||||
logger.info('Crawling ActivityPub data on %s.', url)
|
||||
|
||||
const options = { activityPub: true }
|
||||
|
||||
const startDate = new Date()
|
||||
|
||||
const response = await doJSONRequest<ActivityPubOrderedCollection<T>>(url, options)
|
||||
const response = await fetchAP<ActivityPubOrderedCollection<T>>(url)
|
||||
const firstBody = response.body
|
||||
|
||||
const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT
|
||||
|
@ -34,7 +32,7 @@ async function crawlCollectionPage <T> (argUrl: string, handler: HandlerFunction
|
|||
|
||||
url = nextLink
|
||||
|
||||
const res = await doJSONRequest<ActivityPubOrderedCollection<T>>(url, options)
|
||||
const res = await fetchAP<ActivityPubOrderedCollection<T>>(url)
|
||||
body = res.body
|
||||
} else {
|
||||
// nextLink is already the object we want
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '@server/helpers/custom-validators/activitypub/playlist'
|
||||
import { isArray } from '@server/helpers/custom-validators/misc'
|
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger'
|
||||
import { doJSONRequest } from '@server/helpers/requests'
|
||||
import { PlaylistElementObject, PlaylistObject } from '@shared/models'
|
||||
import { fetchAP } from '../../activity'
|
||||
import { checkUrlsSameHost } from '../../url'
|
||||
|
||||
async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusCode: number, playlistObject: PlaylistObject }> {
|
||||
|
@ -10,7 +10,7 @@ async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusC
|
|||
|
||||
logger.info('Fetching remote playlist %s.', playlistUrl, lTags())
|
||||
|
||||
const { body, statusCode } = await doJSONRequest<any>(playlistUrl, { activityPub: true })
|
||||
const { body, statusCode } = await fetchAP<any>(playlistUrl)
|
||||
|
||||
if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) {
|
||||
logger.debug('Remote video playlist JSON is not valid.', { body, ...lTags() })
|
||||
|
@ -30,7 +30,7 @@ async function fetchRemotePlaylistElement (elementUrl: string): Promise<{ status
|
|||
|
||||
logger.debug('Fetching remote playlist element %s.', elementUrl, lTags())
|
||||
|
||||
const { body, statusCode } = await doJSONRequest<PlaylistElementObject>(elementUrl, { activityPub: true })
|
||||
const { body, statusCode } = await fetchAP<PlaylistElementObject>(elementUrl)
|
||||
|
||||
if (!isPlaylistElementObjectValid(body)) throw new Error(`Invalid body in fetch playlist element ${elementUrl}`)
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import { sequelizeTypescript } from '../../../initializers/database'
|
|||
import { APProcessorOptions } from '../../../types/activitypub-processor.model'
|
||||
import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models'
|
||||
import { Notifier } from '../../notifier'
|
||||
import { fetchAPObject } from '../activity'
|
||||
import { fetchAPObjectIfNeeded } from '../activity'
|
||||
import { createOrUpdateCacheFile } from '../cache-file'
|
||||
import { createOrUpdateLocalVideoViewer } from '../local-video-viewer'
|
||||
import { createOrUpdateVideoPlaylist } from '../playlists'
|
||||
|
@ -31,7 +31,7 @@ async function processCreateActivity (options: APProcessorOptions<ActivityCreate
|
|||
|
||||
// Only notify if it is not from a fetcher job
|
||||
const notify = options.fromFetch !== true
|
||||
const activityObject = await fetchAPObject<Exclude<ActivityObject, AbuseObject>>(activity.object)
|
||||
const activityObject = await fetchAPObjectIfNeeded<Exclude<ActivityObject, AbuseObject>>(activity.object)
|
||||
const activityType = activityObject.type
|
||||
|
||||
if (activityType === 'Video') {
|
||||
|
|
|
@ -19,7 +19,7 @@ import { VideoRedundancyModel } from '../../../models/redundancy/video-redundanc
|
|||
import { VideoShareModel } from '../../../models/video/video-share'
|
||||
import { APProcessorOptions } from '../../../types/activitypub-processor.model'
|
||||
import { MActorSignature } from '../../../types/models'
|
||||
import { fetchAPObject } from '../activity'
|
||||
import { fetchAPObjectIfNeeded } from '../activity'
|
||||
import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
|
||||
import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos'
|
||||
|
||||
|
@ -32,7 +32,7 @@ async function processUndoActivity (options: APProcessorOptions<ActivityUndo<Act
|
|||
}
|
||||
|
||||
if (activityToUndo.type === 'Create') {
|
||||
const objectToUndo = await fetchAPObject<CacheFileObject>(activityToUndo.object)
|
||||
const objectToUndo = await fetchAPObjectIfNeeded<CacheFileObject>(activityToUndo.object)
|
||||
|
||||
if (objectToUndo.type === 'CacheFile') {
|
||||
return retryTransactionWrapper(processUndoCacheFile, byActor, activity, objectToUndo)
|
||||
|
|
|
@ -10,7 +10,7 @@ import { sequelizeTypescript } from '../../../initializers/database'
|
|||
import { ActorModel } from '../../../models/actor/actor'
|
||||
import { APProcessorOptions } from '../../../types/activitypub-processor.model'
|
||||
import { MActorFull, MActorSignature } from '../../../types/models'
|
||||
import { fetchAPObject } from '../activity'
|
||||
import { fetchAPObjectIfNeeded } from '../activity'
|
||||
import { APActorUpdater } from '../actors/updater'
|
||||
import { createOrUpdateCacheFile } from '../cache-file'
|
||||
import { createOrUpdateVideoPlaylist } from '../playlists'
|
||||
|
@ -20,7 +20,7 @@ import { APVideoUpdater, getOrCreateAPVideo } from '../videos'
|
|||
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) {
|
||||
const { activity, byActor } = options
|
||||
|
||||
const object = await fetchAPObject(activity.object)
|
||||
const object = await fetchAPObjectIfNeeded(activity.object)
|
||||
const objectType = object.type
|
||||
|
||||
if (objectType === 'Video') {
|
||||
|
|
|
@ -23,11 +23,14 @@ async function computeBody <T> (
|
|||
return body
|
||||
}
|
||||
|
||||
async function buildSignedRequestOptions (payload: Payload<any>) {
|
||||
async function buildSignedRequestOptions (options: {
|
||||
signatureActorId?: number
|
||||
hasPayload: boolean
|
||||
}) {
|
||||
let actor: MActor | null
|
||||
|
||||
if (payload.signatureActorId) {
|
||||
actor = await ActorModel.load(payload.signatureActorId)
|
||||
if (options.signatureActorId) {
|
||||
actor = await ActorModel.load(options.signatureActorId)
|
||||
if (!actor) throw new Error('Unknown signature actor id.')
|
||||
} else {
|
||||
// We need to sign the request, so use the server
|
||||
|
@ -40,7 +43,9 @@ async function buildSignedRequestOptions (payload: Payload<any>) {
|
|||
authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
|
||||
keyId,
|
||||
key: actor.privateKey,
|
||||
headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
|
||||
headers: options.hasPayload
|
||||
? HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD
|
||||
: HTTP_SIGNATURE.HEADERS_TO_SIGN_WITHOUT_PAYLOAD
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,10 @@ import { map } from 'bluebird'
|
|||
import { Transaction } from 'sequelize'
|
||||
import { getServerActor } from '@server/models/application/application'
|
||||
import { logger, loggerTagsFactory } from '../../helpers/logger'
|
||||
import { doJSONRequest } from '../../helpers/requests'
|
||||
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
|
||||
import { VideoShareModel } from '../../models/video/video-share'
|
||||
import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../types/models/video'
|
||||
import { getAPId } from './activity'
|
||||
import { fetchAP, getAPId } from './activity'
|
||||
import { getOrCreateAPActor } from './actors'
|
||||
import { sendUndoAnnounce, sendVideoAnnounce } from './send'
|
||||
import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url'
|
||||
|
@ -56,7 +55,7 @@ export {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function addVideoShare (shareUrl: string, video: MVideoId) {
|
||||
const { body } = await doJSONRequest<any>(shareUrl, { activityPub: true })
|
||||
const { body } = await fetchAP<any>(shareUrl)
|
||||
if (!body?.actor) throw new Error('Body or body actor is invalid')
|
||||
|
||||
const actorUrl = getAPId(body.actor)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { map } from 'bluebird'
|
||||
|
||||
import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { doJSONRequest } from '../../helpers/requests'
|
||||
import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
|
||||
import { VideoCommentModel } from '../../models/video/video-comment'
|
||||
import { MComment, MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video'
|
||||
import { isRemoteVideoCommentAccepted } from '../moderation'
|
||||
import { Hooks } from '../plugins/hooks'
|
||||
import { fetchAP } from './activity'
|
||||
import { getOrCreateAPActor } from './actors'
|
||||
import { checkUrlsSameHost } from './url'
|
||||
import { getOrCreateAPVideo } from './videos'
|
||||
|
@ -139,7 +140,7 @@ async function resolveRemoteParentComment (params: ResolveThreadParams) {
|
|||
throw new Error('Recursion limit reached when resolving a thread')
|
||||
}
|
||||
|
||||
const { body } = await doJSONRequest<any>(url, { activityPub: true })
|
||||
const { body } = await fetchAP<any>(url)
|
||||
|
||||
if (sanitizeAndCheckVideoCommentObject(body) === false) {
|
||||
throw new Error(`Remote video comment JSON ${url} is not valid:` + JSON.stringify(body))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { sanitizeAndCheckVideoTorrentObject } from '@server/helpers/custom-validators/activitypub/videos'
|
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger'
|
||||
import { doJSONRequest } from '@server/helpers/requests'
|
||||
import { VideoObject } from '@shared/models'
|
||||
import { fetchAP } from '../../activity'
|
||||
import { checkUrlsSameHost } from '../../url'
|
||||
|
||||
const lTags = loggerTagsFactory('ap', 'video')
|
||||
|
@ -9,7 +9,7 @@ const lTags = loggerTagsFactory('ap', 'video')
|
|||
async function fetchRemoteVideo (videoUrl: string): Promise<{ statusCode: number, videoObject: VideoObject }> {
|
||||
logger.info('Fetching remote video %s.', videoUrl, lTags(videoUrl))
|
||||
|
||||
const { statusCode, body } = await doJSONRequest<any>(videoUrl, { activityPub: true })
|
||||
const { statusCode, body } = await fetchAP<any>(videoUrl)
|
||||
|
||||
if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) {
|
||||
logger.debug('Remote video JSON is not valid.', { body, ...lTags(videoUrl) })
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { runInReadCommittedTransaction } from '@server/helpers/database-utils'
|
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger'
|
||||
import { doJSONRequest } from '@server/helpers/requests'
|
||||
import { JobQueue } from '@server/lib/job-queue'
|
||||
import { VideoModel } from '@server/models/video/video'
|
||||
import { VideoCommentModel } from '@server/models/video/video-comment'
|
||||
import { VideoShareModel } from '@server/models/video/video-share'
|
||||
import { MVideo } from '@server/types/models'
|
||||
import { ActivitypubHttpFetcherPayload, ActivityPubOrderedCollection, VideoObject } from '@shared/models'
|
||||
import { fetchAP } from '../../activity'
|
||||
import { crawlCollectionPage } from '../../crawl'
|
||||
import { addVideoShares } from '../../share'
|
||||
import { addVideoComments } from '../../video-comments'
|
||||
|
@ -63,17 +63,15 @@ async function getRatesCount (type: 'like' | 'dislike', video: MVideo, fetchedVi
|
|||
: fetchedVideo.dislikes
|
||||
|
||||
logger.info('Sync %s of video %s', type, video.url)
|
||||
const options = { activityPub: true }
|
||||
|
||||
const response = await doJSONRequest<ActivityPubOrderedCollection<any>>(uri, options)
|
||||
const totalItems = response.body.totalItems
|
||||
const { body } = await fetchAP<ActivityPubOrderedCollection<any>>(uri)
|
||||
|
||||
if (isNaN(totalItems)) {
|
||||
logger.error('Cannot sync %s of video %s, totalItems is not a number', type, video.url, { body: response.body })
|
||||
if (isNaN(body.totalItems)) {
|
||||
logger.error('Cannot sync %s of video %s, totalItems is not a number', type, video.url, { body })
|
||||
return
|
||||
}
|
||||
|
||||
return totalItems
|
||||
return body.totalItems
|
||||
}
|
||||
|
||||
function syncShares (video: MVideo, fetchedVideo: VideoObject, isSync: boolean) {
|
||||
|
|
|
@ -6,8 +6,9 @@ import {
|
|||
isLikeActivityValid
|
||||
} from '@server/helpers/custom-validators/activitypub/activity'
|
||||
import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments'
|
||||
import { doJSONRequest, PeerTubeRequestError } from '@server/helpers/requests'
|
||||
import { PeerTubeRequestError } from '@server/helpers/requests'
|
||||
import { AP_CLEANER } from '@server/initializers/constants'
|
||||
import { fetchAP } from '@server/lib/activitypub/activity'
|
||||
import { checkUrlsSameHost } from '@server/lib/activitypub/url'
|
||||
import { Redis } from '@server/lib/redis'
|
||||
import { VideoModel } from '@server/models/video/video'
|
||||
|
@ -85,7 +86,7 @@ async function updateObjectIfNeeded <T> (options: {
|
|||
}
|
||||
|
||||
try {
|
||||
const { body } = await doJSONRequest<any>(url, { activityPub: true })
|
||||
const { body } = await fetchAP<any>(url)
|
||||
|
||||
// If not same id, check same host and update
|
||||
if (!body?.id || !bodyValidator(body)) throw new Error(`Body or body id of ${url} is invalid`)
|
||||
|
|
|
@ -38,7 +38,7 @@ export {
|
|||
|
||||
async function buildRequestOptions (payload: ActivitypubHttpBroadcastPayload) {
|
||||
const body = await computeBody(payload)
|
||||
const httpSignatureOptions = await buildSignedRequestOptions(payload)
|
||||
const httpSignatureOptions = await buildSignedRequestOptions({ signatureActorId: payload.signatureActorId, hasPayload: true })
|
||||
|
||||
return {
|
||||
method: 'POST' as 'POST',
|
||||
|
|
|
@ -12,7 +12,7 @@ async function processActivityPubHttpUnicast (job: Job) {
|
|||
const uri = payload.uri
|
||||
|
||||
const body = await computeBody(payload)
|
||||
const httpSignatureOptions = await buildSignedRequestOptions(payload)
|
||||
const httpSignatureOptions = await buildSignedRequestOptions({ signatureActorId: payload.signatureActorId, hasPayload: true })
|
||||
|
||||
const options = {
|
||||
method: 'POST' as 'POST',
|
||||
|
|
|
@ -58,7 +58,7 @@ async function makeFollowRequest (to: { url: string }, by: { url: string, privat
|
|||
authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
|
||||
keyId: by.url,
|
||||
key: by.privateKey,
|
||||
headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
|
||||
headers: HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD
|
||||
}
|
||||
const headers = {
|
||||
'digest': buildDigest(body),
|
||||
|
@ -82,7 +82,7 @@ describe('Test ActivityPub security', function () {
|
|||
authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
|
||||
keyId: 'acct:peertube@' + servers[1].host,
|
||||
key: keys.privateKey,
|
||||
headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
|
||||
headers: HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
|
|
@ -12,6 +12,11 @@ webserver:
|
|||
__name: "PEERTUBE_WEBSERVER_HTTPS"
|
||||
__format: "json"
|
||||
|
||||
federation:
|
||||
sign_federated_fetches:
|
||||
__name: "PEERTUBE_SIGN_FEDERATED_FETCHES"
|
||||
__format: "json"
|
||||
|
||||
secrets:
|
||||
peertube: "PEERTUBE_SECRET"
|
||||
|
||||
|
|
Loading…
Reference in New Issue