]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Refactor server errors handler
authorChocobozzz <me@florianbigard.com>
Wed, 2 Jun 2021 16:15:41 +0000 (18:15 +0200)
committerChocobozzz <me@florianbigard.com>
Wed, 2 Jun 2021 16:15:41 +0000 (18:15 +0200)
20 files changed:
client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
client/src/app/+videos/+video-watch/video-watch.component.ts
client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
client/src/standalone/videos/embed.ts
server.ts
server/controllers/api/videos/index.ts
server/controllers/api/videos/update.ts
server/controllers/api/videos/upload.ts
server/helpers/express-utils.ts
server/middlewares/doc.ts [new file with mode: 0644]
server/middlewares/error.ts [new file with mode: 0644]
server/middlewares/index.ts
server/middlewares/validators/videos/videos.ts
server/tests/api/check-params/videos.ts
server/tests/api/server/follow-constraints.ts
server/typings/express/index.d.ts
shared/models/server/index.ts
shared/models/server/peertube-problem-document.model.ts [new file with mode: 0644]
shared/models/server/server-error-code.enum.ts

index 727bbc32fef58ddc0bcf52b660f3bf9d4c7b1c3d..15178a2675b5ad7aa5997d3c56abd96382b56f93 100644 (file)
@@ -8,7 +8,7 @@ import { FormValidatorService } from '@app/shared/shared-forms'
 import { VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
 import { LiveVideoService } from '@app/shared/shared-video-live'
 import { LoadingBarService } from '@ngx-loading-bar/core'
-import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, ServerErrorCode, VideoPrivacy } from '@shared/models'
+import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode, VideoPrivacy } from '@shared/models'
 import { VideoSend } from './video-send'
 
 @Component({
@@ -92,9 +92,11 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
 
         let message = err.message
 
-        if (err.body?.code === ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED) {
+        const error = err.body as PeerTubeProblemDocument
+
+        if (error?.code === ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED) {
           message = $localize`Cannot create live because this instance have too many created lives`
-        } else if (err.body?.code) {
+        } else if (error?.code === ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED) {
           message = $localize`Cannot create live because you created too many lives`
         }
 
index 23bd5ef767be221f1786ba050cd1d1c138f744c1..2837b30c130e69c96d01e4d1113fcda65772333a 100644 (file)
@@ -5,7 +5,7 @@ import { scrollToTop } from '@app/helpers'
 import { FormValidatorService } from '@app/shared/shared-forms'
 import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
 import { LoadingBarService } from '@ngx-loading-bar/core'
-import { ServerErrorCode, VideoPrivacy, VideoUpdate } from '@shared/models'
+import { PeerTubeProblemDocument, ServerErrorCode, VideoPrivacy, VideoUpdate } from '@shared/models'
 import { hydrateFormFromVideo } from '../shared/video-edit-utils'
 import { VideoSend } from './video-send'
 
@@ -115,7 +115,9 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
         this.firstStepError.emit()
 
         let message = err.message
-        if (err.body?.code === ServerErrorCode.INCORRECT_FILES_IN_TORRENT) {
+
+        const error = err.body as PeerTubeProblemDocument
+        if (error?.code === ServerErrorCode.INCORRECT_FILES_IN_TORRENT) {
           message = $localize`Torrents with only 1 file are supported.`
         }
 
index 8034ccebf2c095489ee1822657ce6254a1c6e1fb..540b568ed9dcea004d10103dc4e508a62c80d729 100644 (file)
@@ -28,7 +28,7 @@ import { VideoActionsDisplayType, VideoDownloadComponent } from '@app/shared/sha
 import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
 import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
 import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
-import { ServerConfig, ServerErrorCode, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '@shared/models'
+import { PeerTubeProblemDocument, ServerConfig, ServerErrorCode, UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '@shared/models'
 import {
   cleanupVideoWatch,
   getStoredP2PEnabled,
@@ -431,9 +431,11 @@ 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.type === ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS && err.body.originUrl) {
+          const errorBody = err.body as PeerTubeProblemDocument
+
+          if (errorBody.code === ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS && errorBody.originUrl) {
             const search = window.location.search
-            let originUrl = err.body.originUrl
+            let originUrl = errorBody.originUrl
             if (search) originUrl += search
 
             this.confirmService.confirm(
index 4fe3b964d64770442cc19027df9da73d474cff1e..5bcad36d01d97e2b5ab11cb877774f4ce7f7157a 100644 (file)
@@ -5,6 +5,7 @@ import { Injectable, Injector } from '@angular/core'
 import { AuthService } from '@app/core/auth/auth.service'
 import { Router } from '@angular/router'
 import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
+import { OAuth2ErrorCode, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models/server'
 
 @Injectable()
 export class AuthInterceptor implements HttpInterceptor {
@@ -25,7 +26,9 @@ export class AuthInterceptor implements HttpInterceptor {
     return next.handle(authReq)
                .pipe(
                  catchError((err: HttpErrorResponse) => {
-                   if (err.status === HttpStatusCode.UNAUTHORIZED_401 && err.error && err.error.code === 'invalid_token') {
+                   const error = err.error as PeerTubeProblemDocument
+
+                   if (err.status === HttpStatusCode.UNAUTHORIZED_401 && error && error.code === OAuth2ErrorCode.INVALID_TOKEN) {
                      return this.handleTokenExpired(req, next)
                    }
 
index fc61d37303bc4b6cc94e27fce1d52f4c7fe1068d..4ce5c78e8b45a26aae24e7055cd8aae98306bc30 100644 (file)
@@ -5,6 +5,7 @@ import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-c
 import {
   ClientHookName,
   HTMLServerConfig,
+  OAuth2ErrorCode,
   PluginType,
   ResultList,
   UserRefreshToken,
@@ -118,8 +119,8 @@ export class PeerTubeEmbed {
             if (res.status === HttpStatusCode.UNAUTHORIZED_401) return undefined
 
             return res.json()
-          }).then((obj: UserRefreshToken & { code: 'invalid_grant'}) => {
-            if (!obj || obj.code === 'invalid_grant') {
+          }).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => {
+            if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) {
               Tokens.flush()
               this.removeTokensFromHeaders()
 
index 66c9173cad11d8fddfd4d2c42f8a894658a3ce5f..2caee18e75864fb03d538b9c34e9e381d3686beb 100644 (file)
--- a/server.ts
+++ b/server.ts
@@ -106,6 +106,7 @@ import {
   downloadRouter
 } from './server/controllers'
 import { advertiseDoNotTrack } from './server/middlewares/dnt'
+import { apiFailMiddleware } from './server/middlewares/error'
 import { Redis } from './server/lib/redis'
 import { ActorFollowScheduler } from './server/lib/schedulers/actor-follow-scheduler'
 import { RemoveOldViewsScheduler } from './server/lib/schedulers/remove-old-views-scheduler'
@@ -127,7 +128,6 @@ 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 -----------
 
@@ -169,8 +169,8 @@ app.use(morgan('combined', {
   skip: req => CONFIG.LOG.LOG_PING_REQUESTS === false && req.originalUrl === '/api/v1/ping'
 }))
 
-// Response helpers used for errors
-app.use(apiResponseHelpers)
+// Add .fail() helper to response
+app.use(apiFailMiddleware)
 
 // For body requests
 app.use(express.urlencoded({ extended: false }))
@@ -179,6 +179,7 @@ app.use(express.json({
   limit: '500kb',
   verify: (req: express.Request, res: express.Response, buf: Buffer) => {
     const valid = isHTTPSignatureDigestValid(buf, req)
+
     if (valid !== true) {
       res.fail({
         status: HttpStatusCode.FORBIDDEN_403,
index db23e563032b09be01a36b2fefbcf4c1bfb306a9..7671f099ebaf1a99a1dc026dea80d0fee13be8b0 100644 (file)
@@ -2,6 +2,7 @@ import * as express from 'express'
 import toInt from 'validator/lib/toInt'
 import { doJSONRequest } from '@server/helpers/requests'
 import { LiveManager } from '@server/lib/live-manager'
+import { docMiddleware } from '@server/middlewares/doc'
 import { getServerActor } from '@server/models/application/application'
 import { MVideoAccountLight } from '@server/types/models'
 import { VideosCommonQuery } from '../../../../shared'
@@ -83,6 +84,7 @@ videosRouter.get('/:id/metadata/:videoFileId',
   asyncMiddleware(getVideoFileMetadata)
 )
 videosRouter.get('/:id',
+  docMiddleware('https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo'),
   optionalAuthenticate,
   asyncMiddleware(videosCustomGetValidator('only-video-with-rights')),
   asyncMiddleware(checkVideoFollowConstraints),
@@ -94,6 +96,7 @@ videosRouter.post('/:id/views',
 )
 
 videosRouter.delete('/:id',
+  docMiddleware('https://docs.joinpeertube.org/api-rest-reference.html#operation/delVideo'),
   authenticate,
   asyncMiddleware(videosRemoveValidator),
   asyncRetryTransactionMiddleware(removeVideo)
index 2450abd0ead1e5ee9264e95e10c920902843790d..09e584d30900dd0b404465cd04398c83fc0264d8 100644 (file)
@@ -20,6 +20,7 @@ import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
 import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares'
 import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
 import { VideoModel } from '../../../models/video/video'
+import { docMiddleware } from '@server/middlewares/doc'
 
 const lTags = loggerTagsFactory('api', 'video')
 const auditLogger = auditLoggerFactory('videos')
@@ -35,6 +36,7 @@ const reqVideoFileUpdate = createReqFiles(
 )
 
 updateRouter.put('/:id',
+  docMiddleware('https://docs.joinpeertube.org/api-rest-reference.html#operation/putVideo'),
   authenticate,
   reqVideoFileUpdate,
   asyncMiddleware(videosUpdateValidator),
index c33d7fcb9f777d3d26be44fdf987c072ac62aeac..93a68f759d03425f6e31091f56d3a80f267bd61c 100644 (file)
@@ -6,6 +6,7 @@ import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
 import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
 import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
 import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
+import { docMiddleware } from '@server/middlewares/doc'
 import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
 import { uploadx } from '@uploadx/core'
 import { VideoCreate, VideoState } from '../../../../shared'
@@ -60,6 +61,7 @@ const reqVideoFileAddResumable = createReqFiles(
 )
 
 uploadRouter.post('/upload',
+  docMiddleware('https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadLegacy'),
   authenticate,
   reqVideoFileAdd,
   asyncMiddleware(videosAddLegacyValidator),
@@ -67,6 +69,7 @@ uploadRouter.post('/upload',
 )
 
 uploadRouter.post('/upload-resumable',
+  docMiddleware('https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadResumableInit'),
   authenticate,
   reqVideoFileAddResumable,
   asyncMiddleware(videosAddResumableInitValidator),
@@ -79,6 +82,7 @@ uploadRouter.delete('/upload-resumable',
 )
 
 uploadRouter.put('/upload-resumable',
+  docMiddleware('https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadResumable'),
   authenticate,
   uploadxMiddleware, // uploadx doesn't use call next() before the file upload completes
   asyncMiddleware(videosAddResumableValidator),
index 10a860787cf44bf8ded1b7c8d49d63fa61d66caa..010c6961a063573409c5e0829373ee8b9ea867ad 100644 (file)
@@ -8,7 +8,6 @@ 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
@@ -126,34 +125,6 @@ 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 = HttpStatusCode.BAD_REQUEST_400, message, title, type, docs = res.docs, instance } = options
-
-    const extension = new ProblemDocumentExtension({
-      ...data,
-      docs,
-      // fields for <= 3.2 compatibility, deprecated
-      error: message,
-      code: type
-    })
-
-    res.status(status)
-    res.setHeader('Content-Type', 'application/problem+json')
-    res.json(new ProblemDocument({
-      status,
-      title,
-      instance,
-      // fields intended to replace 'error' and 'code' respectively
-      detail: message,
-      type: type && 'https://docs.joinpeertube.org/api-rest-reference.html#section/Errors/' + type
-    }, extension))
-  }
-
-  if (next) next()
-}
-
 // ---------------------------------------------------------------------------
 
 export {
@@ -163,6 +134,5 @@ export {
   badRequest,
   createReqFiles,
   cleanUpReqFiles,
-  getCountVideos,
-  apiResponseHelpers
+  getCountVideos
 }
diff --git a/server/middlewares/doc.ts b/server/middlewares/doc.ts
new file mode 100644 (file)
index 0000000..aa852cd
--- /dev/null
@@ -0,0 +1,13 @@
+import * as express from 'express'
+
+function docMiddleware (docUrl: string) {
+  return (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    res.locals.docUrl = docUrl
+
+    if (next) return next()
+  }
+}
+
+export {
+  docMiddleware
+}
diff --git a/server/middlewares/error.ts b/server/middlewares/error.ts
new file mode 100644 (file)
index 0000000..e3eb1c8
--- /dev/null
@@ -0,0 +1,39 @@
+import * as express from 'express'
+import { ProblemDocument, ProblemDocumentExtension } from 'http-problem-details'
+import { HttpStatusCode } from '@shared/core-utils'
+
+function apiFailMiddleware (req: express.Request, res: express.Response, next: express.NextFunction) {
+  res.fail = options => {
+    const { status = HttpStatusCode.BAD_REQUEST_400, message, title, type, data, instance } = options
+
+    const extension = new ProblemDocumentExtension({
+      ...data,
+
+      docs: res.locals.docUrl,
+      code: type,
+
+      // For <= 3.2 compatibility
+      error: message
+    })
+
+    res.status(status)
+    res.setHeader('Content-Type', 'application/problem+json')
+    res.json(new ProblemDocument({
+      status,
+      title,
+      instance,
+
+      detail: message,
+
+      type: type
+        ? `https://docs.joinpeertube.org/api-rest-reference.html#section/Errors/${type}`
+        : undefined
+    }, extension))
+  }
+
+  if (next) next()
+}
+
+export {
+  apiFailMiddleware
+}
index 3e280e16f590a182396934a8b070cea72ee28ee4..413653dac65517dbfeaddce9ff2dbb45b089d0b6 100644 (file)
@@ -7,4 +7,6 @@ export * from './servers'
 export * from './sort'
 export * from './user-right'
 export * from './dnt'
+export * from './error'
+export * from './doc'
 export * from './csp'
index 64e09234e8d9240cb2818846930e9cd13aa8becf..b7a9bcbe30862e71d7d771e81e7e5887f7c0a777 100644 (file)
@@ -73,7 +73,6 @@ 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)
@@ -108,7 +107,6 @@ 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
@@ -170,7 +168,6 @@ 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'],
@@ -214,7 +211,6 @@ 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)
@@ -268,7 +264,6 @@ 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
@@ -334,7 +329,6 @@ 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
index c970c4a155680b2a1b0dbdcd9b95fddb6ff93c93..a6eecb13ae013c39820b32849961833dcd9baab0 100644 (file)
@@ -4,6 +4,8 @@ import 'mocha'
 import * as chai from 'chai'
 import { omit } from 'lodash'
 import { join } from 'path'
+import { randomInt } from '@shared/core-utils'
+import { PeerTubeProblemDocument } from '@shared/models'
 import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
 import {
   checkUploadVideoParam,
@@ -30,7 +32,6 @@ import {
   checkBadStartPagination
 } from '../../../../shared/extra-utils/requests/check-api-params'
 import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
-import { randomInt } from '@shared/core-utils'
 
 const expect = chai.expect
 
@@ -411,6 +412,31 @@ describe('Test videos API validator', function () {
         await checkUploadVideoParam(server.url, server.accessToken, { ...fields, ...attaches }, HttpStatusCode.BAD_REQUEST_400, mode)
       })
 
+      it('Should report the appropriate error', async function () {
+        const fields = immutableAssign(baseCorrectParams, { language: 'a'.repeat(15) })
+        const attaches = baseCorrectAttaches
+
+        const attributes = { ...fields, ...attaches }
+        const res = await checkUploadVideoParam(server.url, server.accessToken, attributes, HttpStatusCode.BAD_REQUEST_400, mode)
+
+        const error = res.body as PeerTubeProblemDocument
+
+        if (mode === 'legacy') {
+          expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadLegacy')
+        } else {
+          expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadResumableInit')
+        }
+
+        expect(error.type).to.equal('about:blank')
+        expect(error.title).to.equal('Bad Request')
+
+        expect(error.detail).to.equal('Incorrect request parameters: language')
+        expect(error.error).to.equal('Incorrect request parameters: language')
+
+        expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
+        expect(error['invalid-params'].language).to.exist
+      })
+
       it('Should succeed with the correct parameters', async function () {
         this.timeout(10000)
 
@@ -645,6 +671,24 @@ describe('Test videos API validator', function () {
 
     it('Should fail with a video of another server')
 
+    it('Shoud report the appropriate error', async function () {
+      const fields = immutableAssign(baseCorrectParams, { licence: 125 })
+
+      const res = await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
+      const error = res.body as PeerTubeProblemDocument
+
+      expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/putVideo')
+
+      expect(error.type).to.equal('about:blank')
+      expect(error.title).to.equal('Bad Request')
+
+      expect(error.detail).to.equal('Incorrect request parameters: licence')
+      expect(error.error).to.equal('Incorrect request parameters: licence')
+
+      expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
+      expect(error['invalid-params'].licence).to.exist
+    })
+
     it('Should succeed with the correct parameters', async function () {
       const fields = baseCorrectParams
 
@@ -678,6 +722,22 @@ describe('Test videos API validator', function () {
       await getVideo(server.url, '4da6fde3-88f7-4d16-b119-108df5630b06', HttpStatusCode.NOT_FOUND_404)
     })
 
+    it('Shoud report the appropriate error', async function () {
+      const res = await getVideo(server.url, 'hi', HttpStatusCode.BAD_REQUEST_400)
+      const error = res.body as PeerTubeProblemDocument
+
+      expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo')
+
+      expect(error.type).to.equal('about:blank')
+      expect(error.title).to.equal('Bad Request')
+
+      expect(error.detail).to.equal('Incorrect request parameters: id')
+      expect(error.error).to.equal('Incorrect request parameters: id')
+
+      expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
+      expect(error['invalid-params'].id).to.exist
+    })
+
     it('Should succeed with the correct parameters', async function () {
       await getVideo(server.url, videoId)
     })
@@ -755,6 +815,22 @@ describe('Test videos API validator', function () {
 
     it('Should fail with a video of another server')
 
+    it('Shoud report the appropriate error', async function () {
+      const res = await removeVideo(server.url, server.accessToken, 'hello', HttpStatusCode.BAD_REQUEST_400)
+      const error = res.body as PeerTubeProblemDocument
+
+      expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/delVideo')
+
+      expect(error.type).to.equal('about:blank')
+      expect(error.title).to.equal('Bad Request')
+
+      expect(error.detail).to.equal('Incorrect request parameters: id')
+      expect(error.error).to.equal('Incorrect request parameters: id')
+
+      expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
+      expect(error['invalid-params'].id).to.exist
+    })
+
     it('Should succeed with the correct parameters', async function () {
       await removeVideo(server.url, server.accessToken, videoId)
     })
index 8a91fbba30901a0bd67d9ef178cccee7245fc75f..3f2f71f46762a32b54635025ef5aa7b10d9daac6 100644 (file)
@@ -18,6 +18,7 @@ import { unfollow } from '../../../../shared/extra-utils/server/follows'
 import { userLogin } from '../../../../shared/extra-utils/users/login'
 import { createUser } from '../../../../shared/extra-utils/users/users'
 import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
+import { PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
 
 const expect = chai.expect
 
@@ -153,7 +154,20 @@ describe('Test follow constraints', function () {
       })
 
       it('Should not get the remote video', async function () {
-        await getVideo(servers[0].url, video2UUID, HttpStatusCode.FORBIDDEN_403)
+        const res = await getVideo(servers[0].url, video2UUID, HttpStatusCode.FORBIDDEN_403)
+
+        const error = res.body as PeerTubeProblemDocument
+
+        const doc = 'https://docs.joinpeertube.org/api-rest-reference.html#section/Errors/does_not_respect_follow_constraints'
+        expect(error.type).to.equal(doc)
+        expect(error.code).to.equal(ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS)
+
+        expect(error.detail).to.equal('Cannot get this video regarding follow constraints')
+        expect(error.error).to.equal(error.detail)
+
+        expect(error.status).to.equal(HttpStatusCode.FORBIDDEN_403)
+
+        expect(error.originUrl).to.contains(servers[1].url)
       })
 
       it('Should list local account videos', async function () {
index f58436ce1e406d6db9d3ec152c9383a58bf0a781..cbbf40a7868d47f3cc173503273f3f6773229b4e 100644 (file)
@@ -1,3 +1,4 @@
+
 import { RegisterServerAuthExternalOptions } from '@server/types'
 import {
   MAbuseMessage,
@@ -20,9 +21,8 @@ import { MVideoImportDefault } from '@server/types/models/video/video-import'
 import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/types/models/video/video-playlist-element'
 import { MAccountVideoRateAccountVideo } from '@server/types/models/video/video-rate'
 import { HttpMethod } from '@shared/core-utils/miscs/http-methods'
-import { VideoCreate } from '@shared/models'
+import { PeerTubeProblemDocumentData, ServerErrorCode, 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,
@@ -41,6 +41,7 @@ import {
   MVideoThumbnail,
   MVideoWithRights
 } from '../../types/models'
+
 declare module 'express' {
   export interface Request {
     query: any
@@ -86,14 +87,20 @@ declare module 'express' {
 
   // 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
+
+      title?: string
+      status?: number
+      type?: ServerErrorCode
+      instance?: string
+
+      data?: PeerTubeProblemDocumentData
+    }) => void
 
     locals: {
+      docUrl?: string
+
       videoAll?: MVideoFullLight
       onlyImmutableVideo?: MVideoImmutable
       onlyVideo?: MVideoThumbnail
index b5163954a9433be30c9c4ef55a739c89aa2fe29e..06bf5c5990c635815c74dc0483ce54320a598a24 100644 (file)
@@ -6,6 +6,7 @@ export * from './debug.model'
 export * from './emailer.model'
 export * from './job.model'
 export * from './log-level.type'
+export * from './peertube-problem-document.model'
 export * from './server-config.model'
 export * from './server-debug.model'
 export * from './server-error-code.enum'
diff --git a/shared/models/server/peertube-problem-document.model.ts b/shared/models/server/peertube-problem-document.model.ts
new file mode 100644 (file)
index 0000000..5e1c320
--- /dev/null
@@ -0,0 +1,32 @@
+import { HttpStatusCode } from '@shared/core-utils'
+import { OAuth2ErrorCode, ServerErrorCode } from './server-error-code.enum'
+
+export interface PeerTubeProblemDocumentData {
+  'invalid-params'?: Record<string, Object>
+
+  originUrl?: string
+
+  keyId?: string
+
+  targetUrl?: string
+
+  actorUrl?: string
+
+  // Feeds
+  format?: string
+  url?: string
+}
+
+export interface PeerTubeProblemDocument extends PeerTubeProblemDocumentData {
+  type: string
+  title: string
+
+  detail: string
+  // Compat PeerTube <= 3.2
+  error: string
+
+  status: HttpStatusCode
+
+  docs?: string
+  code?: ServerErrorCode | OAuth2ErrorCode
+}
index 93b9ce20d34d32866e65b688259648fd09ea9532..43996e7aad678efc18a009f1d8ca13b367372334 100644 (file)
@@ -48,5 +48,13 @@ export const enum OAuth2ErrorCode {
    *
    * @see https://github.com/oauthjs/node-oauth2-server/blob/master/lib/errors/invalid-client-error.js
    */
-  INVALID_CLIENT = 'invalid_client'
+  INVALID_CLIENT = 'invalid_client',
+
+
+  /**
+   * The access token provided is expired, revoked, malformed, or invalid for other reasons
+   *
+   * @see https://github.com/oauthjs/node-oauth2-server/blob/master/lib/errors/invalid-token-error.js
+   */
+  INVALID_TOKEN = 'invalid_token',
 }