aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares
diff options
context:
space:
mode:
Diffstat (limited to 'server/middlewares')
-rw-r--r--server/middlewares/activitypub.ts14
-rw-r--r--server/middlewares/csp.ts30
-rw-r--r--server/middlewares/dnt.ts3
-rw-r--r--server/middlewares/oauth.ts1
-rw-r--r--server/middlewares/sort.ts25
-rw-r--r--server/middlewares/validators/avatar.ts4
-rw-r--r--server/middlewares/validators/config.ts6
-rw-r--r--server/middlewares/validators/feeds.ts8
-rw-r--r--server/middlewares/validators/redundancy.ts74
-rw-r--r--server/middlewares/validators/server.ts5
-rw-r--r--server/middlewares/validators/sort.ts3
-rw-r--r--server/middlewares/validators/users.ts10
-rw-r--r--server/middlewares/validators/videos/video-captions.ts10
-rw-r--r--server/middlewares/validators/videos/video-comments.ts2
-rw-r--r--server/middlewares/validators/videos/video-imports.ts9
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts9
-rw-r--r--server/middlewares/validators/videos/video-rates.ts6
-rw-r--r--server/middlewares/validators/videos/videos.ts36
-rw-r--r--server/middlewares/validators/webfinger.ts5
19 files changed, 173 insertions, 87 deletions
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index c6d8466ac..ab7d04d25 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -1,10 +1,12 @@
1import { NextFunction, Request, Response } from 'express' 1import { NextFunction, Request, Response } from 'express'
2import { ActivityPubSignature } from '../../shared' 2import { ActivityDelete, ActivityPubSignature } from '../../shared'
3import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
4import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' 4import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto'
5import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' 5import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants'
6import { getOrCreateActorAndServerAndModel } from '../lib/activitypub' 6import { getOrCreateActorAndServerAndModel } from '../lib/activitypub'
7import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger' 7import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger'
8import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor'
9import { getAPId } from '@server/helpers/activitypub'
8 10
9async function checkSignature (req: Request, res: Response, next: NextFunction) { 11async function checkSignature (req: Request, res: Response, next: NextFunction) {
10 try { 12 try {
@@ -15,7 +17,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
15 17
16 // Forwarded activity 18 // Forwarded activity
17 const bodyActor = req.body.actor 19 const bodyActor = req.body.actor
18 const bodyActorId = bodyActor && bodyActor.id ? bodyActor.id : bodyActor 20 const bodyActorId = getAPId(bodyActor)
19 if (bodyActorId && bodyActorId !== actor.url) { 21 if (bodyActorId && bodyActorId !== actor.url) {
20 const jsonLDSignatureChecked = await checkJsonLDSignature(req, res) 22 const jsonLDSignatureChecked = await checkJsonLDSignature(req, res)
21 if (jsonLDSignatureChecked !== true) return 23 if (jsonLDSignatureChecked !== true) return
@@ -23,7 +25,13 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
23 25
24 return next() 26 return next()
25 } catch (err) { 27 } catch (err) {
26 logger.error('Error in ActivityPub signature checker.', err) 28 const activity: ActivityDelete = req.body
29 if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) {
30 logger.debug('Handling signature error on actor delete activity', { err })
31 return res.sendStatus(204)
32 }
33
34 logger.warn('Error in ActivityPub signature checker.', { err })
27 return res.sendStatus(403) 35 return res.sendStatus(403)
28 } 36 }
29} 37}
diff --git a/server/middlewares/csp.ts b/server/middlewares/csp.ts
index d11d70790..f5de69603 100644
--- a/server/middlewares/csp.ts
+++ b/server/middlewares/csp.ts
@@ -3,20 +3,20 @@ import { CONFIG } from '../initializers/config'
3 3
4const baseDirectives = Object.assign({}, 4const baseDirectives = Object.assign({},
5 { 5 {
6 defaultSrc: ["'none'"], // by default, not specifying default-src = '*' 6 defaultSrc: [ '\'none\'' ], // by default, not specifying default-src = '*'
7 connectSrc: ['*', 'data:'], 7 connectSrc: [ '*', 'data:' ],
8 mediaSrc: ["'self'", 'https:', 'blob:'], 8 mediaSrc: [ '\'self\'', 'https:', 'blob:' ],
9 fontSrc: ["'self'", 'data:'], 9 fontSrc: [ '\'self\'', 'data:' ],
10 imgSrc: ["'self'", 'data:', 'blob:'], 10 imgSrc: [ '\'self\'', 'data:', 'blob:' ],
11 scriptSrc: ["'self' 'unsafe-inline' 'unsafe-eval'", 'blob:'], 11 scriptSrc: [ '\'self\' \'unsafe-inline\' \'unsafe-eval\'', 'blob:' ],
12 styleSrc: ["'self' 'unsafe-inline'"], 12 styleSrc: [ '\'self\' \'unsafe-inline\'' ],
13 objectSrc: ["'none'"], // only define to allow plugins, else let defaultSrc 'none' block it 13 objectSrc: [ '\'none\'' ], // only define to allow plugins, else let defaultSrc 'none' block it
14 formAction: ["'self'"], 14 formAction: [ '\'self\'' ],
15 frameAncestors: ["'none'"], 15 frameAncestors: [ '\'none\'' ],
16 baseUri: ["'self'"], 16 baseUri: [ '\'self\'' ],
17 manifestSrc: ["'self'"], 17 manifestSrc: [ '\'self\'' ],
18 frameSrc: ["'self'"], // instead of deprecated child-src / self because of test-embed 18 frameSrc: [ '\'self\'' ], // instead of deprecated child-src / self because of test-embed
19 workerSrc: ["'self'", 'blob:'] // instead of deprecated child-src 19 workerSrc: [ '\'self\'', 'blob:' ] // instead of deprecated child-src
20 }, 20 },
21 CONFIG.CSP.REPORT_URI ? { reportUri: CONFIG.CSP.REPORT_URI } : {}, 21 CONFIG.CSP.REPORT_URI ? { reportUri: CONFIG.CSP.REPORT_URI } : {},
22 CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {} 22 CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {}
@@ -29,7 +29,7 @@ const baseCSP = helmet.contentSecurityPolicy({
29}) 29})
30 30
31const embedCSP = helmet.contentSecurityPolicy({ 31const embedCSP = helmet.contentSecurityPolicy({
32 directives: Object.assign({}, baseDirectives, { frameAncestors: ['*'] }), 32 directives: Object.assign({}, baseDirectives, { frameAncestors: [ '*' ] }),
33 browserSniff: false, // assumes a modern browser, but allows CDN in front 33 browserSniff: false, // assumes a modern browser, but allows CDN in front
34 reportOnly: CONFIG.CSP.REPORT_ONLY 34 reportOnly: CONFIG.CSP.REPORT_ONLY
35}) 35})
diff --git a/server/middlewares/dnt.ts b/server/middlewares/dnt.ts
index 607def855..dd88005dd 100644
--- a/server/middlewares/dnt.ts
+++ b/server/middlewares/dnt.ts
@@ -1,6 +1,3 @@
1import * as ipaddr from 'ipaddr.js'
2import { format } from 'util'
3
4const advertiseDoNotTrack = (_, res, next) => { 1const advertiseDoNotTrack = (_, res, next) => {
5 res.setHeader('Tk', 'N') 2 res.setHeader('Tk', 'N')
6 return next() 3 return next()
diff --git a/server/middlewares/oauth.ts b/server/middlewares/oauth.ts
index 749f5cccd..9eef03bb4 100644
--- a/server/middlewares/oauth.ts
+++ b/server/middlewares/oauth.ts
@@ -51,6 +51,7 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
51 51
52 return next() 52 return next()
53 }) 53 })
54 .catch(err => logger.error('Cannot get access token.', { err }))
54} 55}
55 56
56function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) { 57function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) {
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts
index 8c27e8237..fcbb2902c 100644
--- a/server/middlewares/sort.ts
+++ b/server/middlewares/sort.ts
@@ -1,20 +1,14 @@
1import * as express from 'express' 1import * as express from 'express'
2import { SortType } from '../models/utils' 2import { SortType } from '../models/utils'
3 3
4function setDefaultSort (req: express.Request, res: express.Response, next: express.NextFunction) { 4const setDefaultSort = setDefaultSortFactory('-createdAt')
5 if (!req.query.sort) req.query.sort = '-createdAt'
6
7 return next()
8}
9 5
10function setDefaultSearchSort (req: express.Request, res: express.Response, next: express.NextFunction) { 6const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name')
11 if (!req.query.sort) req.query.sort = '-match'
12 7
13 return next() 8const setDefaultSearchSort = setDefaultSortFactory('-match')
14}
15 9
16function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) { 10function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) {
17 let newSort: SortType = { sortModel: undefined, sortValue: '' } 11 const newSort: SortType = { sortModel: undefined, sortValue: '' }
18 12
19 if (!req.query.sort) req.query.sort = '-createdAt' 13 if (!req.query.sort) req.query.sort = '-createdAt'
20 14
@@ -39,5 +33,16 @@ function setBlacklistSort (req: express.Request, res: express.Response, next: ex
39export { 33export {
40 setDefaultSort, 34 setDefaultSort,
41 setDefaultSearchSort, 35 setDefaultSearchSort,
36 setDefaultVideoRedundanciesSort,
42 setBlacklistSort 37 setBlacklistSort
43} 38}
39
40// ---------------------------------------------------------------------------
41
42function setDefaultSortFactory (sort: string) {
43 return (req: express.Request, res: express.Response, next: express.NextFunction) => {
44 if (!req.query.sort) req.query.sort = sort
45
46 return next()
47 }
48}
diff --git a/server/middlewares/validators/avatar.ts b/server/middlewares/validators/avatar.ts
index 8623d07e8..2acb97483 100644
--- a/server/middlewares/validators/avatar.ts
+++ b/server/middlewares/validators/avatar.ts
@@ -8,8 +8,8 @@ import { cleanUpReqFiles } from '../../helpers/express-utils'
8 8
9const updateAvatarValidator = [ 9const updateAvatarValidator = [
10 body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage( 10 body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage(
11 'This file is not supported or too large. Please, make sure it is of the following type : ' 11 'This file is not supported or too large. Please, make sure it is of the following type : ' +
12 + CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ') 12 CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ')
13 ), 13 ),
14 14
15 (req: express.Request, res: express.Response, next: express.NextFunction) => { 15 (req: express.Request, res: express.Response, next: express.NextFunction) => {
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts
index 2d1f61947..dfa549e76 100644
--- a/server/middlewares/validators/config.ts
+++ b/server/middlewares/validators/config.ts
@@ -3,10 +3,10 @@ import { body } from 'express-validator'
3import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' 3import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../helpers/logger'
5import { CustomConfig } from '../../../shared/models/server/custom-config.model' 5import { CustomConfig } from '../../../shared/models/server/custom-config.model'
6import { Emailer } from '../../lib/emailer'
7import { areValidationErrors } from './utils' 6import { areValidationErrors } from './utils'
8import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 7import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
9import { isThemeRegistered } from '../../lib/plugins/theme-utils' 8import { isThemeRegistered } from '../../lib/plugins/theme-utils'
9import { isEmailEnabled } from '@server/initializers/config'
10 10
11const customConfigUpdateValidator = [ 11const customConfigUpdateValidator = [
12 body('instance.name').exists().withMessage('Should have a valid instance name'), 12 body('instance.name').exists().withMessage('Should have a valid instance name'),
@@ -55,7 +55,7 @@ const customConfigUpdateValidator = [
55 55
56 body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), 56 body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'),
57 57
58 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 58 (req: express.Request, res: express.Response, next: express.NextFunction) => {
59 logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) 59 logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body })
60 60
61 if (areValidationErrors(req, res)) return 61 if (areValidationErrors(req, res)) return
@@ -73,7 +73,7 @@ export {
73} 73}
74 74
75function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: express.Response) { 75function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: express.Response) {
76 if (Emailer.isEnabled()) return true 76 if (isEmailEnabled()) return true
77 77
78 if (customConfig.signup.requiresEmailVerification === true) { 78 if (customConfig.signup.requiresEmailVerification === true) {
79 res.status(400) 79 res.status(400)
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts
index 29f6c87be..f34c2b174 100644
--- a/server/middlewares/validators/feeds.ts
+++ b/server/middlewares/validators/feeds.ts
@@ -22,13 +22,13 @@ function setFeedFormatContentType (req: express.Request, res: express.Response,
22 22
23 let acceptableContentTypes: string[] 23 let acceptableContentTypes: string[]
24 if (format === 'atom' || format === 'atom1') { 24 if (format === 'atom' || format === 'atom1') {
25 acceptableContentTypes = ['application/atom+xml', 'application/xml', 'text/xml'] 25 acceptableContentTypes = [ 'application/atom+xml', 'application/xml', 'text/xml' ]
26 } else if (format === 'json' || format === 'json1') { 26 } else if (format === 'json' || format === 'json1') {
27 acceptableContentTypes = ['application/json'] 27 acceptableContentTypes = [ 'application/json' ]
28 } else if (format === 'rss' || format === 'rss2') { 28 } else if (format === 'rss' || format === 'rss2') {
29 acceptableContentTypes = ['application/rss+xml', 'application/xml', 'text/xml'] 29 acceptableContentTypes = [ 'application/rss+xml', 'application/xml', 'text/xml' ]
30 } else { 30 } else {
31 acceptableContentTypes = ['application/xml', 'text/xml'] 31 acceptableContentTypes = [ 'application/xml', 'text/xml' ]
32 } 32 }
33 33
34 if (req.accepts(acceptableContentTypes)) { 34 if (req.accepts(acceptableContentTypes)) {
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts
index 8098e3a44..8cd3bc33d 100644
--- a/server/middlewares/validators/redundancy.ts
+++ b/server/middlewares/validators/redundancy.ts
@@ -1,12 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { exists, isBooleanValid, isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 3import { exists, isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../helpers/logger'
5import { areValidationErrors } from './utils' 5import { areValidationErrors } from './utils'
6import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 6import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
7import { isHostValid } from '../../helpers/custom-validators/servers' 7import { isHostValid } from '../../helpers/custom-validators/servers'
8import { ServerModel } from '../../models/server/server' 8import { ServerModel } from '../../models/server/server'
9import { doesVideoExist } from '../../helpers/middlewares' 9import { doesVideoExist } from '../../helpers/middlewares'
10import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies'
10 11
11const videoFileRedundancyGetValidator = [ 12const videoFileRedundancyGetValidator = [
12 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
@@ -101,10 +102,77 @@ const updateServerRedundancyValidator = [
101 } 102 }
102] 103]
103 104
105const listVideoRedundanciesValidator = [
106 query('target')
107 .custom(isVideoRedundancyTarget).withMessage('Should have a valid video redundancies target'),
108
109 (req: express.Request, res: express.Response, next: express.NextFunction) => {
110 logger.debug('Checking listVideoRedundanciesValidator parameters', { parameters: req.query })
111
112 if (areValidationErrors(req, res)) return
113
114 return next()
115 }
116]
117
118const addVideoRedundancyValidator = [
119 body('videoId')
120 .custom(isIdValid)
121 .withMessage('Should have a valid video id'),
122
123 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
124 logger.debug('Checking addVideoRedundancyValidator parameters', { parameters: req.query })
125
126 if (areValidationErrors(req, res)) return
127
128 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
129
130 if (res.locals.onlyVideo.remote === false) {
131 return res.status(400)
132 .json({ error: 'Cannot create a redundancy on a local video' })
133 .end()
134 }
135
136 const alreadyExists = await VideoRedundancyModel.isLocalByVideoUUIDExists(res.locals.onlyVideo.uuid)
137 if (alreadyExists) {
138 return res.status(409)
139 .json({ error: 'This video is already duplicated by your instance.' })
140 }
141
142 return next()
143 }
144]
145
146const removeVideoRedundancyValidator = [
147 param('redundancyId')
148 .custom(isIdValid)
149 .withMessage('Should have a valid redundancy id'),
150
151 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
152 logger.debug('Checking removeVideoRedundancyValidator parameters', { parameters: req.query })
153
154 if (areValidationErrors(req, res)) return
155
156 const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10))
157 if (!redundancy) {
158 return res.status(404)
159 .json({ error: 'Video redundancy not found' })
160 .end()
161 }
162
163 res.locals.videoRedundancy = redundancy
164
165 return next()
166 }
167]
168
104// --------------------------------------------------------------------------- 169// ---------------------------------------------------------------------------
105 170
106export { 171export {
107 videoFileRedundancyGetValidator, 172 videoFileRedundancyGetValidator,
108 videoPlaylistRedundancyGetValidator, 173 videoPlaylistRedundancyGetValidator,
109 updateServerRedundancyValidator 174 updateServerRedundancyValidator,
175 listVideoRedundanciesValidator,
176 addVideoRedundancyValidator,
177 removeVideoRedundancyValidator
110} 178}
diff --git a/server/middlewares/validators/server.ts b/server/middlewares/validators/server.ts
index f6812647b..6158c3363 100644
--- a/server/middlewares/validators/server.ts
+++ b/server/middlewares/validators/server.ts
@@ -5,9 +5,8 @@ import { isHostValid, isValidContactBody } from '../../helpers/custom-validators
5import { ServerModel } from '../../models/server/server' 5import { ServerModel } from '../../models/server/server'
6import { body } from 'express-validator' 6import { body } from 'express-validator'
7import { isUserDisplayNameValid } from '../../helpers/custom-validators/users' 7import { isUserDisplayNameValid } from '../../helpers/custom-validators/users'
8import { Emailer } from '../../lib/emailer'
9import { Redis } from '../../lib/redis' 8import { Redis } from '../../lib/redis'
10import { CONFIG } from '../../initializers/config' 9import { CONFIG, isEmailEnabled } from '../../initializers/config'
11 10
12const serverGetValidator = [ 11const serverGetValidator = [
13 body('host').custom(isHostValid).withMessage('Should have a valid host'), 12 body('host').custom(isHostValid).withMessage('Should have a valid host'),
@@ -50,7 +49,7 @@ const contactAdministratorValidator = [
50 .end() 49 .end()
51 } 50 }
52 51
53 if (Emailer.isEnabled() === false) { 52 if (isEmailEnabled() === false) {
54 return res 53 return res
55 .status(409) 54 .status(409)
56 .send({ error: 'Emailer is not enabled on this instance.' }) 55 .send({ error: 'Emailer is not enabled on this instance.' })
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index c75e701d6..b76dab722 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -23,6 +23,7 @@ const SORTABLE_USER_NOTIFICATIONS_COLUMNS = createSortableColumns(SORTABLE_COLUM
23const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS) 23const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS)
24const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS) 24const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS)
25const SORTABLE_AVAILABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) 25const SORTABLE_AVAILABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.AVAILABLE_PLUGINS)
26const SORTABLE_VIDEO_REDUNDANCIES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES)
26 27
27const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) 28const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
28const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) 29const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
@@ -45,6 +46,7 @@ const userNotificationsSortValidator = checkSort(SORTABLE_USER_NOTIFICATIONS_COL
45const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS) 46const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS)
46const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS) 47const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS)
47const availablePluginsSortValidator = checkSort(SORTABLE_AVAILABLE_PLUGINS_COLUMNS) 48const availablePluginsSortValidator = checkSort(SORTABLE_AVAILABLE_PLUGINS_COLUMNS)
49const videoRedundanciesSortValidator = checkSort(SORTABLE_VIDEO_REDUNDANCIES_COLUMNS)
48 50
49// --------------------------------------------------------------------------- 51// ---------------------------------------------------------------------------
50 52
@@ -69,5 +71,6 @@ export {
69 serversBlocklistSortValidator, 71 serversBlocklistSortValidator,
70 userNotificationsSortValidator, 72 userNotificationsSortValidator,
71 videoPlaylistsSortValidator, 73 videoPlaylistsSortValidator,
74 videoRedundanciesSortValidator,
72 pluginsSortValidator 75 pluginsSortValidator
73} 76}
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index c78c67a8c..adc67a046 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -14,6 +14,7 @@ import {
14 isUserDisplayNameValid, 14 isUserDisplayNameValid,
15 isUserNSFWPolicyValid, 15 isUserNSFWPolicyValid,
16 isUserPasswordValid, 16 isUserPasswordValid,
17 isUserPasswordValidOrEmpty,
17 isUserRoleValid, 18 isUserRoleValid,
18 isUserUsernameValid, 19 isUserUsernameValid,
19 isUserVideoLanguages, 20 isUserVideoLanguages,
@@ -36,11 +37,10 @@ import { doesVideoExist } from '../../helpers/middlewares'
36import { UserRole } from '../../../shared/models/users' 37import { UserRole } from '../../../shared/models/users'
37import { MUserDefault } from '@server/typings/models' 38import { MUserDefault } from '@server/typings/models'
38import { Hooks } from '@server/lib/plugins/hooks' 39import { Hooks } from '@server/lib/plugins/hooks'
39import { isLocalVideoAccepted } from '@server/lib/moderation'
40 40
41const usersAddValidator = [ 41const usersAddValidator = [
42 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), 42 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
43 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'), 43 body('password').custom(isUserPasswordValidOrEmpty).withMessage('Should have a valid password'),
44 body('email').isEmail().withMessage('Should have a valid email'), 44 body('email').isEmail().withMessage('Should have a valid email'),
45 body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), 45 body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
46 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'), 46 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
@@ -149,7 +149,7 @@ const usersBlockingValidator = [
149] 149]
150 150
151const deleteMeValidator = [ 151const deleteMeValidator = [
152 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 152 (req: express.Request, res: express.Response, next: express.NextFunction) => {
153 const user = res.locals.oauth.token.User 153 const user = res.locals.oauth.token.User
154 if (user.username === 'root') { 154 if (user.username === 'root') {
155 return res.status(400) 155 return res.status(400)
@@ -303,7 +303,7 @@ const ensureUserRegistrationAllowed = [
303] 303]
304 304
305const ensureUserRegistrationAllowedForIP = [ 305const ensureUserRegistrationAllowedForIP = [
306 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 306 (req: express.Request, res: express.Response, next: express.NextFunction) => {
307 const allowed = isSignupAllowedForCurrentIP(req.ip) 307 const allowed = isSignupAllowedForCurrentIP(req.ip)
308 308
309 if (allowed === false) { 309 if (allowed === false) {
@@ -410,7 +410,7 @@ const userAutocompleteValidator = [
410] 410]
411 411
412const ensureAuthUserOwnsAccountValidator = [ 412const ensureAuthUserOwnsAccountValidator = [
413 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 413 (req: express.Request, res: express.Response, next: express.NextFunction) => {
414 const user = res.locals.oauth.token.User 414 const user = res.locals.oauth.token.User
415 415
416 if (res.locals.account.id !== user.Account.id) { 416 if (res.locals.account.id !== user.Account.id) {
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
index 7b0cd6f66..872d9c2ab 100644
--- a/server/middlewares/validators/videos/video-captions.ts
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -13,10 +13,12 @@ const addVideoCaptionValidator = [
13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
14 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), 14 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'),
15 body('captionfile') 15 body('captionfile')
16 .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile')).withMessage( 16 .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile'))
17 `This caption file is not supported or too large. Please, make sure it is under ${CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE} and one of the following mimetypes: ` 17 .withMessage(
18 + Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT).map(key => `${key} (${MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT[key]})`).join(', ') 18 'This caption file is not supported or too large. ' +
19 ), 19 `Please, make sure it is under ${CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE} and one of the following mimetypes: ` +
20 Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT).map(key => `${key} (${MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT[key]})`).join(', ')
21 ),
20 22
21 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 23 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
22 logger.debug('Checking addVideoCaption parameters', { parameters: req.body }) 24 logger.debug('Checking addVideoCaption parameters', { parameters: req.body })
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 77c5f940d..da2fafb10 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -50,7 +50,7 @@ const addVideoCommentThreadValidator = [
50 if (areValidationErrors(req, res)) return 50 if (areValidationErrors(req, res)) return
51 if (!await doesVideoExist(req.params.videoId, res)) return 51 if (!await doesVideoExist(req.params.videoId, res)) return
52 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return 52 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
53 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll,false)) return 53 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
54 54
55 return next() 55 return next()
56 } 56 }
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
index 318dad100..5dc5db533 100644
--- a/server/middlewares/validators/videos/video-imports.ts
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -22,10 +22,11 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
22 .optional() 22 .optional()
23 .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'), 23 .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'),
24 body('torrentfile') 24 body('torrentfile')
25 .custom((value, { req }) => isVideoImportTorrentFile(req.files)).withMessage( 25 .custom((value, { req }) => isVideoImportTorrentFile(req.files))
26 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' 26 .withMessage(
27 + CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ') 27 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' +
28 ), 28 CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ')
29 ),
29 body('name') 30 body('name')
30 .optional() 31 .optional()
31 .custom(isVideoNameValid).withMessage('Should have a valid name'), 32 .custom(isVideoNameValid).withMessage('Should have a valid name'),
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index 1d67e8666..6b15c5464 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -384,10 +384,11 @@ export {
384function getCommonPlaylistEditAttributes () { 384function getCommonPlaylistEditAttributes () {
385 return [ 385 return [
386 body('thumbnailfile') 386 body('thumbnailfile')
387 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( 387 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile'))
388 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' 388 .withMessage(
389 + CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ') 389 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
390 ), 390 CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ')
391 ),
391 392
392 body('description') 393 body('description')
393 .optional() 394 .optional()
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts
index 4021cfecc..cbc144f69 100644
--- a/server/middlewares/validators/videos/video-rates.ts
+++ b/server/middlewares/validators/videos/video-rates.ts
@@ -24,7 +24,7 @@ const videoUpdateRateValidator = [
24 } 24 }
25] 25]
26 26
27const getAccountVideoRateValidator = function (rateType: VideoRateType) { 27const getAccountVideoRateValidatorFactory = function (rateType: VideoRateType) {
28 return [ 28 return [
29 param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), 29 param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'),
30 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 30 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
@@ -51,7 +51,7 @@ const getAccountVideoRateValidator = function (rateType: VideoRateType) {
51const videoRatingValidator = [ 51const videoRatingValidator = [
52 query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'), 52 query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'),
53 53
54 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 54 (req: express.Request, res: express.Response, next: express.NextFunction) => {
55 logger.debug('Checking rating parameter', { parameters: req.params }) 55 logger.debug('Checking rating parameter', { parameters: req.params })
56 56
57 if (areValidationErrors(req, res)) return 57 if (areValidationErrors(req, res)) return
@@ -64,6 +64,6 @@ const videoRatingValidator = [
64 64
65export { 65export {
66 videoUpdateRateValidator, 66 videoUpdateRateValidator,
67 getAccountVideoRateValidator, 67 getAccountVideoRateValidatorFactory,
68 videoRatingValidator 68 videoRatingValidator
69} 69}
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 6733d9dec..a027c4840 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -49,8 +49,8 @@ import { getVideoWithAttributes } from '../../../helpers/video'
49const videosAddValidator = getCommonVideoEditAttributes().concat([ 49const videosAddValidator = getCommonVideoEditAttributes().concat([
50 body('videofile') 50 body('videofile')
51 .custom((value, { req }) => isVideoFile(req.files)).withMessage( 51 .custom((value, { req }) => isVideoFile(req.files)).withMessage(
52 'This file is not supported or too large. Please, make sure it is of the following type: ' 52 'This file is not supported or too large. Please, make sure it is of the following type: ' +
53 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') 53 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
54 ), 54 ),
55 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), 55 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
56 body('channelId') 56 body('channelId')
@@ -147,7 +147,10 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
147 }) 147 })
148} 148}
149 149
150const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-with-rights', authenticateInQuery = false) => { 150const videosCustomGetValidator = (
151 fetchType: 'all' | 'only-video' | 'only-video-with-rights' | 'only-immutable-attributes',
152 authenticateInQuery = false
153) => {
151 return [ 154 return [
152 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 155 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
153 156
@@ -157,6 +160,9 @@ const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-
157 if (areValidationErrors(req, res)) return 160 if (areValidationErrors(req, res)) return
158 if (!await doesVideoExist(req.params.id, res, fetchType)) return 161 if (!await doesVideoExist(req.params.id, res, fetchType)) return
159 162
163 // Controllers does not need to check video rights
164 if (fetchType === 'only-immutable-attributes') return next()
165
160 const video = getVideoWithAttributes(res) 166 const video = getVideoWithAttributes(res)
161 const videoAll = video as MVideoFullLight 167 const videoAll = video as MVideoFullLight
162 168
@@ -245,19 +251,15 @@ const videosTerminateChangeOwnershipValidator = [
245 // Check if the user who did the request is able to change the ownership of the video 251 // Check if the user who did the request is able to change the ownership of the video
246 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return 252 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
247 253
248 return next()
249 },
250 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
251 const videoChangeOwnership = res.locals.videoChangeOwnership 254 const videoChangeOwnership = res.locals.videoChangeOwnership
252 255
253 if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) { 256 if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) {
254 return next()
255 } else {
256 res.status(403) 257 res.status(403)
257 .json({ error: 'Ownership already accepted or refused' }) 258 .json({ error: 'Ownership already accepted or refused' })
258
259 return 259 return
260 } 260 }
261
262 return next()
261 } 263 }
262] 264]
263 265
@@ -284,14 +286,14 @@ function getCommonVideoEditAttributes () {
284 return [ 286 return [
285 body('thumbnailfile') 287 body('thumbnailfile')
286 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( 288 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
287 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' 289 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
288 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') 290 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
289 ), 291 ),
290 body('previewfile') 292 body('previewfile')
291 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( 293 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
292 'This preview file is not supported or too large. Please, make sure it is of the following type: ' 294 'This preview file is not supported or too large. Please, make sure it is of the following type: ' +
293 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') 295 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
294 ), 296 ),
295 297
296 body('category') 298 body('category')
297 .optional() 299 .optional()
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index d50e6527f..5fe864f8b 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -18,15 +18,14 @@ const webfingerValidator = [
18 const nameWithHost = getHostWithPort(req.query.resource.substr(5)) 18 const nameWithHost = getHostWithPort(req.query.resource.substr(5))
19 const [ name ] = nameWithHost.split('@') 19 const [ name ] = nameWithHost.split('@')
20 20
21 // FIXME: we don't need the full actor 21 const actor = await ActorModel.loadLocalUrlByName(name)
22 const actor = await ActorModel.loadLocalByName(name)
23 if (!actor) { 22 if (!actor) {
24 return res.status(404) 23 return res.status(404)
25 .send({ error: 'Actor not found' }) 24 .send({ error: 'Actor not found' })
26 .end() 25 .end()
27 } 26 }
28 27
29 res.locals.actorFull = actor 28 res.locals.actorUrl = actor
30 return next() 29 return next()
31 } 30 }
32] 31]