Init commit

This commit is contained in:
ShreejitPanchal
2026-05-19 19:56:02 +08:00
commit 6601501eff
4047 changed files with 2185372 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
import { Response } from 'express'
import { AbuseModel } from '@server/models/abuse/abuse.js'
import { HttpStatusCode } from '@peertube/peertube-models'
import { forceNumber } from '@peertube/peertube-core-utils'
async function doesAbuseExist (abuseId: number | string, res: Response) {
const abuse = await AbuseModel.loadByIdWithReporter(forceNumber(abuseId))
if (!abuse) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Abuse not found'
})
return false
}
res.locals.abuse = abuse
return true
}
// ---------------------------------------------------------------------------
export {
doesAbuseExist
}

View File

@@ -0,0 +1,76 @@
import { forceNumber } from '@peertube/peertube-core-utils'
import { HttpStatusCode, UserRight } from '@peertube/peertube-models'
import { AccountModel } from '@server/models/account/account.js'
import { MAccountDefault } from '@server/types/models/index.js'
import { Request, Response } from 'express'
import { checkCanManageAccount } from './users.js'
export async function doesAccountIdExist (options: {
id: string | number
req: Request
res: Response
checkCanManage: boolean // Also check the user can manage the account
checkIsLocal: boolean // Also check this is a local channel
}) {
const { id, req, res, checkIsLocal, checkCanManage } = options
const account = await AccountModel.load(forceNumber(id))
return doesAccountExist({ account, req, res, checkIsLocal, checkCanManage })
}
export async function doesAccountHandleExist (options: {
handle: string
req: Request
res: Response
checkCanManage: boolean // Also check the user can manage the account
checkIsLocal: boolean // Also check this is a local channel
}) {
const { handle, req, res, checkIsLocal, checkCanManage } = options
const account = await AccountModel.loadByHandle(handle)
return doesAccountExist({ account, req, res, checkIsLocal, checkCanManage })
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
function doesAccountExist (options: {
account: MAccountDefault
req: Request
res: Response
checkCanManage: boolean
checkIsLocal: boolean
}) {
const { account, req, res, checkIsLocal, checkCanManage } = options
if (!account) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: req.t('Account not found')
})
return false
}
if (checkCanManage) {
const user = res.locals.oauth.token.User
if (!checkCanManageAccount({ account, user, req, res, specialRight: UserRight.MANAGE_USERS })) {
return false
}
}
if (checkIsLocal && account.Actor.isLocal() === false) {
res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: req.t('This account is not owned by the platform')
})
return false
}
res.locals.account = account
return true
}

View File

@@ -0,0 +1,19 @@
import { isActorImageFile } from '@server/helpers/custom-validators/actor-images.js'
import { cleanUpReqFiles } from '@server/helpers/express-utils.js'
import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
import express from 'express'
import { body } from 'express-validator'
import { areValidationErrors } from './utils.js'
export const updateActorImageValidatorFactory = (fieldname: string) => [
body(fieldname).custom((value, { req }) => isActorImageFile(req.files, fieldname)).withMessage(
'This file is not supported or too large. Please, make sure it is of the following type : ' +
CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME.join(', ')
),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
return next()
}
]

View File

@@ -0,0 +1,15 @@
export * from './abuses.js'
export * from './accounts.js'
export * from './images.js'
export * from './users.js'
export * from './utils.js'
export * from './video-blacklists.js'
export * from './video-captions.js'
export * from './video-channels.js'
export * from './video-channel-syncs.js'
export * from './video-comments.js'
export * from './video-imports.js'
export * from './video-ownerships.js'
export * from './video-playlists.js'
export * from './video-passwords.js'
export * from './videos.js'

View File

