209 lines
6.7 KiB
TypeScript
209 lines
6.7 KiB
TypeScript
|
import express from 'express'
|
||
|
import { body, param, query, ValidationChain } from 'express-validator'
|
||
|
import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc.js'
|
||
|
import { isRegistrationModerationResponseValid, isRegistrationReasonValid } from '@server/helpers/custom-validators/user-registration.js'
|
||
|
import { CONFIG } from '@server/initializers/config.js'
|
||
|
import { Hooks } from '@server/lib/plugins/hooks.js'
|
||
|
import { HttpStatusCode, UserRegister, UserRegistrationRequest, UserRegistrationState } from '@peertube/peertube-models'
|
||
|
import { isUserDisplayNameValid, isUserPasswordValid, isUserUsernameValid } from '../../helpers/custom-validators/users.js'
|
||
|
import { isVideoChannelDisplayNameValid, isVideoChannelUsernameValid } from '../../helpers/custom-validators/video-channels.js'
|
||
|
import { isSignupAllowed, isSignupAllowedForCurrentIP, SignupMode } from '../../lib/signup.js'
|
||
|
import { ActorModel } from '../../models/actor/actor.js'
|
||
|
import { areValidationErrors, checkUserNameOrEmailDoNotAlreadyExist } from './shared/index.js'
|
||
|
import { checkRegistrationHandlesDoNotAlreadyExist, checkRegistrationIdExist } from './shared/user-registrations.js'
|
||
|
|
||
|
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 allowed'
|
||
|
})
|
||
|
}
|
||
|
|
||
|
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()
|
||
|
}
|
||
|
]
|
||
|
}
|