aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares
diff options
context:
space:
mode:
authorRigel Kent <sendmemail@rigelk.eu>2021-06-01 01:36:53 +0200
committerChocobozzz <chocobozzz@cpy.re>2021-06-02 16:57:07 +0200
commit76148b27f7501bac061992136852be4303370c8d (patch)
treefc0559253e833c9252fa14ebaec5321d88bfb4e8 /server/middlewares
parent5ed25fb76e920dac364cb9ef46f14ec4bd372949 (diff)
downloadPeerTube-76148b27f7501bac061992136852be4303370c8d.tar.gz
PeerTube-76148b27f7501bac061992136852be4303370c8d.tar.zst
PeerTube-76148b27f7501bac061992136852be4303370c8d.zip
refactor API errors to standard error format
Diffstat (limited to 'server/middlewares')
-rw-r--r--server/middlewares/activitypub.ts40
-rw-r--r--server/middlewares/auth.ts17
-rw-r--r--server/middlewares/servers.ts5
-rw-r--r--server/middlewares/user-right.ts6
-rw-r--r--server/middlewares/validators/abuse.ts27
-rw-r--r--server/middlewares/validators/activitypub/activity.ts2
-rw-r--r--server/middlewares/validators/blocklist.ts29
-rw-r--r--server/middlewares/validators/bulk.ts6
-rw-r--r--server/middlewares/validators/config.ts13
-rw-r--r--server/middlewares/validators/feeds.ts13
-rw-r--r--server/middlewares/validators/follows.ts26
-rw-r--r--server/middlewares/validators/oembed.ts45
-rw-r--r--server/middlewares/validators/plugins.ts42
-rw-r--r--server/middlewares/validators/redundancy.ts57
-rw-r--r--server/middlewares/validators/server.ts31
-rw-r--r--server/middlewares/validators/themes.ts10
-rw-r--r--server/middlewares/validators/user-subscriptions.ts9
-rw-r--r--server/middlewares/validators/users.ts113
-rw-r--r--server/middlewares/validators/utils.ts10
-rw-r--r--server/middlewares/validators/videos/video-blacklist.ts8
-rw-r--r--server/middlewares/validators/videos/video-channels.ts47
-rw-r--r--server/middlewares/validators/videos/video-comments.ts27
-rw-r--r--server/middlewares/validators/videos/video-imports.ts23
-rw-r--r--server/middlewares/validators/videos/video-live.ts61
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts73
-rw-r--r--server/middlewares/validators/videos/video-rates.ts6
-rw-r--r--server/middlewares/validators/videos/video-watch.ts5
-rw-r--r--server/middlewares/validators/videos/videos.ts105
-rw-r--r--server/middlewares/validators/webfinger.ts7
29 files changed, 502 insertions, 361 deletions
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index ce94a2129..6cd23f230 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -29,11 +29,14 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
29 const activity: ActivityDelete = req.body 29 const activity: ActivityDelete = req.body
30 if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) { 30 if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) {
31 logger.debug('Handling signature error on actor delete activity', { err }) 31 logger.debug('Handling signature error on actor delete activity', { err })
32 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 32 return res.status(HttpStatusCode.NO_CONTENT_204).end()
33 } 33 }
34 34
35 logger.warn('Error in ActivityPub signature checker.', { err }) 35 logger.warn('Error in ActivityPub signature checker.', { err })
36 return res.sendStatus(HttpStatusCode.FORBIDDEN_403) 36 return res.fail({
37 status: HttpStatusCode.FORBIDDEN_403,
38 message: 'ActivityPub signature could not be checked'
39 })
37 } 40 }
38} 41}
39 42
@@ -71,13 +74,22 @@ async function checkHttpSignature (req: Request, res: Response) {
71 } catch (err) { 74 } catch (err) {
72 logger.warn('Invalid signature because of exception in signature parser', { reqBody: req.body, err }) 75 logger.warn('Invalid signature because of exception in signature parser', { reqBody: req.body, err })
73 76
74 res.status(HttpStatusCode.FORBIDDEN_403).json({ error: err.message }) 77 res.fail({
78 status: HttpStatusCode.FORBIDDEN_403,
79 message: err.message
80 })
75 return false 81 return false
76 } 82 }
77 83
78 const keyId = parsed.keyId 84 const keyId = parsed.keyId
79 if (!keyId) { 85 if (!keyId) {
80 res.sendStatus(HttpStatusCode.FORBIDDEN_403) 86 res.fail({
87 status: HttpStatusCode.FORBIDDEN_403,
88 message: 'Invalid key ID',
89 data: {
90 keyId
91 }
92 })
81 return false 93 return false
82 } 94 }
83 95
@@ -94,12 +106,17 @@ async function checkHttpSignature (req: Request, res: Response) {
94 if (verified !== true) { 106 if (verified !== true) {
95 logger.warn('Signature from %s is invalid', actorUrl, { parsed }) 107 logger.warn('Signature from %s is invalid', actorUrl, { parsed })
96 108
97 res.sendStatus(HttpStatusCode.FORBIDDEN_403) 109 res.fail({
110 status: HttpStatusCode.FORBIDDEN_403,
111 message: 'Invalid signature',
112 data: {
113 actorUrl
114 }
115 })
98 return false 116 return false
99 } 117 }
100 118
101 res.locals.signature = { actor } 119 res.locals.signature = { actor }
102
103 return true 120 return true
104} 121}
105 122
@@ -107,7 +124,10 @@ async function checkJsonLDSignature (req: Request, res: Response) {
107 const signatureObject: ActivityPubSignature = req.body.signature 124 const signatureObject: ActivityPubSignature = req.body.signature
108 125
109 if (!signatureObject || !signatureObject.creator) { 126 if (!signatureObject || !signatureObject.creator) {
110 res.sendStatus(HttpStatusCode.FORBIDDEN_403) 127 res.fail({
128 status: HttpStatusCode.FORBIDDEN_403,
129 message: 'Object and creator signature do not match'
130 })
111 return false 131 return false
112 } 132 }
113 133
@@ -121,11 +141,13 @@ async function checkJsonLDSignature (req: Request, res: Response) {
121 if (verified !== true) { 141 if (verified !== true) {
122 logger.warn('Signature not verified.', req.body) 142 logger.warn('Signature not verified.', req.body)
123 143
124 res.sendStatus(HttpStatusCode.FORBIDDEN_403) 144 res.fail({
145 status: HttpStatusCode.FORBIDDEN_403,
146 message: 'Signature could not be verified'
147 })
125 return false 148 return false
126 } 149 }
127 150
128 res.locals.signature = { actor } 151 res.locals.signature = { actor }
129
130 return true 152 return true
131} 153}
diff --git a/server/middlewares/auth.ts b/server/middlewares/auth.ts
index f38373624..176461cc2 100644
--- a/server/middlewares/auth.ts
+++ b/server/middlewares/auth.ts
@@ -16,11 +16,11 @@ function authenticate (req: express.Request, res: express.Response, next: expres
16 .catch(err => { 16 .catch(err => {
17 logger.warn('Cannot authenticate.', { err }) 17 logger.warn('Cannot authenticate.', { err })
18 18
19 return res.status(err.status) 19 return res.fail({
20 .json({ 20 status: err.status,
21 error: 'Token is invalid.', 21 message: 'Token is invalid',
22 code: err.name 22 type: err.name
23 }) 23 })
24 }) 24 })
25} 25}
26 26
@@ -52,7 +52,12 @@ function authenticatePromiseIfNeeded (req: express.Request, res: express.Respons
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()
54 54
55 if (res.locals.authenticated === false) return res.sendStatus(HttpStatusCode.UNAUTHORIZED_401) 55 if (res.locals.authenticated === false) {
56 return res.fail({
57 status: HttpStatusCode.UNAUTHORIZED_401,
58 message: 'Not authenticated'
59 })
60 }
56 61
57 authenticate(req, res, () => resolve(), authenticateInQuery) 62 authenticate(req, res, () => resolve(), authenticateInQuery)
58 }) 63 })
diff --git a/server/middlewares/servers.ts b/server/middlewares/servers.ts
index 5e1c165f0..9aa56bc93 100644
--- a/server/middlewares/servers.ts
+++ b/server/middlewares/servers.ts
@@ -10,7 +10,10 @@ function setBodyHostsPort (req: express.Request, res: express.Response, next: ex
10 10
11 // Problem with the url parsing? 11 // Problem with the url parsing?
12 if (hostWithPort === null) { 12 if (hostWithPort === null) {
13 return res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR_500) 13 return res.fail({
14 status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
15 message: 'Could not parse hosts'
16 })
14 } 17 }
15 18
16 req.body.hosts[i] = hostWithPort 19 req.body.hosts[i] = hostWithPort
diff --git a/server/middlewares/user-right.ts b/server/middlewares/user-right.ts
index 45dda4781..d1888c2d3 100644
--- a/server/middlewares/user-right.ts
+++ b/server/middlewares/user-right.ts
@@ -10,8 +10,10 @@ function ensureUserHasRight (userRight: UserRight) {
10 const message = `User ${user.username} does not have right ${userRight} to access to ${req.path}.` 10 const message = `User ${user.username} does not have right ${userRight} to access to ${req.path}.`
11 logger.info(message) 11 logger.info(message)
12 12
13 return res.status(HttpStatusCode.FORBIDDEN_403) 13 return res.fail({
14 .json({ error: message }) 14 status: HttpStatusCode.FORBIDDEN_403,
15 message
16 })
15 } 17 }
16 18
17 return next() 19 return next()
diff --git a/server/middlewares/validators/abuse.ts b/server/middlewares/validators/abuse.ts
index 3b897fdef..7f002e0d5 100644
--- a/server/middlewares/validators/abuse.ts
+++ b/server/middlewares/validators/abuse.ts
@@ -71,9 +71,7 @@ const abuseReportValidator = [
71 if (body.comment?.id && !await doesCommentIdExist(body.comment.id, res)) return 71 if (body.comment?.id && !await doesCommentIdExist(body.comment.id, res)) return
72 72
73 if (!body.video?.id && !body.account?.id && !body.comment?.id) { 73 if (!body.video?.id && !body.account?.id && !body.comment?.id) {
74 res.status(HttpStatusCode.BAD_REQUEST_400) 74 res.fail({ message: 'video id or account id or comment id is required.' })
75 .json({ error: 'video id or account id or comment id is required.' })
76
77 return 75 return
78 } 76 }
79 77
@@ -195,8 +193,10 @@ const getAbuseValidator = [
195 const message = `User ${user.username} does not have right to get abuse ${abuse.id}` 193 const message = `User ${user.username} does not have right to get abuse ${abuse.id}`
196 logger.warn(message) 194 logger.warn(message)
197 195
198 return res.status(HttpStatusCode.FORBIDDEN_403) 196 return res.fail({
199 .json({ error: message }) 197 status: HttpStatusCode.FORBIDDEN_403,
198 message
199 })
200 } 200 }
201 201
202 return next() 202 return next()
@@ -209,10 +209,7 @@ const checkAbuseValidForMessagesValidator = [
209 209
210 const abuse = res.locals.abuse 210 const abuse = res.locals.abuse
211 if (abuse.ReporterAccount.isOwned() === false) { 211 if (abuse.ReporterAccount.isOwned() === false) {
212 return res.status(HttpStatusCode.BAD_REQUEST_400) 212 return res.fail({ message: 'This abuse was created by a user of your instance.' })
213 .json({
214 error: 'This abuse was created by a user of your instance.'
215 })
216 } 213 }
217 214
218 return next() 215 return next()
@@ -246,13 +243,17 @@ const deleteAbuseMessageValidator = [
246 const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id) 243 const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id)
247 244
248 if (!abuseMessage) { 245 if (!abuseMessage) {
249 return res.status(HttpStatusCode.NOT_FOUND_404) 246 return res.fail({
250 .json({ error: 'Abuse message not found' }) 247 status: HttpStatusCode.NOT_FOUND_404,
248 message: 'Abuse message not found'
249 })
251 } 250 }
252 251
253 if (user.hasRight(UserRight.MANAGE_ABUSES) !== true && abuseMessage.accountId !== user.Account.id) { 252 if (user.hasRight(UserRight.MANAGE_ABUSES) !== true && abuseMessage.accountId !== user.Account.id) {
254 return res.status(HttpStatusCode.FORBIDDEN_403) 253 return res.fail({
255 .json({ error: 'Cannot delete this abuse message' }) 254 status: HttpStatusCode.FORBIDDEN_403,
255 message: 'Cannot delete this abuse message'
256 })
256 } 257 }
257 258
258 res.locals.abuseMessage = abuseMessage 259 res.locals.abuseMessage = abuseMessage
diff --git a/server/middlewares/validators/activitypub/activity.ts b/server/middlewares/validators/activitypub/activity.ts
index e78ef07ef..59355e855 100644
--- a/server/middlewares/validators/activitypub/activity.ts
+++ b/server/middlewares/validators/activitypub/activity.ts
@@ -10,7 +10,7 @@ async function activityPubValidator (req: express.Request, res: express.Response
10 if (!isRootActivityValid(req.body)) { 10 if (!isRootActivityValid(req.body)) {
11 logger.warn('Incorrect activity parameters.', { activity: req.body }) 11 logger.warn('Incorrect activity parameters.', { activity: req.body })
12 return res.status(HttpStatusCode.BAD_REQUEST_400) 12 return res.status(HttpStatusCode.BAD_REQUEST_400)
13 .json({ error: 'Incorrect activity.' }) 13 .end()
14 } 14 }
15 15
16 const serverActor = await getServerActor() 16 const serverActor = await getServerActor()
diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts
index f61811a1a..125ff882c 100644
--- a/server/middlewares/validators/blocklist.ts
+++ b/server/middlewares/validators/blocklist.ts
@@ -24,9 +24,10 @@ const blockAccountValidator = [
24 const accountToBlock = res.locals.account 24 const accountToBlock = res.locals.account
25 25
26 if (user.Account.id === accountToBlock.id) { 26 if (user.Account.id === accountToBlock.id) {
27 res.status(HttpStatusCode.CONFLICT_409) 27 res.fail({
28 .json({ error: 'You cannot block yourself.' }) 28 status: HttpStatusCode.CONFLICT_409,
29 29 message: 'You cannot block yourself.'
30 })
30 return 31 return
31 } 32 }
32 33
@@ -79,8 +80,10 @@ const blockServerValidator = [
79 const host: string = req.body.host 80 const host: string = req.body.host
80 81
81 if (host === WEBSERVER.HOST) { 82 if (host === WEBSERVER.HOST) {
82 return res.status(HttpStatusCode.CONFLICT_409) 83 return res.fail({
83 .json({ error: 'You cannot block your own server.' }) 84 status: HttpStatusCode.CONFLICT_409,
85 message: 'You cannot block your own server.'
86 })
84 } 87 }
85 88
86 const server = await ServerModel.loadOrCreateByHost(host) 89 const server = await ServerModel.loadOrCreateByHost(host)
@@ -137,27 +140,27 @@ export {
137async function doesUnblockAccountExist (accountId: number, targetAccountId: number, res: express.Response) { 140async function doesUnblockAccountExist (accountId: number, targetAccountId: number, res: express.Response) {
138 const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(accountId, targetAccountId) 141 const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(accountId, targetAccountId)
139 if (!accountBlock) { 142 if (!accountBlock) {
140 res.status(HttpStatusCode.NOT_FOUND_404) 143 res.fail({
141 .json({ error: 'Account block entry not found.' }) 144 status: HttpStatusCode.NOT_FOUND_404,
142 145 message: 'Account block entry not found.'
146 })
143 return false 147 return false
144 } 148 }
145 149
146 res.locals.accountBlock = accountBlock 150 res.locals.accountBlock = accountBlock
147
148 return true 151 return true
149} 152}
150 153
151async function doesUnblockServerExist (accountId: number, host: string, res: express.Response) { 154async function doesUnblockServerExist (accountId: number, host: string, res: express.Response) {
152 const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(accountId, host) 155 const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(accountId, host)
153 if (!serverBlock) { 156 if (!serverBlock) {
154 res.status(HttpStatusCode.NOT_FOUND_404) 157 res.fail({
155 .json({ error: 'Server block entry not found.' }) 158 status: HttpStatusCode.NOT_FOUND_404,
156 159 message: 'Server block entry not found.'
160 })
157 return false 161 return false
158 } 162 }
159 163
160 res.locals.serverBlock = serverBlock 164 res.locals.serverBlock = serverBlock
161
162 return true 165 return true
163} 166}
diff --git a/server/middlewares/validators/bulk.ts b/server/middlewares/validators/bulk.ts
index cfb16d352..847885101 100644
--- a/server/middlewares/validators/bulk.ts
+++ b/server/middlewares/validators/bulk.ts
@@ -23,9 +23,9 @@ const bulkRemoveCommentsOfValidator = [
23 const body = req.body as BulkRemoveCommentsOfBody 23 const body = req.body as BulkRemoveCommentsOfBody
24 24
25 if (body.scope === 'instance' && user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) !== true) { 25 if (body.scope === 'instance' && user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) !== true) {
26 return res.status(HttpStatusCode.FORBIDDEN_403) 26 return res.fail({
27 .json({ 27 status: HttpStatusCode.FORBIDDEN_403,
28 error: 'User cannot remove any comments of this instance.' 28 message: 'User cannot remove any comments of this instance.'
29 }) 29 })
30 } 30 }
31 31
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts
index e3e0c2058..b5d6b4622 100644
--- a/server/middlewares/validators/config.ts
+++ b/server/middlewares/validators/config.ts
@@ -2,7 +2,6 @@ import * as express from 'express'
2import { body } from 'express-validator' 2import { body } from 'express-validator'
3import { isIntOrNull } from '@server/helpers/custom-validators/misc' 3import { isIntOrNull } from '@server/helpers/custom-validators/misc'
4import { isEmailEnabled } from '@server/initializers/config' 4import { isEmailEnabled } from '@server/initializers/config'
5import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
6import { CustomConfig } from '../../../shared/models/server/custom-config.model' 5import { CustomConfig } from '../../../shared/models/server/custom-config.model'
7import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 6import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
8import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' 7import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
@@ -115,9 +114,7 @@ function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: exp
115 if (isEmailEnabled()) return true 114 if (isEmailEnabled()) return true
116 115
117 if (customConfig.signup.requiresEmailVerification === true) { 116 if (customConfig.signup.requiresEmailVerification === true) {
118 res.status(HttpStatusCode.BAD_REQUEST_400) 117 res.fail({ message: 'Emailer is disabled but you require signup email verification.' })
119 .send({ error: 'Emailer is disabled but you require signup email verification.' })
120 .end()
121 return false 118 return false
122 } 119 }
123 120
@@ -128,9 +125,7 @@ function checkInvalidTranscodingConfig (customConfig: CustomConfig, res: express
128 if (customConfig.transcoding.enabled === false) return true 125 if (customConfig.transcoding.enabled === false) return true
129 126
130 if (customConfig.transcoding.webtorrent.enabled === false && customConfig.transcoding.hls.enabled === false) { 127 if (customConfig.transcoding.webtorrent.enabled === false && customConfig.transcoding.hls.enabled === false) {
131 res.status(HttpStatusCode.BAD_REQUEST_400) 128 res.fail({ message: 'You need to enable at least webtorrent transcoding or hls transcoding' })
132 .send({ error: 'You need to enable at least webtorrent transcoding or hls transcoding' })
133 .end()
134 return false 129 return false
135 } 130 }
136 131
@@ -141,9 +136,7 @@ function checkInvalidLiveConfig (customConfig: CustomConfig, res: express.Respon
141 if (customConfig.live.enabled === false) return true 136 if (customConfig.live.enabled === false) return true
142 137
143 if (customConfig.live.allowReplay === true && customConfig.transcoding.enabled === false) { 138 if (customConfig.live.allowReplay === true && customConfig.transcoding.enabled === false) {
144 res.status(HttpStatusCode.BAD_REQUEST_400) 139 res.fail({ message: 'You cannot allow live replay if transcoding is not enabled' })
145 .send({ error: 'You cannot allow live replay if transcoding is not enabled' })
146 .end()
147 return false 140 return false
148 } 141 }
149 142
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts
index 617661813..aa16cc993 100644
--- a/server/middlewares/validators/feeds.ts
+++ b/server/middlewares/validators/feeds.ts
@@ -36,10 +36,10 @@ function setFeedFormatContentType (req: express.Request, res: express.Response,
36 if (req.accepts(acceptableContentTypes)) { 36 if (req.accepts(acceptableContentTypes)) {
37 res.set('Content-Type', req.accepts(acceptableContentTypes) as string) 37 res.set('Content-Type', req.accepts(acceptableContentTypes) as string)
38 } else { 38 } else {
39 return res.status(HttpStatusCode.NOT_ACCEPTABLE_406) 39 return res.fail({
40 .json({ 40 status: HttpStatusCode.NOT_ACCEPTABLE_406,
41 message: `You should accept at least one of the following content-types: ${acceptableContentTypes.join(', ')}` 41 message: `You should accept at least one of the following content-types: ${acceptableContentTypes.join(', ')}`
42 }) 42 })
43 } 43 }
44 44
45 return next() 45 return next()
@@ -106,10 +106,7 @@ const videoCommentsFeedsValidator = [
106 if (areValidationErrors(req, res)) return 106 if (areValidationErrors(req, res)) return
107 107
108 if (req.query.videoId && (req.query.videoChannelId || req.query.videoChannelName)) { 108 if (req.query.videoId && (req.query.videoChannelId || req.query.videoChannelName)) {
109 return res.status(HttpStatusCode.BAD_REQUEST_400) 109 return res.fail({ message: 'videoId cannot be mixed with a channel filter' })
110 .json({
111 message: 'videoId cannot be mixed with a channel filter'
112 })
113 } 110 }
114 111
115 if (req.query.videoId && !await doesVideoExist(req.query.videoId, res)) return 112 if (req.query.videoId && !await doesVideoExist(req.query.videoId, res)) return
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts
index 1d18de8cd..733be379b 100644
--- a/server/middlewares/validators/follows.ts
+++ b/server/middlewares/validators/follows.ts
@@ -63,11 +63,10 @@ const removeFollowingValidator = [
63 const follow = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(serverActor.id, SERVER_ACTOR_NAME, req.params.host) 63 const follow = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(serverActor.id, SERVER_ACTOR_NAME, req.params.host)
64 64
65 if (!follow) { 65 if (!follow) {
66 return res 66 return res.fail({
67 .status(HttpStatusCode.NOT_FOUND_404) 67 status: HttpStatusCode.NOT_FOUND_404,
68 .json({ 68 message: `Following ${req.params.host} not found.`
69 error: `Following ${req.params.host} not found.` 69 })
70 })
71 } 70 }
72 71
73 res.locals.follow = follow 72 res.locals.follow = follow
@@ -95,12 +94,10 @@ const getFollowerValidator = [
95 } 94 }
96 95
97 if (!follow) { 96 if (!follow) {
98 return res 97 return res.fail({
99 .status(HttpStatusCode.NOT_FOUND_404) 98 status: HttpStatusCode.NOT_FOUND_404,
100 .json({ 99 message: `Follower ${req.params.nameWithHost} not found.`
101 error: `Follower ${req.params.nameWithHost} not found.` 100 })
102 })
103 .end()
104 } 101 }
105 102
106 res.locals.follow = follow 103 res.locals.follow = follow
@@ -114,12 +111,7 @@ const acceptOrRejectFollowerValidator = [
114 111
115 const follow = res.locals.follow 112 const follow = res.locals.follow
116 if (follow.state !== 'pending') { 113 if (follow.state !== 'pending') {
117 return res 114 return res.fail({ message: 'Follow is not in pending state.' })
118 .status(HttpStatusCode.BAD_REQUEST_400)
119 .json({
120 error: 'Follow is not in pending state.'
121 })
122 .end()
123 } 115 }
124 116
125 return next() 117 return next()
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts
index 165eda6d5..b1d763fbe 100644
--- a/server/middlewares/validators/oembed.ts
+++ b/server/middlewares/validators/oembed.ts
@@ -51,8 +51,13 @@ const oembedValidator = [
51 if (areValidationErrors(req, res)) return 51 if (areValidationErrors(req, res)) return
52 52
53 if (req.query.format !== undefined && req.query.format !== 'json') { 53 if (req.query.format !== undefined && req.query.format !== 'json') {
54 return res.status(HttpStatusCode.NOT_IMPLEMENTED_501) 54 return res.fail({
55 .json({ error: 'Requested format is not implemented on server.' }) 55 status: HttpStatusCode.NOT_IMPLEMENTED_501,
56 message: 'Requested format is not implemented on server.',
57 data: {
58 format: req.query.format
59 }
60 })
56 } 61 }
57 62
58 const url = req.query.url as string 63 const url = req.query.url as string
@@ -65,27 +70,35 @@ const oembedValidator = [
65 const matches = watchRegex.exec(url) 70 const matches = watchRegex.exec(url)
66 71
67 if (startIsOk === false || matches === null) { 72 if (startIsOk === false || matches === null) {
68 return res.status(HttpStatusCode.BAD_REQUEST_400) 73 return res.fail({
69 .json({ error: 'Invalid url.' }) 74 status: HttpStatusCode.BAD_REQUEST_400,
75 message: 'Invalid url.',
76 data: {
77 url
78 }
79 })
70 } 80 }
71 81
72 const elementId = matches[1] 82 const elementId = matches[1]
73 if (isIdOrUUIDValid(elementId) === false) { 83 if (isIdOrUUIDValid(elementId) === false) {
74 return res.status(HttpStatusCode.BAD_REQUEST_400) 84 return res.fail({ message: 'Invalid video or playlist id.' })
75 .json({ error: 'Invalid video or playlist id.' })
76 } 85 }
77 86
78 if (isVideo) { 87 if (isVideo) {
79 const video = await fetchVideo(elementId, 'all') 88 const video = await fetchVideo(elementId, 'all')
80 89
81 if (!video) { 90 if (!video) {
82 return res.status(HttpStatusCode.NOT_FOUND_404) 91 return res.fail({
83 .json({ error: 'Video not found' }) 92 status: HttpStatusCode.NOT_FOUND_404,
93 message: 'Video not found'
94 })
84 } 95 }
85 96
86 if (video.privacy !== VideoPrivacy.PUBLIC) { 97 if (video.privacy !== VideoPrivacy.PUBLIC) {
87 return res.status(HttpStatusCode.FORBIDDEN_403) 98 return res.fail({
88 .json({ error: 'Video is not public' }) 99 status: HttpStatusCode.FORBIDDEN_403,
100 message: 'Video is not public'
101 })
89 } 102 }
90 103
91 res.locals.videoAll = video 104 res.locals.videoAll = video
@@ -96,13 +109,17 @@ const oembedValidator = [
96 109
97 const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(elementId, undefined) 110 const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(elementId, undefined)
98 if (!videoPlaylist) { 111 if (!videoPlaylist) {
99 return res.status(HttpStatusCode.NOT_FOUND_404) 112 return res.fail({
100 .json({ error: 'Video playlist not found' }) 113 status: HttpStatusCode.NOT_FOUND_404,
114 message: 'Video playlist not found'
115 })
101 } 116 }
102 117
103 if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PUBLIC) { 118 if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PUBLIC) {
104 return res.status(HttpStatusCode.FORBIDDEN_403) 119 return res.fail({
105 .json({ error: 'Playlist is not public' }) 120 status: HttpStatusCode.FORBIDDEN_403,
121 message: 'Playlist is not public'
122 })
106 } 123 }
107 124
108 res.locals.videoPlaylistSummary = videoPlaylist 125 res.locals.videoPlaylistSummary = videoPlaylist
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts
index 2c47ec5bb..5934a28bc 100644
--- a/server/middlewares/validators/plugins.ts
+++ b/server/middlewares/validators/plugins.ts
@@ -31,8 +31,18 @@ const getPluginValidator = (pluginType: PluginType, withVersion = true) => {
31 const npmName = PluginModel.buildNpmName(req.params.pluginName, pluginType) 31 const npmName = PluginModel.buildNpmName(req.params.pluginName, pluginType)
32 const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(npmName) 32 const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(npmName)
33 33
34 if (!plugin) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 34 if (!plugin) {
35 if (withVersion && plugin.version !== req.params.pluginVersion) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 35 return res.fail({
36 status: HttpStatusCode.NOT_FOUND_404,
37 message: 'No plugin found named ' + npmName
38 })
39 }
40 if (withVersion && plugin.version !== req.params.pluginVersion) {
41 return res.fail({
42 status: HttpStatusCode.NOT_FOUND_404,
43 message: 'No plugin found named ' + npmName + ' with version ' + req.params.pluginVersion
44 })
45 }
36 46
37 res.locals.registeredPlugin = plugin 47 res.locals.registeredPlugin = plugin
38 48
@@ -50,10 +60,20 @@ const getExternalAuthValidator = [
50 if (areValidationErrors(req, res)) return 60 if (areValidationErrors(req, res)) return
51 61
52 const plugin = res.locals.registeredPlugin 62 const plugin = res.locals.registeredPlugin
53 if (!plugin.registerHelpers) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 63 if (!plugin.registerHelpers) {
64 return res.fail({
65 status: HttpStatusCode.NOT_FOUND_404,
66 message: 'No registered helpers were found for this plugin'
67 })
68 }
54 69
55 const externalAuth = plugin.registerHelpers.getExternalAuths().find(a => a.authName === req.params.authName) 70 const externalAuth = plugin.registerHelpers.getExternalAuths().find(a => a.authName === req.params.authName)
56 if (!externalAuth) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 71 if (!externalAuth) {
72 return res.fail({
73 status: HttpStatusCode.NOT_FOUND_404,
74 message: 'No external auths were found for this plugin'
75 })
76 }
57 77
58 res.locals.externalAuth = externalAuth 78 res.locals.externalAuth = externalAuth
59 79
@@ -107,8 +127,7 @@ const installOrUpdatePluginValidator = [
107 127
108 const body: InstallOrUpdatePlugin = req.body 128 const body: InstallOrUpdatePlugin = req.body
109 if (!body.path && !body.npmName) { 129 if (!body.path && !body.npmName) {
110 return res.status(HttpStatusCode.BAD_REQUEST_400) 130 return res.fail({ message: 'Should have either a npmName or a path' })
111 .json({ error: 'Should have either a npmName or a path' })
112 } 131 }
113 132
114 return next() 133 return next()
@@ -137,12 +156,13 @@ const existingPluginValidator = [
137 156
138 const plugin = await PluginModel.loadByNpmName(req.params.npmName) 157 const plugin = await PluginModel.loadByNpmName(req.params.npmName)
139 if (!plugin) { 158 if (!plugin) {
140 return res.status(HttpStatusCode.NOT_FOUND_404) 159 return res.fail({
141 .json({ error: 'Plugin not found' }) 160 status: HttpStatusCode.NOT_FOUND_404,
161 message: 'Plugin not found'
162 })
142 } 163 }
143 164
144 res.locals.plugin = plugin 165 res.locals.plugin = plugin
145
146 return next() 166 return next()
147 } 167 }
148] 168]
@@ -177,9 +197,7 @@ const listAvailablePluginsValidator = [
177 if (areValidationErrors(req, res)) return 197 if (areValidationErrors(req, res)) return
178 198
179 if (CONFIG.PLUGINS.INDEX.ENABLED === false) { 199 if (CONFIG.PLUGINS.INDEX.ENABLED === false) {
180 return res.status(HttpStatusCode.BAD_REQUEST_400) 200 return res.fail({ message: 'Plugin index is not enabled' })
181 .json({ error: 'Plugin index is not enabled' })
182 .end()
183 } 201 }
184 202
185 return next() 203 return next()
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts
index c379aebe4..3d557048a 100644
--- a/server/middlewares/validators/redundancy.ts
+++ b/server/middlewares/validators/redundancy.ts
@@ -35,11 +35,21 @@ const videoFileRedundancyGetValidator = [
35 return f.resolution === paramResolution && (!req.params.fps || paramFPS) 35 return f.resolution === paramResolution && (!req.params.fps || paramFPS)
36 }) 36 })
37 37
38 if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video file not found.' }) 38 if (!videoFile) {
39 return res.fail({
40 status: HttpStatusCode.NOT_FOUND_404,
41 message: 'Video file not found.'
42 })
43 }
39 res.locals.videoFile = videoFile 44 res.locals.videoFile = videoFile
40 45
41 const videoRedundancy = await VideoRedundancyModel.loadLocalByFileId(videoFile.id) 46 const videoRedundancy = await VideoRedundancyModel.loadLocalByFileId(videoFile.id)
42 if (!videoRedundancy) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video redundancy not found.' }) 47 if (!videoRedundancy) {
48 return res.fail({
49 status: HttpStatusCode.NOT_FOUND_404,
50 message: 'Video redundancy not found.'
51 })
52 }
43 res.locals.videoRedundancy = videoRedundancy 53 res.locals.videoRedundancy = videoRedundancy
44 54
45 return next() 55 return next()
@@ -65,11 +75,21 @@ const videoPlaylistRedundancyGetValidator = [
65 const paramPlaylistType = req.params.streamingPlaylistType as unknown as number // We casted to int above 75 const paramPlaylistType = req.params.streamingPlaylistType as unknown as number // We casted to int above
66 const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p.type === paramPlaylistType) 76 const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p.type === paramPlaylistType)
67 77
68 if (!videoStreamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video playlist not found.' }) 78 if (!videoStreamingPlaylist) {
79 return res.fail({
80 status: HttpStatusCode.NOT_FOUND_404,
81 message: 'Video playlist not found.'
82 })
83 }
69 res.locals.videoStreamingPlaylist = videoStreamingPlaylist 84 res.locals.videoStreamingPlaylist = videoStreamingPlaylist
70 85
71 const videoRedundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(videoStreamingPlaylist.id) 86 const videoRedundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(videoStreamingPlaylist.id)
72 if (!videoRedundancy) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video redundancy not found.' }) 87 if (!videoRedundancy) {
88 return res.fail({
89 status: HttpStatusCode.NOT_FOUND_404,
90 message: 'Video redundancy not found.'
91 })
92 }
73 res.locals.videoRedundancy = videoRedundancy 93 res.locals.videoRedundancy = videoRedundancy
74 94
75 return next() 95 return next()
@@ -90,12 +110,10 @@ const updateServerRedundancyValidator = [
90 const server = await ServerModel.loadByHost(req.params.host) 110 const server = await ServerModel.loadByHost(req.params.host)
91 111
92 if (!server) { 112 if (!server) {
93 return res 113 return res.fail({
94 .status(HttpStatusCode.NOT_FOUND_404) 114 status: HttpStatusCode.NOT_FOUND_404,
95 .json({ 115 message: `Server ${req.params.host} not found.`
96 error: `Server ${req.params.host} not found.` 116 })
97 })
98 .end()
99 } 117 }
100 118
101 res.locals.server = server 119 res.locals.server = server
@@ -129,19 +147,19 @@ const addVideoRedundancyValidator = [
129 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return 147 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
130 148
131 if (res.locals.onlyVideo.remote === false) { 149 if (res.locals.onlyVideo.remote === false) {
132 return res.status(HttpStatusCode.BAD_REQUEST_400) 150 return res.fail({ message: 'Cannot create a redundancy on a local video' })
133 .json({ error: 'Cannot create a redundancy on a local video' })
134 } 151 }
135 152
136 if (res.locals.onlyVideo.isLive) { 153 if (res.locals.onlyVideo.isLive) {
137 return res.status(HttpStatusCode.BAD_REQUEST_400) 154 return res.fail({ message: 'Cannot create a redundancy of a live video' })
138 .json({ error: 'Cannot create a redundancy of a live video' })
139 } 155 }
140 156
141 const alreadyExists = await VideoRedundancyModel.isLocalByVideoUUIDExists(res.locals.onlyVideo.uuid) 157 const alreadyExists = await VideoRedundancyModel.isLocalByVideoUUIDExists(res.locals.onlyVideo.uuid)
142 if (alreadyExists) { 158 if (alreadyExists) {
143 return res.status(HttpStatusCode.CONFLICT_409) 159 return res.fail({
144 .json({ error: 'This video is already duplicated by your instance.' }) 160 status: HttpStatusCode.CONFLICT_409,
161 message: 'This video is already duplicated by your instance.'
162 })
145 } 163 }
146 164
147 return next() 165 return next()
@@ -160,9 +178,10 @@ const removeVideoRedundancyValidator = [
160 178
161 const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10)) 179 const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10))
162 if (!redundancy) { 180 if (!redundancy) {
163 return res.status(HttpStatusCode.NOT_FOUND_404) 181 return res.fail({
164 .json({ error: 'Video redundancy not found' }) 182 status: HttpStatusCode.NOT_FOUND_404,
165 .end() 183 message: 'Video redundancy not found'
184 })
166 } 185 }
167 186
168 res.locals.videoRedundancy = redundancy 187 res.locals.videoRedundancy = redundancy
diff --git a/server/middlewares/validators/server.ts b/server/middlewares/validators/server.ts
index fe6704716..2b34c4a76 100644
--- a/server/middlewares/validators/server.ts
+++ b/server/middlewares/validators/server.ts
@@ -19,9 +19,10 @@ const serverGetValidator = [
19 19
20 const server = await ServerModel.loadByHost(req.body.host) 20 const server = await ServerModel.loadByHost(req.body.host)
21 if (!server) { 21 if (!server) {
22 return res.status(HttpStatusCode.NOT_FOUND_404) 22 return res.fail({
23 .send({ error: 'Server host not found.' }) 23 status: HttpStatusCode.NOT_FOUND_404,
24 .end() 24 message: 'Server host not found.'
25 })
25 } 26 }
26 27
27 res.locals.server = server 28 res.locals.server = server
@@ -44,26 +45,26 @@ const contactAdministratorValidator = [
44 if (areValidationErrors(req, res)) return 45 if (areValidationErrors(req, res)) return
45 46
46 if (CONFIG.CONTACT_FORM.ENABLED === false) { 47 if (CONFIG.CONTACT_FORM.ENABLED === false) {
47 return res 48 return res.fail({
48 .status(HttpStatusCode.CONFLICT_409) 49 status: HttpStatusCode.CONFLICT_409,
49 .send({ error: 'Contact form is not enabled on this instance.' }) 50 message: 'Contact form is not enabled on this instance.'
50 .end() 51 })
51 } 52 }
52 53
53 if (isEmailEnabled() === false) { 54 if (isEmailEnabled() === false) {
54 return res 55 return res.fail({
55 .status(HttpStatusCode.CONFLICT_409) 56 status: HttpStatusCode.CONFLICT_409,
56 .send({ error: 'Emailer is not enabled on this instance.' }) 57 message: 'Emailer is not enabled on this instance.'
57 .end() 58 })
58 } 59 }
59 60
60 if (await Redis.Instance.doesContactFormIpExist(req.ip)) { 61 if (await Redis.Instance.doesContactFormIpExist(req.ip)) {
61 logger.info('Refusing a contact form by %s: already sent one recently.', req.ip) 62 logger.info('Refusing a contact form by %s: already sent one recently.', req.ip)
62 63
63 return res 64 return res.fail({
64 .status(HttpStatusCode.FORBIDDEN_403) 65 status: HttpStatusCode.FORBIDDEN_403,
65 .send({ error: 'You already sent a contact form recently.' }) 66 message: 'You already sent a contact form recently.'
66 .end() 67 })
67 } 68 }
68 69
69 return next() 70 return next()
diff --git a/server/middlewares/validators/themes.ts b/server/middlewares/validators/themes.ts
index a726a567b..91ec0d7ac 100644
--- a/server/middlewares/validators/themes.ts
+++ b/server/middlewares/validators/themes.ts
@@ -20,11 +20,17 @@ const serveThemeCSSValidator = [
20 const theme = PluginManager.Instance.getRegisteredThemeByShortName(req.params.themeName) 20 const theme = PluginManager.Instance.getRegisteredThemeByShortName(req.params.themeName)
21 21
22 if (!theme || theme.version !== req.params.themeVersion) { 22 if (!theme || theme.version !== req.params.themeVersion) {
23 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 23 return res.fail({
24 status: HttpStatusCode.NOT_FOUND_404,
25 message: 'No theme named ' + req.params.themeName + ' was found with version ' + req.params.themeVersion
26 })
24 } 27 }
25 28
26 if (theme.css.includes(req.params.staticEndpoint) === false) { 29 if (theme.css.includes(req.params.staticEndpoint) === false) {
27 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 30 return res.fail({
31 status: HttpStatusCode.NOT_FOUND_404,
32 message: 'No static endpoint was found for this theme'
33 })
28 } 34 }
29 35
30 res.locals.registeredPlugin = theme 36 res.locals.registeredPlugin = theme
diff --git a/server/middlewares/validators/user-subscriptions.ts b/server/middlewares/validators/user-subscriptions.ts
index 1823892b6..5f928b05b 100644
--- a/server/middlewares/validators/user-subscriptions.ts
+++ b/server/middlewares/validators/user-subscriptions.ts
@@ -61,11 +61,10 @@ const userSubscriptionGetValidator = [
61 const subscription = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(user.Account.Actor.id, name, host) 61 const subscription = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(user.Account.Actor.id, name, host)
62 62
63 if (!subscription || !subscription.ActorFollowing.VideoChannel) { 63 if (!subscription || !subscription.ActorFollowing.VideoChannel) {
64 return res 64 return res.fail({
65 .status(HttpStatusCode.NOT_FOUND_404) 65 status: HttpStatusCode.NOT_FOUND_404,
66 .json({ 66 message: `Subscription ${req.params.uri} not found.`
67 error: `Subscription ${req.params.uri} not found.` 67 })
68 })
69 } 68 }
70 69
71 res.locals.subscription = subscription 70 res.locals.subscription = subscription
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 548d5df4d..0eb9172c4 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -73,23 +73,23 @@ const usersAddValidator = [
73 73
74 const authUser = res.locals.oauth.token.User 74 const authUser = res.locals.oauth.token.User
75 if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) { 75 if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) {
76 return res 76 return res.fail({
77 .status(HttpStatusCode.FORBIDDEN_403) 77 status: HttpStatusCode.FORBIDDEN_403,
78 .json({ error: 'You can only create users (and not administrators or moderators)' }) 78 message: 'You can only create users (and not administrators or moderators)'
79 })
79 } 80 }
80 81
81 if (req.body.channelName) { 82 if (req.body.channelName) {
82 if (req.body.channelName === req.body.username) { 83 if (req.body.channelName === req.body.username) {
83 return res 84 return res.fail({ message: 'Channel name cannot be the same as user username.' })
84 .status(HttpStatusCode.BAD_REQUEST_400)
85 .json({ error: 'Channel name cannot be the same as user username.' })
86 } 85 }
87 86
88 const existing = await ActorModel.loadLocalByName(req.body.channelName) 87 const existing = await ActorModel.loadLocalByName(req.body.channelName)
89 if (existing) { 88 if (existing) {
90 return res 89 return res.fail({
91 .status(HttpStatusCode.CONFLICT_409) 90 status: HttpStatusCode.CONFLICT_409,
92 .json({ error: `Channel with name ${req.body.channelName} already exists.` }) 91 message: `Channel with name ${req.body.channelName} already exists.`
92 })
93 } 93 }
94 } 94 }
95 95
@@ -121,20 +121,19 @@ const usersRegisterValidator = [
121 const body: UserRegister = req.body 121 const body: UserRegister = req.body
122 if (body.channel) { 122 if (body.channel) {
123 if (!body.channel.name || !body.channel.displayName) { 123 if (!body.channel.name || !body.channel.displayName) {
124 return res 124 return res.fail({ message: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
125 .status(HttpStatusCode.BAD_REQUEST_400)
126 .json({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
127 } 125 }
128 126
129 if (body.channel.name === body.username) { 127 if (body.channel.name === body.username) {
130 return res.status(HttpStatusCode.BAD_REQUEST_400) 128 return res.fail({ message: 'Channel name cannot be the same as user username.' })
131 .json({ error: 'Channel name cannot be the same as user username.' })
132 } 129 }
133 130
134 const existing = await ActorModel.loadLocalByName(body.channel.name) 131 const existing = await ActorModel.loadLocalByName(body.channel.name)
135 if (existing) { 132 if (existing) {
136 return res.status(HttpStatusCode.CONFLICT_409) 133 return res.fail({
137 .json({ error: `Channel with name ${body.channel.name} already exists.` }) 134 status: HttpStatusCode.CONFLICT_409,
135 message: `Channel with name ${body.channel.name} already exists.`
136 })
138 } 137 }
139 } 138 }
140 139
@@ -153,8 +152,7 @@ const usersRemoveValidator = [
153 152
154 const user = res.locals.user 153 const user = res.locals.user
155 if (user.username === 'root') { 154 if (user.username === 'root') {
156 return res.status(HttpStatusCode.BAD_REQUEST_400) 155 return res.fail({ message: 'Cannot remove the root user' })
157 .json({ error: 'Cannot remove the root user' })
158 } 156 }
159 157
160 return next() 158 return next()
@@ -173,8 +171,7 @@ const usersBlockingValidator = [
173 171
174 const user = res.locals.user 172 const user = res.locals.user
175 if (user.username === 'root') { 173 if (user.username === 'root') {
176 return res.status(HttpStatusCode.BAD_REQUEST_400) 174 return res.fail({ message: 'Cannot block the root user' })
177 .json({ error: 'Cannot block the root user' })
178 } 175 }
179 176
180 return next() 177 return next()
@@ -185,9 +182,7 @@ const deleteMeValidator = [
185 (req: express.Request, res: express.Response, next: express.NextFunction) => { 182 (req: express.Request, res: express.Response, next: express.NextFunction) => {
186 const user = res.locals.oauth.token.User 183 const user = res.locals.oauth.token.User
187 if (user.username === 'root') { 184 if (user.username === 'root') {
188 return res.status(HttpStatusCode.BAD_REQUEST_400) 185 return res.fail({ message: 'You cannot delete your root account.' })
189 .json({ error: 'You cannot delete your root account.' })
190 .end()
191 } 186 }
192 187
193 return next() 188 return next()
@@ -217,8 +212,7 @@ const usersUpdateValidator = [
217 212
218 const user = res.locals.user 213 const user = res.locals.user
219 if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) { 214 if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) {
220 return res.status(HttpStatusCode.BAD_REQUEST_400) 215 return res.fail({ message: 'Cannot change root role.' })
221 .json({ error: 'Cannot change root role.' })
222 } 216 }
223 217
224 return next() 218 return next()
@@ -273,18 +267,18 @@ const usersUpdateMeValidator = [
273 267
274 if (req.body.password || req.body.email) { 268 if (req.body.password || req.body.email) {
275 if (user.pluginAuth !== null) { 269 if (user.pluginAuth !== null) {
276 return res.status(HttpStatusCode.BAD_REQUEST_400) 270 return res.fail({ message: 'You cannot update your email or password that is associated with an external auth system.' })
277 .json({ error: 'You cannot update your email or password that is associated with an external auth system.' })
278 } 271 }
279 272
280 if (!req.body.currentPassword) { 273 if (!req.body.currentPassword) {
281 return res.status(HttpStatusCode.BAD_REQUEST_400) 274 return res.fail({ message: 'currentPassword parameter is missing.' })
282 .json({ error: 'currentPassword parameter is missing.' })
283 } 275 }
284 276
285 if (await user.isPasswordMatch(req.body.currentPassword) !== true) { 277 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
286 return res.status(HttpStatusCode.UNAUTHORIZED_401) 278 return res.fail({
287 .json({ error: 'currentPassword is invalid.' }) 279 status: HttpStatusCode.UNAUTHORIZED_401,
280 message: 'currentPassword is invalid.'
281 })
288 } 282 }
289 } 283 }
290 284
@@ -335,8 +329,10 @@ const ensureUserRegistrationAllowed = [
335 ) 329 )
336 330
337 if (allowedResult.allowed === false) { 331 if (allowedResult.allowed === false) {
338 return res.status(HttpStatusCode.FORBIDDEN_403) 332 return res.fail({
339 .json({ error: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.' }) 333 status: HttpStatusCode.FORBIDDEN_403,
334 message: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.'
335 })
340 } 336 }
341 337
342 return next() 338 return next()
@@ -348,8 +344,10 @@ const ensureUserRegistrationAllowedForIP = [
348 const allowed = isSignupAllowedForCurrentIP(req.ip) 344 const allowed = isSignupAllowedForCurrentIP(req.ip)
349 345
350 if (allowed === false) { 346 if (allowed === false) {
351 return res.status(HttpStatusCode.FORBIDDEN_403) 347 return res.fail({
352 .json({ error: 'You are not on a network authorized for registration.' }) 348 status: HttpStatusCode.FORBIDDEN_403,
349 message: 'You are not on a network authorized for registration.'
350 })
353 } 351 }
354 352
355 return next() 353 return next()
@@ -390,9 +388,10 @@ const usersResetPasswordValidator = [
390 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id) 388 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id)
391 389
392 if (redisVerificationString !== req.body.verificationString) { 390 if (redisVerificationString !== req.body.verificationString) {
393 return res 391 return res.fail({
394 .status(HttpStatusCode.FORBIDDEN_403) 392 status: HttpStatusCode.FORBIDDEN_403,
395 .json({ error: 'Invalid verification string.' }) 393 message: 'Invalid verification string.'
394 })
396 } 395 }
397 396
398 return next() 397 return next()
@@ -437,9 +436,10 @@ const usersVerifyEmailValidator = [
437 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id) 436 const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id)
438 437
439 if (redisVerificationString !== req.body.verificationString) { 438 if (redisVerificationString !== req.body.verificationString) {
440 return res 439 return res.fail({
441 .status(HttpStatusCode.FORBIDDEN_403) 440 status: HttpStatusCode.FORBIDDEN_403,
442 .json({ error: 'Invalid verification string.' }) 441 message: 'Invalid verification string.'
442 })
443 } 443 }
444 444
445 return next() 445 return next()
@@ -455,8 +455,10 @@ const ensureAuthUserOwnsAccountValidator = [
455 const user = res.locals.oauth.token.User 455 const user = res.locals.oauth.token.User
456 456
457 if (res.locals.account.id !== user.Account.id) { 457 if (res.locals.account.id !== user.Account.id) {
458 return res.status(HttpStatusCode.FORBIDDEN_403) 458 return res.fail({
459 .json({ error: 'Only owner can access ratings list.' }) 459 status: HttpStatusCode.FORBIDDEN_403,
460 message: 'Only owner can access ratings list.'
461 })
460 } 462 }
461 463
462 return next() 464 return next()
@@ -471,8 +473,10 @@ const ensureCanManageUser = [
471 if (authUser.role === UserRole.ADMINISTRATOR) return next() 473 if (authUser.role === UserRole.ADMINISTRATOR) return next()
472 if (authUser.role === UserRole.MODERATOR && onUser.role === UserRole.USER) return next() 474 if (authUser.role === UserRole.MODERATOR && onUser.role === UserRole.USER) return next()
473 475
474 return res.status(HttpStatusCode.FORBIDDEN_403) 476 return res.fail({
475 .json({ error: 'A moderator can only manager users.' }) 477 status: HttpStatusCode.FORBIDDEN_403,
478 message: 'A moderator can only manager users.'
479 })
476 } 480 }
477] 481]
478 482
@@ -515,15 +519,19 @@ async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email:
515 const user = await UserModel.loadByUsernameOrEmail(username, email) 519 const user = await UserModel.loadByUsernameOrEmail(username, email)
516 520
517 if (user) { 521 if (user) {
518 res.status(HttpStatusCode.CONFLICT_409) 522 res.fail({
519 .json({ error: 'User with this username or email already exists.' }) 523 status: HttpStatusCode.CONFLICT_409,
524 message: 'User with this username or email already exists.'
525 })
520 return false 526 return false
521 } 527 }
522 528
523 const actor = await ActorModel.loadLocalByName(username) 529 const actor = await ActorModel.loadLocalByName(username)
524 if (actor) { 530 if (actor) {
525 res.status(HttpStatusCode.CONFLICT_409) 531 res.fail({
526 .json({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' }) 532 status: HttpStatusCode.CONFLICT_409,
533 message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.'
534 })
527 return false 535 return false
528 } 536 }
529 537
@@ -535,14 +543,15 @@ async function checkUserExist (finder: () => Promise<MUserDefault>, res: express
535 543
536 if (!user) { 544 if (!user) {
537 if (abortResponse === true) { 545 if (abortResponse === true) {
538 res.status(HttpStatusCode.NOT_FOUND_404) 546 res.fail({
539 .json({ error: 'User not found' }) 547 status: HttpStatusCode.NOT_FOUND_404,
548 message: 'User not found'
549 })
540 } 550 }
541 551
542 return false 552 return false
543 } 553 }
544 554
545 res.locals.user = user 555 res.locals.user = user
546
547 return true 556 return true
548} 557}
diff --git a/server/middlewares/validators/utils.ts b/server/middlewares/validators/utils.ts
index 4167f6d43..e291f1b17 100644
--- a/server/middlewares/validators/utils.ts
+++ b/server/middlewares/validators/utils.ts
@@ -1,15 +1,19 @@
1import * as express from 'express' 1import * as express from 'express'
2import { query, validationResult } from 'express-validator' 2import { query, validationResult } from 'express-validator'
3import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
4import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
5 4
6function areValidationErrors (req: express.Request, res: express.Response) { 5function areValidationErrors (req: express.Request, res: express.Response) {
7 const errors = validationResult(req) 6 const errors = validationResult(req)
8 7
9 if (!errors.isEmpty()) { 8 if (!errors.isEmpty()) {
10 logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() }) 9 logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() })
11 res.status(HttpStatusCode.BAD_REQUEST_400) 10 res.fail({
12 .json({ errors: errors.mapped() }) 11 message: 'Incorrect request parameters: ' + Object.keys(errors.mapped()).join(', '),
12 instance: req.originalUrl,
13 data: {
14 'invalid-params': errors.mapped()
15 }
16 })
13 17
14 return true 18 return true
15 } 19 }
diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts
index 88c788a43..65132a09f 100644
--- a/server/middlewares/validators/videos/video-blacklist.ts
+++ b/server/middlewares/validators/videos/video-blacklist.ts
@@ -39,10 +39,10 @@ const videosBlacklistAddValidator = [
39 39
40 const video = res.locals.videoAll 40 const video = res.locals.videoAll
41 if (req.body.unfederate === true && video.remote === true) { 41 if (req.body.unfederate === true && video.remote === true) {
42 return res 42 return res.fail({
43 .status(HttpStatusCode.CONFLICT_409) 43 status: HttpStatusCode.CONFLICT_409,
44 .send({ error: 'You cannot unfederate a remote video.' }) 44 message: 'You cannot unfederate a remote video.'
45 .end() 45 })
46 } 46 }
47 47
48 return next() 48 return next()
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts
index e881f0d3e..331a51007 100644
--- a/server/middlewares/validators/videos/video-channels.ts
+++ b/server/middlewares/validators/videos/video-channels.ts
@@ -30,17 +30,16 @@ const videoChannelsAddValidator = [
30 30
31 const actor = await ActorModel.loadLocalByName(req.body.name) 31 const actor = await ActorModel.loadLocalByName(req.body.name)
32 if (actor) { 32 if (actor) {
33 res.status(HttpStatusCode.CONFLICT_409) 33 res.fail({
34 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' }) 34 status: HttpStatusCode.CONFLICT_409,
35 .end() 35 message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.'
36 })
36 return false 37 return false
37 } 38 }
38 39
39 const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) 40 const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
40 if (count >= VIDEO_CHANNELS.MAX_PER_USER) { 41 if (count >= VIDEO_CHANNELS.MAX_PER_USER) {
41 res.status(HttpStatusCode.BAD_REQUEST_400) 42 res.fail({ message: `You cannot create more than ${VIDEO_CHANNELS.MAX_PER_USER} channels` })
42 .send({ error: `You cannot create more than ${VIDEO_CHANNELS.MAX_PER_USER} channels` })
43 .end()
44 return false 43 return false
45 } 44 }
46 45
@@ -71,13 +70,17 @@ const videoChannelsUpdateValidator = [
71 70
72 // We need to make additional checks 71 // We need to make additional checks
73 if (res.locals.videoChannel.Actor.isOwned() === false) { 72 if (res.locals.videoChannel.Actor.isOwned() === false) {
74 return res.status(HttpStatusCode.FORBIDDEN_403) 73 return res.fail({
75 .json({ error: 'Cannot update video channel of another server' }) 74 status: HttpStatusCode.FORBIDDEN_403,
75 message: 'Cannot update video channel of another server'
76 })
76 } 77 }
77 78
78 if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) { 79 if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) {
79 return res.status(HttpStatusCode.FORBIDDEN_403) 80 return res.fail({
80 .json({ error: 'Cannot update video channel of another user' }) 81 status: HttpStatusCode.FORBIDDEN_403,
82 message: 'Cannot update video channel of another user'
83 })
81 } 84 }
82 85
83 return next() 86 return next()
@@ -154,10 +157,10 @@ export {
154 157
155function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) { 158function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) {
156 if (videoChannel.Actor.isOwned() === false) { 159 if (videoChannel.Actor.isOwned() === false) {
157 res.status(HttpStatusCode.FORBIDDEN_403) 160 res.fail({
158 .json({ error: 'Cannot remove video channel of another server.' }) 161 status: HttpStatusCode.FORBIDDEN_403,
159 .end() 162 message: 'Cannot remove video channel of another server.'
160 163 })
161 return false 164 return false
162 } 165 }
163 166
@@ -165,10 +168,10 @@ function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAcco
165 // The user can delete it if s/he is an admin 168 // The user can delete it if s/he is an admin
166 // Or if s/he is the video channel's account 169 // Or if s/he is the video channel's account
167 if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) { 170 if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) {
168 res.status(HttpStatusCode.FORBIDDEN_403) 171 res.fail({
169 .json({ error: 'Cannot remove video channel of another user' }) 172 status: HttpStatusCode.FORBIDDEN_403,
170 .end() 173 message: 'Cannot remove video channel of another user'
171 174 })
172 return false 175 return false
173 } 176 }
174 177
@@ -179,10 +182,10 @@ async function checkVideoChannelIsNotTheLastOne (res: express.Response) {
179 const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) 182 const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
180 183
181 if (count <= 1) { 184 if (count <= 1) {
182 res.status(HttpStatusCode.CONFLICT_409) 185 res.fail({
183 .json({ error: 'Cannot remove the last channel of this user' }) 186 status: HttpStatusCode.CONFLICT_409,
184 .end() 187 message: 'Cannot remove the last channel of this user'
185 188 })
186 return false 189 return false
187 } 190 }
188 191
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 1afacfed8..aac25a787 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -155,9 +155,10 @@ export {
155 155
156function isVideoCommentsEnabled (video: MVideo, res: express.Response) { 156function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
157 if (video.commentsEnabled !== true) { 157 if (video.commentsEnabled !== true) {
158 res.status(HttpStatusCode.CONFLICT_409) 158 res.fail({
159 .json({ error: 'Video comments are disabled for this video.' }) 159 status: HttpStatusCode.CONFLICT_409,
160 160 message: 'Video comments are disabled for this video.'
161 })
161 return false 162 return false
162 } 163 }
163 164
@@ -166,9 +167,10 @@ function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
166 167
167function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) { 168function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) {
168 if (videoComment.isDeleted()) { 169 if (videoComment.isDeleted()) {
169 res.status(HttpStatusCode.CONFLICT_409) 170 res.fail({
170 .json({ error: 'This comment is already deleted' }) 171 status: HttpStatusCode.CONFLICT_409,
171 172 message: 'This comment is already deleted'
173 })
172 return false 174 return false
173 } 175 }
174 176
@@ -179,9 +181,10 @@ function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MC
179 videoComment.accountId !== userAccount.id && // Not the comment owner 181 videoComment.accountId !== userAccount.id && // Not the comment owner
180 videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner 182 videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner
181 ) { 183 ) {
182 res.status(HttpStatusCode.FORBIDDEN_403) 184 res.fail({
183 .json({ error: 'Cannot remove video comment of another user' }) 185 status: HttpStatusCode.FORBIDDEN_403,
184 186 message: 'Cannot remove video comment of another user'
187 })
185 return false 188 return false
186 } 189 }
187 190
@@ -215,9 +218,11 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon
215 218
216 if (!acceptedResult || acceptedResult.accepted !== true) { 219 if (!acceptedResult || acceptedResult.accepted !== true) {
217 logger.info('Refused local comment.', { acceptedResult, acceptParameters }) 220 logger.info('Refused local comment.', { acceptedResult, acceptParameters })
218 res.status(HttpStatusCode.FORBIDDEN_403)
219 .json({ error: acceptedResult?.errorMessage || 'Refused local comment' })
220 221
222 res.fail({
223 status: HttpStatusCode.FORBIDDEN_403,
224 message: acceptedResult?.errorMessage || 'Refused local comment'
225 })
221 return false 226 return false
222 } 227 }
223 228
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
index a5e3ffbcd..55ff09124 100644
--- a/server/middlewares/validators/videos/video-imports.ts
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -47,14 +47,20 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
47 47
48 if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true && req.body.targetUrl) { 48 if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true && req.body.targetUrl) {
49 cleanUpReqFiles(req) 49 cleanUpReqFiles(req)
50 return res.status(HttpStatusCode.CONFLICT_409) 50
51 .json({ error: 'HTTP import is not enabled on this instance.' }) 51 return res.fail({
52 status: HttpStatusCode.CONFLICT_409,
53 message: 'HTTP import is not enabled on this instance.'
54 })
52 } 55 }
53 56
54 if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) { 57 if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) {
55 cleanUpReqFiles(req) 58 cleanUpReqFiles(req)
56 return res.status(HttpStatusCode.CONFLICT_409) 59
57 .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' }) 60 return res.fail({
61 status: HttpStatusCode.CONFLICT_409,
62 message: 'Torrent/magnet URI import is not enabled on this instance.'
63 })
58 } 64 }
59 65
60 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) 66 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
@@ -63,8 +69,7 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
63 if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) { 69 if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) {
64 cleanUpReqFiles(req) 70 cleanUpReqFiles(req)
65 71
66 return res.status(HttpStatusCode.BAD_REQUEST_400) 72 return res.fail({ message: 'Should have a magnetUri or a targetUrl or a torrent file.' })
67 .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' })
68 } 73 }
69 74
70 if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) 75 if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req)
@@ -100,9 +105,11 @@ async function isImportAccepted (req: express.Request, res: express.Response) {
100 105
101 if (!acceptedResult || acceptedResult.accepted !== true) { 106 if (!acceptedResult || acceptedResult.accepted !== true) {
102 logger.info('Refused to import video.', { acceptedResult, acceptParameters }) 107 logger.info('Refused to import video.', { acceptedResult, acceptParameters })
103 res.status(HttpStatusCode.FORBIDDEN_403)
104 .json({ error: acceptedResult.errorMessage || 'Refused to import video' })
105 108
109 res.fail({
110 status: HttpStatusCode.FORBIDDEN_403,
111 message: acceptedResult.errorMessage || 'Refused to import video'
112 })
106 return false 113 return false
107 } 114 }
108 115
diff --git a/server/middlewares/validators/videos/video-live.ts b/server/middlewares/validators/videos/video-live.ts
index ec4c7f32f..9544fa4f5 100644
--- a/server/middlewares/validators/videos/video-live.ts
+++ b/server/middlewares/validators/videos/video-live.ts
@@ -30,7 +30,12 @@ const videoLiveGetValidator = [
30 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.GET_ANY_LIVE, res, false)) return 30 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.GET_ANY_LIVE, res, false)) return
31 31
32 const videoLive = await VideoLiveModel.loadByVideoId(res.locals.videoAll.id) 32 const videoLive = await VideoLiveModel.loadByVideoId(res.locals.videoAll.id)
33 if (!videoLive) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 33 if (!videoLive) {
34 return res.fail({
35 status: HttpStatusCode.NOT_FOUND_404,
36 message: 'Live video not found'
37 })
38 }
34 39
35 res.locals.videoLive = videoLive 40 res.locals.videoLive = videoLive
36 41
@@ -66,22 +71,25 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([
66 if (CONFIG.LIVE.ENABLED !== true) { 71 if (CONFIG.LIVE.ENABLED !== true) {
67 cleanUpReqFiles(req) 72 cleanUpReqFiles(req)
68 73
69 return res.status(HttpStatusCode.FORBIDDEN_403) 74 return res.fail({
70 .json({ error: 'Live is not enabled on this instance' }) 75 status: HttpStatusCode.FORBIDDEN_403,
76 message: 'Live is not enabled on this instance'
77 })
71 } 78 }
72 79
73 if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) { 80 if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) {
74 cleanUpReqFiles(req) 81 cleanUpReqFiles(req)
75 82
76 return res.status(HttpStatusCode.FORBIDDEN_403) 83 return res.fail({
77 .json({ error: 'Saving live replay is not allowed instance' }) 84 status: HttpStatusCode.FORBIDDEN_403,
85 message: 'Saving live replay is not allowed instance'
86 })
78 } 87 }
79 88
80 if (req.body.permanentLive && req.body.saveReplay) { 89 if (req.body.permanentLive && req.body.saveReplay) {
81 cleanUpReqFiles(req) 90 cleanUpReqFiles(req)
82 91
83 return res.status(HttpStatusCode.BAD_REQUEST_400) 92 return res.fail({ message: 'Cannot set this live as permanent while saving its replay' })
84 .json({ error: 'Cannot set this live as permanent while saving its replay' })
85 } 93 }
86 94
87 const user = res.locals.oauth.token.User 95 const user = res.locals.oauth.token.User
@@ -93,11 +101,11 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([
93 if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) { 101 if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) {
94 cleanUpReqFiles(req) 102 cleanUpReqFiles(req)
95 103
96 return res.status(HttpStatusCode.FORBIDDEN_403) 104 return res.fail({
97 .json({ 105 status: HttpStatusCode.FORBIDDEN_403,
98 code: ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED, 106 message: 'Cannot create this live because the max instance lives limit is reached.',
99 error: 'Cannot create this live because the max instance lives limit is reached.' 107 type: ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED.toString()
100 }) 108 })
101 } 109 }
102 } 110 }
103 111
@@ -107,11 +115,11 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([
107 if (totalUserLives >= CONFIG.LIVE.MAX_USER_LIVES) { 115 if (totalUserLives >= CONFIG.LIVE.MAX_USER_LIVES) {
108 cleanUpReqFiles(req) 116 cleanUpReqFiles(req)
109 117
110 return res.status(HttpStatusCode.FORBIDDEN_403) 118 return res.fail({
111 .json({ 119 status: HttpStatusCode.FORBIDDEN_403,
112 code: ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED, 120 type: ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED.toString(),
113 error: 'Cannot create this live because the max user lives limit is reached.' 121 message: 'Cannot create this live because the max user lives limit is reached.'
114 }) 122 })
115 } 123 }
116 } 124 }
117 125
@@ -133,18 +141,18 @@ const videoLiveUpdateValidator = [
133 if (areValidationErrors(req, res)) return 141 if (areValidationErrors(req, res)) return
134 142
135 if (req.body.permanentLive && req.body.saveReplay) { 143 if (req.body.permanentLive && req.body.saveReplay) {
136 return res.status(HttpStatusCode.BAD_REQUEST_400) 144 return res.fail({ message: 'Cannot set this live as permanent while saving its replay' })
137 .json({ error: 'Cannot set this live as permanent while saving its replay' })
138 } 145 }
139 146
140 if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) { 147 if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) {
141 return res.status(HttpStatusCode.FORBIDDEN_403) 148 return res.fail({
142 .json({ error: 'Saving live replay is not allowed instance' }) 149 status: HttpStatusCode.FORBIDDEN_403,
150 message: 'Saving live replay is not allowed instance'
151 })
143 } 152 }
144 153
145 if (res.locals.videoAll.state !== VideoState.WAITING_FOR_LIVE) { 154 if (res.locals.videoAll.state !== VideoState.WAITING_FOR_LIVE) {
146 return res.status(HttpStatusCode.BAD_REQUEST_400) 155 return res.fail({ message: 'Cannot update a live that has already started' })
147 .json({ error: 'Cannot update a live that has already started' })
148 } 156 }
149 157
150 // Check the user can manage the live 158 // Check the user can manage the live
@@ -180,9 +188,10 @@ async function isLiveVideoAccepted (req: express.Request, res: express.Response)
180 if (!acceptedResult || acceptedResult.accepted !== true) { 188 if (!acceptedResult || acceptedResult.accepted !== true) {
181 logger.info('Refused local live video.', { acceptedResult, acceptParameters }) 189 logger.info('Refused local live video.', { acceptedResult, acceptParameters })
182 190
183 res.status(HttpStatusCode.FORBIDDEN_403) 191 res.fail({
184 .json({ error: acceptedResult.errorMessage || 'Refused local live video' }) 192 status: HttpStatusCode.FORBIDDEN_403,
185 193 message: acceptedResult.errorMessage || 'Refused local live video'
194 })
186 return false 195 return false
187 } 196 }
188 197
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index c872d045e..90815dd3a 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -46,8 +46,8 @@ const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
46 46
47 if (body.privacy === VideoPlaylistPrivacy.PUBLIC && !body.videoChannelId) { 47 if (body.privacy === VideoPlaylistPrivacy.PUBLIC && !body.videoChannelId) {
48 cleanUpReqFiles(req) 48 cleanUpReqFiles(req)
49 return res.status(HttpStatusCode.BAD_REQUEST_400) 49
50 .json({ error: 'Cannot set "public" a playlist that is not assigned to a channel.' }) 50 return res.fail({ message: 'Cannot set "public" a playlist that is not assigned to a channel.' })
51 } 51 }
52 52
53 return next() 53 return next()
@@ -85,14 +85,14 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
85 ) 85 )
86 ) { 86 ) {
87 cleanUpReqFiles(req) 87 cleanUpReqFiles(req)
88 return res.status(HttpStatusCode.BAD_REQUEST_400) 88
89 .json({ error: 'Cannot set "public" a playlist that is not assigned to a channel.' }) 89 return res.fail({ message: 'Cannot set "public" a playlist that is not assigned to a channel.' })
90 } 90 }
91 91
92 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { 92 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
93 cleanUpReqFiles(req) 93 cleanUpReqFiles(req)
94 return res.status(HttpStatusCode.BAD_REQUEST_400) 94
95 .json({ error: 'Cannot update a watch later playlist.' }) 95 return res.fail({ message: 'Cannot update a watch later playlist.' })
96 } 96 }
97 97
98 if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req) 98 if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req)
@@ -114,8 +114,7 @@ const videoPlaylistsDeleteValidator = [
114 114
115 const videoPlaylist = getPlaylist(res) 115 const videoPlaylist = getPlaylist(res)
116 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { 116 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
117 return res.status(HttpStatusCode.BAD_REQUEST_400) 117 return res.fail({ message: 'Cannot delete a watch later playlist.' })
118 .json({ error: 'Cannot delete a watch later playlist.' })
119 } 118 }
120 119
121 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { 120 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
@@ -144,7 +143,10 @@ const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => {
144 if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { 143 if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) {
145 if (isUUIDValid(req.params.playlistId)) return next() 144 if (isUUIDValid(req.params.playlistId)) return next()
146 145
147 return res.status(HttpStatusCode.NOT_FOUND_404).end() 146 return res.fail({
147 status: HttpStatusCode.NOT_FOUND_404,
148 message: 'Playlist not found'
149 })
148 } 150 }
149 151
150 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { 152 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
@@ -156,8 +158,10 @@ const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => {
156 !user || 158 !user ||
157 (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) 159 (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST))
158 ) { 160 ) {
159 return res.status(HttpStatusCode.FORBIDDEN_403) 161 return res.fail({
160 .json({ error: 'Cannot get this private video playlist.' }) 162 status: HttpStatusCode.FORBIDDEN_403,
163 message: 'Cannot get this private video playlist.'
164 })
161 } 165 }
162 166
163 return next() 167 return next()
@@ -233,10 +237,10 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [
233 237
234 const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) 238 const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId)
235 if (!videoPlaylistElement) { 239 if (!videoPlaylistElement) {
236 res.status(HttpStatusCode.NOT_FOUND_404) 240 res.fail({
237 .json({ error: 'Video playlist element not found' }) 241 status: HttpStatusCode.NOT_FOUND_404,
238 .end() 242 message: 'Video playlist element not found'
239 243 })
240 return 244 return
241 } 245 }
242 res.locals.videoPlaylistElement = videoPlaylistElement 246 res.locals.videoPlaylistElement = videoPlaylistElement
@@ -263,15 +267,18 @@ const videoPlaylistElementAPGetValidator = [
263 267
264 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId) 268 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId)
265 if (!videoPlaylistElement) { 269 if (!videoPlaylistElement) {
266 res.status(HttpStatusCode.NOT_FOUND_404) 270 res.fail({
267 .json({ error: 'Video playlist element not found' }) 271 status: HttpStatusCode.NOT_FOUND_404,
268 .end() 272 message: 'Video playlist element not found'
269 273 })
270 return 274 return
271 } 275 }
272 276
273 if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { 277 if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
274 return res.status(HttpStatusCode.FORBIDDEN_403).end() 278 return res.fail({
279 status: HttpStatusCode.FORBIDDEN_403,
280 message: 'Cannot get this private video playlist.'
281 })
275 } 282 }
276 283
277 res.locals.videoPlaylistElementAP = videoPlaylistElement 284 res.locals.videoPlaylistElementAP = videoPlaylistElement
@@ -307,18 +314,12 @@ const videoPlaylistsReorderVideosValidator = [
307 const reorderLength: number = req.body.reorderLength 314 const reorderLength: number = req.body.reorderLength
308 315
309 if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) { 316 if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) {
310 res.status(HttpStatusCode.BAD_REQUEST_400) 317 res.fail({ message: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` })
311 .json({ error: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` })
312 .end()
313
314 return 318 return
315 } 319 }
316 320
317 if (reorderLength && reorderLength + startPosition > nextPosition) { 321 if (reorderLength && reorderLength + startPosition > nextPosition) {
318 res.status(HttpStatusCode.BAD_REQUEST_400) 322 res.fail({ message: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` })
319 .json({ error: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` })
320 .end()
321
322 return 323 return
323 } 324 }
324 325
@@ -401,10 +402,10 @@ function getCommonPlaylistEditAttributes () {
401 402
402function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) { 403function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) {
403 if (videoPlaylist.isOwned() === false) { 404 if (videoPlaylist.isOwned() === false) {
404 res.status(HttpStatusCode.FORBIDDEN_403) 405 res.fail({
405 .json({ error: 'Cannot manage video playlist of another server.' }) 406 status: HttpStatusCode.FORBIDDEN_403,
406 .end() 407 message: 'Cannot manage video playlist of another server.'
407 408 })
408 return false 409 return false
409 } 410 }
410 411
@@ -412,10 +413,10 @@ function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: M
412 // The user can delete it if s/he is an admin 413 // The user can delete it if s/he is an admin
413 // Or if s/he is the video playlist's owner 414 // Or if s/he is the video playlist's owner
414 if (user.hasRight(right) === false && videoPlaylist.ownerAccountId !== user.Account.id) { 415 if (user.hasRight(right) === false && videoPlaylist.ownerAccountId !== user.Account.id) {
415 res.status(HttpStatusCode.FORBIDDEN_403) 416 res.fail({
416 .json({ error: 'Cannot manage video playlist of another user' }) 417 status: HttpStatusCode.FORBIDDEN_403,
417 .end() 418 message: 'Cannot manage video playlist of another user'
418 419 })
419 return false 420 return false
420 } 421 }
421 422
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts
index 01bdef25f..5c4176f54 100644
--- a/server/middlewares/validators/videos/video-rates.ts
+++ b/server/middlewares/validators/videos/video-rates.ts
@@ -37,8 +37,10 @@ const getAccountVideoRateValidatorFactory = function (rateType: VideoRateType) {
37 37
38 const rate = await AccountVideoRateModel.loadLocalAndPopulateVideo(rateType, req.params.name, +req.params.videoId) 38 const rate = await AccountVideoRateModel.loadLocalAndPopulateVideo(rateType, req.params.name, +req.params.videoId)
39 if (!rate) { 39 if (!rate) {
40 return res.status(HttpStatusCode.NOT_FOUND_404) 40 return res.fail({
41 .json({ error: 'Video rate not found' }) 41 status: HttpStatusCode.NOT_FOUND_404,
42 message: 'Video rate not found'
43 })
42 } 44 }
43 45
44 res.locals.accountVideoRate = rate 46 res.locals.accountVideoRate = rate
diff --git a/server/middlewares/validators/videos/video-watch.ts b/server/middlewares/validators/videos/video-watch.ts
index 29ce0dab6..00c739d31 100644
--- a/server/middlewares/validators/videos/video-watch.ts
+++ b/server/middlewares/validators/videos/video-watch.ts
@@ -21,7 +21,10 @@ const videoWatchingValidator = [
21 const user = res.locals.oauth.token.User 21 const user = res.locals.oauth.token.User
22 if (user.videosHistoryEnabled === false) { 22 if (user.videosHistoryEnabled === false) {
23 logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id) 23 logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id)
24 return res.status(HttpStatusCode.CONFLICT_409).end() 24 return res.fail({
25 status: HttpStatusCode.CONFLICT_409,
26 message: 'Video history is disabled'
27 })
25 } 28 }
26 29
27 return next() 30 return next()
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 8864be269..dfd472400 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -73,6 +73,7 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
73 .custom(isIdValid).withMessage('Should have correct video channel id'), 73 .custom(isIdValid).withMessage('Should have correct video channel id'),
74 74
75 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 75 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
76 res.docs = "https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadLegacy"
76 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) 77 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
77 78
78 if (areValidationErrors(req, res)) return cleanUpReqFiles(req) 79 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
@@ -88,9 +89,11 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
88 if (!videoFile.duration) await addDurationToVideo(videoFile) 89 if (!videoFile.duration) await addDurationToVideo(videoFile)
89 } catch (err) { 90 } catch (err) {
90 logger.error('Invalid input file in videosAddLegacyValidator.', { err }) 91 logger.error('Invalid input file in videosAddLegacyValidator.', { err })
91 res.status(HttpStatusCode.UNPROCESSABLE_ENTITY_422)
92 .json({ error: 'Video file unreadable.' })
93 92
93 res.fail({
94 status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
95 message: 'Video file unreadable.'
96 })
94 return cleanUpReqFiles(req) 97 return cleanUpReqFiles(req)
95 } 98 }
96 99
@@ -105,6 +108,7 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
105 */ 108 */
106const videosAddResumableValidator = [ 109const videosAddResumableValidator = [
107 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 110 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
111 res.docs = "https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadResumable"
108 const user = res.locals.oauth.token.User 112 const user = res.locals.oauth.token.User
109 113
110 const body: express.CustomUploadXFile<express.UploadXFileMetadata> = req.body 114 const body: express.CustomUploadXFile<express.UploadXFileMetadata> = req.body
@@ -118,9 +122,11 @@ const videosAddResumableValidator = [
118 if (!file.duration) await addDurationToVideo(file) 122 if (!file.duration) await addDurationToVideo(file)
119 } catch (err) { 123 } catch (err) {
120 logger.error('Invalid input file in videosAddResumableValidator.', { err }) 124 logger.error('Invalid input file in videosAddResumableValidator.', { err })
121 res.status(HttpStatusCode.UNPROCESSABLE_ENTITY_422)
122 .json({ error: 'Video file unreadable.' })
123 125
126 res.fail({
127 status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
128 message: 'Video file unreadable.'
129 })
124 return cleanup() 130 return cleanup()
125 } 131 }
126 132
@@ -164,6 +170,7 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
164 .withMessage('Should specify the file mimetype'), 170 .withMessage('Should specify the file mimetype'),
165 171
166 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 172 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
173 res.docs = "https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadResumableInit"
167 const videoFileMetadata = { 174 const videoFileMetadata = {
168 mimetype: req.headers['x-upload-content-type'] as string, 175 mimetype: req.headers['x-upload-content-type'] as string,
169 size: +req.headers['x-upload-content-length'], 176 size: +req.headers['x-upload-content-length'],
@@ -207,6 +214,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([
207 .custom(isIdValid).withMessage('Should have correct video channel id'), 214 .custom(isIdValid).withMessage('Should have correct video channel id'),
208 215
209 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 216 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
217 res.docs = 'https://docs.joinpeertube.org/api-rest-reference.html#operation/putVideo'
210 logger.debug('Checking videosUpdate parameters', { parameters: req.body }) 218 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
211 219
212 if (areValidationErrors(req, res)) return cleanUpReqFiles(req) 220 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
@@ -242,12 +250,14 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
242 const serverActor = await getServerActor() 250 const serverActor = await getServerActor()
243 if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next() 251 if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next()
244 252
245 return res.status(HttpStatusCode.FORBIDDEN_403) 253 return res.fail({
246 .json({ 254 status: HttpStatusCode.FORBIDDEN_403,
247 errorCode: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS, 255 message: 'Cannot get this video regarding follow constraints.',
248 error: 'Cannot get this video regarding follow constraints.', 256 type: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS.toString(),
249 originUrl: video.url 257 data: {
250 }) 258 originUrl: video.url
259 }
260 })
251} 261}
252 262
253const videosCustomGetValidator = ( 263const videosCustomGetValidator = (
@@ -258,6 +268,7 @@ const videosCustomGetValidator = (
258 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 268 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
259 269
260 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 270 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
271 res.docs = 'https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo'
261 logger.debug('Checking videosGet parameters', { parameters: req.params }) 272 logger.debug('Checking videosGet parameters', { parameters: req.params })
262 273
263 if (areValidationErrors(req, res)) return 274 if (areValidationErrors(req, res)) return
@@ -276,8 +287,10 @@ const videosCustomGetValidator = (
276 287
277 // Only the owner or a user that have blacklist rights can see the video 288 // Only the owner or a user that have blacklist rights can see the video
278 if (!user || !user.canGetVideo(video)) { 289 if (!user || !user.canGetVideo(video)) {
279 return res.status(HttpStatusCode.FORBIDDEN_403) 290 return res.fail({
280 .json({ error: 'Cannot get this private/internal or blacklisted video.' }) 291 status: HttpStatusCode.FORBIDDEN_403,
292 message: 'Cannot get this private/internal or blacklisted video.'
293 })
281 } 294 }
282 295
283 return next() 296 return next()
@@ -291,7 +304,10 @@ const videosCustomGetValidator = (
291 if (isUUIDValid(req.params.id)) return next() 304 if (isUUIDValid(req.params.id)) return next()
292 305
293 // Don't leak this unlisted video 306 // Don't leak this unlisted video
294 return res.status(HttpStatusCode.NOT_FOUND_404).end() 307 return res.fail({
308 status: HttpStatusCode.NOT_FOUND_404,
309 message: 'Video not found'
310 })
295 } 311 }
296 } 312 }
297 ] 313 ]
@@ -318,6 +334,7 @@ const videosRemoveValidator = [
318 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 334 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
319 335
320 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 336 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
337 res.docs = "https://docs.joinpeertube.org/api-rest-reference.html#operation/delVideo"
321 logger.debug('Checking videosRemove parameters', { parameters: req.params }) 338 logger.debug('Checking videosRemove parameters', { parameters: req.params })
322 339
323 if (areValidationErrors(req, res)) return 340 if (areValidationErrors(req, res)) return
@@ -344,13 +361,11 @@ const videosChangeOwnershipValidator = [
344 361
345 const nextOwner = await AccountModel.loadLocalByName(req.body.username) 362 const nextOwner = await AccountModel.loadLocalByName(req.body.username)
346 if (!nextOwner) { 363 if (!nextOwner) {
347 res.status(HttpStatusCode.BAD_REQUEST_400) 364 res.fail({ message: 'Changing video ownership to a remote account is not supported yet' })
348 .json({ error: 'Changing video ownership to a remote account is not supported yet' })
349
350 return 365 return
351 } 366 }
352 res.locals.nextOwner = nextOwner
353 367
368 res.locals.nextOwner = nextOwner
354 return next() 369 return next()
355 } 370 }
356] 371]
@@ -370,8 +385,10 @@ const videosTerminateChangeOwnershipValidator = [
370 const videoChangeOwnership = res.locals.videoChangeOwnership 385 const videoChangeOwnership = res.locals.videoChangeOwnership
371 386
372 if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) { 387 if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) {
373 res.status(HttpStatusCode.FORBIDDEN_403) 388 res.fail({
374 .json({ error: 'Ownership already accepted or refused' }) 389 status: HttpStatusCode.FORBIDDEN_403,
390 message: 'Ownership already accepted or refused'
391 })
375 return 392 return
376 } 393 }
377 394
@@ -388,9 +405,10 @@ const videosAcceptChangeOwnershipValidator = [
388 const videoChangeOwnership = res.locals.videoChangeOwnership 405 const videoChangeOwnership = res.locals.videoChangeOwnership
389 const isAble = await isAbleToUploadVideo(user.id, videoChangeOwnership.Video.getMaxQualityFile().size) 406 const isAble = await isAbleToUploadVideo(user.id, videoChangeOwnership.Video.getMaxQualityFile().size)
390 if (isAble === false) { 407 if (isAble === false) {
391 res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413) 408 res.fail({
392 .json({ error: 'The user video quota is exceeded with this video.' }) 409 status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
393 410 message: 'The user video quota is exceeded with this video.'
411 })
394 return 412 return
395 } 413 }
396 414
@@ -538,9 +556,10 @@ const commonVideosFiltersValidator = [
538 (req.query.filter === 'all-local' || req.query.filter === 'all') && 556 (req.query.filter === 'all-local' || req.query.filter === 'all') &&
539 (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false) 557 (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)
540 ) { 558 ) {
541 res.status(HttpStatusCode.UNAUTHORIZED_401) 559 res.fail({
542 .json({ error: 'You are not allowed to see all local videos.' }) 560 status: HttpStatusCode.UNAUTHORIZED_401,
543 561 message: 'You are not allowed to see all local videos.'
562 })
544 return 563 return
545 } 564 }
546 565
@@ -581,9 +600,7 @@ function areErrorsInScheduleUpdate (req: express.Request, res: express.Response)
581 if (!req.body.scheduleUpdate.updateAt) { 600 if (!req.body.scheduleUpdate.updateAt) {
582 logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.') 601 logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.')
583 602
584 res.status(HttpStatusCode.BAD_REQUEST_400) 603 res.fail({ message: 'Schedule update at is mandatory.' })
585 .json({ error: 'Schedule update at is mandatory.' })
586
587 return true 604 return true
588 } 605 }
589 } 606 }
@@ -605,26 +622,27 @@ async function commonVideoChecksPass (parameters: {
605 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return false 622 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return false
606 623
607 if (!isVideoFileMimeTypeValid(files)) { 624 if (!isVideoFileMimeTypeValid(files)) {
608 res.status(HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415) 625 res.fail({
609 .json({ 626 status: HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415,
610 error: 'This file is not supported. Please, make sure it is of the following type: ' + 627 message: 'This file is not supported. Please, make sure it is of the following type: ' +
611 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') 628 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
612 }) 629 })
613
614 return false 630 return false
615 } 631 }
616 632
617 if (!isVideoFileSizeValid(videoFileSize.toString())) { 633 if (!isVideoFileSizeValid(videoFileSize.toString())) {
618 res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413) 634 res.fail({
619 .json({ error: 'This file is too large. It exceeds the maximum file size authorized.' }) 635 status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
620 636 message: 'This file is too large. It exceeds the maximum file size authorized.'
637 })
621 return false 638 return false
622 } 639 }
623 640
624 if (await isAbleToUploadVideo(user.id, videoFileSize) === false) { 641 if (await isAbleToUploadVideo(user.id, videoFileSize) === false) {
625 res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413) 642 res.fail({
626 .json({ error: 'The user video quota is exceeded with this video.' }) 643 status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
627 644 message: 'The user video quota is exceeded with this video.'
645 })
628 return false 646 return false
629 } 647 }
630 648
@@ -650,9 +668,10 @@ export async function isVideoAccepted (
650 668
651 if (!acceptedResult || acceptedResult.accepted !== true) { 669 if (!acceptedResult || acceptedResult.accepted !== true) {
652 logger.info('Refused local video.', { acceptedResult, acceptParameters }) 670 logger.info('Refused local video.', { acceptedResult, acceptParameters })
653 res.status(HttpStatusCode.FORBIDDEN_403) 671 res.fail({
654 .json({ error: acceptedResult.errorMessage || 'Refused local video' }) 672 status: HttpStatusCode.FORBIDDEN_403,
655 673 message: acceptedResult.errorMessage || 'Refused local video'
674 })
656 return false 675 return false
657 } 676 }
658 677
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index c2dfccc96..097a5ece1 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -21,9 +21,10 @@ const webfingerValidator = [
21 21
22 const actor = await ActorModel.loadLocalUrlByName(name) 22 const actor = await ActorModel.loadLocalUrlByName(name)
23 if (!actor) { 23 if (!actor) {
24 return res.status(HttpStatusCode.NOT_FOUND_404) 24 return res.fail({
25 .send({ error: 'Actor not found' }) 25 status: HttpStatusCode.NOT_FOUND_404,
26 .end() 26 message: 'Actor not found'
27 })
27 } 28 }
28 29
29 res.locals.actorUrl = actor 30 res.locals.actorUrl = actor