@@ -0,0 +1,150 @@
import { forceNumber } from '@peertube/peertube-core-utils'
import { HttpStatusCode, UserRightType } from '@peertube/peertube-models'
import { loadReservedActorName } from '@server/lib/local-actor.js'
import { getByEmailPermissive } from '@server/lib/user.js'
import { UserModel } from '@server/models/user/user.js'
import { MAccountId, MUserAccountId, MUserDefault } from '@server/types/models/index.js'
import express from 'express'
export function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) {
const id = forceNumber(idArg)
return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res)
}
export function checkUserEmailExistPermissive (email: string, res: express.Response, abortResponse = true) {
return checkUserExist(
async () => {
const users = await UserModel.loadByEmailCaseInsensitive(email)
return getByEmailPermissive(users, email)
},
res,
abortResponse
)
}
export function checkUserPendingEmailExistPermissive (email: string, res: express.Response, abortResponse = true) {
return checkUserExist(
async () => {
const users = await UserModel.loadByPendingEmailCaseInsensitive(email)
return getByEmailPermissive(users, email)
},
res,
abortResponse
)
}
export async function checkUsernameOrEmailDoNotAlreadyExist (options: {
username: string
email: string
req: express.Request
res: express.Response
}) {
const { username, email, req, res } = options
const existingUser = await UserModel.loadByUsernameOrEmailCaseInsensitive(username)
const existingEmail = await UserModel.loadByUsernameOrEmailCaseInsensitive(email)
if (existingUser.length > 0 || existingEmail.length > 0) {
res.fail({
status: HttpStatusCode.CONFLICT_409,
message: req.t('User with this username or email already exists.')
})
return false
}
const actor = await loadReservedActorName(username)
if (actor) {
res.fail({
status: HttpStatusCode.CONFLICT_409,
message: req.t('Another actor (account/channel) with this name on this instance already exists or has already existed.')
})
return false
}
return true
}
export async function checkEmailDoesNotAlreadyExist (options: {
email: string
req: express.Request
res: express.Response
}) {
const { email, req, res } = options
const user = await UserModel.loadByEmailCaseInsensitive(email)
if (user.length !== 0) {
res.fail({
status: HttpStatusCode.CONFLICT_409,
message: req.t('User with this email already exists.')
})
return false
}
return true
}
export async function checkUserExist (finder: () => Promise<MUserDefault>, res: express.Response, abortResponse = true) {
const user = await finder()
if (!user) {
if (abortResponse === true) {
res.sendStatus(HttpStatusCode.NOT_FOUND_404)
}
return false
}
res.locals.user = user
return true
}
export function checkCanManageAccount (options: {
user: MUserAccountId
account: MAccountId
specialRight: UserRightType
req: express.Request
res: express.Response | null
}) {
const { user, account, specialRight, res, req } = options
if (!user) {
res?.fail({
status: HttpStatusCode.UNAUTHORIZED_401,
message: req.t('Authentication is required')
})
return false
}
if (account.id === user.Account.id) return true
if (specialRight && user.hasRight(specialRight) === true) return true
res?.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: req.t('Only a user with sufficient right can manage this account resource.')
})
return false
}
export async function doesUserFeedTokenCorrespond (options: {
id: number
token: string
req: express.Request
res: express.Response
}) {
const { id, token, req, res } = options
const user = await UserModel.loadByIdWithChannels(forceNumber(id))
if (token !== user.feedToken) {
res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: req.t('User and token mismatch')
})
return false
}
res.locals.user = user
return true
}

View File

@@ -0,0 +1,69 @@
import express from 'express'
import { param, validationResult } from 'express-validator'
import { isIdOrUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc.js'
import { logger } from '../../../helpers/logger.js'
function areValidationErrors (
req: express.Request,
res: express.Response,
options: {
omitLog?: boolean
omitBodyLog?: boolean
tags?: (number | string)[]
} = {}) {
const { omitLog = false, omitBodyLog = false, tags = [] } = options
if (!omitLog) {
logger.debug(
'Checking %s - %s parameters',
req.method, req.originalUrl,
{
body: omitBodyLog
? 'omitted'
: req.body,
params: req.params,
query: req.query,
files: req.files,
tags
}
)
}
const errors = validationResult(req)
if (!errors.isEmpty()) {
logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() })
res.fail({
message: 'Incorrect request parameters: ' + Object.keys(errors.mapped()).join(', '),
instance: req.originalUrl,
data: {
'invalid-params': errors.mapped()
}
})
return true
}
return false
}
function isValidVideoIdParam (paramName: string) {
return param(paramName)
.customSanitizer(toCompleteUUID)
.custom(isIdOrUUIDValid).withMessage('Should have a valid video id (id, short UUID or UUID)')
}
function isValidPlaylistIdParam (paramName: string) {
return param(paramName)
.customSanitizer(toCompleteUUID)
.custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id (id, short UUID or UUID)')
}
// ---------------------------------------------------------------------------
export {
areValidationErrors,
isValidVideoIdParam,
isValidPlaylistIdParam
}

