diff options
Diffstat (limited to 'server/middlewares')
59 files changed, 1490 insertions, 680 deletions
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts index ce94a2129..6b43b7764 100644 --- a/server/middlewares/activitypub.ts +++ b/server/middlewares/activitypub.ts | |||
@@ -1,13 +1,12 @@ | |||
1 | import { NextFunction, Request, Response } from 'express' | 1 | import { NextFunction, Request, Response } from 'express' |
2 | import { getAPId } from '@server/helpers/activitypub' | ||
3 | import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor' | ||
2 | import { ActivityDelete, ActivityPubSignature } from '../../shared' | 4 | import { ActivityDelete, ActivityPubSignature } from '../../shared' |
5 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' | ||
3 | import { logger } from '../helpers/logger' | 6 | import { logger } from '../helpers/logger' |
4 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' | 7 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' |
5 | import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' | 8 | import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' |
6 | import { getOrCreateActorAndServerAndModel } from '../lib/activitypub/actor' | 9 | import { getOrCreateAPActor, loadActorUrlOrGetFromWebfinger } from '../lib/activitypub/actors' |
7 | import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger' | ||
8 | import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor' | ||
9 | import { getAPId } from '@server/helpers/activitypub' | ||
10 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' | ||
11 | 10 | ||
12 | async function checkSignature (req: Request, res: Response, next: NextFunction) { | 11 | async function checkSignature (req: Request, res: Response, next: NextFunction) { |
13 | try { | 12 | try { |
@@ -29,11 +28,14 @@ async function checkSignature (req: Request, res: Response, next: NextFunction) | |||
29 | const activity: ActivityDelete = req.body | 28 | const activity: ActivityDelete = req.body |
30 | if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) { | 29 | if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) { |
31 | logger.debug('Handling signature error on actor delete activity', { err }) | 30 | logger.debug('Handling signature error on actor delete activity', { err }) |
32 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | 31 | return res.status(HttpStatusCode.NO_CONTENT_204).end() |
33 | } | 32 | } |
34 | 33 | ||
35 | logger.warn('Error in ActivityPub signature checker.', { err }) | 34 | logger.warn('Error in ActivityPub signature checker.', { err }) |
36 | return res.sendStatus(HttpStatusCode.FORBIDDEN_403) | 35 | return res.fail({ |
36 | status: HttpStatusCode.FORBIDDEN_403, | ||
37 | message: 'ActivityPub signature could not be checked' | ||
38 | }) | ||
37 | } | 39 | } |
38 | } | 40 | } |
39 | 41 | ||
@@ -71,13 +73,22 @@ async function checkHttpSignature (req: Request, res: Response) { | |||
71 | } catch (err) { | 73 | } catch (err) { |
72 | logger.warn('Invalid signature because of exception in signature parser', { reqBody: req.body, err }) | 74 | logger.warn('Invalid signature because of exception in signature parser', { reqBody: req.body, err }) |
73 | 75 | ||
74 | res.status(HttpStatusCode.FORBIDDEN_403).json({ error: err.message }) | 76 | res.fail({ |
77 | status: HttpStatusCode.FORBIDDEN_403, | ||
78 | message: err.message | ||
79 | }) | ||
75 | return false | 80 | return false |
76 | } | 81 | } |
77 | 82 | ||
78 | const keyId = parsed.keyId | 83 | const keyId = parsed.keyId |
79 | if (!keyId) { | 84 | if (!keyId) { |
80 | res.sendStatus(HttpStatusCode.FORBIDDEN_403) | 85 | res.fail({ |
86 | status: HttpStatusCode.FORBIDDEN_403, | ||
87 | message: 'Invalid key ID', | ||
88 | data: { | ||
89 | keyId | ||
90 | } | ||
91 | }) | ||
81 | return false | 92 | return false |
82 | } | 93 | } |
83 | 94 | ||
@@ -88,18 +99,23 @@ async function checkHttpSignature (req: Request, res: Response) { | |||
88 | actorUrl = await loadActorUrlOrGetFromWebfinger(actorUrl.replace(/^acct:/, '')) | 99 | actorUrl = await loadActorUrlOrGetFromWebfinger(actorUrl.replace(/^acct:/, '')) |
89 | } | 100 | } |
90 | 101 | ||
91 | const actor = await getOrCreateActorAndServerAndModel(actorUrl) | 102 | const actor = await getOrCreateAPActor(actorUrl) |
92 | 103 | ||
93 | const verified = isHTTPSignatureVerified(parsed, actor) | 104 | const verified = isHTTPSignatureVerified(parsed, actor) |
94 | if (verified !== true) { | 105 | if (verified !== true) { |
95 | logger.warn('Signature from %s is invalid', actorUrl, { parsed }) | 106 | logger.warn('Signature from %s is invalid', actorUrl, { parsed }) |
96 | 107 | ||
97 | res.sendStatus(HttpStatusCode.FORBIDDEN_403) | 108 | res.fail({ |
109 | status: HttpStatusCode.FORBIDDEN_403, | ||
110 | message: 'Invalid signature', | ||
111 | data: { | ||
112 | actorUrl | ||
113 | } | ||
114 | }) | ||
98 | return false | 115 | return false |
99 | } | 116 | } |
100 | 117 | ||
101 | res.locals.signature = { actor } | 118 | res.locals.signature = { actor } |
102 | |||
103 | return true | 119 | return true |
104 | } | 120 | } |
105 | 121 | ||
@@ -107,7 +123,10 @@ async function checkJsonLDSignature (req: Request, res: Response) { | |||
107 | const signatureObject: ActivityPubSignature = req.body.signature | 123 | const signatureObject: ActivityPubSignature = req.body.signature |
108 | 124 | ||
109 | if (!signatureObject || !signatureObject.creator) { | 125 | if (!signatureObject || !signatureObject.creator) { |
110 | res.sendStatus(HttpStatusCode.FORBIDDEN_403) | 126 | res.fail({ |
127 | status: HttpStatusCode.FORBIDDEN_403, | ||
128 | message: 'Object and creator signature do not match' | ||
129 | }) | ||
111 | return false | 130 | return false |
112 | } | 131 | } |
113 | 132 | ||
@@ -115,17 +134,19 @@ async function checkJsonLDSignature (req: Request, res: Response) { | |||
115 | 134 | ||
116 | logger.debug('Checking JsonLD signature of actor %s...', creator) | 135 | logger.debug('Checking JsonLD signature of actor %s...', creator) |
117 | 136 | ||
118 | const actor = await getOrCreateActorAndServerAndModel(creator) | 137 | const actor = await getOrCreateAPActor(creator) |
119 | const verified = await isJsonLDSignatureVerified(actor, req.body) | 138 | const verified = await isJsonLDSignatureVerified(actor, req.body) |
120 | 139 | ||
121 | if (verified !== true) { | 140 | if (verified !== true) { |
122 | logger.warn('Signature not verified.', req.body) | 141 | logger.warn('Signature not verified.', req.body) |
123 | 142 | ||
124 | res.sendStatus(HttpStatusCode.FORBIDDEN_403) | 143 | res.fail({ |
144 | status: HttpStatusCode.FORBIDDEN_403, | ||
145 | message: 'Signature could not be verified' | ||
146 | }) | ||
125 | return false | 147 | return false |
126 | } | 148 | } |
127 | 149 | ||
128 | res.locals.signature = { actor } | 150 | res.locals.signature = { actor } |
129 | |||
130 | return true | 151 | return true |
131 | } | 152 | } |
diff --git a/server/middlewares/auth.ts b/server/middlewares/auth.ts index f38373624..176461cc2 100644 --- a/server/middlewares/auth.ts +++ b/server/middlewares/auth.ts | |||
@@ -16,11 +16,11 @@ function authenticate (req: express.Request, res: express.Response, next: expres | |||
16 | .catch(err => { | 16 | .catch(err => { |
17 | logger.warn('Cannot authenticate.', { err }) | 17 | logger.warn('Cannot authenticate.', { err }) |
18 | 18 | ||
19 | return res.status(err.status) | 19 | return res.fail({ |
20 | .json({ | 20 | status: err.status, |
21 | error: 'Token is invalid.', | 21 | message: 'Token is invalid', |
22 | code: err.name | 22 | type: err.name |
23 | }) | 23 | }) |
24 | }) | 24 | }) |
25 | } | 25 | } |
26 | 26 | ||
@@ -52,7 +52,12 @@ function authenticatePromiseIfNeeded (req: express.Request, res: express.Respons | |||
52 | // Already authenticated? (or tried to) | 52 | // Already authenticated? (or tried to) |
53 | if (res.locals.oauth?.token.User) return resolve() | 53 | if (res.locals.oauth?.token.User) return resolve() |
54 | 54 | ||
55 | if (res.locals.authenticated === false) return res.sendStatus(HttpStatusCode.UNAUTHORIZED_401) | 55 | if (res.locals.authenticated === false) { |
56 | return res.fail({ | ||
57 | status: HttpStatusCode.UNAUTHORIZED_401, | ||
58 | message: 'Not authenticated' | ||
59 | }) | ||
60 | } | ||
56 | 61 | ||
57 | authenticate(req, res, () => resolve(), authenticateInQuery) | 62 | authenticate(req, res, () => resolve(), authenticateInQuery) |
58 | }) | 63 | }) |
diff --git a/server/middlewares/doc.ts b/server/middlewares/doc.ts new file mode 100644 index 000000000..3db85c68d --- /dev/null +++ b/server/middlewares/doc.ts | |||
@@ -0,0 +1,16 @@ | |||
1 | import * as express from 'express' | ||
2 | |||
3 | function openapiOperationDoc (options: { | ||
4 | url?: string | ||
5 | operationId?: string | ||
6 | }) { | ||
7 | return (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
8 | res.locals.docUrl = options.url || 'https://docs.joinpeertube.org/api-rest-reference.html#operation/' + options.operationId | ||
9 | |||
10 | if (next) return next() | ||
11 | } | ||
12 | } | ||
13 | |||
14 | export { | ||
15 | openapiOperationDoc | ||
16 | } | ||
diff --git a/server/middlewares/error.ts b/server/middlewares/error.ts new file mode 100644 index 000000000..e3eb1c8f5 --- /dev/null +++ b/server/middlewares/error.ts | |||
@@ -0,0 +1,39 @@ | |||
1 | import * as express from 'express' | ||
2 | import { ProblemDocument, ProblemDocumentExtension } from 'http-problem-details' | ||
3 | import { HttpStatusCode } from '@shared/core-utils' | ||
4 | |||
5 | function apiFailMiddleware (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
6 | res.fail = options => { | ||
7 | const { status = HttpStatusCode.BAD_REQUEST_400, message, title, type, data, instance } = options | ||
8 | |||
9 | const extension = new ProblemDocumentExtension({ | ||
10 | ...data, | ||
11 | |||
12 | docs: res.locals.docUrl, | ||
13 | code: type, | ||
14 | |||
15 | // For <= 3.2 compatibility | ||
16 | error: message | ||
17 | }) | ||
18 | |||
19 | res.status(status) | ||
20 | res.setHeader('Content-Type', 'application/problem+json') | ||
21 | res.json(new ProblemDocument({ | ||
22 | status, | ||
23 | title, | ||
24 | instance, | ||
25 | |||
26 | detail: message, | ||
27 | |||
28 | type: type | ||
29 | ? `https://docs.joinpeertube.org/api-rest-reference.html#section/Errors/${type}` | ||
30 | : undefined | ||
31 | }, extension)) | ||
32 | } | ||
33 | |||
34 | if (next) next() | ||
35 | } | ||
36 | |||
37 | export { | ||
38 | apiFailMiddleware | ||
39 | } | ||
diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts index 3e280e16f..413653dac 100644 --- a/server/middlewares/index.ts +++ b/server/middlewares/index.ts | |||
@@ -7,4 +7,6 @@ export * from './servers' | |||
7 | export * from './sort' | 7 | export * from './sort' |
8 | export * from './user-right' | 8 | export * from './user-right' |
9 | export * from './dnt' | 9 | export * from './dnt' |
10 | export * from './error' | ||
11 | export * from './doc' | ||
10 | export * from './csp' | 12 | export * from './csp' |
diff --git a/server/middlewares/servers.ts b/server/middlewares/servers.ts index 5e1c165f0..9aa56bc93 100644 --- a/server/middlewares/servers.ts +++ b/server/middlewares/servers.ts | |||
@@ -10,7 +10,10 @@ function setBodyHostsPort (req: express.Request, res: express.Response, next: ex | |||
10 | 10 | ||
11 | // Problem with the url parsing? | 11 | // Problem with the url parsing? |
12 | if (hostWithPort === null) { | 12 | if (hostWithPort === null) { |
13 | return res.sendStatus(HttpStatusCode.INTERNAL_SERVER_ERROR_500) | 13 | return res.fail({ |
14 | status: HttpStatusCode.INTERNAL_SERVER_ERROR_500, | ||
15 | message: 'Could not parse hosts' | ||
16 | }) | ||
14 | } | 17 | } |
15 | 18 | ||
16 | req.body.hosts[i] = hostWithPort | 19 | req.body.hosts[i] = hostWithPort |
diff --git a/server/middlewares/user-right.ts b/server/middlewares/user-right.ts index 45dda4781..d1888c2d3 100644 --- a/server/middlewares/user-right.ts +++ b/server/middlewares/user-right.ts | |||
@@ -10,8 +10,10 @@ function ensureUserHasRight (userRight: UserRight) { | |||
10 | const message = `User ${user.username} does not have right ${userRight} to access to ${req.path}.` | 10 | const message = `User ${user.username} does not have right ${userRight} to access to ${req.path}.` |
11 | logger.info(message) | 11 | logger.info(message) |
12 | 12 | ||
13 | return res.status(HttpStatusCode.FORBIDDEN_403) | 13 | return res.fail({ |
14 | .json({ error: message }) | 14 | status: HttpStatusCode.FORBIDDEN_403, |
15 | message | ||
16 | }) | ||
15 | } | 17 | } |
16 | 18 | ||
17 | return next() | 19 | return next() |
diff --git a/server/middlewares/validators/abuse.ts b/server/middlewares/validators/abuse.ts index 3b897fdef..c048bc6af 100644 --- a/server/middlewares/validators/abuse.ts +++ b/server/middlewares/validators/abuse.ts | |||
@@ -12,14 +12,12 @@ import { | |||
12 | isAbuseTimestampValid, | 12 | isAbuseTimestampValid, |
13 | isAbuseVideoIsValid | 13 | isAbuseVideoIsValid |
14 | } from '@server/helpers/custom-validators/abuses' | 14 | } from '@server/helpers/custom-validators/abuses' |
15 | import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '@server/helpers/custom-validators/misc' | 15 | import { exists, isIdOrUUIDValid, isIdValid, toCompleteUUID, toIntOrNull } from '@server/helpers/custom-validators/misc' |
16 | import { doesCommentIdExist } from '@server/helpers/custom-validators/video-comments' | ||
17 | import { logger } from '@server/helpers/logger' | 16 | import { logger } from '@server/helpers/logger' |
18 | import { doesAbuseExist, doesAccountIdExist, doesVideoExist } from '@server/helpers/middlewares' | ||
19 | import { AbuseMessageModel } from '@server/models/abuse/abuse-message' | 17 | import { AbuseMessageModel } from '@server/models/abuse/abuse-message' |
20 | import { AbuseCreate, UserRight } from '@shared/models' | 18 | import { AbuseCreate, UserRight } from '@shared/models' |
21 | import { areValidationErrors } from './utils' | ||
22 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 19 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
20 | import { areValidationErrors, doesAbuseExist, doesAccountIdExist, doesCommentIdExist, doesVideoExist } from './shared' | ||
23 | 21 | ||
24 | const abuseReportValidator = [ | 22 | const abuseReportValidator = [ |
25 | body('account.id') | 23 | body('account.id') |
@@ -29,6 +27,7 @@ const abuseReportValidator = [ | |||
29 | 27 | ||
30 | body('video.id') | 28 | body('video.id') |
31 | .optional() | 29 | .optional() |
30 | .customSanitizer(toCompleteUUID) | ||
32 | .custom(isIdOrUUIDValid) | 31 | .custom(isIdOrUUIDValid) |
33 | .withMessage('Should have a valid videoId'), | 32 | .withMessage('Should have a valid videoId'), |
34 | body('video.startAt') | 33 | body('video.startAt') |
@@ -71,9 +70,7 @@ const abuseReportValidator = [ | |||
71 | if (body.comment?.id && !await doesCommentIdExist(body.comment.id, res)) return | 70 | if (body.comment?.id && !await doesCommentIdExist(body.comment.id, res)) return |
72 | 71 | ||
73 | if (!body.video?.id && !body.account?.id && !body.comment?.id) { | 72 | if (!body.video?.id && !body.account?.id && !body.comment?.id) { |
74 | res.status(HttpStatusCode.BAD_REQUEST_400) | 73 | res.fail({ message: 'video id or account id or comment id is required.' }) |
75 | .json({ error: 'video id or account id or comment id is required.' }) | ||
76 | |||
77 | return | 74 | return |
78 | } | 75 | } |
79 | 76 | ||
@@ -195,8 +192,10 @@ const getAbuseValidator = [ | |||
195 | const message = `User ${user.username} does not have right to get abuse ${abuse.id}` | 192 | const message = `User ${user.username} does not have right to get abuse ${abuse.id}` |
196 | logger.warn(message) | 193 | logger.warn(message) |
197 | 194 | ||
198 | return res.status(HttpStatusCode.FORBIDDEN_403) | 195 | return res.fail({ |
199 | .json({ error: message }) | 196 | status: HttpStatusCode.FORBIDDEN_403, |
197 | message | ||
198 | }) | ||
200 | } | 199 | } |
201 | 200 | ||
202 | return next() | 201 | return next() |
@@ -209,10 +208,7 @@ const checkAbuseValidForMessagesValidator = [ | |||
209 | 208 | ||
210 | const abuse = res.locals.abuse | 209 | const abuse = res.locals.abuse |
211 | if (abuse.ReporterAccount.isOwned() === false) { | 210 | if (abuse.ReporterAccount.isOwned() === false) { |
212 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 211 | return res.fail({ message: 'This abuse was created by a user of your instance.' }) |
213 | .json({ | ||
214 | error: 'This abuse was created by a user of your instance.' | ||
215 | }) | ||
216 | } | 212 | } |
217 | 213 | ||
218 | return next() | 214 | return next() |
@@ -246,13 +242,17 @@ const deleteAbuseMessageValidator = [ | |||
246 | const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id) | 242 | const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id) |
247 | 243 | ||
248 | if (!abuseMessage) { | 244 | if (!abuseMessage) { |
249 | return res.status(HttpStatusCode.NOT_FOUND_404) | 245 | return res.fail({ |
250 | .json({ error: 'Abuse message not found' }) | 246 | status: HttpStatusCode.NOT_FOUND_404, |
247 | message: 'Abuse message not found' | ||
248 | }) | ||
251 | } | 249 | } |
252 | 250 | ||
253 | if (user.hasRight(UserRight.MANAGE_ABUSES) !== true && abuseMessage.accountId !== user.Account.id) { | 251 | if (user.hasRight(UserRight.MANAGE_ABUSES) !== true && abuseMessage.accountId !== user.Account.id) { |
254 | return res.status(HttpStatusCode.FORBIDDEN_403) | 252 | return res.fail({ |
255 | .json({ error: 'Cannot delete this abuse message' }) | 253 | status: HttpStatusCode.FORBIDDEN_403, |
254 | message: 'Cannot delete this abuse message' | ||
255 | }) | ||
256 | } | 256 | } |
257 | 257 | ||
258 | res.locals.abuseMessage = abuseMessage | 258 | res.locals.abuseMessage = abuseMessage |
diff --git a/server/middlewares/validators/account.ts b/server/middlewares/validators/account.ts index cbdcef2fd..599eb10bb 100644 --- a/server/middlewares/validators/account.ts +++ b/server/middlewares/validators/account.ts | |||
@@ -2,8 +2,7 @@ import * as express from 'express' | |||
2 | import { param } from 'express-validator' | 2 | import { param } from 'express-validator' |
3 | import { isAccountNameValid } from '../../helpers/custom-validators/accounts' | 3 | import { isAccountNameValid } from '../../helpers/custom-validators/accounts' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { areValidationErrors } from './utils' | 5 | import { areValidationErrors, doesAccountNameWithHostExist, doesLocalAccountNameExist } from './shared' |
6 | import { doesAccountNameWithHostExist, doesLocalAccountNameExist } from '../../helpers/middlewares' | ||
7 | 6 | ||
8 | const localAccountValidator = [ | 7 | const localAccountValidator = [ |
9 | param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), | 8 | param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), |
diff --git a/server/middlewares/validators/activitypub/activity.ts b/server/middlewares/validators/activitypub/activity.ts index e78ef07ef..cc6acd4b1 100644 --- a/server/middlewares/validators/activitypub/activity.ts +++ b/server/middlewares/validators/activitypub/activity.ts | |||
@@ -9,16 +9,14 @@ async function activityPubValidator (req: express.Request, res: express.Response | |||
9 | 9 | ||
10 | if (!isRootActivityValid(req.body)) { | 10 | if (!isRootActivityValid(req.body)) { |
11 | logger.warn('Incorrect activity parameters.', { activity: req.body }) | 11 | logger.warn('Incorrect activity parameters.', { activity: req.body }) |
12 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 12 | return res.fail({ message: 'Incorrect activity' }) |
13 | .json({ error: 'Incorrect activity.' }) | ||
14 | } | 13 | } |
15 | 14 | ||
16 | const serverActor = await getServerActor() | 15 | const serverActor = await getServerActor() |
17 | const remoteActor = res.locals.signature.actor | 16 | const remoteActor = res.locals.signature.actor |
18 | if (serverActor.id === remoteActor.id || remoteActor.serverId === null) { | 17 | if (serverActor.id === remoteActor.id || remoteActor.serverId === null) { |
19 | logger.error('Receiving request in INBOX by ourselves!', req.body) | 18 | logger.error('Receiving request in INBOX by ourselves!', req.body) |
20 | return res.status(HttpStatusCode.CONFLICT_409) | 19 | return res.status(HttpStatusCode.CONFLICT_409).end() |
21 | .end() | ||
22 | } | 20 | } |
23 | 21 | ||
24 | return next() | 22 | return next() |
diff --git a/server/middlewares/validators/activitypub/pagination.ts b/server/middlewares/validators/activitypub/pagination.ts index fa21f063d..c8ec34eb6 100644 --- a/server/middlewares/validators/activitypub/pagination.ts +++ b/server/middlewares/validators/activitypub/pagination.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query } from 'express-validator' | 2 | import { query } from 'express-validator' |
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { areValidationErrors } from '../utils' | ||
5 | import { PAGINATION } from '@server/initializers/constants' | 3 | import { PAGINATION } from '@server/initializers/constants' |
4 | import { logger } from '../../../helpers/logger' | ||
5 | import { areValidationErrors } from '../shared' | ||
6 | 6 | ||
7 | const apPaginationValidator = [ | 7 | const apPaginationValidator = [ |
8 | query('page') | 8 | query('page') |
diff --git a/server/middlewares/validators/activitypub/signature.ts b/server/middlewares/validators/activitypub/signature.ts index 7c4e49463..f2f7d5848 100644 --- a/server/middlewares/validators/activitypub/signature.ts +++ b/server/middlewares/validators/activitypub/signature.ts | |||
@@ -1,12 +1,13 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body } from 'express-validator' | 2 | import { body } from 'express-validator' |
3 | import { | 3 | import { |
4 | isSignatureCreatorValid, isSignatureTypeValid, | 4 | isSignatureCreatorValid, |
5 | isSignatureTypeValid, | ||
5 | isSignatureValueValid | 6 | isSignatureValueValid |
6 | } from '../../../helpers/custom-validators/activitypub/signature' | 7 | } from '../../../helpers/custom-validators/activitypub/signature' |
7 | import { isDateValid } from '../../../helpers/custom-validators/misc' | 8 | import { isDateValid } from '../../../helpers/custom-validators/misc' |
8 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
9 | import { areValidationErrors } from '../utils' | 10 | import { areValidationErrors } from '../shared' |
10 | 11 | ||
11 | const signatureValidator = [ | 12 | const signatureValidator = [ |
12 | body('signature.type') | 13 | body('signature.type') |
@@ -14,7 +15,7 @@ const signatureValidator = [ | |||
14 | .custom(isSignatureTypeValid).withMessage('Should have a valid signature type'), | 15 | .custom(isSignatureTypeValid).withMessage('Should have a valid signature type'), |
15 | body('signature.created') | 16 | body('signature.created') |
16 | .optional() | 17 | .optional() |
17 | .custom(isDateValid).withMessage('Should have a valid signature created date'), | 18 | .custom(isDateValid).withMessage('Should have a signature created date that conforms to ISO 8601'), |
18 | body('signature.creator') | 19 | body('signature.creator') |
19 | .optional() | 20 | .optional() |
20 | .custom(isSignatureCreatorValid).withMessage('Should have a valid signature creator'), | 21 | .custom(isSignatureCreatorValid).withMessage('Should have a valid signature creator'), |
diff --git a/server/middlewares/validators/actor-image.ts b/server/middlewares/validators/actor-image.ts index 961d7a7e5..49daadd61 100644 --- a/server/middlewares/validators/actor-image.ts +++ b/server/middlewares/validators/actor-image.ts | |||
@@ -4,7 +4,7 @@ import { isActorImageFile } from '@server/helpers/custom-validators/actor-images | |||
4 | import { cleanUpReqFiles } from '../../helpers/express-utils' | 4 | import { cleanUpReqFiles } from '../../helpers/express-utils' |
5 | import { logger } from '../../helpers/logger' | 5 | import { logger } from '../../helpers/logger' |
6 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 6 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
7 | import { areValidationErrors } from './utils' | 7 | import { areValidationErrors } from './shared' |
8 | 8 | ||
9 | const updateActorImageValidatorFactory = (fieldname: string) => ([ | 9 | const updateActorImageValidatorFactory = (fieldname: string) => ([ |
10 | body(fieldname).custom((value, { req }) => isActorImageFile(req.files, fieldname)).withMessage( | 10 | body(fieldname).custom((value, { req }) => isActorImageFile(req.files, fieldname)).withMessage( |
diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts index f61811a1a..826b16fc8 100644 --- a/server/middlewares/validators/blocklist.ts +++ b/server/middlewares/validators/blocklist.ts | |||
@@ -1,15 +1,14 @@ | |||
1 | import { body, param } from 'express-validator' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator' | ||
3 | import { getServerActor } from '@server/models/application/application' | ||
4 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
5 | import { isHostValid } from '../../helpers/custom-validators/servers' | ||
3 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
4 | import { areValidationErrors } from './utils' | 7 | import { WEBSERVER } from '../../initializers/constants' |
5 | import { AccountBlocklistModel } from '../../models/account/account-blocklist' | 8 | import { AccountBlocklistModel } from '../../models/account/account-blocklist' |
6 | import { isHostValid } from '../../helpers/custom-validators/servers' | ||
7 | import { ServerBlocklistModel } from '../../models/server/server-blocklist' | ||
8 | import { ServerModel } from '../../models/server/server' | 9 | import { ServerModel } from '../../models/server/server' |
9 | import { WEBSERVER } from '../../initializers/constants' | 10 | import { ServerBlocklistModel } from '../../models/server/server-blocklist' |
10 | import { doesAccountNameWithHostExist } from '../../helpers/middlewares' | 11 | import { areValidationErrors, doesAccountNameWithHostExist } from './shared' |
11 | import { getServerActor } from '@server/models/application/application' | ||
12 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
13 | 12 | ||
14 | const blockAccountValidator = [ | 13 | const blockAccountValidator = [ |
15 | body('accountName').exists().withMessage('Should have an account name with host'), | 14 | body('accountName').exists().withMessage('Should have an account name with host'), |
@@ -24,9 +23,10 @@ const blockAccountValidator = [ | |||
24 | const accountToBlock = res.locals.account | 23 | const accountToBlock = res.locals.account |
25 | 24 | ||
26 | if (user.Account.id === accountToBlock.id) { | 25 | if (user.Account.id === accountToBlock.id) { |
27 | res.status(HttpStatusCode.CONFLICT_409) | 26 | res.fail({ |
28 | .json({ error: 'You cannot block yourself.' }) | 27 | status: HttpStatusCode.CONFLICT_409, |
29 | 28 | message: 'You cannot block yourself.' | |
29 | }) | ||
30 | return | 30 | return |
31 | } | 31 | } |
32 | 32 | ||
@@ -79,8 +79,10 @@ const blockServerValidator = [ | |||
79 | const host: string = req.body.host | 79 | const host: string = req.body.host |
80 | 80 | ||
81 | if (host === WEBSERVER.HOST) { | 81 | if (host === WEBSERVER.HOST) { |
82 | return res.status(HttpStatusCode.CONFLICT_409) | 82 | return res.fail({ |
83 | .json({ error: 'You cannot block your own server.' }) | 83 | status: HttpStatusCode.CONFLICT_409, |
84 | message: 'You cannot block your own server.' | ||
85 | }) | ||
84 | } | 86 | } |
85 | 87 | ||
86 | const server = await ServerModel.loadOrCreateByHost(host) | 88 | const server = await ServerModel.loadOrCreateByHost(host) |
@@ -137,27 +139,27 @@ export { | |||
137 | async function doesUnblockAccountExist (accountId: number, targetAccountId: number, res: express.Response) { | 139 | async function doesUnblockAccountExist (accountId: number, targetAccountId: number, res: express.Response) { |
138 | const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(accountId, targetAccountId) | 140 | const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(accountId, targetAccountId) |
139 | if (!accountBlock) { | 141 | if (!accountBlock) { |
140 | res.status(HttpStatusCode.NOT_FOUND_404) | 142 | res.fail({ |
141 | .json({ error: 'Account block entry not found.' }) | 143 | status: HttpStatusCode.NOT_FOUND_404, |
142 | 144 | message: 'Account block entry not found.' | |
145 | }) | ||
143 | return false | 146 | return false |
144 | } | 147 | } |
145 | 148 | ||
146 | res.locals.accountBlock = accountBlock | 149 | res.locals.accountBlock = accountBlock |
147 | |||
148 | return true | 150 | return true |
149 | } | 151 | } |
150 | 152 | ||
151 | async function doesUnblockServerExist (accountId: number, host: string, res: express.Response) { | 153 | async function doesUnblockServerExist (accountId: number, host: string, res: express.Response) { |
152 | const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(accountId, host) | 154 | const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(accountId, host) |
153 | if (!serverBlock) { | 155 | if (!serverBlock) { |
154 | res.status(HttpStatusCode.NOT_FOUND_404) | 156 | res.fail({ |
155 | .json({ error: 'Server block entry not found.' }) | 157 | status: HttpStatusCode.NOT_FOUND_404, |
156 | 158 | message: 'Server block entry not found.' | |
159 | }) | ||
157 | return false | 160 | return false |
158 | } | 161 | } |
159 | 162 | ||
160 | res.locals.serverBlock = serverBlock | 163 | res.locals.serverBlock = serverBlock |
161 | |||
162 | return true | 164 | return true |
163 | } | 165 | } |
diff --git a/server/middlewares/validators/bulk.ts b/server/middlewares/validators/bulk.ts index cfb16d352..9bb95f5b7 100644 --- a/server/middlewares/validators/bulk.ts +++ b/server/middlewares/validators/bulk.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body } from 'express-validator' | 2 | import { body } from 'express-validator' |
3 | import { isBulkRemoveCommentsOfScopeValid } from '@server/helpers/custom-validators/bulk' | 3 | import { isBulkRemoveCommentsOfScopeValid } from '@server/helpers/custom-validators/bulk' |
4 | import { doesAccountNameWithHostExist } from '@server/helpers/middlewares' | 4 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' |
5 | import { UserRight } from '@shared/models' | 5 | import { UserRight } from '@shared/models' |
6 | import { BulkRemoveCommentsOfBody } from '@shared/models/bulk/bulk-remove-comments-of-body.model' | 6 | import { BulkRemoveCommentsOfBody } from '@shared/models/bulk/bulk-remove-comments-of-body.model' |
7 | import { logger } from '../../helpers/logger' | 7 | import { logger } from '../../helpers/logger' |
8 | import { areValidationErrors } from './utils' | 8 | import { areValidationErrors, doesAccountNameWithHostExist } from './shared' |
9 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
10 | 9 | ||
11 | const bulkRemoveCommentsOfValidator = [ | 10 | const bulkRemoveCommentsOfValidator = [ |
12 | body('accountName').exists().withMessage('Should have an account name with host'), | 11 | body('accountName').exists().withMessage('Should have an account name with host'), |
@@ -23,9 +22,9 @@ const bulkRemoveCommentsOfValidator = [ | |||
23 | const body = req.body as BulkRemoveCommentsOfBody | 22 | const body = req.body as BulkRemoveCommentsOfBody |
24 | 23 | ||
25 | if (body.scope === 'instance' && user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) !== true) { | 24 | if (body.scope === 'instance' && user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) !== true) { |
26 | return res.status(HttpStatusCode.FORBIDDEN_403) | 25 | return res.fail({ |
27 | .json({ | 26 | status: HttpStatusCode.FORBIDDEN_403, |
28 | error: 'User cannot remove any comments of this instance.' | 27 | message: 'User cannot remove any comments of this instance.' |
29 | }) | 28 | }) |
30 | } | 29 | } |
31 | 30 | ||
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index a85883b19..1aeadbe65 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -2,13 +2,12 @@ import * as express from 'express' | |||
2 | import { body } from 'express-validator' | 2 | import { body } from 'express-validator' |
3 | import { isIntOrNull } from '@server/helpers/custom-validators/misc' | 3 | import { isIntOrNull } from '@server/helpers/custom-validators/misc' |
4 | import { isEmailEnabled } from '@server/initializers/config' | 4 | import { isEmailEnabled } from '@server/initializers/config' |
5 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
6 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
7 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | 6 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' |
8 | import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' | 7 | import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' |
9 | import { logger } from '../../helpers/logger' | 8 | import { logger } from '../../helpers/logger' |
10 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' | 9 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' |
11 | import { areValidationErrors } from './utils' | 10 | import { areValidationErrors } from './shared' |
12 | 11 | ||
13 | const customConfigUpdateValidator = [ | 12 | const customConfigUpdateValidator = [ |
14 | body('instance.name').exists().withMessage('Should have a valid instance name'), | 13 | body('instance.name').exists().withMessage('Should have a valid instance name'), |
@@ -30,6 +29,7 @@ const customConfigUpdateValidator = [ | |||
30 | body('signup.enabled').isBoolean().withMessage('Should have a valid signup enabled boolean'), | 29 | body('signup.enabled').isBoolean().withMessage('Should have a valid signup enabled boolean'), |
31 | body('signup.limit').isInt().withMessage('Should have a valid signup limit'), | 30 | body('signup.limit').isInt().withMessage('Should have a valid signup limit'), |
32 | body('signup.requiresEmailVerification').isBoolean().withMessage('Should have a valid requiresEmailVerification boolean'), | 31 | body('signup.requiresEmailVerification').isBoolean().withMessage('Should have a valid requiresEmailVerification boolean'), |
32 | body('signup.minimumAge').isInt().withMessage("Should have a valid minimum age required"), | ||
33 | 33 | ||
34 | body('admin.email').isEmail().withMessage('Should have a valid administrator email'), | 34 | body('admin.email').isEmail().withMessage('Should have a valid administrator email'), |
35 | body('contactForm.enabled').isBoolean().withMessage('Should have a valid contact form enabled boolean'), | 35 | body('contactForm.enabled').isBoolean().withMessage('Should have a valid contact form enabled boolean'), |
@@ -114,9 +114,7 @@ function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: exp | |||
114 | if (isEmailEnabled()) return true | 114 | if (isEmailEnabled()) return true |
115 | 115 | ||
116 | if (customConfig.signup.requiresEmailVerification === true) { | 116 | if (customConfig.signup.requiresEmailVerification === true) { |
117 | res.status(HttpStatusCode.BAD_REQUEST_400) | 117 | res.fail({ message: 'Emailer is disabled but you require signup email verification.' }) |
118 | .send({ error: 'Emailer is disabled but you require signup email verification.' }) | ||
119 | .end() | ||
120 | return false | 118 | return false |
121 | } | 119 | } |
122 | 120 | ||
@@ -127,9 +125,7 @@ function checkInvalidTranscodingConfig (customConfig: CustomConfig, res: express | |||
127 | if (customConfig.transcoding.enabled === false) return true | 125 | if (customConfig.transcoding.enabled === false) return true |
128 | 126 | ||
129 | if (customConfig.transcoding.webtorrent.enabled === false && customConfig.transcoding.hls.enabled === false) { | 127 | if (customConfig.transcoding.webtorrent.enabled === false && customConfig.transcoding.hls.enabled === false) { |
130 | res.status(HttpStatusCode.BAD_REQUEST_400) | 128 | res.fail({ message: 'You need to enable at least webtorrent transcoding or hls transcoding' }) |
131 | .send({ error: 'You need to enable at least webtorrent transcoding or hls transcoding' }) | ||
132 | .end() | ||
133 | return false | 129 | return false |
134 | } | 130 | } |
135 | 131 | ||
@@ -140,9 +136,7 @@ function checkInvalidLiveConfig (customConfig: CustomConfig, res: express.Respon | |||
140 | if (customConfig.live.enabled === false) return true | 136 | if (customConfig.live.enabled === false) return true |
141 | 137 | ||
142 | if (customConfig.live.allowReplay === true && customConfig.transcoding.enabled === false) { | 138 | if (customConfig.live.allowReplay === true && customConfig.transcoding.enabled === false) { |
143 | res.status(HttpStatusCode.BAD_REQUEST_400) | 139 | res.fail({ message: 'You cannot allow live replay if transcoding is not enabled' }) |
144 | .send({ error: 'You cannot allow live replay if transcoding is not enabled' }) | ||
145 | .end() | ||
146 | return false | 140 | return false |
147 | } | 141 | } |
148 | 142 | ||
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts index 617661813..51b8fdd19 100644 --- a/server/middlewares/validators/feeds.ts +++ b/server/middlewares/validators/feeds.ts | |||
@@ -1,18 +1,19 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { param, query } from 'express-validator' | 2 | import { param, query } from 'express-validator' |
3 | |||
4 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
3 | import { isValidRSSFeed } from '../../helpers/custom-validators/feeds' | 5 | import { isValidRSSFeed } from '../../helpers/custom-validators/feeds' |
4 | import { exists, isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' | 6 | import { exists, isIdOrUUIDValid, isIdValid, toCompleteUUID } from '../../helpers/custom-validators/misc' |
5 | import { logger } from '../../helpers/logger' | 7 | import { logger } from '../../helpers/logger' |
6 | import { | 8 | import { |
9 | areValidationErrors, | ||
7 | doesAccountIdExist, | 10 | doesAccountIdExist, |
8 | doesAccountNameWithHostExist, | 11 | doesAccountNameWithHostExist, |
9 | doesUserFeedTokenCorrespond, | 12 | doesUserFeedTokenCorrespond, |
10 | doesVideoChannelIdExist, | 13 | doesVideoChannelIdExist, |
11 | doesVideoChannelNameWithHostExist | 14 | doesVideoChannelNameWithHostExist, |
12 | } from '../../helpers/middlewares' | 15 | doesVideoExist |
13 | import { doesVideoExist } from '../../helpers/middlewares/videos' | 16 | } from './shared' |
14 | import { areValidationErrors } from './utils' | ||
15 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
16 | 17 | ||
17 | const feedsFormatValidator = [ | 18 | const feedsFormatValidator = [ |
18 | param('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'), | 19 | param('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'), |
@@ -36,10 +37,10 @@ function setFeedFormatContentType (req: express.Request, res: express.Response, | |||
36 | if (req.accepts(acceptableContentTypes)) { | 37 | if (req.accepts(acceptableContentTypes)) { |
37 | res.set('Content-Type', req.accepts(acceptableContentTypes) as string) | 38 | res.set('Content-Type', req.accepts(acceptableContentTypes) as string) |
38 | } else { | 39 | } else { |
39 | return res.status(HttpStatusCode.NOT_ACCEPTABLE_406) | 40 | return res.fail({ |
40 | .json({ | 41 | status: HttpStatusCode.NOT_ACCEPTABLE_406, |
41 | message: `You should accept at least one of the following content-types: ${acceptableContentTypes.join(', ')}` | 42 | message: `You should accept at least one of the following content-types: ${acceptableContentTypes.join(', ')}` |
42 | }) | 43 | }) |
43 | } | 44 | } |
44 | 45 | ||
45 | return next() | 46 | return next() |
@@ -98,7 +99,10 @@ const videoSubscriptionFeedsValidator = [ | |||
98 | ] | 99 | ] |
99 | 100 | ||
100 | const videoCommentsFeedsValidator = [ | 101 | const videoCommentsFeedsValidator = [ |
101 | query('videoId').optional().custom(isIdOrUUIDValid), | 102 | query('videoId') |
103 | .customSanitizer(toCompleteUUID) | ||
104 | .optional() | ||
105 | .custom(isIdOrUUIDValid), | ||
102 | 106 | ||
103 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 107 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
104 | logger.debug('Checking feeds parameters', { parameters: req.query }) | 108 | logger.debug('Checking feeds parameters', { parameters: req.query }) |
@@ -106,10 +110,7 @@ const videoCommentsFeedsValidator = [ | |||
106 | if (areValidationErrors(req, res)) return | 110 | if (areValidationErrors(req, res)) return |
107 | 111 | ||
108 | if (req.query.videoId && (req.query.videoChannelId || req.query.videoChannelName)) { | 112 | if (req.query.videoId && (req.query.videoChannelId || req.query.videoChannelName)) { |
109 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 113 | return res.fail({ message: 'videoId cannot be mixed with a channel filter' }) |
110 | .json({ | ||
111 | message: 'videoId cannot be mixed with a channel filter' | ||
112 | }) | ||
113 | } | 114 | } |
114 | 115 | ||
115 | if (req.query.videoId && !await doesVideoExist(req.query.videoId, res)) return | 116 | if (req.query.videoId && !await doesVideoExist(req.query.videoId, res)) return |
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index bb849dc72..205baca48 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts | |||
@@ -1,18 +1,18 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { isFollowStateValid } from '@server/helpers/custom-validators/follows' | ||
4 | import { loadActorUrlOrGetFromWebfinger } from '@server/lib/activitypub/actors' | ||
5 | import { getServerActor } from '@server/models/application/application' | ||
6 | import { MActorFollowActorsDefault } from '@server/types/models' | ||
7 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
3 | import { isTestInstance } from '../../helpers/core-utils' | 8 | import { isTestInstance } from '../../helpers/core-utils' |
9 | import { isActorTypeValid, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | ||
4 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' | 10 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' |
5 | import { logger } from '../../helpers/logger' | 11 | import { logger } from '../../helpers/logger' |
6 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' | 12 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' |
7 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 13 | import { ActorModel } from '../../models/actor/actor' |
8 | import { areValidationErrors } from './utils' | 14 | import { ActorFollowModel } from '../../models/actor/actor-follow' |
9 | import { ActorModel } from '../../models/activitypub/actor' | 15 | import { areValidationErrors } from './shared' |
10 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | ||
11 | import { isActorTypeValid, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | ||
12 | import { MActorFollowActorsDefault } from '@server/types/models' | ||
13 | import { isFollowStateValid } from '@server/helpers/custom-validators/follows' | ||
14 | import { getServerActor } from '@server/models/application/application' | ||
15 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
16 | 16 | ||
17 | const listFollowsValidator = [ | 17 | const listFollowsValidator = [ |
18 | query('state') | 18 | query('state') |
@@ -63,11 +63,10 @@ const removeFollowingValidator = [ | |||
63 | const follow = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(serverActor.id, SERVER_ACTOR_NAME, req.params.host) | 63 | const follow = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(serverActor.id, SERVER_ACTOR_NAME, req.params.host) |
64 | 64 | ||
65 | if (!follow) { | 65 | if (!follow) { |
66 | return res | 66 | return res.fail({ |
67 | .status(HttpStatusCode.NOT_FOUND_404) | 67 | status: HttpStatusCode.NOT_FOUND_404, |
68 | .json({ | 68 | message: `Following ${req.params.host} not found.` |
69 | error: `Following ${req.params.host} not found.` | 69 | }) |
70 | }) | ||
71 | } | 70 | } |
72 | 71 | ||
73 | res.locals.follow = follow | 72 | res.locals.follow = follow |
@@ -95,12 +94,10 @@ const getFollowerValidator = [ | |||
95 | } | 94 | } |
96 | 95 | ||
97 | if (!follow) { | 96 | if (!follow) { |
98 | return res | 97 | return res.fail({ |
99 | .status(HttpStatusCode.NOT_FOUND_404) | 98 | status: HttpStatusCode.NOT_FOUND_404, |
100 | .json({ | 99 | message: `Follower ${req.params.nameWithHost} not found.` |
101 | error: `Follower ${req.params.nameWithHost} not found.` | 100 | }) |
102 | }) | ||
103 | .end() | ||
104 | } | 101 | } |
105 | 102 | ||
106 | res.locals.follow = follow | 103 | res.locals.follow = follow |
@@ -114,12 +111,7 @@ const acceptOrRejectFollowerValidator = [ | |||
114 | 111 | ||
115 | const follow = res.locals.follow | 112 | const follow = res.locals.follow |
116 | if (follow.state !== 'pending') { | 113 | if (follow.state !== 'pending') { |
117 | return res | 114 | return res.fail({ message: 'Follow is not in pending state.' }) |
118 | .status(HttpStatusCode.BAD_REQUEST_400) | ||
119 | .json({ | ||
120 | error: 'Follow is not in pending state.' | ||
121 | }) | ||
122 | .end() | ||
123 | } | 115 | } |
124 | 116 | ||
125 | return next() | 117 | return next() |
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index 24faeea3e..94a3c2dea 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts | |||
@@ -11,7 +11,7 @@ export * from './sort' | |||
11 | export * from './users' | 11 | export * from './users' |
12 | export * from './user-subscriptions' | 12 | export * from './user-subscriptions' |
13 | export * from './videos' | 13 | export * from './videos' |
14 | export * from './webfinger' | ||
15 | export * from './search' | 14 | export * from './search' |
16 | export * from './server' | 15 | export * from './server' |
17 | export * from './user-history' | 16 | export * from './user-history' |
17 | export * from './webfinger' | ||
diff --git a/server/middlewares/validators/jobs.ts b/server/middlewares/validators/jobs.ts index d87b28c06..5d89d167f 100644 --- a/server/middlewares/validators/jobs.ts +++ b/server/middlewares/validators/jobs.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import { param, query } from 'express-validator' | 2 | import { param, query } from 'express-validator' |
3 | import { isValidJobState, isValidJobType } from '../../helpers/custom-validators/jobs' | 3 | import { isValidJobState, isValidJobType } from '../../helpers/custom-validators/jobs' |
4 | import { logger, loggerTagsFactory } from '../../helpers/logger' | 4 | import { logger, loggerTagsFactory } from '../../helpers/logger' |
5 | import { areValidationErrors } from './utils' | 5 | import { areValidationErrors } from './shared' |
6 | 6 | ||
7 | const lTags = loggerTagsFactory('validators', 'jobs') | 7 | const lTags = loggerTagsFactory('validators', 'jobs') |
8 | 8 | ||
diff --git a/server/middlewares/validators/logs.ts b/server/middlewares/validators/logs.ts index 70e4d0d99..c55baaee3 100644 --- a/server/middlewares/validators/logs.ts +++ b/server/middlewares/validators/logs.ts | |||
@@ -1,19 +1,19 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { logger } from '../../helpers/logger' | ||
3 | import { areValidationErrors } from './utils' | ||
4 | import { isDateValid } from '../../helpers/custom-validators/misc' | ||
5 | import { query } from 'express-validator' | 2 | import { query } from 'express-validator' |
6 | import { isValidLogLevel } from '../../helpers/custom-validators/logs' | 3 | import { isValidLogLevel } from '../../helpers/custom-validators/logs' |
4 | import { isDateValid } from '../../helpers/custom-validators/misc' | ||
5 | import { logger } from '../../helpers/logger' | ||
6 | import { areValidationErrors } from './shared' | ||
7 | 7 | ||
8 | const getLogsValidator = [ | 8 | const getLogsValidator = [ |
9 | query('startDate') | 9 | query('startDate') |
10 | .custom(isDateValid).withMessage('Should have a valid start date'), | 10 | .custom(isDateValid).withMessage('Should have a start date that conforms to ISO 8601'), |
11 | query('level') | 11 | query('level') |
12 | .optional() | 12 | .optional() |
13 | .custom(isValidLogLevel).withMessage('Should have a valid level'), | 13 | .custom(isValidLogLevel).withMessage('Should have a valid level'), |
14 | query('endDate') | 14 | query('endDate') |
15 | .optional() | 15 | .optional() |
16 | .custom(isDateValid).withMessage('Should have a valid end date'), | 16 | .custom(isDateValid).withMessage('Should have an end date that conforms to ISO 8601'), |
17 | 17 | ||
18 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 18 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
19 | logger.debug('Checking getLogsValidator parameters.', { parameters: req.query }) | 19 | logger.debug('Checking getLogsValidator parameters.', { parameters: req.query }) |
@@ -26,10 +26,10 @@ const getLogsValidator = [ | |||
26 | 26 | ||
27 | const getAuditLogsValidator = [ | 27 | const getAuditLogsValidator = [ |
28 | query('startDate') | 28 | query('startDate') |
29 | .custom(isDateValid).withMessage('Should have a valid start date'), | 29 | .custom(isDateValid).withMessage('Should have a start date that conforms to ISO 8601'), |
30 | query('endDate') | 30 | query('endDate') |
31 | .optional() | 31 | .optional() |
32 | .custom(isDateValid).withMessage('Should have a valid end date'), | 32 | .custom(isDateValid).withMessage('Should have a end date that conforms to ISO 8601'), |
33 | 33 | ||
34 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 34 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
35 | logger.debug('Checking getAuditLogsValidator parameters.', { parameters: req.query }) | 35 | logger.debug('Checking getAuditLogsValidator parameters.', { parameters: req.query }) |
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts index 2a7dc257b..0a82e6932 100644 --- a/server/middlewares/validators/oembed.ts +++ b/server/middlewares/validators/oembed.ts | |||
@@ -1,18 +1,32 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query } from 'express-validator' | 2 | import { query } from 'express-validator' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { fetchVideo } from '@server/helpers/video' | 4 | import { loadVideo } from '@server/lib/model-loaders' |
5 | import { VideoPlaylistModel } from '@server/models/video/video-playlist' | 5 | import { VideoPlaylistModel } from '@server/models/video/video-playlist' |
6 | import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' | 6 | import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' |
7 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
7 | import { isTestInstance } from '../../helpers/core-utils' | 8 | import { isTestInstance } from '../../helpers/core-utils' |
8 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | 9 | import { isIdOrUUIDValid, toCompleteUUID } from '../../helpers/custom-validators/misc' |
9 | import { logger } from '../../helpers/logger' | 10 | import { logger } from '../../helpers/logger' |
10 | import { WEBSERVER } from '../../initializers/constants' | 11 | import { WEBSERVER } from '../../initializers/constants' |
11 | import { areValidationErrors } from './utils' | 12 | import { areValidationErrors } from './shared' |
12 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 13 | |
14 | const playlistPaths = [ | ||
15 | join('videos', 'watch', 'playlist'), | ||
16 | join('w', 'p') | ||
17 | ] | ||
18 | |||
19 | const videoPaths = [ | ||
20 | join('videos', 'watch'), | ||
21 | 'w' | ||
22 | ] | ||
23 | |||
24 | function buildUrls (paths: string[]) { | ||
25 | return paths.map(p => WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, p) + '/') | ||
26 | } | ||
13 | 27 | ||
14 | const startVideoPlaylistsURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch', 'playlist') + '/' | 28 | const startPlaylistURLs = buildUrls(playlistPaths) |
15 | const startVideosURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch') + '/' | 29 | const startVideoURLs = buildUrls(videoPaths) |
16 | 30 | ||
17 | const watchRegex = /([^/]+)$/ | 31 | const watchRegex = /([^/]+)$/ |
18 | const isURLOptions = { | 32 | const isURLOptions = { |
@@ -37,41 +51,54 @@ const oembedValidator = [ | |||
37 | if (areValidationErrors(req, res)) return | 51 | if (areValidationErrors(req, res)) return |
38 | 52 | ||
39 | if (req.query.format !== undefined && req.query.format !== 'json') { | 53 | if (req.query.format !== undefined && req.query.format !== 'json') { |
40 | return res.status(HttpStatusCode.NOT_IMPLEMENTED_501) | 54 | return res.fail({ |
41 | .json({ error: 'Requested format is not implemented on server.' }) | 55 | status: HttpStatusCode.NOT_IMPLEMENTED_501, |
56 | message: 'Requested format is not implemented on server.', | ||
57 | data: { | ||
58 | format: req.query.format | ||
59 | } | ||
60 | }) | ||
42 | } | 61 | } |
43 | 62 | ||
44 | const url = req.query.url as string | 63 | const url = req.query.url as string |
45 | 64 | ||
46 | const isPlaylist = url.startsWith(startVideoPlaylistsURL) | 65 | const isPlaylist = startPlaylistURLs.some(u => url.startsWith(u)) |
47 | const isVideo = isPlaylist ? false : url.startsWith(startVideosURL) | 66 | const isVideo = isPlaylist ? false : startVideoURLs.some(u => url.startsWith(u)) |
48 | 67 | ||
49 | const startIsOk = isVideo || isPlaylist | 68 | const startIsOk = isVideo || isPlaylist |
50 | 69 | ||
51 | const matches = watchRegex.exec(url) | 70 | const matches = watchRegex.exec(url) |
52 | 71 | ||
53 | if (startIsOk === false || matches === null) { | 72 | if (startIsOk === false || matches === null) { |
54 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 73 | return res.fail({ |
55 | .json({ error: 'Invalid url.' }) | 74 | status: HttpStatusCode.BAD_REQUEST_400, |
75 | message: 'Invalid url.', | ||
76 | data: { | ||
77 | url | ||
78 | } | ||
79 | }) | ||
56 | } | 80 | } |
57 | 81 | ||
58 | const elementId = matches[1] | 82 | const elementId = toCompleteUUID(matches[1]) |
59 | if (isIdOrUUIDValid(elementId) === false) { | 83 | if (isIdOrUUIDValid(elementId) === false) { |
60 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 84 | return res.fail({ message: 'Invalid video or playlist id.' }) |
61 | .json({ error: 'Invalid video or playlist id.' }) | ||
62 | } | 85 | } |
63 | 86 | ||
64 | if (isVideo) { | 87 | if (isVideo) { |
65 | const video = await fetchVideo(elementId, 'all') | 88 | const video = await loadVideo(elementId, 'all') |
66 | 89 | ||
67 | if (!video) { | 90 | if (!video) { |
68 | return res.status(HttpStatusCode.NOT_FOUND_404) | 91 | return res.fail({ |
69 | .json({ error: 'Video not found' }) | 92 | status: HttpStatusCode.NOT_FOUND_404, |
93 | message: 'Video not found' | ||
94 | }) | ||
70 | } | 95 | } |
71 | 96 | ||
72 | if (video.privacy !== VideoPrivacy.PUBLIC) { | 97 | if (video.privacy !== VideoPrivacy.PUBLIC) { |
73 | return res.status(HttpStatusCode.FORBIDDEN_403) | 98 | return res.fail({ |
74 | .json({ error: 'Video is not public' }) | 99 | status: HttpStatusCode.FORBIDDEN_403, |
100 | message: 'Video is not public' | ||
101 | }) | ||
75 | } | 102 | } |
76 | 103 | ||
77 | res.locals.videoAll = video | 104 | res.locals.videoAll = video |
@@ -82,13 +109,17 @@ const oembedValidator = [ | |||
82 | 109 | ||
83 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(elementId, undefined) | 110 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(elementId, undefined) |
84 | if (!videoPlaylist) { | 111 | if (!videoPlaylist) { |
85 | return res.status(HttpStatusCode.NOT_FOUND_404) | 112 | return res.fail({ |
86 | .json({ error: 'Video playlist not found' }) | 113 | status: HttpStatusCode.NOT_FOUND_404, |
114 | message: 'Video playlist not found' | ||
115 | }) | ||
87 | } | 116 | } |
88 | 117 | ||
89 | if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PUBLIC) { | 118 | if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PUBLIC) { |
90 | return res.status(HttpStatusCode.FORBIDDEN_403) | 119 | return res.fail({ |
91 | .json({ error: 'Playlist is not public' }) | 120 | status: HttpStatusCode.FORBIDDEN_403, |
121 | message: 'Playlist is not public' | ||
122 | }) | ||
92 | } | 123 | } |
93 | 124 | ||
94 | res.locals.videoPlaylistSummary = videoPlaylist | 125 | res.locals.videoPlaylistSummary = videoPlaylist |
diff --git a/server/middlewares/validators/pagination.ts b/server/middlewares/validators/pagination.ts index 6b0a83d80..74eae251e 100644 --- a/server/middlewares/validators/pagination.ts +++ b/server/middlewares/validators/pagination.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query } from 'express-validator' | 2 | import { query } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | ||
4 | import { areValidationErrors } from './utils' | ||
5 | import { PAGINATION } from '@server/initializers/constants' | 3 | import { PAGINATION } from '@server/initializers/constants' |
4 | import { logger } from '../../helpers/logger' | ||
5 | import { areValidationErrors } from './shared' | ||
6 | 6 | ||
7 | const paginationValidator = paginationValidatorBuilder() | 7 | const paginationValidator = paginationValidatorBuilder() |
8 | 8 | ||
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index ab87fe720..8c76d2e36 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -1,15 +1,15 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query, ValidationChain } from 'express-validator' | 2 | import { body, param, query, ValidationChain } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | 3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
4 | import { areValidationErrors } from './utils' | 4 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
5 | import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/server/api/install-plugin.model' | ||
6 | import { exists, isBooleanValid, isSafePath, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | ||
5 | import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' | 7 | import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' |
8 | import { logger } from '../../helpers/logger' | ||
9 | import { CONFIG } from '../../initializers/config' | ||
6 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 10 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
7 | import { isBooleanValid, isSafePath, toBooleanOrNull, exists, toIntOrNull } from '../../helpers/custom-validators/misc' | ||
8 | import { PluginModel } from '../../models/server/plugin' | 11 | import { PluginModel } from '../../models/server/plugin' |
9 | import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' | 12 | import { areValidationErrors } from './shared' |
10 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | ||
11 | import { CONFIG } from '../../initializers/config' | ||
12 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
13 | 13 | ||
14 | const getPluginValidator = (pluginType: PluginType, withVersion = true) => { | 14 | const getPluginValidator = (pluginType: PluginType, withVersion = true) => { |
15 | const validators: (ValidationChain | express.Handler)[] = [ | 15 | const validators: (ValidationChain | express.Handler)[] = [ |
@@ -31,8 +31,18 @@ const getPluginValidator = (pluginType: PluginType, withVersion = true) => { | |||
31 | const npmName = PluginModel.buildNpmName(req.params.pluginName, pluginType) | 31 | const npmName = PluginModel.buildNpmName(req.params.pluginName, pluginType) |
32 | const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(npmName) | 32 | const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(npmName) |
33 | 33 | ||
34 | if (!plugin) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 34 | if (!plugin) { |
35 | if (withVersion && plugin.version !== req.params.pluginVersion) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 35 | return res.fail({ |
36 | status: HttpStatusCode.NOT_FOUND_404, | ||
37 | message: 'No plugin found named ' + npmName | ||
38 | }) | ||
39 | } | ||
40 | if (withVersion && plugin.version !== req.params.pluginVersion) { | ||
41 | return res.fail({ | ||
42 | status: HttpStatusCode.NOT_FOUND_404, | ||
43 | message: 'No plugin found named ' + npmName + ' with version ' + req.params.pluginVersion | ||
44 | }) | ||
45 | } | ||
36 | 46 | ||
37 | res.locals.registeredPlugin = plugin | 47 | res.locals.registeredPlugin = plugin |
38 | 48 | ||
@@ -50,10 +60,20 @@ const getExternalAuthValidator = [ | |||
50 | if (areValidationErrors(req, res)) return | 60 | if (areValidationErrors(req, res)) return |
51 | 61 | ||
52 | const plugin = res.locals.registeredPlugin | 62 | const plugin = res.locals.registeredPlugin |
53 | if (!plugin.registerHelpers) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 63 | if (!plugin.registerHelpers) { |
64 | return res.fail({ | ||
65 | status: HttpStatusCode.NOT_FOUND_404, | ||
66 | message: 'No registered helpers were found for this plugin' | ||
67 | }) | ||
68 | } | ||
54 | 69 | ||
55 | const externalAuth = plugin.registerHelpers.getExternalAuths().find(a => a.authName === req.params.authName) | 70 | const externalAuth = plugin.registerHelpers.getExternalAuths().find(a => a.authName === req.params.authName) |
56 | if (!externalAuth) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 71 | if (!externalAuth) { |
72 | return res.fail({ | ||
73 | status: HttpStatusCode.NOT_FOUND_404, | ||
74 | message: 'No external auths were found for this plugin' | ||
75 | }) | ||
76 | } | ||
57 | 77 | ||
58 | res.locals.externalAuth = externalAuth | 78 | res.locals.externalAuth = externalAuth |
59 | 79 | ||
@@ -107,8 +127,7 @@ const installOrUpdatePluginValidator = [ | |||
107 | 127 | ||
108 | const body: InstallOrUpdatePlugin = req.body | 128 | const body: InstallOrUpdatePlugin = req.body |
109 | if (!body.path && !body.npmName) { | 129 | if (!body.path && !body.npmName) { |
110 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 130 | return res.fail({ message: 'Should have either a npmName or a path' }) |
111 | .json({ error: 'Should have either a npmName or a path' }) | ||
112 | } | 131 | } |
113 | 132 | ||
114 | return next() | 133 | return next() |
@@ -137,12 +156,13 @@ const existingPluginValidator = [ | |||
137 | 156 | ||
138 | const plugin = await PluginModel.loadByNpmName(req.params.npmName) | 157 | const plugin = await PluginModel.loadByNpmName(req.params.npmName) |
139 | if (!plugin) { | 158 | if (!plugin) { |
140 | return res.status(HttpStatusCode.NOT_FOUND_404) | 159 | return res.fail({ |
141 | .json({ error: 'Plugin not found' }) | 160 | status: HttpStatusCode.NOT_FOUND_404, |
161 | message: 'Plugin not found' | ||
162 | }) | ||
142 | } | 163 | } |
143 | 164 | ||
144 | res.locals.plugin = plugin | 165 | res.locals.plugin = plugin |
145 | |||
146 | return next() | 166 | return next() |
147 | } | 167 | } |
148 | ] | 168 | ] |
@@ -177,9 +197,7 @@ const listAvailablePluginsValidator = [ | |||
177 | if (areValidationErrors(req, res)) return | 197 | if (areValidationErrors(req, res)) return |
178 | 198 | ||
179 | if (CONFIG.PLUGINS.INDEX.ENABLED === false) { | 199 | if (CONFIG.PLUGINS.INDEX.ENABLED === false) { |
180 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 200 | return res.fail({ message: 'Plugin index is not enabled' }) |
181 | .json({ error: 'Plugin index is not enabled' }) | ||
182 | .end() | ||
183 | } | 201 | } |
184 | 202 | ||
185 | return next() | 203 | return next() |
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts index c379aebe4..116c8c611 100644 --- a/server/middlewares/validators/redundancy.ts +++ b/server/middlewares/validators/redundancy.ts | |||
@@ -1,17 +1,25 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { exists, isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 3 | import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies' |
4 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
5 | import { | ||
6 | exists, | ||
7 | isBooleanValid, | ||
8 | isIdOrUUIDValid, | ||
9 | isIdValid, | ||
10 | toBooleanOrNull, | ||
11 | toCompleteUUID, | ||
12 | toIntOrNull | ||
13 | } from '../../helpers/custom-validators/misc' | ||
14 | import { isHostValid } from '../../helpers/custom-validators/servers' | ||
4 | import { logger } from '../../helpers/logger' | 15 | import { logger } from '../../helpers/logger' |
5 | import { areValidationErrors } from './utils' | ||
6 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 16 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
7 | import { isHostValid } from '../../helpers/custom-validators/servers' | ||
8 | import { ServerModel } from '../../models/server/server' | 17 | import { ServerModel } from '../../models/server/server' |
9 | import { doesVideoExist } from '../../helpers/middlewares' | 18 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared' |
10 | import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies' | ||
11 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
12 | 19 | ||
13 | const videoFileRedundancyGetValidator = [ | 20 | const videoFileRedundancyGetValidator = [ |
14 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | 21 | isValidVideoIdParam('videoId'), |
22 | |||
15 | param('resolution') | 23 | param('resolution') |
16 | .customSanitizer(toIntOrNull) | 24 | .customSanitizer(toIntOrNull) |
17 | .custom(exists).withMessage('Should have a valid resolution'), | 25 | .custom(exists).withMessage('Should have a valid resolution'), |
@@ -35,11 +43,21 @@ const videoFileRedundancyGetValidator = [ | |||
35 | return f.resolution === paramResolution && (!req.params.fps || paramFPS) | 43 | return f.resolution === paramResolution && (!req.params.fps || paramFPS) |
36 | }) | 44 | }) |
37 | 45 | ||
38 | if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video file not found.' }) | 46 | if (!videoFile) { |
47 | return res.fail({ | ||
48 | status: HttpStatusCode.NOT_FOUND_404, | ||
49 | message: 'Video file not found.' | ||
50 | }) | ||
51 | } | ||
39 | res.locals.videoFile = videoFile | 52 | res.locals.videoFile = videoFile |
40 | 53 | ||
41 | const videoRedundancy = await VideoRedundancyModel.loadLocalByFileId(videoFile.id) | 54 | const videoRedundancy = await VideoRedundancyModel.loadLocalByFileId(videoFile.id) |
42 | if (!videoRedundancy) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video redundancy not found.' }) | 55 | if (!videoRedundancy) { |
56 | return res.fail({ | ||
57 | status: HttpStatusCode.NOT_FOUND_404, | ||
58 | message: 'Video redundancy not found.' | ||
59 | }) | ||
60 | } | ||
43 | res.locals.videoRedundancy = videoRedundancy | 61 | res.locals.videoRedundancy = videoRedundancy |
44 | 62 | ||
45 | return next() | 63 | return next() |
@@ -47,9 +65,8 @@ const videoFileRedundancyGetValidator = [ | |||
47 | ] | 65 | ] |
48 | 66 | ||
49 | const videoPlaylistRedundancyGetValidator = [ | 67 | const videoPlaylistRedundancyGetValidator = [ |
50 | param('videoId') | 68 | isValidVideoIdParam('videoId'), |
51 | .custom(isIdOrUUIDValid) | 69 | |
52 | .not().isEmpty().withMessage('Should have a valid video id'), | ||
53 | param('streamingPlaylistType') | 70 | param('streamingPlaylistType') |
54 | .customSanitizer(toIntOrNull) | 71 | .customSanitizer(toIntOrNull) |
55 | .custom(exists).withMessage('Should have a valid streaming playlist type'), | 72 | .custom(exists).withMessage('Should have a valid streaming playlist type'), |
@@ -65,11 +82,21 @@ const videoPlaylistRedundancyGetValidator = [ | |||
65 | const paramPlaylistType = req.params.streamingPlaylistType as unknown as number // We casted to int above | 82 | const paramPlaylistType = req.params.streamingPlaylistType as unknown as number // We casted to int above |
66 | const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p.type === paramPlaylistType) | 83 | const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p.type === paramPlaylistType) |
67 | 84 | ||
68 | if (!videoStreamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video playlist not found.' }) | 85 | if (!videoStreamingPlaylist) { |
86 | return res.fail({ | ||
87 | status: HttpStatusCode.NOT_FOUND_404, | ||
88 | message: 'Video playlist not found.' | ||
89 | }) | ||
90 | } | ||
69 | res.locals.videoStreamingPlaylist = videoStreamingPlaylist | 91 | res.locals.videoStreamingPlaylist = videoStreamingPlaylist |
70 | 92 | ||
71 | const videoRedundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(videoStreamingPlaylist.id) | 93 | const videoRedundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(videoStreamingPlaylist.id) |
72 | if (!videoRedundancy) return res.status(HttpStatusCode.NOT_FOUND_404).json({ error: 'Video redundancy not found.' }) | 94 | if (!videoRedundancy) { |
95 | return res.fail({ | ||
96 | status: HttpStatusCode.NOT_FOUND_404, | ||
97 | message: 'Video redundancy not found.' | ||
98 | }) | ||
99 | } | ||
73 | res.locals.videoRedundancy = videoRedundancy | 100 | res.locals.videoRedundancy = videoRedundancy |
74 | 101 | ||
75 | return next() | 102 | return next() |
@@ -90,12 +117,10 @@ const updateServerRedundancyValidator = [ | |||
90 | const server = await ServerModel.loadByHost(req.params.host) | 117 | const server = await ServerModel.loadByHost(req.params.host) |
91 | 118 | ||
92 | if (!server) { | 119 | if (!server) { |
93 | return res | 120 | return res.fail({ |
94 | .status(HttpStatusCode.NOT_FOUND_404) | 121 | status: HttpStatusCode.NOT_FOUND_404, |
95 | .json({ | 122 | message: `Server ${req.params.host} not found.` |
96 | error: `Server ${req.params.host} not found.` | 123 | }) |
97 | }) | ||
98 | .end() | ||
99 | } | 124 | } |
100 | 125 | ||
101 | res.locals.server = server | 126 | res.locals.server = server |
@@ -118,7 +143,8 @@ const listVideoRedundanciesValidator = [ | |||
118 | 143 | ||
119 | const addVideoRedundancyValidator = [ | 144 | const addVideoRedundancyValidator = [ |
120 | body('videoId') | 145 | body('videoId') |
121 | .custom(isIdValid) | 146 | .customSanitizer(toCompleteUUID) |
147 | .custom(isIdOrUUIDValid) | ||
122 | .withMessage('Should have a valid video id'), | 148 | .withMessage('Should have a valid video id'), |
123 | 149 | ||
124 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 150 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
@@ -129,19 +155,19 @@ const addVideoRedundancyValidator = [ | |||
129 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return | 155 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return |
130 | 156 | ||
131 | if (res.locals.onlyVideo.remote === false) { | 157 | if (res.locals.onlyVideo.remote === false) { |
132 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 158 | return res.fail({ message: 'Cannot create a redundancy on a local video' }) |
133 | .json({ error: 'Cannot create a redundancy on a local video' }) | ||
134 | } | 159 | } |
135 | 160 | ||
136 | if (res.locals.onlyVideo.isLive) { | 161 | if (res.locals.onlyVideo.isLive) { |
137 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 162 | return res.fail({ message: 'Cannot create a redundancy of a live video' }) |
138 | .json({ error: 'Cannot create a redundancy of a live video' }) | ||
139 | } | 163 | } |
140 | 164 | ||
141 | const alreadyExists = await VideoRedundancyModel.isLocalByVideoUUIDExists(res.locals.onlyVideo.uuid) | 165 | const alreadyExists = await VideoRedundancyModel.isLocalByVideoUUIDExists(res.locals.onlyVideo.uuid) |
142 | if (alreadyExists) { | 166 | if (alreadyExists) { |
143 | return res.status(HttpStatusCode.CONFLICT_409) | 167 | return res.fail({ |
144 | .json({ error: 'This video is already duplicated by your instance.' }) | 168 | status: HttpStatusCode.CONFLICT_409, |
169 | message: 'This video is already duplicated by your instance.' | ||
170 | }) | ||
145 | } | 171 | } |
146 | 172 | ||
147 | return next() | 173 | return next() |
@@ -160,9 +186,10 @@ const removeVideoRedundancyValidator = [ | |||
160 | 186 | ||
161 | const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10)) | 187 | const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10)) |
162 | if (!redundancy) { | 188 | if (!redundancy) { |
163 | return res.status(HttpStatusCode.NOT_FOUND_404) | 189 | return res.fail({ |
164 | .json({ error: 'Video redundancy not found' }) | 190 | status: HttpStatusCode.NOT_FOUND_404, |
165 | .end() | 191 | message: 'Video redundancy not found' |
192 | }) | ||
166 | } | 193 | } |
167 | 194 | ||
168 | res.locals.videoRedundancy = redundancy | 195 | res.locals.videoRedundancy = redundancy |
diff --git a/server/middlewares/validators/search.ts b/server/middlewares/validators/search.ts index 78213c70d..7bbf81048 100644 --- a/server/middlewares/validators/search.ts +++ b/server/middlewares/validators/search.ts | |||
@@ -1,18 +1,26 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { areValidationErrors } from './utils' | ||
3 | import { logger } from '../../helpers/logger' | ||
4 | import { query } from 'express-validator' | 2 | import { query } from 'express-validator' |
5 | import { isDateValid } from '../../helpers/custom-validators/misc' | ||
6 | import { isSearchTargetValid } from '@server/helpers/custom-validators/search' | 3 | import { isSearchTargetValid } from '@server/helpers/custom-validators/search' |
4 | import { isDateValid } from '../../helpers/custom-validators/misc' | ||
5 | import { logger } from '../../helpers/logger' | ||
6 | import { areValidationErrors } from './shared' | ||
7 | 7 | ||
8 | const videosSearchValidator = [ | 8 | const videosSearchValidator = [ |
9 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), | 9 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), |
10 | 10 | ||
11 | query('startDate').optional().custom(isDateValid).withMessage('Should have a valid start date'), | 11 | query('startDate') |
12 | query('endDate').optional().custom(isDateValid).withMessage('Should have a valid end date'), | 12 | .optional() |
13 | .custom(isDateValid).withMessage('Should have a start date that conforms to ISO 8601'), | ||
14 | query('endDate') | ||
15 | .optional() | ||
16 | .custom(isDateValid).withMessage('Should have a end date that conforms to ISO 8601'), | ||
13 | 17 | ||
14 | query('originallyPublishedStartDate').optional().custom(isDateValid).withMessage('Should have a valid published start date'), | 18 | query('originallyPublishedStartDate') |
15 | query('originallyPublishedEndDate').optional().custom(isDateValid).withMessage('Should have a valid published end date'), | 19 | .optional() |
20 | .custom(isDateValid).withMessage('Should have a published start date that conforms to ISO 8601'), | ||
21 | query('originallyPublishedEndDate') | ||
22 | .optional() | ||
23 | .custom(isDateValid).withMessage('Should have a published end date that conforms to ISO 8601'), | ||
16 | 24 | ||
17 | query('durationMin').optional().isInt().withMessage('Should have a valid min duration'), | 25 | query('durationMin').optional().isInt().withMessage('Should have a valid min duration'), |
18 | query('durationMax').optional().isInt().withMessage('Should have a valid max duration'), | 26 | query('durationMax').optional().isInt().withMessage('Should have a valid max duration'), |
@@ -41,11 +49,12 @@ const videoChannelsListSearchValidator = [ | |||
41 | } | 49 | } |
42 | ] | 50 | ] |
43 | 51 | ||
44 | const videoChannelsOwnSearchValidator = [ | 52 | const videoPlaylistsListSearchValidator = [ |
45 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), | 53 | query('search').not().isEmpty().withMessage('Should have a valid search'), |
54 | query('searchTarget').optional().custom(isSearchTargetValid).withMessage('Should have a valid search target'), | ||
46 | 55 | ||
47 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 56 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
48 | logger.debug('Checking video channels search query', { parameters: req.query }) | 57 | logger.debug('Checking video playlists search query', { parameters: req.query }) |
49 | 58 | ||
50 | if (areValidationErrors(req, res)) return | 59 | if (areValidationErrors(req, res)) return |
51 | 60 | ||
@@ -58,5 +67,5 @@ const videoChannelsOwnSearchValidator = [ | |||
58 | export { | 67 | export { |
59 | videosSearchValidator, | 68 | videosSearchValidator, |
60 | videoChannelsListSearchValidator, | 69 | videoChannelsListSearchValidator, |
61 | videoChannelsOwnSearchValidator | 70 | videoPlaylistsListSearchValidator |
62 | } | 71 | } |
diff --git a/server/middlewares/validators/server.ts b/server/middlewares/validators/server.ts index fe6704716..fc7239b25 100644 --- a/server/middlewares/validators/server.ts +++ b/server/middlewares/validators/server.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { logger } from '../../helpers/logger' | ||
3 | import { areValidationErrors } from './utils' | ||
4 | import { isHostValid, isValidContactBody } from '../../helpers/custom-validators/servers' | ||
5 | import { ServerModel } from '../../models/server/server' | ||
6 | import { body } from 'express-validator' | 2 | import { body } from 'express-validator' |
3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
4 | import { isHostValid, isValidContactBody } from '../../helpers/custom-validators/servers' | ||
7 | import { isUserDisplayNameValid } from '../../helpers/custom-validators/users' | 5 | import { isUserDisplayNameValid } from '../../helpers/custom-validators/users' |
8 | import { Redis } from '../../lib/redis' | 6 | import { logger } from '../../helpers/logger' |
9 | import { CONFIG, isEmailEnabled } from '../../initializers/config' | 7 | import { CONFIG, isEmailEnabled } from '../../initializers/config' |
10 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 8 | import { Redis } from '../../lib/redis' |
9 | import { ServerModel } from '../../models/server/server' | ||
10 | import { areValidationErrors } from './shared' | ||
11 | 11 | ||
12 | const serverGetValidator = [ | 12 | const serverGetValidator = [ |
13 | body('host').custom(isHostValid).withMessage('Should have a valid host'), | 13 | body('host').custom(isHostValid).withMessage('Should have a valid host'), |
@@ -19,9 +19,10 @@ const serverGetValidator = [ | |||
19 | 19 | ||
20 | const server = await ServerModel.loadByHost(req.body.host) | 20 | const server = await ServerModel.loadByHost(req.body.host) |
21 | if (!server) { | 21 | if (!server) { |
22 | return res.status(HttpStatusCode.NOT_FOUND_404) | 22 | return res.fail({ |
23 | .send({ error: 'Server host not found.' }) | 23 | status: HttpStatusCode.NOT_FOUND_404, |
24 | .end() | 24 | message: 'Server host not found.' |
25 | }) | ||
25 | } | 26 | } |
26 | 27 | ||
27 | res.locals.server = server | 28 | res.locals.server = server |
@@ -44,26 +45,26 @@ const contactAdministratorValidator = [ | |||
44 | if (areValidationErrors(req, res)) return | 45 | if (areValidationErrors(req, res)) return |
45 | 46 | ||
46 | if (CONFIG.CONTACT_FORM.ENABLED === false) { | 47 | if (CONFIG.CONTACT_FORM.ENABLED === false) { |
47 | return res | 48 | return res.fail({ |
48 | .status(HttpStatusCode.CONFLICT_409) | 49 | status: HttpStatusCode.CONFLICT_409, |
49 | .send({ error: 'Contact form is not enabled on this instance.' }) | 50 | message: 'Contact form is not enabled on this instance.' |
50 | .end() | 51 | }) |
51 | } | 52 | } |
52 | 53 | ||
53 | if (isEmailEnabled() === false) { | 54 | if (isEmailEnabled() === false) { |
54 | return res | 55 | return res.fail({ |
55 | .status(HttpStatusCode.CONFLICT_409) | 56 | status: HttpStatusCode.CONFLICT_409, |
56 | .send({ error: 'Emailer is not enabled on this instance.' }) | 57 | message: 'Emailer is not enabled on this instance.' |
57 | .end() | 58 | }) |
58 | } | 59 | } |
59 | 60 | ||
60 | if (await Redis.Instance.doesContactFormIpExist(req.ip)) { | 61 | if (await Redis.Instance.doesContactFormIpExist(req.ip)) { |
61 | logger.info('Refusing a contact form by %s: already sent one recently.', req.ip) | 62 | logger.info('Refusing a contact form by %s: already sent one recently.', req.ip) |
62 | 63 | ||
63 | return res | 64 | return res.fail({ |
64 | .status(HttpStatusCode.FORBIDDEN_403) | 65 | status: HttpStatusCode.FORBIDDEN_403, |
65 | .send({ error: 'You already sent a contact form recently.' }) | 66 | message: 'You already sent a contact form recently.' |
66 | .end() | 67 | }) |
67 | } | 68 | } |
68 | 69 | ||
69 | return next() | 70 | return next() |
diff --git a/server/middlewares/validators/shared/abuses.ts b/server/middlewares/validators/shared/abuses.ts new file mode 100644 index 000000000..4a20a55fa --- /dev/null +++ b/server/middlewares/validators/shared/abuses.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { Response } from 'express' | ||
2 | import { AbuseModel } from '@server/models/abuse/abuse' | ||
3 | import { HttpStatusCode } from '@shared/core-utils' | ||
4 | |||
5 | async function doesAbuseExist (abuseId: number | string, res: Response) { | ||
6 | const abuse = await AbuseModel.loadByIdWithReporter(parseInt(abuseId + '', 10)) | ||
7 | |||
8 | if (!abuse) { | ||
9 | res.fail({ | ||
10 | status: HttpStatusCode.NOT_FOUND_404, | ||
11 | message: 'Abuse not found' | ||
12 | }) | ||
13 | |||
14 | return false | ||
15 | } | ||
16 | |||
17 | res.locals.abuse = abuse | ||
18 | return true | ||
19 | } | ||
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | export { | ||
24 | doesAbuseExist | ||
25 | } | ||
diff --git a/server/middlewares/validators/shared/accounts.ts b/server/middlewares/validators/shared/accounts.ts new file mode 100644 index 000000000..04da15441 --- /dev/null +++ b/server/middlewares/validators/shared/accounts.ts | |||
@@ -0,0 +1,65 @@ | |||
1 | import { Response } from 'express' | ||
2 | import { AccountModel } from '@server/models/account/account' | ||
3 | import { UserModel } from '@server/models/user/user' | ||
4 | import { MAccountDefault } from '@server/types/models' | ||
5 | import { HttpStatusCode } from '@shared/core-utils' | ||
6 | |||
7 | function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) { | ||
8 | const promise = AccountModel.load(parseInt(id + '', 10)) | ||
9 | |||
10 | return doesAccountExist(promise, res, sendNotFound) | ||
11 | } | ||
12 | |||
13 | function doesLocalAccountNameExist (name: string, res: Response, sendNotFound = true) { | ||
14 | const promise = AccountModel.loadLocalByName(name) | ||
15 | |||
16 | return doesAccountExist(promise, res, sendNotFound) | ||
17 | } | ||
18 | |||
19 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { | ||
20 | const promise = AccountModel.loadByNameWithHost(nameWithDomain) | ||
21 | |||
22 | return doesAccountExist(promise, res, sendNotFound) | ||
23 | } | ||
24 | |||
25 | async function doesAccountExist (p: Promise<MAccountDefault>, res: Response, sendNotFound: boolean) { | ||
26 | const account = await p | ||
27 | |||
28 | if (!account) { | ||
29 | if (sendNotFound === true) { | ||
30 | res.fail({ | ||
31 | status: HttpStatusCode.NOT_FOUND_404, | ||
32 | message: 'Account not found' | ||
33 | }) | ||
34 | } | ||
35 | return false | ||
36 | } | ||
37 | |||
38 | res.locals.account = account | ||
39 | return true | ||
40 | } | ||
41 | |||
42 | async function doesUserFeedTokenCorrespond (id: number, token: string, res: Response) { | ||
43 | const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10)) | ||
44 | |||
45 | if (token !== user.feedToken) { | ||
46 | res.fail({ | ||
47 | status: HttpStatusCode.FORBIDDEN_403, | ||
48 | message: 'User and token mismatch' | ||
49 | }) | ||
50 | return false | ||
51 | } | ||
52 | |||
53 | res.locals.user = user | ||
54 | return true | ||
55 | } | ||
56 | |||
57 | // --------------------------------------------------------------------------- | ||
58 | |||
59 | export { | ||
60 | doesAccountIdExist, | ||
61 | doesLocalAccountNameExist, | ||
62 | doesAccountNameWithHostExist, | ||
63 | doesAccountExist, | ||
64 | doesUserFeedTokenCorrespond | ||
65 | } | ||
diff --git a/server/middlewares/validators/shared/index.ts b/server/middlewares/validators/shared/index.ts new file mode 100644 index 000000000..fa89d05f2 --- /dev/null +++ b/server/middlewares/validators/shared/index.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | export * from './abuses' | ||
2 | export * from './accounts' | ||
3 | export * from './utils' | ||
4 | export * from './video-blacklists' | ||
5 | export * from './video-captions' | ||
6 | export * from './video-channels' | ||
7 | export * from './video-comments' | ||
8 | export * from './video-imports' | ||
9 | export * from './video-ownerships' | ||
10 | export * from './video-playlists' | ||
11 | export * from './videos' | ||
diff --git a/server/middlewares/validators/utils.ts b/server/middlewares/validators/shared/utils.ts index 4167f6d43..4f08560af 100644 --- a/server/middlewares/validators/utils.ts +++ b/server/middlewares/validators/shared/utils.ts | |||
@@ -1,15 +1,20 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query, validationResult } from 'express-validator' | 2 | import { param, query, validationResult } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | 3 | import { isIdOrUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc' |
4 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 4 | import { logger } from '../../../helpers/logger' |
5 | 5 | ||
6 | function areValidationErrors (req: express.Request, res: express.Response) { | 6 | function areValidationErrors (req: express.Request, res: express.Response) { |
7 | const errors = validationResult(req) | 7 | const errors = validationResult(req) |
8 | 8 | ||
9 | if (!errors.isEmpty()) { | 9 | if (!errors.isEmpty()) { |
10 | logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() }) | 10 | logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() }) |
11 | res.status(HttpStatusCode.BAD_REQUEST_400) | 11 | res.fail({ |
12 | .json({ errors: errors.mapped() }) | 12 | message: 'Incorrect request parameters: ' + Object.keys(errors.mapped()).join(', '), |
13 | instance: req.originalUrl, | ||
14 | data: { | ||
15 | 'invalid-params': errors.mapped() | ||
16 | } | ||
17 | }) | ||
13 | 18 | ||
14 | return true | 19 | return true |
15 | } | 20 | } |
@@ -37,10 +42,24 @@ function createSortableColumns (sortableColumns: string[]) { | |||
37 | return sortableColumns.concat(sortableColumnDesc) | 42 | return sortableColumns.concat(sortableColumnDesc) |
38 | } | 43 | } |
39 | 44 | ||
45 | function isValidVideoIdParam (paramName: string) { | ||
46 | return param(paramName) | ||
47 | .customSanitizer(toCompleteUUID) | ||
48 | .custom(isIdOrUUIDValid).withMessage('Should have a valid video id') | ||
49 | } | ||
50 | |||
51 | function isValidPlaylistIdParam (paramName: string) { | ||
52 | return param(paramName) | ||
53 | .customSanitizer(toCompleteUUID) | ||
54 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id') | ||
55 | } | ||
56 | |||
40 | // --------------------------------------------------------------------------- | 57 | // --------------------------------------------------------------------------- |
41 | 58 | ||
42 | export { | 59 | export { |
43 | areValidationErrors, | 60 | areValidationErrors, |
44 | checkSort, | 61 | checkSort, |
45 | createSortableColumns | 62 | createSortableColumns, |
63 | isValidVideoIdParam, | ||
64 | isValidPlaylistIdParam | ||
46 | } | 65 | } |
diff --git a/server/middlewares/validators/shared/video-blacklists.ts b/server/middlewares/validators/shared/video-blacklists.ts new file mode 100644 index 000000000..01491c10f --- /dev/null +++ b/server/middlewares/validators/shared/video-blacklists.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { Response } from 'express' | ||
2 | import { VideoBlacklistModel } from '@server/models/video/video-blacklist' | ||
3 | import { HttpStatusCode } from '@shared/core-utils' | ||
4 | |||
5 | async function doesVideoBlacklistExist (videoId: number, res: Response) { | ||
6 | const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId) | ||
7 | |||
8 | if (videoBlacklist === null) { | ||
9 | res.fail({ | ||
10 | status: HttpStatusCode.NOT_FOUND_404, | ||
11 | message: 'Blacklisted video not found' | ||
12 | }) | ||
13 | return false | ||
14 | } | ||
15 | |||
16 | res.locals.videoBlacklist = videoBlacklist | ||
17 | return true | ||
18 | } | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | export { | ||
23 | doesVideoBlacklistExist | ||
24 | } | ||
diff --git a/server/middlewares/validators/shared/video-captions.ts b/server/middlewares/validators/shared/video-captions.ts new file mode 100644 index 000000000..80f6c5a52 --- /dev/null +++ b/server/middlewares/validators/shared/video-captions.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { Response } from 'express' | ||
2 | import { VideoCaptionModel } from '@server/models/video/video-caption' | ||
3 | import { MVideoId } from '@server/types/models' | ||
4 | import { HttpStatusCode } from '@shared/core-utils' | ||
5 | |||
6 | async function doesVideoCaptionExist (video: MVideoId, language: string, res: Response) { | ||
7 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) | ||
8 | |||
9 | if (!videoCaption) { | ||
10 | res.fail({ | ||
11 | status: HttpStatusCode.NOT_FOUND_404, | ||
12 | message: 'Video caption not found' | ||
13 | }) | ||
14 | return false | ||
15 | } | ||
16 | |||
17 | res.locals.videoCaption = videoCaption | ||
18 | return true | ||
19 | } | ||
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | export { | ||
24 | doesVideoCaptionExist | ||
25 | } | ||
diff --git a/server/middlewares/validators/shared/video-channels.ts b/server/middlewares/validators/shared/video-channels.ts new file mode 100644 index 000000000..fe2e663b7 --- /dev/null +++ b/server/middlewares/validators/shared/video-channels.ts | |||
@@ -0,0 +1,43 @@ | |||
1 | import * as express from 'express' | ||
2 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
3 | import { MChannelBannerAccountDefault } from '@server/types/models' | ||
4 | import { HttpStatusCode } from '@shared/core-utils' | ||
5 | |||
6 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { | ||
7 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | ||
8 | |||
9 | return processVideoChannelExist(videoChannel, res) | ||
10 | } | ||
11 | |||
12 | async function doesVideoChannelIdExist (id: number, res: express.Response) { | ||
13 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) | ||
14 | |||
15 | return processVideoChannelExist(videoChannel, res) | ||
16 | } | ||
17 | |||
18 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | ||
19 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) | ||
20 | |||
21 | return processVideoChannelExist(videoChannel, res) | ||
22 | } | ||
23 | |||
24 | // --------------------------------------------------------------------------- | ||
25 | |||
26 | export { | ||
27 | doesLocalVideoChannelNameExist, | ||
28 | doesVideoChannelIdExist, | ||
29 | doesVideoChannelNameWithHostExist | ||
30 | } | ||
31 | |||
32 | function processVideoChannelExist (videoChannel: MChannelBannerAccountDefault, res: express.Response) { | ||
33 | if (!videoChannel) { | ||
34 | res.fail({ | ||
35 | status: HttpStatusCode.NOT_FOUND_404, | ||
36 | message: 'Video channel not found' | ||
37 | }) | ||
38 | return false | ||
39 | } | ||
40 | |||
41 | res.locals.videoChannel = videoChannel | ||
42 | return true | ||
43 | } | ||
diff --git a/server/middlewares/validators/shared/video-comments.ts b/server/middlewares/validators/shared/video-comments.ts new file mode 100644 index 000000000..83ea15c98 --- /dev/null +++ b/server/middlewares/validators/shared/video-comments.ts | |||
@@ -0,0 +1,73 @@ | |||
1 | import * as express from 'express' | ||
2 | import { VideoCommentModel } from '@server/models/video/video-comment' | ||
3 | import { MVideoId } from '@server/types/models' | ||
4 | import { HttpStatusCode } from '@shared/core-utils' | ||
5 | |||
6 | async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) { | ||
7 | const id = parseInt(idArg + '', 10) | ||
8 | const videoComment = await VideoCommentModel.loadById(id) | ||
9 | |||
10 | if (!videoComment) { | ||
11 | res.fail({ | ||
12 | status: HttpStatusCode.NOT_FOUND_404, | ||
13 | message: 'Video comment thread not found' | ||
14 | }) | ||
15 | return false | ||
16 | } | ||
17 | |||
18 | if (videoComment.videoId !== video.id) { | ||
19 | res.fail({ message: 'Video comment is not associated to this video.' }) | ||
20 | return false | ||
21 | } | ||
22 | |||
23 | if (videoComment.inReplyToCommentId !== null) { | ||
24 | res.fail({ message: 'Video comment is not a thread.' }) | ||
25 | return false | ||
26 | } | ||
27 | |||
28 | res.locals.videoCommentThread = videoComment | ||
29 | return true | ||
30 | } | ||
31 | |||
32 | async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) { | ||
33 | const id = parseInt(idArg + '', 10) | ||
34 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) | ||
35 | |||
36 | if (!videoComment) { | ||
37 | res.fail({ | ||
38 | status: HttpStatusCode.NOT_FOUND_404, | ||
39 | message: 'Video comment thread not found' | ||
40 | }) | ||
41 | return false | ||
42 | } | ||
43 | |||
44 | if (videoComment.videoId !== video.id) { | ||
45 | res.fail({ message: 'Video comment is not associated to this video.' }) | ||
46 | return false | ||
47 | } | ||
48 | |||
49 | res.locals.videoCommentFull = videoComment | ||
50 | return true | ||
51 | } | ||
52 | |||
53 | async function doesCommentIdExist (idArg: number | string, res: express.Response) { | ||
54 | const id = parseInt(idArg + '', 10) | ||
55 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) | ||
56 | |||
57 | if (!videoComment) { | ||
58 | res.fail({ | ||
59 | status: HttpStatusCode.NOT_FOUND_404, | ||
60 | message: 'Video comment thread not found' | ||
61 | }) | ||
62 | return false | ||
63 | } | ||
64 | |||
65 | res.locals.videoCommentFull = videoComment | ||
66 | return true | ||
67 | } | ||
68 | |||
69 | export { | ||
70 | doesVideoCommentThreadExist, | ||
71 | doesVideoCommentExist, | ||
72 | doesCommentIdExist | ||
73 | } | ||
diff --git a/server/middlewares/validators/shared/video-imports.ts b/server/middlewares/validators/shared/video-imports.ts new file mode 100644 index 000000000..0f984bc17 --- /dev/null +++ b/server/middlewares/validators/shared/video-imports.ts | |||
@@ -0,0 +1,22 @@ | |||
1 | import * as express from 'express' | ||
2 | import { VideoImportModel } from '@server/models/video/video-import' | ||
3 | import { HttpStatusCode } from '@shared/core-utils' | ||
4 | |||
5 | async function doesVideoImportExist (id: number, res: express.Response) { | ||
6 | const videoImport = await VideoImportModel.loadAndPopulateVideo(id) | ||
7 | |||
8 | if (!videoImport) { | ||
9 | res.fail({ | ||
10 | status: HttpStatusCode.NOT_FOUND_404, | ||
11 | message: 'Video import not found' | ||
12 | }) | ||
13 | return false | ||
14 | } | ||
15 | |||
16 | res.locals.videoImport = videoImport | ||
17 | return true | ||
18 | } | ||
19 | |||
20 | export { | ||
21 | doesVideoImportExist | ||
22 | } | ||
diff --git a/server/middlewares/validators/shared/video-ownerships.ts b/server/middlewares/validators/shared/video-ownerships.ts new file mode 100644 index 000000000..fc27006ce --- /dev/null +++ b/server/middlewares/validators/shared/video-ownerships.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import * as express from 'express' | ||
2 | import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership' | ||
3 | import { HttpStatusCode } from '@shared/core-utils' | ||
4 | |||
5 | async function doesChangeVideoOwnershipExist (idArg: number | string, res: express.Response) { | ||
6 | const id = parseInt(idArg + '', 10) | ||
7 | const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) | ||
8 | |||
9 | if (!videoChangeOwnership) { | ||
10 | res.fail({ | ||
11 | status: HttpStatusCode.NOT_FOUND_404, | ||
12 | message: 'Video change ownership not found' | ||
13 | }) | ||
14 | return false | ||
15 | } | ||
16 | |||
17 | res.locals.videoChangeOwnership = videoChangeOwnership | ||
18 | |||
19 | return true | ||
20 | } | ||
21 | |||
22 | export { | ||
23 | doesChangeVideoOwnershipExist | ||
24 | } | ||
diff --git a/server/middlewares/validators/shared/video-playlists.ts b/server/middlewares/validators/shared/video-playlists.ts new file mode 100644 index 000000000..d762859a8 --- /dev/null +++ b/server/middlewares/validators/shared/video-playlists.ts | |||
@@ -0,0 +1,39 @@ | |||
1 | import * as express from 'express' | ||
2 | import { VideoPlaylistModel } from '@server/models/video/video-playlist' | ||
3 | import { MVideoPlaylist } from '@server/types/models' | ||
4 | import { HttpStatusCode } from '@shared/core-utils' | ||
5 | |||
6 | export type VideoPlaylistFetchType = 'summary' | 'all' | ||
7 | async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: VideoPlaylistFetchType = 'summary') { | ||
8 | if (fetchType === 'summary') { | ||
9 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) | ||
10 | res.locals.videoPlaylistSummary = videoPlaylist | ||
11 | |||
12 | return handleVideoPlaylist(videoPlaylist, res) | ||
13 | } | ||
14 | |||
15 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) | ||
16 | res.locals.videoPlaylistFull = videoPlaylist | ||
17 | |||
18 | return handleVideoPlaylist(videoPlaylist, res) | ||
19 | } | ||
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | export { | ||
24 | doesVideoPlaylistExist | ||
25 | } | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) { | ||
30 | if (!videoPlaylist) { | ||
31 | res.fail({ | ||
32 | status: HttpStatusCode.NOT_FOUND_404, | ||
33 | message: 'Video playlist not found' | ||
34 | }) | ||
35 | return false | ||
36 | } | ||
37 | |||
38 | return true | ||
39 | } | ||
diff --git a/server/middlewares/validators/shared/videos.ts b/server/middlewares/validators/shared/videos.ts new file mode 100644 index 000000000..2c66c1a3a --- /dev/null +++ b/server/middlewares/validators/shared/videos.ts | |||
@@ -0,0 +1,125 @@ | |||
1 | import { Response } from 'express' | ||
2 | import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' | ||
3 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
4 | import { VideoFileModel } from '@server/models/video/video-file' | ||
5 | import { | ||
6 | MUser, | ||
7 | MUserAccountId, | ||
8 | MVideoAccountLight, | ||
9 | MVideoFormattableDetails, | ||
10 | MVideoFullLight, | ||
11 | MVideoId, | ||
12 | MVideoImmutable, | ||
13 | MVideoThumbnail | ||
14 | } from '@server/types/models' | ||
15 | import { HttpStatusCode } from '@shared/core-utils' | ||
16 | import { UserRight } from '@shared/models' | ||
17 | |||
18 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') { | ||
19 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | ||
20 | |||
21 | const video = await loadVideo(id, fetchType, userId) | ||
22 | |||
23 | if (video === null) { | ||
24 | res.fail({ | ||
25 | status: HttpStatusCode.NOT_FOUND_404, | ||
26 | message: 'Video not found' | ||
27 | }) | ||
28 | return false | ||
29 | } | ||
30 | |||
31 | switch (fetchType) { | ||
32 | case 'for-api': | ||
33 | res.locals.videoAPI = video as MVideoFormattableDetails | ||
34 | break | ||
35 | |||
36 | case 'all': | ||
37 | res.locals.videoAll = video as MVideoFullLight | ||
38 | break | ||
39 | |||
40 | case 'only-immutable-attributes': | ||
41 | res.locals.onlyImmutableVideo = video as MVideoImmutable | ||
42 | break | ||
43 | |||
44 | case 'id': | ||
45 | res.locals.videoId = video as MVideoId | ||
46 | break | ||
47 | |||
48 | case 'only-video': | ||
49 | res.locals.onlyVideo = video as MVideoThumbnail | ||
50 | break | ||
51 | } | ||
52 | |||
53 | return true | ||
54 | } | ||
55 | |||
56 | async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) { | ||
57 | if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) { | ||
58 | res.fail({ | ||
59 | status: HttpStatusCode.NOT_FOUND_404, | ||
60 | message: 'VideoFile matching Video not found' | ||
61 | }) | ||
62 | return false | ||
63 | } | ||
64 | |||
65 | return true | ||
66 | } | ||
67 | |||
68 | async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { | ||
69 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) | ||
70 | |||
71 | if (videoChannel === null) { | ||
72 | res.fail({ message: 'Unknown video "video channel" for this instance.' }) | ||
73 | return false | ||
74 | } | ||
75 | |||
76 | // Don't check account id if the user can update any video | ||
77 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { | ||
78 | res.locals.videoChannel = videoChannel | ||
79 | return true | ||
80 | } | ||
81 | |||
82 | if (videoChannel.Account.id !== user.Account.id) { | ||
83 | res.fail({ | ||
84 | message: 'Unknown video "video channel" for this account.' | ||
85 | }) | ||
86 | return false | ||
87 | } | ||
88 | |||
89 | res.locals.videoChannel = videoChannel | ||
90 | return true | ||
91 | } | ||
92 | |||
93 | function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { | ||
94 | // Retrieve the user who did the request | ||
95 | if (onlyOwned && video.isOwned() === false) { | ||
96 | res.fail({ | ||
97 | status: HttpStatusCode.FORBIDDEN_403, | ||
98 | message: 'Cannot manage a video of another server.' | ||
99 | }) | ||
100 | return false | ||
101 | } | ||
102 | |||
103 | // Check if the user can delete the video | ||
104 | // The user can delete it if he has the right | ||
105 | // Or if s/he is the video's account | ||
106 | const account = video.VideoChannel.Account | ||
107 | if (user.hasRight(right) === false && account.userId !== user.id) { | ||
108 | res.fail({ | ||
109 | status: HttpStatusCode.FORBIDDEN_403, | ||
110 | message: 'Cannot manage a video of another user.' | ||
111 | }) | ||
112 | return false | ||
113 | } | ||
114 | |||
115 | return true | ||
116 | } | ||
117 | |||
118 | // --------------------------------------------------------------------------- | ||
119 | |||
120 | export { | ||
121 | doesVideoChannelOfAccountExist, | ||
122 | doesVideoExist, | ||
123 | doesVideoFileOfVideoExist, | ||
124 | checkUserCanManageVideo | ||
125 | } | ||
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index beecc155b..473010460 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { SORTABLE_COLUMNS } from '../../initializers/constants' | 1 | import { SORTABLE_COLUMNS } from '../../initializers/constants' |
2 | import { checkSort, createSortableColumns } from './utils' | 2 | import { checkSort, createSortableColumns } from './shared' |
3 | 3 | ||
4 | // Initialize constants here for better performances | 4 | // Initialize constants here for better performances |
5 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) | 5 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) |
@@ -9,6 +9,7 @@ const SORTABLE_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ABUSES) | |||
9 | const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) | 9 | const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) |
10 | const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH) | 10 | const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH) |
11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) | 11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) |
12 | const SORTABLE_VIDEO_PLAYLISTS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS_SEARCH) | ||
12 | const SORTABLE_VIDEO_IMPORTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_IMPORTS) | 13 | const SORTABLE_VIDEO_IMPORTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_IMPORTS) |
13 | const SORTABLE_VIDEO_COMMENTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) | 14 | const SORTABLE_VIDEO_COMMENTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) |
14 | const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) | 15 | const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) |
@@ -34,6 +35,7 @@ const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) | |||
34 | const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS) | 35 | const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS) |
35 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) | 36 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) |
36 | const videoChannelsSearchSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS) | 37 | const videoChannelsSearchSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS) |
38 | const videoPlaylistsSearchSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_SEARCH_COLUMNS) | ||
37 | const videoCommentsValidator = checkSort(SORTABLE_VIDEO_COMMENTS_COLUMNS) | 39 | const videoCommentsValidator = checkSort(SORTABLE_VIDEO_COMMENTS_COLUMNS) |
38 | const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS) | 40 | const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS) |
39 | const videoRatesSortValidator = checkSort(SORTABLE_VIDEO_RATES_COLUMNS) | 41 | const videoRatesSortValidator = checkSort(SORTABLE_VIDEO_RATES_COLUMNS) |
@@ -75,5 +77,6 @@ export { | |||
75 | userNotificationsSortValidator, | 77 | userNotificationsSortValidator, |
76 | videoPlaylistsSortValidator, | 78 | videoPlaylistsSortValidator, |
77 | videoRedundanciesSortValidator, | 79 | videoRedundanciesSortValidator, |
80 | videoPlaylistsSearchSortValidator, | ||
78 | pluginsSortValidator | 81 | pluginsSortValidator |
79 | } | 82 | } |
diff --git a/server/middlewares/validators/themes.ts b/server/middlewares/validators/themes.ts index a726a567b..d4716257f 100644 --- a/server/middlewares/validators/themes.ts +++ b/server/middlewares/validators/themes.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { param } from 'express-validator' | 2 | import { param } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | 3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
4 | import { areValidationErrors } from './utils' | 4 | import { isSafePath } from '../../helpers/custom-validators/misc' |
5 | import { isPluginNameValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' | 5 | import { isPluginNameValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' |
6 | import { logger } from '../../helpers/logger' | ||
6 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 7 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
7 | import { isSafePath } from '../../helpers/custom-validators/misc' | 8 | import { areValidationErrors } from './shared' |
8 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
9 | 9 | ||
10 | const serveThemeCSSValidator = [ | 10 | const serveThemeCSSValidator = [ |
11 | param('themeName').custom(isPluginNameValid).withMessage('Should have a valid theme name'), | 11 | param('themeName').custom(isPluginNameValid).withMessage('Should have a valid theme name'), |
@@ -20,11 +20,17 @@ const serveThemeCSSValidator = [ | |||
20 | const theme = PluginManager.Instance.getRegisteredThemeByShortName(req.params.themeName) | 20 | const theme = PluginManager.Instance.getRegisteredThemeByShortName(req.params.themeName) |
21 | 21 | ||
22 | if (!theme || theme.version !== req.params.themeVersion) { | 22 | if (!theme || theme.version !== req.params.themeVersion) { |
23 | return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 23 | return res.fail({ |
24 | status: HttpStatusCode.NOT_FOUND_404, | ||
25 | message: 'No theme named ' + req.params.themeName + ' was found with version ' + req.params.themeVersion | ||
26 | }) | ||
24 | } | 27 | } |
25 | 28 | ||
26 | if (theme.css.includes(req.params.staticEndpoint) === false) { | 29 | if (theme.css.includes(req.params.staticEndpoint) === false) { |
27 | return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 30 | return res.fail({ |
31 | status: HttpStatusCode.NOT_FOUND_404, | ||
32 | message: 'No static endpoint was found for this theme' | ||
33 | }) | ||
28 | } | 34 | } |
29 | 35 | ||
30 | res.locals.registeredPlugin = theme | 36 | res.locals.registeredPlugin = theme |
diff --git a/server/middlewares/validators/user-history.ts b/server/middlewares/validators/user-history.ts index 058bf7758..1db0d9b26 100644 --- a/server/middlewares/validators/user-history.ts +++ b/server/middlewares/validators/user-history.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, query } from 'express-validator' | 2 | import { body, query } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | ||
4 | import { areValidationErrors } from './utils' | ||
5 | import { exists, isDateValid } from '../../helpers/custom-validators/misc' | 3 | import { exists, isDateValid } from '../../helpers/custom-validators/misc' |
4 | import { logger } from '../../helpers/logger' | ||
5 | import { areValidationErrors } from './shared' | ||
6 | 6 | ||
7 | const userHistoryListValidator = [ | 7 | const userHistoryListValidator = [ |
8 | query('search') | 8 | query('search') |
@@ -21,7 +21,7 @@ const userHistoryListValidator = [ | |||
21 | const userHistoryRemoveValidator = [ | 21 | const userHistoryRemoveValidator = [ |
22 | body('beforeDate') | 22 | body('beforeDate') |
23 | .optional() | 23 | .optional() |
24 | .custom(isDateValid).withMessage('Should have a valid before date'), | 24 | .custom(isDateValid).withMessage('Should have a before date that conforms to ISO 8601'), |
25 | 25 | ||
26 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 26 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
27 | logger.debug('Checking userHistoryRemoveValidator parameters', { parameters: req.body }) | 27 | logger.debug('Checking userHistoryRemoveValidator parameters', { parameters: req.body }) |
diff --git a/server/middlewares/validators/user-notifications.ts b/server/middlewares/validators/user-notifications.ts index 21a7be08d..2f8e7686e 100644 --- a/server/middlewares/validators/user-notifications.ts +++ b/server/middlewares/validators/user-notifications.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, query } from 'express-validator' | 2 | import { body, query } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | ||
4 | import { areValidationErrors } from './utils' | ||
5 | import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' | ||
6 | import { isNotEmptyIntArray, toBooleanOrNull } from '../../helpers/custom-validators/misc' | 3 | import { isNotEmptyIntArray, toBooleanOrNull } from '../../helpers/custom-validators/misc' |
4 | import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' | ||
5 | import { logger } from '../../helpers/logger' | ||
6 | import { areValidationErrors } from './shared' | ||
7 | 7 | ||
8 | const listUserNotificationsValidator = [ | 8 | const listUserNotificationsValidator = [ |
9 | query('unread') | 9 | query('unread') |
diff --git a/server/middlewares/validators/user-subscriptions.ts b/server/middlewares/validators/user-subscriptions.ts index 0d0c8ccbf..ab7962923 100644 --- a/server/middlewares/validators/user-subscriptions.ts +++ b/server/middlewares/validators/user-subscriptions.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | 3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
4 | import { areValidationErrors } from './utils' | ||
5 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | ||
6 | import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | 4 | import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' |
7 | import { toArray } from '../../helpers/custom-validators/misc' | 5 | import { toArray } from '../../helpers/custom-validators/misc' |
6 | import { logger } from '../../helpers/logger' | ||
8 | import { WEBSERVER } from '../../initializers/constants' | 7 | import { WEBSERVER } from '../../initializers/constants' |
9 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 8 | import { ActorFollowModel } from '../../models/actor/actor-follow' |
9 | import { areValidationErrors } from './shared' | ||
10 | 10 | ||
11 | const userSubscriptionListValidator = [ | 11 | const userSubscriptionListValidator = [ |
12 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), | 12 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), |
@@ -61,11 +61,10 @@ const userSubscriptionGetValidator = [ | |||
61 | const subscription = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(user.Account.Actor.id, name, host) | 61 | const subscription = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(user.Account.Actor.id, name, host) |
62 | 62 | ||
63 | if (!subscription || !subscription.ActorFollowing.VideoChannel) { | 63 | if (!subscription || !subscription.ActorFollowing.VideoChannel) { |
64 | return res | 64 | return res.fail({ |
65 | .status(HttpStatusCode.NOT_FOUND_404) | 65 | status: HttpStatusCode.NOT_FOUND_404, |
66 | .json({ | 66 | message: `Subscription ${req.params.uri} not found.` |
67 | error: `Subscription ${req.params.uri} not found.` | 67 | }) |
68 | }) | ||
69 | } | 68 | } |
70 | 69 | ||
71 | res.locals.subscription = subscription | 70 | res.locals.subscription = subscription |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 37119e279..698d7d814 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -7,7 +7,7 @@ import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-code | |||
7 | import { UserRole } from '../../../shared/models/users' | 7 | import { UserRole } from '../../../shared/models/users' |
8 | import { UserRegister } from '../../../shared/models/users/user-register.model' | 8 | import { UserRegister } from '../../../shared/models/users/user-register.model' |
9 | import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' | 9 | import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' |
10 | import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 10 | import { toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
11 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | 11 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' |
12 | import { | 12 | import { |
13 | isNoInstanceConfigWarningModal, | 13 | isNoInstanceConfigWarningModal, |
@@ -30,13 +30,12 @@ import { | |||
30 | } from '../../helpers/custom-validators/users' | 30 | } from '../../helpers/custom-validators/users' |
31 | import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' | 31 | import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' |
32 | import { logger } from '../../helpers/logger' | 32 | import { logger } from '../../helpers/logger' |
33 | import { doesVideoExist } from '../../helpers/middlewares' | ||
34 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | ||
35 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' | 33 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' |
36 | import { Redis } from '../../lib/redis' | 34 | import { Redis } from '../../lib/redis' |
37 | import { UserModel } from '../../models/account/user' | 35 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup' |
38 | import { ActorModel } from '../../models/activitypub/actor' | 36 | import { ActorModel } from '../../models/actor/actor' |
39 | import { areValidationErrors } from './utils' | 37 | import { UserModel } from '../../models/user/user' |
38 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared' | ||
40 | 39 | ||
41 | const usersListValidator = [ | 40 | const usersListValidator = [ |
42 | query('blocked') | 41 | query('blocked') |
@@ -73,23 +72,23 @@ const usersAddValidator = [ | |||
73 | 72 | ||
74 | const authUser = res.locals.oauth.token.User | 73 | const authUser = res.locals.oauth.token.User |
75 | if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) { | 74 | if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) { |
76 | return res | 75 | return res.fail({ |
77 | .status(HttpStatusCode.FORBIDDEN_403) | 76 | status: HttpStatusCode.FORBIDDEN_403, |
78 | .json({ error: 'You can only create users (and not administrators or moderators)' }) | 77 | message: 'You can only create users (and not administrators or moderators)' |
78 | }) | ||
79 | } | 79 | } |
80 | 80 | ||
81 | if (req.body.channelName) { | 81 | if (req.body.channelName) { |
82 | if (req.body.channelName === req.body.username) { | 82 | if (req.body.channelName === req.body.username) { |
83 | return res | 83 | return res.fail({ message: 'Channel name cannot be the same as user username.' }) |
84 | .status(HttpStatusCode.BAD_REQUEST_400) | ||
85 | .json({ error: 'Channel name cannot be the same as user username.' }) | ||
86 | } | 84 | } |
87 | 85 | ||
88 | const existing = await ActorModel.loadLocalByName(req.body.channelName) | 86 | const existing = await ActorModel.loadLocalByName(req.body.channelName) |
89 | if (existing) { | 87 | if (existing) { |
90 | return res | 88 | return res.fail({ |
91 | .status(HttpStatusCode.CONFLICT_409) | 89 | status: HttpStatusCode.CONFLICT_409, |
92 | .json({ error: `Channel with name ${req.body.channelName} already exists.` }) | 90 | message: `Channel with name ${req.body.channelName} already exists.` |
91 | }) | ||
93 | } | 92 | } |
94 | } | 93 | } |
95 | 94 | ||
@@ -121,20 +120,19 @@ const usersRegisterValidator = [ | |||
121 | const body: UserRegister = req.body | 120 | const body: UserRegister = req.body |
122 | if (body.channel) { | 121 | if (body.channel) { |
123 | if (!body.channel.name || !body.channel.displayName) { | 122 | if (!body.channel.name || !body.channel.displayName) { |
124 | return res | 123 | return res.fail({ message: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' }) |
125 | .status(HttpStatusCode.BAD_REQUEST_400) | ||
126 | .json({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' }) | ||
127 | } | 124 | } |
128 | 125 | ||
129 | if (body.channel.name === body.username) { | 126 | if (body.channel.name === body.username) { |
130 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 127 | return res.fail({ message: 'Channel name cannot be the same as user username.' }) |
131 | .json({ error: 'Channel name cannot be the same as user username.' }) | ||
132 | } | 128 | } |
133 | 129 | ||
134 | const existing = await ActorModel.loadLocalByName(body.channel.name) | 130 | const existing = await ActorModel.loadLocalByName(body.channel.name) |
135 | if (existing) { | 131 | if (existing) { |
136 | return res.status(HttpStatusCode.CONFLICT_409) | 132 | return res.fail({ |
137 | .json({ error: `Channel with name ${body.channel.name} already exists.` }) | 133 | status: HttpStatusCode.CONFLICT_409, |
134 | message: `Channel with name ${body.channel.name} already exists.` | ||
135 | }) | ||
138 | } | 136 | } |
139 | } | 137 | } |
140 | 138 | ||
@@ -153,8 +151,7 @@ const usersRemoveValidator = [ | |||
153 | 151 | ||
154 | const user = res.locals.user | 152 | const user = res.locals.user |
155 | if (user.username === 'root') { | 153 | if (user.username === 'root') { |
156 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 154 | return res.fail({ message: 'Cannot remove the root user' }) |
157 | .json({ error: 'Cannot remove the root user' }) | ||
158 | } | 155 | } |
159 | 156 | ||
160 | return next() | 157 | return next() |
@@ -173,8 +170,7 @@ const usersBlockingValidator = [ | |||
173 | 170 | ||
174 | const user = res.locals.user | 171 | const user = res.locals.user |
175 | if (user.username === 'root') { | 172 | if (user.username === 'root') { |
176 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 173 | return res.fail({ message: 'Cannot block the root user' }) |
177 | .json({ error: 'Cannot block the root user' }) | ||
178 | } | 174 | } |
179 | 175 | ||
180 | return next() | 176 | return next() |
@@ -185,9 +181,7 @@ const deleteMeValidator = [ | |||
185 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 181 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
186 | const user = res.locals.oauth.token.User | 182 | const user = res.locals.oauth.token.User |
187 | if (user.username === 'root') { | 183 | if (user.username === 'root') { |
188 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 184 | return res.fail({ message: 'You cannot delete your root account.' }) |
189 | .json({ error: 'You cannot delete your root account.' }) | ||
190 | .end() | ||
191 | } | 185 | } |
192 | 186 | ||
193 | return next() | 187 | return next() |
@@ -217,8 +211,7 @@ const usersUpdateValidator = [ | |||
217 | 211 | ||
218 | const user = res.locals.user | 212 | const user = res.locals.user |
219 | if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) { | 213 | if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) { |
220 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 214 | return res.fail({ message: 'Cannot change root role.' }) |
221 | .json({ error: 'Cannot change root role.' }) | ||
222 | } | 215 | } |
223 | 216 | ||
224 | return next() | 217 | return next() |
@@ -273,18 +266,18 @@ const usersUpdateMeValidator = [ | |||
273 | 266 | ||
274 | if (req.body.password || req.body.email) { | 267 | if (req.body.password || req.body.email) { |
275 | if (user.pluginAuth !== null) { | 268 | if (user.pluginAuth !== null) { |
276 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 269 | return res.fail({ message: 'You cannot update your email or password that is associated with an external auth system.' }) |
277 | .json({ error: 'You cannot update your email or password that is associated with an external auth system.' }) | ||
278 | } | 270 | } |
279 | 271 | ||
280 | if (!req.body.currentPassword) { | 272 | if (!req.body.currentPassword) { |
281 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 273 | return res.fail({ message: 'currentPassword parameter is missing.' }) |
282 | .json({ error: 'currentPassword parameter is missing.' }) | ||
283 | } | 274 | } |
284 | 275 | ||
285 | if (await user.isPasswordMatch(req.body.currentPassword) !== true) { | 276 | if (await user.isPasswordMatch(req.body.currentPassword) !== true) { |
286 | return res.status(HttpStatusCode.UNAUTHORIZED_401) | 277 | return res.fail({ |
287 | .json({ error: 'currentPassword is invalid.' }) | 278 | status: HttpStatusCode.UNAUTHORIZED_401, |
279 | message: 'currentPassword is invalid.' | ||
280 | }) | ||
288 | } | 281 | } |
289 | } | 282 | } |
290 | 283 | ||
@@ -309,7 +302,7 @@ const usersGetValidator = [ | |||
309 | ] | 302 | ] |
310 | 303 | ||
311 | const usersVideoRatingValidator = [ | 304 | const usersVideoRatingValidator = [ |
312 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | 305 | isValidVideoIdParam('videoId'), |
313 | 306 | ||
314 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 307 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
315 | logger.debug('Checking usersVideoRating parameters', { parameters: req.params }) | 308 | logger.debug('Checking usersVideoRating parameters', { parameters: req.params }) |
@@ -335,8 +328,10 @@ const ensureUserRegistrationAllowed = [ | |||
335 | ) | 328 | ) |
336 | 329 | ||
337 | if (allowedResult.allowed === false) { | 330 | if (allowedResult.allowed === false) { |
338 | return res.status(HttpStatusCode.FORBIDDEN_403) | 331 | return res.fail({ |
339 | .json({ error: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.' }) | 332 | status: HttpStatusCode.FORBIDDEN_403, |
333 | message: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.' | ||
334 | }) | ||
340 | } | 335 | } |
341 | 336 | ||
342 | return next() | 337 | return next() |
@@ -348,8 +343,10 @@ const ensureUserRegistrationAllowedForIP = [ | |||
348 | const allowed = isSignupAllowedForCurrentIP(req.ip) | 343 | const allowed = isSignupAllowedForCurrentIP(req.ip) |
349 | 344 | ||
350 | if (allowed === false) { | 345 | if (allowed === false) { |
351 | return res.status(HttpStatusCode.FORBIDDEN_403) | 346 | return res.fail({ |
352 | .json({ error: 'You are not on a network authorized for registration.' }) | 347 | status: HttpStatusCode.FORBIDDEN_403, |
348 | message: 'You are not on a network authorized for registration.' | ||
349 | }) | ||
353 | } | 350 | } |
354 | 351 | ||
355 | return next() | 352 | return next() |
@@ -390,9 +387,10 @@ const usersResetPasswordValidator = [ | |||
390 | const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id) | 387 | const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id) |
391 | 388 | ||
392 | if (redisVerificationString !== req.body.verificationString) { | 389 | if (redisVerificationString !== req.body.verificationString) { |
393 | return res | 390 | return res.fail({ |
394 | .status(HttpStatusCode.FORBIDDEN_403) | 391 | status: HttpStatusCode.FORBIDDEN_403, |
395 | .json({ error: 'Invalid verification string.' }) | 392 | message: 'Invalid verification string.' |
393 | }) | ||
396 | } | 394 | } |
397 | 395 | ||
398 | return next() | 396 | return next() |
@@ -437,9 +435,10 @@ const usersVerifyEmailValidator = [ | |||
437 | const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id) | 435 | const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id) |
438 | 436 | ||
439 | if (redisVerificationString !== req.body.verificationString) { | 437 | if (redisVerificationString !== req.body.verificationString) { |
440 | return res | 438 | return res.fail({ |
441 | .status(HttpStatusCode.FORBIDDEN_403) | 439 | status: HttpStatusCode.FORBIDDEN_403, |
442 | .json({ error: 'Invalid verification string.' }) | 440 | message: 'Invalid verification string.' |
441 | }) | ||
443 | } | 442 | } |
444 | 443 | ||
445 | return next() | 444 | return next() |
@@ -455,8 +454,10 @@ const ensureAuthUserOwnsAccountValidator = [ | |||
455 | const user = res.locals.oauth.token.User | 454 | const user = res.locals.oauth.token.User |
456 | 455 | ||
457 | if (res.locals.account.id !== user.Account.id) { | 456 | if (res.locals.account.id !== user.Account.id) { |
458 | return res.status(HttpStatusCode.FORBIDDEN_403) | 457 | return res.fail({ |
459 | .json({ error: 'Only owner can access ratings list.' }) | 458 | status: HttpStatusCode.FORBIDDEN_403, |
459 | message: 'Only owner can access ratings list.' | ||
460 | }) | ||
460 | } | 461 | } |
461 | 462 | ||
462 | return next() | 463 | return next() |
@@ -471,8 +472,10 @@ const ensureCanManageUser = [ | |||
471 | if (authUser.role === UserRole.ADMINISTRATOR) return next() | 472 | if (authUser.role === UserRole.ADMINISTRATOR) return next() |
472 | if (authUser.role === UserRole.MODERATOR && onUser.role === UserRole.USER) return next() | 473 | if (authUser.role === UserRole.MODERATOR && onUser.role === UserRole.USER) return next() |
473 | 474 | ||
474 | return res.status(HttpStatusCode.FORBIDDEN_403) | 475 | return res.fail({ |
475 | .json({ error: 'A moderator can only manager users.' }) | 476 | status: HttpStatusCode.FORBIDDEN_403, |
477 | message: 'A moderator can only manager users.' | ||
478 | }) | ||
476 | } | 479 | } |
477 | ] | 480 | ] |
478 | 481 | ||
@@ -515,15 +518,19 @@ async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: | |||
515 | const user = await UserModel.loadByUsernameOrEmail(username, email) | 518 | const user = await UserModel.loadByUsernameOrEmail(username, email) |
516 | 519 | ||
517 | if (user) { | 520 | if (user) { |
518 | res.status(HttpStatusCode.CONFLICT_409) | 521 | res.fail({ |
519 | .json({ error: 'User with this username or email already exists.' }) | 522 | status: HttpStatusCode.CONFLICT_409, |
523 | message: 'User with this username or email already exists.' | ||
524 | }) | ||
520 | return false | 525 | return false |
521 | } | 526 | } |
522 | 527 | ||
523 | const actor = await ActorModel.loadLocalByName(username) | 528 | const actor = await ActorModel.loadLocalByName(username) |
524 | if (actor) { | 529 | if (actor) { |
525 | res.status(HttpStatusCode.CONFLICT_409) | 530 | res.fail({ |
526 | .json({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' }) | 531 | status: HttpStatusCode.CONFLICT_409, |
532 | message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' | ||
533 | }) | ||
527 | return false | 534 | return false |
528 | } | 535 | } |
529 | 536 | ||
@@ -535,14 +542,15 @@ async function checkUserExist (finder: () => Promise<MUserDefault>, res: express | |||
535 | 542 | ||
536 | if (!user) { | 543 | if (!user) { |
537 | if (abortResponse === true) { | 544 | if (abortResponse === true) { |
538 | res.status(HttpStatusCode.NOT_FOUND_404) | 545 | res.fail({ |
539 | .json({ error: 'User not found' }) | 546 | status: HttpStatusCode.NOT_FOUND_404, |
547 | message: 'User not found' | ||
548 | }) | ||
540 | } | 549 | } |
541 | 550 | ||
542 | return false | 551 | return false |
543 | } | 552 | } |
544 | 553 | ||
545 | res.locals.user = user | 554 | res.locals.user = user |
546 | |||
547 | return true | 555 | return true |
548 | } | 556 | } |
diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts index 1eabada0a..369c2c9b6 100644 --- a/server/middlewares/validators/videos/index.ts +++ b/server/middlewares/validators/videos/index.ts | |||
@@ -3,6 +3,8 @@ export * from './video-captions' | |||
3 | export * from './video-channels' | 3 | export * from './video-channels' |
4 | export * from './video-comments' | 4 | export * from './video-comments' |
5 | export * from './video-imports' | 5 | export * from './video-imports' |
6 | export * from './video-live' | ||
7 | export * from './video-ownership-changes' | ||
6 | export * from './video-watch' | 8 | export * from './video-watch' |
7 | export * from './video-rates' | 9 | export * from './video-rates' |
8 | export * from './video-shares' | 10 | export * from './video-shares' |
diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts index 88c788a43..21141d84d 100644 --- a/server/middlewares/validators/videos/video-blacklist.ts +++ b/server/middlewares/validators/videos/video-blacklist.ts | |||
@@ -1,14 +1,13 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, query } from 'express-validator' |
3 | import { isBooleanValid, isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' | 3 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
4 | import { isBooleanValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' | ||
4 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../../helpers/custom-validators/video-blacklist' | 5 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../../helpers/custom-validators/video-blacklist' |
5 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
6 | import { doesVideoBlacklistExist, doesVideoExist } from '../../../helpers/middlewares' | 7 | import { areValidationErrors, doesVideoBlacklistExist, doesVideoExist, isValidVideoIdParam } from '../shared' |
7 | import { areValidationErrors } from '../utils' | ||
8 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
9 | 8 | ||
10 | const videosBlacklistRemoveValidator = [ | 9 | const videosBlacklistRemoveValidator = [ |
11 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 10 | isValidVideoIdParam('videoId'), |
12 | 11 | ||
13 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 12 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
14 | logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) | 13 | logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) |
@@ -22,7 +21,8 @@ const videosBlacklistRemoveValidator = [ | |||
22 | ] | 21 | ] |
23 | 22 | ||
24 | const videosBlacklistAddValidator = [ | 23 | const videosBlacklistAddValidator = [ |
25 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 24 | isValidVideoIdParam('videoId'), |
25 | |||
26 | body('unfederate') | 26 | body('unfederate') |
27 | .optional() | 27 | .optional() |
28 | .customSanitizer(toBooleanOrNull) | 28 | .customSanitizer(toBooleanOrNull) |
@@ -39,10 +39,10 @@ const videosBlacklistAddValidator = [ | |||
39 | 39 | ||
40 | const video = res.locals.videoAll | 40 | const video = res.locals.videoAll |
41 | if (req.body.unfederate === true && video.remote === true) { | 41 | if (req.body.unfederate === true && video.remote === true) { |
42 | return res | 42 | return res.fail({ |
43 | .status(HttpStatusCode.CONFLICT_409) | 43 | status: HttpStatusCode.CONFLICT_409, |
44 | .send({ error: 'You cannot unfederate a remote video.' }) | 44 | message: 'You cannot unfederate a remote video.' |
45 | .end() | 45 | }) |
46 | } | 46 | } |
47 | 47 | ||
48 | return next() | 48 | return next() |
@@ -50,7 +50,8 @@ const videosBlacklistAddValidator = [ | |||
50 | ] | 50 | ] |
51 | 51 | ||
52 | const videosBlacklistUpdateValidator = [ | 52 | const videosBlacklistUpdateValidator = [ |
53 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 53 | isValidVideoIdParam('videoId'), |
54 | |||
54 | body('reason') | 55 | body('reason') |
55 | .optional() | 56 | .optional() |
56 | .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'), | 57 | .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'), |
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts index 872d9c2ab..2946f3e15 100644 --- a/server/middlewares/validators/videos/video-captions.ts +++ b/server/middlewares/validators/videos/video-captions.ts | |||
@@ -1,17 +1,18 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { areValidationErrors } from '../utils' | ||
3 | import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' | ||
4 | import { body, param } from 'express-validator' | 2 | import { body, param } from 'express-validator' |
5 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' | ||
6 | import { UserRight } from '../../../../shared' | 3 | import { UserRight } from '../../../../shared' |
7 | import { logger } from '../../../helpers/logger' | ||
8 | import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' | 4 | import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' |
9 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 5 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
10 | import { checkUserCanManageVideo, doesVideoCaptionExist, doesVideoExist } from '../../../helpers/middlewares' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' | ||
8 | import { areValidationErrors, checkUserCanManageVideo, doesVideoCaptionExist, doesVideoExist, isValidVideoIdParam } from '../shared' | ||
11 | 9 | ||
12 | const addVideoCaptionValidator = [ | 10 | const addVideoCaptionValidator = [ |
13 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | 11 | isValidVideoIdParam('videoId'), |
14 | param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), | 12 | |
13 | param('captionLanguage') | ||
14 | .custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), | ||
15 | |||
15 | body('captionfile') | 16 | body('captionfile') |
16 | .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile')) | 17 | .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile')) |
17 | .withMessage( | 18 | .withMessage( |
@@ -35,8 +36,10 @@ const addVideoCaptionValidator = [ | |||
35 | ] | 36 | ] |
36 | 37 | ||
37 | const deleteVideoCaptionValidator = [ | 38 | const deleteVideoCaptionValidator = [ |
38 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | 39 | isValidVideoIdParam('videoId'), |
39 | param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), | 40 | |
41 | param('captionLanguage') | ||
42 | .custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), | ||
40 | 43 | ||
41 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 44 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
42 | logger.debug('Checking deleteVideoCaption parameters', { parameters: req.params }) | 45 | logger.debug('Checking deleteVideoCaption parameters', { parameters: req.params }) |
@@ -54,7 +57,7 @@ const deleteVideoCaptionValidator = [ | |||
54 | ] | 57 | ] |
55 | 58 | ||
56 | const listVideoCaptionsValidator = [ | 59 | const listVideoCaptionsValidator = [ |
57 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | 60 | isValidVideoIdParam('videoId'), |
58 | 61 | ||
59 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 62 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
60 | logger.debug('Checking listVideoCaptions parameters', { parameters: req.params }) | 63 | logger.debug('Checking listVideoCaptions parameters', { parameters: req.params }) |
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index 2463d281c..e7df185e4 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -3,6 +3,7 @@ import { body, param, query } from 'express-validator' | |||
3 | import { VIDEO_CHANNELS } from '@server/initializers/constants' | 3 | import { VIDEO_CHANNELS } from '@server/initializers/constants' |
4 | import { MChannelAccountDefault, MUser } from '@server/types/models' | 4 | import { MChannelAccountDefault, MUser } from '@server/types/models' |
5 | import { UserRight } from '../../../../shared' | 5 | import { UserRight } from '../../../../shared' |
6 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
6 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' | 7 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' |
7 | import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' | 8 | import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' |
8 | import { | 9 | import { |
@@ -11,11 +12,9 @@ import { | |||
11 | isVideoChannelSupportValid | 12 | isVideoChannelSupportValid |
12 | } from '../../../helpers/custom-validators/video-channels' | 13 | } from '../../../helpers/custom-validators/video-channels' |
13 | import { logger } from '../../../helpers/logger' | 14 | import { logger } from '../../../helpers/logger' |
14 | import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' | 15 | import { ActorModel } from '../../../models/actor/actor' |
15 | import { ActorModel } from '../../../models/activitypub/actor' | ||
16 | import { VideoChannelModel } from '../../../models/video/video-channel' | 16 | import { VideoChannelModel } from '../../../models/video/video-channel' |
17 | import { areValidationErrors } from '../utils' | 17 | import { areValidationErrors, doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../shared' |
18 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
19 | 18 | ||
20 | const videoChannelsAddValidator = [ | 19 | const videoChannelsAddValidator = [ |
21 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), | 20 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), |
@@ -30,17 +29,16 @@ const videoChannelsAddValidator = [ | |||
30 | 29 | ||
31 | const actor = await ActorModel.loadLocalByName(req.body.name) | 30 | const actor = await ActorModel.loadLocalByName(req.body.name) |
32 | if (actor) { | 31 | if (actor) { |
33 | res.status(HttpStatusCode.CONFLICT_409) | 32 | res.fail({ |
34 | .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' }) | 33 | status: HttpStatusCode.CONFLICT_409, |
35 | .end() | 34 | message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' |
35 | }) | ||
36 | return false | 36 | return false |
37 | } | 37 | } |
38 | 38 | ||
39 | const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) | 39 | const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) |
40 | if (count >= VIDEO_CHANNELS.MAX_PER_USER) { | 40 | if (count >= VIDEO_CHANNELS.MAX_PER_USER) { |
41 | res.status(HttpStatusCode.BAD_REQUEST_400) | 41 | res.fail({ message: `You cannot create more than ${VIDEO_CHANNELS.MAX_PER_USER} channels` }) |
42 | .send({ error: `You cannot create more than ${VIDEO_CHANNELS.MAX_PER_USER} channels` }) | ||
43 | .end() | ||
44 | return false | 42 | return false |
45 | } | 43 | } |
46 | 44 | ||
@@ -71,13 +69,17 @@ const videoChannelsUpdateValidator = [ | |||
71 | 69 | ||
72 | // We need to make additional checks | 70 | // We need to make additional checks |
73 | if (res.locals.videoChannel.Actor.isOwned() === false) { | 71 | if (res.locals.videoChannel.Actor.isOwned() === false) { |
74 | return res.status(HttpStatusCode.FORBIDDEN_403) | 72 | return res.fail({ |
75 | .json({ error: 'Cannot update video channel of another server' }) | 73 | status: HttpStatusCode.FORBIDDEN_403, |
74 | message: 'Cannot update video channel of another server' | ||
75 | }) | ||
76 | } | 76 | } |
77 | 77 | ||
78 | if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) { | 78 | if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) { |
79 | return res.status(HttpStatusCode.FORBIDDEN_403) | 79 | return res.fail({ |
80 | .json({ error: 'Cannot update video channel of another user' }) | 80 | status: HttpStatusCode.FORBIDDEN_403, |
81 | message: 'Cannot update video channel of another user' | ||
82 | }) | ||
81 | } | 83 | } |
82 | 84 | ||
83 | return next() | 85 | return next() |
@@ -139,6 +141,18 @@ const videoChannelStatsValidator = [ | |||
139 | } | 141 | } |
140 | ] | 142 | ] |
141 | 143 | ||
144 | const videoChannelsListValidator = [ | ||
145 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), | ||
146 | |||
147 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
148 | logger.debug('Checking video channels search query', { parameters: req.query }) | ||
149 | |||
150 | if (areValidationErrors(req, res)) return | ||
151 | |||
152 | return next() | ||
153 | } | ||
154 | ] | ||
155 | |||
142 | // --------------------------------------------------------------------------- | 156 | // --------------------------------------------------------------------------- |
143 | 157 | ||
144 | export { | 158 | export { |
@@ -146,6 +160,7 @@ export { | |||
146 | videoChannelsUpdateValidator, | 160 | videoChannelsUpdateValidator, |
147 | videoChannelsRemoveValidator, | 161 | videoChannelsRemoveValidator, |
148 | videoChannelsNameWithHostValidator, | 162 | videoChannelsNameWithHostValidator, |
163 | videoChannelsListValidator, | ||
149 | localVideoChannelValidator, | 164 | localVideoChannelValidator, |
150 | videoChannelStatsValidator | 165 | videoChannelStatsValidator |
151 | } | 166 | } |
@@ -154,10 +169,10 @@ export { | |||
154 | 169 | ||
155 | function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) { | 170 | function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) { |
156 | if (videoChannel.Actor.isOwned() === false) { | 171 | if (videoChannel.Actor.isOwned() === false) { |
157 | res.status(HttpStatusCode.FORBIDDEN_403) | 172 | res.fail({ |
158 | .json({ error: 'Cannot remove video channel of another server.' }) | 173 | status: HttpStatusCode.FORBIDDEN_403, |
159 | .end() | 174 | message: 'Cannot remove video channel of another server.' |
160 | 175 | }) | |
161 | return false | 176 | return false |
162 | } | 177 | } |
163 | 178 | ||
@@ -165,10 +180,10 @@ function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAcco | |||
165 | // The user can delete it if s/he is an admin | 180 | // The user can delete it if s/he is an admin |
166 | // Or if s/he is the video channel's account | 181 | // Or if s/he is the video channel's account |
167 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) { | 182 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) { |
168 | res.status(HttpStatusCode.FORBIDDEN_403) | 183 | res.fail({ |
169 | .json({ error: 'Cannot remove video channel of another user' }) | 184 | status: HttpStatusCode.FORBIDDEN_403, |
170 | .end() | 185 | message: 'Cannot remove video channel of another user' |
171 | 186 | }) | |
172 | return false | 187 | return false |
173 | } | 188 | } |
174 | 189 | ||
@@ -179,10 +194,10 @@ async function checkVideoChannelIsNotTheLastOne (res: express.Response) { | |||
179 | const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) | 194 | const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) |
180 | 195 | ||
181 | if (count <= 1) { | 196 | if (count <= 1) { |
182 | res.status(HttpStatusCode.CONFLICT_409) | 197 | res.fail({ |
183 | .json({ error: 'Cannot remove the last channel of this user' }) | 198 | status: HttpStatusCode.CONFLICT_409, |
184 | .end() | 199 | message: 'Cannot remove the last channel of this user' |
185 | 200 | }) | |
186 | return false | 201 | return false |
187 | } | 202 | } |
188 | 203 | ||
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts index 1afacfed8..885506ebe 100644 --- a/server/middlewares/validators/videos/video-comments.ts +++ b/server/middlewares/validators/videos/video-comments.ts | |||
@@ -2,19 +2,14 @@ import * as express from 'express' | |||
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { MUserAccountUrl } from '@server/types/models' | 3 | import { MUserAccountUrl } from '@server/types/models' |
4 | import { UserRight } from '../../../../shared' | 4 | import { UserRight } from '../../../../shared' |
5 | import { exists, isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' | 5 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
6 | import { | 6 | import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' |
7 | doesVideoCommentExist, | 7 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' |
8 | doesVideoCommentThreadExist, | ||
9 | isValidVideoCommentText | ||
10 | } from '../../../helpers/custom-validators/video-comments' | ||
11 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
12 | import { doesVideoExist } from '../../../helpers/middlewares' | ||
13 | import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation' | 9 | import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation' |
14 | import { Hooks } from '../../../lib/plugins/hooks' | 10 | import { Hooks } from '../../../lib/plugins/hooks' |
15 | import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video' | 11 | import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video' |
16 | import { areValidationErrors } from '../utils' | 12 | import { areValidationErrors, doesVideoCommentExist, doesVideoCommentThreadExist, doesVideoExist, isValidVideoIdParam } from '../shared' |
17 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
18 | 13 | ||
19 | const listVideoCommentsValidator = [ | 14 | const listVideoCommentsValidator = [ |
20 | query('isLocal') | 15 | query('isLocal') |
@@ -45,7 +40,7 @@ const listVideoCommentsValidator = [ | |||
45 | ] | 40 | ] |
46 | 41 | ||
47 | const listVideoCommentThreadsValidator = [ | 42 | const listVideoCommentThreadsValidator = [ |
48 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 43 | isValidVideoIdParam('videoId'), |
49 | 44 | ||
50 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 45 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
51 | logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params }) | 46 | logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params }) |
@@ -58,8 +53,10 @@ const listVideoCommentThreadsValidator = [ | |||
58 | ] | 53 | ] |
59 | 54 | ||
60 | const listVideoThreadCommentsValidator = [ | 55 | const listVideoThreadCommentsValidator = [ |
61 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 56 | isValidVideoIdParam('videoId'), |
62 | param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'), | 57 | |
58 | param('threadId') | ||
59 | .custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'), | ||
63 | 60 | ||
64 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 61 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
65 | logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params }) | 62 | logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params }) |
@@ -73,8 +70,10 @@ const listVideoThreadCommentsValidator = [ | |||
73 | ] | 70 | ] |
74 | 71 | ||
75 | const addVideoCommentThreadValidator = [ | 72 | const addVideoCommentThreadValidator = [ |
76 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 73 | isValidVideoIdParam('videoId'), |
77 | body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), | 74 | |
75 | body('text') | ||
76 | .custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), | ||
78 | 77 | ||
79 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 78 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
80 | logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body }) | 79 | logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body }) |
@@ -89,8 +88,10 @@ const addVideoCommentThreadValidator = [ | |||
89 | ] | 88 | ] |
90 | 89 | ||
91 | const addVideoCommentReplyValidator = [ | 90 | const addVideoCommentReplyValidator = [ |
92 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 91 | isValidVideoIdParam('videoId'), |
92 | |||
93 | param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), | 93 | param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), |
94 | |||
94 | body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), | 95 | body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), |
95 | 96 | ||
96 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 97 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
@@ -107,8 +108,10 @@ const addVideoCommentReplyValidator = [ | |||
107 | ] | 108 | ] |
108 | 109 | ||
109 | const videoCommentGetValidator = [ | 110 | const videoCommentGetValidator = [ |
110 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 111 | isValidVideoIdParam('videoId'), |
111 | param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), | 112 | |
113 | param('commentId') | ||
114 | .custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), | ||
112 | 115 | ||
113 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 116 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
114 | logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params }) | 117 | logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params }) |
@@ -122,7 +125,8 @@ const videoCommentGetValidator = [ | |||
122 | ] | 125 | ] |
123 | 126 | ||
124 | const removeVideoCommentValidator = [ | 127 | const removeVideoCommentValidator = [ |
125 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 128 | isValidVideoIdParam('videoId'), |
129 | |||
126 | param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), | 130 | param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), |
127 | 131 | ||
128 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 132 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
@@ -155,9 +159,10 @@ export { | |||
155 | 159 | ||
156 | function isVideoCommentsEnabled (video: MVideo, res: express.Response) { | 160 | function isVideoCommentsEnabled (video: MVideo, res: express.Response) { |
157 | if (video.commentsEnabled !== true) { | 161 | if (video.commentsEnabled !== true) { |
158 | res.status(HttpStatusCode.CONFLICT_409) | 162 | res.fail({ |
159 | .json({ error: 'Video comments are disabled for this video.' }) | 163 | status: HttpStatusCode.CONFLICT_409, |
160 | 164 | message: 'Video comments are disabled for this video.' | |
165 | }) | ||
161 | return false | 166 | return false |
162 | } | 167 | } |
163 | 168 | ||
@@ -166,9 +171,10 @@ function isVideoCommentsEnabled (video: MVideo, res: express.Response) { | |||
166 | 171 | ||
167 | function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) { | 172 | function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) { |
168 | if (videoComment.isDeleted()) { | 173 | if (videoComment.isDeleted()) { |
169 | res.status(HttpStatusCode.CONFLICT_409) | 174 | res.fail({ |
170 | .json({ error: 'This comment is already deleted' }) | 175 | status: HttpStatusCode.CONFLICT_409, |
171 | 176 | message: 'This comment is already deleted' | |
177 | }) | ||
172 | return false | 178 | return false |
173 | } | 179 | } |
174 | 180 | ||
@@ -179,9 +185,10 @@ function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MC | |||
179 | videoComment.accountId !== userAccount.id && // Not the comment owner | 185 | videoComment.accountId !== userAccount.id && // Not the comment owner |
180 | videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner | 186 | videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner |
181 | ) { | 187 | ) { |
182 | res.status(HttpStatusCode.FORBIDDEN_403) | 188 | res.fail({ |
183 | .json({ error: 'Cannot remove video comment of another user' }) | 189 | status: HttpStatusCode.FORBIDDEN_403, |
184 | 190 | message: 'Cannot remove video comment of another user' | |
191 | }) | ||
185 | return false | 192 | return false |
186 | } | 193 | } |
187 | 194 | ||
@@ -215,9 +222,11 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon | |||
215 | 222 | ||
216 | if (!acceptedResult || acceptedResult.accepted !== true) { | 223 | if (!acceptedResult || acceptedResult.accepted !== true) { |
217 | logger.info('Refused local comment.', { acceptedResult, acceptParameters }) | 224 | logger.info('Refused local comment.', { acceptedResult, acceptParameters }) |
218 | res.status(HttpStatusCode.FORBIDDEN_403) | ||
219 | .json({ error: acceptedResult?.errorMessage || 'Refused local comment' }) | ||
220 | 225 | ||
226 | res.fail({ | ||
227 | status: HttpStatusCode.FORBIDDEN_403, | ||
228 | message: acceptedResult?.errorMessage || 'Refused local comment' | ||
229 | }) | ||
221 | return false | 230 | return false |
222 | } | 231 | } |
223 | 232 | ||
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts index c53af3861..85dc647ce 100644 --- a/server/middlewares/validators/videos/video-imports.ts +++ b/server/middlewares/validators/videos/video-imports.ts | |||
@@ -2,18 +2,17 @@ import * as express from 'express' | |||
2 | import { body } from 'express-validator' | 2 | import { body } from 'express-validator' |
3 | import { isPreImportVideoAccepted } from '@server/lib/moderation' | 3 | import { isPreImportVideoAccepted } from '@server/lib/moderation' |
4 | import { Hooks } from '@server/lib/plugins/hooks' | 4 | import { Hooks } from '@server/lib/plugins/hooks' |
5 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
5 | import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model' | 6 | import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model' |
6 | import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' | 7 | import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' |
7 | import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports' | 8 | import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports' |
8 | import { isVideoMagnetUriValid, isVideoNameValid } from '../../../helpers/custom-validators/videos' | 9 | import { isVideoMagnetUriValid, isVideoNameValid } from '../../../helpers/custom-validators/videos' |
9 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 10 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
10 | import { logger } from '../../../helpers/logger' | 11 | import { logger } from '../../../helpers/logger' |
11 | import { doesVideoChannelOfAccountExist } from '../../../helpers/middlewares' | ||
12 | import { CONFIG } from '../../../initializers/config' | 12 | import { CONFIG } from '../../../initializers/config' |
13 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 13 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
14 | import { areValidationErrors } from '../utils' | 14 | import { areValidationErrors, doesVideoChannelOfAccountExist } from '../shared' |
15 | import { getCommonVideoEditAttributes } from './videos' | 15 | import { getCommonVideoEditAttributes } from './videos' |
16 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
17 | 16 | ||
18 | const videoImportAddValidator = getCommonVideoEditAttributes().concat([ | 17 | const videoImportAddValidator = getCommonVideoEditAttributes().concat([ |
19 | body('channelId') | 18 | body('channelId') |
@@ -33,7 +32,9 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([ | |||
33 | ), | 32 | ), |
34 | body('name') | 33 | body('name') |
35 | .optional() | 34 | .optional() |
36 | .custom(isVideoNameValid).withMessage('Should have a valid name'), | 35 | .custom(isVideoNameValid).withMessage( |
36 | `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long` | ||
37 | ), | ||
37 | 38 | ||
38 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 39 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
39 | logger.debug('Checking videoImportAddValidator parameters', { parameters: req.body }) | 40 | logger.debug('Checking videoImportAddValidator parameters', { parameters: req.body }) |
@@ -45,16 +46,20 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([ | |||
45 | 46 | ||
46 | if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true && req.body.targetUrl) { | 47 | if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true && req.body.targetUrl) { |
47 | cleanUpReqFiles(req) | 48 | cleanUpReqFiles(req) |
48 | return res.status(HttpStatusCode.CONFLICT_409) | 49 | |
49 | .json({ error: 'HTTP import is not enabled on this instance.' }) | 50 | return res.fail({ |
50 | .end() | 51 | status: HttpStatusCode.CONFLICT_409, |
52 | message: 'HTTP import is not enabled on this instance.' | ||
53 | }) | ||
51 | } | 54 | } |
52 | 55 | ||
53 | if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) { | 56 | if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) { |
54 | cleanUpReqFiles(req) | 57 | cleanUpReqFiles(req) |
55 | return res.status(HttpStatusCode.CONFLICT_409) | 58 | |
56 | .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' }) | 59 | return res.fail({ |
57 | .end() | 60 | status: HttpStatusCode.CONFLICT_409, |
61 | message: 'Torrent/magnet URI import is not enabled on this instance.' | ||
62 | }) | ||
58 | } | 63 | } |
59 | 64 | ||
60 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | 65 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) |
@@ -63,9 +68,7 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([ | |||
63 | if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) { | 68 | if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) { |
64 | cleanUpReqFiles(req) | 69 | cleanUpReqFiles(req) |
65 | 70 | ||
66 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 71 | return res.fail({ message: 'Should have a magnetUri or a targetUrl or a torrent file.' }) |
67 | .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' }) | ||
68 | .end() | ||
69 | } | 72 | } |
70 | 73 | ||
71 | if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) | 74 | if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) |
@@ -101,9 +104,11 @@ async function isImportAccepted (req: express.Request, res: express.Response) { | |||
101 | 104 | ||
102 | if (!acceptedResult || acceptedResult.accepted !== true) { | 105 | if (!acceptedResult || acceptedResult.accepted !== true) { |
103 | logger.info('Refused to import video.', { acceptedResult, acceptParameters }) | 106 | logger.info('Refused to import video.', { acceptedResult, acceptParameters }) |
104 | res.status(HttpStatusCode.FORBIDDEN_403) | ||
105 | .json({ error: acceptedResult.errorMessage || 'Refused to import video' }) | ||
106 | 107 | ||
108 | res.fail({ | ||
109 | status: HttpStatusCode.FORBIDDEN_403, | ||
110 | message: acceptedResult.errorMessage || 'Refused to import video' | ||
111 | }) | ||
107 | return false | 112 | return false |
108 | } | 113 | } |
109 | 114 | ||
diff --git a/server/middlewares/validators/videos/video-live.ts b/server/middlewares/validators/videos/video-live.ts index 3a73e1272..7cfb935e3 100644 --- a/server/middlewares/validators/videos/video-live.ts +++ b/server/middlewares/validators/videos/video-live.ts | |||
@@ -1,22 +1,28 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator' | 2 | import { body } from 'express-validator' |
3 | import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '@server/helpers/middlewares/videos' | 3 | import { CONSTRAINTS_FIELDS } from '@server/initializers/constants' |
4 | import { isLocalLiveVideoAccepted } from '@server/lib/moderation' | ||
5 | import { Hooks } from '@server/lib/plugins/hooks' | ||
6 | import { VideoModel } from '@server/models/video/video' | ||
4 | import { VideoLiveModel } from '@server/models/video/video-live' | 7 | import { VideoLiveModel } from '@server/models/video/video-live' |
8 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
5 | import { ServerErrorCode, UserRight, VideoState } from '@shared/models' | 9 | import { ServerErrorCode, UserRight, VideoState } from '@shared/models' |
6 | import { isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' | 10 | import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' |
7 | import { isVideoNameValid } from '../../../helpers/custom-validators/videos' | 11 | import { isVideoNameValid } from '../../../helpers/custom-validators/videos' |
8 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 12 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
9 | import { logger } from '../../../helpers/logger' | 13 | import { logger } from '../../../helpers/logger' |
10 | import { CONFIG } from '../../../initializers/config' | 14 | import { CONFIG } from '../../../initializers/config' |
11 | import { areValidationErrors } from '../utils' | 15 | import { |
16 | areValidationErrors, | ||
17 | checkUserCanManageVideo, | ||
18 | doesVideoChannelOfAccountExist, | ||
19 | doesVideoExist, | ||
20 | isValidVideoIdParam | ||
21 | } from '../shared' | ||
12 | import { getCommonVideoEditAttributes } from './videos' | 22 | import { getCommonVideoEditAttributes } from './videos' |
13 | import { VideoModel } from '@server/models/video/video' | ||
14 | import { Hooks } from '@server/lib/plugins/hooks' | ||
15 | import { isLocalLiveVideoAccepted } from '@server/lib/moderation' | ||
16 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
17 | 23 | ||
18 | const videoLiveGetValidator = [ | 24 | const videoLiveGetValidator = [ |
19 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 25 | isValidVideoIdParam('videoId'), |
20 | 26 | ||
21 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 27 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
22 | logger.debug('Checking videoLiveGetValidator parameters', { parameters: req.params, user: res.locals.oauth.token.User.username }) | 28 | logger.debug('Checking videoLiveGetValidator parameters', { parameters: req.params, user: res.locals.oauth.token.User.username }) |
@@ -29,7 +35,12 @@ const videoLiveGetValidator = [ | |||
29 | if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.GET_ANY_LIVE, res, false)) return | 35 | if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.GET_ANY_LIVE, res, false)) return |
30 | 36 | ||
31 | const videoLive = await VideoLiveModel.loadByVideoId(res.locals.videoAll.id) | 37 | const videoLive = await VideoLiveModel.loadByVideoId(res.locals.videoAll.id) |
32 | if (!videoLive) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 38 | if (!videoLive) { |
39 | return res.fail({ | ||
40 | status: HttpStatusCode.NOT_FOUND_404, | ||
41 | message: 'Live video not found' | ||
42 | }) | ||
43 | } | ||
33 | 44 | ||
34 | res.locals.videoLive = videoLive | 45 | res.locals.videoLive = videoLive |
35 | 46 | ||
@@ -43,7 +54,9 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([ | |||
43 | .custom(isIdValid).withMessage('Should have correct video channel id'), | 54 | .custom(isIdValid).withMessage('Should have correct video channel id'), |
44 | 55 | ||
45 | body('name') | 56 | body('name') |
46 | .custom(isVideoNameValid).withMessage('Should have a valid name'), | 57 | .custom(isVideoNameValid).withMessage( |
58 | `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long` | ||
59 | ), | ||
47 | 60 | ||
48 | body('saveReplay') | 61 | body('saveReplay') |
49 | .optional() | 62 | .optional() |
@@ -63,22 +76,27 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([ | |||
63 | if (CONFIG.LIVE.ENABLED !== true) { | 76 | if (CONFIG.LIVE.ENABLED !== true) { |
64 | cleanUpReqFiles(req) | 77 | cleanUpReqFiles(req) |
65 | 78 | ||
66 | return res.status(HttpStatusCode.FORBIDDEN_403) | 79 | return res.fail({ |
67 | .json({ error: 'Live is not enabled on this instance' }) | 80 | status: HttpStatusCode.FORBIDDEN_403, |
81 | message: 'Live is not enabled on this instance', | ||
82 | type: ServerErrorCode.LIVE_NOT_ENABLED | ||
83 | }) | ||
68 | } | 84 | } |
69 | 85 | ||
70 | if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) { | 86 | if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) { |
71 | cleanUpReqFiles(req) | 87 | cleanUpReqFiles(req) |
72 | 88 | ||
73 | return res.status(HttpStatusCode.FORBIDDEN_403) | 89 | return res.fail({ |
74 | .json({ error: 'Saving live replay is not allowed instance' }) | 90 | status: HttpStatusCode.FORBIDDEN_403, |
91 | message: 'Saving live replay is not enabled on this instance', | ||
92 | type: ServerErrorCode.LIVE_NOT_ALLOWING_REPLAY | ||
93 | }) | ||
75 | } | 94 | } |
76 | 95 | ||
77 | if (req.body.permanentLive && req.body.saveReplay) { | 96 | if (req.body.permanentLive && req.body.saveReplay) { |
78 | cleanUpReqFiles(req) | 97 | cleanUpReqFiles(req) |
79 | 98 | ||
80 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 99 | return res.fail({ message: 'Cannot set this live as permanent while saving its replay' }) |
81 | .json({ error: 'Cannot set this live as permanent while saving its replay' }) | ||
82 | } | 100 | } |
83 | 101 | ||
84 | const user = res.locals.oauth.token.User | 102 | const user = res.locals.oauth.token.User |
@@ -90,11 +108,11 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([ | |||
90 | if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) { | 108 | if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) { |
91 | cleanUpReqFiles(req) | 109 | cleanUpReqFiles(req) |
92 | 110 | ||
93 | return res.status(HttpStatusCode.FORBIDDEN_403) | 111 | return res.fail({ |
94 | .json({ | 112 | status: HttpStatusCode.FORBIDDEN_403, |
95 | code: ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED, | 113 | message: 'Cannot create this live because the max instance lives limit is reached.', |
96 | error: 'Cannot create this live because the max instance lives limit is reached.' | 114 | type: ServerErrorCode.MAX_INSTANCE_LIVES_LIMIT_REACHED |
97 | }) | 115 | }) |
98 | } | 116 | } |
99 | } | 117 | } |
100 | 118 | ||
@@ -104,11 +122,11 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([ | |||
104 | if (totalUserLives >= CONFIG.LIVE.MAX_USER_LIVES) { | 122 | if (totalUserLives >= CONFIG.LIVE.MAX_USER_LIVES) { |
105 | cleanUpReqFiles(req) | 123 | cleanUpReqFiles(req) |
106 | 124 | ||
107 | return res.status(HttpStatusCode.FORBIDDEN_403) | 125 | return res.fail({ |
108 | .json({ | 126 | status: HttpStatusCode.FORBIDDEN_403, |
109 | code: ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED, | 127 | message: 'Cannot create this live because the max user lives limit is reached.', |
110 | error: 'Cannot create this live because the max user lives limit is reached.' | 128 | type: ServerErrorCode.MAX_USER_LIVES_LIMIT_REACHED |
111 | }) | 129 | }) |
112 | } | 130 | } |
113 | } | 131 | } |
114 | 132 | ||
@@ -130,18 +148,18 @@ const videoLiveUpdateValidator = [ | |||
130 | if (areValidationErrors(req, res)) return | 148 | if (areValidationErrors(req, res)) return |
131 | 149 | ||
132 | if (req.body.permanentLive && req.body.saveReplay) { | 150 | if (req.body.permanentLive && req.body.saveReplay) { |
133 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 151 | return res.fail({ message: 'Cannot set this live as permanent while saving its replay' }) |
134 | .json({ error: 'Cannot set this live as permanent while saving its replay' }) | ||
135 | } | 152 | } |
136 | 153 | ||
137 | if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) { | 154 | if (CONFIG.LIVE.ALLOW_REPLAY !== true && req.body.saveReplay === true) { |
138 | return res.status(HttpStatusCode.FORBIDDEN_403) | 155 | return res.fail({ |
139 | .json({ error: 'Saving live replay is not allowed instance' }) | 156 | status: HttpStatusCode.FORBIDDEN_403, |
157 | message: 'Saving live replay is not allowed instance' | ||
158 | }) | ||
140 | } | 159 | } |
141 | 160 | ||
142 | if (res.locals.videoAll.state !== VideoState.WAITING_FOR_LIVE) { | 161 | if (res.locals.videoAll.state !== VideoState.WAITING_FOR_LIVE) { |
143 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 162 | return res.fail({ message: 'Cannot update a live that has already started' }) |
144 | .json({ error: 'Cannot update a live that has already started' }) | ||
145 | } | 163 | } |
146 | 164 | ||
147 | // Check the user can manage the live | 165 | // Check the user can manage the live |
@@ -177,9 +195,10 @@ async function isLiveVideoAccepted (req: express.Request, res: express.Response) | |||
177 | if (!acceptedResult || acceptedResult.accepted !== true) { | 195 | if (!acceptedResult || acceptedResult.accepted !== true) { |
178 | logger.info('Refused local live video.', { acceptedResult, acceptParameters }) | 196 | logger.info('Refused local live video.', { acceptedResult, acceptParameters }) |
179 | 197 | ||
180 | res.status(HttpStatusCode.FORBIDDEN_403) | 198 | res.fail({ |
181 | .json({ error: acceptedResult.errorMessage || 'Refused local live video' }) | 199 | status: HttpStatusCode.FORBIDDEN_403, |
182 | 200 | message: acceptedResult.errorMessage || 'Refused local live video' | |
201 | }) | ||
183 | return false | 202 | return false |
184 | } | 203 | } |
185 | 204 | ||
diff --git a/server/middlewares/validators/videos/video-ownership-changes.ts b/server/middlewares/validators/videos/video-ownership-changes.ts new file mode 100644 index 000000000..54ac46c99 --- /dev/null +++ b/server/middlewares/validators/videos/video-ownership-changes.ts | |||
@@ -0,0 +1,121 @@ | |||
1 | import * as express from 'express' | ||
2 | import { param } from 'express-validator' | ||
3 | import { isIdValid } from '@server/helpers/custom-validators/misc' | ||
4 | import { checkUserCanTerminateOwnershipChange } from '@server/helpers/custom-validators/video-ownership' | ||
5 | import { logger } from '@server/helpers/logger' | ||
6 | import { isAbleToUploadVideo } from '@server/lib/user' | ||
7 | import { AccountModel } from '@server/models/account/account' | ||
8 | import { MVideoWithAllFiles } from '@server/types/models' | ||
9 | import { HttpStatusCode } from '@shared/core-utils' | ||
10 | import { ServerErrorCode, UserRight, VideoChangeOwnershipAccept, VideoChangeOwnershipStatus, VideoState } from '@shared/models' | ||
11 | import { | ||
12 | areValidationErrors, | ||
13 | checkUserCanManageVideo, | ||
14 | doesChangeVideoOwnershipExist, | ||
15 | doesVideoChannelOfAccountExist, | ||
16 | doesVideoExist, | ||
17 | isValidVideoIdParam | ||
18 | } from '../shared' | ||
19 | |||
20 | const videosChangeOwnershipValidator = [ | ||
21 | isValidVideoIdParam('videoId'), | ||
22 | |||
23 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
24 | logger.debug('Checking changeOwnership parameters', { parameters: req.params }) | ||
25 | |||
26 | if (areValidationErrors(req, res)) return | ||
27 | if (!await doesVideoExist(req.params.videoId, res)) return | ||
28 | |||
29 | // Check if the user who did the request is able to change the ownership of the video | ||
30 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return | ||
31 | |||
32 | const nextOwner = await AccountModel.loadLocalByName(req.body.username) | ||
33 | if (!nextOwner) { | ||
34 | res.fail({ message: 'Changing video ownership to a remote account is not supported yet' }) | ||
35 | return | ||
36 | } | ||
37 | |||
38 | res.locals.nextOwner = nextOwner | ||
39 | return next() | ||
40 | } | ||
41 | ] | ||
42 | |||
43 | const videosTerminateChangeOwnershipValidator = [ | ||
44 | param('id') | ||
45 | .custom(isIdValid).withMessage('Should have a valid id'), | ||
46 | |||
47 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
48 | logger.debug('Checking changeOwnership parameters', { parameters: req.params }) | ||
49 | |||
50 | if (areValidationErrors(req, res)) return | ||
51 | if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return | ||
52 | |||
53 | // Check if the user who did the request is able to change the ownership of the video | ||
54 | if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return | ||
55 | |||
56 | const videoChangeOwnership = res.locals.videoChangeOwnership | ||
57 | |||
58 | if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) { | ||
59 | res.fail({ | ||
60 | status: HttpStatusCode.FORBIDDEN_403, | ||
61 | message: 'Ownership already accepted or refused' | ||
62 | }) | ||
63 | return | ||
64 | } | ||
65 | |||
66 | return next() | ||
67 | } | ||
68 | ] | ||
69 | |||
70 | const videosAcceptChangeOwnershipValidator = [ | ||
71 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
72 | const body = req.body as VideoChangeOwnershipAccept | ||
73 | if (!await doesVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return | ||
74 | |||
75 | const videoChangeOwnership = res.locals.videoChangeOwnership | ||
76 | |||
77 | const video = videoChangeOwnership.Video | ||
78 | |||
79 | if (!await checkCanAccept(video, res)) return | ||
80 | |||
81 | return next() | ||
82 | } | ||
83 | ] | ||
84 | |||
85 | export { | ||
86 | videosChangeOwnershipValidator, | ||
87 | videosTerminateChangeOwnershipValidator, | ||
88 | videosAcceptChangeOwnershipValidator | ||
89 | } | ||
90 | |||
91 | // --------------------------------------------------------------------------- | ||
92 | |||
93 | async function checkCanAccept (video: MVideoWithAllFiles, res: express.Response): Promise<boolean> { | ||
94 | if (video.isLive) { | ||
95 | |||
96 | if (video.state !== VideoState.WAITING_FOR_LIVE) { | ||
97 | res.fail({ | ||
98 | status: HttpStatusCode.BAD_REQUEST_400, | ||
99 | message: 'You can accept an ownership change of a published live.' | ||
100 | }) | ||
101 | |||
102 | return false | ||
103 | } | ||
104 | |||
105 | return true | ||
106 | } | ||
107 | |||
108 | const user = res.locals.oauth.token.User | ||
109 | |||
110 | if (!await isAbleToUploadVideo(user.id, video.getMaxQualityFile().size)) { | ||
111 | res.fail({ | ||
112 | status: HttpStatusCode.PAYLOAD_TOO_LARGE_413, | ||
113 | message: 'The user video quota is exceeded with this video.', | ||
114 | type: ServerErrorCode.QUOTA_REACHED | ||
115 | }) | ||
116 | |||
117 | return false | ||
118 | } | ||
119 | |||
120 | return true | ||
121 | } | ||
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index c872d045e..5ee7ee0ce 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -11,6 +11,7 @@ import { | |||
11 | isIdOrUUIDValid, | 11 | isIdOrUUIDValid, |
12 | isIdValid, | 12 | isIdValid, |
13 | isUUIDValid, | 13 | isUUIDValid, |
14 | toCompleteUUID, | ||
14 | toIntArray, | 15 | toIntArray, |
15 | toIntOrNull, | 16 | toIntOrNull, |
16 | toValueOrNull | 17 | toValueOrNull |
@@ -25,12 +26,18 @@ import { | |||
25 | import { isVideoImage } from '../../../helpers/custom-validators/videos' | 26 | import { isVideoImage } from '../../../helpers/custom-validators/videos' |
26 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 27 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
27 | import { logger } from '../../../helpers/logger' | 28 | import { logger } from '../../../helpers/logger' |
28 | import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist, VideoPlaylistFetchType } from '../../../helpers/middlewares' | ||
29 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 29 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
30 | import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' | 30 | import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' |
31 | import { MVideoPlaylist } from '../../../types/models/video/video-playlist' | 31 | import { MVideoPlaylist } from '../../../types/models/video/video-playlist' |
32 | import { authenticatePromiseIfNeeded } from '../../auth' | 32 | import { authenticatePromiseIfNeeded } from '../../auth' |
33 | import { areValidationErrors } from '../utils' | 33 | import { |
34 | areValidationErrors, | ||
35 | doesVideoChannelIdExist, | ||
36 | doesVideoExist, | ||
37 | doesVideoPlaylistExist, | ||
38 | isValidPlaylistIdParam, | ||
39 | VideoPlaylistFetchType | ||
40 | } from '../shared' | ||
34 | 41 | ||
35 | const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ | 42 | const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ |
36 | body('displayName') | 43 | body('displayName') |
@@ -44,10 +51,13 @@ const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ | |||
44 | const body: VideoPlaylistCreate = req.body | 51 | const body: VideoPlaylistCreate = req.body |
45 | if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req) | 52 | if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req) |
46 | 53 | ||
47 | if (body.privacy === VideoPlaylistPrivacy.PUBLIC && !body.videoChannelId) { | 54 | if ( |
55 | !body.videoChannelId && | ||
56 | (body.privacy === VideoPlaylistPrivacy.PUBLIC || body.privacy === VideoPlaylistPrivacy.UNLISTED) | ||
57 | ) { | ||
48 | cleanUpReqFiles(req) | 58 | cleanUpReqFiles(req) |
49 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 59 | |
50 | .json({ error: 'Cannot set "public" a playlist that is not assigned to a channel.' }) | 60 | return res.fail({ message: 'Cannot set "public" or "unlisted" a playlist that is not assigned to a channel.' }) |
51 | } | 61 | } |
52 | 62 | ||
53 | return next() | 63 | return next() |
@@ -55,8 +65,7 @@ const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ | |||
55 | ]) | 65 | ]) |
56 | 66 | ||
57 | const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ | 67 | const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ |
58 | param('playlistId') | 68 | isValidPlaylistIdParam('playlistId'), |
59 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
60 | 69 | ||
61 | body('displayName') | 70 | body('displayName') |
62 | .optional() | 71 | .optional() |
@@ -85,14 +94,14 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ | |||
85 | ) | 94 | ) |
86 | ) { | 95 | ) { |
87 | cleanUpReqFiles(req) | 96 | cleanUpReqFiles(req) |
88 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 97 | |
89 | .json({ error: 'Cannot set "public" a playlist that is not assigned to a channel.' }) | 98 | return res.fail({ message: 'Cannot set "public" a playlist that is not assigned to a channel.' }) |
90 | } | 99 | } |
91 | 100 | ||
92 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { | 101 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { |
93 | cleanUpReqFiles(req) | 102 | cleanUpReqFiles(req) |
94 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 103 | |
95 | .json({ error: 'Cannot update a watch later playlist.' }) | 104 | return res.fail({ message: 'Cannot update a watch later playlist.' }) |
96 | } | 105 | } |
97 | 106 | ||
98 | if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req) | 107 | if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req) |
@@ -102,8 +111,7 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ | |||
102 | ]) | 111 | ]) |
103 | 112 | ||
104 | const videoPlaylistsDeleteValidator = [ | 113 | const videoPlaylistsDeleteValidator = [ |
105 | param('playlistId') | 114 | isValidPlaylistIdParam('playlistId'), |
106 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
107 | 115 | ||
108 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 116 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
109 | logger.debug('Checking videoPlaylistsDeleteValidator parameters', { parameters: req.params }) | 117 | logger.debug('Checking videoPlaylistsDeleteValidator parameters', { parameters: req.params }) |
@@ -114,8 +122,7 @@ const videoPlaylistsDeleteValidator = [ | |||
114 | 122 | ||
115 | const videoPlaylist = getPlaylist(res) | 123 | const videoPlaylist = getPlaylist(res) |
116 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { | 124 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { |
117 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 125 | return res.fail({ message: 'Cannot delete a watch later playlist.' }) |
118 | .json({ error: 'Cannot delete a watch later playlist.' }) | ||
119 | } | 126 | } |
120 | 127 | ||
121 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { | 128 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { |
@@ -128,8 +135,7 @@ const videoPlaylistsDeleteValidator = [ | |||
128 | 135 | ||
129 | const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => { | 136 | const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => { |
130 | return [ | 137 | return [ |
131 | param('playlistId') | 138 | isValidPlaylistIdParam('playlistId'), |
132 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
133 | 139 | ||
134 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 140 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
135 | logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) | 141 | logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) |
@@ -144,7 +150,10 @@ const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => { | |||
144 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { | 150 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { |
145 | if (isUUIDValid(req.params.playlistId)) return next() | 151 | if (isUUIDValid(req.params.playlistId)) return next() |
146 | 152 | ||
147 | return res.status(HttpStatusCode.NOT_FOUND_404).end() | 153 | return res.fail({ |
154 | status: HttpStatusCode.NOT_FOUND_404, | ||
155 | message: 'Playlist not found' | ||
156 | }) | ||
148 | } | 157 | } |
149 | 158 | ||
150 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { | 159 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { |
@@ -156,8 +165,10 @@ const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => { | |||
156 | !user || | 165 | !user || |
157 | (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) | 166 | (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) |
158 | ) { | 167 | ) { |
159 | return res.status(HttpStatusCode.FORBIDDEN_403) | 168 | return res.fail({ |
160 | .json({ error: 'Cannot get this private video playlist.' }) | 169 | status: HttpStatusCode.FORBIDDEN_403, |
170 | message: 'Cannot get this private video playlist.' | ||
171 | }) | ||
161 | } | 172 | } |
162 | 173 | ||
163 | return next() | 174 | return next() |
@@ -181,9 +192,10 @@ const videoPlaylistsSearchValidator = [ | |||
181 | ] | 192 | ] |
182 | 193 | ||
183 | const videoPlaylistsAddVideoValidator = [ | 194 | const videoPlaylistsAddVideoValidator = [ |
184 | param('playlistId') | 195 | isValidPlaylistIdParam('playlistId'), |
185 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | 196 | |
186 | body('videoId') | 197 | body('videoId') |
198 | .customSanitizer(toCompleteUUID) | ||
187 | .custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid'), | 199 | .custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid'), |
188 | body('startTimestamp') | 200 | body('startTimestamp') |
189 | .optional() | 201 | .optional() |
@@ -211,9 +223,9 @@ const videoPlaylistsAddVideoValidator = [ | |||
211 | ] | 223 | ] |
212 | 224 | ||
213 | const videoPlaylistsUpdateOrRemoveVideoValidator = [ | 225 | const videoPlaylistsUpdateOrRemoveVideoValidator = [ |
214 | param('playlistId') | 226 | isValidPlaylistIdParam('playlistId'), |
215 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
216 | param('playlistElementId') | 227 | param('playlistElementId') |
228 | .customSanitizer(toCompleteUUID) | ||
217 | .custom(isIdValid).withMessage('Should have an element id/uuid'), | 229 | .custom(isIdValid).withMessage('Should have an element id/uuid'), |
218 | body('startTimestamp') | 230 | body('startTimestamp') |
219 | .optional() | 231 | .optional() |
@@ -233,10 +245,10 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [ | |||
233 | 245 | ||
234 | const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) | 246 | const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) |
235 | if (!videoPlaylistElement) { | 247 | if (!videoPlaylistElement) { |
236 | res.status(HttpStatusCode.NOT_FOUND_404) | 248 | res.fail({ |
237 | .json({ error: 'Video playlist element not found' }) | 249 | status: HttpStatusCode.NOT_FOUND_404, |
238 | .end() | 250 | message: 'Video playlist element not found' |
239 | 251 | }) | |
240 | return | 252 | return |
241 | } | 253 | } |
242 | res.locals.videoPlaylistElement = videoPlaylistElement | 254 | res.locals.videoPlaylistElement = videoPlaylistElement |
@@ -248,8 +260,7 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [ | |||
248 | ] | 260 | ] |
249 | 261 | ||
250 | const videoPlaylistElementAPGetValidator = [ | 262 | const videoPlaylistElementAPGetValidator = [ |
251 | param('playlistId') | 263 | isValidPlaylistIdParam('playlistId'), |
252 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
253 | param('playlistElementId') | 264 | param('playlistElementId') |
254 | .custom(isIdValid).withMessage('Should have an playlist element id'), | 265 | .custom(isIdValid).withMessage('Should have an playlist element id'), |
255 | 266 | ||
@@ -263,15 +274,18 @@ const videoPlaylistElementAPGetValidator = [ | |||
263 | 274 | ||
264 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId) | 275 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId) |
265 | if (!videoPlaylistElement) { | 276 | if (!videoPlaylistElement) { |
266 | res.status(HttpStatusCode.NOT_FOUND_404) | 277 | res.fail({ |
267 | .json({ error: 'Video playlist element not found' }) | 278 | status: HttpStatusCode.NOT_FOUND_404, |
268 | .end() | 279 | message: 'Video playlist element not found' |
269 | 280 | }) | |
270 | return | 281 | return |
271 | } | 282 | } |
272 | 283 | ||
273 | if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { | 284 | if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { |
274 | return res.status(HttpStatusCode.FORBIDDEN_403).end() | 285 | return res.fail({ |
286 | status: HttpStatusCode.FORBIDDEN_403, | ||
287 | message: 'Cannot get this private video playlist.' | ||
288 | }) | ||
275 | } | 289 | } |
276 | 290 | ||
277 | res.locals.videoPlaylistElementAP = videoPlaylistElement | 291 | res.locals.videoPlaylistElementAP = videoPlaylistElement |
@@ -281,8 +295,7 @@ const videoPlaylistElementAPGetValidator = [ | |||
281 | ] | 295 | ] |
282 | 296 | ||
283 | const videoPlaylistsReorderVideosValidator = [ | 297 | const videoPlaylistsReorderVideosValidator = [ |
284 | param('playlistId') | 298 | isValidPlaylistIdParam('playlistId'), |
285 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
286 | body('startPosition') | 299 | body('startPosition') |
287 | .isInt({ min: 1 }).withMessage('Should have a valid start position'), | 300 | .isInt({ min: 1 }).withMessage('Should have a valid start position'), |
288 | body('insertAfterPosition') | 301 | body('insertAfterPosition') |
@@ -307,18 +320,12 @@ const videoPlaylistsReorderVideosValidator = [ | |||
307 | const reorderLength: number = req.body.reorderLength | 320 | const reorderLength: number = req.body.reorderLength |
308 | 321 | ||
309 | if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) { | 322 | if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) { |
310 | res.status(HttpStatusCode.BAD_REQUEST_400) | 323 | res.fail({ message: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` }) |
311 | .json({ error: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` }) | ||
312 | .end() | ||
313 | |||
314 | return | 324 | return |
315 | } | 325 | } |
316 | 326 | ||
317 | if (reorderLength && reorderLength + startPosition > nextPosition) { | 327 | if (reorderLength && reorderLength + startPosition > nextPosition) { |
318 | res.status(HttpStatusCode.BAD_REQUEST_400) | 328 | res.fail({ message: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` }) |
319 | .json({ error: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` }) | ||
320 | .end() | ||
321 | |||
322 | return | 329 | return |
323 | } | 330 | } |
324 | 331 | ||
@@ -401,10 +408,10 @@ function getCommonPlaylistEditAttributes () { | |||
401 | 408 | ||
402 | function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) { | 409 | function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) { |
403 | if (videoPlaylist.isOwned() === false) { | 410 | if (videoPlaylist.isOwned() === false) { |
404 | res.status(HttpStatusCode.FORBIDDEN_403) | 411 | res.fail({ |
405 | .json({ error: 'Cannot manage video playlist of another server.' }) | 412 | status: HttpStatusCode.FORBIDDEN_403, |
406 | .end() | 413 | message: 'Cannot manage video playlist of another server.' |
407 | 414 | }) | |
408 | return false | 415 | return false |
409 | } | 416 | } |
410 | 417 | ||
@@ -412,10 +419,10 @@ function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: M | |||
412 | // The user can delete it if s/he is an admin | 419 | // The user can delete it if s/he is an admin |
413 | // Or if s/he is the video playlist's owner | 420 | // Or if s/he is the video playlist's owner |
414 | if (user.hasRight(right) === false && videoPlaylist.ownerAccountId !== user.Account.id) { | 421 | if (user.hasRight(right) === false && videoPlaylist.ownerAccountId !== user.Account.id) { |
415 | res.status(HttpStatusCode.FORBIDDEN_403) | 422 | res.fail({ |
416 | .json({ error: 'Cannot manage video playlist of another user' }) | 423 | status: HttpStatusCode.FORBIDDEN_403, |
417 | .end() | 424 | message: 'Cannot manage video playlist of another user' |
418 | 425 | }) | |
419 | return false | 426 | return false |
420 | } | 427 | } |
421 | 428 | ||
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts index 01bdef25f..5d5dfb222 100644 --- a/server/middlewares/validators/videos/video-rates.ts +++ b/server/middlewares/validators/videos/video-rates.ts | |||
@@ -1,18 +1,18 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 3 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
4 | import { VideoRateType } from '../../../../shared/models/videos' | ||
5 | import { isAccountNameValid } from '../../../helpers/custom-validators/accounts' | ||
6 | import { isIdValid } from '../../../helpers/custom-validators/misc' | ||
4 | import { isRatingValid } from '../../../helpers/custom-validators/video-rates' | 7 | import { isRatingValid } from '../../../helpers/custom-validators/video-rates' |
5 | import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' | 8 | import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' |
6 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
7 | import { areValidationErrors } from '../utils' | ||
8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 10 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
9 | import { VideoRateType } from '../../../../shared/models/videos' | 11 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' |
10 | import { isAccountNameValid } from '../../../helpers/custom-validators/accounts' | ||
11 | import { doesVideoExist } from '../../../helpers/middlewares' | ||
12 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
13 | 12 | ||
14 | const videoUpdateRateValidator = [ | 13 | const videoUpdateRateValidator = [ |
15 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 14 | isValidVideoIdParam('id'), |
15 | |||
16 | body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), | 16 | body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), |
17 | 17 | ||
18 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 18 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
@@ -37,8 +37,10 @@ const getAccountVideoRateValidatorFactory = function (rateType: VideoRateType) { | |||
37 | 37 | ||
38 | const rate = await AccountVideoRateModel.loadLocalAndPopulateVideo(rateType, req.params.name, +req.params.videoId) | 38 | const rate = await AccountVideoRateModel.loadLocalAndPopulateVideo(rateType, req.params.name, +req.params.videoId) |
39 | if (!rate) { | 39 | if (!rate) { |
40 | return res.status(HttpStatusCode.NOT_FOUND_404) | 40 | return res.fail({ |
41 | .json({ error: 'Video rate not found' }) | 41 | status: HttpStatusCode.NOT_FOUND_404, |
42 | message: 'Video rate not found' | ||
43 | }) | ||
42 | } | 44 | } |
43 | 45 | ||
44 | res.locals.accountVideoRate = rate | 46 | res.locals.accountVideoRate = rate |
diff --git a/server/middlewares/validators/videos/video-shares.ts b/server/middlewares/validators/videos/video-shares.ts index f0d8e0c36..7e54b6fc0 100644 --- a/server/middlewares/validators/videos/video-shares.ts +++ b/server/middlewares/validators/videos/video-shares.ts | |||
@@ -1,15 +1,16 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { param } from 'express-validator' | 2 | import { param } from 'express-validator' |
3 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 3 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
4 | import { isIdValid } from '../../../helpers/custom-validators/misc' | ||
4 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
5 | import { VideoShareModel } from '../../../models/video/video-share' | 6 | import { VideoShareModel } from '../../../models/video/video-share' |
6 | import { areValidationErrors } from '../utils' | 7 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' |
7 | import { doesVideoExist } from '../../../helpers/middlewares' | ||
8 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
9 | 8 | ||
10 | const videosShareValidator = [ | 9 | const videosShareValidator = [ |
11 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 10 | isValidVideoIdParam('id'), |
12 | param('actorId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid actor id'), | 11 | |
12 | param('actorId') | ||
13 | .custom(isIdValid).not().isEmpty().withMessage('Should have a valid actor id'), | ||
13 | 14 | ||
14 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 15 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
15 | logger.debug('Checking videoShare parameters', { parameters: req.params }) | 16 | logger.debug('Checking videoShare parameters', { parameters: req.params }) |
diff --git a/server/middlewares/validators/videos/video-watch.ts b/server/middlewares/validators/videos/video-watch.ts index 29ce0dab6..43306f7cd 100644 --- a/server/middlewares/validators/videos/video-watch.ts +++ b/server/middlewares/validators/videos/video-watch.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { body, param } from 'express-validator' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
3 | import { isIdOrUUIDValid, toIntOrNull } from '../../../helpers/custom-validators/misc' | 2 | import { body } from 'express-validator' |
4 | import { areValidationErrors } from '../utils' | ||
5 | import { logger } from '../../../helpers/logger' | ||
6 | import { doesVideoExist } from '../../../helpers/middlewares' | ||
7 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 3 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
4 | import { toIntOrNull } from '../../../helpers/custom-validators/misc' | ||
5 | import { logger } from '../../../helpers/logger' | ||
6 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' | ||
8 | 7 | ||
9 | const videoWatchingValidator = [ | 8 | const videoWatchingValidator = [ |
10 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 9 | isValidVideoIdParam('videoId'), |
10 | |||
11 | body('currentTime') | 11 | body('currentTime') |
12 | .customSanitizer(toIntOrNull) | 12 | .customSanitizer(toIntOrNull) |
13 | .isInt().withMessage('Should have correct current time'), | 13 | .isInt().withMessage('Should have correct current time'), |
@@ -21,7 +21,10 @@ const videoWatchingValidator = [ | |||
21 | const user = res.locals.oauth.token.User | 21 | const user = res.locals.oauth.token.User |
22 | if (user.videosHistoryEnabled === false) { | 22 | if (user.videosHistoryEnabled === false) { |
23 | logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id) | 23 | logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id) |
24 | return res.status(HttpStatusCode.CONFLICT_409).end() | 24 | return res.fail({ |
25 | status: HttpStatusCode.CONFLICT_409, | ||
26 | message: 'Video history is disabled' | ||
27 | }) | ||
25 | } | 28 | } |
26 | 29 | ||
27 | return next() | 30 | return next() |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index d26bcd4a6..49e10e2b5 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -4,16 +4,14 @@ import { getResumableUploadPath } from '@server/helpers/upload' | |||
4 | import { isAbleToUploadVideo } from '@server/lib/user' | 4 | import { isAbleToUploadVideo } from '@server/lib/user' |
5 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { ExpressPromiseHandler } from '@server/types/express' | 6 | import { ExpressPromiseHandler } from '@server/types/express' |
7 | import { MUserAccountId, MVideoWithRights } from '@server/types/models' | 7 | import { MUserAccountId, MVideoFullLight } from '@server/types/models' |
8 | import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared' | 8 | import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared' |
9 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 9 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
10 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' | ||
11 | import { | 10 | import { |
12 | exists, | 11 | exists, |
13 | isBooleanValid, | 12 | isBooleanValid, |
14 | isDateValid, | 13 | isDateValid, |
15 | isFileFieldValid, | 14 | isFileFieldValid, |
16 | isIdOrUUIDValid, | ||
17 | isIdValid, | 15 | isIdValid, |
18 | isUUIDValid, | 16 | isUUIDValid, |
19 | toArray, | 17 | toArray, |
@@ -22,7 +20,6 @@ import { | |||
22 | toValueOrNull | 20 | toValueOrNull |
23 | } from '../../../helpers/custom-validators/misc' | 21 | } from '../../../helpers/custom-validators/misc' |
24 | import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' | 22 | import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' |
25 | import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' | ||
26 | import { | 23 | import { |
27 | isScheduleVideoUpdatePrivacyValid, | 24 | isScheduleVideoUpdatePrivacyValid, |
28 | isVideoCategoryValid, | 25 | isVideoCategoryValid, |
@@ -42,22 +39,22 @@ import { | |||
42 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 39 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
43 | import { getDurationFromVideoFile } from '../../../helpers/ffprobe-utils' | 40 | import { getDurationFromVideoFile } from '../../../helpers/ffprobe-utils' |
44 | import { logger } from '../../../helpers/logger' | 41 | import { logger } from '../../../helpers/logger' |
45 | import { | ||
46 | checkUserCanManageVideo, | ||
47 | doesVideoChannelOfAccountExist, | ||
48 | doesVideoExist, | ||
49 | doesVideoFileOfVideoExist | ||
50 | } from '../../../helpers/middlewares' | ||
51 | import { deleteFileAndCatch } from '../../../helpers/utils' | 42 | import { deleteFileAndCatch } from '../../../helpers/utils' |
52 | import { getVideoWithAttributes } from '../../../helpers/video' | 43 | import { getVideoWithAttributes } from '../../../helpers/video' |
53 | import { CONFIG } from '../../../initializers/config' | 44 | import { CONFIG } from '../../../initializers/config' |
54 | import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants' | 45 | import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants' |
55 | import { isLocalVideoAccepted } from '../../../lib/moderation' | 46 | import { isLocalVideoAccepted } from '../../../lib/moderation' |
56 | import { Hooks } from '../../../lib/plugins/hooks' | 47 | import { Hooks } from '../../../lib/plugins/hooks' |
57 | import { AccountModel } from '../../../models/account/account' | ||
58 | import { VideoModel } from '../../../models/video/video' | 48 | import { VideoModel } from '../../../models/video/video' |
59 | import { authenticatePromiseIfNeeded } from '../../auth' | 49 | import { authenticatePromiseIfNeeded } from '../../auth' |
60 | import { areValidationErrors } from '../utils' | 50 | import { |
51 | areValidationErrors, | ||
52 | checkUserCanManageVideo, | ||
53 | doesVideoChannelOfAccountExist, | ||
54 | doesVideoExist, | ||
55 | doesVideoFileOfVideoExist, | ||
56 | isValidVideoIdParam | ||
57 | } from '../shared' | ||
61 | 58 | ||
62 | const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([ | 59 | const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([ |
63 | body('videofile') | 60 | body('videofile') |
@@ -65,8 +62,9 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([ | |||
65 | .withMessage('Should have a file'), | 62 | .withMessage('Should have a file'), |
66 | body('name') | 63 | body('name') |
67 | .trim() | 64 | .trim() |
68 | .custom(isVideoNameValid) | 65 | .custom(isVideoNameValid).withMessage( |
69 | .withMessage('Should have a valid name'), | 66 | `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long` |
67 | ), | ||
70 | body('channelId') | 68 | body('channelId') |
71 | .customSanitizer(toIntOrNull) | 69 | .customSanitizer(toIntOrNull) |
72 | .custom(isIdValid).withMessage('Should have correct video channel id'), | 70 | .custom(isIdValid).withMessage('Should have correct video channel id'), |
@@ -87,9 +85,11 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([ | |||
87 | if (!videoFile.duration) await addDurationToVideo(videoFile) | 85 | if (!videoFile.duration) await addDurationToVideo(videoFile) |
88 | } catch (err) { | 86 | } catch (err) { |
89 | logger.error('Invalid input file in videosAddLegacyValidator.', { err }) | 87 | logger.error('Invalid input file in videosAddLegacyValidator.', { err }) |
90 | res.status(HttpStatusCode.UNPROCESSABLE_ENTITY_422) | ||
91 | .json({ error: 'Video file unreadable.' }) | ||
92 | 88 | ||
89 | res.fail({ | ||
90 | status: HttpStatusCode.UNPROCESSABLE_ENTITY_422, | ||
91 | message: 'Video file unreadable.' | ||
92 | }) | ||
93 | return cleanUpReqFiles(req) | 93 | return cleanUpReqFiles(req) |
94 | } | 94 | } |
95 | 95 | ||
@@ -117,9 +117,11 @@ const videosAddResumableValidator = [ | |||
117 | if (!file.duration) await addDurationToVideo(file) | 117 | if (!file.duration) await addDurationToVideo(file) |
118 | } catch (err) { | 118 | } catch (err) { |
119 | logger.error('Invalid input file in videosAddResumableValidator.', { err }) | 119 | logger.error('Invalid input file in videosAddResumableValidator.', { err }) |
120 | res.status(HttpStatusCode.UNPROCESSABLE_ENTITY_422) | ||
121 | .json({ error: 'Video file unreadable.' }) | ||
122 | 120 | ||
121 | res.fail({ | ||
122 | status: HttpStatusCode.UNPROCESSABLE_ENTITY_422, | ||
123 | message: 'Video file unreadable.' | ||
124 | }) | ||
123 | return cleanup() | 125 | return cleanup() |
124 | } | 126 | } |
125 | 127 | ||
@@ -146,8 +148,9 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([ | |||
146 | .withMessage('Should have a valid filename'), | 148 | .withMessage('Should have a valid filename'), |
147 | body('name') | 149 | body('name') |
148 | .trim() | 150 | .trim() |
149 | .custom(isVideoNameValid) | 151 | .custom(isVideoNameValid).withMessage( |
150 | .withMessage('Should have a valid name'), | 152 | `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long` |
153 | ), | ||
151 | body('channelId') | 154 | body('channelId') |
152 | .customSanitizer(toIntOrNull) | 155 | .customSanitizer(toIntOrNull) |
153 | .custom(isIdValid).withMessage('Should have correct video channel id'), | 156 | .custom(isIdValid).withMessage('Should have correct video channel id'), |
@@ -192,11 +195,14 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([ | |||
192 | ]) | 195 | ]) |
193 | 196 | ||
194 | const videosUpdateValidator = getCommonVideoEditAttributes().concat([ | 197 | const videosUpdateValidator = getCommonVideoEditAttributes().concat([ |
195 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 198 | isValidVideoIdParam('id'), |
199 | |||
196 | body('name') | 200 | body('name') |
197 | .optional() | 201 | .optional() |
198 | .trim() | 202 | .trim() |
199 | .custom(isVideoNameValid).withMessage('Should have a valid name'), | 203 | .custom(isVideoNameValid).withMessage( |
204 | `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long` | ||
205 | ), | ||
200 | body('channelId') | 206 | body('channelId') |
201 | .optional() | 207 | .optional() |
202 | .customSanitizer(toIntOrNull) | 208 | .customSanitizer(toIntOrNull) |
@@ -238,20 +244,22 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R | |||
238 | const serverActor = await getServerActor() | 244 | const serverActor = await getServerActor() |
239 | if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next() | 245 | if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next() |
240 | 246 | ||
241 | return res.status(HttpStatusCode.FORBIDDEN_403) | 247 | return res.fail({ |
242 | .json({ | 248 | status: HttpStatusCode.FORBIDDEN_403, |
243 | errorCode: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS, | 249 | message: 'Cannot get this video regarding follow constraints', |
244 | error: 'Cannot get this video regarding follow constraints.', | 250 | type: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS, |
245 | originUrl: video.url | 251 | data: { |
246 | }) | 252 | originUrl: video.url |
253 | } | ||
254 | }) | ||
247 | } | 255 | } |
248 | 256 | ||
249 | const videosCustomGetValidator = ( | 257 | const videosCustomGetValidator = ( |
250 | fetchType: 'all' | 'only-video' | 'only-video-with-rights' | 'only-immutable-attributes', | 258 | fetchType: 'for-api' | 'all' | 'only-video' | 'only-immutable-attributes', |
251 | authenticateInQuery = false | 259 | authenticateInQuery = false |
252 | ) => { | 260 | ) => { |
253 | return [ | 261 | return [ |
254 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 262 | isValidVideoIdParam('id'), |
255 | 263 | ||
256 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 264 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
257 | logger.debug('Checking videosGet parameters', { parameters: req.params }) | 265 | logger.debug('Checking videosGet parameters', { parameters: req.params }) |
@@ -262,7 +270,7 @@ const videosCustomGetValidator = ( | |||
262 | // Controllers does not need to check video rights | 270 | // Controllers does not need to check video rights |
263 | if (fetchType === 'only-immutable-attributes') return next() | 271 | if (fetchType === 'only-immutable-attributes') return next() |
264 | 272 | ||
265 | const video = getVideoWithAttributes(res) as MVideoWithRights | 273 | const video = getVideoWithAttributes(res) as MVideoFullLight |
266 | 274 | ||
267 | // Video private or blacklisted | 275 | // Video private or blacklisted |
268 | if (video.requiresAuth()) { | 276 | if (video.requiresAuth()) { |
@@ -270,10 +278,12 @@ const videosCustomGetValidator = ( | |||
270 | 278 | ||
271 | const user = res.locals.oauth ? res.locals.oauth.token.User : null | 279 | const user = res.locals.oauth ? res.locals.oauth.token.User : null |
272 | 280 | ||
273 | // Only the owner or a user that have blacklist rights can see the video | 281 | // Only the owner or a user that have blocklist rights can see the video |
274 | if (!user || !user.canGetVideo(video)) { | 282 | if (!user || !user.canGetVideo(video)) { |
275 | return res.status(HttpStatusCode.FORBIDDEN_403) | 283 | return res.fail({ |
276 | .json({ error: 'Cannot get this private/internal or blacklisted video.' }) | 284 | status: HttpStatusCode.FORBIDDEN_403, |
285 | message: 'Cannot get this private/internal or blocklisted video' | ||
286 | }) | ||
277 | } | 287 | } |
278 | 288 | ||
279 | return next() | 289 | return next() |
@@ -287,7 +297,10 @@ const videosCustomGetValidator = ( | |||
287 | if (isUUIDValid(req.params.id)) return next() | 297 | if (isUUIDValid(req.params.id)) return next() |
288 | 298 | ||
289 | // Don't leak this unlisted video | 299 | // Don't leak this unlisted video |
290 | return res.status(HttpStatusCode.NOT_FOUND_404).end() | 300 | return res.fail({ |
301 | status: HttpStatusCode.NOT_FOUND_404, | ||
302 | message: 'Video not found' | ||
303 | }) | ||
291 | } | 304 | } |
292 | } | 305 | } |
293 | ] | 306 | ] |
@@ -297,8 +310,10 @@ const videosGetValidator = videosCustomGetValidator('all') | |||
297 | const videosDownloadValidator = videosCustomGetValidator('all', true) | 310 | const videosDownloadValidator = videosCustomGetValidator('all', true) |
298 | 311 | ||
299 | const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([ | 312 | const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([ |
300 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 313 | isValidVideoIdParam('id'), |
301 | param('videoFileId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'), | 314 | |
315 | param('videoFileId') | ||
316 | .custom(isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'), | ||
302 | 317 | ||
303 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 318 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
304 | logger.debug('Checking videoFileMetadataGet parameters', { parameters: req.params }) | 319 | logger.debug('Checking videoFileMetadataGet parameters', { parameters: req.params }) |
@@ -311,7 +326,7 @@ const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([ | |||
311 | ]) | 326 | ]) |
312 | 327 | ||
313 | const videosRemoveValidator = [ | 328 | const videosRemoveValidator = [ |
314 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 329 | isValidVideoIdParam('id'), |
315 | 330 | ||
316 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 331 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
317 | logger.debug('Checking videosRemove parameters', { parameters: req.params }) | 332 | logger.debug('Checking videosRemove parameters', { parameters: req.params }) |
@@ -326,74 +341,6 @@ const videosRemoveValidator = [ | |||
326 | } | 341 | } |
327 | ] | 342 | ] |
328 | 343 | ||
329 | const videosChangeOwnershipValidator = [ | ||
330 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
331 | |||
332 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
333 | logger.debug('Checking changeOwnership parameters', { parameters: req.params }) | ||
334 | |||
335 | if (areValidationErrors(req, res)) return | ||
336 | if (!await doesVideoExist(req.params.videoId, res)) return | ||
337 | |||
338 | // Check if the user who did the request is able to change the ownership of the video | ||
339 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return | ||
340 | |||
341 | const nextOwner = await AccountModel.loadLocalByName(req.body.username) | ||
342 | if (!nextOwner) { | ||
343 | res.status(HttpStatusCode.BAD_REQUEST_400) | ||
344 | .json({ error: 'Changing video ownership to a remote account is not supported yet' }) | ||
345 | |||
346 | return | ||
347 | } | ||
348 | res.locals.nextOwner = nextOwner | ||
349 | |||
350 | return next() | ||
351 | } | ||
352 | ] | ||
353 | |||
354 | const videosTerminateChangeOwnershipValidator = [ | ||
355 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
356 | |||
357 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
358 | logger.debug('Checking changeOwnership parameters', { parameters: req.params }) | ||
359 | |||
360 | if (areValidationErrors(req, res)) return | ||
361 | if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return | ||
362 | |||
363 | // Check if the user who did the request is able to change the ownership of the video | ||
364 | if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return | ||
365 | |||
366 | const videoChangeOwnership = res.locals.videoChangeOwnership | ||
367 | |||
368 | if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) { | ||
369 | res.status(HttpStatusCode.FORBIDDEN_403) | ||
370 | .json({ error: 'Ownership already accepted or refused' }) | ||
371 | return | ||
372 | } | ||
373 | |||
374 | return next() | ||
375 | } | ||
376 | ] | ||
377 | |||
378 | const videosAcceptChangeOwnershipValidator = [ | ||
379 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
380 | const body = req.body as VideoChangeOwnershipAccept | ||
381 | if (!await doesVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return | ||
382 | |||
383 | const user = res.locals.oauth.token.User | ||
384 | const videoChangeOwnership = res.locals.videoChangeOwnership | ||
385 | const isAble = await isAbleToUploadVideo(user.id, videoChangeOwnership.Video.getMaxQualityFile().size) | ||
386 | if (isAble === false) { | ||
387 | res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413) | ||
388 | .json({ error: 'The user video quota is exceeded with this video.' }) | ||
389 | |||
390 | return | ||
391 | } | ||
392 | |||
393 | return next() | ||
394 | } | ||
395 | ] | ||
396 | |||
397 | const videosOverviewValidator = [ | 344 | const videosOverviewValidator = [ |
398 | query('page') | 345 | query('page') |
399 | .optional() | 346 | .optional() |
@@ -455,7 +402,11 @@ function getCommonVideoEditAttributes () { | |||
455 | body('tags') | 402 | body('tags') |
456 | .optional() | 403 | .optional() |
457 | .customSanitizer(toValueOrNull) | 404 | .customSanitizer(toValueOrNull) |
458 | .custom(isVideoTagsValid).withMessage('Should have correct tags'), | 405 | .custom(isVideoTagsValid) |
406 | .withMessage( | ||
407 | `Should have an array of up to ${CONSTRAINTS_FIELDS.VIDEOS.TAGS.max} tags between ` + | ||
408 | `${CONSTRAINTS_FIELDS.VIDEOS.TAG.min} and ${CONSTRAINTS_FIELDS.VIDEOS.TAG.max} characters each` | ||
409 | ), | ||
459 | body('commentsEnabled') | 410 | body('commentsEnabled') |
460 | .optional() | 411 | .optional() |
461 | .customSanitizer(toBooleanOrNull) | 412 | .customSanitizer(toBooleanOrNull) |
@@ -473,7 +424,7 @@ function getCommonVideoEditAttributes () { | |||
473 | .customSanitizer(toValueOrNull), | 424 | .customSanitizer(toValueOrNull), |
474 | body('scheduleUpdate.updateAt') | 425 | body('scheduleUpdate.updateAt') |
475 | .optional() | 426 | .optional() |
476 | .custom(isDateValid).withMessage('Should have a valid schedule update date'), | 427 | .custom(isDateValid).withMessage('Should have a schedule update date that conforms to ISO 8601'), |
477 | body('scheduleUpdate.privacy') | 428 | body('scheduleUpdate.privacy') |
478 | .optional() | 429 | .optional() |
479 | .customSanitizer(toIntOrNull) | 430 | .customSanitizer(toIntOrNull) |
@@ -530,9 +481,10 @@ const commonVideosFiltersValidator = [ | |||
530 | (req.query.filter === 'all-local' || req.query.filter === 'all') && | 481 | (req.query.filter === 'all-local' || req.query.filter === 'all') && |
531 | (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false) | 482 | (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false) |
532 | ) { | 483 | ) { |
533 | res.status(HttpStatusCode.UNAUTHORIZED_401) | 484 | res.fail({ |
534 | .json({ error: 'You are not allowed to see all local videos.' }) | 485 | status: HttpStatusCode.UNAUTHORIZED_401, |
535 | 486 | message: 'You are not allowed to see all local videos.' | |
487 | }) | ||
536 | return | 488 | return |
537 | } | 489 | } |
538 | 490 | ||
@@ -555,10 +507,6 @@ export { | |||
555 | videosCustomGetValidator, | 507 | videosCustomGetValidator, |
556 | videosRemoveValidator, | 508 | videosRemoveValidator, |
557 | 509 | ||
558 | videosChangeOwnershipValidator, | ||
559 | videosTerminateChangeOwnershipValidator, | ||
560 | videosAcceptChangeOwnershipValidator, | ||
561 | |||
562 | getCommonVideoEditAttributes, | 510 | getCommonVideoEditAttributes, |
563 | 511 | ||
564 | commonVideosFiltersValidator, | 512 | commonVideosFiltersValidator, |
@@ -573,9 +521,7 @@ function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) | |||
573 | if (!req.body.scheduleUpdate.updateAt) { | 521 | if (!req.body.scheduleUpdate.updateAt) { |
574 | logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.') | 522 | logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.') |
575 | 523 | ||
576 | res.status(HttpStatusCode.BAD_REQUEST_400) | 524 | res.fail({ message: 'Schedule update at is mandatory.' }) |
577 | .json({ error: 'Schedule update at is mandatory.' }) | ||
578 | |||
579 | return true | 525 | return true |
580 | } | 526 | } |
581 | } | 527 | } |
@@ -597,26 +543,29 @@ async function commonVideoChecksPass (parameters: { | |||
597 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return false | 543 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return false |
598 | 544 | ||
599 | if (!isVideoFileMimeTypeValid(files)) { | 545 | if (!isVideoFileMimeTypeValid(files)) { |
600 | res.status(HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415) | 546 | res.fail({ |
601 | .json({ | 547 | status: HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415, |
602 | error: 'This file is not supported. Please, make sure it is of the following type: ' + | 548 | message: 'This file is not supported. Please, make sure it is of the following type: ' + |
603 | CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') | 549 | CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') |
604 | }) | 550 | }) |
605 | |||
606 | return false | 551 | return false |
607 | } | 552 | } |
608 | 553 | ||
609 | if (!isVideoFileSizeValid(videoFileSize.toString())) { | 554 | if (!isVideoFileSizeValid(videoFileSize.toString())) { |
610 | res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413) | 555 | res.fail({ |
611 | .json({ error: 'This file is too large. It exceeds the maximum file size authorized.' }) | 556 | status: HttpStatusCode.PAYLOAD_TOO_LARGE_413, |
612 | 557 | message: 'This file is too large. It exceeds the maximum file size authorized.', | |
558 | type: ServerErrorCode.MAX_FILE_SIZE_REACHED | ||
559 | }) | ||
613 | return false | 560 | return false |
614 | } | 561 | } |
615 | 562 | ||
616 | if (await isAbleToUploadVideo(user.id, videoFileSize) === false) { | 563 | if (await isAbleToUploadVideo(user.id, videoFileSize) === false) { |
617 | res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413) | 564 | res.fail({ |
618 | .json({ error: 'The user video quota is exceeded with this video.' }) | 565 | status: HttpStatusCode.PAYLOAD_TOO_LARGE_413, |
619 | 566 | message: 'The user video quota is exceeded with this video.', | |
567 | type: ServerErrorCode.QUOTA_REACHED | ||
568 | }) | ||
620 | return false | 569 | return false |
621 | } | 570 | } |
622 | 571 | ||
@@ -642,9 +591,10 @@ export async function isVideoAccepted ( | |||
642 | 591 | ||
643 | if (!acceptedResult || acceptedResult.accepted !== true) { | 592 | if (!acceptedResult || acceptedResult.accepted !== true) { |
644 | logger.info('Refused local video.', { acceptedResult, acceptParameters }) | 593 | logger.info('Refused local video.', { acceptedResult, acceptParameters }) |
645 | res.status(HttpStatusCode.FORBIDDEN_403) | 594 | res.fail({ |
646 | .json({ error: acceptedResult.errorMessage || 'Refused local video' }) | 595 | status: HttpStatusCode.FORBIDDEN_403, |
647 | 596 | message: acceptedResult.errorMessage || 'Refused local video' | |
597 | }) | ||
648 | return false | 598 | return false |
649 | } | 599 | } |
650 | 600 | ||
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts index a71422ed8..bcdd136c6 100644 --- a/server/middlewares/validators/webfinger.ts +++ b/server/middlewares/validators/webfinger.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query } from 'express-validator' | 2 | import { query } from 'express-validator' |
3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
3 | import { isWebfingerLocalResourceValid } from '../../helpers/custom-validators/webfinger' | 4 | import { isWebfingerLocalResourceValid } from '../../helpers/custom-validators/webfinger' |
4 | import { logger } from '../../helpers/logger' | ||
5 | import { ActorModel } from '../../models/activitypub/actor' | ||
6 | import { areValidationErrors } from './utils' | ||
7 | import { getHostWithPort } from '../../helpers/express-utils' | 5 | import { getHostWithPort } from '../../helpers/express-utils' |
8 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 6 | import { logger } from '../../helpers/logger' |
7 | import { ActorModel } from '../../models/actor/actor' | ||
8 | import { areValidationErrors } from './shared' | ||
9 | 9 | ||
10 | const webfingerValidator = [ | 10 | const webfingerValidator = [ |
11 | query('resource').custom(isWebfingerLocalResourceValid).withMessage('Should have a valid webfinger resource'), | 11 | query('resource').custom(isWebfingerLocalResourceValid).withMessage('Should have a valid webfinger resource'), |
@@ -21,9 +21,10 @@ const webfingerValidator = [ | |||
21 | 21 | ||
22 | const actor = await ActorModel.loadLocalUrlByName(name) | 22 | const actor = await ActorModel.loadLocalUrlByName(name) |
23 | if (!actor) { | 23 | if (!actor) { |
24 | return res.status(HttpStatusCode.NOT_FOUND_404) | 24 | return res.fail({ |
25 | .send({ error: 'Actor not found' }) | 25 | status: HttpStatusCode.NOT_FOUND_404, |
26 | .end() | 26 | message: 'Actor not found' |
27 | }) | ||
27 | } | 28 | } |
28 | 29 | ||
29 | res.locals.actorUrl = actor | 30 | res.locals.actorUrl = actor |