refactor API errors to standard error format
This commit is contained in:
parent
5ed25fb76e
commit
76148b27f7
75 changed files with 785 additions and 547 deletions
|
@ -431,7 +431,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
.pipe(
|
||||
// If 400, 403 or 404, the video is private or blocked so redirect to 404
|
||||
catchError(err => {
|
||||
if (err.body.errorCode === ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS && err.body.originUrl) {
|
||||
if (err.body.type === ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS && err.body.originUrl) {
|
||||
const search = window.location.search
|
||||
let originUrl = err.body.originUrl
|
||||
if (search) originUrl += search
|
||||
|
|
|
@ -41,7 +41,7 @@ export class RestExtractor {
|
|||
|
||||
if (err.error instanceof Error) {
|
||||
// A client-side or network error occurred. Handle it accordingly.
|
||||
errorMessage = err.error.message
|
||||
errorMessage = err.error.detail || err.error.title
|
||||
console.error('An error occurred:', errorMessage)
|
||||
} else if (typeof err.error === 'string') {
|
||||
errorMessage = err.error
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
"fs-extra": "^10.0.0",
|
||||
"got": "^11.8.2",
|
||||
"helmet": "^4.1.0",
|
||||
"http-problem-details": "^0.1.5",
|
||||
"http-signature": "1.3.5",
|
||||
"ip-anonymize": "^0.1.0",
|
||||
"ipaddr.js": "2.0.0",
|
||||
|
|
10
server.ts
10
server.ts
|
@ -128,6 +128,7 @@ import { LiveManager } from './server/lib/live-manager'
|
|||
import { HttpStatusCode } from './shared/core-utils/miscs/http-error-codes'
|
||||
import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache'
|
||||
import { ServerConfigManager } from '@server/lib/server-config-manager'
|
||||
import { apiResponseHelpers } from '@server/helpers/express-utils'
|
||||
|
||||
// ----------- Command line -----------
|
||||
|
||||
|
@ -186,6 +187,9 @@ app.use(cookieParser())
|
|||
// W3C DNT Tracking Status
|
||||
app.use(advertiseDoNotTrack)
|
||||
|
||||
// Response helpers used in developement
|
||||
app.use(apiResponseHelpers)
|
||||
|
||||
// ----------- Views, routes and static files -----------
|
||||
|
||||
// API
|
||||
|
@ -235,7 +239,11 @@ app.use(function (err, req, res, next) {
|
|||
const sql = err.parent ? err.parent.sql : undefined
|
||||
|
||||
logger.error('Error in controller.', { err: error, sql })
|
||||
return res.status(err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500).end()
|
||||
return res.fail({
|
||||
status: err.status || HttpStatusCode.INTERNAL_SERVER_ERROR_500,
|
||||
message: err.message,
|
||||
type: err.name
|
||||
})
|
||||
})
|
||||
|
||||
const server = createWebsocketTrackerServer(app)
|
||||
|
|
|
@ -142,7 +142,7 @@ async function updateAbuse (req: express.Request, res: express.Response) {
|
|||
|
||||
// Do not send the delete to other instances, we updated OUR copy of this abuse
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function deleteAbuse (req: express.Request, res: express.Response) {
|
||||
|
@ -154,7 +154,7 @@ async function deleteAbuse (req: express.Request, res: express.Response) {
|
|||
|
||||
// Do not send the delete to other instances, we delete OUR copy of this abuse
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function reportAbuse (req: express.Request, res: express.Response) {
|
||||
|
@ -244,5 +244,5 @@ async function deleteAbuseMessage (req: express.Request, res: express.Response)
|
|||
return abuseMessage.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ async function bulkRemoveCommentsOf (req: express.Request, res: express.Response
|
|||
const comments = await VideoCommentModel.listForBulkDelete(account, filter)
|
||||
|
||||
// Don't wait result
|
||||
res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
|
||||
for (const comment of comments) {
|
||||
await removeComment(comment)
|
||||
|
|
|
@ -27,7 +27,12 @@ export {
|
|||
|
||||
async function getInstanceHomepage (req: express.Request, res: express.Response) {
|
||||
const page = await ActorCustomPageModel.loadInstanceHomepage()
|
||||
if (!page) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!page) {
|
||||
return res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Instance homepage could not be found'
|
||||
})
|
||||
}
|
||||
|
||||
return res.json(page.toFormattedJSON())
|
||||
}
|
||||
|
@ -38,5 +43,5 @@ async function updateInstanceHomepage (req: express.Request, res: express.Respon
|
|||
await ActorCustomPageModel.updateInstanceHomepage(content)
|
||||
ServerConfigManager.Instance.updateHomepageState(content)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
|
|
@ -24,7 +24,10 @@ async function getLocalClient (req: express.Request, res: express.Response, next
|
|||
// Don't make this check if this is a test instance
|
||||
if (process.env.NODE_ENV !== 'test' && req.get('host') !== headerHostShouldBe) {
|
||||
logger.info('Getting client tokens for host %s is forbidden (expected %s).', req.get('host'), headerHostShouldBe)
|
||||
return res.type('json').status(HttpStatusCode.FORBIDDEN_403).end()
|
||||
return res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: `Getting client tokens for host ${req.get('host')} is forbidden`
|
||||
})
|
||||
}
|
||||
|
||||
const client = await OAuthClientModel.loadFirstClient()
|
||||
|
|
|
@ -144,7 +144,7 @@ async function installPlugin (req: express.Request, res: express.Response) {
|
|||
return res.json(plugin.toFormattedJSON())
|
||||
} catch (err) {
|
||||
logger.warn('Cannot install plugin %s.', toInstall, { err })
|
||||
return res.sendStatus(HttpStatusCode.BAD_REQUEST_400)
|
||||
return res.fail({ message: 'Cannot install plugin ' + toInstall })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,7 +159,7 @@ async function updatePlugin (req: express.Request, res: express.Response) {
|
|||
return res.json(plugin.toFormattedJSON())
|
||||
} catch (err) {
|
||||
logger.warn('Cannot update plugin %s.', toUpdate, { err })
|
||||
return res.sendStatus(HttpStatusCode.BAD_REQUEST_400)
|
||||
return res.fail({ message: 'Cannot update plugin ' + toUpdate })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +168,7 @@ async function uninstallPlugin (req: express.Request, res: express.Response) {
|
|||
|
||||
await PluginManager.Instance.uninstall(body.npmName)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
function getPublicPluginSettings (req: express.Request, res: express.Response) {
|
||||
|
@ -197,7 +197,7 @@ async function updatePluginSettings (req: express.Request, res: express.Response
|
|||
|
||||
await PluginManager.Instance.onSettingsChanged(plugin.name, plugin.settings)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function listAvailablePlugins (req: express.Request, res: express.Response) {
|
||||
|
@ -206,8 +206,10 @@ async function listAvailablePlugins (req: express.Request, res: express.Response
|
|||
const resultList = await listAvailablePluginsFromIndex(query)
|
||||
|
||||
if (!resultList) {
|
||||
return res.status(HttpStatusCode.SERVICE_UNAVAILABLE_503)
|
||||
.json({ error: 'Plugin index unavailable. Please retry later' })
|
||||
return res.fail({
|
||||
status: HttpStatusCode.SERVICE_UNAVAILABLE_503,
|
||||
message: 'Plugin index unavailable. Please retry later'
|
||||
})
|
||||
}
|
||||
|
||||
return res.json(resultList)
|
||||
|
|
|
@ -102,7 +102,10 @@ async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: e
|
|||
} catch (err) {
|
||||
logger.warn('Cannot use search index to make video channels search.', { err })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR_500)
|
||||
return res.fail({
|
||||
status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
|
||||
message: 'Cannot use search index to make video channels search'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +205,10 @@ async function searchVideosIndex (query: VideosSearchQuery, res: express.Respons
|
|||
} catch (err) {
|
||||
logger.warn('Cannot use search index to make video search.', { err })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR_500)
|
||||
return res.fail({
|
||||
status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
|
||||
message: 'Cannot use search index to make video search'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { InboxManager } from '@server/lib/activitypub/inbox-manager'
|
||||
import { RemoveDanglingResumableUploadsScheduler } from '@server/lib/schedulers/remove-dangling-resumable-uploads-scheduler'
|
||||
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
||||
import { SendDebugCommand } from '@shared/models'
|
||||
import * as express from 'express'
|
||||
import { UserRight } from '../../../../shared/models/users'
|
||||
|
@ -41,5 +42,5 @@ async function runCommand (req: express.Request, res: express.Response) {
|
|||
await RemoveDanglingResumableUploadsScheduler.Instance.execute()
|
||||
}
|
||||
|
||||
return res.sendStatus(204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
|
|
@ -90,13 +90,13 @@ async function addVideoRedundancy (req: express.Request, res: express.Response)
|
|||
payload
|
||||
})
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function removeVideoRedundancyController (req: express.Request, res: express.Response) {
|
||||
await removeVideoRedundancy(res.locals.videoRedundancy)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function updateRedundancy (req: express.Request, res: express.Response) {
|
||||
|
@ -110,5 +110,5 @@ async function updateRedundancy (req: express.Request, res: express.Response) {
|
|||
removeRedundanciesOfServer(server.id)
|
||||
.catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err }))
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
|
|
@ -314,7 +314,7 @@ async function removeUser (req: express.Request, res: express.Response) {
|
|||
|
||||
Hooks.runAction('action:api.user.deleted', { user })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function updateUser (req: express.Request, res: express.Response) {
|
||||
|
@ -349,7 +349,7 @@ async function updateUser (req: express.Request, res: express.Response) {
|
|||
|
||||
// Don't need to send this update to followers, these attributes are not federated
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function askResetUserPassword (req: express.Request, res: express.Response) {
|
||||
|
|
|
@ -183,7 +183,7 @@ async function deleteMe (req: express.Request, res: express.Response) {
|
|||
|
||||
await user.destroy()
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function updateMe (req: express.Request, res: express.Response) {
|
||||
|
@ -237,7 +237,7 @@ async function updateMe (req: express.Request, res: express.Response) {
|
|||
await sendVerifyUserEmail(user, true)
|
||||
}
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function updateMyAvatar (req: express.Request, res: express.Response) {
|
||||
|
@ -257,5 +257,5 @@ async function deleteMyAvatar (req: express.Request, res: express.Response) {
|
|||
const userAccount = await AccountModel.load(user.Account.id)
|
||||
await deleteLocalActorImageFile(userAccount, ActorImageType.AVATAR)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
|
|
@ -78,9 +78,10 @@ async function handleToken (req: express.Request, res: express.Response, next: e
|
|||
} catch (err) {
|
||||
logger.warn('Login error', { err })
|
||||
|
||||
return res.status(err.code || 400).json({
|
||||
code: err.name,
|
||||
error: err.message
|
||||
return res.fail({
|
||||
status: err.code,
|
||||
message: err.message,
|
||||
type: err.name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ async function deleteVideoChannelAvatar (req: express.Request, res: express.Resp
|
|||
|
||||
await deleteLocalActorImageFile(videoChannel, ActorImageType.AVATAR)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function deleteVideoChannelBanner (req: express.Request, res: express.Response) {
|
||||
|
@ -188,7 +188,7 @@ async function deleteVideoChannelBanner (req: express.Request, res: express.Resp
|
|||
|
||||
await deleteLocalActorImageFile(videoChannel, ActorImageType.BANNER)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function addVideoChannel (req: express.Request, res: express.Response) {
|
||||
|
|
|
@ -70,7 +70,7 @@ async function addVideoToBlacklistController (req: express.Request, res: express
|
|||
|
||||
logger.info('Video %s blacklisted.', videoInstance.uuid)
|
||||
|
||||
return res.type('json').sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function updateVideoBlacklistController (req: express.Request, res: express.Response) {
|
||||
|
@ -82,7 +82,7 @@ async function updateVideoBlacklistController (req: express.Request, res: expres
|
|||
return videoBlacklist.save({ transaction: t })
|
||||
})
|
||||
|
||||
return res.type('json').sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function listBlacklist (req: express.Request, res: express.Response) {
|
||||
|
@ -105,5 +105,5 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex
|
|||
|
||||
logger.info('Video %s removed from blacklist.', video.uuid)
|
||||
|
||||
return res.type('json').sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
|
|
@ -166,7 +166,10 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo
|
|||
}
|
||||
|
||||
if (resultList.data.length === 0) {
|
||||
return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
return res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'No comments were found'
|
||||
})
|
||||
}
|
||||
|
||||
return res.json(buildFormattedCommentTree(resultList))
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
} from '@server/types/models'
|
||||
import { MVideoImportFormattable } from '@server/types/models/video/video-import'
|
||||
import { ServerErrorCode, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared'
|
||||
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
||||
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
|
||||
import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
|
||||
import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
|
||||
|
@ -143,10 +142,12 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
|
|||
} catch (err) {
|
||||
logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err })
|
||||
|
||||
return res.status(HttpStatusCode.BAD_REQUEST_400)
|
||||
.json({
|
||||
error: 'Cannot fetch remote information of this URL.'
|
||||
})
|
||||
return res.fail({
|
||||
message: 'Cannot fetch remote information of this URL.',
|
||||
data: {
|
||||
targetUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo)
|
||||
|
@ -333,12 +334,10 @@ async function processTorrentOrAbortRequest (req: express.Request, res: express.
|
|||
if (parsedTorrent.files.length !== 1) {
|
||||
cleanUpReqFiles(req)
|
||||
|
||||
res.status(HttpStatusCode.BAD_REQUEST_400)
|
||||
.json({
|
||||
code: ServerErrorCode.INCORRECT_FILES_IN_TORRENT,
|
||||
error: 'Torrents with only 1 file are supported.'
|
||||
})
|
||||
|
||||
res.fail({
|
||||
type: ServerErrorCode.INCORRECT_FILES_IN_TORRENT.toString(),
|
||||
message: 'Torrents with only 1 file are supported.'
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
|
|||
const exists = await Redis.Instance.doesVideoIPViewExist(ip, immutableVideoAttrs.uuid)
|
||||
if (exists) {
|
||||
logger.debug('View for ip %s and video %s already exists.', ip, immutableVideoAttrs.uuid)
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
const video = await VideoModel.load(immutableVideoAttrs.id)
|
||||
|
@ -179,7 +179,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
|
|||
|
||||
Hooks.runAction('action:api.video.viewed', { video, ip })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function getVideoDescription (req: express.Request, res: express.Response) {
|
||||
|
|
|
@ -76,7 +76,7 @@ async function updateLiveVideo (req: express.Request, res: express.Response) {
|
|||
|
||||
await federateVideoIfNeeded(video, false)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function addLiveVideo (req: express.Request, res: express.Response) {
|
||||
|
|
|
@ -122,7 +122,7 @@ function acceptOwnership (req: express.Request, res: express.Response) {
|
|||
videoChangeOwnership.status = VideoChangeOwnershipStatus.ACCEPTED
|
||||
await videoChangeOwnership.save({ transaction: t })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -133,6 +133,6 @@ function refuseOwnership (req: express.Request, res: express.Response) {
|
|||
videoChangeOwnership.status = VideoChangeOwnershipStatus.REFUSED
|
||||
await videoChangeOwnership.save({ transaction: t })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -97,8 +97,11 @@ export async function addVideoLegacy (req: express.Request, res: express.Respons
|
|||
// Uploading the video could be long
|
||||
// Set timeout to 10 minutes, as Express's default is 2 minutes
|
||||
req.setTimeout(1000 * 60 * 10, () => {
|
||||
logger.error('Upload video has timed out.')
|
||||
return res.sendStatus(HttpStatusCode.REQUEST_TIMEOUT_408)
|
||||
logger.error('Video upload has timed out.')
|
||||
return res.fail({
|
||||
status: HttpStatusCode.REQUEST_TIMEOUT_408,
|
||||
message: 'Video upload has timed out.'
|
||||
})
|
||||
})
|
||||
|
||||
const videoPhysicalFile = req.files['videofile'][0]
|
||||
|
|
|
@ -78,7 +78,7 @@ clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE.C
|
|||
|
||||
// 404 for static files not found
|
||||
clientsRouter.use('/client/*', (req: express.Request, res: express.Response) => {
|
||||
res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
})
|
||||
|
||||
// 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) {
|
|||
return res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER })
|
||||
}
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
}
|
||||
|
||||
async function generateEmbedHtmlPage (req: express.Request, res: express.Response) {
|
||||
|
|
|
@ -41,7 +41,12 @@ export {
|
|||
|
||||
async function downloadTorrent (req: express.Request, res: express.Response) {
|
||||
const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename)
|
||||
if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!result) {
|
||||
return res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Torrent file not found'
|
||||
})
|
||||
}
|
||||
|
||||
const allowParameters = { torrentPath: result.path, downloadName: result.downloadName }
|
||||
|
||||
|
@ -60,7 +65,12 @@ async function downloadVideoFile (req: express.Request, res: express.Response) {
|
|||
const video = res.locals.videoAll
|
||||
|
||||
const videoFile = getVideoFile(req, video.VideoFiles)
|
||||
if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
if (!videoFile) {
|
||||
return res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video file not found'
|
||||
})
|
||||
}
|
||||
|
||||
const allowParameters = { video, videoFile }
|
||||
|
||||
|
@ -81,7 +91,12 @@ async function downloadHLSVideoFile (req: express.Request, res: express.Response
|
|||
if (!streamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).end
|
||||
|
||||
const videoFile = getVideoFile(req, streamingPlaylist.VideoFiles)
|
||||
if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
if (!videoFile) {
|
||||
return res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video file not found'
|
||||
})
|
||||
}
|
||||
|
||||
const allowParameters = { video, streamingPlaylist, videoFile }
|
||||
|
||||
|
@ -131,9 +146,11 @@ function isVideoDownloadAllowed (_object: {
|
|||
function checkAllowResult (res: express.Response, allowParameters: any, result?: AllowedResult) {
|
||||
if (!result || result.allowed !== true) {
|
||||
logger.info('Download is not allowed.', { result, allowParameters })
|
||||
res.status(HttpStatusCode.FORBIDDEN_403)
|
||||
.json({ error: result?.errorMessage || 'Refused download' })
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: result?.errorMessage || 'Refused download'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -56,10 +56,10 @@ async function getActorImage (req: express.Request, res: express.Response) {
|
|||
}
|
||||
|
||||
const image = await ActorImageModel.loadByName(filename)
|
||||
if (!image) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!image) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
|
||||
if (image.onDisk === false) {
|
||||
if (!image.fileUrl) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!image.fileUrl) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
|
||||
logger.info('Lazy serve remote actor image %s.', image.fileUrl)
|
||||
|
||||
|
@ -67,7 +67,7 @@ async function getActorImage (req: express.Request, res: express.Response) {
|
|||
await pushActorImageProcessInQueue({ filename: image.filename, fileUrl: image.fileUrl, type: image.type })
|
||||
} catch (err) {
|
||||
logger.warn('Cannot process remote actor image %s.', image.fileUrl, { err })
|
||||
return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
}
|
||||
|
||||
image.onDisk = true
|
||||
|
@ -83,21 +83,21 @@ async function getActorImage (req: express.Request, res: express.Response) {
|
|||
|
||||
async function getPreview (req: express.Request, res: express.Response) {
|
||||
const result = await VideosPreviewCache.Instance.getFilePath(req.params.filename)
|
||||
if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
|
||||
return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER })
|
||||
}
|
||||
|
||||
async function getVideoCaption (req: express.Request, res: express.Response) {
|
||||
const result = await VideosCaptionCache.Instance.getFilePath(req.params.filename)
|
||||
if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
|
||||
return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER })
|
||||
}
|
||||
|
||||
async function getTorrent (req: express.Request, res: express.Response) {
|
||||
const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename)
|
||||
if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
|
||||
// Torrents still use the old naming convention (video uuid + .torrent)
|
||||
return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER })
|
||||
|
|
|
@ -25,7 +25,7 @@ function getSegmentsSha256 (req: express.Request, res: express.Response) {
|
|||
const result = LiveManager.Instance.getSegmentsSha256(videoUUID)
|
||||
|
||||
if (!result) {
|
||||
return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
}
|
||||
|
||||
return res.json(mapToJSON(result))
|
||||
|
|
|
@ -100,7 +100,7 @@ function getPluginTranslations (req: express.Request, res: express.Response) {
|
|||
return res.json(json)
|
||||
}
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
}
|
||||
|
||||
function servePluginStaticDirectory (req: express.Request, res: express.Response) {
|
||||
|
@ -110,7 +110,7 @@ function servePluginStaticDirectory (req: express.Request, res: express.Response
|
|||
const [ directory, ...file ] = staticEndpoint.split('/')
|
||||
|
||||
const staticPath = plugin.staticDirs[directory]
|
||||
if (!staticPath) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!staticPath) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
|
||||
const filepath = file.join('/')
|
||||
return res.sendFile(join(plugin.path, staticPath, filepath), sendFileOptions)
|
||||
|
@ -120,7 +120,7 @@ function servePluginCustomRoutes (req: express.Request, res: express.Response, n
|
|||
const plugin: RegisteredPlugin = res.locals.registeredPlugin
|
||||
const router = PluginManager.Instance.getRouter(plugin.npmName)
|
||||
|
||||
if (!router) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!router) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
|
||||
return router(req, res, next)
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ function servePluginClientScripts (req: express.Request, res: express.Response)
|
|||
const staticEndpoint = req.params.staticEndpoint
|
||||
|
||||
const file = plugin.clientScripts[staticEndpoint]
|
||||
if (!file) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
if (!file) return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
|
||||
return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ function serveThemeCSSDirectory (req: express.Request, res: express.Response) {
|
|||
const staticEndpoint = req.params.staticEndpoint
|
||||
|
||||
if (plugin.css.includes(staticEndpoint) === false) {
|
||||
return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
return res.status(HttpStatusCode.NOT_FOUND_404).end()
|
||||
}
|
||||
|
||||
return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
|
||||
|
|
|
@ -160,10 +160,9 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
|
|||
const { totalVideos } = await VideoModel.getStats()
|
||||
const { totalLocalVideoComments } = await VideoCommentModel.getStats()
|
||||
const { totalUsers, totalMonthlyActiveUsers, totalHalfYearActiveUsers } = await UserModel.getStats()
|
||||
let json = {}
|
||||
|
||||
if (req.params.version && (req.params.version === '2.0')) {
|
||||
json = {
|
||||
const json = {
|
||||
version: '2.0',
|
||||
software: {
|
||||
name: 'peertube',
|
||||
|
@ -291,12 +290,14 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
|
|||
}
|
||||
} as HttpNodeinfoDiasporaSoftwareNsSchema20
|
||||
res.contentType('application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"')
|
||||
} else {
|
||||
json = { error: 'Nodeinfo schema version not handled' }
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.send(json)
|
||||
.end()
|
||||
}
|
||||
|
||||
return res.send(json).end()
|
||||
return res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Nodeinfo schema version not handled'
|
||||
})
|
||||
}
|
||||
|
||||
function getCup (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
|
|
|
@ -16,26 +16,20 @@ async function doesVideoCommentThreadExist (idArg: number | string, video: MVide
|
|||
const videoComment = await VideoCommentModel.loadById(id)
|
||||
|
||||
if (!videoComment) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video comment thread not found' })
|
||||
.end()
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video comment thread not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (videoComment.videoId !== video.id) {
|
||||
res.status(HttpStatusCode.BAD_REQUEST_400)
|
||||
.json({ error: 'Video comment is not associated to this video.' })
|
||||
.end()
|
||||
|
||||
res.fail({ message: 'Video comment is not associated to this video.' })
|
||||
return false
|
||||
}
|
||||
|
||||
if (videoComment.inReplyToCommentId !== null) {
|
||||
res.status(HttpStatusCode.BAD_REQUEST_400)
|
||||
.json({ error: 'Video comment is not a thread.' })
|
||||
.end()
|
||||
|
||||
res.fail({ message: 'Video comment is not a thread.' })
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -48,18 +42,15 @@ async function doesVideoCommentExist (idArg: number | string, video: MVideoId, r
|
|||
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
|
||||
|
||||
if (!videoComment) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video comment thread not found' })
|
||||
.end()
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video comment thread not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (videoComment.videoId !== video.id) {
|
||||
res.status(HttpStatusCode.BAD_REQUEST_400)
|
||||
.json({ error: 'Video comment is not associated to this video.' })
|
||||
.end()
|
||||
|
||||
res.fail({ message: 'Video comment is not associated to this video.' })
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -72,14 +63,14 @@ async function doesCommentIdExist (idArg: number | string, res: express.Response
|
|||
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
|
||||
|
||||
if (!videoComment) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video comment thread not found' })
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video comment thread not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
res.locals.videoCommentFull = videoComment
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,10 @@ async function doesVideoImportExist (id: number, res: express.Response) {
|
|||
const videoImport = await VideoImportModel.loadAndPopulateVideo(id)
|
||||
|
||||
if (!videoImport) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video import not found' })
|
||||
.end()
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video import not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,10 @@ export async function doesChangeVideoOwnershipExist (idArg: number | string, res
|
|||
const videoChangeOwnership = await VideoChangeOwnershipModel.load(id)
|
||||
|
||||
if (!videoChangeOwnership) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video change ownership not found' })
|
||||
.end()
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video change ownership not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -25,8 +25,9 @@ export function checkUserCanTerminateOwnershipChange (user: MUserId, videoChange
|
|||
return true
|
||||
}
|
||||
|
||||
res.status(HttpStatusCode.FORBIDDEN_403)
|
||||
.json({ error: 'Cannot terminate an ownership change of another user' })
|
||||
.end()
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: 'Cannot terminate an ownership change of another user'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { isArray } from './custom-validators/misc'
|
|||
import { logger } from './logger'
|
||||
import { deleteFileAndCatch, generateRandomString } from './utils'
|
||||
import { getExtFromMimetype } from './video'
|
||||
import { ProblemDocument, ProblemDocumentExtension } from 'http-problem-details'
|
||||
|
||||
function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
|
||||
if (paramNSFW === 'true') return true
|
||||
|
@ -125,6 +126,34 @@ function getCountVideos (req: express.Request) {
|
|||
return req.query.skipCount !== true
|
||||
}
|
||||
|
||||
// helpers added in server.ts and used in subsequent controllers used
|
||||
const apiResponseHelpers = (req, res: express.Response, next = null) => {
|
||||
res.fail = (options) => {
|
||||
const { data, status, message, title, type, docs, instance } = {
|
||||
data: null,
|
||||
status: HttpStatusCode.BAD_REQUEST_400,
|
||||
...options
|
||||
}
|
||||
|
||||
const extension = new ProblemDocumentExtension({
|
||||
...data,
|
||||
docs: docs || res.docs
|
||||
})
|
||||
|
||||
res.status(status)
|
||||
res.setHeader('Content-Type', 'application/problem+json')
|
||||
res.json(new ProblemDocument({
|
||||
status,
|
||||
title,
|
||||
instance,
|
||||
type: type && '' + type,
|
||||
detail: message
|
||||
}, extension))
|
||||
}
|
||||
|
||||
if (next !== null) next()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -134,5 +163,6 @@ export {
|
|||
badRequest,
|
||||
createReqFiles,
|
||||
cleanUpReqFiles,
|
||||
getCountVideos
|
||||
getCountVideos,
|
||||
apiResponseHelpers
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ async function doesAbuseExist (abuseId: number | string, res: Response) {
|
|||
const abuse = await AbuseModel.loadByIdWithReporter(parseInt(abuseId + '', 10))
|
||||
|
||||
if (!abuse) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Abuse not found' })
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Abuse not found'
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -27,15 +27,15 @@ async function doesAccountExist (p: Promise<MAccountDefault>, res: Response, sen
|
|||
|
||||
if (!account) {
|
||||
if (sendNotFound === true) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Account not found' })
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Account not found'
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
res.locals.account = account
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -43,14 +43,14 @@ async function doesUserFeedTokenCorrespond (id: number, token: string, res: Resp
|
|||
const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10))
|
||||
|
||||
if (token !== user.feedToken) {
|
||||
res.status(HttpStatusCode.FORBIDDEN_403)
|
||||
.json({ error: 'User and token mismatch' })
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: 'User and token mismatch'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
res.locals.user = user
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -6,10 +6,10 @@ async function doesVideoBlacklistExist (videoId: number, res: Response) {
|
|||
const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId)
|
||||
|
||||
if (videoBlacklist === null) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Blacklisted video not found' })
|
||||
.end()
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Blacklisted video not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,10 @@ async function doesVideoCaptionExist (video: MVideoId, language: string, res: Re
|
|||
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language)
|
||||
|
||||
if (!videoCaption) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video caption not found' })
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video caption not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -31,9 +31,10 @@ export {
|
|||
|
||||
function processVideoChannelExist (videoChannel: MChannelBannerAccountDefault, res: express.Response) {
|
||||
if (!videoChannel) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video channel not found' })
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video channel not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -28,10 +28,10 @@ export {
|
|||
|
||||
function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) {
|
||||
if (!videoPlaylist) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video playlist not found' })
|
||||
.end()
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video playlist not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
|
|||
const video = await fetchVideo(id, fetchType, userId)
|
||||
|
||||
if (video === null) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video not found' })
|
||||
.end()
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'Video not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -55,10 +55,10 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
|
|||
|
||||
async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) {
|
||||
if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'VideoFile matching Video not found' })
|
||||
.end()
|
||||
|
||||
res.fail({
|
||||
status: HttpStatusCode.NOT_FOUND_404,
|
||||
message: 'VideoFile matching Video not found'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -69,9 +69,7 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAcc
|
|||
const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
|
||||
|
||||
if (videoChannel === null) {
|
||||
res.status(HttpStatusCode.BAD_REQUEST_400)
|
||||
.json({ error: 'Unknown video "video channel" for this instance.' })
|
||||
|
||||
res.fail({ message: 'Unknown video "video channel" for this instance.' })
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -82,9 +80,9 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAcc
|
|||
}
|
||||
|
||||
if (videoChannel.Account.id !== user.Account.id) {
|
||||
res.status(HttpStatusCode.BAD_REQUEST_400)
|
||||
.json({ error: 'Unknown video "video channel" for this account.' })
|
||||
|
||||
res.fail({
|
||||
message: 'Unknown video "video channel" for this account.'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -95,9 +93,10 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAcc
|
|||
function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) {
|
||||
// Retrieve the user who did the request
|
||||
if (onlyOwned && video.isOwned() === false) {
|
||||
res.status(HttpStatusCode.FORBIDDEN_403)
|
||||
.json({ error: 'Cannot manage a video of another server.' })
|
||||
.end()
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: 'Cannot manage a video of another server.'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -106,9 +105,10 @@ function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right:
|
|||
// Or if s/he is the video's account
|
||||
const account = video.VideoChannel.Account
|
||||
if (user.hasRight(right) === false && account.userId !== user.id) {
|
||||
res.status(HttpStatusCode.FORBIDDEN_403)
|
||||
.json({ error: 'Cannot manage a video of another user.' })
|
||||
.end()
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: 'Cannot manage a video of another user.'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -549,11 +549,11 @@ async function serveIndexHTML (req: express.Request, res: express.Response) {
|
|||
return
|
||||
} catch (err) {
|
||||
logger.error('Cannot generate HTML page.', err)
|
||||
return res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR_500)
|
||||
return res.status(HttpStatusCode.INTERNAL_SERVER_ERROR_500).end()
|
||||
}
|
||||
}
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NOT_ACCEPTABLE_406)
|
||||
return res.status(HttpStatusCode.NOT_ACCEPTABLE_406).end()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -29,11 +29,14 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
|
|||
const activity: ActivityDelete = req.body
|
||||
if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) {
|
||||
logger.debug('Handling signature error on actor delete activity', { err })
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
logger.warn('Error in ActivityPub signature checker.', { err })
|
||||
return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
|
||||
return res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: 'ActivityPub signature could not be checked'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,13 +74,22 @@ async function checkHttpSignature (req: Request, res: Response) {
|
|||
} catch (err) {
|
||||
logger.warn('Invalid signature because of exception in signature parser', { reqBody: req.body, err })
|
||||
|
||||
res.status(HttpStatusCode.FORBIDDEN_403).json({ error: err.message })
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: err.message
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
const keyId = parsed.keyId
|
||||
if (!keyId) {
|
||||
res.sendStatus(HttpStatusCode.FORBIDDEN_403)
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: 'Invalid key ID',
|
||||
data: {
|
||||
keyId
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -94,12 +106,17 @@ async function checkHttpSignature (req: Request, res: Response) {
|
|||
if (verified !== true) {
|
||||
logger.warn('Signature from %s is invalid', actorUrl, { parsed })
|
||||
|
||||
res.sendStatus(HttpStatusCode.FORBIDDEN_403)
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: 'Invalid signature',
|
||||
data: {
|
||||
actorUrl
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
res.locals.signature = { actor }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -107,7 +124,10 @@ async function checkJsonLDSignature (req: Request, res: Response) {
|
|||
const signatureObject: ActivityPubSignature = req.body.signature
|
||||
|
||||
if (!signatureObject || !signatureObject.creator) {
|
||||
res.sendStatus(HttpStatusCode.FORBIDDEN_403)
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: 'Object and creator signature do not match'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -121,11 +141,13 @@ async function checkJsonLDSignature (req: Request, res: Response) {
|
|||
if (verified !== true) {
|
||||
logger.warn('Signature not verified.', req.body)
|
||||
|
||||
res.sendStatus(HttpStatusCode.FORBIDDEN_403)
|
||||
res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message: 'Signature could not be verified'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
res.locals.signature = { actor }
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@ function authenticate (req: express.Request, res: express.Response, next: expres
|
|||
.catch(err => {
|
||||
logger.warn('Cannot authenticate.', { err })
|
||||
|
||||
return res.status(err.status)
|
||||
.json({
|
||||
error: 'Token is invalid.',
|
||||
code: err.name
|
||||
})
|
||||
return res.fail({
|
||||
status: err.status,
|
||||
message: 'Token is invalid',
|
||||
type: err.name
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,12 @@ function authenticatePromiseIfNeeded (req: express.Request, res: express.Respons
|
|||
// Already authenticated? (or tried to)
|
||||
if (res.locals.oauth?.token.User) return resolve()
|
||||
|
||||
if (res.locals.authenticated === false) return res.sendStatus(HttpStatusCode.UNAUTHORIZED_401)
|
||||
if (res.locals.authenticated === false) {
|
||||
return res.fail({
|
||||
status: HttpStatusCode.UNAUTHORIZED_401,
|
||||
message: 'Not authenticated'
|
||||
})
|
||||
}
|
||||
|
||||
authenticate(req, res, () => resolve(), authenticateInQuery)
|
||||
})
|
||||
|
|
|
@ -10,7 +10,10 @@ function setBodyHostsPort (req: express.Request, res: express.Response, next: ex
|
|||
|
||||
// Problem with the url parsing?
|
||||
if (hostWithPort === null) {
|
||||
return res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR_500)
|
||||
return res.fail({
|
||||
status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
|
||||
message: 'Could not parse hosts'
|
||||
})
|
||||
}
|
||||
|
||||
req.body.hosts[i] = hostWithPort
|
||||
|
|
|
@ -10,8 +10,10 @@ function ensureUserHasRight (userRight: UserRight) {
|
|||
const message = `User ${user.username} does not have right ${userRight} to access to ${req.path}.`
|
||||
logger.info(message)
|
||||
|
||||
return res.status(HttpStatusCode.FORBIDDEN_403)
|
||||
.json({ error: message })
|
||||
return res.fail({
|
||||
status: HttpStatusCode.FORBIDDEN_403,
|
||||
message
|
||||
})
|
||||
}
|
||||
|
||||
return next()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<MUserDefault>, 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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<express.UploadXFileMetadata> = 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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,6 +19,7 @@ import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/l
|
|||
import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
|
||||
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
|
||||
import { User } from '../../../../shared/models/users'
|
||||
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
|
@ -89,8 +90,8 @@ describe('Test users account verification', function () {
|
|||
})
|
||||
|
||||
it('Should not allow login for user with unverified email', async function () {
|
||||
const resLogin = await login(server.url, server.client, user1, 400)
|
||||
expect(resLogin.body.error).to.contain('User email is not verified.')
|
||||
const resLogin = await login(server.url, server.client, user1, HttpStatusCode.BAD_REQUEST_400)
|
||||
expect(resLogin.body.detail).to.contain('User email is not verified.')
|
||||
})
|
||||
|
||||
it('Should verify the user via email and allow login', async function () {
|
||||
|
|
|
@ -93,16 +93,16 @@ describe('Test users', function () {
|
|||
const client = { id: 'client', secret: server.client.secret }
|
||||
const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400)
|
||||
|
||||
expect(res.body.code).to.equal('invalid_client')
|
||||
expect(res.body.error).to.contain('client is invalid')
|
||||
expect(res.body.type).to.equal('invalid_client')
|
||||
expect(res.body.detail).to.contain('client is invalid')
|
||||
})
|
||||
|
||||
it('Should not login with an invalid client secret', async function () {
|
||||
const client = { id: server.client.id, secret: 'coucou' }
|
||||
const res = await login(server.url, client, server.user, HttpStatusCode.BAD_REQUEST_400)
|
||||
|
||||
expect(res.body.code).to.equal('invalid_client')
|
||||
expect(res.body.error).to.contain('client is invalid')
|
||||
expect(res.body.type).to.equal('invalid_client')
|
||||
expect(res.body.detail).to.contain('client is invalid')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -112,16 +112,16 @@ describe('Test users', function () {
|
|||
const user = { username: 'captain crochet', password: server.user.password }
|
||||
const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400)
|
||||
|
||||
expect(res.body.code).to.equal('invalid_grant')
|
||||
expect(res.body.error).to.contain('credentials are invalid')
|
||||
expect(res.body.type).to.equal('invalid_grant')
|
||||
expect(res.body.detail).to.contain('credentials are invalid')
|
||||
})
|
||||
|
||||
it('Should not login with an invalid password', async function () {
|
||||
const user = { username: server.user.username, password: 'mew_three' }
|
||||
const res = await login(server.url, server.client, user, HttpStatusCode.BAD_REQUEST_400)
|
||||
|
||||
expect(res.body.code).to.equal('invalid_grant')
|
||||
expect(res.body.error).to.contain('credentials are invalid')
|
||||
expect(res.body.type).to.equal('invalid_grant')
|
||||
expect(res.body.detail).to.contain('credentials are invalid')
|
||||
})
|
||||
|
||||
it('Should not be able to upload a video', async function () {
|
||||
|
|
10
server/typings/express/index.d.ts
vendored
10
server/typings/express/index.d.ts
vendored
|
@ -22,6 +22,7 @@ import { MAccountVideoRateAccountVideo } from '@server/types/models/video/video-
|
|||
import { HttpMethod } from '@shared/core-utils/miscs/http-methods'
|
||||
import { VideoCreate } from '@shared/models'
|
||||
import { File as UploadXFile, Metadata } from '@uploadx/core'
|
||||
import { ProblemDocumentOptions } from 'http-problem-details/dist/ProblemDocument'
|
||||
import { RegisteredPlugin } from '../../lib/plugins/plugin-manager'
|
||||
import {
|
||||
MAccountDefault,
|
||||
|
@ -83,8 +84,15 @@ declare module 'express' {
|
|||
filename: string
|
||||
}
|
||||
|
||||
// Extends locals property from Response
|
||||
// Extends Response with added functions and potential variables passed by middlewares
|
||||
interface Response {
|
||||
docs?: string
|
||||
fail: (options: {
|
||||
data?: Record<string, Object>
|
||||
docs?: string
|
||||
message: string
|
||||
} & ProblemDocumentOptions) => void
|
||||
|
||||
locals: {
|
||||
videoAll?: MVideoFullLight
|
||||
onlyImmutableVideo?: MVideoImmutable
|
||||
|
|
|
@ -38,46 +38,53 @@ info:
|
|||
# Errors
|
||||
|
||||
The API uses standard HTTP status codes to indicate the success or failure
|
||||
of the API call.
|
||||
of the API call, completed by a [RFC7807-compliant](https://tools.ietf.org/html/rfc7807) response body.
|
||||
|
||||
```
|
||||
HTTP 1.1 404 Not Found
|
||||
Content-Type: application/json
|
||||
Content-Type: application/problem+json; charset=utf-8
|
||||
|
||||
{
|
||||
"errorCode": 1
|
||||
"error": "Account not found"
|
||||
"detail": "Video not found",
|
||||
"status": 404,
|
||||
"title": "Not Found",
|
||||
"type": "about:blank"
|
||||
}
|
||||
```
|
||||
|
||||
We provide error codes for [a growing number of cases](https://github.com/Chocobozzz/PeerTube/blob/develop/shared/models/server/server-error-code.enum.ts),
|
||||
We provide error types for [a growing number of cases](https://github.com/Chocobozzz/PeerTube/blob/develop/shared/models/server/server-error-code.enum.ts),
|
||||
but it is still optional.
|
||||
|
||||
### Validation errors
|
||||
|
||||
Each parameter is evaluated on its own against a set of rules before the route validator
|
||||
proceeds with potential testing involving parameter combinations. Errors coming from Validation
|
||||
proceeds with potential testing involving parameter combinations. Errors coming from validation
|
||||
errors appear earlier and benefit from a more detailed error type:
|
||||
|
||||
```
|
||||
HTTP 1.1 400 Bad Request
|
||||
Content-Type: application/json
|
||||
Content-Type: application/problem+json; charset=utf-8
|
||||
|
||||
{
|
||||
"errors": {
|
||||
"detail": "Incorrect request parameters: id",
|
||||
"instance": "/api/v1/videos/9c9de5e8-0a1e-484a-b099-e80766180",
|
||||
"invalid-params": {
|
||||
"id": {
|
||||
"value": "a117eb-c6a9-4756-bb09-2a956239f",
|
||||
"msg": "Should have a valid id",
|
||||
"location": "params",
|
||||
"msg": "Invalid value",
|
||||
"param": "id",
|
||||
"location": "params"
|
||||
"value": "9c9de5e8-0a1e-484a-b099-e80766180"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": 400,
|
||||
"title": "Bad Request",
|
||||
"type": "about:blank"
|
||||
}
|
||||
```
|
||||
|
||||
Where `id` is the name of the field concerned by the error, within the route definition.
|
||||
`errors.<field>.location` can be either 'params', 'body', 'header', 'query' or 'cookies', and
|
||||
`errors.<field>.value` reports the value that didn't pass validation whose `errors.<field>.msg`
|
||||
`invalid-params.<field>.location` can be either 'params', 'body', 'header', 'query' or 'cookies', and
|
||||
`invalid-params.<field>.value` reports the value that didn't pass validation whose `invalid-params.<field>.msg`
|
||||
is about.
|
||||
|
||||
# Rate limits
|
||||
|
|
|
@ -4136,6 +4136,11 @@ http-parser-js@^0.5.2:
|
|||
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9"
|
||||
integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==
|
||||
|
||||
http-problem-details@^0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/http-problem-details/-/http-problem-details-0.1.5.tgz#f8f94f4ab9d4050749e9f8566fb85bb8caa2be56"
|
||||
integrity sha512-GHxfQZ0POP4FWbAM0guOyZyJNWwbLUXp+4XOJdmitS2tp3gHVSatrSX59Yyq/dCkhk4KiGtTWIlXZC83yCkBkA==
|
||||
|
||||
http-signature@1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.5.tgz#9f19496ffbf3227298d7b5f156e0e1a948678683"
|
||||
|
|
Loading…
Reference in a new issue