aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/abuse.ts6
-rw-r--r--server/controllers/api/bulk.ts2
-rw-r--r--server/controllers/api/custom-page.ts9
-rw-r--r--server/controllers/api/oauth-clients.ts5
-rw-r--r--server/controllers/api/plugins.ts14
-rw-r--r--server/controllers/api/search.ts10
-rw-r--r--server/controllers/api/server/debug.ts3
-rw-r--r--server/controllers/api/server/redundancy.ts6
-rw-r--r--server/controllers/api/users/index.ts4
-rw-r--r--server/controllers/api/users/me.ts6
-rw-r--r--server/controllers/api/users/token.ts7
-rw-r--r--server/controllers/api/video-channel.ts4
-rw-r--r--server/controllers/api/videos/blacklist.ts6
-rw-r--r--server/controllers/api/videos/comment.ts5
-rw-r--r--server/controllers/api/videos/import.ts21
-rw-r--r--server/controllers/api/videos/index.ts4
-rw-r--r--server/controllers/api/videos/live.ts2
-rw-r--r--server/controllers/api/videos/ownership.ts4
-rw-r--r--server/controllers/api/videos/upload.ts7
-rw-r--r--server/controllers/client.ts4
-rw-r--r--server/controllers/download.ts27
-rw-r--r--server/controllers/lazy-static.ts12
-rw-r--r--server/controllers/live.ts2
-rw-r--r--server/controllers/plugins.ts10
-rw-r--r--server/controllers/static.ts13
-rw-r--r--server/helpers/custom-validators/video-comments.ts39
-rw-r--r--server/helpers/custom-validators/video-imports.ts8
-rw-r--r--server/helpers/custom-validators/video-ownership.ts15
-rw-r--r--server/helpers/express-utils.ts32
-rw-r--r--server/helpers/middlewares/abuses.ts6
-rw-r--r--server/helpers/middlewares/accounts.ts16
-rw-r--r--server/helpers/middlewares/video-blacklists.ts8
-rw-r--r--server/helpers/middlewares/video-captions.ts7
-rw-r--r--server/helpers/middlewares/video-channels.ts7
-rw-r--r--server/helpers/middlewares/video-playlists.ts8
-rw-r--r--server/helpers/middlewares/videos.ts40
-rw-r--r--server/lib/client-html.ts4
-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
-rw-r--r--server/tests/api/users/users-verification.ts5
-rw-r--r--server/tests/api/users/users.ts16
-rw-r--r--server/typings/express/index.d.ts10
69 files changed, 747 insertions, 530 deletions
diff --git a/server/controllers/api/abuse.ts b/server/controllers/api/abuse.ts
index 0ab74bdff..108627f81 100644
--- a/server/controllers/api/abuse.ts
+++ b/server/controllers/api/abuse.ts
@@ -142,7 +142,7 @@ async function updateAbuse (req: express.Request, res: express.Response) {
142 142
143 // Do not send the delete to other instances, we updated OUR copy of this abuse 143 // Do not send the delete to other instances, we updated OUR copy of this abuse
144 144
145 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 145 return res.status(HttpStatusCode.NO_CONTENT_204).end()
146} 146}
147 147
148async function deleteAbuse (req: express.Request, res: express.Response) { 148async function deleteAbuse (req: express.Request, res: express.Response) {
@@ -154,7 +154,7 @@ async function deleteAbuse (req: express.Request, res: express.Response) {
154 154
155 // Do not send the delete to other instances, we delete OUR copy of this abuse 155 // Do not send the delete to other instances, we delete OUR copy of this abuse
156 156
157 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 157 return res.status(HttpStatusCode.NO_CONTENT_204).end()
158} 158}
159 159
160async function reportAbuse (req: express.Request, res: express.Response) { 160async function reportAbuse (req: express.Request, res: express.Response) {
@@ -244,5 +244,5 @@ async function deleteAbuseMessage (req: express.Request, res: express.Response)
244 return abuseMessage.destroy({ transaction: t }) 244 return abuseMessage.destroy({ transaction: t })
245 }) 245 })
246 246
247 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 247 return res.status(HttpStatusCode.NO_CONTENT_204).end()
248} 248}
diff --git a/server/controllers/api/bulk.ts b/server/controllers/api/bulk.ts
index 649351029..192daccde 100644
--- a/server/controllers/api/bulk.ts
+++ b/server/controllers/api/bulk.ts
@@ -34,7 +34,7 @@ async function bulkRemoveCommentsOf (req: express.Request, res: express.Response
34 const comments = await VideoCommentModel.listForBulkDelete(account, filter) 34 const comments = await VideoCommentModel.listForBulkDelete(account, filter)
35 35
36 // Don't wait result 36 // Don't wait result
37 res.sendStatus(HttpStatusCode.NO_CONTENT_204) 37 res.status(HttpStatusCode.NO_CONTENT_204).end()
38 38
39 for (const comment of comments) { 39 for (const comment of comments) {
40 await removeComment(comment) 40 await removeComment(comment)
diff --git a/server/controllers/api/custom-page.ts b/server/controllers/api/custom-page.ts
index 3c47f7b9a..c19f03c56 100644
--- a/server/controllers/api/custom-page.ts
+++ b/server/controllers/api/custom-page.ts
@@ -27,7 +27,12 @@ export {
27 27
28async function getInstanceHomepage (req: express.Request, res: express.Response) { 28async function getInstanceHomepage (req: express.Request, res: express.Response) {
29 const page = await ActorCustomPageModel.loadInstanceHomepage() 29 const page = await ActorCustomPageModel.loadInstanceHomepage()
30 if (!page) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 30 if (!page) {
31 return res.fail({
32 status: HttpStatusCode.NOT_FOUND_404,
33 message: 'Instance homepage could not be found'
34 })
35 }
31 36
32 return res.json(page.toFormattedJSON()) 37 return res.json(page.toFormattedJSON())
33} 38}
@@ -38,5 +43,5 @@ async function updateInstanceHomepage (req: express.Request, res: express.Respon
38 await ActorCustomPageModel.updateInstanceHomepage(content) 43 await ActorCustomPageModel.updateInstanceHomepage(content)
39 ServerConfigManager.Instance.updateHomepageState(content) 44 ServerConfigManager.Instance.updateHomepageState(content)
40 45
41 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 46 return res.status(HttpStatusCode.NO_CONTENT_204).end()
42} 47}
diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts
index c21e2298d..48a10d31f 100644
--- a/server/controllers/api/oauth-clients.ts
+++ b/server/controllers/api/oauth-clients.ts
@@ -24,7 +24,10 @@ async function getLocalClient (req: express.Request, res: express.Response, next
24 // Don't make this check if this is a test instance 24 // Don't make this check if this is a test instance
25 if (process.env.NODE_ENV !== 'test' && req.get('host') !== headerHostShouldBe) { 25 if (process.env.NODE_ENV !== 'test' && req.get('host') !== headerHostShouldBe) {
26 logger.info('Getting client tokens for host %s is forbidden (expected %s).', req.get('host'), headerHostShouldBe) 26 logger.info('Getting client tokens for host %s is forbidden (expected %s).', req.get('host'), headerHostShouldBe)
27 return res.type('json').status(HttpStatusCode.FORBIDDEN_403).end() 27 return res.fail({
28 status: HttpStatusCode.FORBIDDEN_403,
29 message: `Getting client tokens for host ${req.get('host')} is forbidden`
30 })
28 } 31 }
29 32
30 const client = await OAuthClientModel.loadFirstClient() 33 const client = await OAuthClientModel.loadFirstClient()
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts
index e18eed332..b64062287 100644
--- a/server/controllers/api/plugins.ts
+++ b/server/controllers/api/plugins.ts
@@ -144,7 +144,7 @@ async function installPlugin (req: express.Request, res: express.Response) {
144 return res.json(plugin.toFormattedJSON()) 144 return res.json(plugin.toFormattedJSON())
145 } catch (err) { 145 } catch (err) {
146 logger.warn('Cannot install plugin %s.', toInstall, { err }) 146 logger.warn('Cannot install plugin %s.', toInstall, { err })
147 return res.sendStatus(HttpStatusCode.BAD_REQUEST_400) 147 return res.fail({ message: 'Cannot install plugin ' + toInstall })
148 } 148 }
149} 149}
150 150
@@ -159,7 +159,7 @@ async function updatePlugin (req: express.Request, res: express.Response) {
159 return res.json(plugin.toFormattedJSON()) 159 return res.json(plugin.toFormattedJSON())
160 } catch (err) { 160 } catch (err) {
161 logger.warn('Cannot update plugin %s.', toUpdate, { err }) 161 logger.warn('Cannot update plugin %s.', toUpdate, { err })
162 return res.sendStatus(HttpStatusCode.BAD_REQUEST_400) 162 return res.fail({ message: 'Cannot update plugin ' + toUpdate })
163 } 163 }
164} 164}
165 165
@@ -168,7 +168,7 @@ async function uninstallPlugin (req: express.Request, res: express.Response) {
168 168
169 await PluginManager.Instance.uninstall(body.npmName) 169 await PluginManager.Instance.uninstall(body.npmName)
170 170
171 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 171 return res.status(HttpStatusCode.NO_CONTENT_204).end()
172} 172}
173 173
174function getPublicPluginSettings (req: express.Request, res: express.Response) { 174function getPublicPluginSettings (req: express.Request, res: express.Response) {
@@ -197,7 +197,7 @@ async function updatePluginSettings (req: express.Request, res: express.Response
197 197
198 await PluginManager.Instance.onSettingsChanged(plugin.name, plugin.settings) 198 await PluginManager.Instance.onSettingsChanged(plugin.name, plugin.settings)
199 199
200 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 200 return res.status(HttpStatusCode.NO_CONTENT_204).end()
201} 201}
202 202
203async function listAvailablePlugins (req: express.Request, res: express.Response) { 203async function listAvailablePlugins (req: express.Request, res: express.Response) {
@@ -206,8 +206,10 @@ async function listAvailablePlugins (req: express.Request, res: express.Response
206 const resultList = await listAvailablePluginsFromIndex(query) 206 const resultList = await listAvailablePluginsFromIndex(query)
207 207
208 if (!resultList) { 208 if (!resultList) {
209 return res.status(HttpStatusCode.SERVICE_UNAVAILABLE_503) 209 return res.fail({
210 .json({ error: 'Plugin index unavailable. Please retry later' }) 210 status: HttpStatusCode.SERVICE_UNAVAILABLE_503,
211 message: 'Plugin index unavailable. Please retry later'
212 })
211 } 213 }
212 214
213 return res.json(resultList) 215 return res.json(resultList)
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts
index f0cdf3a89..77e3a024d 100644
--- a/server/controllers/api/search.ts
+++ b/server/controllers/api/search.ts
@@ -102,7 +102,10 @@ async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: e
102 } catch (err) { 102 } catch (err) {
103 logger.warn('Cannot use search index to make video channels search.', { err }) 103 logger.warn('Cannot use search index to make video channels search.', { err })
104 104
105 return res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR_500) 105 return res.fail({
106 status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
107 message: 'Cannot use search index to make video channels search'
108 })
106 } 109 }
107} 110}
108 111
@@ -202,7 +205,10 @@ async function searchVideosIndex (query: VideosSearchQuery, res: express.Respons
202 } catch (err) { 205 } catch (err) {
203 logger.warn('Cannot use search index to make video search.', { err }) 206 logger.warn('Cannot use search index to make video search.', { err })
204 207
205 return res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR_500) 208 return res.fail({
209 status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
210 message: 'Cannot use search index to make video search'
211 })
206 } 212 }
207} 213}
208 214
diff --git a/server/controllers/api/server/debug.ts b/server/controllers/api/server/debug.ts
index ff0d9ca3c..a6e9147f3 100644
--- a/server/controllers/api/server/debug.ts
+++ b/server/controllers/api/server/debug.ts
@@ -1,5 +1,6 @@
1import { InboxManager } from '@server/lib/activitypub/inbox-manager' 1import { InboxManager } from '@server/lib/activitypub/inbox-manager'
2import { RemoveDanglingResumableUploadsScheduler } from '@server/lib/schedulers/remove-dangling-resumable-uploads-scheduler' 2import { RemoveDanglingResumableUploadsScheduler } from '@server/lib/schedulers/remove-dangling-resumable-uploads-scheduler'
3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
3import { SendDebugCommand } from '@shared/models' 4import { SendDebugCommand } from '@shared/models'
4import * as express from 'express' 5import * as express from 'express'
5import { UserRight } from '../../../../shared/models/users' 6import { UserRight } from '../../../../shared/models/users'
@@ -41,5 +42,5 @@ async function runCommand (req: express.Request, res: express.Response) {
41 await RemoveDanglingResumableUploadsScheduler.Instance.execute() 42 await RemoveDanglingResumableUploadsScheduler.Instance.execute()
42 } 43 }
43 44
44 return res.sendStatus(204) 45 return res.status(HttpStatusCode.NO_CONTENT_204).end()
45} 46}
diff --git a/server/controllers/api/server/redundancy.ts b/server/controllers/api/server/redundancy.ts
index 7c13dc21b..bc593ad43 100644
--- a/server/controllers/api/server/redundancy.ts
+++ b/server/controllers/api/server/redundancy.ts
@@ -90,13 +90,13 @@ async function addVideoRedundancy (req: express.Request, res: express.Response)
90 payload 90 payload
91 }) 91 })
92 92
93 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 93 return res.status(HttpStatusCode.NO_CONTENT_204).end()
94} 94}
95 95
96async function removeVideoRedundancyController (req: express.Request, res: express.Response) { 96async function removeVideoRedundancyController (req: express.Request, res: express.Response) {
97 await removeVideoRedundancy(res.locals.videoRedundancy) 97 await removeVideoRedundancy(res.locals.videoRedundancy)
98 98
99 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 99 return res.status(HttpStatusCode.NO_CONTENT_204).end()
100} 100}
101 101
102async function updateRedundancy (req: express.Request, res: express.Response) { 102async function updateRedundancy (req: express.Request, res: express.Response) {
@@ -110,5 +110,5 @@ async function updateRedundancy (req: express.Request, res: express.Response) {
110 removeRedundanciesOfServer(server.id) 110 removeRedundanciesOfServer(server.id)
111 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err })) 111 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err }))
112 112
113 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 113 return res.status(HttpStatusCode.NO_CONTENT_204).end()
114} 114}
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index f384f0f28..d907b49bf 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -314,7 +314,7 @@ async function removeUser (req: express.Request, res: express.Response) {
314 314
315 Hooks.runAction('action:api.user.deleted', { user }) 315 Hooks.runAction('action:api.user.deleted', { user })
316 316
317 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 317 return res.status(HttpStatusCode.NO_CONTENT_204).end()
318} 318}
319 319
320async function updateUser (req: express.Request, res: express.Response) { 320async function updateUser (req: express.Request, res: express.Response) {
@@ -349,7 +349,7 @@ async function updateUser (req: express.Request, res: express.Response) {
349 349
350 // Don't need to send this update to followers, these attributes are not federated 350 // Don't need to send this update to followers, these attributes are not federated
351 351
352 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 352 return res.status(HttpStatusCode.NO_CONTENT_204).end()
353} 353}
354 354
355async function askResetUserPassword (req: express.Request, res: express.Response) { 355async function askResetUserPassword (req: express.Request, res: express.Response) {
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index a609abaa6..810e4295e 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -183,7 +183,7 @@ async function deleteMe (req: express.Request, res: express.Response) {
183 183
184 await user.destroy() 184 await user.destroy()
185 185
186 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 186 return res.status(HttpStatusCode.NO_CONTENT_204).end()
187} 187}
188 188
189async function updateMe (req: express.Request, res: express.Response) { 189async function updateMe (req: express.Request, res: express.Response) {
@@ -237,7 +237,7 @@ async function updateMe (req: express.Request, res: express.Response) {
237 await sendVerifyUserEmail(user, true) 237 await sendVerifyUserEmail(user, true)
238 } 238 }
239 239
240 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 240 return res.status(HttpStatusCode.NO_CONTENT_204).end()
241} 241}
242 242
243async function updateMyAvatar (req: express.Request, res: express.Response) { 243async function updateMyAvatar (req: express.Request, res: express.Response) {
@@ -257,5 +257,5 @@ async function deleteMyAvatar (req: express.Request, res: express.Response) {
257 const userAccount = await AccountModel.load(user.Account.id) 257 const userAccount = await AccountModel.load(user.Account.id)
258 await deleteLocalActorImageFile(userAccount, ActorImageType.AVATAR) 258 await deleteLocalActorImageFile(userAccount, ActorImageType.AVATAR)
259 259
260 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 260 return res.status(HttpStatusCode.NO_CONTENT_204).end()
261} 261}
diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts
index 694bb0a92..863a3d74c 100644
--- a/server/controllers/api/users/token.ts
+++ b/server/controllers/api/users/token.ts
@@ -78,9 +78,10 @@ async function handleToken (req: express.Request, res: express.Response, next: e
78 } catch (err) { 78 } catch (err) {
79 logger.warn('Login error', { err }) 79 logger.warn('Login error', { err })
80 80
81 return res.status(err.code || 400).json({ 81 return res.fail({
82 code: err.name, 82 status: err.code,
83 error: err.message 83 message: err.message,
84 type: err.name
84 }) 85 })
85 } 86 }
86} 87}
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 859d8b3c0..34207ea8a 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -180,7 +180,7 @@ async function deleteVideoChannelAvatar (req: express.Request, res: express.Resp
180 180
181 await deleteLocalActorImageFile(videoChannel, ActorImageType.AVATAR) 181 await deleteLocalActorImageFile(videoChannel, ActorImageType.AVATAR)
182 182
183 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 183 return res.status(HttpStatusCode.NO_CONTENT_204).end()
184} 184}
185 185
186async function deleteVideoChannelBanner (req: express.Request, res: express.Response) { 186async function deleteVideoChannelBanner (req: express.Request, res: express.Response) {
@@ -188,7 +188,7 @@ async function deleteVideoChannelBanner (req: express.Request, res: express.Resp
188 188
189 await deleteLocalActorImageFile(videoChannel, ActorImageType.BANNER) 189 await deleteLocalActorImageFile(videoChannel, ActorImageType.BANNER)
190 190
191 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 191 return res.status(HttpStatusCode.NO_CONTENT_204).end()
192} 192}
193 193
194async function addVideoChannel (req: express.Request, res: express.Response) { 194async function addVideoChannel (req: express.Request, res: express.Response) {
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index fa8448c86..ca2b85ea5 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -70,7 +70,7 @@ async function addVideoToBlacklistController (req: express.Request, res: express
70 70
71 logger.info('Video %s blacklisted.', videoInstance.uuid) 71 logger.info('Video %s blacklisted.', videoInstance.uuid)
72 72
73 return res.type('json').sendStatus(HttpStatusCode.NO_CONTENT_204) 73 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
74} 74}
75 75
76async function updateVideoBlacklistController (req: express.Request, res: express.Response) { 76async function updateVideoBlacklistController (req: express.Request, res: express.Response) {
@@ -82,7 +82,7 @@ async function updateVideoBlacklistController (req: express.Request, res: expres
82 return videoBlacklist.save({ transaction: t }) 82 return videoBlacklist.save({ transaction: t })
83 }) 83 })
84 84
85 return res.type('json').sendStatus(HttpStatusCode.NO_CONTENT_204) 85 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
86} 86}
87 87
88async function listBlacklist (req: express.Request, res: express.Response) { 88async function listBlacklist (req: express.Request, res: express.Response) {
@@ -105,5 +105,5 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex
105 105
106 logger.info('Video %s removed from blacklist.', video.uuid) 106 logger.info('Video %s removed from blacklist.', video.uuid)
107 107
108 return res.type('json').sendStatus(HttpStatusCode.NO_CONTENT_204) 108 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
109} 109}
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts
index cfdf2773f..e6f28c1cb 100644
--- a/server/controllers/api/videos/comment.ts
+++ b/server/controllers/api/videos/comment.ts
@@ -166,7 +166,10 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo
166 } 166 }
167 167
168 if (resultList.data.length === 0) { 168 if (resultList.data.length === 0) {
169 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 169 return res.fail({
170 status: HttpStatusCode.NOT_FOUND_404,
171 message: 'No comments were found'
172 })
170 } 173 }
171 174
172 return res.json(buildFormattedCommentTree(resultList)) 175 return res.json(buildFormattedCommentTree(resultList))
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 0d5d7a962..6ee109a8f 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -18,7 +18,6 @@ import {
18} from '@server/types/models' 18} from '@server/types/models'
19import { MVideoImportFormattable } from '@server/types/models/video/video-import' 19import { MVideoImportFormattable } from '@server/types/models/video/video-import'
20import { ServerErrorCode, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' 20import { ServerErrorCode, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared'
21import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
22import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' 21import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
23import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' 22import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
24import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' 23import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
@@ -143,10 +142,12 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
143 } catch (err) { 142 } catch (err) {
144 logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err }) 143 logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err })
145 144
146 return res.status(HttpStatusCode.BAD_REQUEST_400) 145 return res.fail({
147 .json({ 146 message: 'Cannot fetch remote information of this URL.',
148 error: 'Cannot fetch remote information of this URL.' 147 data: {
149 }) 148 targetUrl
149 }
150 })
150 } 151 }
151 152
152 const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) 153 const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo)
@@ -333,12 +334,10 @@ async function processTorrentOrAbortRequest (req: express.Request, res: express.
333 if (parsedTorrent.files.length !== 1) { 334 if (parsedTorrent.files.length !== 1) {
334 cleanUpReqFiles(req) 335 cleanUpReqFiles(req)
335 336
336 res.status(HttpStatusCode.BAD_REQUEST_400) 337 res.fail({
337 .json({ 338 type: ServerErrorCode.INCORRECT_FILES_IN_TORRENT.toString(),
338 code: ServerErrorCode.INCORRECT_FILES_IN_TORRENT, 339 message: 'Torrents with only 1 file are supported.'
339 error: 'Torrents with only 1 file are supported.' 340 })
340 })
341
342 return undefined 341 return undefined
343 } 342 }
344 343
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 6483d2e8a..47ab098ef 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -146,7 +146,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
146 const exists = await Redis.Instance.doesVideoIPViewExist(ip, immutableVideoAttrs.uuid) 146 const exists = await Redis.Instance.doesVideoIPViewExist(ip, immutableVideoAttrs.uuid)
147 if (exists) { 147 if (exists) {
148 logger.debug('View for ip %s and video %s already exists.', ip, immutableVideoAttrs.uuid) 148 logger.debug('View for ip %s and video %s already exists.', ip, immutableVideoAttrs.uuid)
149 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 149 return res.status(HttpStatusCode.NO_CONTENT_204).end()
150 } 150 }
151 151
152 const video = await VideoModel.load(immutableVideoAttrs.id) 152 const video = await VideoModel.load(immutableVideoAttrs.id)
@@ -179,7 +179,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
179 179
180 Hooks.runAction('action:api.video.viewed', { video, ip }) 180 Hooks.runAction('action:api.video.viewed', { video, ip })
181 181
182 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 182 return res.status(HttpStatusCode.NO_CONTENT_204).end()
183} 183}
184 184
185async function getVideoDescription (req: express.Request, res: express.Response) { 185async function getVideoDescription (req: express.Request, res: express.Response) {
diff --git a/server/controllers/api/videos/live.ts b/server/controllers/api/videos/live.ts
index 04d2494ce..6b733c577 100644
--- a/server/controllers/api/videos/live.ts
+++ b/server/controllers/api/videos/live.ts
@@ -76,7 +76,7 @@ async function updateLiveVideo (req: express.Request, res: express.Response) {
76 76
77 await federateVideoIfNeeded(video, false) 77 await federateVideoIfNeeded(video, false)
78 78
79 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 79 return res.status(HttpStatusCode.NO_CONTENT_204).end()
80} 80}
81 81
82async function addLiveVideo (req: express.Request, res: express.Response) { 82async function addLiveVideo (req: express.Request, res: express.Response) {
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts
index 6102f28dc..2d6ca60a8 100644
--- a/server/controllers/api/videos/ownership.ts
+++ b/server/controllers/api/videos/ownership.ts
@@ -122,7 +122,7 @@ function acceptOwnership (req: express.Request, res: express.Response) {
122 videoChangeOwnership.status = VideoChangeOwnershipStatus.ACCEPTED 122 videoChangeOwnership.status = VideoChangeOwnershipStatus.ACCEPTED
123 await videoChangeOwnership.save({ transaction: t }) 123 await videoChangeOwnership.save({ transaction: t })
124 124
125 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 125 return res.status(HttpStatusCode.NO_CONTENT_204).end()
126 }) 126 })
127} 127}
128 128
@@ -133,6 +133,6 @@ function refuseOwnership (req: express.Request, res: express.Response) {
133 videoChangeOwnership.status = VideoChangeOwnershipStatus.REFUSED 133 videoChangeOwnership.status = VideoChangeOwnershipStatus.REFUSED
134 await videoChangeOwnership.save({ transaction: t }) 134 await videoChangeOwnership.save({ transaction: t })
135 135
136 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 136 return res.status(HttpStatusCode.NO_CONTENT_204).end()
137 }) 137 })
138} 138}
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts
index ebc17c760..c33d7fcb9 100644
--- a/server/controllers/api/videos/upload.ts
+++ b/server/controllers/api/videos/upload.ts
@@ -97,8 +97,11 @@ export async function addVideoLegacy (req: express.Request, res: express.Respons
97 // Uploading the video could be long 97 // Uploading the video could be long
98 // Set timeout to 10 minutes, as Express's default is 2 minutes 98 // Set timeout to 10 minutes, as Express's default is 2 minutes
99 req.setTimeout(1000 * 60 * 10, () => { 99 req.setTimeout(1000 * 60 * 10, () => {
100 logger.error('Upload video has timed out.') 100 logger.error('Video upload has timed out.')
101 return res.sendStatus(HttpStatusCode.REQUEST_TIMEOUT_408) 101 return res.fail({
102 status: HttpStatusCode.REQUEST_TIMEOUT_408,
103 message: 'Video upload has timed out.'
104 })
102 }) 105 })
103 106
104 const videoPhysicalFile = req.files['videofile'][0] 107 const videoPhysicalFile = req.files['videofile'][0]
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index fcccc48e0..eb1ee6cbd 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -78,7 +78,7 @@ clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE.C
78 78
79// 404 for static files not found 79// 404 for static files not found
80clientsRouter.use('/client/*', (req: express.Request, res: express.Response) => { 80clientsRouter.use('/client/*', (req: express.Request, res: express.Response) => {
81 res.sendStatus(HttpStatusCode.NOT_FOUND_404) 81 res.status(HttpStatusCode.NOT_FOUND_404).end()
82}) 82})
83 83
84// Always serve index client page (the client is a single page application, let it handle routing) 84// Always serve index client page (the client is a single page application, let it handle routing)
@@ -105,7 +105,7 @@ function serveServerTranslations (req: express.Request, res: express.Response) {
105 return res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER }) 105 return res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER })
106 } 106 }
107 107
108 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 108 return res.status(HttpStatusCode.NOT_FOUND_404).end()
109} 109}
110 110
111async function generateEmbedHtmlPage (req: express.Request, res: express.Response) { 111async function generateEmbedHtmlPage (req: express.Request, res: express.Response) {
diff --git a/server/controllers/download.ts b/server/controllers/download.ts
index 9a8194c5c..4293a32e2 100644
--- a/server/controllers/download.ts
+++ b/server/controllers/download.ts
@@ -41,7 +41,12 @@ export {
41 41
42async function downloadTorrent (req: express.Request, res: express.Response) { 42async function downloadTorrent (req: express.Request, res: express.Response) {
43 const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename) 43 const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename)
44 if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 44 if (!result) {
45 return res.fail({
46 status: HttpStatusCode.NOT_FOUND_404,
47 message: 'Torrent file not found'
48 })
49 }
45 50
46 const allowParameters = { torrentPath: result.path, downloadName: result.downloadName } 51 const allowParameters = { torrentPath: result.path, downloadName: result.downloadName }
47 52
@@ -60,7 +65,12 @@ async function downloadVideoFile (req: express.Request, res: express.Response) {
60 const video = res.locals.videoAll 65 const video = res.locals.videoAll
61 66
62 const videoFile = getVideoFile(req, video.VideoFiles) 67 const videoFile = getVideoFile(req, video.VideoFiles)
63 if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() 68 if (!videoFile) {
69 return res.fail({
70 status: HttpStatusCode.NOT_FOUND_404,
71 message: 'Video file not found'
72 })
73 }
64 74
65 const allowParameters = { video, videoFile } 75 const allowParameters = { video, videoFile }
66 76
@@ -81,7 +91,12 @@ async function downloadHLSVideoFile (req: express.Request, res: express.Response
81 if (!streamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).end 91 if (!streamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).end
82 92
83 const videoFile = getVideoFile(req, streamingPlaylist.VideoFiles) 93 const videoFile = getVideoFile(req, streamingPlaylist.VideoFiles)
84 if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() 94 if (!videoFile) {
95 return res.fail({
96 status: HttpStatusCode.NOT_FOUND_404,
97 message: 'Video file not found'
98 })
99 }
85 100
86 const allowParameters = { video, streamingPlaylist, videoFile } 101 const allowParameters = { video, streamingPlaylist, videoFile }
87 102
@@ -131,9 +146,11 @@ function isVideoDownloadAllowed (_object: {
131function checkAllowResult (res: express.Response, allowParameters: any, result?: AllowedResult) { 146function checkAllowResult (res: express.Response, allowParameters: any, result?: AllowedResult) {
132 if (!result || result.allowed !== true) { 147 if (!result || result.allowed !== true) {
133 logger.info('Download is not allowed.', { result, allowParameters }) 148 logger.info('Download is not allowed.', { result, allowParameters })
134 res.status(HttpStatusCode.FORBIDDEN_403)
135 .json({ error: result?.errorMessage || 'Refused download' })
136 149
150 res.fail({
151 status: HttpStatusCode.FORBIDDEN_403,
152 message: result?.errorMessage || 'Refused download'
153 })
137 return false 154 return false
138 } 155 }
139 156
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts
index 25d3b49b4..9f260cef0 100644
--- a/server/controllers/lazy-static.ts
+++ b/server/controllers/lazy-static.ts
@@ -56,10 +56,10 @@ async function getActorImage (req: express.Request, res: express.Response) {
56 } 56 }
57 57
58 const image = await ActorImageModel.loadByName(filename) 58 const image = await ActorImageModel.loadByName(filename)
59 if (!image) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 59 if (!image) return res.status(HttpStatusCode.NOT_FOUND_404).end()
60 60
61 if (image.onDisk === false) { 61 if (image.onDisk === false) {
62 if (!image.fileUrl) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 62 if (!image.fileUrl) return res.status(HttpStatusCode.NOT_FOUND_404).end()
63 63
64 logger.info('Lazy serve remote actor image %s.', image.fileUrl) 64 logger.info('Lazy serve remote actor image %s.', image.fileUrl)
65 65
@@ -67,7 +67,7 @@ async function getActorImage (req: express.Request, res: express.Response) {
67 await pushActorImageProcessInQueue({ filename: image.filename, fileUrl: image.fileUrl, type: image.type }) 67 await pushActorImageProcessInQueue({ filename: image.filename, fileUrl: image.fileUrl, type: image.type })
68 } catch (err) { 68 } catch (err) {
69 logger.warn('Cannot process remote actor image %s.', image.fileUrl, { err }) 69 logger.warn('Cannot process remote actor image %s.', image.fileUrl, { err })
70 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 70 return res.status(HttpStatusCode.NOT_FOUND_404).end()
71 } 71 }
72 72
73 image.onDisk = true 73 image.onDisk = true
@@ -83,21 +83,21 @@ async function getActorImage (req: express.Request, res: express.Response) {
83 83
84async function getPreview (req: express.Request, res: express.Response) { 84async function getPreview (req: express.Request, res: express.Response) {
85 const result = await VideosPreviewCache.Instance.getFilePath(req.params.filename) 85 const result = await VideosPreviewCache.Instance.getFilePath(req.params.filename)
86 if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 86 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
87 87
88 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }) 88 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER })
89} 89}
90 90
91async function getVideoCaption (req: express.Request, res: express.Response) { 91async function getVideoCaption (req: express.Request, res: express.Response) {
92 const result = await VideosCaptionCache.Instance.getFilePath(req.params.filename) 92 const result = await VideosCaptionCache.Instance.getFilePath(req.params.filename)
93 if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 93 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
94 94
95 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }) 95 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER })
96} 96}
97 97
98async function getTorrent (req: express.Request, res: express.Response) { 98async function getTorrent (req: express.Request, res: express.Response) {
99 const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename) 99 const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename)
100 if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 100 if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
101 101
102 // Torrents still use the old naming convention (video uuid + .torrent) 102 // Torrents still use the old naming convention (video uuid + .torrent)
103 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER }) 103 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER })
diff --git a/server/controllers/live.ts b/server/controllers/live.ts
index ff48b0e21..cfb4741b7 100644
--- a/server/controllers/live.ts
+++ b/server/controllers/live.ts
@@ -25,7 +25,7 @@ function getSegmentsSha256 (req: express.Request, res: express.Response) {
25 const result = LiveManager.Instance.getSegmentsSha256(videoUUID) 25 const result = LiveManager.Instance.getSegmentsSha256(videoUUID)
26 26
27 if (!result) { 27 if (!result) {
28 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 28 return res.status(HttpStatusCode.NOT_FOUND_404).end()
29 } 29 }
30 30
31 return res.json(mapToJSON(result)) 31 return res.json(mapToJSON(result))
diff --git a/server/controllers/plugins.ts b/server/controllers/plugins.ts
index 105f51518..7213e3f15 100644
--- a/server/controllers/plugins.ts
+++ b/server/controllers/plugins.ts
@@ -100,7 +100,7 @@ function getPluginTranslations (req: express.Request, res: express.Response) {
100 return res.json(json) 100 return res.json(json)
101 } 101 }
102 102
103 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 103 return res.status(HttpStatusCode.NOT_FOUND_404).end()
104} 104}
105 105
106function servePluginStaticDirectory (req: express.Request, res: express.Response) { 106function servePluginStaticDirectory (req: express.Request, res: express.Response) {
@@ -110,7 +110,7 @@ function servePluginStaticDirectory (req: express.Request, res: express.Response
110 const [ directory, ...file ] = staticEndpoint.split('/') 110 const [ directory, ...file ] = staticEndpoint.split('/')
111 111
112 const staticPath = plugin.staticDirs[directory] 112 const staticPath = plugin.staticDirs[directory]
113 if (!staticPath) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 113 if (!staticPath) return res.status(HttpStatusCode.NOT_FOUND_404).end()
114 114
115 const filepath = file.join('/') 115 const filepath = file.join('/')
116 return res.sendFile(join(plugin.path, staticPath, filepath), sendFileOptions) 116 return res.sendFile(join(plugin.path, staticPath, filepath), sendFileOptions)
@@ -120,7 +120,7 @@ function servePluginCustomRoutes (req: express.Request, res: express.Response, n
120 const plugin: RegisteredPlugin = res.locals.registeredPlugin 120 const plugin: RegisteredPlugin = res.locals.registeredPlugin
121 const router = PluginManager.Instance.getRouter(plugin.npmName) 121 const router = PluginManager.Instance.getRouter(plugin.npmName)
122 122
123 if (!router) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 123 if (!router) return res.status(HttpStatusCode.NOT_FOUND_404).end()
124 124
125 return router(req, res, next) 125 return router(req, res, next)
126} 126}
@@ -130,7 +130,7 @@ function servePluginClientScripts (req: express.Request, res: express.Response)
130 const staticEndpoint = req.params.staticEndpoint 130 const staticEndpoint = req.params.staticEndpoint
131 131
132 const file = plugin.clientScripts[staticEndpoint] 132 const file = plugin.clientScripts[staticEndpoint]
133 if (!file) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 133 if (!file) return res.status(HttpStatusCode.NOT_FOUND_404).end()
134 134
135 return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions) 135 return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
136} 136}
@@ -140,7 +140,7 @@ function serveThemeCSSDirectory (req: express.Request, res: express.Response) {
140 const staticEndpoint = req.params.staticEndpoint 140 const staticEndpoint = req.params.staticEndpoint
141 141
142 if (plugin.css.includes(staticEndpoint) === false) { 142 if (plugin.css.includes(staticEndpoint) === false) {
143 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 143 return res.status(HttpStatusCode.NOT_FOUND_404).end()
144 } 144 }
145 145
146 return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions) 146 return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 3870ebfe9..52e104346 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -160,10 +160,9 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
160 const { totalVideos } = await VideoModel.getStats() 160 const { totalVideos } = await VideoModel.getStats()
161 const { totalLocalVideoComments } = await VideoCommentModel.getStats() 161 const { totalLocalVideoComments } = await VideoCommentModel.getStats()
162 const { totalUsers, totalMonthlyActiveUsers, totalHalfYearActiveUsers } = await UserModel.getStats() 162 const { totalUsers, totalMonthlyActiveUsers, totalHalfYearActiveUsers } = await UserModel.getStats()
163 let json = {}
164 163
165 if (req.params.version && (req.params.version === '2.0')) { 164 if (req.params.version && (req.params.version === '2.0')) {
166 json = { 165 const json = {
167 version: '2.0', 166 version: '2.0',
168 software: { 167 software: {
169 name: 'peertube', 168 name: 'peertube',
@@ -291,12 +290,14 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
291 } 290 }
292 } as HttpNodeinfoDiasporaSoftwareNsSchema20 291 } as HttpNodeinfoDiasporaSoftwareNsSchema20
293 res.contentType('application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"') 292 res.contentType('application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"')
294 } else { 293 .send(json)
295 json = { error: 'Nodeinfo schema version not handled' } 294 .end()
296 res.status(HttpStatusCode.NOT_FOUND_404)
297 } 295 }
298 296
299 return res.send(json).end() 297 return res.fail({
298 status: HttpStatusCode.NOT_FOUND_404,
299 message: 'Nodeinfo schema version not handled'
300 })
300} 301}
301 302
302function getCup (req: express.Request, res: express.Response, next: express.NextFunction) { 303function getCup (req: express.Request, res: express.Response, next: express.NextFunction) {
diff --git a/server/helpers/custom-validators/video-comments.ts b/server/helpers/custom-validators/video-comments.ts
index 8d3ce580e..5c88447ad 100644
--- a/server/helpers/custom-validators/video-comments.ts
+++ b/server/helpers/custom-validators/video-comments.ts
@@ -16,26 +16,20 @@ async function doesVideoCommentThreadExist (idArg: number | string, video: MVide
16 const videoComment = await VideoCommentModel.loadById(id) 16 const videoComment = await VideoCommentModel.loadById(id)
17 17
18 if (!videoComment) { 18 if (!videoComment) {
19 res.status(HttpStatusCode.NOT_FOUND_404) 19 res.fail({
20 .json({ error: 'Video comment thread not found' }) 20 status: HttpStatusCode.NOT_FOUND_404,
21 .end() 21 message: 'Video comment thread not found'
22 22 })
23 return false 23 return false
24 } 24 }
25 25
26 if (videoComment.videoId !== video.id) { 26 if (videoComment.videoId !== video.id) {
27 res.status(HttpStatusCode.BAD_REQUEST_400) 27 res.fail({ message: 'Video comment is not associated to this video.' })
28 .json({ error: 'Video comment is not associated to this video.' })
29 .end()
30
31 return false 28 return false
32 } 29 }
33 30
34 if (videoComment.inReplyToCommentId !== null) { 31 if (videoComment.inReplyToCommentId !== null) {
35 res.status(HttpStatusCode.BAD_REQUEST_400) 32 res.fail({ message: 'Video comment is not a thread.' })
36 .json({ error: 'Video comment is not a thread.' })
37 .end()
38
39 return false 33 return false
40 } 34 }
41 35
@@ -48,18 +42,15 @@ async function doesVideoCommentExist (idArg: number | string, video: MVideoId, r
48 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) 42 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
49 43
50 if (!videoComment) { 44 if (!videoComment) {
51 res.status(HttpStatusCode.NOT_FOUND_404) 45 res.fail({
52 .json({ error: 'Video comment thread not found' }) 46 status: HttpStatusCode.NOT_FOUND_404,
53 .end() 47 message: 'Video comment thread not found'
54 48 })
55 return false 49 return false
56 } 50 }
57 51
58 if (videoComment.videoId !== video.id) { 52 if (videoComment.videoId !== video.id) {
59 res.status(HttpStatusCode.BAD_REQUEST_400) 53 res.fail({ message: 'Video comment is not associated to this video.' })
60 .json({ error: 'Video comment is not associated to this video.' })
61 .end()
62
63 return false 54 return false
64 } 55 }
65 56
@@ -72,14 +63,14 @@ async function doesCommentIdExist (idArg: number | string, res: express.Response
72 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) 63 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
73 64
74 if (!videoComment) { 65 if (!videoComment) {
75 res.status(HttpStatusCode.NOT_FOUND_404) 66 res.fail({
76 .json({ error: 'Video comment thread not found' }) 67 status: HttpStatusCode.NOT_FOUND_404,
77 68 message: 'Video comment thread not found'
69 })
78 return false 70 return false
79 } 71 }
80 72
81 res.locals.videoCommentFull = videoComment 73 res.locals.videoCommentFull = videoComment
82
83 return true 74 return true
84} 75}
85 76
diff --git a/server/helpers/custom-validators/video-imports.ts b/server/helpers/custom-validators/video-imports.ts
index 0063d3337..3ad7a4648 100644
--- a/server/helpers/custom-validators/video-imports.ts
+++ b/server/helpers/custom-validators/video-imports.ts
@@ -36,10 +36,10 @@ async function doesVideoImportExist (id: number, res: express.Response) {
36 const videoImport = await VideoImportModel.loadAndPopulateVideo(id) 36 const videoImport = await VideoImportModel.loadAndPopulateVideo(id)
37 37
38 if (!videoImport) { 38 if (!videoImport) {
39 res.status(HttpStatusCode.NOT_FOUND_404) 39 res.fail({
40 .json({ error: 'Video import not found' }) 40 status: HttpStatusCode.NOT_FOUND_404,
41 .end() 41 message: 'Video import not found'
42 42 })
43 return false 43 return false
44 } 44 }
45 45
diff --git a/server/helpers/custom-validators/video-ownership.ts b/server/helpers/custom-validators/video-ownership.ts
index ee3cebe10..21a6b7203 100644
--- a/server/helpers/custom-validators/video-ownership.ts
+++ b/server/helpers/custom-validators/video-ownership.ts
@@ -9,10 +9,10 @@ export async function doesChangeVideoOwnershipExist (idArg: number | string, res
9 const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) 9 const videoChangeOwnership = await VideoChangeOwnershipModel.load(id)
10 10
11 if (!videoChangeOwnership) { 11 if (!videoChangeOwnership) {
12 res.status(HttpStatusCode.NOT_FOUND_404) 12 res.fail({
13 .json({ error: 'Video change ownership not found' }) 13 status: HttpStatusCode.NOT_FOUND_404,
14 .end() 14 message: 'Video change ownership not found'
15 15 })
16 return false 16 return false
17 } 17 }
18 18
@@ -25,8 +25,9 @@ export function checkUserCanTerminateOwnershipChange (user: MUserId, videoChange
25 return true 25 return true
26 } 26 }
27 27
28 res.status(HttpStatusCode.FORBIDDEN_403) 28 res.fail({
29 .json({ error: 'Cannot terminate an ownership change of another user' }) 29 status: HttpStatusCode.FORBIDDEN_403,
30 .end() 30 message: 'Cannot terminate an ownership change of another user'
31 })
31 return false 32 return false
32} 33}
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
index 010c6961a..e3ff93cdd 100644
--- a/server/helpers/express-utils.ts
+++ b/server/helpers/express-utils.ts
@@ -8,6 +8,7 @@ import { isArray } from './custom-validators/misc'
8import { logger } from './logger' 8import { logger } from './logger'
9import { deleteFileAndCatch, generateRandomString } from './utils' 9import { deleteFileAndCatch, generateRandomString } from './utils'
10import { getExtFromMimetype } from './video' 10import { getExtFromMimetype } from './video'
11import { ProblemDocument, ProblemDocumentExtension } from 'http-problem-details'
11 12
12function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { 13function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
13 if (paramNSFW === 'true') return true 14 if (paramNSFW === 'true') return true
@@ -125,6 +126,34 @@ function getCountVideos (req: express.Request) {
125 return req.query.skipCount !== true 126 return req.query.skipCount !== true
126} 127}
127 128
129// helpers added in server.ts and used in subsequent controllers used
130const apiResponseHelpers = (req, res: express.Response, next = null) => {
131 res.fail = (options) => {
132 const { data, status, message, title, type, docs, instance } = {
133 data: null,
134 status: HttpStatusCode.BAD_REQUEST_400,
135 ...options
136 }
137
138 const extension = new ProblemDocumentExtension({
139 ...data,
140 docs: docs || res.docs
141 })
142
143 res.status(status)
144 res.setHeader('Content-Type', 'application/problem+json')
145 res.json(new ProblemDocument({
146 status,
147 title,
148 instance,
149 type: type && '' + type,
150 detail: message
151 }, extension))
152 }
153
154 if (next !== null) next()
155}
156
128// --------------------------------------------------------------------------- 157// ---------------------------------------------------------------------------
129 158
130export { 159export {
@@ -134,5 +163,6 @@ export {
134 badRequest, 163 badRequest,
135 createReqFiles, 164 createReqFiles,
136 cleanUpReqFiles, 165 cleanUpReqFiles,
137 getCountVideos 166 getCountVideos,
167 apiResponseHelpers
138} 168}
diff --git a/server/helpers/middlewares/abuses.ts b/server/helpers/middlewares/abuses.ts
index c53bd9efd..f0b1caba8 100644
--- a/server/helpers/middlewares/abuses.ts
+++ b/server/helpers/middlewares/abuses.ts
@@ -6,8 +6,10 @@ async function doesAbuseExist (abuseId: number | string, res: Response) {
6 const abuse = await AbuseModel.loadByIdWithReporter(parseInt(abuseId + '', 10)) 6 const abuse = await AbuseModel.loadByIdWithReporter(parseInt(abuseId + '', 10))
7 7
8 if (!abuse) { 8 if (!abuse) {
9 res.status(HttpStatusCode.NOT_FOUND_404) 9 res.fail({
10 .json({ error: 'Abuse not found' }) 10 status: HttpStatusCode.NOT_FOUND_404,
11 message: 'Abuse not found'
12 })
11 13
12 return false 14 return false
13 } 15 }
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts
index 5addd3e1a..7db79bc48 100644
--- a/server/helpers/middlewares/accounts.ts
+++ b/server/helpers/middlewares/accounts.ts
@@ -27,15 +27,15 @@ async function doesAccountExist (p: Promise<MAccountDefault>, res: Response, sen
27 27
28 if (!account) { 28 if (!account) {
29 if (sendNotFound === true) { 29 if (sendNotFound === true) {
30 res.status(HttpStatusCode.NOT_FOUND_404) 30 res.fail({
31 .json({ error: 'Account not found' }) 31 status: HttpStatusCode.NOT_FOUND_404,
32 message: 'Account not found'
33 })
32 } 34 }
33
34 return false 35 return false
35 } 36 }
36 37
37 res.locals.account = account 38 res.locals.account = account
38
39 return true 39 return true
40} 40}
41 41
@@ -43,14 +43,14 @@ async function doesUserFeedTokenCorrespond (id: number, token: string, res: Resp
43 const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10)) 43 const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10))
44 44
45 if (token !== user.feedToken) { 45 if (token !== user.feedToken) {
46 res.status(HttpStatusCode.FORBIDDEN_403) 46 res.fail({
47 .json({ error: 'User and token mismatch' }) 47 status: HttpStatusCode.FORBIDDEN_403,
48 48 message: 'User and token mismatch'
49 })
49 return false 50 return false
50 } 51 }
51 52
52 res.locals.user = user 53 res.locals.user = user
53
54 return true 54 return true
55} 55}
56 56
diff --git a/server/helpers/middlewares/video-blacklists.ts b/server/helpers/middlewares/video-blacklists.ts
index eda1324d3..3494fd6b0 100644
--- a/server/helpers/middlewares/video-blacklists.ts
+++ b/server/helpers/middlewares/video-blacklists.ts
@@ -6,10 +6,10 @@ async function doesVideoBlacklistExist (videoId: number, res: Response) {
6 const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId) 6 const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId)
7 7
8 if (videoBlacklist === null) { 8 if (videoBlacklist === null) {
9 res.status(HttpStatusCode.NOT_FOUND_404) 9 res.fail({
10 .json({ error: 'Blacklisted video not found' }) 10 status: HttpStatusCode.NOT_FOUND_404,
11 .end() 11 message: 'Blacklisted video not found'
12 12 })
13 return false 13 return false
14 } 14 }
15 15
diff --git a/server/helpers/middlewares/video-captions.ts b/server/helpers/middlewares/video-captions.ts
index 226d3c5f8..2a12c4813 100644
--- a/server/helpers/middlewares/video-captions.ts
+++ b/server/helpers/middlewares/video-captions.ts
@@ -7,9 +7,10 @@ async function doesVideoCaptionExist (video: MVideoId, language: string, res: Re
7 const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) 7 const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language)
8 8
9 if (!videoCaption) { 9 if (!videoCaption) {
10 res.status(HttpStatusCode.NOT_FOUND_404) 10 res.fail({
11 .json({ error: 'Video caption not found' }) 11 status: HttpStatusCode.NOT_FOUND_404,
12 12 message: 'Video caption not found'
13 })
13 return false 14 return false
14 } 15 }
15 16
diff --git a/server/helpers/middlewares/video-channels.ts b/server/helpers/middlewares/video-channels.ts
index 602555921..f5ed5ef0f 100644
--- a/server/helpers/middlewares/video-channels.ts
+++ b/server/helpers/middlewares/video-channels.ts
@@ -31,9 +31,10 @@ export {
31 31
32function processVideoChannelExist (videoChannel: MChannelBannerAccountDefault, res: express.Response) { 32function processVideoChannelExist (videoChannel: MChannelBannerAccountDefault, res: express.Response) {
33 if (!videoChannel) { 33 if (!videoChannel) {
34 res.status(HttpStatusCode.NOT_FOUND_404) 34 res.fail({
35 .json({ error: 'Video channel not found' }) 35 status: HttpStatusCode.NOT_FOUND_404,
36 36 message: 'Video channel not found'
37 })
37 return false 38 return false
38 } 39 }
39 40
diff --git a/server/helpers/middlewares/video-playlists.ts b/server/helpers/middlewares/video-playlists.ts
index d2dd80a35..3faeab677 100644
--- a/server/helpers/middlewares/video-playlists.ts
+++ b/server/helpers/middlewares/video-playlists.ts
@@ -28,10 +28,10 @@ export {
28 28
29function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) { 29function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) {
30 if (!videoPlaylist) { 30 if (!videoPlaylist) {
31 res.status(HttpStatusCode.NOT_FOUND_404) 31 res.fail({
32 .json({ error: 'Video playlist not found' }) 32 status: HttpStatusCode.NOT_FOUND_404,
33 .end() 33 message: 'Video playlist not found'
34 34 })
35 return false 35 return false
36 } 36 }
37 37
diff --git a/server/helpers/middlewares/videos.ts b/server/helpers/middlewares/videos.ts
index 403cae092..52b934eb7 100644
--- a/server/helpers/middlewares/videos.ts
+++ b/server/helpers/middlewares/videos.ts
@@ -21,10 +21,10 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
21 const video = await fetchVideo(id, fetchType, userId) 21 const video = await fetchVideo(id, fetchType, userId)
22 22
23 if (video === null) { 23 if (video === null) {
24 res.status(HttpStatusCode.NOT_FOUND_404) 24 res.fail({
25 .json({ error: 'Video not found' }) 25 status: HttpStatusCode.NOT_FOUND_404,
26 .end() 26 message: 'Video not found'
27 27 })
28 return false 28 return false
29 } 29 }
30 30
@@ -55,10 +55,10 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
55 55
56async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) { 56async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) {
57 if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) { 57 if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) {
58 res.status(HttpStatusCode.NOT_FOUND_404) 58 res.fail({
59 .json({ error: 'VideoFile matching Video not found' }) 59 status: HttpStatusCode.NOT_FOUND_404,
60 .end() 60 message: 'VideoFile matching Video not found'
61 61 })
62 return false 62 return false
63 } 63 }
64 64
@@ -69,9 +69,7 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAcc
69 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) 69 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
70 70
71 if (videoChannel === null) { 71 if (videoChannel === null) {
72 res.status(HttpStatusCode.BAD_REQUEST_400) 72 res.fail({ message: 'Unknown video "video channel" for this instance.' })
73 .json({ error: 'Unknown video "video channel" for this instance.' })
74
75 return false 73 return false
76 } 74 }
77 75
@@ -82,9 +80,9 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAcc
82 } 80 }
83 81
84 if (videoChannel.Account.id !== user.Account.id) { 82 if (videoChannel.Account.id !== user.Account.id) {
85 res.status(HttpStatusCode.BAD_REQUEST_400) 83 res.fail({
86 .json({ error: 'Unknown video "video channel" for this account.' }) 84 message: 'Unknown video "video channel" for this account.'
87 85 })
88 return false 86 return false
89 } 87 }
90 88
@@ -95,9 +93,10 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAcc
95function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { 93function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) {
96 // Retrieve the user who did the request 94 // Retrieve the user who did the request
97 if (onlyOwned && video.isOwned() === false) { 95 if (onlyOwned && video.isOwned() === false) {
98 res.status(HttpStatusCode.FORBIDDEN_403) 96 res.fail({
99 .json({ error: 'Cannot manage a video of another server.' }) 97 status: HttpStatusCode.FORBIDDEN_403,
100 .end() 98 message: 'Cannot manage a video of another server.'
99 })
101 return false 100 return false
102 } 101 }
103 102
@@ -106,9 +105,10 @@ function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right:
106 // Or if s/he is the video's account 105 // Or if s/he is the video's account
107 const account = video.VideoChannel.Account 106 const account = video.VideoChannel.Account
108 if (user.hasRight(right) === false && account.userId !== user.id) { 107 if (user.hasRight(right) === false && account.userId !== user.id) {
109 res.status(HttpStatusCode.FORBIDDEN_403) 108 res.fail({
110 .json({ error: 'Cannot manage a video of another user.' }) 109 status: HttpStatusCode.FORBIDDEN_403,
111 .end() 110 message: 'Cannot manage a video of another user.'
111 })
112 return false 112 return false
113 } 113 }
114 114
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 3c09332b5..4068e3d7b 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -549,11 +549,11 @@ async function serveIndexHTML (req: express.Request, res: express.Response) {
549 return 549 return
550 } catch (err) { 550 } catch (err) {
551 logger.error('Cannot generate HTML page.', err) 551 logger.error('Cannot generate HTML page.', err)
552 return res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR_500) 552 return res.status(HttpStatusCode.INTERNAL_SERVER_ERROR_500).end()
553 } 553 }
554 } 554 }
555 555
556 return res.sendStatus(HttpStatusCode.NOT_ACCEPTABLE_406) 556 return res.status(HttpStatusCode.NOT_ACCEPTABLE_406).end()
557} 557}
558 558
559// --------------------------------------------------------------------------- 559// ---------------------------------------------------------------------------
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
diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts
index 1a9a519a0..e0f2f2112 100644
--- a/server/tests/api/users/users-verification.ts
+++ b/server/tests/api/users/users-verification.ts
@@ -19,6 +19,7 @@ import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/l
19import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' 19import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
20import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 20import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
21import { User } from '../../../../shared/models/users' 21import { User } from '../../../../shared/models/users'
22import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
22 23
23const expect = chai.expect 24const expect = chai.expect
24 25
@@ -89,8 +90,8 @@ describe('Test users account verification', function () {
89 }) 90 })
90 91
91 it('Should not allow login for user with unverified email', async function () { 92 it('Should not allow login for user with unverified email', async function () {
92 const resLogin = await login(server.url, server.client, user1, 400) 93 const resLogin = await login(server.url, server.client, user1, HttpStatusCode.BAD_REQUEST_400)
93 expect(resLogin.body.error).to.contain('User email is not verified.') 94 expect(resLogin.body.detail).to.contain('User email is not verified.')
94 }) 95 })
95 96
96 it('Should verify the user via email and allow login', async function () { 97 it('Should verify the user via email and allow login', async function () {
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index cea98aac7..363236b62 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -93,16 +93,16 @@ describe('Test users', function () {
93 const client = { id: 'client', secret: server.client.secret } 93 const client = { id: 'client', secret: server.client.secret }
94 const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400) 94 const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400)
95 95
96 expect(res.body.code).to.equal('invalid_client') 96 expect(res.body.type).to.equal('invalid_client')
97 expect(res.body.error).to.contain('client is invalid') 97 expect(res.body.detail).to.contain('client is invalid')
98 }) 98 })
99 99
100 it('Should not login with an invalid client secret', async function () { 100 it('Should not login with an invalid client secret', async function () {
101 const client = { id: server.client.id, secret: 'coucou' } 101 const client = { id: server.client.id, secret: 'coucou' }
102 const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400) 102 const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400)
103 103
104 expect(res.body.code).to.equal('invalid_client') 104 expect(res.body.type).to.equal('invalid_client')
105 expect(res.body.error).to.contain('client is invalid') 105 expect(res.body.detail).to.contain('client is invalid')
106 }) 106 })
107 }) 107 })
108 108
@@ -112,16 +112,16 @@ describe('Test users', function () {
112 const user = { username: 'captain crochet', password: server.user.password } 112 const user = { username: 'captain crochet', password: server.user.password }
113 const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400) 113 const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400)
114 114
115 expect(res.body.code).to.equal('invalid_grant') 115 expect(res.body.type).to.equal('invalid_grant')
116 expect(res.body.error).to.contain('credentials are invalid') 116 expect(res.body.detail).to.contain('credentials are invalid')
117 }) 117 })
118 118
119 it('Should not login with an invalid password', async function () { 119 it('Should not login with an invalid password', async function () {
120 const user = { username: server.user.username, password: 'mew_three' } 120 const user = { username: server.user.username, password: 'mew_three' }
121 const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400) 121 const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400)
122 122
123 expect(res.body.code).to.equal('invalid_grant') 123 expect(res.body.type).to.equal('invalid_grant')
124 expect(res.body.error).to.contain('credentials are invalid') 124 expect(res.body.detail).to.contain('credentials are invalid')
125 }) 125 })
126 126
127 it('Should not be able to upload a video', async function () { 127 it('Should not be able to upload a video', async function () {
diff --git a/server/typings/express/index.d.ts b/server/typings/express/index.d.ts
index 55b6e0039..f58436ce1 100644
--- a/server/typings/express/index.d.ts
+++ b/server/typings/express/index.d.ts
@@ -22,6 +22,7 @@ import { MAccountVideoRateAccountVideo } from '@server/types/models/video/video-
22import { HttpMethod } from '@shared/core-utils/miscs/http-methods' 22import { HttpMethod } from '@shared/core-utils/miscs/http-methods'
23import { VideoCreate } from '@shared/models' 23import { VideoCreate } from '@shared/models'
24import { File as UploadXFile, Metadata } from '@uploadx/core' 24import { File as UploadXFile, Metadata } from '@uploadx/core'
25import { ProblemDocumentOptions } from 'http-problem-details/dist/ProblemDocument'
25import { RegisteredPlugin } from '../../lib/plugins/plugin-manager' 26import { RegisteredPlugin } from '../../lib/plugins/plugin-manager'
26import { 27import {
27 MAccountDefault, 28 MAccountDefault,
@@ -83,8 +84,15 @@ declare module 'express' {
83 filename: string 84 filename: string
84 } 85 }
85 86
86 // Extends locals property from Response 87 // Extends Response with added functions and potential variables passed by middlewares
87 interface Response { 88 interface Response {
89 docs?: string
90 fail: (options: {
91 data?: Record<string, Object>
92 docs?: string
93 message: string
94 } & ProblemDocumentOptions) => void
95
88 locals: { 96 locals: {
89 videoAll?: MVideoFullLight 97 videoAll?: MVideoFullLight
90 onlyImmutableVideo?: MVideoImmutable 98 onlyImmutableVideo?: MVideoImmutable