From e364e31e25bd1d4b8d801c845a96d6be708f0a18 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 19 Jan 2023 09:27:16 +0100 Subject: Implement signup approval in server --- server/middlewares/validators/config.ts | 1 + server/middlewares/validators/index.ts | 2 + .../validators/shared/user-registrations.ts | 60 ++++++ server/middlewares/validators/shared/users.ts | 4 +- server/middlewares/validators/sort.ts | 95 ++++------ .../validators/user-email-verification.ts | 94 ++++++++++ .../middlewares/validators/user-registrations.ts | 203 +++++++++++++++++++++ server/middlewares/validators/users.ts | 151 +-------------- 8 files changed, 399 insertions(+), 211 deletions(-) create mode 100644 server/middlewares/validators/shared/user-registrations.ts create mode 100644 server/middlewares/validators/user-email-verification.ts create mode 100644 server/middlewares/validators/user-registrations.ts (limited to 'server/middlewares') diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index 3a7daa573..c2dbfadb7 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts @@ -29,6 +29,7 @@ const customConfigUpdateValidator = [ body('signup.enabled').isBoolean(), body('signup.limit').isInt(), body('signup.requiresEmailVerification').isBoolean(), + body('signup.requiresApproval').isBoolean(), body('signup.minimumAge').isInt(), body('admin.email').isEmail(), diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index 9bc8887ff..1d0964667 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts @@ -21,8 +21,10 @@ export * from './server' export * from './sort' export * from './static' export * from './themes' +export * from './user-email-verification' export * from './user-history' export * from './user-notifications' +export * from './user-registrations' export * from './user-subscriptions' export * from './users' export * from './videos' diff --git a/server/middlewares/validators/shared/user-registrations.ts b/server/middlewares/validators/shared/user-registrations.ts new file mode 100644 index 000000000..dbc7dda06 --- /dev/null +++ b/server/middlewares/validators/shared/user-registrations.ts @@ -0,0 +1,60 @@ +import express from 'express' +import { UserRegistrationModel } from '@server/models/user/user-registration' +import { MRegistration } from '@server/types/models' +import { forceNumber, pick } from '@shared/core-utils' +import { HttpStatusCode } from '@shared/models' + +function checkRegistrationIdExist (idArg: number | string, res: express.Response) { + const id = forceNumber(idArg) + return checkRegistrationExist(() => UserRegistrationModel.load(id), res) +} + +function checkRegistrationEmailExist (email: string, res: express.Response, abortResponse = true) { + return checkRegistrationExist(() => UserRegistrationModel.loadByEmail(email), res, abortResponse) +} + +async function checkRegistrationHandlesDoNotAlreadyExist (options: { + username: string + channelHandle: string + email: string + res: express.Response +}) { + const { res } = options + + const registration = await UserRegistrationModel.loadByEmailOrHandle(pick(options, [ 'username', 'email', 'channelHandle' ])) + + if (registration) { + res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Registration with this username, channel name or email already exists.' + }) + return false + } + + return true +} + +async function checkRegistrationExist (finder: () => Promise, res: express.Response, abortResponse = true) { + const registration = await finder() + + if (!registration) { + if (abortResponse === true) { + res.fail({ + status: HttpStatusCode.NOT_FOUND_404, + message: 'User not found' + }) + } + + return false + } + + res.locals.userRegistration = registration + return true +} + +export { + checkRegistrationIdExist, + checkRegistrationEmailExist, + checkRegistrationHandlesDoNotAlreadyExist, + checkRegistrationExist +} diff --git a/server/middlewares/validators/shared/users.ts b/server/middlewares/validators/shared/users.ts index b8f1436d3..030adc9f7 100644 --- a/server/middlewares/validators/shared/users.ts +++ b/server/middlewares/validators/shared/users.ts @@ -14,7 +14,7 @@ function checkUserEmailExist (email: string, res: express.Response, abortRespons return checkUserExist(() => UserModel.loadByEmail(email), res, abortResponse) } -async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) { +async function checkUserNameOrEmailDoNotAlreadyExist (username: string, email: string, res: express.Response) { const user = await UserModel.loadByUsernameOrEmail(username, email) if (user) { @@ -58,6 +58,6 @@ async function checkUserExist (finder: () => Promise, res: express export { checkUserIdExist, checkUserEmailExist, - checkUserNameOrEmailDoesNotAlreadyExist, + checkUserNameOrEmailDoNotAlreadyExist, checkUserExist } diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index 7d0639107..e6cc46317 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts @@ -1,9 +1,41 @@ import express from 'express' import { query } from 'express-validator' - import { SORTABLE_COLUMNS } from '../../initializers/constants' import { areValidationErrors } from './shared' +export const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS) +export const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS) +export const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ]) +export const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES) +export const videosSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEOS) +export const videoImportsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_IMPORTS) +export const videosSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEOS_SEARCH) +export const videoChannelsSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) +export const videoPlaylistsSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PLAYLISTS_SEARCH) +export const videoCommentsValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_COMMENTS) +export const videoCommentThreadsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) +export const videoRatesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_RATES) +export const blacklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.BLACKLISTS) +export const videoChannelsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNELS) +export const instanceFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.INSTANCE_FOLLOWERS) +export const instanceFollowingSortValidator = checkSortFactory(SORTABLE_COLUMNS.INSTANCE_FOLLOWING) +export const userSubscriptionsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_SUBSCRIPTIONS) +export const accountsBlocklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS_BLOCKLIST) +export const serversBlocklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.SERVERS_BLOCKLIST) +export const userNotificationsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_NOTIFICATIONS) +export const videoPlaylistsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PLAYLISTS) +export const pluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.PLUGINS) +export const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) +export const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) +export const videoChannelSyncsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNEL_SYNCS) + +export const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS) +export const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS) + +export const userRegistrationsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_REGISTRATIONS) + +// --------------------------------------------------------------------------- + function checkSortFactory (columns: string[], tags: string[] = []) { return checkSort(createSortableColumns(columns), tags) } @@ -27,64 +59,3 @@ function createSortableColumns (sortableColumns: string[]) { return sortableColumns.concat(sortableColumnDesc) } - -const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS) -const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS) -const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ]) -const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES) -const videosSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEOS) -const videoImportsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_IMPORTS) -const videosSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEOS_SEARCH) -const videoChannelsSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) -const videoPlaylistsSearchSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PLAYLISTS_SEARCH) -const videoCommentsValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_COMMENTS) -const videoCommentThreadsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) -const videoRatesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_RATES) -const blacklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.BLACKLISTS) -const videoChannelsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNELS) -const instanceFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.INSTANCE_FOLLOWERS) -const instanceFollowingSortValidator = checkSortFactory(SORTABLE_COLUMNS.INSTANCE_FOLLOWING) -const userSubscriptionsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_SUBSCRIPTIONS) -const accountsBlocklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS_BLOCKLIST) -const serversBlocklistSortValidator = checkSortFactory(SORTABLE_COLUMNS.SERVERS_BLOCKLIST) -const userNotificationsSortValidator = checkSortFactory(SORTABLE_COLUMNS.USER_NOTIFICATIONS) -const videoPlaylistsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PLAYLISTS) -const pluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.PLUGINS) -const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) -const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) -const videoChannelSyncsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNEL_SYNCS) - -const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS) -const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS) - -// --------------------------------------------------------------------------- - -export { - adminUsersSortValidator, - abusesSortValidator, - videoChannelsSortValidator, - videoImportsSortValidator, - videoCommentsValidator, - videosSearchSortValidator, - videosSortValidator, - blacklistSortValidator, - accountsSortValidator, - instanceFollowersSortValidator, - instanceFollowingSortValidator, - jobsSortValidator, - videoCommentThreadsSortValidator, - videoRatesSortValidator, - userSubscriptionsSortValidator, - availablePluginsSortValidator, - videoChannelsSearchSortValidator, - accountsBlocklistSortValidator, - serversBlocklistSortValidator, - userNotificationsSortValidator, - videoPlaylistsSortValidator, - videoRedundanciesSortValidator, - videoPlaylistsSearchSortValidator, - accountsFollowersSortValidator, - videoChannelsFollowersSortValidator, - videoChannelSyncsSortValidator, - pluginsSortValidator -} diff --git a/server/middlewares/validators/user-email-verification.ts b/server/middlewares/validators/user-email-verification.ts new file mode 100644 index 000000000..74702a8f5 --- /dev/null +++ b/server/middlewares/validators/user-email-verification.ts @@ -0,0 +1,94 @@ +import express from 'express' +import { body, param } from 'express-validator' +import { toBooleanOrNull } from '@server/helpers/custom-validators/misc' +import { HttpStatusCode } from '@shared/models' +import { logger } from '../../helpers/logger' +import { Redis } from '../../lib/redis' +import { areValidationErrors, checkUserEmailExist, checkUserIdExist } from './shared' +import { checkRegistrationEmailExist, checkRegistrationIdExist } from './shared/user-registrations' + +const usersAskSendVerifyEmailValidator = [ + body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (areValidationErrors(req, res)) return + + const [ userExists, registrationExists ] = await Promise.all([ + checkUserEmailExist(req.body.email, res, false), + checkRegistrationEmailExist(req.body.email, res, false) + ]) + + if (!userExists && !registrationExists) { + logger.debug('User or registration with email %s does not exist (asking verify email).', req.body.email) + // Do not leak our emails + return res.status(HttpStatusCode.NO_CONTENT_204).end() + } + + if (res.locals.user?.pluginAuth) { + return res.fail({ + status: HttpStatusCode.CONFLICT_409, + message: 'Cannot ask verification email of a user that uses a plugin authentication.' + }) + } + + return next() + } +] + +const usersVerifyEmailValidator = [ + param('id') + .isInt().not().isEmpty().withMessage('Should have a valid id'), + + body('verificationString') + .not().isEmpty().withMessage('Should have a valid verification string'), + body('isPendingEmail') + .optional() + .customSanitizer(toBooleanOrNull), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (areValidationErrors(req, res)) return + if (!await checkUserIdExist(req.params.id, res)) return + + const user = res.locals.user + const redisVerificationString = await Redis.Instance.getUserVerifyEmailLink(user.id) + + if (redisVerificationString !== req.body.verificationString) { + return res.fail({ status: HttpStatusCode.FORBIDDEN_403, message: 'Invalid verification string.' }) + } + + return next() + } +] + +// --------------------------------------------------------------------------- + +const registrationVerifyEmailValidator = [ + param('registrationId') + .isInt().not().isEmpty().withMessage('Should have a valid registrationId'), + + body('verificationString') + .not().isEmpty().withMessage('Should have a valid verification string'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (areValidationErrors(req, res)) return + if (!await checkRegistrationIdExist(req.params.registrationId, res)) return + + const registration = res.locals.userRegistration + const redisVerificationString = await Redis.Instance.getRegistrationVerifyEmailLink(registration.id) + + if (redisVerificationString !== req.body.verificationString) { + return res.fail({ status: HttpStatusCode.FORBIDDEN_403, message: 'Invalid verification string.' }) + } + + return next() + } +] + +// --------------------------------------------------------------------------- + +export { + usersAskSendVerifyEmailValidator, + usersVerifyEmailValidator, + + registrationVerifyEmailValidator +} diff --git a/server/middlewares/validators/user-registrations.ts b/server/middlewares/validators/user-registrations.ts new file mode 100644 index 000000000..e263c27c5 --- /dev/null +++ b/server/middlewares/validators/user-registrations.ts @@ -0,0 +1,203 @@ +import express from 'express' +import { body, param, query, ValidationChain } from 'express-validator' +import { exists, isIdValid } 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), + + 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() + } + ] +} 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 @@ import express from 'express' import { body, param, query } from 'express-validator' -import { Hooks } from '@server/lib/plugins/hooks' import { forceNumber } from '@shared/core-utils' -import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models' +import { HttpStatusCode, UserRight, UserRole } from '@shared/models' import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' import { isThemeNameValid } from '../../helpers/custom-validators/plugins' import { @@ -24,17 +23,16 @@ import { isUserVideoQuotaValid, isUserVideosHistoryEnabledValid } from '../../helpers/custom-validators/users' -import { isVideoChannelDisplayNameValid, isVideoChannelUsernameValid } from '../../helpers/custom-validators/video-channels' +import { isVideoChannelUsernameValid } from '../../helpers/custom-validators/video-channels' import { logger } from '../../helpers/logger' import { isThemeRegistered } from '../../lib/plugins/theme-utils' import { Redis } from '../../lib/redis' -import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup' import { ActorModel } from '../../models/actor/actor' import { areValidationErrors, checkUserEmailExist, checkUserIdExist, - checkUserNameOrEmailDoesNotAlreadyExist, + checkUserNameOrEmailDoNotAlreadyExist, doesVideoChannelIdExist, doesVideoExist, isValidVideoIdParam @@ -81,7 +79,7 @@ const usersAddValidator = [ async (req: express.Request, res: express.Response, next: express.NextFunction) => { if (areValidationErrors(req, res, { omitBodyLog: true })) return - if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return + if (!await checkUserNameOrEmailDoNotAlreadyExist(req.body.username, req.body.email, res)) return const authUser = res.locals.oauth.token.User if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) { @@ -109,51 +107,6 @@ const usersAddValidator = [ } ] -const usersRegisterValidator = [ - 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), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - if (areValidationErrors(req, res, { omitBodyLog: true })) return - if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return - - const body: UserRegister = req.body - 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() - } -] - const usersRemoveValidator = [ param('id') .custom(isIdValid), @@ -365,45 +318,6 @@ const usersVideosValidator = [ } ] -const ensureUserRegistrationAllowed = [ - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - const allowedParams = { - body: req.body, - ip: req.ip - } - - const allowedResult = await Hooks.wrapPromiseFun( - isSignupAllowed, - allowedParams, - 'filter:api.user.signup.allowed.result' - ) - - if (allowedResult.allowed === false) { - return res.fail({ - status: HttpStatusCode.FORBIDDEN_403, - message: allowedResult.errorMessage || 'User registration is not enabled or user limit is reached.' - }) - } - - 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 usersAskResetPasswordValidator = [ body('email') .isEmail(), @@ -455,58 +369,6 @@ const usersResetPasswordValidator = [ } ] -const usersAskSendVerifyEmailValidator = [ - body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - if (areValidationErrors(req, res)) return - - const exists = await checkUserEmailExist(req.body.email, res, false) - if (!exists) { - logger.debug('User with email %s does not exist (asking verify email).', req.body.email) - // Do not leak our emails - return res.status(HttpStatusCode.NO_CONTENT_204).end() - } - - if (res.locals.user.pluginAuth) { - return res.fail({ - status: HttpStatusCode.CONFLICT_409, - message: 'Cannot ask verification email of a user that uses a plugin authentication.' - }) - } - - return next() - } -] - -const usersVerifyEmailValidator = [ - param('id') - .isInt().not().isEmpty().withMessage('Should have a valid id'), - - body('verificationString') - .not().isEmpty().withMessage('Should have a valid verification string'), - body('isPendingEmail') - .optional() - .customSanitizer(toBooleanOrNull), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - if (areValidationErrors(req, res)) return - if (!await checkUserIdExist(req.params.id, res)) return - - const user = res.locals.user - const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id) - - if (redisVerificationString !== req.body.verificationString) { - return res.fail({ - status: HttpStatusCode.FORBIDDEN_403, - message: 'Invalid verification string.' - }) - } - - return next() - } -] - const usersCheckCurrentPasswordFactory = (targetUserIdGetter: (req: express.Request) => number | string) => { return [ body('currentPassword').optional().custom(exists), @@ -603,21 +465,16 @@ export { usersListValidator, usersAddValidator, deleteMeValidator, - usersRegisterValidator, usersBlockingValidator, usersRemoveValidator, usersUpdateValidator, usersUpdateMeValidator, usersVideoRatingValidator, usersCheckCurrentPasswordFactory, - ensureUserRegistrationAllowed, - ensureUserRegistrationAllowedForIP, usersGetValidator, usersVideosValidator, usersAskResetPasswordValidator, usersResetPasswordValidator, - usersAskSendVerifyEmailValidator, - usersVerifyEmailValidator, userAutocompleteValidator, ensureAuthUserOwnsAccountValidator, ensureCanModerateUser, -- cgit v1.2.3