1
0
Fork 0

Don't inject untrusted input

Even if it's already checked in middlewares
It's better to have safe modals too
This commit is contained in:
Chocobozzz 2022-11-15 14:41:55 +01:00
parent 6bcb559fc9
commit 4638cd713d
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
35 changed files with 101 additions and 63 deletions

View File

@ -105,8 +105,6 @@ export class ManagerOptionsBuilder {
Object.assign(videojsOptions, { language: commonOptions.language }) Object.assign(videojsOptions, { language: commonOptions.language })
} }
console.log(videojsOptions)
return videojsOptions return videojsOptions
} }

View File

@ -1,3 +1,4 @@
import { forceNumber } from '@shared/core-utils'
import express from 'express' import express from 'express'
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
import { getFormattedObjects } from '../../../helpers/utils' import { getFormattedObjects } from '../../../helpers/utils'
@ -55,7 +56,7 @@ async function listMyVideosHistory (req: express.Request, res: express.Response)
async function removeUserHistoryElement (req: express.Request, res: express.Response) { async function removeUserHistoryElement (req: express.Request, res: express.Response) {
const user = res.locals.oauth.token.User const user = res.locals.oauth.token.User
await UserVideoHistoryModel.removeUserHistoryElement(user, parseInt(req.params.videoId + '')) await UserVideoHistoryModel.removeUserHistoryElement(user, forceNumber(req.params.videoId))
return res.sendStatus(HttpStatusCode.NO_CONTENT_204) return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
} }

View File

