aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares
diff options
context:
space:
mode:
Diffstat (limited to 'server/middlewares')
-rw-r--r--server/middlewares/activitypub.ts2
-rw-r--r--server/middlewares/auth.ts8
-rw-r--r--server/middlewares/cache/shared/api-cache.ts6
-rw-r--r--server/middlewares/pagination.ts5
-rw-r--r--server/middlewares/validators/abuse.ts3
-rw-r--r--server/middlewares/validators/index.ts8
-rw-r--r--server/middlewares/validators/object-storage-proxy.ts20
-rw-r--r--server/middlewares/validators/plugins.ts13
-rw-r--r--server/middlewares/validators/redundancy.ts3
-rw-r--r--server/middlewares/validators/shared/abuses.ts3
-rw-r--r--server/middlewares/validators/shared/accounts.ts5
-rw-r--r--server/middlewares/validators/shared/index.ts1
-rw-r--r--server/middlewares/validators/shared/users.ts63
-rw-r--r--server/middlewares/validators/shared/video-comments.ts7
-rw-r--r--server/middlewares/validators/shared/video-ownerships.ts3
-rw-r--r--server/middlewares/validators/shared/videos.ts54
-rw-r--r--server/middlewares/validators/static.ts169
-rw-r--r--server/middlewares/validators/themes.ts4
-rw-r--r--server/middlewares/validators/two-factor.ts81
-rw-r--r--server/middlewares/validators/user-subscriptions.ts4
-rw-r--r--server/middlewares/validators/users.ts119
-rw-r--r--server/middlewares/validators/videos/video-comments.ts5
-rw-r--r--server/middlewares/validators/videos/video-imports.ts3
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts3
-rw-r--r--server/middlewares/validators/videos/videos.ts33
25 files changed, 512 insertions, 113 deletions
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index 0064a4760..261b9f690 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -125,7 +125,7 @@ async function checkJsonLDSignature (req: Request, res: Response) {
125 return wrapWithSpanAndContext('peertube.activitypub.JSONLDSignature', async () => { 125 return wrapWithSpanAndContext('peertube.activitypub.JSONLDSignature', async () => {
126 const signatureObject: ActivityPubSignature = req.body.signature 126 const signatureObject: ActivityPubSignature = req.body.signature
127 127
128 if (!signatureObject || !signatureObject.creator) { 128 if (!signatureObject?.creator) {
129 res.fail({ 129 res.fail({
130 status: HttpStatusCode.FORBIDDEN_403, 130 status: HttpStatusCode.FORBIDDEN_403,
131 message: 'Object and creator signature do not match' 131 message: 'Object and creator signature do not match'
diff --git a/server/middlewares/auth.ts b/server/middlewares/auth.ts
index 904d47efd..e6025c8ce 100644
--- a/server/middlewares/auth.ts
+++ b/server/middlewares/auth.ts
@@ -5,8 +5,8 @@ import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
5import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
6import { handleOAuthAuthenticate } from '../lib/auth/oauth' 6import { handleOAuthAuthenticate } from '../lib/auth/oauth'
7 7
8function authenticate (req: express.Request, res: express.Response, next: express.NextFunction, authenticateInQuery = false) { 8function authenticate (req: express.Request, res: express.Response, next: express.NextFunction) {
9 handleOAuthAuthenticate(req, res, authenticateInQuery) 9 handleOAuthAuthenticate(req, res)
10 .then((token: any) => { 10 .then((token: any) => {
11 res.locals.oauth = { token } 11 res.locals.oauth = { token }
12 res.locals.authenticated = true 12 res.locals.authenticated = true
@@ -47,7 +47,7 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
47 .catch(err => logger.error('Cannot get access token.', { err })) 47 .catch(err => logger.error('Cannot get access token.', { err }))
48} 48}
49 49
50function authenticatePromise (req: express.Request, res: express.Response, authenticateInQuery = false) { 50function authenticatePromise (req: express.Request, res: express.Response) {
51 return new Promise<void>(resolve => { 51 return new Promise<void>(resolve => {
52 // Already authenticated? (or tried to) 52 // Already authenticated? (or tried to)
53 if (res.locals.oauth?.token.User) return resolve() 53 if (res.locals.oauth?.token.User) return resolve()
@@ -59,7 +59,7 @@ function authenticatePromise (req: express.Request, res: express.Response, authe
59 }) 59 })
60 } 60 }
61 61
62 authenticate(req, res, () => resolve(), authenticateInQuery) 62 authenticate(req, res, () => resolve())
63 }) 63 })
64} 64}
65 65
diff --git a/server/middlewares/cache/shared/api-cache.ts b/server/middlewares/cache/shared/api-cache.ts
index abc919339..9e15bf2d6 100644
--- a/server/middlewares/cache/shared/api-cache.ts
+++ b/server/middlewares/cache/shared/api-cache.ts
@@ -49,7 +49,7 @@ export class ApiCache {
49 if (!Redis.Instance.isConnected()) return this.makeResponseCacheable(res, next, key, duration) 49 if (!Redis.Instance.isConnected()) return this.makeResponseCacheable(res, next, key, duration)
50 50
51 try { 51 try {
52 const obj = await redis.hGetAll(key) 52 const obj = await redis.hgetall(key)
53 if (obj?.response) { 53 if (obj?.response) {
54 return this.sendCachedResponse(req, res, JSON.parse(obj.response), duration) 54 return this.sendCachedResponse(req, res, JSON.parse(obj.response), duration)
55 } 55 }
@@ -100,8 +100,8 @@ export class ApiCache {
100 100
101 if (Redis.Instance.isConnected()) { 101 if (Redis.Instance.isConnected()) {
102 await Promise.all([ 102 await Promise.all([
103 redis.hSet(key, 'response', JSON.stringify(value)), 103 redis.hset(key, 'response', JSON.stringify(value)),
104 redis.hSet(key, 'duration', duration + ''), 104 redis.hset(key, 'duration', duration + ''),
105 redis.expire(key, duration / 1000) 105 redis.expire(key, duration / 1000)
106 ]) 106 ])
107 } 107 }
diff --git a/server/middlewares/pagination.ts b/server/middlewares/pagination.ts
index 9812af9e4..17e43f743 100644
--- a/server/middlewares/pagination.ts
+++ b/server/middlewares/pagination.ts
@@ -1,12 +1,13 @@
1import express from 'express' 1import express from 'express'
2import { forceNumber } from '@shared/core-utils'
2import { PAGINATION } from '../initializers/constants' 3import { PAGINATION } from '../initializers/constants'
3 4
4function setDefaultPagination (req: express.Request, res: express.Response, next: express.NextFunction) { 5function setDefaultPagination (req: express.Request, res: express.Response, next: express.NextFunction) {
5 if (!req.query.start) req.query.start = 0 6 if (!req.query.start) req.query.start = 0
6 else req.query.start = parseInt(req.query.start, 10) 7 else req.query.start = forceNumber(req.query.start)
7 8
8 if (!req.query.count) req.query.count = PAGINATION.GLOBAL.COUNT.DEFAULT 9 if (!req.query.count) req.query.count = PAGINATION.GLOBAL.COUNT.DEFAULT
9 else req.query.count = parseInt(req.query.count, 10) 10 else req.query.count = forceNumber(req.query.count)
10 11
11 return next() 12 return next()
12} 13}
diff --git a/server/middlewares/validators/abuse.ts b/server/middlewares/validators/abuse.ts
index 9b94008ce..70bae1775 100644
--- a/server/middlewares/validators/abuse.ts
+++ b/server/middlewares/validators/abuse.ts
@@ -18,6 +18,7 @@ import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
18import { AbuseCreate, UserRight } from '@shared/models' 18import { AbuseCreate, UserRight } from '@shared/models'
19import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 19import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
20import { areValidationErrors, doesAbuseExist, doesAccountIdExist, doesCommentIdExist, doesVideoExist } from './shared' 20import { areValidationErrors, doesAbuseExist, doesAccountIdExist, doesCommentIdExist, doesVideoExist } from './shared'
21import { forceNumber } from '@shared/core-utils'
21 22
22const abuseReportValidator = [ 23const abuseReportValidator = [
23 body('account.id') 24 body('account.id')
@@ -216,7 +217,7 @@ const deleteAbuseMessageValidator = [
216 const user = res.locals.oauth.token.user 217 const user = res.locals.oauth.token.user
217 const abuse = res.locals.abuse 218 const abuse = res.locals.abuse
218 219
219 const messageId = parseInt(req.params.messageId + '', 10) 220 const messageId = forceNumber(req.params.messageId)
220 const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id) 221 const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id)
221 222
222 if (!abuseMessage) { 223 if (!abuseMessage) {
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts
index ffadb3b49..9bc8887ff 100644
--- a/server/middlewares/validators/index.ts
+++ b/server/middlewares/validators/index.ts
@@ -1,7 +1,6 @@
1export * from './activitypub'
2export * from './videos'
3export * from './abuse' 1export * from './abuse'
4export * from './account' 2export * from './account'
3export * from './activitypub'
5export * from './actor-image' 4export * from './actor-image'
6export * from './blocklist' 5export * from './blocklist'
7export * from './bulk' 6export * from './bulk'
@@ -10,8 +9,9 @@ export * from './express'
10export * from './feeds' 9export * from './feeds'
11export * from './follows' 10export * from './follows'
12export * from './jobs' 11export * from './jobs'
13export * from './metrics'
14export * from './logs' 12export * from './logs'
13export * from './metrics'
14export * from './object-storage-proxy'
15export * from './oembed' 15export * from './oembed'
16export * from './pagination' 16export * from './pagination'
17export * from './plugins' 17export * from './plugins'
@@ -19,9 +19,11 @@ export * from './redundancy'
19export * from './search' 19export * from './search'
20export * from './server' 20export * from './server'
21export * from './sort' 21export * from './sort'
22export * from './static'
22export * from './themes' 23export * from './themes'
23export * from './user-history' 24export * from './user-history'
24export * from './user-notifications' 25export * from './user-notifications'
25export * from './user-subscriptions' 26export * from './user-subscriptions'
26export * from './users' 27export * from './users'
28export * from './videos'
27export * from './webfinger' 29export * from './webfinger'
diff --git a/server/middlewares/validators/object-storage-proxy.ts b/server/middlewares/validators/object-storage-proxy.ts
new file mode 100644
index 000000000..bbd77f262
--- /dev/null
+++ b/server/middlewares/validators/object-storage-proxy.ts
@@ -0,0 +1,20 @@
1import express from 'express'
2import { CONFIG } from '@server/initializers/config'
3import { HttpStatusCode } from '@shared/models'
4
5const ensurePrivateObjectStorageProxyIsEnabled = [
6 (req: express.Request, res: express.Response, next: express.NextFunction) => {
7 if (CONFIG.OBJECT_STORAGE.PROXY.PROXIFY_PRIVATE_FILES !== true) {
8 return res.fail({
9 message: 'Private object storage proxy is not enabled',
10 status: HttpStatusCode.BAD_REQUEST_400
11 })
12 }
13
14 return next()
15 }
16]
17
18export {
19 ensurePrivateObjectStorageProxyIsEnabled
20}
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts
index 78c030333..64bef2648 100644
--- a/server/middlewares/validators/plugins.ts
+++ b/server/middlewares/validators/plugins.ts
@@ -4,7 +4,12 @@ import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
4import { PluginType } from '../../../shared/models/plugins/plugin.type' 4import { PluginType } from '../../../shared/models/plugins/plugin.type'
5import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/server/api/install-plugin.model' 5import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/server/api/install-plugin.model'
6import { exists, isBooleanValid, isSafePath, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 6import { exists, isBooleanValid, isSafePath, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
7import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' 7import {
8 isNpmPluginNameValid,
9 isPluginNameValid,
10 isPluginStableOrUnstableVersionValid,
11 isPluginTypeValid
12} from '../../helpers/custom-validators/plugins'
8import { CONFIG } from '../../initializers/config' 13import { CONFIG } from '../../initializers/config'
9import { PluginManager } from '../../lib/plugins/plugin-manager' 14import { PluginManager } from '../../lib/plugins/plugin-manager'
10import { PluginModel } from '../../models/server/plugin' 15import { PluginModel } from '../../models/server/plugin'
@@ -19,7 +24,7 @@ const getPluginValidator = (pluginType: PluginType, withVersion = true) => {
19 if (withVersion) { 24 if (withVersion) {
20 validators.push( 25 validators.push(
21 param('pluginVersion') 26 param('pluginVersion')
22 .custom(isPluginVersionValid) 27 .custom(isPluginStableOrUnstableVersionValid)
23 ) 28 )
24 } 29 }
25 30
@@ -113,7 +118,7 @@ const installOrUpdatePluginValidator = [
113 .custom(isNpmPluginNameValid), 118 .custom(isNpmPluginNameValid),
114 body('pluginVersion') 119 body('pluginVersion')
115 .optional() 120 .optional()
116 .custom(isPluginVersionValid), 121 .custom(isPluginStableOrUnstableVersionValid),
117 body('path') 122 body('path')
118 .optional() 123 .optional()
119 .custom(isSafePath), 124 .custom(isSafePath),
@@ -185,7 +190,7 @@ const listAvailablePluginsValidator = [
185 .custom(isPluginTypeValid), 190 .custom(isPluginTypeValid),
186 query('currentPeerTubeEngine') 191 query('currentPeerTubeEngine')
187 .optional() 192 .optional()
188 .custom(isPluginVersionValid), 193 .custom(isPluginStableOrUnstableVersionValid),
189 194
190 (req: express.Request, res: express.Response, next: express.NextFunction) => { 195 (req: express.Request, res: express.Response, next: express.NextFunction) => {
191 if (areValidationErrors(req, res)) return 196 if (areValidationErrors(req, res)) return
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts
index 79460f63c..c80f9b728 100644
--- a/server/middlewares/validators/redundancy.ts
+++ b/server/middlewares/validators/redundancy.ts
@@ -1,6 +1,7 @@
1import express from 'express' 1import express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies' 3import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies'
4import { forceNumber } from '@shared/core-utils'
4import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 5import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
5import { 6import {
6 exists, 7 exists,
@@ -171,7 +172,7 @@ const removeVideoRedundancyValidator = [
171 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 172 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
172 if (areValidationErrors(req, res)) return 173 if (areValidationErrors(req, res)) return
173 174
174 const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10)) 175 const redundancy = await VideoRedundancyModel.loadByIdWithVideo(forceNumber(req.params.redundancyId))
175 if (!redundancy) { 176 if (!redundancy) {
176 return res.fail({ 177 return res.fail({
177 status: HttpStatusCode.NOT_FOUND_404, 178 status: HttpStatusCode.NOT_FOUND_404,
diff --git a/server/middlewares/validators/shared/abuses.ts b/server/middlewares/validators/shared/abuses.ts
index 2b8d86ba5..2c988f9ec 100644
--- a/server/middlewares/validators/shared/abuses.ts
+++ b/server/middlewares/validators/shared/abuses.ts
@@ -1,9 +1,10 @@
1import { Response } from 'express' 1import { Response } from 'express'
2import { AbuseModel } from '@server/models/abuse/abuse' 2import { AbuseModel } from '@server/models/abuse/abuse'
3import { HttpStatusCode } from '@shared/models' 3import { HttpStatusCode } from '@shared/models'
4import { forceNumber } from '@shared/core-utils'
4 5
5async function doesAbuseExist (abuseId: number | string, res: Response) { 6async function doesAbuseExist (abuseId: number | string, res: Response) {
6 const abuse = await AbuseModel.loadByIdWithReporter(parseInt(abuseId + '', 10)) 7 const abuse = await AbuseModel.loadByIdWithReporter(forceNumber(abuseId))
7 8
8 if (!abuse) { 9 if (!abuse) {
9 res.fail({ 10 res.fail({
diff --git a/server/middlewares/validators/shared/accounts.ts b/server/middlewares/validators/shared/accounts.ts
index fe4f83aa0..72b0e235e 100644
--- a/server/middlewares/validators/shared/accounts.ts
+++ b/server/middlewares/validators/shared/accounts.ts
@@ -2,10 +2,11 @@ import { Response } from 'express'
2import { AccountModel } from '@server/models/account/account' 2import { AccountModel } from '@server/models/account/account'
3import { UserModel } from '@server/models/user/user' 3import { UserModel } from '@server/models/user/user'
4import { MAccountDefault } from '@server/types/models' 4import { MAccountDefault } from '@server/types/models'
5import { forceNumber } from '@shared/core-utils'
5import { HttpStatusCode } from '@shared/models' 6import { HttpStatusCode } from '@shared/models'
6 7
7function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) { 8function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) {
8 const promise = AccountModel.load(parseInt(id + '', 10)) 9 const promise = AccountModel.load(forceNumber(id))
9 10
10 return doesAccountExist(promise, res, sendNotFound) 11 return doesAccountExist(promise, res, sendNotFound)
11} 12}
@@ -40,7 +41,7 @@ async function doesAccountExist (p: Promise<MAccountDefault>, res: Response, sen
40} 41}
41 42
42async function doesUserFeedTokenCorrespond (id: number, token: string, res: Response) { 43async function doesUserFeedTokenCorrespond (id: number, token: string, res: Response) {
43 const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10)) 44 const user = await UserModel.loadByIdWithChannels(forceNumber(id))
44 45
45 if (token !== user.feedToken) { 46 if (token !== user.feedToken) {
46 res.fail({ 47 res.fail({
diff --git a/server/middlewares/validators/shared/index.ts b/server/middlewares/validators/shared/index.ts
index bbd03b248..de98cd442 100644
--- a/server/middlewares/validators/shared/index.ts
+++ b/server/middlewares/validators/shared/index.ts
@@ -1,5 +1,6 @@
1export * from './abuses' 1export * from './abuses'
2export * from './accounts' 2export * from './accounts'
3export * from './users'
3export * from './utils' 4export * from './utils'
4export * from './video-blacklists' 5export * from './video-blacklists'
5export * from './video-captions' 6export * from './video-captions'
diff --git a/server/middlewares/validators/shared/users.ts b/server/middlewares/validators/shared/users.ts
new file mode 100644
index 000000000..b8f1436d3
--- /dev/null
+++ b/server/middlewares/validators/shared/users.ts
@@ -0,0 +1,63 @@
1import express from 'express'
2import { ActorModel } from '@server/models/actor/actor'
3import { UserModel } from '@server/models/user/user'
4import { MUserDefault } from '@server/types/models'
5import { forceNumber } from '@shared/core-utils'
6import { HttpStatusCode } from '@shared/models'
7
8function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) {
9 const id = forceNumber(idArg)
10 return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res)
11}
12
13function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
14 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
15}
16
17async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
18 const user = await UserModel.loadByUsernameOrEmail(username, email)
19
20 if (user) {
21 res.fail({
22 status: HttpStatusCode.CONFLICT_409,
23 message: 'User with this username or email already exists.'
24 })
25 return false
26 }
27
28 const actor = await ActorModel.loadLocalByName(username)
29 if (actor) {
30 res.fail({
31 status: HttpStatusCode.CONFLICT_409,
32 message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.'
33 })
34 return false
35 }
36
37 return true
38}
39
40async function checkUserExist (finder: () => Promise<MUserDefault>, res: express.Response, abortResponse = true) {
41 const user = await finder()
42
43 if (!user) {
44 if (abortResponse === true) {
45 res.fail({
46 status: HttpStatusCode.NOT_FOUND_404,
47 message: 'User not found'
48 })
49 }
50
51 return false
52 }
53
54 res.locals.user = user
55 return true
56}
57
58export {
59 checkUserIdExist,
60 checkUserEmailExist,
61 checkUserNameOrEmailDoesNotAlreadyExist,
62 checkUserExist
63}
diff --git a/server/middlewares/validators/shared/video-comments.ts b/server/middlewares/validators/shared/video-comments.ts
index 8d1a16294..0961b3ec9 100644
--- a/server/middlewares/validators/shared/video-comments.ts
+++ b/server/middlewares/validators/shared/video-comments.ts
@@ -1,10 +1,11 @@
1import express from 'express' 1import express from 'express'
2import { VideoCommentModel } from '@server/models/video/video-comment' 2import { VideoCommentModel } from '@server/models/video/video-comment'
3import { MVideoId } from '@server/types/models' 3import { MVideoId } from '@server/types/models'
4import { forceNumber } from '@shared/core-utils'
4import { HttpStatusCode, ServerErrorCode } from '@shared/models' 5import { HttpStatusCode, ServerErrorCode } from '@shared/models'
5 6
6async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) { 7async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) {
7 const id = parseInt(idArg + '', 10) 8 const id = forceNumber(idArg)
8 const videoComment = await VideoCommentModel.loadById(id) 9 const videoComment = await VideoCommentModel.loadById(id)
9 10
10 if (!videoComment) { 11 if (!videoComment) {
@@ -33,7 +34,7 @@ async function doesVideoCommentThreadExist (idArg: number | string, video: MVide
33} 34}
34 35
35async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) { 36async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) {
36 const id = parseInt(idArg + '', 10) 37 const id = forceNumber(idArg)
37 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) 38 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
38 39
39 if (!videoComment) { 40 if (!videoComment) {
@@ -57,7 +58,7 @@ async function doesVideoCommentExist (idArg: number | string, video: MVideoId, r
57} 58}
58 59
59async function doesCommentIdExist (idArg: number | string, res: express.Response) { 60async function doesCommentIdExist (idArg: number | string, res: express.Response) {
60 const id = parseInt(idArg + '', 10) 61 const id = forceNumber(idArg)
61 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) 62 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
62 63
63 if (!videoComment) { 64 if (!videoComment) {
diff --git a/server/middlewares/validators/shared/video-ownerships.ts b/server/middlewares/validators/shared/video-ownerships.ts
index 680613cda..33ac9c8b6 100644
--- a/server/middlewares/validators/shared/video-ownerships.ts
+++ b/server/middlewares/validators/shared/video-ownerships.ts
@@ -1,9 +1,10 @@
1import express from 'express' 1import express from 'express'
2import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership' 2import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership'
3import { forceNumber } from '@shared/core-utils'
3import { HttpStatusCode } from '@shared/models' 4import { HttpStatusCode } from '@shared/models'
4 5
5async function doesChangeVideoOwnershipExist (idArg: number | string, res: express.Response) { 6async function doesChangeVideoOwnershipExist (idArg: number | string, res: express.Response) {
6 const id = parseInt(idArg + '', 10) 7 const id = forceNumber(idArg)
7 const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) 8 const videoChangeOwnership = await VideoChangeOwnershipModel.load(id)
8 9
9 if (!videoChangeOwnership) { 10 if (!videoChangeOwnership) {
diff --git a/server/middlewares/validators/shared/videos.ts b/server/middlewares/validators/shared/videos.ts
index e3a98c58f..ebbfc0a0a 100644
--- a/server/middlewares/validators/shared/videos.ts
+++ b/server/middlewares/validators/shared/videos.ts
@@ -1,7 +1,7 @@
1import { Request, Response } from 'express' 1import { Request, Response } from 'express'
2import { isUUIDValid } from '@server/helpers/custom-validators/misc'
3import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' 2import { loadVideo, VideoLoadType } from '@server/lib/model-loaders'
4import { isAbleToUploadVideo } from '@server/lib/user' 3import { isAbleToUploadVideo } from '@server/lib/user'
4import { VideoTokensManager } from '@server/lib/video-tokens-manager'
5import { authenticatePromise } from '@server/middlewares/auth' 5import { authenticatePromise } from '@server/middlewares/auth'
6import { VideoModel } from '@server/models/video/video' 6import { VideoModel } from '@server/models/video/video'
7import { VideoChannelModel } from '@server/models/video/video-channel' 7import { VideoChannelModel } from '@server/models/video/video-channel'
@@ -108,26 +108,21 @@ async function checkCanSeeVideo (options: {
108 res: Response 108 res: Response
109 paramId: string 109 paramId: string
110 video: MVideo 110 video: MVideo
111 authenticateInQuery?: boolean // default false
112}) { 111}) {
113 const { req, res, video, paramId, authenticateInQuery = false } = options 112 const { req, res, video, paramId } = options
114 113
115 if (video.requiresAuth()) { 114 if (video.requiresAuth({ urlParamId: paramId, checkBlacklist: true })) {
116 return checkCanSeeAuthVideo(req, res, video, authenticateInQuery) 115 return checkCanSeeAuthVideo(req, res, video)
117 } 116 }
118 117
119 if (video.privacy === VideoPrivacy.UNLISTED) { 118 if (video.privacy === VideoPrivacy.UNLISTED || video.privacy === VideoPrivacy.PUBLIC) {
120 if (isUUIDValid(paramId)) return true 119 return true
121
122 return checkCanSeeAuthVideo(req, res, video, authenticateInQuery)
123 } 120 }
124 121
125 if (video.privacy === VideoPrivacy.PUBLIC) return true 122 throw new Error('Unknown video privacy when checking video right ' + video.url)
126
127 throw new Error('Fatal error when checking video right ' + video.url)
128} 123}
129 124
130async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoId | MVideoWithRights, authenticateInQuery = false) { 125async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoId | MVideoWithRights) {
131 const fail = () => { 126 const fail = () => {
132 res.fail({ 127 res.fail({
133 status: HttpStatusCode.FORBIDDEN_403, 128 status: HttpStatusCode.FORBIDDEN_403,
@@ -137,7 +132,7 @@ async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoI
137 return false 132 return false
138 } 133 }
139 134
140 await authenticatePromise(req, res, authenticateInQuery) 135 await authenticatePromise(req, res)
141 136
142 const user = res.locals.oauth?.token.User 137 const user = res.locals.oauth?.token.User
143 if (!user) return fail() 138 if (!user) return fail()
@@ -173,6 +168,36 @@ async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoI
173 168
174// --------------------------------------------------------------------------- 169// ---------------------------------------------------------------------------
175 170
171async function checkCanAccessVideoStaticFiles (options: {
172 video: MVideo
173 req: Request
174 res: Response
175 paramId: string
176}) {
177 const { video, req, res } = options
178
179 if (res.locals.oauth?.token.User) {
180 return checkCanSeeVideo(options)
181 }
182
183 if (!video.hasPrivateStaticPath()) return true
184
185 const videoFileToken = req.query.videoFileToken
186 if (!videoFileToken) {
187 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
188 return false
189 }
190
191 if (VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) {
192 return true
193 }
194
195 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
196 return false
197}
198
199// ---------------------------------------------------------------------------
200
176function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { 201function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) {
177 // Retrieve the user who did the request 202 // Retrieve the user who did the request
178 if (onlyOwned && video.isOwned() === false) { 203 if (onlyOwned && video.isOwned() === false) {
@@ -220,6 +245,7 @@ export {
220 doesVideoExist, 245 doesVideoExist,
221 doesVideoFileOfVideoExist, 246 doesVideoFileOfVideoExist,
222 247
248 checkCanAccessVideoStaticFiles,
223 checkUserCanManageVideo, 249 checkUserCanManageVideo,
224 checkCanSeeVideo, 250 checkCanSeeVideo,
225 checkUserQuota 251 checkUserQuota
diff --git a/server/middlewares/validators/static.ts b/server/middlewares/validators/static.ts
new file mode 100644
index 000000000..13fde6dd1
--- /dev/null
+++ b/server/middlewares/validators/static.ts
@@ -0,0 +1,169 @@
1import express from 'express'
2import { query } from 'express-validator'
3import LRUCache from 'lru-cache'
4import { basename, dirname } from 'path'
5import { exists, isUUIDValid } from '@server/helpers/custom-validators/misc'
6import { logger } from '@server/helpers/logger'
7import { LRU_CACHE } from '@server/initializers/constants'
8import { VideoModel } from '@server/models/video/video'
9import { VideoFileModel } from '@server/models/video/video-file'
10import { MStreamingPlaylist, MVideoFile, MVideoThumbnail } from '@server/types/models'
11import { HttpStatusCode } from '@shared/models'
12import { areValidationErrors, checkCanAccessVideoStaticFiles } from './shared'
13
14type LRUValue = {
15 allowed: boolean
16 video?: MVideoThumbnail
17 file?: MVideoFile
18 playlist?: MStreamingPlaylist }
19
20const staticFileTokenBypass = new LRUCache<string, LRUValue>({
21 max: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.MAX_SIZE,
22 ttl: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.TTL
23})
24
25const ensureCanAccessVideoPrivateWebTorrentFiles = [
26 query('videoFileToken').optional().custom(exists),
27
28 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
29 if (areValidationErrors(req, res)) return
30
31 const token = extractTokenOrDie(req, res)
32 if (!token) return
33
34 const cacheKey = token + '-' + req.originalUrl
35
36 if (staticFileTokenBypass.has(cacheKey)) {
37 const { allowed, file, video } = staticFileTokenBypass.get(cacheKey)
38
39 if (allowed === true) {
40 res.locals.onlyVideo = video
41 res.locals.videoFile = file
42
43 return next()
44 }
45
46 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
47 }
48
49 const result = await isWebTorrentAllowed(req, res)
50
51 staticFileTokenBypass.set(cacheKey, result)
52
53 if (result.allowed !== true) return
54
55 res.locals.onlyVideo = result.video
56 res.locals.videoFile = result.file
57
58 return next()
59 }
60]
61
62const ensureCanAccessPrivateVideoHLSFiles = [
63 query('videoFileToken').optional().custom(exists),
64
65 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
66 if (areValidationErrors(req, res)) return
67
68 const videoUUID = basename(dirname(req.originalUrl))
69
70 if (!isUUIDValid(videoUUID)) {
71 logger.debug('Path does not contain valid video UUID to serve static file %s', req.originalUrl)
72
73 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
74 }
75
76 const token = extractTokenOrDie(req, res)
77 if (!token) return
78
79 const cacheKey = token + '-' + videoUUID
80
81 if (staticFileTokenBypass.has(cacheKey)) {
82 const { allowed, file, playlist, video } = staticFileTokenBypass.get(cacheKey)
83
84 if (allowed === true) {
85 res.locals.onlyVideo = video
86 res.locals.videoFile = file
87 res.locals.videoStreamingPlaylist = playlist
88
89 return next()
90 }
91
92 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
93 }
94
95 const result = await isHLSAllowed(req, res, videoUUID)
96
97 staticFileTokenBypass.set(cacheKey, result)
98
99 if (result.allowed !== true) return
100
101 res.locals.onlyVideo = result.video
102 res.locals.videoFile = result.file
103 res.locals.videoStreamingPlaylist = result.playlist
104
105 return next()
106 }
107]
108
109export {
110 ensureCanAccessVideoPrivateWebTorrentFiles,
111 ensureCanAccessPrivateVideoHLSFiles
112}
113
114// ---------------------------------------------------------------------------
115
116async function isWebTorrentAllowed (req: express.Request, res: express.Response) {
117 const filename = basename(req.path)
118
119 const file = await VideoFileModel.loadWithVideoByFilename(filename)
120 if (!file) {
121 logger.debug('Unknown static file %s to serve', req.originalUrl, { filename })
122
123 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
124 return { allowed: false }
125 }
126
127 const video = await VideoModel.load(file.getVideo().id)
128
129 return {
130 file,
131 video,
132 allowed: await checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
133 }
134}
135
136async function isHLSAllowed (req: express.Request, res: express.Response, videoUUID: string) {
137 const filename = basename(req.path)
138
139 const video = await VideoModel.loadWithFiles(videoUUID)
140
141 if (!video) {
142 logger.debug('Unknown static file %s to serve', req.originalUrl, { videoUUID })
143
144 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
145 return { allowed: false }
146 }
147
148 const file = await VideoFileModel.loadByFilename(filename)
149
150 return {
151 file,
152 video,
153 playlist: video.getHLSPlaylist(),
154 allowed: await checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
155 }
156}
157
158function extractTokenOrDie (req: express.Request, res: express.Response) {
159 const token = res.locals.oauth?.token.accessToken || req.query.videoFileToken
160
161 if (!token) {
162 return res.fail({
163 message: 'Bearer token is missing in headers or video file token is missing in URL query parameters',
164 status: HttpStatusCode.FORBIDDEN_403
165 })
166 }
167
168 return token
169}
diff --git a/server/middlewares/validators/themes.ts b/server/middlewares/validators/themes.ts
index c130801a0..080b3e096 100644
--- a/server/middlewares/validators/themes.ts
+++ b/server/middlewares/validators/themes.ts
@@ -2,7 +2,7 @@ import express from 'express'
2import { param } from 'express-validator' 2import { param } from 'express-validator'
3import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 3import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
4import { isSafePath } from '../../helpers/custom-validators/misc' 4import { isSafePath } from '../../helpers/custom-validators/misc'
5import { isPluginNameValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' 5import { isPluginNameValid, isPluginStableOrUnstableVersionValid } from '../../helpers/custom-validators/plugins'
6import { PluginManager } from '../../lib/plugins/plugin-manager' 6import { PluginManager } from '../../lib/plugins/plugin-manager'
7import { areValidationErrors } from './shared' 7import { areValidationErrors } from './shared'
8 8
@@ -10,7 +10,7 @@ const serveThemeCSSValidator = [
10 param('themeName') 10 param('themeName')
11 .custom(isPluginNameValid), 11 .custom(isPluginNameValid),
12 param('themeVersion') 12 param('themeVersion')
13 .custom(isPluginVersionValid), 13 .custom(isPluginStableOrUnstableVersionValid),
14 param('staticEndpoint') 14 param('staticEndpoint')
15 .custom(isSafePath), 15 .custom(isSafePath),
16 16
diff --git a/server/middlewares/validators/two-factor.ts b/server/middlewares/validators/two-factor.ts
new file mode 100644
index 000000000..106b579b5
--- /dev/null
+++ b/server/middlewares/validators/two-factor.ts
@@ -0,0 +1,81 @@
1import express from 'express'
2import { body, param } from 'express-validator'
3import { HttpStatusCode, UserRight } from '@shared/models'
4import { exists, isIdValid } from '../../helpers/custom-validators/misc'
5import { areValidationErrors, checkUserIdExist } from './shared'
6
7const requestOrConfirmTwoFactorValidator = [
8 param('id').custom(isIdValid),
9
10 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
11 if (areValidationErrors(req, res)) return
12
13 if (!await checkCanEnableOrDisableTwoFactor(req.params.id, res)) return
14
15 if (res.locals.user.otpSecret) {
16 return res.fail({
17 status: HttpStatusCode.BAD_REQUEST_400,
18 message: `Two factor is already enabled.`
19 })
20 }
21
22 return next()
23 }
24]
25
26const confirmTwoFactorValidator = [
27 body('requestToken').custom(exists),
28 body('otpToken').custom(exists),
29
30 (req: express.Request, res: express.Response, next: express.NextFunction) => {
31 if (areValidationErrors(req, res)) return
32
33 return next()
34 }
35]
36
37const disableTwoFactorValidator = [
38 param('id').custom(isIdValid),
39
40 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
41 if (areValidationErrors(req, res)) return
42
43 if (!await checkCanEnableOrDisableTwoFactor(req.params.id, res)) return
44
45 if (!res.locals.user.otpSecret) {
46 return res.fail({
47 status: HttpStatusCode.BAD_REQUEST_400,
48 message: `Two factor is already disabled.`
49 })
50 }
51
52 return next()
53 }
54]
55
56// ---------------------------------------------------------------------------
57
58export {
59 requestOrConfirmTwoFactorValidator,
60 confirmTwoFactorValidator,
61 disableTwoFactorValidator
62}
63
64// ---------------------------------------------------------------------------
65
66async function checkCanEnableOrDisableTwoFactor (userId: number | string, res: express.Response) {
67 const authUser = res.locals.oauth.token.user
68
69 if (!await checkUserIdExist(userId, res)) return
70
71 if (res.locals.user.id !== authUser.id && authUser.hasRight(UserRight.MANAGE_USERS) !== true) {
72 res.fail({
73 status: HttpStatusCode.FORBIDDEN_403,
74 message: `User ${authUser.username} does not have right to change two factor setting of this user.`
75 })
76
77 return false
78 }
79
80 return true
81}
diff --git a/server/middlewares/validators/user-subscriptions.ts b/server/middlewares/validators/user-subscriptions.ts
index d01043c17..d8d3fc28b 100644
--- a/server/middlewares/validators/user-subscriptions.ts
+++ b/server/middlewares/validators/user-subscriptions.ts
@@ -1,6 +1,6 @@
1import { arrayify } from '@shared/core-utils'
2import express from 'express' 1import express from 'express'
3import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { arrayify } from '@shared/core-utils'
4import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 4import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
5import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' 5import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
6import { WEBSERVER } from '../../initializers/constants' 6import { WEBSERVER } from '../../initializers/constants'
@@ -60,7 +60,7 @@ const userSubscriptionGetValidator = [
60 state: 'accepted' 60 state: 'accepted'
61 }) 61 })
62 62
63 if (!subscription || !subscription.ActorFollowing.VideoChannel) { 63 if (!subscription?.ActorFollowing.VideoChannel) {
64 return res.fail({ 64 return res.fail({
65 status: HttpStatusCode.NOT_FOUND_404, 65 status: HttpStatusCode.NOT_FOUND_404,
66 message: `Subscription ${req.params.uri} not found.` 66 message: `Subscription ${req.params.uri} not found.`
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 2de5265fb..50327b6ae 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -1,9 +1,9 @@
1import express from 'express' 1import express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { Hooks } from '@server/lib/plugins/hooks' 3import { Hooks } from '@server/lib/plugins/hooks'
4import { MUserDefault } from '@server/types/models' 4import { forceNumber } from '@shared/core-utils'
5import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models' 5import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models'
6import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 6import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
7import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 7import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
8import { 8import {
9 isUserAdminFlagsValid, 9 isUserAdminFlagsValid,
@@ -30,8 +30,15 @@ import { isThemeRegistered } from '../../lib/plugins/theme-utils'
30import { Redis } from '../../lib/redis' 30import { Redis } from '../../lib/redis'
31import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup' 31import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup'
32import { ActorModel } from '../../models/actor/actor' 32import { ActorModel } from '../../models/actor/actor'
33import { UserModel } from '../../models/user/user' 33import {
34import { areValidationErrors, doesVideoChannelIdExist, doesVideoExist, isValidVideoIdParam } from './shared' 34 areValidationErrors,
35 checkUserEmailExist,
36 checkUserIdExist,
37 checkUserNameOrEmailDoesNotAlreadyExist,
38 doesVideoChannelIdExist,
39 doesVideoExist,
40 isValidVideoIdParam
41} from './shared'
35 42
36const usersListValidator = [ 43const usersListValidator = [
37 query('blocked') 44 query('blocked')
@@ -411,6 +418,13 @@ const usersAskResetPasswordValidator = [
411 return res.status(HttpStatusCode.NO_CONTENT_204).end() 418 return res.status(HttpStatusCode.NO_CONTENT_204).end()
412 } 419 }
413 420
421 if (res.locals.user.pluginAuth) {
422 return res.fail({
423 status: HttpStatusCode.CONFLICT_409,
424 message: 'Cannot recover password of a user that uses a plugin authentication.'
425 })
426 }
427
414 return next() 428 return next()
415 } 429 }
416] 430]
@@ -428,7 +442,7 @@ const usersResetPasswordValidator = [
428 if (!await checkUserIdExist(req.params.id, res)) return 442 if (!await checkUserIdExist(req.params.id, res)) return
429 443
430 const user = res.locals.user 444 const user = res.locals.user
431 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id) 445 const redisVerificationString = await Redis.Instance.getResetPasswordVerificationString(user.id)
432 446
433 if (redisVerificationString !== req.body.verificationString) { 447 if (redisVerificationString !== req.body.verificationString) {
434 return res.fail({ 448 return res.fail({
@@ -454,6 +468,13 @@ const usersAskSendVerifyEmailValidator = [
454 return res.status(HttpStatusCode.NO_CONTENT_204).end() 468 return res.status(HttpStatusCode.NO_CONTENT_204).end()
455 } 469 }
456 470
471 if (res.locals.user.pluginAuth) {
472 return res.fail({
473 status: HttpStatusCode.CONFLICT_409,
474 message: 'Cannot ask verification email of a user that uses a plugin authentication.'
475 })
476 }
477
457 return next() 478 return next()
458 } 479 }
459] 480]
@@ -486,6 +507,41 @@ const usersVerifyEmailValidator = [
486 } 507 }
487] 508]
488 509
510const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Request) => number | string) => {
511 return [
512 body('currentPassword').optional().custom(exists),
513
514 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
515 if (areValidationErrors(req, res)) return
516
517 const user = res.locals.oauth.token.User
518 const isAdminOrModerator = user.role === UserRole.ADMINISTRATOR || user.role === UserRole.MODERATOR
519 const targetUserId = forceNumber(targetUserIdGetter(req))
520
521 // Admin/moderator action on another user, skip the password check
522 if (isAdminOrModerator && targetUserId !== user.id) {
523 return next()
524 }
525
526 if (!req.body.currentPassword) {
527 return res.fail({
528 status: HttpStatusCode.BAD_REQUEST_400,
529 message: 'currentPassword is missing'
530 })
531 }
532
533 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
534 return res.fail({
535 status: HttpStatusCode.FORBIDDEN_403,
536 message: 'currentPassword is invalid.'
537 })
538 }
539
540 return next()
541 }
542 ]
543}
544
489const userAutocompleteValidator = [ 545const userAutocompleteValidator = [
490 param('search') 546 param('search')
491 .isString() 547 .isString()
@@ -553,6 +609,7 @@ export {
553 usersUpdateValidator, 609 usersUpdateValidator,
554 usersUpdateMeValidator, 610 usersUpdateMeValidator,
555 usersVideoRatingValidator, 611 usersVideoRatingValidator,
612 usersCheckCurrentPasswordFactory,
556 ensureUserRegistrationAllowed, 613 ensureUserRegistrationAllowed,
557 ensureUserRegistrationAllowedForIP, 614 ensureUserRegistrationAllowedForIP,
558 usersGetValidator, 615 usersGetValidator,
@@ -566,55 +623,3 @@ export {
566 ensureCanModerateUser, 623 ensureCanModerateUser,
567 ensureCanManageChannelOrAccount 624 ensureCanManageChannelOrAccount
568} 625}
569
570// ---------------------------------------------------------------------------
571
572function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) {
573 const id = parseInt(idArg + '', 10)
574 return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res)
575}
576
577function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
578 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
579}
580
581async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
582 const user = await UserModel.loadByUsernameOrEmail(username, email)
583
584 if (user) {
585 res.fail({
586 status: HttpStatusCode.CONFLICT_409,
587 message: 'User with this username or email already exists.'
588 })
589 return false
590 }
591
592 const actor = await ActorModel.loadLocalByName(username)
593 if (actor) {
594 res.fail({
595 status: HttpStatusCode.CONFLICT_409,
596 message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.'
597 })
598 return false
599 }
600
601 return true
602}
603
604async function checkUserExist (finder: () => Promise<MUserDefault>, res: express.Response, abortResponse = true) {
605 const user = await finder()
606
607 if (!user) {
608 if (abortResponse === true) {
609 res.fail({
610 status: HttpStatusCode.NOT_FOUND_404,
611 message: 'User not found'
612 })
613 }
614
615 return false
616 }
617
618 res.locals.user = user
619 return true
620}
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 69062701b..133feb7bd 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -208,7 +208,8 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon
208 const acceptParameters = { 208 const acceptParameters = {
209 video, 209 video,
210 commentBody: req.body, 210 commentBody: req.body,
211 user: res.locals.oauth.token.User 211 user: res.locals.oauth.token.User,
212 req
212 } 213 }
213 214
214 let acceptedResult: AcceptResult 215 let acceptedResult: AcceptResult
@@ -234,7 +235,7 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon
234 235
235 res.fail({ 236 res.fail({
236 status: HttpStatusCode.FORBIDDEN_403, 237 status: HttpStatusCode.FORBIDDEN_403,
237 message: acceptedResult?.errorMessage || 'Refused local comment' 238 message: acceptedResult?.errorMessage || 'Comment has been rejected.'
238 }) 239 })
239 return false 240 return false
240 } 241 }
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
index f295b1885..72442aeb6 100644
--- a/server/middlewares/validators/videos/video-imports.ts
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -4,6 +4,7 @@ import { isResolvingToUnicastOnly } from '@server/helpers/dns'
4import { isPreImportVideoAccepted } from '@server/lib/moderation' 4import { isPreImportVideoAccepted } from '@server/lib/moderation'
5import { Hooks } from '@server/lib/plugins/hooks' 5import { Hooks } from '@server/lib/plugins/hooks'
6import { MUserAccountId, MVideoImport } from '@server/types/models' 6import { MUserAccountId, MVideoImport } from '@server/types/models'
7import { forceNumber } from '@shared/core-utils'
7import { HttpStatusCode, UserRight, VideoImportState } from '@shared/models' 8import { HttpStatusCode, UserRight, VideoImportState } from '@shared/models'
8import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model' 9import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model'
9import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' 10import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc'
@@ -130,7 +131,7 @@ const videoImportCancelValidator = [
130 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 131 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
131 if (areValidationErrors(req, res)) return 132 if (areValidationErrors(req, res)) return
132 133
133 if (!await doesVideoImportExist(parseInt(req.params.id), res)) return 134 if (!await doesVideoImportExist(forceNumber(req.params.id), res)) return
134 if (!checkUserCanManageImport(res.locals.oauth.token.user, res.locals.videoImport, res)) return 135 if (!checkUserCanManageImport(res.locals.oauth.token.user, res.locals.videoImport, res)) return
135 136
136 if (res.locals.videoImport.state !== VideoImportState.PENDING) { 137 if (res.locals.videoImport.state !== VideoImportState.PENDING) {
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index 6d4b8a6f1..e4b7e5c56 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -2,6 +2,7 @@ import express from 'express'
2import { body, param, query, ValidationChain } from 'express-validator' 2import { body, param, query, ValidationChain } from 'express-validator'
3import { ExpressPromiseHandler } from '@server/types/express-handler' 3import { ExpressPromiseHandler } from '@server/types/express-handler'
4import { MUserAccountId } from '@server/types/models' 4import { MUserAccountId } from '@server/types/models'
5import { forceNumber } from '@shared/core-utils'
5import { 6import {
6 HttpStatusCode, 7 HttpStatusCode,
7 UserRight, 8 UserRight,
@@ -258,7 +259,7 @@ const videoPlaylistElementAPGetValidator = [
258 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 259 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
259 if (areValidationErrors(req, res)) return 260 if (areValidationErrors(req, res)) return
260 261
261 const playlistElementId = parseInt(req.params.playlistElementId + '', 10) 262 const playlistElementId = forceNumber(req.params.playlistElementId)
262 const playlistId = req.params.playlistId 263 const playlistId = req.params.playlistId
263 264
264 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId) 265 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId)
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 7fd2b03d1..e29eb4a32 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -7,7 +7,7 @@ import { getServerActor } from '@server/models/application/application'
7import { ExpressPromiseHandler } from '@server/types/express-handler' 7import { ExpressPromiseHandler } from '@server/types/express-handler'
8import { MUserAccountId, MVideoFullLight } from '@server/types/models' 8import { MUserAccountId, MVideoFullLight } from '@server/types/models'
9import { arrayify, getAllPrivacies } from '@shared/core-utils' 9import { arrayify, getAllPrivacies } from '@shared/core-utils'
10import { HttpStatusCode, ServerErrorCode, UserRight, VideoInclude } from '@shared/models' 10import { HttpStatusCode, ServerErrorCode, UserRight, VideoInclude, VideoState } from '@shared/models'
11import { 11import {
12 exists, 12 exists,
13 isBooleanValid, 13 isBooleanValid,
@@ -48,6 +48,7 @@ import { Hooks } from '../../../lib/plugins/hooks'
48import { VideoModel } from '../../../models/video/video' 48import { VideoModel } from '../../../models/video/video'
49import { 49import {
50 areValidationErrors, 50 areValidationErrors,
51 checkCanAccessVideoStaticFiles,
51 checkCanSeeVideo, 52 checkCanSeeVideo,
52 checkUserCanManageVideo, 53 checkUserCanManageVideo,
53 checkUserQuota, 54 checkUserQuota,
@@ -232,6 +233,11 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([
232 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) 233 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
233 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req) 234 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req)
234 235
236 const video = getVideoWithAttributes(res)
237 if (req.body.privacy && video.isLive && video.state !== VideoState.WAITING_FOR_LIVE) {
238 return res.fail({ message: 'Cannot update privacy of a live that has already started' })
239 }
240
235 // Check if the user who did the request is able to update the video 241 // Check if the user who did the request is able to update the video
236 const user = res.locals.oauth.token.User 242 const user = res.locals.oauth.token.User
237 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) 243 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
@@ -271,10 +277,7 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
271 }) 277 })
272} 278}
273 279
274const videosCustomGetValidator = ( 280const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video' | 'only-immutable-attributes') => {
275 fetchType: 'for-api' | 'all' | 'only-video' | 'only-immutable-attributes',
276 authenticateInQuery = false
277) => {
278 return [ 281 return [
279 isValidVideoIdParam('id'), 282 isValidVideoIdParam('id'),
280 283
@@ -287,7 +290,7 @@ const videosCustomGetValidator = (
287 290
288 const video = getVideoWithAttributes(res) as MVideoFullLight 291 const video = getVideoWithAttributes(res) as MVideoFullLight
289 292
290 if (!await checkCanSeeVideo({ req, res, video, paramId: req.params.id, authenticateInQuery })) return 293 if (!await checkCanSeeVideo({ req, res, video, paramId: req.params.id })) return
291 294
292 return next() 295 return next()
293 } 296 }
@@ -295,7 +298,6 @@ const videosCustomGetValidator = (
295} 298}
296 299
297const videosGetValidator = videosCustomGetValidator('all') 300const videosGetValidator = videosCustomGetValidator('all')
298const videosDownloadValidator = videosCustomGetValidator('all', true)
299 301
300const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([ 302const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
301 isValidVideoIdParam('id'), 303 isValidVideoIdParam('id'),
@@ -311,6 +313,21 @@ const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
311 } 313 }
312]) 314])
313 315
316const videosDownloadValidator = [
317 isValidVideoIdParam('id'),
318
319 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
320 if (areValidationErrors(req, res)) return
321 if (!await doesVideoExist(req.params.id, res, 'all')) return
322
323 const video = getVideoWithAttributes(res)
324
325 if (!await checkCanAccessVideoStaticFiles({ req, res, video, paramId: req.params.id })) return
326
327 return next()
328 }
329]
330
314const videosRemoveValidator = [ 331const videosRemoveValidator = [
315 isValidVideoIdParam('id'), 332 isValidVideoIdParam('id'),
316 333
@@ -372,7 +389,7 @@ function getCommonVideoEditAttributes () {
372 .custom(isBooleanValid).withMessage('Should have a valid waitTranscoding boolean'), 389 .custom(isBooleanValid).withMessage('Should have a valid waitTranscoding boolean'),
373 body('privacy') 390 body('privacy')
374 .optional() 391 .optional()
375 .customSanitizer(toValueOrNull) 392 .customSanitizer(toIntOrNull)
376 .custom(isVideoPrivacyValid), 393 .custom(isVideoPrivacyValid),
377 body('description') 394 body('description')
378 .optional() 395 .optional()