import express from 'express' import { body, param, query, ValidationChain } from 'express-validator' import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc' import { isRegistrationModerationResponseValid, isRegistrationReasonValid } from '@server/helpers/custom-validators/user-registration' import { CONFIG } from '@server/initializers/config' import { Hooks } from '@server/lib/plugins/hooks' import { HttpStatusCode, UserRegister, UserRegistrationRequest, UserRegistrationState } from '@shared/models' import { isUserDisplayNameValid, isUserPasswordValid, isUserUsernameValid } from '../../helpers/custom-validators/users' import { isVideoChannelDisplayNameValid, isVideoChannelUsernameValid } from '../../helpers/custom-validators/video-channels' import { isSignupAllowed, isSignupAllowedForCurrentIP, SignupMode } from '../../lib/signup' import { ActorModel } from '../../models/actor/actor' import { areValidationErrors, checkUserNameOrEmailDoNotAlreadyExist } from './shared' import { checkRegistrationHandlesDoNotAlreadyExist, checkRegistrationIdExist } from './shared/user-registrations' const usersDirectRegistrationValidator = usersCommonRegistrationValidatorFactory() const usersRequestRegistrationValidator = [ ...usersCommonRegistrationValidatorFactory([ body('registrationReason') .custom(isRegistrationReasonValid) ]), async (req: express.Request, res: express.Response, next: express.NextFunction) => { const body: UserRegistrationRequest = req.body if (CONFIG.SIGNUP.REQUIRES_APPROVAL !== true) { return res.fail({ status: HttpStatusCode.BAD_REQUEST_400, message: 'Signup approval is not enabled on this instance' }) } const options = { username: body.username, email: body.email, channelHandle: body.channel?.name, res } if (!await checkRegistrationHandlesDoNotAlreadyExist(options)) return return next() } ] // --------------------------------------------------------------------------- function ensureUserRegistrationAllowedFactory (signupMode: SignupMode) { return async (req: express.Request, res: express.Response, next: express.NextFunction) => { const allowedParams = { body: req.body, ip: req.ip, signupMode } const allowedResult = await Hooks.wrapPromiseFun( isSignupAllowed, allowedParams, signupMode === 'direct-registration' ? 'filter:api.user.signup.allowed.result' : 'filter:api.user.request-signup.allowed.result' ) if (allowedResult.allowed === false) { return res.fail({ status: HttpStatusCode.FORBIDDEN_403, message: allowedResult.errorMessage || 'User registration is not enabled, user limit is reached or registration requires approval.' }) } return next() } } const ensureUserRegistrationAllowedForIP = [ (req: express.Request, res: express.Response, next: express.NextFunction) => { const allowed = isSignupAllowedForCurrentIP(req.ip) if (allowed === false) { return res.fail({ status: HttpStatusCode.FORBIDDEN_403, message: 'You are not on a network authorized for registration.' }) } return next() } ] // --------------------------------------------------------------------------- const acceptOrRejectRegistrationValidator = [ param('registrationId') .custom(isIdValid), body('moderationResponse') .custom(isRegistrationModerationResponseValid), body('preventEmailDelivery') .optional() .customSanitizer(toBooleanOrNull) .custom(isBooleanValid).withMessage('Should have preventEmailDelivery boolean'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { if (areValidationErrors(req, res)) return if (!await checkRegistrationIdExist(req.params.registrationId, res)) return if (res.locals.userRegistration.state !== UserRegistrationState.PENDING) { return res.fail({ status: HttpStatusCode.CONFLICT_409, message: 'This registration is already accepted or rejected.' }) } return next() } ] // --------------------------------------------------------------------------- const getRegistrationValidator = [ param('registrationId') .custom(isIdValid), async (req: express.Request, res: express.Response, next: express.NextFunction) => { if (areValidationErrors(req, res)) return if (!await checkRegistrationIdExist(req.params.registrationId, res)) return return next() } ] // --------------------------------------------------------------------------- const listRegistrationsValidator = [ query('search') .optional() .custom(exists), (req: express.Request, res: express.Response, next: express.NextFunction) => { if (areValidationErrors(req, res)) return return next() } ] // --------------------------------------------------------------------------- export { usersDirectRegistrationValidator, usersRequestRegistrationValidator, ensureUserRegistrationAllowedFactory, ensureUserRegistrationAllowedForIP, getRegistrationValidator, listRegistrationsValidator, acceptOrRejectRegistrationValidator } // --------------------------------------------------------------------------- function usersCommonRegistrationValidatorFactory (additionalValidationChain: ValidationChain[] = []) { return [ body('username') .custom(isUserUsernameValid), body('password') .custom(isUserPasswordValid), body('email') .isEmail(), body('displayName') .optional() .custom(isUserDisplayNameValid), body('channel.name') .optional() .custom(isVideoChannelUsernameValid), body('channel.displayName') .optional() .custom(isVideoChannelDisplayNameValid), ...additionalValidationChain, async (req: express.Request, res: express.Response, next: express.NextFunction) => { if (areValidationErrors(req, res, { omitBodyLog: true })) return const body: UserRegister | UserRegistrationRequest = req.body if (!await checkUserNameOrEmailDoNotAlreadyExist(body.username, body.email, res)) return if (body.channel) { if (!body.channel.name || !body.channel.displayName) { return res.fail({ message: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' }) } if (body.channel.name === body.username) { return res.fail({ message: 'Channel name cannot be the same as user username.' }) } const existing = await ActorModel.loadLocalByName(body.channel.name) if (existing) { return res.fail({ status: HttpStatusCode.CONFLICT_409, message: `Channel with name ${body.channel.name} already exists.` }) } } return next() } ] }