diff options
author | Chocobozzz <me@florianbigard.com> | 2023-01-19 09:27:16 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2023-01-19 13:53:40 +0100 |
commit | e364e31e25bd1d4b8d801c845a96d6be708f0a18 (patch) | |
tree | 220785a42af361706eb8243960c5da9cddf4d2be /server/middlewares/validators/users.ts | |
parent | bc48e33b80f357767b98c1d310b04bdda24c6d46 (diff) | |
download | PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.tar.gz PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.tar.zst PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.zip |
Implement signup approval in server
Diffstat (limited to 'server/middlewares/validators/users.ts')
-rw-r--r-- | server/middlewares/validators/users.ts | 151 |
1 files changed, 4 insertions, 147 deletions
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 64bd9ca70..f7033f44a 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { Hooks } from '@server/lib/plugins/hooks' | ||
4 | import { forceNumber } from '@shared/core-utils' | 3 | import { forceNumber } from '@shared/core-utils' |
5 | import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models' | 4 | import { HttpStatusCode, UserRight, UserRole } from '@shared/models' |
6 | import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 5 | import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
7 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | 6 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' |
8 | import { | 7 | import { |
@@ -24,17 +23,16 @@ import { | |||
24 | isUserVideoQuotaValid, | 23 | isUserVideoQuotaValid, |
25 | isUserVideosHistoryEnabledValid | 24 | isUserVideosHistoryEnabledValid |
26 | } from '../../helpers/custom-validators/users' | 25 | } from '../../helpers/custom-validators/users' |
27 | import { isVideoChannelDisplayNameValid, isVideoChannelUsernameValid } from '../../helpers/custom-validators/video-channels' | 26 | import { isVideoChannelUsernameValid } from '../../helpers/custom-validators/video-channels' |
28 | import { logger } from '../../helpers/logger' | 27 | import { logger } from '../../helpers/logger' |
29 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' | 28 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' |
30 | import { Redis } from '../../lib/redis' | 29 | import { Redis } from '../../lib/redis' |
31 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup' | ||
32 | import { ActorModel } from '../../models/actor/actor' | 30 | import { ActorModel } from '../../models/actor/actor' |
33 | import { | 31 | import { |
34 | areValidationErrors, | 32 | areValidationErrors, |
35 | checkUserEmailExist, | 33 | checkUserEmailExist, |
36 | checkUserIdExist, | 34 | checkUserIdExist, |
37 | checkUserNameOrEmailDoesNotAlreadyExist, | 35 | checkUserNameOrEmailDoNotAlreadyExist, |
38 | doesVideoChannelIdExist, | 36 | doesVideoChannelIdExist, |
39 | doesVideoExist, | 37 | doesVideoExist, |
40 | isValidVideoIdParam | 38 | isValidVideoIdParam |
@@ -81,7 +79,7 @@ const usersAddValidator = [ | |||
81 | 79 | ||
82 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 80 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
83 | if (areValidationErrors(req, res, { omitBodyLog: true })) return | 81 | if (areValidationErrors(req, res, { omitBodyLog: true })) return |
84 | if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return | 82 | if (!await checkUserNameOrEmailDoNotAlreadyExist(req.body.username, req.body.email, res)) return |
85 | 83 | ||
86 | const authUser = res.locals.oauth.token.User | 84 | const authUser = res.locals.oauth.token.User |
87 | if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) { | 85 | if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) { |
@@ -109,51 +107,6 @@ const usersAddValidator = [ | |||
109 | } | 107 | } |
110 | ] | 108 | ] |
111 | 109 | ||
112 | const usersRegisterValidator = [ | ||
113 | body('username') | ||
114 | .custom(isUserUsernameValid), | ||
115 | body('password') | ||
116 | .custom(isUserPasswordValid), | ||
117 | body('email') | ||
118 | .isEmail(), | ||
119 | body('displayName') | ||
120 | .optional() | ||
121 | .custom(isUserDisplayNameValid), | ||
122 | |||
123 | body('channel.name') | ||
124 | .optional() | ||
125 | .custom(isVideoChannelUsernameValid), | ||
126 | body('channel.displayName') | ||
127 | .optional() | ||
128 | .custom(isVideoChannelDisplayNameValid), | ||
129 | |||
130 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
131 | if (areValidationErrors(req, res, { omitBodyLog: true })) return | ||
132 | if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return | ||
133 | |||
134 | const body: UserRegister = req.body | ||
135 | if (body.channel) { | ||
136 | if (!body.channel.name || !body.channel.displayName) { | ||
137 | return res.fail({ message: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' }) | ||
138 | } | ||
139 | |||
140 | if (body.channel.name === body.username) { | ||
141 | return res.fail({ message: 'Channel name cannot be the same as user username.' }) | ||
142 | } | ||
143 | |||
144 | const existing = await ActorModel.loadLocalByName(body.channel.name) | ||
145 | if (existing) { | ||
146 | return res.fail({ | ||
147 | status: HttpStatusCode.CONFLICT_409, | ||
148 | message: `Channel with name ${body.channel.name} already exists.` | ||
149 | }) | ||
150 | } | ||
151 | } | ||
152 | |||
153 | return next() | ||
154 | } | ||
155 | ] | ||
156 | |||
157 | const usersRemoveValidator = [ | 110 | const usersRemoveValidator = [ |
158 | param('id') | 111 | param('id') |
159 | .custom(isIdValid), | 112 | .custom(isIdValid), |
@@ -365,45 +318,6 @@ const usersVideosValidator = [ | |||
365 | } | 318 | } |
366 | ] | 319 | ] |
367 | 320 | ||
368 | const ensureUserRegistrationAllowed = [ | ||
369 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
370 | const allowedParams = { | ||
371 | body: req.body, | ||
372 | ip: req.ip | ||
373 | } | ||
374 | |||
375 | const allowedResult = await Hooks.wrapPromiseFun( | ||
376 | isSignupAllowed, | ||
377 | allowedParams, | ||
378 | 'filter:api.user.signup.allowed.result' | ||
379 | ) | ||
380 | |||
381 | if (allowedResult.allowed === false) { | ||
382 | return res.fail({ | ||
383 | status: HttpStatusCode.FORBIDDEN_403, | ||
384 | message: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.' | ||
385 | }) | ||
386 | } | ||
387 | |||
388 | return next() | ||
389 | } | ||
390 | ] | ||
391 | |||
392 | const ensureUserRegistrationAllowedForIP = [ | ||
393 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
394 | const allowed = isSignupAllowedForCurrentIP(req.ip) | ||
395 | |||
396 | if (allowed === false) { | ||
397 | return res.fail({ | ||
398 | status: HttpStatusCode.FORBIDDEN_403, | ||
399 | message: 'You are not on a network authorized for registration.' | ||
400 | }) | ||
401 | } | ||
402 | |||
403 | return next() | ||
404 | } | ||
405 | ] | ||
406 | |||
407 | const usersAskResetPasswordValidator = [ | 321 | const usersAskResetPasswordValidator = [ |
408 | body('email') | 322 | body('email') |
409 | .isEmail(), | 323 | .isEmail(), |
@@ -455,58 +369,6 @@ const usersResetPasswordValidator = [ | |||
455 | } | 369 | } |
456 | ] | 370 | ] |
457 | 371 | ||
458 | const usersAskSendVerifyEmailValidator = [ | ||
459 | body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'), | ||
460 | |||
461 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
462 | if (areValidationErrors(req, res)) return | ||
463 | |||
464 | const exists = await checkUserEmailExist(req.body.email, res, false) | ||
465 | if (!exists) { | ||
466 | logger.debug('User with email %s does not exist (asking verify email).', req.body.email) | ||
467 | // Do not leak our emails | ||
468 | return res.status(HttpStatusCode.NO_CONTENT_204).end() | ||
469 | } | ||
470 | |||
471 | if (res.locals.user.pluginAuth) { | ||
472 | return res.fail({ | ||
473 | status: HttpStatusCode.CONFLICT_409, | ||
474 | message: 'Cannot ask verification email of a user that uses a plugin authentication.' | ||
475 | }) | ||
476 | } | ||
477 | |||
478 | return next() | ||
479 | } | ||
480 | ] | ||
481 | |||
482 | const usersVerifyEmailValidator = [ | ||
483 | param('id') | ||
484 | .isInt().not().isEmpty().withMessage('Should have a valid id'), | ||
485 | |||
486 | body('verificationString') | ||
487 | .not().isEmpty().withMessage('Should have a valid verification string'), | ||
488 | body('isPendingEmail') | ||
489 | .optional() | ||
490 | .customSanitizer(toBooleanOrNull), | ||
491 | |||
492 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
493 | if (areValidationErrors(req, res)) return | ||
494 | if (!await checkUserIdExist(req.params.id, res)) return | ||
495 | |||
496 | const user = res.locals.user | ||
497 | const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id) | ||
498 | |||
499 | if (redisVerificationString !== req.body.verificationString) { | ||
500 | return res.fail({ | ||
501 | status: HttpStatusCode.FORBIDDEN_403, | ||
502 | message: 'Invalid verification string.' | ||
503 | }) | ||
504 | } | ||
505 | |||
506 | return next() | ||
507 | } | ||
508 | ] | ||
509 | |||
510 | const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Request) => number | string) => { | 372 | const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Request) => number | string) => { |
511 | return [ | 373 | return [ |
512 | body('currentPassword').optional().custom(exists), | 374 | body('currentPassword').optional().custom(exists), |
@@ -603,21 +465,16 @@ export { | |||
603 | usersListValidator, | 465 | usersListValidator, |
604 | usersAddValidator, | 466 | usersAddValidator, |
605 | deleteMeValidator, | 467 | deleteMeValidator, |
606 | usersRegisterValidator, | ||
607 | usersBlockingValidator, | 468 | usersBlockingValidator, |
608 | usersRemoveValidator, | 469 | usersRemoveValidator, |
609 | usersUpdateValidator, | 470 | usersUpdateValidator, |
610 | usersUpdateMeValidator, | 471 | usersUpdateMeValidator, |
611 | usersVideoRatingValidator, | 472 | usersVideoRatingValidator, |
612 | usersCheckCurrentPasswordFactory, | 473 | usersCheckCurrentPasswordFactory, |
613 | ensureUserRegistrationAllowed, | ||
614 | ensureUserRegistrationAllowedForIP, | ||
615 | usersGetValidator, | 474 | usersGetValidator, |
616 | usersVideosValidator, | 475 | usersVideosValidator, |
617 | usersAskResetPasswordValidator, | 476 | usersAskResetPasswordValidator, |
618 | usersResetPasswordValidator, | 477 | usersResetPasswordValidator, |
619 | usersAskSendVerifyEmailValidator, | ||
620 | usersVerifyEmailValidator, | ||
621 | userAutocompleteValidator, | 478 | userAutocompleteValidator, |
622 | ensureAuthUserOwnsAccountValidator, | 479 | ensureAuthUserOwnsAccountValidator, |
623 | ensureCanModerateUser, | 480 | ensureCanModerateUser, |