2023-01-19 02:27:16 -06:00
|
|
|
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'
|
2023-01-20 08:34:01 -06:00
|
|
|
import {
|
|
|
|
HttpStatusCode,
|
|
|
|
UserRegister,
|
|
|
|
UserRegistrationRequest,
|
|
|
|
UserRegistrationState,
|
|
|
|
UserRegistrationUpdateState,
|
|
|
|
UserRight
|
|
|
|
} from '@shared/models'
|
2023-01-19 02:27:16 -06:00
|
|
|
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
|
2023-01-20 08:34:01 -06:00
|
|
|
const body: UserRegistrationUpdateState = req.body
|
2023-01-19 02:27:16 -06:00
|
|
|
|
|
|
|
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
|
2023-01-20 08:34:01 -06:00
|
|
|
registration.moderationResponse = body.moderationResponse
|
2023-01-19 02:27:16 -06:00
|
|
|
|
|
|
|
await registration.save()
|
|
|
|
|
|
|
|
logger.info('Registration of %s accepted', registration.username)
|
|
|
|
|
2023-01-20 08:34:01 -06:00
|
|
|
if (body.preventEmailDelivery !== true) {
|
|
|
|
Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
|
|
|
|
}
|
2023-01-19 02:27:16 -06:00
|
|
|
|
|
|
|
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function rejectRegistration (req: express.Request, res: express.Response) {
|
|
|
|
const registration = res.locals.userRegistration
|
2023-01-20 08:34:01 -06:00
|
|
|
const body: UserRegistrationUpdateState = req.body
|
2023-01-19 02:27:16 -06:00
|
|
|
|
|
|
|
registration.state = UserRegistrationState.REJECTED
|
2023-01-20 08:34:01 -06:00
|
|
|
registration.moderationResponse = body.moderationResponse
|
2023-01-19 02:27:16 -06:00
|
|
|
|
|
|
|
await registration.save()
|
|
|
|
|
2023-01-20 08:34:01 -06:00
|
|
|
if (body.preventEmailDelivery !== true) {
|
|
|
|
Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
|
|
|
|
}
|
2023-01-19 02:27:16 -06:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|