@ -1,5 +1,6 @@
import { uuidToShort } from '@shared/extra-utils'
import express from 'express' import express from 'express'
import { forceNumber } from '@shared/core-utils'
import { uuidToShort } from '@shared/extra-utils'
import { VideosExistInPlaylists } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model' import { VideosExistInPlaylists } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model'
import { asyncMiddleware, authenticate } from '../../../middlewares' import { asyncMiddleware, authenticate } from '../../../middlewares'
import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists' import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists'
@ -22,7 +23,7 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function doVideosInPlaylistExist (req: express.Request, res: express.Response) { async function doVideosInPlaylistExist (req: express.Request, res: express.Response) {
const videoIds = req.query.videoIds.map(i => parseInt(i + '', 10)) const videoIds = req.query.videoIds.map(i => forceNumber(i))
const user = res.locals.oauth.token.User const user = res.locals.oauth.token.User
const results = await VideoPlaylistModel.listPlaylistSummariesOf(user.Account.id, videoIds) const results = await VideoPlaylistModel.listPlaylistSummariesOf(user.Account.id, videoIds)

View File

@ -46,6 +46,7 @@ import {
import { AccountModel } from '../../models/account/account' import { AccountModel } from '../../models/account/account'
import { VideoPlaylistModel } from '../../models/video/video-playlist' import { VideoPlaylistModel } from '../../models/video/video-playlist'
import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
import { forceNumber } from '@shared/core-utils'
const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT) const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT)
@ -245,7 +246,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
if (videoPlaylistInfoToUpdate.description !== undefined) videoPlaylistInstance.description = videoPlaylistInfoToUpdate.description if (videoPlaylistInfoToUpdate.description !== undefined) videoPlaylistInstance.description = videoPlaylistInfoToUpdate.description
if (videoPlaylistInfoToUpdate.privacy !== undefined) { if (videoPlaylistInfoToUpdate.privacy !== undefined) {
videoPlaylistInstance.privacy = parseInt(videoPlaylistInfoToUpdate.privacy.toString(), 10) videoPlaylistInstance.privacy = forceNumber(videoPlaylistInfoToUpdate.privacy)
if (wasNotPrivatePlaylist === true && videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE) { if (wasNotPrivatePlaylist === true && videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE) {
await sendDeleteVideoPlaylist(videoPlaylistInstance, t) await sendDeleteVideoPlaylist(videoPlaylistInstance, t)
@ -424,7 +425,7 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons
const endOldPosition = oldPosition + reorderLength - 1 const endOldPosition = oldPosition + reorderLength - 1
// Insert our reordered elements in their place (update) // Insert our reordered elements in their place (update)
await VideoPlaylistElementModel.reassignPositionOf(videoPlaylist.id, oldPosition, endOldPosition, newPosition, t) await VideoPlaylistElementModel.reassignPositionOf({ videoPlaylistId: videoPlaylist.id, firstPosition: oldPosition, endPosition: endOldPosition, newPosition, transaction: t })
// Decrease positions of elements after the old position of our ordered elements (decrease) // Decrease positions of elements after the old position of our ordered elements (decrease)
await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, -reorderLength, t) await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, -reorderLength, t)

View File

@ -19,6 +19,7 @@ import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosU
import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
import { VideoModel } from '../../../models/video/video' import { VideoModel } from '../../../models/video/video'
import { VideoPathManager } from '@server/lib/video-path-manager' import { VideoPathManager } from '@server/lib/video-path-manager'
import { forceNumber } from '@shared/core-utils'
const lTags = loggerTagsFactory('api', 'video') const lTags = loggerTagsFactory('api', 'video')
const auditLogger = auditLoggerFactory('videos') const auditLogger = auditLoggerFactory('videos')
@ -174,7 +175,7 @@ async function updateVideoPrivacy (options: {
const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options
const isNewVideo = videoInstance.isNewVideo(videoInfoToUpdate.privacy) const isNewVideo = videoInstance.isNewVideo(videoInfoToUpdate.privacy)
const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) const newPrivacy = forceNumber(videoInfoToUpdate.privacy)
setVideoPrivacy(videoInstance, newPrivacy) setVideoPrivacy(videoInstance, newPrivacy)
// Unfederate the video if the new privacy is not compatible with federation // Unfederate the video if the new privacy is not compatible with federation

View File

@ -5,7 +5,7 @@ import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache
import { Hooks } from '@server/lib/plugins/hooks' import { Hooks } from '@server/lib/plugins/hooks'
import { VideoPathManager } from '@server/lib/video-path-manager' import { VideoPathManager } from '@server/lib/video-path-manager'
import { MStreamingPlaylist, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' import { MStreamingPlaylist, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
import { addQueryParams } from '@shared/core-utils' import { addQueryParams, forceNumber } from '@shared/core-utils'
import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models' import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models'
import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants' import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants'
import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares' import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares'
@ -132,7 +132,7 @@ async function downloadHLSVideoFile (req: express.Request, res: express.Response
} }
function getVideoFile (req: express.Request, files: MVideoFile[]) { function getVideoFile (req: express.Request, files: MVideoFile[]) {
const resolution = parseInt(req.params.resolution, 10) const resolution = forceNumber(req.params.resolution)
return files.find(f => f.resolution === resolution) return files.find(f => f.resolution === resolution)
} }

View File

@ -4,6 +4,7 @@ import { escapeHTML } from '@shared/core-utils/renderer'
import { EMBED_SIZE, PREVIEWS_SIZE, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants' import { EMBED_SIZE, PREVIEWS_SIZE, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants'
import { asyncMiddleware, oembedValidator } from '../middlewares' import { asyncMiddleware, oembedValidator } from '../middlewares'
import { accountNameWithHostGetValidator } from '../middlewares/validators' import { accountNameWithHostGetValidator } from '../middlewares/validators'
import { forceNumber } from '@shared/core-utils'
const servicesRouter = express.Router() const servicesRouter = express.Router()
@ -108,8 +109,8 @@ function buildOEmbed (options: {
const { req, previewSize, previewPath, title, channel, embedPath } = options const { req, previewSize, previewPath, title, channel, embedPath } = options
const webserverUrl = WEBSERVER.URL const webserverUrl = WEBSERVER.URL
const maxHeight = parseInt(req.query.maxheight, 10) const maxHeight = forceNumber(req.query.maxheight)
const maxWidth = parseInt(req.query.maxwidth, 10) const maxWidth = forceNumber(req.query.maxwidth)
const embedUrl = webserverUrl + embedPath const embedUrl = webserverUrl + embedPath
const embedTitle = escapeHTML(title) const embedTitle = escapeHTML(title)

View File

@ -4,6 +4,7 @@ import { buildTaskFileFieldname } from '@server/lib/video-studio'
import { VideoStudioTask } from '@shared/models' import { VideoStudioTask } from '@shared/models'
import { isArray } from './misc' import { isArray } from './misc'
import { isVideoFileMimeTypeValid, isVideoImageValid } from './videos' import { isVideoFileMimeTypeValid, isVideoImageValid } from './videos'
import { forceNumber } from '@shared/core-utils'
function isValidStudioTasksArray (tasks: any) { function isValidStudioTasksArray (tasks: any) {
if (!isArray(tasks)) return false if (!isArray(tasks)) return false
@ -24,7 +25,7 @@ function isStudioCutTaskValid (task: VideoStudioTask) {
if (!start || !end) return true if (!start || !end) return true
return parseInt(start + '') < parseInt(end + '') return forceNumber(start) < forceNumber(end)
} }
function isStudioTaskAddIntroOutroValid (task: VideoStudioTask, indice: number, files: Express.Multer.File[]) { function isStudioTaskAddIntroOutroValid (task: VideoStudioTask, indice: number, files: Express.Multer.File[]) {

View File

@ -2,6 +2,7 @@ import { Response } from 'express'
import { CONFIG } from '@server/initializers/config' import { CONFIG } from '@server/initializers/config'
import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models' import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models'
import { VideoPrivacy, VideoState } from '@shared/models' import { VideoPrivacy, VideoState } from '@shared/models'
import { forceNumber } from '@shared/core-utils'
function getVideoWithAttributes (res: Response) { function getVideoWithAttributes (res: Response) {
return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo
@ -14,14 +15,14 @@ function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) {
} }
function isPrivacyForFederation (privacy: VideoPrivacy) { function isPrivacyForFederation (privacy: VideoPrivacy) {
const castedPrivacy = parseInt(privacy + '', 10) const castedPrivacy = forceNumber(privacy)
return castedPrivacy === VideoPrivacy.PUBLIC || return castedPrivacy === VideoPrivacy.PUBLIC ||
(CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED) (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED)
} }
function isStateForFederation (state: VideoState) { function isStateForFederation (state: VideoState) {
const castedState = parseInt(state + '', 10) const castedState = forceNumber(state)
return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED
} }

View File

@ -3,6 +3,7 @@ import validator from 'validator'
import { pageToStartAndCount } from '@server/helpers/core-utils' import { pageToStartAndCount } from '@server/helpers/core-utils'
import { ACTIVITY_PUB } from '@server/initializers/constants' import { ACTIVITY_PUB } from '@server/initializers/constants'
import { ResultList } from '@shared/models' import { ResultList } from '@shared/models'
import { forceNumber } from '@shared/core-utils'
type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird<ResultList<any>> | Promise<ResultList<any>> type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird<ResultList<any>> | Promise<ResultList<any>>
@ -33,7 +34,7 @@ async function activityPubCollectionPagination (
let prev: string | undefined let prev: string | undefined
// Assert page is a number // Assert page is a number
page = parseInt(page, 10) page = forceNumber(page)
// There are more results // There are more results
if (result.total > page * size) { if (result.total > page * size) {

View File

@ -1,12 +1,13 @@
import express from 'express' import express from 'express'
import { forceNumber } from '@shared/core-utils'
import { PAGINATION } from '../initializers/constants' import { PAGINATION } from '../initializers/constants'
function setDefaultPagination (req: express.Request, res: express.Response, next: express.NextFunction) { function setDefaultPagination (req: express.Request, res: express.Response, next: express.NextFunction) {
if (!req.query.start) req.query.start = 0 if (!req.query.start) req.query.start = 0
else req.query.start = parseInt(req.query.start, 10) else req.query.start = forceNumber(req.query.start)
if (!req.query.count) req.query.count = PAGINATION.GLOBAL.COUNT.DEFAULT if (!req.query.count) req.query.count = PAGINATION.GLOBAL.COUNT.DEFAULT
else req.query.count = parseInt(req.query.count, 10) else req.query.count = forceNumber(req.query.count)
return next() return next()
} }

View File

@ -18,6 +18,7 @@ import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
import { AbuseCreate, UserRight } from '@shared/models' import { AbuseCreate, UserRight } from '@shared/models'
import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
import { areValidationErrors, doesAbuseExist, doesAccountIdExist, doesCommentIdExist, doesVideoExist } from './shared' import { areValidationErrors, doesAbuseExist, doesAccountIdExist, doesCommentIdExist, doesVideoExist } from './shared'
import { forceNumber } from '@shared/core-utils'
const abuseReportValidator = [ const abuseReportValidator = [
body('account.id') body('account.id')
@ -216,7 +217,7 @@ const deleteAbuseMessageValidator = [
const user = res.locals.oauth.token.user const user = res.locals.oauth.token.user
const abuse = res.locals.abuse const abuse = res.locals.abuse
const messageId = parseInt(req.params.messageId + '', 10) const messageId = forceNumber(req.params.messageId)
const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id) const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id)
if (!abuseMessage) { if (!abuseMessage) {

View File

@ -1,6 +1,7 @@
import express from 'express' import express from 'express'
import { body, param, query } from 'express-validator' import { body, param, query } from 'express-validator'
import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies' import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies'
import { forceNumber } from '@shared/core-utils'
import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
import { import {
exists, exists,
@ -171,7 +172,7 @@ const removeVideoRedundancyValidator = [
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
const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10)) const redundancy = await VideoRedundancyModel.loadByIdWithVideo(forceNumber(req.params.redundancyId))
if (!redundancy) { if (!redundancy) {
return res.fail({ return res.fail({
status: HttpStatusCode.NOT_FOUND_404, status: HttpStatusCode.NOT_FOUND_404,

View File

@ -1,9 +1,10 @@
import { Response } from 'express' import { Response } from 'express'
import { AbuseModel } from '@server/models/abuse/abuse' import { AbuseModel } from '@server/models/abuse/abuse'
import { HttpStatusCode } from '@shared/models' import { HttpStatusCode } from '@shared/models'
import { forceNumber } from '@shared/core-utils'
async function doesAbuseExist (abuseId: number | string, res: Response) { async function doesAbuseExist (abuseId: number | string, res: Response) {
const abuse = await AbuseModel.loadByIdWithReporter(parseInt(abuseId + '', 10)) const abuse = await AbuseModel.loadByIdWithReporter(forceNumber(abuseId))
if (!abuse) { if (!abuse) {
res.fail({ res.fail({

View File

@ -2,10 +2,11 @@ import { Response } from 'express'
import { AccountModel } from '@server/models/account/account' import { AccountModel } from '@server/models/account/account'
import { UserModel } from '@server/models/user/user' import { UserModel } from '@server/models/user/user'
import { MAccountDefault } from '@server/types/models' import { MAccountDefault } from '@server/types/models'
import { forceNumber } from '@shared/core-utils'
import { HttpStatusCode } from '@shared/models' import { HttpStatusCode } from '@shared/models'
function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) { function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) {
const promise = AccountModel.load(parseInt(id + '', 10)) const promise = AccountModel.load(forceNumber(id))
return doesAccountExist(promise, res, sendNotFound) return doesAccountExist(promise, res, sendNotFound)
} }
@ -40,7 +41,7 @@ async function doesAccountExist (p: Promise<MAccountDefault>, res: Response, sen
} }
async function doesUserFeedTokenCorrespond (id: number, token: string, res: Response) { async function doesUserFeedTokenCorrespond (id: number, token: string, res: Response) {
const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10)) const user = await UserModel.loadByIdWithChannels(forceNumber(id))
if (token !== user.feedToken) { if (token !== user.feedToken) {
res.fail({ res.fail({

View File

@ -2,10 +2,11 @@ import express from 'express'
import { ActorModel } from '@server/models/actor/actor' import { ActorModel } from '@server/models/actor/actor'
import { UserModel } from '@server/models/user/user' import { UserModel } from '@server/models/user/user'
import { MUserDefault } from '@server/types/models' import { MUserDefault } from '@server/types/models'
import { forceNumber } from '@shared/core-utils'
import { HttpStatusCode } from '@shared/models' import { HttpStatusCode } from '@shared/models'
function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) { function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) {
const id = parseInt(idArg + '', 10) const id = forceNumber(idArg)
return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res) return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res)
} }

View File

@ -1,10 +1,11 @@
import express from 'express' import express from 'express'
import { VideoCommentModel } from '@server/models/video/video-comment' import { VideoCommentModel } from '@server/models/video/video-comment'
import { MVideoId } from '@server/types/models' import { MVideoId } from '@server/types/models'
import { forceNumber } from '@shared/core-utils'
import { HttpStatusCode, ServerErrorCode } from '@shared/models' import { HttpStatusCode, ServerErrorCode } from '@shared/models'
async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) { async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) {
const id = parseInt(idArg + '', 10) const id = forceNumber(idArg)
const videoComment = await VideoCommentModel.loadById(id) const videoComment = await VideoCommentModel.loadById(id)
if (!videoComment) { if (!videoComment) {
@ -33,7 +34,7 @@ async function doesVideoCommentThreadExist (idArg: number | string, video: MVide
} }
async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) { async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) {
const id = parseInt(idArg + '', 10) const id = forceNumber(idArg)
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
if (!videoComment) { if (!videoComment) {
@ -57,7 +58,7 @@ async function doesVideoCommentExist (idArg: number | string, video: MVideoId, r
} }
async function doesCommentIdExist (idArg: number | string, res: express.Response) { async function doesCommentIdExist (idArg: number | string, res: express.Response) {
const id = parseInt(idArg + '', 10) const id = forceNumber(idArg)
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
if (!videoComment) { if (!videoComment) {

View File

@ -1,9 +1,10 @@
import express from 'express' import express from 'express'
import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership' import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership'
import { forceNumber } from '@shared/core-utils'
import { HttpStatusCode } from '@shared/models' import { HttpStatusCode } from '@shared/models'
async function doesChangeVideoOwnershipExist (idArg: number | string, res: express.Response) { async function doesChangeVideoOwnershipExist (idArg: number | string, res: express.Response) {
const id = parseInt(idArg + '', 10) const id = forceNumber(idArg)
const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) const videoChangeOwnership = await VideoChangeOwnershipModel.load(id)
if (!videoChangeOwnership) { if (!videoChangeOwnership) {

View File

@ -1,6 +1,7 @@
import express from 'express' import express from 'express'
import { body, param, query } from 'express-validator' import { body, param, query } from 'express-validator'
import { Hooks } from '@server/lib/plugins/hooks' import { Hooks } from '@server/lib/plugins/hooks'
import { forceNumber } from '@shared/core-utils'
import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models' import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models'
import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
import { isThemeNameValid } from '../../helpers/custom-validators/plugins' import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
@ -515,7 +516,7 @@ const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Requ
const user = res.locals.oauth.token.User const user = res.locals.oauth.token.User
const isAdminOrModerator = user.role === UserRole.ADMINISTRATOR || user.role === UserRole.MODERATOR const isAdminOrModerator = user.role === UserRole.ADMINISTRATOR || user.role === UserRole.MODERATOR
const targetUserId = parseInt(targetUserIdGetter(req) + '') const targetUserId = forceNumber(targetUserIdGetter(req))
// Admin/moderator action on another user, skip the password check // Admin/moderator action on another user, skip the password check
if (isAdminOrModerator && targetUserId !== user.id) { if (isAdminOrModerator && targetUserId !== user.id) {

View File

@ -4,6 +4,7 @@ import { isResolvingToUnicastOnly } from '@server/helpers/dns'
import { isPreImportVideoAccepted } from '@server/lib/moderation' import { isPreImportVideoAccepted } from '@server/lib/moderation'
import { Hooks } from '@server/lib/plugins/hooks' import { Hooks } from '@server/lib/plugins/hooks'
import { MUserAccountId, MVideoImport } from '@server/types/models' import { MUserAccountId, MVideoImport } from '@server/types/models'
import { forceNumber } from '@shared/core-utils'
import { HttpStatusCode, UserRight, VideoImportState } from '@shared/models' import { HttpStatusCode, UserRight, VideoImportState } from '@shared/models'
import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model' import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model'
import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc'
@ -130,7 +131,7 @@ const videoImportCancelValidator = [
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 doesVideoImportExist(parseInt(req.params.id), res)) return if (!await doesVideoImportExist(forceNumber(req.params.id), res)) return
if (!checkUserCanManageImport(res.locals.oauth.token.user, res.locals.videoImport, res)) return if (!checkUserCanManageImport(res.locals.oauth.token.user, res.locals.videoImport, res)) return
if (res.locals.videoImport.state !== VideoImportState.PENDING) { if (res.locals.videoImport.state !== VideoImportState.PENDING) {

View File

@ -2,6 +2,7 @@ import express from 'express'
import { body, param, query, ValidationChain } from 'express-validator' import { body, param, query, ValidationChain } from 'express-validator'
import { ExpressPromiseHandler } from '@server/types/express-handler' import { ExpressPromiseHandler } from '@server/types/express-handler'
import { MUserAccountId } from '@server/types/models' import { MUserAccountId } from '@server/types/models'
import { forceNumber } from '@shared/core-utils'
import { import {
HttpStatusCode, HttpStatusCode,
UserRight, UserRight,
@ -258,7 +259,7 @@ const videoPlaylistElementAPGetValidator = [
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
const playlistElementId = parseInt(req.params.playlistElementId + '', 10) const playlistElementId = forceNumber(req.params.playlistElementId)
const playlistId = req.params.playlistId const playlistId = req.params.playlistId
const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId) const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId)

View File

@ -1,5 +1,6 @@
import { exists } from '@server/helpers/custom-validators/misc' import { exists } from '@server/helpers/custom-validators/misc'
import { forceNumber } from '@shared/core-utils'
import { AbuseFilter, AbuseState, AbuseVideoIs } from '@shared/models' import { AbuseFilter, AbuseState, AbuseVideoIs } from '@shared/models'
import { buildBlockedAccountSQL, buildDirectionAndField } from '../utils' import { buildBlockedAccountSQL, buildDirectionAndField } from '../utils'
@ -135,12 +136,12 @@ function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' |
} }
if (exists(options.count)) { if (exists(options.count)) {
const count = parseInt(options.count + '', 10) const count = forceNumber(options.count)
suffix += `LIMIT ${count} ` suffix += `LIMIT ${count} `
} }
if (exists(options.start)) { if (exists(options.start)) {
const start = parseInt(options.start + '', 10) const start = forceNumber(options.start)
suffix += `OFFSET ${start} ` suffix += `OFFSET ${start} `
} }
} }

View File

@ -18,7 +18,7 @@ import {
import { activityPubContextify } from '@server/lib/activitypub/context' import { activityPubContextify } from '@server/lib/activitypub/context'
import { getBiggestActorImage } from '@server/lib/actor-image' import { getBiggestActorImage } from '@server/lib/actor-image'
import { ModelCache } from '@server/models/model-cache' import { ModelCache } from '@server/models/model-cache'
import { getLowercaseExtension } from '@shared/core-utils' import { forceNumber, getLowercaseExtension } from '@shared/core-utils'
import { ActivityIconObject, ActivityPubActorType, ActorImageType } from '@shared/models' import { ActivityIconObject, ActivityPubActorType, ActorImageType } from '@shared/models'
import { AttributesOnly } from '@shared/typescript-utils' import { AttributesOnly } from '@shared/typescript-utils'
import { import {
@ -446,7 +446,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
} }
static rebuildFollowsCount (ofId: number, type: 'followers' | 'following', transaction?: Transaction) { static rebuildFollowsCount (ofId: number, type: 'followers' | 'following', transaction?: Transaction) {
const sanitizedOfId = parseInt(ofId + '', 10) const sanitizedOfId = forceNumber(ofId)
const where = { id: sanitizedOfId } const where = { id: sanitizedOfId }
let columnToUpdate: string let columnToUpdate: string

View File

@ -2,6 +2,7 @@ import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { getBiggestActorImage } from '@server/lib/actor-image' import { getBiggestActorImage } from '@server/lib/actor-image'
import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user'
import { forceNumber } from '@shared/core-utils'
import { uuidToShort } from '@shared/extra-utils' import { uuidToShort } from '@shared/extra-utils'
import { UserNotification, UserNotificationType } from '@shared/models' import { UserNotification, UserNotificationType } from '@shared/models'
import { AttributesOnly } from '@shared/typescript-utils' import { AttributesOnly } from '@shared/typescript-utils'
@ -284,7 +285,7 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
} }
static removeNotificationsOf (options: { id: number, type: 'account' | 'server', forUserId?: number }) { static removeNotificationsOf (options: { id: number, type: 'account' | 'server', forUserId?: number }) {
const id = parseInt(options.id + '', 10) const id = forceNumber(options.id)
function buildAccountWhereQuery (base: string) { function buildAccountWhereQuery (base: string) {
const whereSuffix = options.forUserId const whereSuffix = options.forUserId

View File

@ -70,6 +70,7 @@ import { VideoImportModel } from '../video/video-import'
import { VideoLiveModel } from '../video/video-live' import { VideoLiveModel } from '../video/video-live'
import { VideoPlaylistModel } from '../video/video-playlist' import { VideoPlaylistModel } from '../video/video-playlist'
import { UserNotificationSettingModel } from './user-notification-setting' import { UserNotificationSettingModel } from './user-notification-setting'
import { forceNumber } from '@shared/core-utils'
enum ScopeNames { enum ScopeNames {
FOR_ME_API = 'FOR_ME_API', FOR_ME_API = 'FOR_ME_API',
@ -900,27 +901,27 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
videoQuotaDaily: this.videoQuotaDaily, videoQuotaDaily: this.videoQuotaDaily,
videoQuotaUsed: videoQuotaUsed !== undefined videoQuotaUsed: videoQuotaUsed !== undefined
? parseInt(videoQuotaUsed + '', 10) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) ? forceNumber(videoQuotaUsed) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id)
: undefined, : undefined,
videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
? parseInt(videoQuotaUsedDaily + '', 10) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) ? forceNumber(videoQuotaUsedDaily) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id)
: undefined, : undefined,
videosCount: videosCount !== undefined videosCount: videosCount !== undefined
? parseInt(videosCount + '', 10) ? forceNumber(videosCount)
: undefined, : undefined,
abusesCount: abusesCount abusesCount: abusesCount
? parseInt(abusesCount, 10) ? forceNumber(abusesCount)
: undefined, : undefined,
abusesAcceptedCount: abusesAcceptedCount abusesAcceptedCount: abusesAcceptedCount
? parseInt(abusesAcceptedCount, 10) ? forceNumber(abusesAcceptedCount)
: undefined, : undefined,
abusesCreatedCount: abusesCreatedCount !== undefined abusesCreatedCount: abusesCreatedCount !== undefined
? parseInt(abusesCreatedCount + '', 10) ? forceNumber(abusesCreatedCount)
: undefined, : undefined,
videoCommentsCount: videoCommentsCount !== undefined videoCommentsCount: videoCommentsCount !== undefined
? parseInt(videoCommentsCount + '', 10) ? forceNumber(videoCommentsCount)
: undefined, : undefined,
noInstanceConfigWarningModal: this.noInstanceConfigWarningModal, noInstanceConfigWarningModal: this.noInstanceConfigWarningModal,

View File

@ -1,5 +1,6 @@
import { literal, Op, OrderItem, Sequelize } from 'sequelize' import { literal, Op, OrderItem, Sequelize } from 'sequelize'
import validator from 'validator' import validator from 'validator'
import { forceNumber } from '@shared/core-utils'
type SortType = { sortModel: string, sortValue: string } type SortType = { sortModel: string, sortValue: string }
@ -202,7 +203,7 @@ function buildBlockedAccountSQLOptimized (columnNameJoin: string, blockerIds: nu
} }
function buildServerIdsFollowedBy (actorId: any) { function buildServerIdsFollowedBy (actorId: any) {
const actorIdNumber = parseInt(actorId + '', 10) const actorIdNumber = forceNumber(actorId)
return '(' + return '(' +
'SELECT "actor"."serverId" FROM "actorFollow" ' + 'SELECT "actor"."serverId" FROM "actorFollow" ' +
@ -218,7 +219,7 @@ function buildWhereIdOrUUID (id: number | string) {
function parseAggregateResult (result: any) { function parseAggregateResult (result: any) {
if (!result) return 0 if (!result) return 0
const total = parseInt(result + '', 10) const total = forceNumber(result)
if (isNaN(total)) return 0 if (isNaN(total)) return 0
return total return total

View File

@ -6,6 +6,7 @@ import { buildDirectionAndField, createSafeIn, parseRowCountResult } from '@serv
import { MUserAccountId, MUserId } from '@server/types/models' import { MUserAccountId, MUserId } from '@server/types/models'
import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models' import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models'
import { AbstractRunQuery } from '../../../shared/abstract-run-query' import { AbstractRunQuery } from '../../../shared/abstract-run-query'
import { forceNumber } from '@shared/core-utils'
/** /**
* *
@ -689,12 +690,12 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
} }
private setLimit (countArg: number) { private setLimit (countArg: number) {
const count = parseInt(countArg + '', 10) const count = forceNumber(countArg)
this.limit = `LIMIT ${count}` this.limit = `LIMIT ${count}`
} }
private setOffset (startArg: number) { private setOffset (startArg: number) {
const start = parseInt(startArg + '', 10) const start = forceNumber(startArg)
this.offset = `OFFSET ${start}` this.offset = `OFFSET ${start}`
} }
} }

View File

@ -19,7 +19,7 @@ import {
} from 'sequelize-typescript' } from 'sequelize-typescript'
import { CONFIG } from '@server/initializers/config' import { CONFIG } from '@server/initializers/config'
import { MAccountActor } from '@server/types/models' import { MAccountActor } from '@server/types/models'
import { pick } from '@shared/core-utils' import { forceNumber, pick } from '@shared/core-utils'
import { AttributesOnly } from '@shared/typescript-utils' import { AttributesOnly } from '@shared/typescript-utils'
import { ActivityPubActor } from '../../../shared/models/activitypub' import { ActivityPubActor } from '../../../shared/models/activitypub'
import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos' import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos'
@ -280,7 +280,7 @@ export type SummaryOptions = {
] ]
}, },
[ScopeNames.WITH_STATS]: (options: AvailableWithStatsOptions = { daysPrior: 30 }) => { [ScopeNames.WITH_STATS]: (options: AvailableWithStatsOptions = { daysPrior: 30 }) => {
const daysPrior = parseInt(options.daysPrior + '', 10) const daysPrior = forceNumber(options.daysPrior)
return { return {
attributes: { attributes: {

View File

@ -23,6 +23,7 @@ import {
MVideoPlaylistElementVideoUrlPlaylistPrivacy, MVideoPlaylistElementVideoUrlPlaylistPrivacy,
MVideoPlaylistVideoThumbnail MVideoPlaylistVideoThumbnail
} from '@server/types/models/video/video-playlist-element' } from '@server/types/models/video/video-playlist-element'
import { forceNumber } from '@shared/core-utils'
import { AttributesOnly } from '@shared/typescript-utils' import { AttributesOnly } from '@shared/typescript-utils'
import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
import { VideoPrivacy } from '../../../shared/models/videos' import { VideoPrivacy } from '../../../shared/models/videos'
@ -185,7 +186,9 @@ export class VideoPlaylistElementModel extends Model<Partial<AttributesOnly<Vide
playlistId: number | string, playlistId: number | string,
playlistElementId: number playlistElementId: number
): Promise<MVideoPlaylistElementVideoUrlPlaylistPrivacy> { ): Promise<MVideoPlaylistElementVideoUrlPlaylistPrivacy> {
const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } const playlistWhere = validator.isUUID('' + playlistId)
? { uuid: playlistId }
: { id: playlistId }
const query = { const query = {
include: [ include: [
@ -262,13 +265,15 @@ export class VideoPlaylistElementModel extends Model<Partial<AttributesOnly<Vide
.then(position => position ? position + 1 : 1) .then(position => position ? position + 1 : 1)
} }
static reassignPositionOf ( static reassignPositionOf (options: {
videoPlaylistId: number, videoPlaylistId: number
firstPosition: number, firstPosition: number
endPosition: number, endPosition: number
newPosition: number, newPosition: number
transaction?: Transaction transaction?: Transaction
) { }) {
const { videoPlaylistId, firstPosition, endPosition, newPosition, transaction } = options
const query = { const query = {
where: { where: {
videoPlaylistId, videoPlaylistId,
@ -281,7 +286,7 @@ export class VideoPlaylistElementModel extends Model<Partial<AttributesOnly<Vide
validate: false // We use a literal to update the position validate: false // We use a literal to update the position
} }
const positionQuery = Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) const positionQuery = Sequelize.literal(`${forceNumber(newPosition)} + "position" - ${forceNumber(firstPosition)}`)
return VideoPlaylistElementModel.update({ position: positionQuery }, query) return VideoPlaylistElementModel.update({ position: positionQuery }, query)
} }

View File

@ -1,5 +1,6 @@
import { literal, Op, QueryTypes, Transaction } from 'sequelize' import { literal, Op, QueryTypes, Transaction } from 'sequelize'
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
import { forceNumber } from '@shared/core-utils'
import { AttributesOnly } from '@shared/typescript-utils' import { AttributesOnly } from '@shared/typescript-utils'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { CONSTRAINTS_FIELDS } from '../../initializers/constants' import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
@ -123,7 +124,7 @@ export class VideoShareModel extends Model<Partial<AttributesOnly<VideoShareMode
} }
static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Promise<MActorDefault[]> { static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Promise<MActorDefault[]> {
const safeOwnerId = parseInt(actorOwnerId + '', 10) const safeOwnerId = forceNumber(actorOwnerId)
// /!\ On actor model // /!\ On actor model
const query = { const query = {
@ -148,7 +149,7 @@ export class VideoShareModel extends Model<Partial<AttributesOnly<VideoShareMode
} }
static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Promise<MActorDefault[]> { static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Promise<MActorDefault[]> {
const safeChannelId = parseInt(videoChannelId + '', 10) const safeChannelId = forceNumber(videoChannelId)
// /!\ On actor model // /!\ On actor model
const query = { const query = {

View File

@ -2,7 +2,7 @@ import CliTable3 from 'cli-table3'
import { Command, program } from 'commander' import { Command, program } from 'commander'
import { URL } from 'url' import { URL } from 'url'
import validator from 'validator' import validator from 'validator'
import { uniqify } from '@shared/core-utils' import { forceNumber, uniqify } from '@shared/core-utils'
import { HttpStatusCode, VideoRedundanciesTarget } from '@shared/models' import { HttpStatusCode, VideoRedundanciesTarget } from '@shared/models'
import { assignToken, buildServer, getServerCredentials } from './cli' import { assignToken, buildServer, getServerCredentials } from './cli'
@ -138,7 +138,7 @@ async function removeRedundancyCLI (options: { video: number }, command: Command
process.exit(-1) process.exit(-1)
} }
const videoId = parseInt(options.video + '', 10) const videoId = forceNumber(options.video)
const myVideoRedundancies = await server.redundancy.listVideos({ target: 'my-videos' }) const myVideoRedundancies = await server.redundancy.listVideos({ target: 'my-videos' })
let videoRedundancy = myVideoRedundancies.data.find(r => videoId === r.id) let videoRedundancy = myVideoRedundancies.data.find(r => videoId === r.id)

View File

@ -2,6 +2,7 @@ export * from './array'
export * from './random' export * from './random'
export * from './date' export * from './date'
export * from './env' export * from './env'
export * from './number'
export * from './object' export * from './object'
export * from './path' export * from './path'
export * from './regexp' export * from './regexp'

View File

@ -0,0 +1,7 @@
function forceNumber (value: any) {
return parseInt(value + '')
}
export {
forceNumber
}

View File

@ -1,4 +1,5 @@
import { ffprobe, FfprobeData } from 'fluent-ffmpeg' import { ffprobe, FfprobeData } from 'fluent-ffmpeg'
import { forceNumber } from '@shared/core-utils'
import { VideoFileMetadata, VideoResolution } from '@shared/models/videos' import { VideoFileMetadata, VideoResolution } from '@shared/models/videos'
/** /**
@ -55,7 +56,7 @@ async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
return { return {
absolutePath: data.format.filename, absolutePath: data.format.filename,
audioStream, audioStream,
bitrate: parseInt(audioStream['bit_rate'] + '', 10) bitrate: forceNumber(audioStream['bit_rate'])
} }
} }
} }

View File

@ -1,4 +1,5 @@
import { QueryTypes, Sequelize } from 'sequelize' import { QueryTypes, Sequelize } from 'sequelize'
import { forceNumber } from '@shared/core-utils'
import { AbstractCommand } from '../shared' import { AbstractCommand } from '../shared'
export class SQLCommand extends AbstractCommand { export class SQLCommand extends AbstractCommand {
@ -63,7 +64,7 @@ export class SQLCommand extends AbstractCommand {
if (!total) return 0 if (!total) return 0
return parseInt(total + '', 10) return forceNumber(total)
} }
getActorImage (filename: string) { getActorImage (filename: string) {