import express from 'express'
import { Emailer } from '@server/lib/emailer'
import { Hooks } from '@server/lib/plugins/hooks'
import { UserRegistrationModel } from '@server/models/user/user-registration'
import { pick } from '@shared/core-utils'
import {
HttpStatusCode,
UserRegister,
UserRegistrationRequest,
UserRegistrationState,
UserRegistrationUpdateState,
UserRight
} from '@shared/models'
import { auditLoggerFactory, UserAuditView } from '../../../helpers/audit-logger'
import { logger } from '../../../helpers/logger'
import { CONFIG } from '../../../initializers/config'
import { Notifier } from '../../../lib/notifier'
import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyRegistrationEmail, sendVerifyUserEmail } from '../../../lib/user'
import {
acceptOrRejectRegistrationValidator,
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
buildRateLimiter,
ensureUserHasRight,
ensureUserRegistrationAllowedFactory,
ensureUserRegistrationAllowedForIP,
getRegistrationValidator,
listRegistrationsValidator,
paginationValidator,
setDefaultPagination,
setDefaultSort,
userRegistrationsSortValidator,
usersDirectRegistrationValidator,
usersRequestRegistrationValidator
} from '../../../middlewares'
const auditLogger = auditLoggerFactory('users')
const registrationRateLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS,
max: CONFIG.RATES_LIMIT.SIGNUP.MAX,
skipFailedRequests: true
})
const registrationsRouter = express.Router()
registrationsRouter.post('/registrations/request',
registrationRateLimiter,
asyncMiddleware(ensureUserRegistrationAllowedFactory('request-registration')),
ensureUserRegistrationAllowedForIP,
asyncMiddleware(usersRequestRegistrationValidator),
asyncRetryTransactionMiddleware(requestRegistration)
)
registrationsRouter.post('/registrations/:registrationId/accept',
authenticate,
ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
asyncMiddleware(acceptOrRejectRegistrationValidator),
asyncRetryTransactionMiddleware(acceptRegistration)
)
registrationsRouter.post('/registrations/:registrationId/reject',
authenticate,
ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
asyncMiddleware(acceptOrRejectRegistrationValidator),
asyncRetryTransactionMiddleware(rejectRegistration)
)
registrationsRouter.delete('/registrations/:registrationId',
authenticate,
ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
asyncMiddleware(getRegistrationValidator),
asyncRetryTransactionMiddleware(deleteRegistration)
)
registrationsRouter.get('/registrations',
authenticate,
ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
paginationValidator,
userRegistrationsSortValidator,
setDefaultSort,
setDefaultPagination,
listRegistrationsValidator,
asyncMiddleware(listRegistrations)
)
registrationsRouter.post('/register',
registrationRateLimiter,
asyncMiddleware(ensureUserRegistrationAllowedFactory('direct-registration')),
ensureUserRegistrationAllowedForIP,
asyncMiddleware(usersDirectRegistrationValidator),
asyncRetryTransactionMiddleware(registerUser)
)
// ---------------------------------------------------------------------------
export {
registrationsRouter
}
// ---------------------------------------------------------------------------
async function requestRegistration (req: express.Request, res: express.Response) {
const body: UserRegistrationRequest = req.body
const registration = new UserRegistrationModel({
...pick(body, [ 'username', 'password', 'email', 'registrationReason' ]),
accountDisplayName: body.displayName,
channelDisplayName: body.channel?.displayName,
channelHandle: body.channel?.name,
state: UserRegistrationState.PENDING,
emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
})
await registration.save()
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
await sendVerifyRegistrationEmail(registration)
}
Notifier.Instance.notifyOnNewRegistrationRequest(registration)
Hooks.runAction('action:api.user.requested-registration', { body, registration, req, res })
return res.json(registration.toFormattedJSON())
}
// ---------------------------------------------------------------------------
async function acceptRegistration (req: express.Request, res: express.Response) {
const registration = res.locals.userRegistration
const body: UserRegistrationUpdateState = req.body
const userToCreate = buildUser({
username: registration.username,
password: registration.password,
email: registration.email,
emailVerified: registration.emailVerified
})
// We already encrypted password in registration model
userToCreate.skipPasswordEncryption = true
// TODO: handle conflicts if someone else created a channel handle/user handle/user email between registration and approval
const { user } = await createUserAccountAndChannelAndPlaylist({
userToCreate,
userDisplayName: registration.accountDisplayName,
channelNames: registration.channelHandle && registration.channelDisplayName
? {
name: registration.channelHandle,
displayName: registration.channelDisplayName
}
: undefined
})
registration.userId = user.id
registration.state = UserRegistrationState.ACCEPTED
registration.moderationResponse = body.moderationResponse
await registration.save()
logger.info('Registration of %s accepted', registration.username)
if (body.preventEmailDelivery !== true) {
Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
}
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}
async function rejectRegistration (req: express.Request, res: express.Response) {
const registration = res.locals.userRegistration
const body: UserRegistrationUpdateState = req.body
registration.state = UserRegistrationState.REJECTED
registration.moderationResponse = body.moderationResponse
await registration.save()
if (body.preventEmailDelivery !== true) {
Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
}
logger.info('Registration of %s rejected', registration.username)
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}
// ---------------------------------------------------------------------------
async function deleteRegistration (req: express.Request, res: express.Response) {
const registration = res.locals.userRegistration
await registration.destroy()
logger.info('Registration of %s deleted', registration.username)
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}
// ---------------------------------------------------------------------------
async function listRegistrations (req: express.Request, res: express.Response) {
const resultList = await UserRegistrationModel.listForApi({
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
search: req.query.search
})
return res.json({
total: resultList.total,
data: resultList.data.map(d => d.toFormattedJSON())
})
}
// ---------------------------------------------------------------------------
async function registerUser (req: express.Request, res: express.Response) {
const body: UserRegister = req.body
const userToCreate = buildUser({
...pick(body, [ 'username', 'password', 'email' ]),
emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
})
const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
userToCreate,
userDisplayName: body.displayName || undefined,
channelNames: body.channel
})
auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
logger.info('User %s with its channel and account registered.', body.username)
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
await sendVerifyUserEmail(user)
}
Notifier.Instance.notifyOnNewDirectRegistration(user)
Hooks.runAction('action:api.user.registered', { body, user, account, videoChannel, req, res })
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}