View File

@@ -0,0 +1,24 @@
import { Response } from 'express'
import { VideoBlacklistModel } from '@server/models/video/video-blacklist.js'
import { HttpStatusCode } from '@peertube/peertube-models'
async function doesVideoBlacklistExist (videoId: number, res: Response) {
const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId)
if (videoBlacklist === null) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Blacklisted video not found'
})
return false
}
res.locals.videoBlacklist = videoBlacklist
return true
}
// ---------------------------------------------------------------------------
export {
doesVideoBlacklistExist
}

View File

@@ -0,0 +1,25 @@
import { Response } from 'express'
import { VideoCaptionModel } from '@server/models/video/video-caption.js'
import { MVideoId } from '@server/types/models/index.js'
import { HttpStatusCode } from '@peertube/peertube-models'
async function doesVideoCaptionExist (video: MVideoId, language: string, res: Response) {
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language)
if (!videoCaption) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Video caption not found'
})
return false
}
res.locals.videoCaption = videoCaption
return true
}
// ---------------------------------------------------------------------------
export {
doesVideoCaptionExist
}

View File

@@ -0,0 +1,24 @@
import express from 'express'
import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync.js'
import { HttpStatusCode } from '@peertube/peertube-models'
async function doesVideoChannelSyncIdExist (id: number, res: express.Response) {
const sync = await VideoChannelSyncModel.loadWithChannel(+id)
if (!sync) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Video channel sync not found'
})
return false
}
res.locals.videoChannelSync = sync
return true
}
// ---------------------------------------------------------------------------
export {
doesVideoChannelSyncIdExist
}

View File

@@ -0,0 +1,124 @@
import { HttpStatusCode, UserRight, UserRightType } from '@peertube/peertube-models'
import { VideoChannelCollaboratorModel } from '@server/models/video/video-channel-collaborator.js'
import { VideoChannelModel } from '@server/models/video/video-channel.js'
import { MChannelBannerAccountDefault, MChannelUserId, MUserAccountId } from '@server/types/models/index.js'
import express from 'express'
import { checkCanManageAccount } from './users.js'
type CommonOptions = {
checkCanManage: boolean // Also check the user can manage the account
checkIsOwner: boolean // Also check this is the owner of the channel
req: express.Request
res: express.Response
specialRight?: UserRightType
}
export async function doesChannelIdExist (
options: CommonOptions & {
id: number
checkIsLocal: boolean // Also check this is a local channel
}
) {
const { id, checkCanManage, checkIsLocal, checkIsOwner, req, res, specialRight } = options
const channel = await VideoChannelModel.loadAndPopulateAccount(+id)
return processVideoChannelExist({ channel, checkCanManage, checkIsLocal, checkIsOwner, req, res, specialRight })
}
export async function doesChannelHandleExist (
options: CommonOptions & {
handle: string
checkIsLocal: boolean // Also check this is a local channel
}
) {
const { handle, checkCanManage, checkIsLocal, checkIsOwner, req, res, specialRight } = options
const channel = await VideoChannelModel.loadByHandleAndPopulateAccount(handle)
return processVideoChannelExist({ channel, checkCanManage, checkIsLocal, checkIsOwner, req, res, specialRight })
}
export async function checkCanManageChannel (
options: CommonOptions & {
user: MUserAccountId
channel: MChannelUserId
}
) {
const { channel, user, req, res, checkCanManage, checkIsOwner, specialRight = UserRight.MANAGE_ANY_VIDEO_CHANNEL } = options
if (!channel) {
res?.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: req.t('Video channel not found')
})
return false
}
if (checkIsOwner || checkCanManage) {
if (!user) {
res?.fail({
status: HttpStatusCode.UNAUTHORIZED_401,
message: req.t('Authentication is required')
})
return false
}
const isOwner = checkCanManageAccount({
account: channel.Account,
user,
req,
res: null,
specialRight
})
if (!isOwner) {
if (checkIsOwner) {
res?.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: req.t('This user has not owner rights on this channel')
})
return false
}
if (checkCanManage && !await VideoChannelCollaboratorModel.isCollaborator({ user, channel })) {
res?.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: req.t('This user cannot manage this channel')
})
return false
}
}
}
return true
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
async function processVideoChannelExist (
options: CommonOptions & {
channel: MChannelBannerAccountDefault
checkIsLocal: boolean // Also check this is a local channel
}
) {
const { channel, req, res, checkCanManage, checkIsLocal, checkIsOwner, specialRight = UserRight.MANAGE_ANY_VIDEO_CHANNEL } = options
const user = res.locals.oauth?.token.User
if (!await checkCanManageChannel({ channel, user, req, res, checkCanManage, checkIsOwner, specialRight })) return false
if (checkIsLocal && channel.Actor.isLocal() === false) {
res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: req.t('The channel must be local.')
})
return false
}
res.locals.videoChannel = channel
return true
}

