diff options
Diffstat (limited to 'server/middlewares/validators/shared')
16 files changed, 0 insertions, 964 deletions
diff --git a/server/middlewares/validators/shared/abuses.ts b/server/middlewares/validators/shared/abuses.ts deleted file mode 100644 index 2c988f9ec..000000000 --- a/server/middlewares/validators/shared/abuses.ts +++ /dev/null | |||
@@ -1,26 +0,0 @@ | |||
1 | import { Response } from 'express' | ||
2 | import { AbuseModel } from '@server/models/abuse/abuse' | ||
3 | import { HttpStatusCode } from '@shared/models' | ||
4 | import { forceNumber } from '@shared/core-utils' | ||
5 | |||
6 | async function doesAbuseExist (abuseId: number | string, res: Response) { | ||
7 | const abuse = await AbuseModel.loadByIdWithReporter(forceNumber(abuseId)) | ||
8 | |||
9 | if (!abuse) { | ||
10 | res.fail({ | ||
11 | status: HttpStatusCode.NOT_FOUND_404, | ||
12 | message: 'Abuse not found' | ||
13 | }) | ||
14 | |||
15 | return false | ||
16 | } | ||
17 | |||
18 | res.locals.abuse = abuse | ||
19 | return true | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | export { | ||
25 | doesAbuseExist | ||
26 | } | ||
diff --git a/server/middlewares/validators/shared/accounts.ts b/server/middlewares/validators/shared/accounts.ts deleted file mode 100644 index 72b0e235e..000000000 --- a/server/middlewares/validators/shared/accounts.ts +++ /dev/null | |||
@@ -1,66 +0,0 @@ | |||
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 { forceNumber } from '@shared/core-utils' | ||
6 | import { HttpStatusCode } from '@shared/models' | ||
7 | |||
8 | function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) { | ||
9 | const promise = AccountModel.load(forceNumber(id)) | ||
10 | |||
11 | return doesAccountExist(promise, res, sendNotFound) | ||
12 | } | ||
13 | |||
14 | function doesLocalAccountNameExist (name: string, res: Response, sendNotFound = true) { | ||
15 | const promise = AccountModel.loadLocalByName(name) | ||
16 | |||
17 | return doesAccountExist(promise, res, sendNotFound) | ||
18 | } | ||
19 | |||
20 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { | ||
21 | const promise = AccountModel.loadByNameWithHost(nameWithDomain) | ||
22 | |||
23 | return doesAccountExist(promise, res, sendNotFound) | ||
24 | } | ||
25 | |||
26 | async function doesAccountExist (p: Promise<MAccountDefault>, res: Response, sendNotFound: boolean) { | ||
27 | const account = await p | ||
28 | |||
29 | if (!account) { | ||
30 | if (sendNotFound === true) { | ||
31 | res.fail({ | ||
32 | status: HttpStatusCode.NOT_FOUND_404, | ||
33 | message: 'Account not found' | ||
34 | }) | ||
35 | } | ||
36 | return false | ||
37 | } | ||
38 | |||
39 | res.locals.account = account | ||
40 | return true | ||
41 | } | ||
42 | |||
43 | async function doesUserFeedTokenCorrespond (id: number, token: string, res: Response) { | ||
44 | const user = await UserModel.loadByIdWithChannels(forceNumber(id)) | ||
45 | |||
46 | if (token !== user.feedToken) { | ||
47 | res.fail({ | ||
48 | status: HttpStatusCode.FORBIDDEN_403, | ||
49 | message: 'User and token mismatch' | ||
50 | }) | ||
51 | return false | ||
52 | } | ||
53 | |||
54 | res.locals.user = user | ||
55 | return true | ||
56 | } | ||
57 | |||
58 | // --------------------------------------------------------------------------- | ||
59 | |||
60 | export { | ||
61 | doesAccountIdExist, | ||
62 | doesLocalAccountNameExist, | ||
63 | doesAccountNameWithHostExist, | ||
64 | doesAccountExist, | ||
65 | doesUserFeedTokenCorrespond | ||
66 | } | ||
diff --git a/server/middlewares/validators/shared/index.ts b/server/middlewares/validators/shared/index.ts deleted file mode 100644 index e5cff2dda..000000000 --- a/server/middlewares/validators/shared/index.ts +++ /dev/null | |||
@@ -1,14 +0,0 @@ | |||
1 | export * from './abuses' | ||
2 | export * from './accounts' | ||
3 | export * from './users' | ||
4 | export * from './utils' | ||
5 | export * from './video-blacklists' | ||
6 | export * from './video-captions' | ||
7 | export * from './video-channels' | ||
8 | export * from './video-channel-syncs' | ||
9 | export * from './video-comments' | ||
10 | export * from './video-imports' | ||
11 | export * from './video-ownerships' | ||
12 | export * from './video-playlists' | ||
13 | export * from './video-passwords' | ||
14 | export * from './videos' | ||
diff --git a/server/middlewares/validators/shared/user-registrations.ts b/server/middlewares/validators/shared/user-registrations.ts deleted file mode 100644 index dbc7dda06..000000000 --- a/server/middlewares/validators/shared/user-registrations.ts +++ /dev/null | |||
@@ -1,60 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { UserRegistrationModel } from '@server/models/user/user-registration' | ||
3 | import { MRegistration } from '@server/types/models' | ||
4 | import { forceNumber, pick } from '@shared/core-utils' | ||
5 | import { HttpStatusCode } from '@shared/models' | ||
6 | |||
7 | function checkRegistrationIdExist (idArg: number | string, res: express.Response) { | ||
8 | const id = forceNumber(idArg) | ||
9 | return checkRegistrationExist(() => UserRegistrationModel.load(id), res) | ||
10 | } | ||
11 | |||
12 | function checkRegistrationEmailExist (email: string, res: express.Response, abortResponse = true) { | ||
13 | return checkRegistrationExist(() => UserRegistrationModel.loadByEmail(email), res, abortResponse) | ||
14 | } | ||
15 | |||
16 | async function checkRegistrationHandlesDoNotAlreadyExist (options: { | ||
17 | username: string | ||
18 | channelHandle: string | ||
19 | email: string | ||
20 | res: express.Response | ||
21 | }) { | ||
22 | const { res } = options | ||
23 | |||
24 | const registration = await UserRegistrationModel.loadByEmailOrHandle(pick(options, [ 'username', 'email', 'channelHandle' ])) | ||
25 | |||
26 | if (registration) { | ||
27 | res.fail({ | ||
28 | status: HttpStatusCode.CONFLICT_409, | ||
29 | message: 'Registration with this username, channel name or email already exists.' | ||
30 | }) | ||
31 | return false | ||
32 | } | ||
33 | |||
34 | return true | ||
35 | } | ||
36 | |||
37 | async function checkRegistrationExist (finder: () => Promise<MRegistration>, res: express.Response, abortResponse = true) { | ||
38 | const registration = await finder() | ||
39 | |||
40 | if (!registration) { | ||
41 | if (abortResponse === true) { | ||
42 | res.fail({ | ||
43 | status: HttpStatusCode.NOT_FOUND_404, | ||
44 | message: 'User not found' | ||
45 | }) | ||
46 | } | ||
47 | |||
48 | return false | ||
49 | } | ||
50 | |||
51 | res.locals.userRegistration = registration | ||
52 | return true | ||
53 | } | ||
54 | |||
55 | export { | ||
56 | checkRegistrationIdExist, | ||
57 | checkRegistrationEmailExist, | ||
58 | checkRegistrationHandlesDoNotAlreadyExist, | ||
59 | checkRegistrationExist | ||
60 | } | ||
diff --git a/server/middlewares/validators/shared/users.ts b/server/middlewares/validators/shared/users.ts deleted file mode 100644 index 030adc9f7..000000000 --- a/server/middlewares/validators/shared/users.ts +++ /dev/null | |||
@@ -1,63 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { ActorModel } from '@server/models/actor/actor' | ||
3 | import { UserModel } from '@server/models/user/user' | ||
4 | import { MUserDefault } from '@server/types/models' | ||
5 | import { forceNumber } from '@shared/core-utils' | ||
6 | import { HttpStatusCode } from '@shared/models' | ||
7 | |||
8 | function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) { | ||
9 | const id = forceNumber(idArg) | ||
10 | return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res) | ||
11 | } | ||
12 | |||
13 | function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) { | ||
14 | return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse) | ||
15 | } | ||
16 | |||
17 | async function checkUserNameOrEmailDoNotAlreadyExist (username: string, email: string, res: express.Response) { | ||
18 | const user = await UserModel.loadByUsernameOrEmail(username, email) | ||
19 | |||
20 | if (user) { | ||
21 | res.fail({ | ||
22 | status: HttpStatusCode.CONFLICT_409, | ||
23 | message: 'User with this username or email already exists.' | ||
24 | }) | ||
25 | return false | ||
26 | } | ||
27 | |||
28 | const actor = await ActorModel.loadLocalByName(username) | ||
29 | if (actor) { | ||
30 | res.fail({ | ||
31 | status: HttpStatusCode.CONFLICT_409, | ||
32 | message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' | ||
33 | }) | ||
34 | return false | ||
35 | } | ||
36 | |||
37 | return true | ||
38 | } | ||
39 | |||
40 | async function checkUserExist (finder: () => Promise<MUserDefault>, res: express.Response, abortResponse = true) { | ||
41 | const user = await finder() | ||
42 | |||
43 | if (!user) { | ||
44 | if (abortResponse === true) { | ||
45 | res.fail({ | ||
46 | status: HttpStatusCode.NOT_FOUND_404, | ||
47 | message: 'User not found' | ||
48 | }) | ||
49 | } | ||
50 | |||
51 | return false | ||
52 | } | ||
53 | |||
54 | res.locals.user = user | ||
55 | return true | ||
56 | } | ||
57 | |||
58 | export { | ||
59 | checkUserIdExist, | ||
60 | checkUserEmailExist, | ||
61 | checkUserNameOrEmailDoNotAlreadyExist, | ||
62 | checkUserExist | ||
63 | } | ||
diff --git a/server/middlewares/validators/shared/utils.ts b/server/middlewares/validators/shared/utils.ts deleted file mode 100644 index f39128fdd..000000000 --- a/server/middlewares/validators/shared/utils.ts +++ /dev/null | |||
@@ -1,69 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { param, validationResult } from 'express-validator' | ||
3 | import { isIdOrUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc' | ||
4 | import { logger } from '../../../helpers/logger' | ||
5 | |||
6 | function areValidationErrors ( | ||
7 | req: express.Request, | ||
8 | res: express.Response, | ||
9 | options: { | ||
10 | omitLog?: boolean | ||
11 | omitBodyLog?: boolean | ||
12 | tags?: string[] | ||
13 | } = {}) { | ||
14 | const { omitLog = false, omitBodyLog = false, tags = [] } = options | ||
15 | |||
16 | if (!omitLog) { | ||
17 | logger.debug( | ||
18 | 'Checking %s - %s parameters', | ||
19 | req.method, req.originalUrl, | ||
20 | { | ||
21 | body: omitBodyLog | ||
22 | ? 'omitted' | ||
23 | : req.body, | ||
24 | params: req.params, | ||
25 | query: req.query, | ||
26 | files: req.files, | ||
27 | tags | ||
28 | } | ||
29 | ) | ||
30 | } | ||
31 | |||
32 | const errors = validationResult(req) | ||
33 | |||
34 | if (!errors.isEmpty()) { | ||
35 | logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors.mapped() }) | ||
36 | |||
37 | res.fail({ | ||
38 | message: 'Incorrect request parameters: ' + Object.keys(errors.mapped()).join(', '), | ||
39 | instance: req.originalUrl, | ||
40 | data: { | ||
41 | 'invalid-params': errors.mapped() | ||
42 | } | ||
43 | }) | ||
44 | |||
45 | return true | ||
46 | } | ||
47 | |||
48 | return false | ||
49 | } | ||
50 | |||
51 | function isValidVideoIdParam (paramName: string) { | ||
52 | return param(paramName) | ||
53 | .customSanitizer(toCompleteUUID) | ||
54 | .custom(isIdOrUUIDValid).withMessage('Should have a valid video id (id, short UUID or UUID)') | ||
55 | } | ||
56 | |||
57 | function isValidPlaylistIdParam (paramName: string) { | ||
58 | return param(paramName) | ||
59 | .customSanitizer(toCompleteUUID) | ||
60 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id (id, short UUID or UUID)') | ||
61 | } | ||
62 | |||
63 | // --------------------------------------------------------------------------- | ||
64 | |||
65 | export { | ||
66 | areValidationErrors, | ||
67 | isValidVideoIdParam, | ||
68 | isValidPlaylistIdParam | ||
69 | } | ||
diff --git a/server/middlewares/validators/shared/video-blacklists.ts b/server/middlewares/validators/shared/video-blacklists.ts deleted file mode 100644 index f85b39b23..000000000 --- a/server/middlewares/validators/shared/video-blacklists.ts +++ /dev/null | |||
@@ -1,24 +0,0 @@ | |||
1 | import { Response } from 'express' | ||
2 | import { VideoBlacklistModel } from '@server/models/video/video-blacklist' | ||
3 | import { HttpStatusCode } from '@shared/models' | ||
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 deleted file mode 100644 index 831b366ea..000000000 --- a/server/middlewares/validators/shared/video-captions.ts +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
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/models' | ||
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-channel-syncs.ts b/server/middlewares/validators/shared/video-channel-syncs.ts deleted file mode 100644 index a6e51eb97..000000000 --- a/server/middlewares/validators/shared/video-channel-syncs.ts +++ /dev/null | |||
@@ -1,24 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync' | ||
3 | import { HttpStatusCode } from '@shared/models' | ||
4 | |||
5 | async function doesVideoChannelSyncIdExist (id: number, res: express.Response) { | ||
6 | const sync = await VideoChannelSyncModel.loadWithChannel(+id) | ||
7 | |||
8 | if (!sync) { | ||
9 | res.fail({ | ||
10 | status: HttpStatusCode.NOT_FOUND_404, | ||
11 | message: 'Video channel sync not found' | ||
12 | }) | ||
13 | return false | ||
14 | } | ||
15 | |||
16 | res.locals.videoChannelSync = sync | ||
17 | return true | ||
18 | } | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | export { | ||
23 | doesVideoChannelSyncIdExist | ||
24 | } | ||
diff --git a/server/middlewares/validators/shared/video-channels.ts b/server/middlewares/validators/shared/video-channels.ts deleted file mode 100644 index bed9f5dbe..000000000 --- a/server/middlewares/validators/shared/video-channels.ts +++ /dev/null | |||
@@ -1,36 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
3 | import { MChannelBannerAccountDefault } from '@server/types/models' | ||
4 | import { HttpStatusCode } from '@shared/models' | ||
5 | |||
6 | async function doesVideoChannelIdExist (id: number, res: express.Response) { | ||
7 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) | ||
8 | |||
9 | return processVideoChannelExist(videoChannel, res) | ||
10 | } | ||
11 | |||
12 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | ||
13 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) | ||
14 | |||
15 | return processVideoChannelExist(videoChannel, res) | ||
16 | } | ||
17 | |||
18 | // --------------------------------------------------------------------------- | ||
19 | |||
20 | export { | ||
21 | doesVideoChannelIdExist, | ||
22 | doesVideoChannelNameWithHostExist | ||
23 | } | ||
24 | |||
25 | function processVideoChannelExist (videoChannel: MChannelBannerAccountDefault, res: express.Response) { | ||
26 | if (!videoChannel) { | ||
27 | res.fail({ | ||
28 | status: HttpStatusCode.NOT_FOUND_404, | ||
29 | message: 'Video channel not found' | ||
30 | }) | ||
31 | return false | ||
32 | } | ||
33 | |||
34 | res.locals.videoChannel = videoChannel | ||
35 | return true | ||
36 | } | ||
diff --git a/server/middlewares/validators/shared/video-comments.ts b/server/middlewares/validators/shared/video-comments.ts deleted file mode 100644 index 0961b3ec9..000000000 --- a/server/middlewares/validators/shared/video-comments.ts +++ /dev/null | |||
@@ -1,80 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { VideoCommentModel } from '@server/models/video/video-comment' | ||
3 | import { MVideoId } from '@server/types/models' | ||
4 | import { forceNumber } from '@shared/core-utils' | ||
5 | import { HttpStatusCode, ServerErrorCode } from '@shared/models' | ||
6 | |||
7 | async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) { | ||
8 | const id = forceNumber(idArg) | ||
9 | const videoComment = await VideoCommentModel.loadById(id) | ||
10 | |||
11 | if (!videoComment) { | ||
12 | res.fail({ | ||
13 | status: HttpStatusCode.NOT_FOUND_404, | ||
14 | message: 'Video comment thread not found' | ||
15 | }) | ||
16 | return false | ||
17 | } | ||
18 | |||
19 | if (videoComment.videoId !== video.id) { | ||
20 | res.fail({ | ||
21 | type: ServerErrorCode.COMMENT_NOT_ASSOCIATED_TO_VIDEO, | ||
22 | message: 'Video comment is not associated to this video.' | ||
23 | }) | ||
24 | return false | ||
25 | } | ||
26 | |||
27 | if (videoComment.inReplyToCommentId !== null) { | ||
28 | res.fail({ message: 'Video comment is not a thread.' }) | ||
29 | return false | ||
30 | } | ||
31 | |||
32 | res.locals.videoCommentThread = videoComment | ||
33 | return true | ||
34 | } | ||
35 | |||
36 | async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) { | ||
37 | const id = forceNumber(idArg) | ||
38 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) | ||
39 | |||
40 | if (!videoComment) { | ||
41 | res.fail({ | ||
42 | status: HttpStatusCode.NOT_FOUND_404, | ||
43 | message: 'Video comment thread not found' | ||
44 | }) | ||
45 | return false | ||
46 | } | ||
47 | |||
48 | if (videoComment.videoId !== video.id) { | ||
49 | res.fail({ | ||
50 | type: ServerErrorCode.COMMENT_NOT_ASSOCIATED_TO_VIDEO, | ||
51 | message: 'Video comment is not associated to this video.' | ||
52 | }) | ||
53 | return false | ||
54 | } | ||
55 | |||
56 | res.locals.videoCommentFull = videoComment | ||
57 | return true | ||
58 | } | ||
59 | |||
60 | async function doesCommentIdExist (idArg: number | string, res: express.Response) { | ||
61 | const id = forceNumber(idArg) | ||
62 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) | ||
63 | |||
64 | if (!videoComment) { | ||
65 | res.fail({ | ||
66 | status: HttpStatusCode.NOT_FOUND_404, | ||
67 | message: 'Video comment thread not found' | ||
68 | }) | ||
69 | return false | ||
70 | } | ||
71 | |||
72 | res.locals.videoCommentFull = videoComment | ||
73 | return true | ||
74 | } | ||
75 | |||
76 | export { | ||
77 | doesVideoCommentThreadExist, | ||
78 | doesVideoCommentExist, | ||
79 | doesCommentIdExist | ||
80 | } | ||
diff --git a/server/middlewares/validators/shared/video-imports.ts b/server/middlewares/validators/shared/video-imports.ts deleted file mode 100644 index 69fda4b32..000000000 --- a/server/middlewares/validators/shared/video-imports.ts +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { VideoImportModel } from '@server/models/video/video-import' | ||
3 | import { HttpStatusCode } from '@shared/models' | ||
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 deleted file mode 100644 index 33ac9c8b6..000000000 --- a/server/middlewares/validators/shared/video-ownerships.ts +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership' | ||
3 | import { forceNumber } from '@shared/core-utils' | ||
4 | import { HttpStatusCode } from '@shared/models' | ||
5 | |||
6 | async function doesChangeVideoOwnershipExist (idArg: number | string, res: express.Response) { | ||
7 | const id = forceNumber(idArg) | ||
8 | const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) | ||
9 | |||
10 | if (!videoChangeOwnership) { | ||
11 | res.fail({ | ||
12 | status: HttpStatusCode.NOT_FOUND_404, | ||
13 | message: 'Video change ownership not found' | ||
14 | }) | ||
15 | return false | ||
16 | } | ||
17 | |||
18 | res.locals.videoChangeOwnership = videoChangeOwnership | ||
19 | |||
20 | return true | ||
21 | } | ||
22 | |||
23 | export { | ||
24 | doesChangeVideoOwnershipExist | ||
25 | } | ||
diff --git a/server/middlewares/validators/shared/video-passwords.ts b/server/middlewares/validators/shared/video-passwords.ts deleted file mode 100644 index efcc95dc4..000000000 --- a/server/middlewares/validators/shared/video-passwords.ts +++ /dev/null | |||
@@ -1,80 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { HttpStatusCode, UserRight, VideoPrivacy } from '@shared/models' | ||
3 | import { forceNumber } from '@shared/core-utils' | ||
4 | import { VideoPasswordModel } from '@server/models/video/video-password' | ||
5 | import { header } from 'express-validator' | ||
6 | import { getVideoWithAttributes } from '@server/helpers/video' | ||
7 | |||
8 | function isValidVideoPasswordHeader () { | ||
9 | return header('x-peertube-video-password') | ||
10 | .optional() | ||
11 | .isString() | ||
12 | } | ||
13 | |||
14 | function checkVideoIsPasswordProtected (res: express.Response) { | ||
15 | const video = getVideoWithAttributes(res) | ||
16 | if (video.privacy !== VideoPrivacy.PASSWORD_PROTECTED) { | ||
17 | res.fail({ | ||
18 | status: HttpStatusCode.BAD_REQUEST_400, | ||
19 | message: 'Video is not password protected' | ||
20 | }) | ||
21 | return false | ||
22 | } | ||
23 | |||
24 | return true | ||
25 | } | ||
26 | |||
27 | async function doesVideoPasswordExist (idArg: number | string, res: express.Response) { | ||
28 | const video = getVideoWithAttributes(res) | ||
29 | const id = forceNumber(idArg) | ||
30 | const videoPassword = await VideoPasswordModel.loadByIdAndVideo({ id, videoId: video.id }) | ||
31 | |||
32 | if (!videoPassword) { | ||
33 | res.fail({ | ||
34 | status: HttpStatusCode.NOT_FOUND_404, | ||
35 | message: 'Video password not found' | ||
36 | }) | ||
37 | return false | ||
38 | } | ||
39 | |||
40 | res.locals.videoPassword = videoPassword | ||
41 | |||
42 | return true | ||
43 | } | ||
44 | |||
45 | async function isVideoPasswordDeletable (res: express.Response) { | ||
46 | const user = res.locals.oauth.token.User | ||
47 | const userAccount = user.Account | ||
48 | const video = res.locals.videoAll | ||
49 | |||
50 | // Check if the user who did the request is able to delete the video passwords | ||
51 | if ( | ||
52 | user.hasRight(UserRight.UPDATE_ANY_VIDEO) === false && // Not a moderator | ||
53 | video.VideoChannel.accountId !== userAccount.id // Not the video owner | ||
54 | ) { | ||
55 | res.fail({ | ||
56 | status: HttpStatusCode.FORBIDDEN_403, | ||
57 | message: 'Cannot remove passwords of another user\'s video' | ||
58 | }) | ||
59 | return false | ||
60 | } | ||
61 | |||
62 | const passwordCount = await VideoPasswordModel.countByVideoId(video.id) | ||
63 | |||
64 | if (passwordCount <= 1) { | ||
65 | res.fail({ | ||
66 | status: HttpStatusCode.BAD_REQUEST_400, | ||
67 | message: 'Cannot delete the last password of the protected video' | ||
68 | }) | ||
69 | return false | ||
70 | } | ||
71 | |||
72 | return true | ||
73 | } | ||
74 | |||
75 | export { | ||
76 | isValidVideoPasswordHeader, | ||
77 | checkVideoIsPasswordProtected as isVideoPasswordProtected, | ||
78 | doesVideoPasswordExist, | ||
79 | isVideoPasswordDeletable | ||
80 | } | ||
diff --git a/server/middlewares/validators/shared/video-playlists.ts b/server/middlewares/validators/shared/video-playlists.ts deleted file mode 100644 index 4342fe552..000000000 --- a/server/middlewares/validators/shared/video-playlists.ts +++ /dev/null | |||
@@ -1,39 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { VideoPlaylistModel } from '@server/models/video/video-playlist' | ||
3 | import { MVideoPlaylist } from '@server/types/models' | ||
4 | import { HttpStatusCode } from '@shared/models' | ||
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 deleted file mode 100644 index 9a7497007..000000000 --- a/server/middlewares/validators/shared/videos.ts +++ /dev/null | |||
@@ -1,311 +0,0 @@ | |||
1 | import { Request, Response } from 'express' | ||
2 | import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' | ||
3 | import { isAbleToUploadVideo } from '@server/lib/user' | ||
4 | import { VideoTokensManager } from '@server/lib/video-tokens-manager' | ||
5 | import { authenticatePromise } from '@server/middlewares/auth' | ||
6 | import { VideoModel } from '@server/models/video/video' | ||
7 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
8 | import { VideoFileModel } from '@server/models/video/video-file' | ||
9 | import { | ||
10 | MUser, | ||
11 | MUserAccountId, | ||
12 | MUserId, | ||
13 | MVideo, | ||
14 | MVideoAccountLight, | ||
15 | MVideoFormattableDetails, | ||
16 | MVideoFullLight, | ||
17 | MVideoId, | ||
18 | MVideoImmutable, | ||
19 | MVideoThumbnail, | ||
20 | MVideoWithRights | ||
21 | } from '@server/types/models' | ||
22 | import { HttpStatusCode, ServerErrorCode, UserRight, VideoPrivacy } from '@shared/models' | ||
23 | import { VideoPasswordModel } from '@server/models/video/video-password' | ||
24 | import { exists } from '@server/helpers/custom-validators/misc' | ||
25 | |||
26 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') { | ||
27 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | ||
28 | |||
29 | const video = await loadVideo(id, fetchType, userId) | ||
30 | |||
31 | if (!video) { | ||
32 | res.fail({ | ||
33 | status: HttpStatusCode.NOT_FOUND_404, | ||
34 | message: 'Video not found' | ||
35 | }) | ||
36 | |||
37 | return false | ||
38 | } | ||
39 | |||
40 | switch (fetchType) { | ||
41 | case 'for-api': | ||
42 | res.locals.videoAPI = video as MVideoFormattableDetails | ||
43 | break | ||
44 | |||
45 | case 'all': | ||
46 | res.locals.videoAll = video as MVideoFullLight | ||
47 | break | ||
48 | |||
49 | case 'only-immutable-attributes': | ||
50 | res.locals.onlyImmutableVideo = video as MVideoImmutable | ||
51 | break | ||
52 | |||
53 | case 'id': | ||
54 | res.locals.videoId = video as MVideoId | ||
55 | break | ||
56 | |||
57 | case 'only-video': | ||
58 | res.locals.onlyVideo = video as MVideoThumbnail | ||
59 | break | ||
60 | } | ||
61 | |||
62 | return true | ||
63 | } | ||
64 | |||
65 | // --------------------------------------------------------------------------- | ||
66 | |||
67 | async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) { | ||
68 | if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) { | ||
69 | res.fail({ | ||
70 | status: HttpStatusCode.NOT_FOUND_404, | ||
71 | message: 'VideoFile matching Video not found' | ||
72 | }) | ||
73 | return false | ||
74 | } | ||
75 | |||
76 | return true | ||
77 | } | ||
78 | |||
79 | // --------------------------------------------------------------------------- | ||
80 | |||
81 | async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { | ||
82 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) | ||
83 | |||
84 | if (videoChannel === null) { | ||
85 | res.fail({ message: 'Unknown video "video channel" for this instance.' }) | ||
86 | return false | ||
87 | } | ||
88 | |||
89 | // Don't check account id if the user can update any video | ||
90 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { | ||
91 | res.locals.videoChannel = videoChannel | ||
92 | return true | ||
93 | } | ||
94 | |||
95 | if (videoChannel.Account.id !== user.Account.id) { | ||
96 | res.fail({ | ||
97 | message: 'Unknown video "video channel" for this account.' | ||
98 | }) | ||
99 | return false | ||
100 | } | ||
101 | |||
102 | res.locals.videoChannel = videoChannel | ||
103 | return true | ||
104 | } | ||
105 | |||
106 | // --------------------------------------------------------------------------- | ||
107 | |||
108 | async function checkCanSeeVideo (options: { | ||
109 | req: Request | ||
110 | res: Response | ||
111 | paramId: string | ||
112 | video: MVideo | ||
113 | }) { | ||
114 | const { req, res, video, paramId } = options | ||
115 | |||
116 | if (video.requiresUserAuth({ urlParamId: paramId, checkBlacklist: true })) { | ||
117 | return checkCanSeeUserAuthVideo({ req, res, video }) | ||
118 | } | ||
119 | |||
120 | if (video.privacy === VideoPrivacy.PASSWORD_PROTECTED) { | ||
121 | return checkCanSeePasswordProtectedVideo({ req, res, video }) | ||
122 | } | ||
123 | |||
124 | if (video.privacy === VideoPrivacy.UNLISTED || video.privacy === VideoPrivacy.PUBLIC) { | ||
125 | return true | ||
126 | } | ||
127 | |||
128 | throw new Error('Unknown video privacy when checking video right ' + video.url) | ||
129 | } | ||
130 | |||
131 | async function checkCanSeeUserAuthVideo (options: { | ||
132 | req: Request | ||
133 | res: Response | ||
134 | video: MVideoId | MVideoWithRights | ||
135 | }) { | ||
136 | const { req, res, video } = options | ||
137 | |||
138 | const fail = () => { | ||
139 | res.fail({ | ||
140 | status: HttpStatusCode.FORBIDDEN_403, | ||
141 | message: 'Cannot fetch information of private/internal/blocked video' | ||
142 | }) | ||
143 | |||
144 | return false | ||
145 | } | ||
146 | |||
147 | await authenticatePromise({ req, res }) | ||
148 | |||
149 | const user = res.locals.oauth?.token.User | ||
150 | if (!user) return fail() | ||
151 | |||
152 | const videoWithRights = await getVideoWithRights(video as MVideoWithRights) | ||
153 | |||
154 | const privacy = videoWithRights.privacy | ||
155 | |||
156 | if (privacy === VideoPrivacy.INTERNAL) { | ||
157 | // We know we have a user | ||
158 | return true | ||
159 | } | ||
160 | |||
161 | if (videoWithRights.isBlacklisted()) { | ||
162 | if (canUserAccessVideo(user, videoWithRights, UserRight.MANAGE_VIDEO_BLACKLIST)) return true | ||
163 | |||
164 | return fail() | ||
165 | } | ||
166 | |||
167 | if (privacy === VideoPrivacy.PRIVATE || privacy === VideoPrivacy.UNLISTED) { | ||
168 | if (canUserAccessVideo(user, videoWithRights, UserRight.SEE_ALL_VIDEOS)) return true | ||
169 | |||
170 | return fail() | ||
171 | } | ||
172 | |||
173 | // Should not happen | ||
174 | return fail() | ||
175 | } | ||
176 | |||
177 | async function checkCanSeePasswordProtectedVideo (options: { | ||
178 | req: Request | ||
179 | res: Response | ||
180 | video: MVideo | ||
181 | }) { | ||
182 | const { req, res, video } = options | ||
183 | |||
184 | const videoWithRights = await getVideoWithRights(video as MVideoWithRights) | ||
185 | |||
186 | const videoPassword = req.header('x-peertube-video-password') | ||
187 | |||
188 | if (!exists(videoPassword)) { | ||
189 | const errorMessage = 'Please provide a password to access this password protected video' | ||
190 | const errorType = ServerErrorCode.VIDEO_REQUIRES_PASSWORD | ||
191 | |||
192 | if (req.header('authorization')) { | ||
193 | await authenticatePromise({ req, res, errorMessage, errorStatus: HttpStatusCode.FORBIDDEN_403, errorType }) | ||
194 | const user = res.locals.oauth?.token.User | ||
195 | |||
196 | if (canUserAccessVideo(user, videoWithRights, UserRight.SEE_ALL_VIDEOS)) return true | ||
197 | } | ||
198 | |||
199 | res.fail({ | ||
200 | status: HttpStatusCode.FORBIDDEN_403, | ||
201 | type: errorType, | ||
202 | message: errorMessage | ||
203 | }) | ||
204 | return false | ||
205 | } | ||
206 | |||
207 | if (await VideoPasswordModel.isACorrectPassword({ videoId: video.id, password: videoPassword })) return true | ||
208 | |||
209 | res.fail({ | ||
210 | status: HttpStatusCode.FORBIDDEN_403, | ||
211 | type: ServerErrorCode.INCORRECT_VIDEO_PASSWORD, | ||
212 | message: 'Incorrect video password. Access to the video is denied.' | ||
213 | }) | ||
214 | |||
215 | return false | ||
216 | } | ||
217 | |||
218 | function canUserAccessVideo (user: MUser, video: MVideoWithRights | MVideoAccountLight, right: UserRight) { | ||
219 | const isOwnedByUser = video.VideoChannel.Account.userId === user.id | ||
220 | |||
221 | return isOwnedByUser || user.hasRight(right) | ||
222 | } | ||
223 | |||
224 | async function getVideoWithRights (video: MVideoWithRights): Promise<MVideoWithRights> { | ||
225 | return video.VideoChannel?.Account?.userId | ||
226 | ? video | ||
227 | : VideoModel.loadFull(video.id) | ||
228 | } | ||
229 | |||
230 | // --------------------------------------------------------------------------- | ||
231 | |||
232 | async function checkCanAccessVideoStaticFiles (options: { | ||
233 | video: MVideo | ||
234 | req: Request | ||
235 | res: Response | ||
236 | paramId: string | ||
237 | }) { | ||
238 | const { video, req, res } = options | ||
239 | |||
240 | if (res.locals.oauth?.token.User || exists(req.header('x-peertube-video-password'))) { | ||
241 | return checkCanSeeVideo(options) | ||
242 | } | ||
243 | |||
244 | const videoFileToken = req.query.videoFileToken | ||
245 | if (videoFileToken && VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) { | ||
246 | const user = VideoTokensManager.Instance.getUserFromToken({ token: videoFileToken }) | ||
247 | |||
248 | res.locals.videoFileToken = { user } | ||
249 | return true | ||
250 | } | ||
251 | |||
252 | if (!video.hasPrivateStaticPath()) return true | ||
253 | |||
254 | res.sendStatus(HttpStatusCode.FORBIDDEN_403) | ||
255 | return false | ||
256 | } | ||
257 | |||
258 | // --------------------------------------------------------------------------- | ||
259 | |||
260 | function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { | ||
261 | // Retrieve the user who did the request | ||
262 | if (onlyOwned && video.isOwned() === false) { | ||
263 | res.fail({ | ||
264 | status: HttpStatusCode.FORBIDDEN_403, | ||
265 | message: 'Cannot manage a video of another server.' | ||
266 | }) | ||
267 | return false | ||
268 | } | ||
269 | |||
270 | // Check if the user can delete the video | ||
271 | // The user can delete it if he has the right | ||
272 | // Or if s/he is the video's account | ||
273 | const account = video.VideoChannel.Account | ||
274 | if (user.hasRight(right) === false && account.userId !== user.id) { | ||
275 | res.fail({ | ||
276 | status: HttpStatusCode.FORBIDDEN_403, | ||
277 | message: 'Cannot manage a video of another user.' | ||
278 | }) | ||
279 | return false | ||
280 | } | ||
281 | |||
282 | return true | ||
283 | } | ||
284 | |||
285 | // --------------------------------------------------------------------------- | ||
286 | |||
287 | async function checkUserQuota (user: MUserId, videoFileSize: number, res: Response) { | ||
288 | if (await isAbleToUploadVideo(user.id, videoFileSize) === false) { | ||
289 | res.fail({ | ||
290 | status: HttpStatusCode.PAYLOAD_TOO_LARGE_413, | ||
291 | message: 'The user video quota is exceeded with this video.', | ||
292 | type: ServerErrorCode.QUOTA_REACHED | ||
293 | }) | ||
294 | return false | ||
295 | } | ||
296 | |||
297 | return true | ||
298 | } | ||
299 | |||
300 | // --------------------------------------------------------------------------- | ||
301 | |||
302 | export { | ||
303 | doesVideoChannelOfAccountExist, | ||
304 | doesVideoExist, | ||
305 | doesVideoFileOfVideoExist, | ||
306 | |||
307 | checkCanAccessVideoStaticFiles, | ||
308 | checkUserCanManageVideo, | ||
309 | checkCanSeeVideo, | ||
310 | checkUserQuota | ||
311 | } | ||