diff options
Diffstat (limited to 'server/middlewares')
-rw-r--r-- | server/middlewares/activitypub.ts | 3 | ||||
-rw-r--r-- | server/middlewares/async.ts | 2 | ||||
-rw-r--r-- | server/middlewares/cache/cache.ts | 4 | ||||
-rw-r--r-- | server/middlewares/cache/shared/api-cache.ts | 50 | ||||
-rw-r--r-- | server/middlewares/error.ts | 10 | ||||
-rw-r--r-- | server/middlewares/user-right.ts | 3 | ||||
-rw-r--r-- | server/middlewares/validators/blocklist.ts | 29 | ||||
-rw-r--r-- | server/middlewares/validators/plugins.ts | 6 | ||||
-rw-r--r-- | server/middlewares/validators/shared/video-channels.ts | 7 | ||||
-rw-r--r-- | server/middlewares/validators/users.ts | 21 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-captions.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-channels.ts | 60 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-comments.ts | 3 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-files.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-playlists.ts | 14 | ||||
-rw-r--r-- | server/middlewares/validators/videos/videos.ts | 6 |
16 files changed, 113 insertions, 109 deletions
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts index 6ef90b275..86d3c1d6c 100644 --- a/server/middlewares/activitypub.ts +++ b/server/middlewares/activitypub.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { NextFunction, Request, Response } from 'express' | 1 | import { NextFunction, Request, Response } from 'express' |
2 | import { getAPId } from '@server/helpers/activitypub' | 2 | import { getAPId } from '@server/helpers/activitypub' |
3 | import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor' | 3 | import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor' |
4 | import { ActivityDelete, ActivityPubSignature } from '../../shared' | 4 | import { ActivityDelete, ActivityPubSignature, HttpStatusCode } from '@shared/models' |
5 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' | ||
6 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
7 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' | 6 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' |
8 | import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' | 7 | import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' |
diff --git a/server/middlewares/async.ts b/server/middlewares/async.ts index 3d6e38809..9d0193536 100644 --- a/server/middlewares/async.ts +++ b/server/middlewares/async.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { eachSeries } from 'async' | 1 | import { eachSeries } from 'async' |
2 | import { NextFunction, Request, RequestHandler, Response } from 'express' | 2 | import { NextFunction, Request, RequestHandler, Response } from 'express' |
3 | import { ValidationChain } from 'express-validator' | 3 | import { ValidationChain } from 'express-validator' |
4 | import { ExpressPromiseHandler } from '@server/types/express' | 4 | import { ExpressPromiseHandler } from '@server/types/express-handler' |
5 | import { retryTransactionWrapper } from '../helpers/database-utils' | 5 | import { retryTransactionWrapper } from '../helpers/database-utils' |
6 | 6 | ||
7 | // Syntactic sugar to avoid try/catch in express controllers | 7 | // Syntactic sugar to avoid try/catch in express controllers |
diff --git a/server/middlewares/cache/cache.ts b/server/middlewares/cache/cache.ts index 48162a0ae..e14160ba8 100644 --- a/server/middlewares/cache/cache.ts +++ b/server/middlewares/cache/cache.ts | |||
@@ -1,10 +1,6 @@ | |||
1 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' | 1 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' |
2 | import { Redis } from '../../lib/redis' | ||
3 | import { ApiCache, APICacheOptions } from './shared' | 2 | import { ApiCache, APICacheOptions } from './shared' |
4 | 3 | ||
5 | // Ensure Redis is initialized | ||
6 | Redis.Instance.init() | ||
7 | |||
8 | const defaultOptions: APICacheOptions = { | 4 | const defaultOptions: APICacheOptions = { |
9 | excludeStatus: [ | 5 | excludeStatus: [ |
10 | HttpStatusCode.FORBIDDEN_403, | 6 | HttpStatusCode.FORBIDDEN_403, |
diff --git a/server/middlewares/cache/shared/api-cache.ts b/server/middlewares/cache/shared/api-cache.ts index f8846dcfc..86c5095b5 100644 --- a/server/middlewares/cache/shared/api-cache.ts +++ b/server/middlewares/cache/shared/api-cache.ts | |||
@@ -7,6 +7,7 @@ import { isTestInstance, parseDurationToMs } from '@server/helpers/core-utils' | |||
7 | import { logger } from '@server/helpers/logger' | 7 | import { logger } from '@server/helpers/logger' |
8 | import { Redis } from '@server/lib/redis' | 8 | import { Redis } from '@server/lib/redis' |
9 | import { HttpStatusCode } from '@shared/models' | 9 | import { HttpStatusCode } from '@shared/models' |
10 | import { asyncMiddleware } from '@server/middlewares' | ||
10 | 11 | ||
11 | export interface APICacheOptions { | 12 | export interface APICacheOptions { |
12 | headerBlacklist?: string[] | 13 | headerBlacklist?: string[] |
@@ -40,24 +41,25 @@ export class ApiCache { | |||
40 | buildMiddleware (strDuration: string) { | 41 | buildMiddleware (strDuration: string) { |
41 | const duration = parseDurationToMs(strDuration) | 42 | const duration = parseDurationToMs(strDuration) |
42 | 43 | ||
43 | return (req: express.Request, res: express.Response, next: express.NextFunction) => { | 44 | return asyncMiddleware( |
44 | const key = Redis.Instance.getPrefix() + 'api-cache-' + req.originalUrl | 45 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
45 | const redis = Redis.Instance.getClient() | 46 | const key = Redis.Instance.getPrefix() + 'api-cache-' + req.originalUrl |
47 | const redis = Redis.Instance.getClient() | ||
46 | 48 | ||
47 | if (!redis.connected) return this.makeResponseCacheable(res, next, key, duration) | 49 | if (!Redis.Instance.isConnected()) return this.makeResponseCacheable(res, next, key, duration) |
48 | 50 | ||
49 | try { | 51 | try { |
50 | redis.hgetall(key, (err, obj) => { | 52 | const obj = await redis.hGetAll(key) |
51 | if (!err && obj && obj.response) { | 53 | if (obj?.response) { |
52 | return this.sendCachedResponse(req, res, JSON.parse(obj.response), duration) | 54 | return this.sendCachedResponse(req, res, JSON.parse(obj.response), duration) |
53 | } | 55 | } |
54 | 56 | ||
55 | return this.makeResponseCacheable(res, next, key, duration) | 57 | return this.makeResponseCacheable(res, next, key, duration) |
56 | }) | 58 | } catch (err) { |
57 | } catch (err) { | 59 | return this.makeResponseCacheable(res, next, key, duration) |
58 | return this.makeResponseCacheable(res, next, key, duration) | 60 | } |
59 | } | 61 | } |
60 | } | 62 | ) |
61 | } | 63 | } |
62 | 64 | ||
63 | private shouldCacheResponse (response: express.Response) { | 65 | private shouldCacheResponse (response: express.Response) { |
@@ -93,21 +95,22 @@ export class ApiCache { | |||
93 | } as CacheObject | 95 | } as CacheObject |
94 | } | 96 | } |
95 | 97 | ||
96 | private cacheResponse (key: string, value: object, duration: number) { | 98 | private async cacheResponse (key: string, value: object, duration: number) { |
97 | const redis = Redis.Instance.getClient() | 99 | const redis = Redis.Instance.getClient() |
98 | 100 | ||
99 | if (redis.connected) { | 101 | if (Redis.Instance.isConnected()) { |
100 | try { | 102 | await Promise.all([ |
101 | redis.hset(key, 'response', JSON.stringify(value)) | 103 | redis.hSet(key, 'response', JSON.stringify(value)), |
102 | redis.hset(key, 'duration', duration + '') | 104 | redis.hSet(key, 'duration', duration + ''), |
103 | redis.expire(key, duration / 1000) | 105 | redis.expire(key, duration / 1000) |
104 | } catch (err) { | 106 | ]) |
105 | logger.error('Cannot set cache in redis.', { err }) | ||
106 | } | ||
107 | } | 107 | } |
108 | 108 | ||
109 | // add automatic cache clearing from duration, includes max limit on setTimeout | 109 | // add automatic cache clearing from duration, includes max limit on setTimeout |
110 | this.timers[key] = setTimeout(() => this.clear(key), Math.min(duration, 2147483647)) | 110 | this.timers[key] = setTimeout(() => { |
111 | this.clear(key) | ||
112 | .catch(err => logger.error('Cannot clear Redis key %s.', key, { err })) | ||
113 | }, Math.min(duration, 2147483647)) | ||
111 | } | 114 | } |
112 | 115 | ||
113 | private accumulateContent (res: express.Response, content: any) { | 116 | private accumulateContent (res: express.Response, content: any) { |
@@ -184,6 +187,7 @@ export class ApiCache { | |||
184 | encoding | 187 | encoding |
185 | ) | 188 | ) |
186 | self.cacheResponse(key, cacheObject, duration) | 189 | self.cacheResponse(key, cacheObject, duration) |
190 | .catch(err => logger.error('Cannot cache response', { err })) | ||
187 | } | 191 | } |
188 | } | 192 | } |
189 | 193 | ||
@@ -235,7 +239,7 @@ export class ApiCache { | |||
235 | return response.end(data, cacheObject.encoding) | 239 | return response.end(data, cacheObject.encoding) |
236 | } | 240 | } |
237 | 241 | ||
238 | private clear (target: string) { | 242 | private async clear (target: string) { |
239 | const redis = Redis.Instance.getClient() | 243 | const redis = Redis.Instance.getClient() |
240 | 244 | ||
241 | if (target) { | 245 | if (target) { |
@@ -243,7 +247,7 @@ export class ApiCache { | |||
243 | delete this.timers[target] | 247 | delete this.timers[target] |
244 | 248 | ||
245 | try { | 249 | try { |
246 | redis.del(target) | 250 | await redis.del(target) |
247 | } catch (err) { | 251 | } catch (err) { |
248 | logger.error('Cannot delete %s in redis cache.', target, { err }) | 252 | logger.error('Cannot delete %s in redis cache.', target, { err }) |
249 | } | 253 | } |
@@ -255,7 +259,7 @@ export class ApiCache { | |||
255 | delete this.timers[key] | 259 | delete this.timers[key] |
256 | 260 | ||
257 | try { | 261 | try { |
258 | redis.del(key) | 262 | await redis.del(key) |
259 | } catch (err) { | 263 | } catch (err) { |
260 | logger.error('Cannot delete %s in redis cache.', key, { err }) | 264 | logger.error('Cannot delete %s in redis cache.', key, { err }) |
261 | } | 265 | } |
diff --git a/server/middlewares/error.ts b/server/middlewares/error.ts index 6c52ce7bd..34c87a26d 100644 --- a/server/middlewares/error.ts +++ b/server/middlewares/error.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { ProblemDocument, ProblemDocumentExtension } from 'http-problem-details' | 2 | import { ProblemDocument, ProblemDocumentExtension } from 'http-problem-details' |
3 | import { logger } from '@server/helpers/logger' | ||
3 | import { HttpStatusCode } from '@shared/models' | 4 | import { HttpStatusCode } from '@shared/models' |
4 | 5 | ||
5 | function apiFailMiddleware (req: express.Request, res: express.Response, next: express.NextFunction) { | 6 | function apiFailMiddleware (req: express.Request, res: express.Response, next: express.NextFunction) { |
@@ -18,7 +19,8 @@ function apiFailMiddleware (req: express.Request, res: express.Response, next: e | |||
18 | 19 | ||
19 | res.status(status) | 20 | res.status(status) |
20 | res.setHeader('Content-Type', 'application/problem+json') | 21 | res.setHeader('Content-Type', 'application/problem+json') |
21 | res.json(new ProblemDocument({ | 22 | |
23 | const json = new ProblemDocument({ | ||
22 | status, | 24 | status, |
23 | title, | 25 | title, |
24 | instance, | 26 | instance, |
@@ -28,7 +30,11 @@ function apiFailMiddleware (req: express.Request, res: express.Response, next: e | |||
28 | type: type | 30 | type: type |
29 | ? `https://docs.joinpeertube.org/api-rest-reference.html#section/Errors/${type}` | 31 | ? `https://docs.joinpeertube.org/api-rest-reference.html#section/Errors/${type}` |
30 | : undefined | 32 | : undefined |
31 | }, extension)) | 33 | }, extension) |
34 | |||
35 | logger.debug('Bad HTTP request.', { json }) | ||
36 | |||
37 | res.json(json) | ||
32 | } | 38 | } |
33 | 39 | ||
34 | if (next) next() | 40 | if (next) next() |
diff --git a/server/middlewares/user-right.ts b/server/middlewares/user-right.ts index ea95b16c2..7d53e8341 100644 --- a/server/middlewares/user-right.ts +++ b/server/middlewares/user-right.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { UserRight } from '../../shared' | 2 | import { HttpStatusCode, UserRight } from '@shared/models' |
3 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' | ||
4 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
5 | 4 | ||
6 | function ensureUserHasRight (userRight: UserRight) { | 5 | function ensureUserHasRight (userRight: UserRight) { |
diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts index b7749e204..12980ced4 100644 --- a/server/middlewares/validators/blocklist.ts +++ b/server/middlewares/validators/blocklist.ts | |||
@@ -1,8 +1,10 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { body, param } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { areValidActorHandles } from '@server/helpers/custom-validators/activitypub/actor' | ||
4 | import { toArray } from '@server/helpers/custom-validators/misc' | ||
3 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
4 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' | 6 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' |
5 | import { isHostValid } from '../../helpers/custom-validators/servers' | 7 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' |
6 | import { logger } from '../../helpers/logger' | 8 | import { logger } from '../../helpers/logger' |
7 | import { WEBSERVER } from '../../initializers/constants' | 9 | import { WEBSERVER } from '../../initializers/constants' |
8 | import { AccountBlocklistModel } from '../../models/account/account-blocklist' | 10 | import { AccountBlocklistModel } from '../../models/account/account-blocklist' |
@@ -123,6 +125,26 @@ const unblockServerByServerValidator = [ | |||
123 | } | 125 | } |
124 | ] | 126 | ] |
125 | 127 | ||
128 | const blocklistStatusValidator = [ | ||
129 | query('hosts') | ||
130 | .optional() | ||
131 | .customSanitizer(toArray) | ||
132 | .custom(isEachUniqueHostValid).withMessage('Should have a valid hosts array'), | ||
133 | |||
134 | query('accounts') | ||
135 | .optional() | ||
136 | .customSanitizer(toArray) | ||
137 | .custom(areValidActorHandles).withMessage('Should have a valid accounts array'), | ||
138 | |||
139 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
140 | logger.debug('Checking blocklistStatusValidator parameters', { query: req.query }) | ||
141 | |||
142 | if (areValidationErrors(req, res)) return | ||
143 | |||
144 | return next() | ||
145 | } | ||
146 | ] | ||
147 | |||
126 | // --------------------------------------------------------------------------- | 148 | // --------------------------------------------------------------------------- |
127 | 149 | ||
128 | export { | 150 | export { |
@@ -131,7 +153,8 @@ export { | |||
131 | unblockAccountByAccountValidator, | 153 | unblockAccountByAccountValidator, |
132 | unblockServerByAccountValidator, | 154 | unblockServerByAccountValidator, |
133 | unblockAccountByServerValidator, | 155 | unblockAccountByServerValidator, |
134 | unblockServerByServerValidator | 156 | unblockServerByServerValidator, |
157 | blocklistStatusValidator | ||
135 | } | 158 | } |
136 | 159 | ||
137 | // --------------------------------------------------------------------------- | 160 | // --------------------------------------------------------------------------- |
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index 21171af23..c1e9ebefb 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -116,6 +116,9 @@ const installOrUpdatePluginValidator = [ | |||
116 | body('npmName') | 116 | body('npmName') |
117 | .optional() | 117 | .optional() |
118 | .custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), | 118 | .custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), |
119 | body('pluginVersion') | ||
120 | .optional() | ||
121 | .custom(isPluginVersionValid).withMessage('Should have a valid plugin version'), | ||
119 | body('path') | 122 | body('path') |
120 | .optional() | 123 | .optional() |
121 | .custom(isSafePath).withMessage('Should have a valid safe path'), | 124 | .custom(isSafePath).withMessage('Should have a valid safe path'), |
@@ -129,6 +132,9 @@ const installOrUpdatePluginValidator = [ | |||
129 | if (!body.path && !body.npmName) { | 132 | if (!body.path && !body.npmName) { |
130 | return res.fail({ message: 'Should have either a npmName or a path' }) | 133 | return res.fail({ message: 'Should have either a npmName or a path' }) |
131 | } | 134 | } |
135 | if (body.pluginVersion && !body.npmName) { | ||
136 | return res.fail({ message: 'Should have a npmName when specifying a pluginVersion' }) | ||
137 | } | ||
132 | 138 | ||
133 | return next() | 139 | return next() |
134 | } | 140 | } |
diff --git a/server/middlewares/validators/shared/video-channels.ts b/server/middlewares/validators/shared/video-channels.ts index 7c0c89267..bed9f5dbe 100644 --- a/server/middlewares/validators/shared/video-channels.ts +++ b/server/middlewares/validators/shared/video-channels.ts | |||
@@ -3,12 +3,6 @@ import { VideoChannelModel } from '@server/models/video/video-channel' | |||
3 | import { MChannelBannerAccountDefault } from '@server/types/models' | 3 | import { MChannelBannerAccountDefault } from '@server/types/models' |
4 | import { HttpStatusCode } from '@shared/models' | 4 | import { HttpStatusCode } from '@shared/models' |
5 | 5 | ||
6 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { | ||
7 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | ||
8 | |||
9 | return processVideoChannelExist(videoChannel, res) | ||
10 | } | ||
11 | |||
12 | async function doesVideoChannelIdExist (id: number, res: express.Response) { | 6 | async function doesVideoChannelIdExist (id: number, res: express.Response) { |
13 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) | 7 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) |
14 | 8 | ||
@@ -24,7 +18,6 @@ async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: e | |||
24 | // --------------------------------------------------------------------------- | 18 | // --------------------------------------------------------------------------- |
25 | 19 | ||
26 | export { | 20 | export { |
27 | doesLocalVideoChannelNameExist, | ||
28 | doesVideoChannelIdExist, | 21 | doesVideoChannelIdExist, |
29 | doesVideoChannelNameWithHostExist | 22 | doesVideoChannelNameWithHostExist |
30 | } | 23 | } |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 33b31d54b..bc6007c6d 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -3,7 +3,7 @@ import { body, param, query } from 'express-validator' | |||
3 | import { omit } from 'lodash' | 3 | import { omit } from 'lodash' |
4 | import { Hooks } from '@server/lib/plugins/hooks' | 4 | import { Hooks } from '@server/lib/plugins/hooks' |
5 | import { MUserDefault } from '@server/types/models' | 5 | import { MUserDefault } from '@server/types/models' |
6 | import { HttpStatusCode, UserRegister, UserRole } from '@shared/models' | 6 | import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models' |
7 | import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 7 | import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
8 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | 8 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' |
9 | import { | 9 | import { |
@@ -15,6 +15,7 @@ import { | |||
15 | isUserDisplayNameValid, | 15 | isUserDisplayNameValid, |
16 | isUserNoModal, | 16 | isUserNoModal, |
17 | isUserNSFWPolicyValid, | 17 | isUserNSFWPolicyValid, |
18 | isUserP2PEnabledValid, | ||
18 | isUserPasswordValid, | 19 | isUserPasswordValid, |
19 | isUserPasswordValidOrEmpty, | 20 | isUserPasswordValidOrEmpty, |
20 | isUserRoleValid, | 21 | isUserRoleValid, |
@@ -239,6 +240,9 @@ const usersUpdateMeValidator = [ | |||
239 | body('autoPlayVideo') | 240 | body('autoPlayVideo') |
240 | .optional() | 241 | .optional() |
241 | .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'), | 242 | .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'), |
243 | body('p2pEnabled') | ||
244 | .optional() | ||
245 | .custom(isUserP2PEnabledValid).withMessage('Should have a valid p2p enabled boolean'), | ||
242 | body('videoLanguages') | 246 | body('videoLanguages') |
243 | .optional() | 247 | .optional() |
244 | .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'), | 248 | .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'), |
@@ -490,14 +494,17 @@ const ensureAuthUserOwnsAccountValidator = [ | |||
490 | } | 494 | } |
491 | ] | 495 | ] |
492 | 496 | ||
493 | const ensureAuthUserOwnsChannelValidator = [ | 497 | const ensureCanManageChannel = [ |
494 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 498 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
495 | const user = res.locals.oauth.token.User | 499 | const user = res.locals.oauth.token.user |
500 | const isUserOwner = res.locals.videoChannel.Account.userId === user.id | ||
501 | |||
502 | if (!isUserOwner && user.hasRight(UserRight.MANAGE_ANY_VIDEO_CHANNEL) === false) { | ||
503 | const message = `User ${user.username} does not have right to manage channel ${req.params.nameWithHost}.` | ||
496 | 504 | ||
497 | if (res.locals.videoChannel.Account.userId !== user.id) { | ||
498 | return res.fail({ | 505 | return res.fail({ |
499 | status: HttpStatusCode.FORBIDDEN_403, | 506 | status: HttpStatusCode.FORBIDDEN_403, |
500 | message: 'Only owner of this video channel can access this ressource' | 507 | message |
501 | }) | 508 | }) |
502 | } | 509 | } |
503 | 510 | ||
@@ -542,8 +549,8 @@ export { | |||
542 | usersVerifyEmailValidator, | 549 | usersVerifyEmailValidator, |
543 | userAutocompleteValidator, | 550 | userAutocompleteValidator, |
544 | ensureAuthUserOwnsAccountValidator, | 551 | ensureAuthUserOwnsAccountValidator, |
545 | ensureAuthUserOwnsChannelValidator, | 552 | ensureCanManageUser, |
546 | ensureCanManageUser | 553 | ensureCanManageChannel |
547 | } | 554 | } |
548 | 555 | ||
549 | // --------------------------------------------------------------------------- | 556 | // --------------------------------------------------------------------------- |
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts index 4fc4c8ec5..a399871e1 100644 --- a/server/middlewares/validators/videos/video-captions.ts +++ b/server/middlewares/validators/videos/video-captions.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { body, param } from 'express-validator' | 2 | import { body, param } from 'express-validator' |
3 | import { HttpStatusCode, UserRight } from '../../../../shared' | 3 | import { HttpStatusCode, UserRight } from '@shared/models' |
4 | import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' | 4 | import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' |
5 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 5 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
6 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index edce48c7f..3bfdebbb1 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { MChannelAccountDefault, MUser } from '@server/types/models' | 3 | import { CONFIG } from '@server/initializers/config' |
4 | import { UserRight } from '../../../../shared' | 4 | import { MChannelAccountDefault } from '@server/types/models' |
5 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | 5 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' |
6 | import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' | 6 | import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' |
7 | import { | 7 | import { |
@@ -13,8 +13,7 @@ import { | |||
13 | import { logger } from '../../../helpers/logger' | 13 | import { logger } from '../../../helpers/logger' |
14 | import { ActorModel } from '../../../models/actor/actor' | 14 | import { ActorModel } from '../../../models/actor/actor' |
15 | import { VideoChannelModel } from '../../../models/video/video-channel' | 15 | import { VideoChannelModel } from '../../../models/video/video-channel' |
16 | import { areValidationErrors, doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../shared' | 16 | import { areValidationErrors, doesVideoChannelNameWithHostExist } from '../shared' |
17 | import { CONFIG } from '@server/initializers/config' | ||
18 | 17 | ||
19 | const videoChannelsAddValidator = [ | 18 | const videoChannelsAddValidator = [ |
20 | body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'), | 19 | body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'), |
@@ -71,16 +70,10 @@ const videoChannelsUpdateValidator = [ | |||
71 | ] | 70 | ] |
72 | 71 | ||
73 | const videoChannelsRemoveValidator = [ | 72 | const videoChannelsRemoveValidator = [ |
74 | param('nameWithHost').exists().withMessage('Should have an video channel name with host'), | ||
75 | |||
76 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 73 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
77 | logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) | 74 | logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) |
78 | 75 | ||
79 | if (areValidationErrors(req, res)) return | 76 | if (!await checkVideoChannelIsNotTheLastOne(res.locals.videoChannel, res)) return |
80 | if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return | ||
81 | |||
82 | if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return | ||
83 | if (!await checkVideoChannelIsNotTheLastOne(res)) return | ||
84 | 77 | ||
85 | return next() | 78 | return next() |
86 | } | 79 | } |
@@ -100,14 +93,14 @@ const videoChannelsNameWithHostValidator = [ | |||
100 | } | 93 | } |
101 | ] | 94 | ] |
102 | 95 | ||
103 | const localVideoChannelValidator = [ | 96 | const ensureIsLocalChannel = [ |
104 | param('name').custom(isVideoChannelDisplayNameValid).withMessage('Should have a valid video channel name'), | 97 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
105 | 98 | if (res.locals.videoChannel.Actor.isOwned() === false) { | |
106 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 99 | return res.fail({ |
107 | logger.debug('Checking localVideoChannelValidator parameters', { parameters: req.params }) | 100 | status: HttpStatusCode.FORBIDDEN_403, |
108 | 101 | message: 'This channel is not owned.' | |
109 | if (areValidationErrors(req, res)) return | 102 | }) |
110 | if (!await doesLocalVideoChannelNameExist(req.params.name, res)) return | 103 | } |
111 | 104 | ||
112 | return next() | 105 | return next() |
113 | } | 106 | } |
@@ -144,38 +137,15 @@ export { | |||
144 | videoChannelsUpdateValidator, | 137 | videoChannelsUpdateValidator, |
145 | videoChannelsRemoveValidator, | 138 | videoChannelsRemoveValidator, |
146 | videoChannelsNameWithHostValidator, | 139 | videoChannelsNameWithHostValidator, |
140 | ensureIsLocalChannel, | ||
147 | videoChannelsListValidator, | 141 | videoChannelsListValidator, |
148 | localVideoChannelValidator, | ||
149 | videoChannelStatsValidator | 142 | videoChannelStatsValidator |
150 | } | 143 | } |
151 | 144 | ||
152 | // --------------------------------------------------------------------------- | 145 | // --------------------------------------------------------------------------- |
153 | 146 | ||
154 | function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) { | 147 | async function checkVideoChannelIsNotTheLastOne (videoChannel: MChannelAccountDefault, res: express.Response) { |
155 | if (videoChannel.Actor.isOwned() === false) { | 148 | const count = await VideoChannelModel.countByAccount(videoChannel.Account.id) |
156 | res.fail({ | ||
157 | status: HttpStatusCode.FORBIDDEN_403, | ||
158 | message: 'Cannot remove video channel of another server.' | ||
159 | }) | ||
160 | return false | ||
161 | } | ||
162 | |||
163 | // Check if the user can delete the video channel | ||
164 | // The user can delete it if s/he is an admin | ||
165 | // Or if s/he is the video channel's account | ||
166 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) { | ||
167 | res.fail({ | ||
168 | status: HttpStatusCode.FORBIDDEN_403, | ||
169 | message: 'Cannot remove video channel of another user' | ||
170 | }) | ||
171 | return false | ||
172 | } | ||
173 | |||
174 | return true | ||
175 | } | ||
176 | |||
177 | async function checkVideoChannelIsNotTheLastOne (res: express.Response) { | ||
178 | const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) | ||
179 | 149 | ||
180 | if (count <= 1) { | 150 | if (count <= 1) { |
181 | res.fail({ | 151 | res.fail({ |
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts index 3ea8bdcbb..665fb04c8 100644 --- a/server/middlewares/validators/videos/video-comments.ts +++ b/server/middlewares/validators/videos/video-comments.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { MUserAccountUrl } from '@server/types/models' | 3 | import { MUserAccountUrl } from '@server/types/models' |
4 | import { UserRight } from '../../../../shared' | 4 | import { HttpStatusCode, UserRight } from '@shared/models' |
5 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | ||
6 | import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' | 5 | import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' |
7 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' | 6 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' |
8 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
diff --git a/server/middlewares/validators/videos/video-files.ts b/server/middlewares/validators/videos/video-files.ts index c1fa77502..35b0ac757 100644 --- a/server/middlewares/validators/videos/video-files.ts +++ b/server/middlewares/validators/videos/video-files.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { MVideo } from '@server/types/models' | 2 | import { MVideo } from '@server/types/models' |
3 | import { HttpStatusCode } from '../../../../shared' | 3 | import { HttpStatusCode } from '@shared/models' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' | 5 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' |
6 | 6 | ||
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index 8f5c75fd5..f5fee845e 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -1,11 +1,15 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { body, param, query, ValidationChain } from 'express-validator' | 2 | import { body, param, query, ValidationChain } from 'express-validator' |
3 | import { ExpressPromiseHandler } from '@server/types/express' | 3 | import { ExpressPromiseHandler } from '@server/types/express-handler' |
4 | import { MUserAccountId } from '@server/types/models' | 4 | import { MUserAccountId } from '@server/types/models' |
5 | import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared' | 5 | import { |
6 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | 6 | HttpStatusCode, |
7 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 7 | UserRight, |
8 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' | 8 | VideoPlaylistCreate, |
9 | VideoPlaylistPrivacy, | ||
10 | VideoPlaylistType, | ||
11 | VideoPlaylistUpdate | ||
12 | } from '@shared/models' | ||
9 | import { | 13 | import { |
10 | isArrayOf, | 14 | isArrayOf, |
11 | isIdOrUUIDValid, | 15 | isIdOrUUIDValid, |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index 782f495e8..3a1a905f3 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -5,12 +5,10 @@ import { getResumableUploadPath } from '@server/helpers/upload' | |||
5 | import { Redis } from '@server/lib/redis' | 5 | import { Redis } from '@server/lib/redis' |
6 | import { isAbleToUploadVideo } from '@server/lib/user' | 6 | import { isAbleToUploadVideo } from '@server/lib/user' |
7 | import { getServerActor } from '@server/models/application/application' | 7 | import { getServerActor } from '@server/models/application/application' |
8 | import { ExpressPromiseHandler } from '@server/types/express' | 8 | import { ExpressPromiseHandler } from '@server/types/express-handler' |
9 | import { MUserAccountId, MVideoFullLight } from '@server/types/models' | 9 | import { MUserAccountId, MVideoFullLight } from '@server/types/models' |
10 | import { getAllPrivacies } from '@shared/core-utils' | 10 | import { getAllPrivacies } from '@shared/core-utils' |
11 | import { VideoInclude } from '@shared/models' | 11 | import { HttpStatusCode, ServerErrorCode, UserRight, VideoInclude, VideoPrivacy } from '@shared/models' |
12 | import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared' | ||
13 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | ||
14 | import { | 12 | import { |
15 | exists, | 13 | exists, |
16 | isBooleanValid, | 14 | isBooleanValid, |