View File

@@ -0,0 +1,80 @@
import express from 'express'
import { VideoCommentModel } from '@server/models/video/video-comment.js'
import { MVideoId } from '@server/types/models/index.js'
import { forceNumber } from '@peertube/peertube-core-utils'
import { HttpStatusCode, ServerErrorCode } from '@peertube/peertube-models'
async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) {
const id = forceNumber(idArg)
const videoComment = await VideoCommentModel.loadById(id)
if (!videoComment) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Video comment thread not found'
})
return false
}
if (videoComment.videoId !== video.id) {
res.fail({
type: ServerErrorCode.COMMENT_NOT_ASSOCIATED_TO_VIDEO,
message: 'Video comment is not associated to this video.'
})
return false
}
if (videoComment.inReplyToCommentId !== null) {
res.fail({ message: 'Video comment is not a thread.' })
return false
}
res.locals.videoCommentThread = videoComment
return true
}
async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) {
const id = forceNumber(idArg)
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
if (!videoComment) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Video comment thread not found'
})
return false
}
if (videoComment.videoId !== video.id) {
res.fail({
type: ServerErrorCode.COMMENT_NOT_ASSOCIATED_TO_VIDEO,
message: 'Video comment is not associated to this video.'
})
return false
}
res.locals.videoCommentFull = videoComment
return true
}
async function doesCommentIdExist (idArg: number | string, res: express.Response) {
const id = forceNumber(idArg)
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
if (!videoComment) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Video comment thread not found'
})
return false
}
res.locals.videoCommentFull = videoComment
return true
}
export {
doesVideoCommentThreadExist,
doesVideoCommentExist,
doesCommentIdExist
}

View File

@@ -0,0 +1,22 @@
import express from 'express'
import { VideoImportModel } from '@server/models/video/video-import.js'
import { HttpStatusCode } from '@peertube/peertube-models'
async function doesVideoImportExist (id: number, res: express.Response) {
const videoImport = await VideoImportModel.loadAndPopulateVideo(id)
if (!videoImport) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Video import not found'
})
return false
}
res.locals.videoImport = videoImport
return true
}
export {
doesVideoImportExist
}

View File

@@ -0,0 +1,21 @@
import express from 'express'
import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership.js'
import { forceNumber } from '@peertube/peertube-core-utils'
import { HttpStatusCode } from '@peertube/peertube-models'
export async function doesChangeVideoOwnershipExist (idArg: number | string, req: express.Request, res: express.Response) {
const id = forceNumber(idArg)
const videoChangeOwnership = await VideoChangeOwnershipModel.load(id)
if (!videoChangeOwnership) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: req.t('Video ownership change not found')
})
return false
}
res.locals.videoChangeOwnership = videoChangeOwnership
return true
}

