From 76148b27f7501bac061992136852be4303370c8d Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Tue, 1 Jun 2021 01:36:53 +0200 Subject: refactor API errors to standard error format --- server/middlewares/validators/abuse.ts | 27 ++--- .../middlewares/validators/activitypub/activity.ts | 2 +- server/middlewares/validators/blocklist.ts | 29 +++--- server/middlewares/validators/bulk.ts | 6 +- server/middlewares/validators/config.ts | 13 +-- server/middlewares/validators/feeds.ts | 13 +-- server/middlewares/validators/follows.ts | 26 ++--- server/middlewares/validators/oembed.ts | 45 +++++--- server/middlewares/validators/plugins.ts | 42 +++++--- server/middlewares/validators/redundancy.ts | 57 +++++++---- server/middlewares/validators/server.ts | 31 +++--- server/middlewares/validators/themes.ts | 10 +- .../middlewares/validators/user-subscriptions.ts | 9 +- server/middlewares/validators/users.ts | 113 +++++++++++---------- server/middlewares/validators/utils.ts | 10 +- .../validators/videos/video-blacklist.ts | 8 +- .../validators/videos/video-channels.ts | 47 +++++---- .../validators/videos/video-comments.ts | 27 +++-- .../middlewares/validators/videos/video-imports.ts | 23 +++-- server/middlewares/validators/videos/video-live.ts | 61 ++++++----- .../validators/videos/video-playlists.ts | 73 ++++++------- .../middlewares/validators/videos/video-rates.ts | 6 +- .../middlewares/validators/videos/video-watch.ts | 5 +- server/middlewares/validators/videos/videos.ts | 105 +++++++++++-------- server/middlewares/validators/webfinger.ts | 7 +- 25 files changed, 452 insertions(+), 343 deletions(-) (limited to 'server/middlewares/validators') 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 = [ if (body.comment?.id && !await doesCommentIdExist(body.comment.id, res)) return if (!body.video?.id && !body.account?.id && !body.comment?.id) { - res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'video id or account id or comment id is required.' }) - + res.fail({ message: 'video id or account id or comment id is required.' }) return } @@ -195,8 +193,10 @@ const getAbuseValidator = [ const message = `User ${user.username} does not have right to get abuse ${abuse.id}` logger.warn(message) - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: message }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message + }) } return next() @@ -209,10 +209,7 @@ const checkAbuseValidForMessagesValidator = [ const abuse = res.locals.abuse if (abuse.ReporterAccount.isOwned() === false) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ - error: 'This abuse was created by a user of your instance.' - }) + return res.fail({ message: 'This abuse was created by a user of your instance.' }) } return next() @@ -246,13 +243,17 @@ const deleteAbuseMessageValidator = [ const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id) if (!abuseMessage) { - return res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Abuse message not found' }) + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Abuse message not found' + }) } if (user.hasRight(UserRight.MANAGE_ABUSES) !== true && abuseMessage.accountId !== user.Account.id) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot delete this abuse message' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot delete this abuse message' + }) } 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 if (!isRootActivityValid(req.body)) { logger.warn('Incorrect activity parameters.', { activity: req.body }) return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Incorrect activity.' }) + .end() } 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 = [ const accountToBlock = res.locals.account if (user.Account.id === accountToBlock.id) { - res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'You cannot block yourself.' }) - + res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'You cannot block yourself.' + }) return } @@ -79,8 +80,10 @@ const blockServerValidator = [ const host: string = req.body.host if (host === WEBSERVER.HOST) { - return res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'You cannot block your own server.' }) + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'You cannot block your own server.' + }) } const server = await ServerModel.loadOrCreateByHost(host) @@ -137,27 +140,27 @@ export { async function doesUnblockAccountExist (accountId: number, targetAccountId: number, res: express.Response) { const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(accountId, targetAccountId) if (!accountBlock) { - res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Account block entry not found.' }) - + res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Account block entry not found.' + }) return false } res.locals.accountBlock = accountBlock - return true } async function doesUnblockServerExist (accountId: number, host: string, res: express.Response) { const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(accountId, host) if (!serverBlock) { - res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Server block entry not found.' }) - + res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Server block entry not found.' + }) return false } res.locals.serverBlock = serverBlock - return true } 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 = [ const body = req.body as BulkRemoveCommentsOfBody if (body.scope === 'instance' && user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) !== true) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ - error: 'User cannot remove any comments of this instance.' + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'User cannot remove any comments of this instance.' }) } 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' import { body } from 'express-validator' import { isIntOrNull } from '@server/helpers/custom-validators/misc' import { isEmailEnabled } from '@server/initializers/config' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' import { CustomConfig } from '../../../shared/models/server/custom-config.model' import { isThemeNameValid } from '../../helpers/custom-validators/plugins' import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' @@ -115,9 +114,7 @@ function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: exp if (isEmailEnabled()) return true if (customConfig.signup.requiresEmailVerification === true) { - res.status(HttpStatusCode.BAD_REQUEST_400) - .send({ error: 'Emailer is disabled but you require signup email verification.' }) - .end() + res.fail({ message: 'Emailer is disabled but you require signup email verification.' }) return false } @@ -128,9 +125,7 @@ function checkInvalidTranscodingConfig (customConfig: CustomConfig, res: express if (customConfig.transcoding.enabled === false) return true if (customConfig.transcoding.webtorrent.enabled === false && customConfig.transcoding.hls.enabled === false) { - res.status(HttpStatusCode.BAD_REQUEST_400) - .send({ error: 'You need to enable at least webtorrent transcoding or hls transcoding' }) - .end() + res.fail({ message: 'You need to enable at least webtorrent transcoding or hls transcoding' }) return false } @@ -141,9 +136,7 @@ function checkInvalidLiveConfig (customConfig: CustomConfig, res: express.Respon if (customConfig.live.enabled === false) return true if (customConfig.live.allowReplay === true && customConfig.transcoding.enabled === false) { - res.status(HttpStatusCode.BAD_REQUEST_400) - .send({ error: 'You cannot allow live replay if transcoding is not enabled' }) - .end() + res.fail({ message: 'You cannot allow live replay if transcoding is not enabled' }) return false } 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, if (req.accepts(acceptableContentTypes)) { res.set('Content-Type', req.accepts(acceptableContentTypes) as string) } else { - return res.status(HttpStatusCode.NOT_ACCEPTABLE_406) - .json({ - message: `You should accept at least one of the following content-types: ${acceptableContentTypes.join(', ')}` - }) + return res.fail({ + status: HttpStatusCode.NOT_ACCEPTABLE_406, + message: `You should accept at least one of the following content-types: ${acceptableContentTypes.join(', ')}` + }) } return next() @@ -106,10 +106,7 @@ const videoCommentsFeedsValidator = [ if (areValidationErrors(req, res)) return if (req.query.videoId && (req.query.videoChannelId || req.query.videoChannelName)) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ - message: 'videoId cannot be mixed with a channel filter' - }) + return res.fail({ message: 'videoId cannot be mixed with a channel filter' }) } 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 = [ const follow = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(serverActor.id, SERVER_ACTOR_NAME, req.params.host) if (!follow) { - return res - .status(HttpStatusCode.NOT_FOUND_404) - .json({ - error: `Following ${req.params.host} not found.` - }) + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: `Following ${req.params.host} not found.` + }) } res.locals.follow = follow @@ -95,12 +94,10 @@ const getFollowerValidator = [ } if (!follow) { - return res - .status(HttpStatusCode.NOT_FOUND_404) - .json({ - error: `Follower ${req.params.nameWithHost} not found.` - }) - .end() + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: `Follower ${req.params.nameWithHost} not found.` + }) } res.locals.follow = follow @@ -114,12 +111,7 @@ const acceptOrRejectFollowerValidator = [ const follow = res.locals.follow if (follow.state !== 'pending') { - return res - .status(HttpStatusCode.BAD_REQUEST_400) - .json({ - error: 'Follow is not in pending state.' - }) - .end() + return res.fail({ message: 'Follow is not in pending state.' }) } 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 = [ if (areValidationErrors(req, res)) return if (req.query.format !== undefined && req.query.format !== 'json') { - return res.status(HttpStatusCode.NOT_IMPLEMENTED_501) - .json({ error: 'Requested format is not implemented on server.' }) + return res.fail({ + status: HttpStatusCode.NOT_IMPLEMENTED_501, + message: 'Requested format is not implemented on server.', + data: { + format: req.query.format + } + }) } const url = req.query.url as string @@ -65,27 +70,35 @@ const oembedValidator = [ const matches = watchRegex.exec(url) if (startIsOk === false || matches === null) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Invalid url.' }) + return res.fail({ + status: HttpStatusCode.BAD_REQUEST_400, + message: 'Invalid url.', + data: { + url + } + }) } const elementId = matches[1] if (isIdOrUUIDValid(elementId) === false) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Invalid video or playlist id.' }) + return res.fail({ message: 'Invalid video or playlist id.' }) } if (isVideo) { const video = await fetchVideo(elementId, 'all') if (!video) { - return res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Video not found' }) + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video not found' + }) } if (video.privacy !== VideoPrivacy.PUBLIC) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Video is not public' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Video is not public' + }) } res.locals.videoAll = video @@ -96,13 +109,17 @@ const oembedValidator = [ const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(elementId, undefined) if (!videoPlaylist) { - return res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Video playlist not found' }) + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video playlist not found' + }) } if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PUBLIC) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Playlist is not public' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Playlist is not public' + }) } 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) => { const npmName = PluginModel.buildNpmName(req.params.pluginName, pluginType) const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(npmName) - if (!plugin) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) - if (withVersion && plugin.version !== req.params.pluginVersion) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) + if (!plugin) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'No plugin found named ' + npmName + }) + } + if (withVersion && plugin.version !== req.params.pluginVersion) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'No plugin found named ' + npmName + ' with version ' + req.params.pluginVersion + }) + } res.locals.registeredPlugin = plugin @@ -50,10 +60,20 @@ const getExternalAuthValidator = [ if (areValidationErrors(req, res)) return const plugin = res.locals.registeredPlugin - if (!plugin.registerHelpers) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) + if (!plugin.registerHelpers) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'No registered helpers were found for this plugin' + }) + } const externalAuth = plugin.registerHelpers.getExternalAuths().find(a => a.authName === req.params.authName) - if (!externalAuth) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) + if (!externalAuth) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'No external auths were found for this plugin' + }) + } res.locals.externalAuth = externalAuth @@ -107,8 +127,7 @@ const installOrUpdatePluginValidator = [ const body: InstallOrUpdatePlugin = req.body if (!body.path && !body.npmName) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Should have either a npmName or a path' }) + return res.fail({ message: 'Should have either a npmName or a path' }) } return next() @@ -137,12 +156,13 @@ const existingPluginValidator = [ const plugin = await PluginModel.loadByNpmName(req.params.npmName) if (!plugin) { - return res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Plugin not found' }) + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Plugin not found' + }) } res.locals.plugin = plugin - return next() } ] @@ -177,9 +197,7 @@ const listAvailablePluginsValidator = [ if (areValidationErrors(req, res)) return if (CONFIG.PLUGINS.INDEX.ENABLED === false) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Plugin index is not enabled' }) - .end() + return res.fail({ message: 'Plugin index is not enabled' }) } 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 = [ return f.resolution === paramResolution && (!req.params.fps || paramFPS) }) - if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video file not found.' }) + if (!videoFile) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video file not found.' + }) + } res.locals.videoFile = videoFile const videoRedundancy = await VideoRedundancyModel.loadLocalByFileId(videoFile.id) - if (!videoRedundancy) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video redundancy not found.' }) + if (!videoRedundancy) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video redundancy not found.' + }) + } res.locals.videoRedundancy = videoRedundancy return next() @@ -65,11 +75,21 @@ const videoPlaylistRedundancyGetValidator = [ const paramPlaylistType = req.params.streamingPlaylistType as unknown as number // We casted to int above const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p.type === paramPlaylistType) - if (!videoStreamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video playlist not found.' }) + if (!videoStreamingPlaylist) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video playlist not found.' + }) + } res.locals.videoStreamingPlaylist = videoStreamingPlaylist const videoRedundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(videoStreamingPlaylist.id) - if (!videoRedundancy) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video redundancy not found.' }) + if (!videoRedundancy) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video redundancy not found.' + }) + } res.locals.videoRedundancy = videoRedundancy return next() @@ -90,12 +110,10 @@ const updateServerRedundancyValidator = [ const server = await ServerModel.loadByHost(req.params.host) if (!server) { - return res - .status(HttpStatusCode.NOT_FOUND_404) - .json({ - error: `Server ${req.params.host} not found.` - }) - .end() + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: `Server ${req.params.host} not found.` + }) } res.locals.server = server @@ -129,19 +147,19 @@ const addVideoRedundancyValidator = [ if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return if (res.locals.onlyVideo.remote === false) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot create a redundancy on a local video' }) + return res.fail({ message: 'Cannot create a redundancy on a local video' }) } if (res.locals.onlyVideo.isLive) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot create a redundancy of a live video' }) + return res.fail({ message: 'Cannot create a redundancy of a live video' }) } const alreadyExists = await VideoRedundancyModel.isLocalByVideoUUIDExists(res.locals.onlyVideo.uuid) if (alreadyExists) { - return res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'This video is already duplicated by your instance.' }) + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'This video is already duplicated by your instance.' + }) } return next() @@ -160,9 +178,10 @@ const removeVideoRedundancyValidator = [ const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10)) if (!redundancy) { - return res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Video redundancy not found' }) - .end() + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video redundancy not found' + }) } 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 = [ const server = await ServerModel.loadByHost(req.body.host) if (!server) { - return res.status(HttpStatusCode.NOT_FOUND_404) - .send({ error: 'Server host not found.' }) - .end() + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Server host not found.' + }) } res.locals.server = server @@ -44,26 +45,26 @@ const contactAdministratorValidator = [ if (areValidationErrors(req, res)) return if (CONFIG.CONTACT_FORM.ENABLED === false) { - return res - .status(HttpStatusCode.CONFLICT_409) - .send({ error: 'Contact form is not enabled on this instance.' }) - .end() + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Contact form is not enabled on this instance.' + }) } if (isEmailEnabled() === false) { - return res - .status(HttpStatusCode.CONFLICT_409) - .send({ error: 'Emailer is not enabled on this instance.' }) - .end() + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Emailer is not enabled on this instance.' + }) } if (await Redis.Instance.doesContactFormIpExist(req.ip)) { logger.info('Refusing a contact form by %s: already sent one recently.', req.ip) - return res - .status(HttpStatusCode.FORBIDDEN_403) - .send({ error: 'You already sent a contact form recently.' }) - .end() + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'You already sent a contact form recently.' + }) } 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 = [ const theme = PluginManager.Instance.getRegisteredThemeByShortName(req.params.themeName) if (!theme || theme.version !== req.params.themeVersion) { - return res.sendStatus(HttpStatusCode.NOT_FOUND_404) + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'No theme named ' + req.params.themeName + ' was found with version ' + req.params.themeVersion + }) } if (theme.css.includes(req.params.staticEndpoint) === false) { - return res.sendStatus(HttpStatusCode.NOT_FOUND_404) + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'No static endpoint was found for this theme' + }) } 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 = [ const subscription = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(user.Account.Actor.id, name, host) if (!subscription || !subscription.ActorFollowing.VideoChannel) { - return res - .status(HttpStatusCode.NOT_FOUND_404) - .json({ - error: `Subscription ${req.params.uri} not found.` - }) + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: `Subscription ${req.params.uri} not found.` + }) } 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 = [ const authUser = res.locals.oauth.token.User if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) { - return res - .status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'You can only create users (and not administrators or moderators)' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'You can only create users (and not administrators or moderators)' + }) } if (req.body.channelName) { if (req.body.channelName === req.body.username) { - return res - .status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Channel name cannot be the same as user username.' }) + return res.fail({ message: 'Channel name cannot be the same as user username.' }) } const existing = await ActorModel.loadLocalByName(req.body.channelName) if (existing) { - return res - .status(HttpStatusCode.CONFLICT_409) - .json({ error: `Channel with name ${req.body.channelName} already exists.` }) + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: `Channel with name ${req.body.channelName} already exists.` + }) } } @@ -121,20 +121,19 @@ const usersRegisterValidator = [ const body: UserRegister = req.body if (body.channel) { if (!body.channel.name || !body.channel.displayName) { - return res - .status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' }) + return res.fail({ message: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' }) } if (body.channel.name === body.username) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Channel name cannot be the same as user username.' }) + return res.fail({ message: 'Channel name cannot be the same as user username.' }) } const existing = await ActorModel.loadLocalByName(body.channel.name) if (existing) { - return res.status(HttpStatusCode.CONFLICT_409) - .json({ error: `Channel with name ${body.channel.name} already exists.` }) + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: `Channel with name ${body.channel.name} already exists.` + }) } } @@ -153,8 +152,7 @@ const usersRemoveValidator = [ const user = res.locals.user if (user.username === 'root') { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot remove the root user' }) + return res.fail({ message: 'Cannot remove the root user' }) } return next() @@ -173,8 +171,7 @@ const usersBlockingValidator = [ const user = res.locals.user if (user.username === 'root') { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot block the root user' }) + return res.fail({ message: 'Cannot block the root user' }) } return next() @@ -185,9 +182,7 @@ const deleteMeValidator = [ (req: express.Request, res: express.Response, next: express.NextFunction) => { const user = res.locals.oauth.token.User if (user.username === 'root') { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'You cannot delete your root account.' }) - .end() + return res.fail({ message: 'You cannot delete your root account.' }) } return next() @@ -217,8 +212,7 @@ const usersUpdateValidator = [ const user = res.locals.user if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot change root role.' }) + return res.fail({ message: 'Cannot change root role.' }) } return next() @@ -273,18 +267,18 @@ const usersUpdateMeValidator = [ if (req.body.password || req.body.email) { if (user.pluginAuth !== null) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'You cannot update your email or password that is associated with an external auth system.' }) + return res.fail({ message: 'You cannot update your email or password that is associated with an external auth system.' }) } if (!req.body.currentPassword) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'currentPassword parameter is missing.' }) + return res.fail({ message: 'currentPassword parameter is missing.' }) } if (await user.isPasswordMatch(req.body.currentPassword) !== true) { - return res.status(HttpStatusCode.UNAUTHORIZED_401) - .json({ error: 'currentPassword is invalid.' }) + return res.fail({ + status: HttpStatusCode.UNAUTHORIZED_401, + message: 'currentPassword is invalid.' + }) } } @@ -335,8 +329,10 @@ const ensureUserRegistrationAllowed = [ ) if (allowedResult.allowed === false) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.' + }) } return next() @@ -348,8 +344,10 @@ const ensureUserRegistrationAllowedForIP = [ const allowed = isSignupAllowedForCurrentIP(req.ip) if (allowed === false) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'You are not on a network authorized for registration.' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'You are not on a network authorized for registration.' + }) } return next() @@ -390,9 +388,10 @@ const usersResetPasswordValidator = [ const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id) if (redisVerificationString !== req.body.verificationString) { - return res - .status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Invalid verification string.' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Invalid verification string.' + }) } return next() @@ -437,9 +436,10 @@ const usersVerifyEmailValidator = [ const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id) if (redisVerificationString !== req.body.verificationString) { - return res - .status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Invalid verification string.' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Invalid verification string.' + }) } return next() @@ -455,8 +455,10 @@ const ensureAuthUserOwnsAccountValidator = [ const user = res.locals.oauth.token.User if (res.locals.account.id !== user.Account.id) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Only owner can access ratings list.' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Only owner can access ratings list.' + }) } return next() @@ -471,8 +473,10 @@ const ensureCanManageUser = [ if (authUser.role === UserRole.ADMINISTRATOR) return next() if (authUser.role === UserRole.MODERATOR && onUser.role === UserRole.USER) return next() - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'A moderator can only manager users.' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'A moderator can only manager users.' + }) } ] @@ -515,15 +519,19 @@ async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: const user = await UserModel.loadByUsernameOrEmail(username, email) if (user) { - res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'User with this username or email already exists.' }) + res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'User with this username or email already exists.' + }) return false } const actor = await ActorModel.loadLocalByName(username) if (actor) { - res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' }) + res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' + }) return false } @@ -535,14 +543,15 @@ async function checkUserExist (finder: () => Promise, res: express if (!user) { if (abortResponse === true) { - res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'User not found' }) + res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'User not found' + }) } return false } res.locals.user = user - return true } 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 @@ import * as express from 'express' import { query, validationResult } from 'express-validator' import { logger } from '../../helpers/logger' -import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' function areValidationErrors (req: express.Request, res: express.Response) { const errors = validationResult(req) if (!errors.isEmpty()) { logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() }) - res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ errors: errors.mapped() }) + res.fail({ + message: 'Incorrect request parameters: ' + Object.keys(errors.mapped()).join(', '), + instance: req.originalUrl, + data: { + 'invalid-params': errors.mapped() + } + }) return true } 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 = [ const video = res.locals.videoAll if (req.body.unfederate === true && video.remote === true) { - return res - .status(HttpStatusCode.CONFLICT_409) - .send({ error: 'You cannot unfederate a remote video.' }) - .end() + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'You cannot unfederate a remote video.' + }) } 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 = [ const actor = await ActorModel.loadLocalByName(req.body.name) if (actor) { - res.status(HttpStatusCode.CONFLICT_409) - .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' }) - .end() + res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' + }) return false } const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) if (count >= VIDEO_CHANNELS.MAX_PER_USER) { - res.status(HttpStatusCode.BAD_REQUEST_400) - .send({ error: `You cannot create more than ${VIDEO_CHANNELS.MAX_PER_USER} channels` }) - .end() + res.fail({ message: `You cannot create more than ${VIDEO_CHANNELS.MAX_PER_USER} channels` }) return false } @@ -71,13 +70,17 @@ const videoChannelsUpdateValidator = [ // We need to make additional checks if (res.locals.videoChannel.Actor.isOwned() === false) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot update video channel of another server' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot update video channel of another server' + }) } if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot update video channel of another user' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot update video channel of another user' + }) } return next() @@ -154,10 +157,10 @@ export { function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) { if (videoChannel.Actor.isOwned() === false) { - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot remove video channel of another server.' }) - .end() - + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot remove video channel of another server.' + }) return false } @@ -165,10 +168,10 @@ function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAcco // The user can delete it if s/he is an admin // Or if s/he is the video channel's account if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) { - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot remove video channel of another user' }) - .end() - + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot remove video channel of another user' + }) return false } @@ -179,10 +182,10 @@ async function checkVideoChannelIsNotTheLastOne (res: express.Response) { const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) if (count <= 1) { - res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'Cannot remove the last channel of this user' }) - .end() - + res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Cannot remove the last channel of this user' + }) return false } 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 { function isVideoCommentsEnabled (video: MVideo, res: express.Response) { if (video.commentsEnabled !== true) { - res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'Video comments are disabled for this video.' }) - + res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Video comments are disabled for this video.' + }) return false } @@ -166,9 +167,10 @@ function isVideoCommentsEnabled (video: MVideo, res: express.Response) { function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) { if (videoComment.isDeleted()) { - res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'This comment is already deleted' }) - + res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'This comment is already deleted' + }) return false } @@ -179,9 +181,10 @@ function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MC videoComment.accountId !== userAccount.id && // Not the comment owner videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner ) { - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot remove video comment of another user' }) - + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot remove video comment of another user' + }) return false } @@ -215,9 +218,11 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon if (!acceptedResult || acceptedResult.accepted !== true) { logger.info('Refused local comment.', { acceptedResult, acceptParameters }) - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: acceptedResult?.errorMessage || 'Refused local comment' }) + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: acceptedResult?.errorMessage || 'Refused local comment' + }) return false } 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([ if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true && req.body.targetUrl) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'HTTP import is not enabled on this instance.' }) + + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'HTTP import is not enabled on this instance.' + }) } if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.CONFLICT_409) - .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' }) + + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Torrent/magnet URI import is not enabled on this instance.' + }) } if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) @@ -63,8 +69,7 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([ if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' }) + return res.fail({ message: 'Should have a magnetUri or a targetUrl or a torrent file.' }) } if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) @@ -100,9 +105,11 @@ async function isImportAccepted (req: express.Request, res: express.Response) { if (!acceptedResult || acceptedResult.accepted !== true) { logger.info('Refused to import video.', { acceptedResult, acceptParameters }) - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: acceptedResult.errorMessage || 'Refused to import video' }) + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: acceptedResult.errorMessage || 'Refused to import video' + }) return false } 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 = [ if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.GET_ANY_LIVE, res, false)) return const videoLive = await VideoLiveModel.loadByVideoId(res.locals.videoAll.id) - if (!videoLive) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) + if (!videoLive) { + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Live video not found' + }) + } res.locals.videoLive = videoLive @@ -66,22 +71,25 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([ if (CONFIG.LIVE.ENABLED !== true) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Live is not enabled on this instance' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Live is not enabled on this instance' + }) } if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Saving live replay is not allowed instance' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Saving live replay is not allowed instance' + }) } if (req.body.permanentLive && req.body.saveReplay) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot set this live as permanent while saving its replay' }) + return res.fail({ message: 'Cannot set this live as permanent while saving its replay' }) } const user = res.locals.oauth.token.User @@ -93,11 +101,11 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([ if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ - code: ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED, - error: 'Cannot create this live because the max instance lives limit is reached.' - }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot create this live because the max instance lives limit is reached.', + type: ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED.toString() + }) } } @@ -107,11 +115,11 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([ if (totalUserLives >= CONFIG.LIVE.MAX_USER_LIVES) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ - code: ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED, - error: 'Cannot create this live because the max user lives limit is reached.' - }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + type: ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED.toString(), + message: 'Cannot create this live because the max user lives limit is reached.' + }) } } @@ -133,18 +141,18 @@ const videoLiveUpdateValidator = [ if (areValidationErrors(req, res)) return if (req.body.permanentLive && req.body.saveReplay) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot set this live as permanent while saving its replay' }) + return res.fail({ message: 'Cannot set this live as permanent while saving its replay' }) } if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Saving live replay is not allowed instance' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Saving live replay is not allowed instance' + }) } if (res.locals.videoAll.state !== VideoState.WAITING_FOR_LIVE) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot update a live that has already started' }) + return res.fail({ message: 'Cannot update a live that has already started' }) } // Check the user can manage the live @@ -180,9 +188,10 @@ async function isLiveVideoAccepted (req: express.Request, res: express.Response) if (!acceptedResult || acceptedResult.accepted !== true) { logger.info('Refused local live video.', { acceptedResult, acceptParameters }) - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: acceptedResult.errorMessage || 'Refused local live video' }) - + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: acceptedResult.errorMessage || 'Refused local live video' + }) return false } 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([ if (body.privacy === VideoPlaylistPrivacy.PUBLIC && !body.videoChannelId) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot set "public" a playlist that is not assigned to a channel.' }) + + return res.fail({ message: 'Cannot set "public" a playlist that is not assigned to a channel.' }) } return next() @@ -85,14 +85,14 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ ) ) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot set "public" a playlist that is not assigned to a channel.' }) + + return res.fail({ message: 'Cannot set "public" a playlist that is not assigned to a channel.' }) } if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { cleanUpReqFiles(req) - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot update a watch later playlist.' }) + + return res.fail({ message: 'Cannot update a watch later playlist.' }) } if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req) @@ -114,8 +114,7 @@ const videoPlaylistsDeleteValidator = [ const videoPlaylist = getPlaylist(res) if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { - return res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Cannot delete a watch later playlist.' }) + return res.fail({ message: 'Cannot delete a watch later playlist.' }) } if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { @@ -144,7 +143,10 @@ const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => { if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { if (isUUIDValid(req.params.playlistId)) return next() - return res.status(HttpStatusCode.NOT_FOUND_404).end() + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Playlist not found' + }) } if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { @@ -156,8 +158,10 @@ const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => { !user || (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) ) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot get this private video playlist.' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot get this private video playlist.' + }) } return next() @@ -233,10 +237,10 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [ const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) if (!videoPlaylistElement) { - res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Video playlist element not found' }) - .end() - + res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video playlist element not found' + }) return } res.locals.videoPlaylistElement = videoPlaylistElement @@ -263,15 +267,18 @@ const videoPlaylistElementAPGetValidator = [ const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId) if (!videoPlaylistElement) { - res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Video playlist element not found' }) - .end() - + res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video playlist element not found' + }) return } if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { - return res.status(HttpStatusCode.FORBIDDEN_403).end() + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot get this private video playlist.' + }) } res.locals.videoPlaylistElementAP = videoPlaylistElement @@ -307,18 +314,12 @@ const videoPlaylistsReorderVideosValidator = [ const reorderLength: number = req.body.reorderLength if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) { - res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` }) - .end() - + res.fail({ message: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` }) return } if (reorderLength && reorderLength + startPosition > nextPosition) { - res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` }) - .end() - + res.fail({ message: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` }) return } @@ -401,10 +402,10 @@ function getCommonPlaylistEditAttributes () { function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) { if (videoPlaylist.isOwned() === false) { - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot manage video playlist of another server.' }) - .end() - + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot manage video playlist of another server.' + }) return false } @@ -412,10 +413,10 @@ function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: M // The user can delete it if s/he is an admin // Or if s/he is the video playlist's owner if (user.hasRight(right) === false && videoPlaylist.ownerAccountId !== user.Account.id) { - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot manage video playlist of another user' }) - .end() - + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot manage video playlist of another user' + }) return false } 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) { const rate = await AccountVideoRateModel.loadLocalAndPopulateVideo(rateType, req.params.name, +req.params.videoId) if (!rate) { - return res.status(HttpStatusCode.NOT_FOUND_404) - .json({ error: 'Video rate not found' }) + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video rate not found' + }) } 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 = [ const user = res.locals.oauth.token.User if (user.videosHistoryEnabled === false) { logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id) - return res.status(HttpStatusCode.CONFLICT_409).end() + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Video history is disabled' + }) } 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([ .custom(isIdValid).withMessage('Should have correct video channel id'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.docs = "https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadLegacy" logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) if (areValidationErrors(req, res)) return cleanUpReqFiles(req) @@ -88,9 +89,11 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([ if (!videoFile.duration) await addDurationToVideo(videoFile) } catch (err) { logger.error('Invalid input file in videosAddLegacyValidator.', { err }) - res.status(HttpStatusCode.UNPROCESSABLE_ENTITY_422) - .json({ error: 'Video file unreadable.' }) + res.fail({ + status: HttpStatusCode.UNPROCESSABLE_ENTITY_422, + message: 'Video file unreadable.' + }) return cleanUpReqFiles(req) } @@ -105,6 +108,7 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([ */ const videosAddResumableValidator = [ async (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.docs = "https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadResumable" const user = res.locals.oauth.token.User const body: express.CustomUploadXFile = req.body @@ -118,9 +122,11 @@ const videosAddResumableValidator = [ if (!file.duration) await addDurationToVideo(file) } catch (err) { logger.error('Invalid input file in videosAddResumableValidator.', { err }) - res.status(HttpStatusCode.UNPROCESSABLE_ENTITY_422) - .json({ error: 'Video file unreadable.' }) + res.fail({ + status: HttpStatusCode.UNPROCESSABLE_ENTITY_422, + message: 'Video file unreadable.' + }) return cleanup() } @@ -164,6 +170,7 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([ .withMessage('Should specify the file mimetype'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.docs = "https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadResumableInit" const videoFileMetadata = { mimetype: req.headers['x-upload-content-type'] as string, size: +req.headers['x-upload-content-length'], @@ -207,6 +214,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([ .custom(isIdValid).withMessage('Should have correct video channel id'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.docs = 'https://docs.joinpeertube.org/api-rest-reference.html#operation/putVideo' logger.debug('Checking videosUpdate parameters', { parameters: req.body }) if (areValidationErrors(req, res)) return cleanUpReqFiles(req) @@ -242,12 +250,14 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R const serverActor = await getServerActor() if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next() - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ - errorCode: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS, - error: 'Cannot get this video regarding follow constraints.', - originUrl: video.url - }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot get this video regarding follow constraints.', + type: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS.toString(), + data: { + originUrl: video.url + } + }) } const videosCustomGetValidator = ( @@ -258,6 +268,7 @@ const videosCustomGetValidator = ( param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.docs = 'https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo' logger.debug('Checking videosGet parameters', { parameters: req.params }) if (areValidationErrors(req, res)) return @@ -276,8 +287,10 @@ const videosCustomGetValidator = ( // Only the owner or a user that have blacklist rights can see the video if (!user || !user.canGetVideo(video)) { - return res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Cannot get this private/internal or blacklisted video.' }) + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot get this private/internal or blacklisted video.' + }) } return next() @@ -291,7 +304,10 @@ const videosCustomGetValidator = ( if (isUUIDValid(req.params.id)) return next() // Don't leak this unlisted video - return res.status(HttpStatusCode.NOT_FOUND_404).end() + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Video not found' + }) } } ] @@ -318,6 +334,7 @@ const videosRemoveValidator = [ param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.docs = "https://docs.joinpeertube.org/api-rest-reference.html#operation/delVideo" logger.debug('Checking videosRemove parameters', { parameters: req.params }) if (areValidationErrors(req, res)) return @@ -344,13 +361,11 @@ const videosChangeOwnershipValidator = [ const nextOwner = await AccountModel.loadLocalByName(req.body.username) if (!nextOwner) { - res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Changing video ownership to a remote account is not supported yet' }) - + res.fail({ message: 'Changing video ownership to a remote account is not supported yet' }) return } - res.locals.nextOwner = nextOwner + res.locals.nextOwner = nextOwner return next() } ] @@ -370,8 +385,10 @@ const videosTerminateChangeOwnershipValidator = [ const videoChangeOwnership = res.locals.videoChangeOwnership if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) { - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: 'Ownership already accepted or refused' }) + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Ownership already accepted or refused' + }) return } @@ -388,9 +405,10 @@ const videosAcceptChangeOwnershipValidator = [ const videoChangeOwnership = res.locals.videoChangeOwnership const isAble = await isAbleToUploadVideo(user.id, videoChangeOwnership.Video.getMaxQualityFile().size) if (isAble === false) { - res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413) - .json({ error: 'The user video quota is exceeded with this video.' }) - + res.fail({ + status: HttpStatusCode.PAYLOAD_TOO_LARGE_413, + message: 'The user video quota is exceeded with this video.' + }) return } @@ -538,9 +556,10 @@ const commonVideosFiltersValidator = [ (req.query.filter === 'all-local' || req.query.filter === 'all') && (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false) ) { - res.status(HttpStatusCode.UNAUTHORIZED_401) - .json({ error: 'You are not allowed to see all local videos.' }) - + res.fail({ + status: HttpStatusCode.UNAUTHORIZED_401, + message: 'You are not allowed to see all local videos.' + }) return } @@ -581,9 +600,7 @@ function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) if (!req.body.scheduleUpdate.updateAt) { logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.') - res.status(HttpStatusCode.BAD_REQUEST_400) - .json({ error: 'Schedule update at is mandatory.' }) - + res.fail({ message: 'Schedule update at is mandatory.' }) return true } } @@ -605,26 +622,27 @@ async function commonVideoChecksPass (parameters: { if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return false if (!isVideoFileMimeTypeValid(files)) { - res.status(HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415) - .json({ - error: 'This file is not supported. Please, make sure it is of the following type: ' + - CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') - }) - + res.fail({ + status: HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415, + message: 'This file is not supported. Please, make sure it is of the following type: ' + + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') + }) return false } if (!isVideoFileSizeValid(videoFileSize.toString())) { - res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413) - .json({ error: 'This file is too large. It exceeds the maximum file size authorized.' }) - + res.fail({ + status: HttpStatusCode.PAYLOAD_TOO_LARGE_413, + message: 'This file is too large. It exceeds the maximum file size authorized.' + }) return false } if (await isAbleToUploadVideo(user.id, videoFileSize) === false) { - res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413) - .json({ error: 'The user video quota is exceeded with this video.' }) - + res.fail({ + status: HttpStatusCode.PAYLOAD_TOO_LARGE_413, + message: 'The user video quota is exceeded with this video.' + }) return false } @@ -650,9 +668,10 @@ export async function isVideoAccepted ( if (!acceptedResult || acceptedResult.accepted !== true) { logger.info('Refused local video.', { acceptedResult, acceptParameters }) - res.status(HttpStatusCode.FORBIDDEN_403) - .json({ error: acceptedResult.errorMessage || 'Refused local video' }) - + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: acceptedResult.errorMessage || 'Refused local video' + }) return false } 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 = [ const actor = await ActorModel.loadLocalUrlByName(name) if (!actor) { - return res.status(HttpStatusCode.NOT_FOUND_404) - .send({ error: 'Actor not found' }) - .end() + return res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'Actor not found' + }) } res.locals.actorUrl = actor -- cgit v1.2.3