Protect all video related AP endpoints
This commit is contained in:
parent
d72ef2a2b9
commit
1642c5b9e7
7 changed files with 37 additions and 32 deletions
|
@ -16,6 +16,7 @@ import { isHostValid } from '../../helpers/custom-validators/servers.js'
|
||||||
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js'
|
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js'
|
||||||
import { ServerModel } from '../../models/server/server.js'
|
import { ServerModel } from '../../models/server/server.js'
|
||||||
import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared/index.js'
|
import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared/index.js'
|
||||||
|
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
|
||||||
|
|
||||||
const videoFileRedundancyGetValidator = [
|
const videoFileRedundancyGetValidator = [
|
||||||
isValidVideoIdParam('videoId'),
|
isValidVideoIdParam('videoId'),
|
||||||
|
@ -31,6 +32,7 @@ const videoFileRedundancyGetValidator = [
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await doesVideoExist(req.params.videoId, res)) return
|
if (!await doesVideoExist(req.params.videoId, res)) return
|
||||||
|
if (!canVideoBeFederated(res.locals.onlyVideo)) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||||
|
|
||||||
const video = res.locals.videoAll
|
const video = res.locals.videoAll
|
||||||
|
|
||||||
|
@ -72,6 +74,7 @@ const videoPlaylistRedundancyGetValidator = [
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await doesVideoExist(req.params.videoId, res)) return
|
if (!await doesVideoExist(req.params.videoId, res)) return
|
||||||
|
if (!canVideoBeFederated(res.locals.onlyVideo)) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||||
|
|
||||||
const video = res.locals.videoAll
|
const video = res.locals.videoAll
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
MVideoFullLight,
|
MVideoFullLight,
|
||||||
MVideoId,
|
MVideoId,
|
||||||
MVideoImmutable,
|
MVideoImmutable,
|
||||||
MVideoThumbnail,
|
MVideoThumbnailBlacklist,
|
||||||
MVideoUUID,
|
MVideoUUID,
|
||||||
MVideoWithRights
|
MVideoWithRights
|
||||||
} from '@server/types/models/index.js'
|
} from '@server/types/models/index.js'
|
||||||
|
@ -56,7 +56,7 @@ export async function doesVideoExist (id: number | string, res: Response, fetchT
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'only-video-and-blacklist':
|
case 'only-video-and-blacklist':
|
||||||
res.locals.onlyVideo = video as MVideoThumbnail
|
res.locals.onlyVideo = video as MVideoThumbnailBlacklist
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
|
import { HttpStatusCode } from '@peertube/peertube-models'
|
||||||
|
import { exists, isSafePeerTubeFilenameWithoutExtension, isUUIDValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc.js'
|
||||||
|
import { logger } from '@server/helpers/logger.js'
|
||||||
|
import { LRU_CACHE } from '@server/initializers/constants.js'
|
||||||
|
import { VideoFileModel } from '@server/models/video/video-file.js'
|
||||||
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
|
import { MStreamingPlaylist, MVideoFile, MVideoThumbnailBlacklist } from '@server/types/models/index.js'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { query } from 'express-validator'
|
import { query } from 'express-validator'
|
||||||
import { LRUCache } from 'lru-cache'
|
import { LRUCache } from 'lru-cache'
|
||||||
import { basename, dirname } from 'path'
|
import { basename, dirname } from 'path'
|
||||||
import { exists, isSafePeerTubeFilenameWithoutExtension, isUUIDValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc.js'
|
|
||||||
import { logger } from '@server/helpers/logger.js'
|
|
||||||
import { LRU_CACHE } from '@server/initializers/constants.js'
|
|
||||||
import { VideoModel } from '@server/models/video/video.js'
|
|
||||||
import { VideoFileModel } from '@server/models/video/video-file.js'
|
|
||||||
import { MStreamingPlaylist, MVideoFile, MVideoThumbnail } from '@server/types/models/index.js'
|
|
||||||
import { HttpStatusCode } from '@peertube/peertube-models'
|
|
||||||
import { areValidationErrors, checkCanAccessVideoStaticFiles, isValidVideoPasswordHeader } from './shared/index.js'
|
import { areValidationErrors, checkCanAccessVideoStaticFiles, isValidVideoPasswordHeader } from './shared/index.js'
|
||||||
|
|
||||||
type LRUValue = {
|
type LRUValue = {
|
||||||
allowed: boolean
|
allowed: boolean
|
||||||
video?: MVideoThumbnail
|
video?: MVideoThumbnailBlacklist
|
||||||
file?: MVideoFile
|
file?: MVideoFile
|
||||||
playlist?: MStreamingPlaylist }
|
playlist?: MStreamingPlaylist }
|
||||||
|
|
||||||
|
@ -122,8 +122,7 @@ const ensureCanAccessPrivateVideoHLSFiles = [
|
||||||
]
|
]
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ensureCanAccessVideoPrivateWebVideoFiles,
|
ensureCanAccessPrivateVideoHLSFiles, ensureCanAccessVideoPrivateWebVideoFiles
|
||||||
ensureCanAccessPrivateVideoHLSFiles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -139,7 +138,7 @@ async function isWebVideoAllowed (req: express.Request, res: express.Response) {
|
||||||
return { allowed: false }
|
return { allowed: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
const video = await VideoModel.load(file.getVideo().id)
|
const video = await VideoModel.loadWithBlacklist(file.getVideo().id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
file,
|
file,
|
||||||
|
@ -151,7 +150,7 @@ async function isWebVideoAllowed (req: express.Request, res: express.Response) {
|
||||||
async function isHLSAllowed (req: express.Request, res: express.Response, videoUUID: string) {
|
async function isHLSAllowed (req: express.Request, res: express.Response, videoUUID: string) {
|
||||||
const filename = basename(req.path)
|
const filename = basename(req.path)
|
||||||
|
|
||||||
const video = await VideoModel.loadWithFiles(videoUUID)
|
const video = await VideoModel.loadAndPopulateAccountAndFiles(videoUUID)
|
||||||
|
|
||||||
if (!video) {
|
if (!video) {
|
||||||
logger.debug('Unknown static file %s to serve', req.originalUrl, { videoUUID })
|
logger.debug('Unknown static file %s to serve', req.originalUrl, { videoUUID })
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
isValidVideoIdParam,
|
isValidVideoIdParam,
|
||||||
isValidVideoPasswordHeader
|
isValidVideoPasswordHeader
|
||||||
} from '../shared/index.js'
|
} from '../shared/index.js'
|
||||||
|
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
|
||||||
|
|
||||||
const listVideoCommentsValidator = [
|
const listVideoCommentsValidator = [
|
||||||
query('isLocal')
|
query('isLocal')
|
||||||
|
@ -132,8 +133,11 @@ const videoCommentGetValidator = [
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await doesVideoExist(req.params.videoId, res, 'id')) return
|
if (!await doesVideoExist(req.params.videoId, res, 'only-video-and-blacklist')) return
|
||||||
if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
|
|
||||||
|
if (!canVideoBeFederated(res.locals.onlyVideo)) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||||
|
|
||||||
|
if (!await doesVideoCommentExist(req.params.commentId, res.locals.onlyVideo, res)) return
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
import { HttpStatusCode } from '@peertube/peertube-models'
|
||||||
|
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { param } from 'express-validator'
|
import { param } from 'express-validator'
|
||||||
import { HttpStatusCode } from '@peertube/peertube-models'
|
|
||||||
import { isIdValid } from '../../../helpers/custom-validators/misc.js'
|
import { isIdValid } from '../../../helpers/custom-validators/misc.js'
|
||||||
import { VideoShareModel } from '../../../models/video/video-share.js'
|
import { VideoShareModel } from '../../../models/video/video-share.js'
|
||||||
import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared/index.js'
|
import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared/index.js'
|
||||||
|
|
||||||
const videosShareValidator = [
|
export const videosShareValidator = [
|
||||||
isValidVideoIdParam('id'),
|
isValidVideoIdParam('id'),
|
||||||
|
|
||||||
param('actorId')
|
param('actorId')
|
||||||
|
@ -16,20 +17,12 @@ const videosShareValidator = [
|
||||||
if (!await doesVideoExist(req.params.id, res)) return
|
if (!await doesVideoExist(req.params.id, res)) return
|
||||||
|
|
||||||
const video = res.locals.videoAll
|
const video = res.locals.videoAll
|
||||||
|
if (!canVideoBeFederated(video)) res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||||
|
|
||||||
const share = await VideoShareModel.load(req.params.actorId, video.id)
|
const share = await VideoShareModel.load(req.params.actorId, video.id)
|
||||||
if (!share) {
|
if (!share) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||||
return res.status(HttpStatusCode.NOT_FOUND_404)
|
|
||||||
.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
res.locals.videoShare = share
|
res.locals.videoShare = share
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
videosShareValidator
|
|
||||||
}
|
|
||||||
|
|
|
@ -1392,6 +1392,12 @@ export class VideoModel extends SequelizeModel<VideoModel> {
|
||||||
return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails-blacklist' })
|
return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails-blacklist' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static loadAndPopulateAccountAndFiles (id: number | string, transaction?: Transaction): Promise<MVideoAccountLightBlacklistAllFiles> {
|
||||||
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
|
return queryBuilder.queryVideo({ id, transaction, type: 'account-blacklist-files' })
|
||||||
|
}
|
||||||
|
|
||||||
static loadImmutableAttributes (id: number | string, t?: Transaction): Promise<MVideoImmutable> {
|
static loadImmutableAttributes (id: number | string, t?: Transaction): Promise<MVideoImmutable> {
|
||||||
const fun = () => {
|
const fun = () => {
|
||||||
const query = {
|
const query = {
|
||||||
|
|
8
server/core/types/express.d.ts
vendored
8
server/core/types/express.d.ts
vendored
|
@ -20,7 +20,8 @@ import {
|
||||||
MVideoLiveFormattable,
|
MVideoLiveFormattable,
|
||||||
MVideoPassword,
|
MVideoPassword,
|
||||||
MVideoPlaylistFull,
|
MVideoPlaylistFull,
|
||||||
MVideoPlaylistFullSummary
|
MVideoPlaylistFullSummary,
|
||||||
|
MVideoThumbnailBlacklist
|
||||||
} from '@server/types/models/index.js'
|
} from '@server/types/models/index.js'
|
||||||
import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token.js'
|
import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token.js'
|
||||||
import { MPlugin, MServer, MServerBlocklist } from '@server/types/models/server.js'
|
import { MPlugin, MServer, MServerBlocklist } from '@server/types/models/server.js'
|
||||||
|
@ -44,8 +45,7 @@ import {
|
||||||
MVideoCaptionVideo,
|
MVideoCaptionVideo,
|
||||||
MVideoFullLight,
|
MVideoFullLight,
|
||||||
MVideoRedundancyVideo,
|
MVideoRedundancyVideo,
|
||||||
MVideoShareActor,
|
MVideoShareActor
|
||||||
MVideoThumbnail
|
|
||||||
} from './models/index.js'
|
} from './models/index.js'
|
||||||
import { MRunner, MRunnerJobRunner, MRunnerRegistrationToken } from './models/runners/index.js'
|
import { MRunner, MRunnerJobRunner, MRunnerRegistrationToken } from './models/runners/index.js'
|
||||||
import { MVideoSource } from './models/video/video-source.js'
|
import { MVideoSource } from './models/video/video-source.js'
|
||||||
|
@ -135,7 +135,7 @@ declare module 'express' {
|
||||||
videoAPI?: MVideoFormattableDetails
|
videoAPI?: MVideoFormattableDetails
|
||||||
videoAll?: MVideoFullLight
|
videoAll?: MVideoFullLight
|
||||||
onlyImmutableVideo?: MVideoImmutable
|
onlyImmutableVideo?: MVideoImmutable
|
||||||
onlyVideo?: MVideoThumbnail
|
onlyVideo?: MVideoThumbnailBlacklist
|
||||||
videoId?: MVideoId
|
videoId?: MVideoId
|
||||||
|
|
||||||
videoLive?: MVideoLiveFormattable
|
videoLive?: MVideoLiveFormattable
|
||||||
|
|
Loading…
Reference in a new issue