View File

@@ -0,0 +1,72 @@
import { forceNumber } from '@peertube/peertube-core-utils'
import { HttpStatusCode, UserRight, VideoPrivacy } from '@peertube/peertube-models'
import { getVideoWithAttributes } from '@server/helpers/video.js'
import { VideoPasswordModel } from '@server/models/video/video-password.js'
import { MUserAccountId, MVideoAccountLight } from '@server/types/models/index.js'
import express from 'express'
import { header } from 'express-validator'
import { checkCanManageVideo } from './videos.js'
export function isValidVideoPasswordHeader () {
return header('x-peertube-video-password')
.optional()
.isString()
}
export function checkVideoIsPasswordProtected (req: express.Request, res: express.Response) {
const video = getVideoWithAttributes(res)
if (video.privacy !== VideoPrivacy.PASSWORD_PROTECTED) {
res.fail({
status: HttpStatusCode.BAD_REQUEST_400,
message: req.t('Video is not password protected')
})
return false
}
return true
}
export async function doesVideoPasswordExist (options: {
id: number | string
req: express.Request
res: express.Response
}) {
const { req, res } = options
const video = getVideoWithAttributes(res)
const id = forceNumber(options.id)
const videoPassword = await VideoPasswordModel.loadByIdAndVideo({ id, videoId: video.id })
if (!videoPassword) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: req.t('Video password not found')
})
return false
}
res.locals.videoPassword = videoPassword
return true
}
export async function checkCanDeleteVideoPassword (options: {
user: MUserAccountId
video: MVideoAccountLight
req: express.Request
res: express.Response
}) {
const { user, video, req, res } = options
const passwordCount = await VideoPasswordModel.countByVideoId(video.id)
if (passwordCount <= 1) {
res.fail({
status: HttpStatusCode.BAD_REQUEST_400,
message: req.t('Cannot delete the last password of the protected video')
})
return false
}
return checkCanManageVideo({ user, video, right: UserRight.UPDATE_ANY_VIDEO, req, res, checkIsLocal: true, checkIsOwner: false })
}

View File

@@ -0,0 +1,42 @@
import express from 'express'
import { VideoPlaylistModel } from '@server/models/video/video-playlist.js'
import { MVideoPlaylist } from '@server/types/models/index.js'
import { HttpStatusCode } from '@peertube/peertube-models'
export type VideoPlaylistFetchType = 'summary' | 'all'
export async function doesVideoPlaylistExist (options: {
id: number | string
req: express.Request
res: express.Response
fetchType?: VideoPlaylistFetchType
}) {
const { id, req, res, fetchType = 'summary' } = options
if (fetchType === 'summary') {
const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined)
res.locals.videoPlaylistSummary = videoPlaylist
return handleVideoPlaylist(videoPlaylist, req, res)
}
const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined)
res.locals.videoPlaylistFull = videoPlaylist
return handleVideoPlaylist(videoPlaylist, req, res)
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
function handleVideoPlaylist (playlist: MVideoPlaylist, req: express.Request, res: express.Response) {
if (!playlist) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: req.t('Video playlist not found')
})
return false
}
return true
}

View File

