aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares
diff options
context:
space:
mode:
Diffstat (limited to 'server/middlewares')
-rw-r--r--server/middlewares/validators/shared/index.ts1
-rw-r--r--server/middlewares/validators/shared/users.ts62
-rw-r--r--server/middlewares/validators/two-factor.ts81
-rw-r--r--server/middlewares/validators/users.ts104
4 files changed, 191 insertions, 57 deletions
diff --git a/server/middlewares/validators/shared/index.ts b/server/middlewares/validators/shared/index.ts
index bbd03b248..de98cd442 100644
--- a/server/middlewares/validators/shared/index.ts
+++ b/server/middlewares/validators/shared/index.ts
@@ -1,5 +1,6 @@
1export * from './abuses' 1export * from './abuses'
2export * from './accounts' 2export * from './accounts'
3export * from './users'
3export * from './utils' 4export * from './utils'
4export * from './video-blacklists' 5export * from './video-blacklists'
5export * from './video-captions' 6export * from './video-captions'
diff --git a/server/middlewares/validators/shared/users.ts b/server/middlewares/validators/shared/users.ts
new file mode 100644
index 000000000..fbaa7db0e
--- /dev/null
+++ b/server/middlewares/validators/shared/users.ts
@@ -0,0 +1,62 @@
1import express from 'express'
2import { ActorModel } from '@server/models/actor/actor'
3import { UserModel } from '@server/models/user/user'
4import { MUserDefault } from '@server/types/models'
5import { HttpStatusCode } from '@shared/models'
6
7function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) {
8 const id = parseInt(idArg + '', 10)
9 return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res)
10}
11
12function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
13 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
14}
15
16async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
17 const user = await UserModel.loadByUsernameOrEmail(username, email)
18
19 if (user) {
20 res.fail({
21 status: HttpStatusCode.CONFLICT_409,
22 message: 'User with this username or email already exists.'
23 })
24 return false
25 }
26
27 const actor = await ActorModel.loadLocalByName(username)
28 if (actor) {
29 res.fail({
30 status: HttpStatusCode.CONFLICT_409,
31 message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.'
32 })
33 return false
34 }
35
36 return true
37}
38
39async function checkUserExist (finder: () => Promise<MUserDefault>, res: express.Response, abortResponse = true) {
40 const user = await finder()
41
42 if (!user) {
43 if (abortResponse === true) {
44 res.fail({
45 status: HttpStatusCode.NOT_FOUND_404,
46 message: 'User not found'
47 })
48 }
49
50 return false
51 }
52
53 res.locals.user = user
54 return true
55}
56
57export {
58 checkUserIdExist,
59 checkUserEmailExist,
60 checkUserNameOrEmailDoesNotAlreadyExist,
61 checkUserExist
62}
diff --git a/server/middlewares/validators/two-factor.ts b/server/middlewares/validators/two-factor.ts
new file mode 100644
index 000000000..106b579b5
--- /dev/null
+++ b/server/middlewares/validators/two-factor.ts
@@ -0,0 +1,81 @@
1import express from 'express'
2import { body, param } from 'express-validator'
3import { HttpStatusCode, UserRight } from '@shared/models'
4import { exists, isIdValid } from '../../helpers/custom-validators/misc'
5import { areValidationErrors, checkUserIdExist } from './shared'
6
7const requestOrConfirmTwoFactorValidator = [
8 param('id').custom(isIdValid),
9
10 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
11 if (areValidationErrors(req, res)) return
12
13 if (!await checkCanEnableOrDisableTwoFactor(req.params.id, res)) return
14
15 if (res.locals.user.otpSecret) {
16 return res.fail({
17 status: HttpStatusCode.BAD_REQUEST_400,
18 message: `Two factor is already enabled.`
19 })
20 }
21
22 return next()
23 }
24]
25
26const confirmTwoFactorValidator = [
27 body('requestToken').custom(exists),
28 body('otpToken').custom(exists),
29
30 (req: express.Request, res: express.Response, next: express.NextFunction) => {
31 if (areValidationErrors(req, res)) return
32
33 return next()
34 }
35]
36
37const disableTwoFactorValidator = [
38 param('id').custom(isIdValid),
39
40 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
41 if (areValidationErrors(req, res)) return
42
43 if (!await checkCanEnableOrDisableTwoFactor(req.params.id, res)) return
44
45 if (!res.locals.user.otpSecret) {
46 return res.fail({
47 status: HttpStatusCode.BAD_REQUEST_400,
48 message: `Two factor is already disabled.`
49 })
50 }
51
52 return next()
53 }
54]
55
56// ---------------------------------------------------------------------------
57
58export {
59 requestOrConfirmTwoFactorValidator,
60 confirmTwoFactorValidator,
61 disableTwoFactorValidator
62}
63
64// ---------------------------------------------------------------------------
65
66async function checkCanEnableOrDisableTwoFactor (userId: number | string, res: express.Response) {
67 const authUser = res.locals.oauth.token.user
68
69 if (!await checkUserIdExist(userId, res)) return
70
71 if (res.locals.user.id !== authUser.id && authUser.hasRight(UserRight.MANAGE_USERS) !== true) {
72 res.fail({
73 status: HttpStatusCode.FORBIDDEN_403,
74 message: `User ${authUser.username} does not have right to change two factor setting of this user.`
75 })
76
77 return false
78 }
79
80 return true
81}
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index eb693318f..055af3b64 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -1,9 +1,8 @@
1import express from 'express' 1import express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { Hooks } from '@server/lib/plugins/hooks' 3import { Hooks } from '@server/lib/plugins/hooks'
4import { MUserDefault } from '@server/types/models'
5import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models' 4import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models'
6import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 5import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
7import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 6import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
8import { 7import {
9 isUserAdminFlagsValid, 8 isUserAdminFlagsValid,
@@ -30,8 +29,15 @@ import { isThemeRegistered } from '../../lib/plugins/theme-utils'
30import { Redis } from '../../lib/redis' 29import { Redis } from '../../lib/redis'
31import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup' 30import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup'
32import { ActorModel } from '../../models/actor/actor' 31import { ActorModel } from '../../models/actor/actor'
33import { UserModel } from '../../models/user/user' 32import {
34import { areValidationErrors, doesVideoChannelIdExist, doesVideoExist, isValidVideoIdParam } from './shared' 33 areValidationErrors,
34 checkUserEmailExist,
35 checkUserIdExist,
36 checkUserNameOrEmailDoesNotAlreadyExist,
37 doesVideoChannelIdExist,
38 doesVideoExist,
39 isValidVideoIdParam
40} from './shared'
35 41
36const usersListValidator = [ 42const usersListValidator = [
37 query('blocked') 43 query('blocked')
@@ -435,7 +441,7 @@ const usersResetPasswordValidator = [
435 if (!await checkUserIdExist(req.params.id, res)) return 441 if (!await checkUserIdExist(req.params.id, res)) return
436 442
437 const user = res.locals.user 443 const user = res.locals.user
438 const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id) 444 const redisVerificationString = await Redis.Instance.getResetPasswordVerificationString(user.id)
439 445
440 if (redisVerificationString !== req.body.verificationString) { 446 if (redisVerificationString !== req.body.verificationString) {
441 return res.fail({ 447 return res.fail({
@@ -500,6 +506,41 @@ const usersVerifyEmailValidator = [
500 } 506 }
501] 507]
502 508
509const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Request) => number | string) => {
510 return [
511 body('currentPassword').optional().custom(exists),
512
513 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
514 if (areValidationErrors(req, res)) return
515
516 const user = res.locals.oauth.token.User
517 const isAdminOrModerator = user.role === UserRole.ADMINISTRATOR || user.role === UserRole.MODERATOR
518 const targetUserId = parseInt(targetUserIdGetter(req) + '')
519
520 // Admin/moderator action on another user, skip the password check
521 if (isAdminOrModerator && targetUserId !== user.id) {
522 return next()
523 }
524
525 if (!req.body.currentPassword) {
526 return res.fail({
527 status: HttpStatusCode.BAD_REQUEST_400,
528 message: 'currentPassword is missing'
529 })
530 }
531
532 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
533 return res.fail({
534 status: HttpStatusCode.FORBIDDEN_403,
535 message: 'currentPassword is invalid.'
536 })
537 }
538
539 return next()
540 }
541 ]
542}
543
503const userAutocompleteValidator = [ 544const userAutocompleteValidator = [
504 param('search') 545 param('search')
505 .isString() 546 .isString()
@@ -567,6 +608,7 @@ export {
567 usersUpdateValidator, 608 usersUpdateValidator,
568 usersUpdateMeValidator, 609 usersUpdateMeValidator,
569 usersVideoRatingValidator, 610 usersVideoRatingValidator,
611 usersCheckCurrentPasswordFactory,
570 ensureUserRegistrationAllowed, 612 ensureUserRegistrationAllowed,
571 ensureUserRegistrationAllowedForIP, 613 ensureUserRegistrationAllowedForIP,
572 usersGetValidator, 614 usersGetValidator,
@@ -580,55 +622,3 @@ export {
580 ensureCanModerateUser, 622 ensureCanModerateUser,
581 ensureCanManageChannelOrAccount 623 ensureCanManageChannelOrAccount
582} 624}
583
584// ---------------------------------------------------------------------------
585
586function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) {
587 const id = parseInt(idArg + '', 10)
588 return checkUserExist(() => UserModel.loadByIdWithChannels(id, withStats), res)
589}
590
591function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
592 return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse)
593}
594
595async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
596 const user = await UserModel.loadByUsernameOrEmail(username, email)
597
598 if (user) {
599 res.fail({
600 status: HttpStatusCode.CONFLICT_409,
601 message: 'User with this username or email already exists.'
602 })
603 return false
604 }
605
606 const actor = await ActorModel.loadLocalByName(username)
607 if (actor) {
608 res.fail({
609 status: HttpStatusCode.CONFLICT_409,
610 message: 'Another actor (account/channel) with this name on this instance already exists or has already existed.'
611 })
612 return false
613 }
614
615 return true
616}
617
618async function checkUserExist (finder: () => Promise<MUserDefault>, res: express.Response, abortResponse = true) {
619 const user = await finder()
620
621 if (!user) {
622 if (abortResponse === true) {
623 res.fail({
624 status: HttpStatusCode.NOT_FOUND_404,
625 message: 'User not found'
626 })
627 }
628
629 return false
630 }
631
632 res.locals.user = user
633 return true
634}