@@ -0,0 +1,364 @@
import { HttpStatusCode, ServerErrorCode, UserRight, UserRightType, VideoPrivacy } from '@peertube/peertube-models'
import { exists } from '@server/helpers/custom-validators/misc.js'
import { VideoLoadType, loadVideo } from '@server/lib/model-loaders/index.js'
import { isUserQuotaValid } from '@server/lib/user.js'
import { VideoTokensManager } from '@server/lib/video-tokens-manager.js'
import { authenticateOrFail } from '@server/middlewares/auth.js'
import { VideoFileModel } from '@server/models/video/video-file.js'
import { VideoPasswordModel } from '@server/models/video/video-password.js'
import { VideoModel } from '@server/models/video/video.js'
import {
MUserAccountId,
MUserAccountUrl,
MUserId,
MVideo,
MVideoAccountLight,
MVideoFormattableDetails,
MVideoFullLight,
MVideoId,
MVideoImmutable,
MVideoThumbnailBlacklist,
MVideoUUID,
MVideoWithRights
} from '@server/types/models/index.js'
import { Request, Response } from 'express'
import { checkCanManageChannel } from './video-channels.js'
export async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') {
const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
const video = await loadVideo(id, fetchType, userId)
if (!video) {
res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Video not found'
})
return false
}
switch (fetchType) {
case 'for-api':
res.locals.videoAPI = video as MVideoFormattableDetails
break
case 'all':
res.locals.videoAll = video as MVideoFullLight
break
case 'unsafe-only-immutable-attributes':
res.locals.onlyImmutableVideo = video as MVideoImmutable
break
case 'id':
res.locals.videoId = video as MVideoId
break
case 'only-video-and-blacklist':
res.locals.onlyVideo = video as MVideoThumbnailBlacklist
break
}
return true
}
// ---------------------------------------------------------------------------
export async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) {
if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) {
res.sendStatus(HttpStatusCode.NOT_FOUND_404)
return false
}
return true
}
// ---------------------------------------------------------------------------
export async function checkCanSeeVideo (options: {
req: Request
res: Response
paramId: string
video: MVideo
videoFileToken?: {
user: MUserAccountUrl
}
}) {
const { req, res, video, videoFileToken, paramId } = options
if (video.requiresUserAuth({ urlParamId: paramId, checkBlacklist: true })) {
return checkCanSeeUserAuthVideo({ req, res, video, videoFileTokenUser: videoFileToken?.user })
}
if (video.privacy === VideoPrivacy.PASSWORD_PROTECTED) {
return checkCanSeePasswordProtectedVideo({ req, res, video, hasVideoFileToken: !!videoFileToken })
}
if (video.privacy === VideoPrivacy.UNLISTED || video.privacy === VideoPrivacy.PUBLIC) {
return true
}
throw new Error('Unknown video privacy when checking video right ' + video.url)
}
async function checkCanSeeUserAuthVideo (options: {
req: Request
res: Response
video: MVideoId | MVideoWithRights
videoFileTokenUser?: MUserAccountUrl
}) {
const { req, res, video } = options
const fail = () => {
res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: req.t('Cannot fetch information of private/internal/blocked video')
})
return false
}
let user = options.videoFileTokenUser
if (!user) {
if (!await authenticateOrFail({ req, res })) return false
user = res.locals.oauth.token.User
}
const videoWithRights = await getVideoWithRights(video as MVideoWithRights)
const privacy = videoWithRights.privacy
if (privacy === VideoPrivacy.INTERNAL) {
// We know we have a user
return true
}
if (videoWithRights.isBlacklisted()) {
if (await canUserManageProtectedVideo({ user, req, video: videoWithRights, right: UserRight.MANAGE_VIDEO_BLACKLIST })) return true
return fail()
}
if (privacy === VideoPrivacy.PRIVATE || privacy === VideoPrivacy.UNLISTED) {
if (await canUserManageProtectedVideo({ user, req, video: videoWithRights, right: UserRight.SEE_ALL_VIDEOS })) return true
return fail()
}
// Should not happen
return fail()
}
async function checkCanSeePasswordProtectedVideo (options: {
req: Request
res: Response
video: MVideo
hasVideoFileToken: boolean
}) {
const { req, res, video, hasVideoFileToken } = options
const videoWithRights = await getVideoWithRights(video as MVideoWithRights)
const videoPassword = req.header('x-peertube-video-password')
if (!exists(videoPassword)) {
if (hasVideoFileToken === true) return true
const errorMessage = req.t('Please provide a password to access this password protected video')
const errorType = ServerErrorCode.VIDEO_REQUIRES_PASSWORD
if (!await authenticateOrFail({ req, res, errorMessage, errorStatus: HttpStatusCode.UNAUTHORIZED_401, errorType })) return false
const user = res.locals.oauth.token.User
if (await canUserManageProtectedVideo({ user, req, video: videoWithRights, right: UserRight.SEE_ALL_VIDEOS })) return true
res.fail({
status: user
? HttpStatusCode.FORBIDDEN_403
: HttpStatusCode.UNAUTHORIZED_401,
type: errorType,
message: errorMessage
})
return false
}
if (await VideoPasswordModel.isACorrectPassword({ videoId: video.id, password: videoPassword })) return true
res.fail({
status: HttpStatusCode.FORBIDDEN_403,
type: ServerErrorCode.INCORRECT_VIDEO_PASSWORD,
message: req.t('Incorrect video password. Access to the video is denied')
})
return false
}
async function canUserManageProtectedVideo (options: {
req: Request
user: MUserAccountId
video: MVideoWithRights | MVideoAccountLight
right: UserRightType
}) {
const { user, video, right, req } = options
return checkCanManageChannel({
channel: video.VideoChannel,
user,
req,
res: null,
checkCanManage: true,
checkIsOwner: false,
specialRight: right
})
}
async function getVideoWithRights (video: MVideoWithRights): Promise<MVideoWithRights> {
const channel = video.VideoChannel
if (channel?.id && channel?.Account?.userId && channel?.Account?.id) return video
return VideoModel.loadFull(video.id)
}
// ---------------------------------------------------------------------------
export async function checkCanAccessVideoStaticFiles (options: {
video: MVideo
req: Request
res: Response
paramId: string
}): Promise<boolean> {
const { video, req, res } = options
if (!checkVideoTokenIfNeeded(req, res, video)) return false
return checkCanSeeVideo({ ...options, videoFileToken: res.locals.videoFileToken })
}
export async function checkCanAccessVideoSourceFile (options: {
videoId: number
req: Request
res: Response
}): Promise<boolean> {
const { req, res, videoId } = options
const video = await VideoModel.loadFull(videoId)
let user = res.locals.oauth?.token.User
if (!user) {
if (!checkVideoTokenIfNeeded(req, res, video)) return false
user = res.locals.videoFileToken?.user
}
if (!user) {
res.fail({ status: HttpStatusCode.UNAUTHORIZED_401, message: req.t('Authentication is required to access the video source file') })
return false
}
if (await canUserManageProtectedVideo({ user, req, video, right: UserRight.SEE_ALL_VIDEOS }) === true) {
return true
}
res.sendStatus(HttpStatusCode.FORBIDDEN_403)
return false
}
function checkVideoTokenIfNeeded (req: Request, res: Response, video: MVideoUUID) {
const videoFileToken = req.query.videoFileToken
if (videoFileToken) {
if (VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) {
const user = VideoTokensManager.Instance.getUserFromToken({ token: videoFileToken })
res.locals.videoFileToken = { user }
} else {
res.fail({
message: req.t('Invalid video file token'),
status: HttpStatusCode.FORBIDDEN_403
})
return false
}
}
return true
}
// ---------------------------------------------------------------------------
export async function checkCanManageVideo (options: {
user: MUserAccountId
video: MVideoAccountLight
right: UserRightType
req: Request
res: Response | null
checkIsLocal: boolean
checkIsOwner: boolean
}) {
const { user, video, right, req, res, checkIsLocal, checkIsOwner } = options
if (!user) {
res?.fail({
status: HttpStatusCode.UNAUTHORIZED_401,
message: req.t('Authentication is required.')
})
return false
}
if (checkIsLocal && video.isLocal() === false) {
res?.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: req.t('Cannot manage a video of another server.')
})
return false
}
if (
!await checkCanManageChannel({
channel: video.VideoChannel,
user,
req,
res: null,
checkCanManage: true,
checkIsOwner,
specialRight: right
})
) {
res?.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: req.t('Cannot manage a video of another user.')
})
return false
}
return true
}
type NewType = MUserId
// ---------------------------------------------------------------------------
export async function checkUserQuota (options: {
user: NewType
videoFileSize: number
req: Request
res: Response
}) {
const { user, videoFileSize, req, res } = options
if (await isUserQuotaValid({ userId: user.id, uploadSize: videoFileSize }) === false) {
res.fail({
status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
message: req.t('The user video quota is exceeded with this video'),
type: ServerErrorCode.QUOTA_REACHED
})
return false
}
return true
}