1 import express from 'express'
2 import { tokensRouter } from '@server/controllers/api/users/token'
3 import { Hooks } from '@server/lib/plugins/hooks'
4 import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
5 import { MUserAccountDefault } from '@server/types/models'
6 import { pick } from '@shared/core-utils'
7 import { HttpStatusCode, UserCreate, UserCreateResult, UserRight, UserUpdate } from '@shared/models'
8 import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
9 import { logger } from '../../../helpers/logger'
10 import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
11 import { WEBSERVER } from '../../../initializers/constants'
12 import { sequelizeTypescript } from '../../../initializers/database'
13 import { Emailer } from '../../../lib/emailer'
14 import { Redis } from '../../../lib/redis'
15 import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user'
17 adminUsersSortValidator,
19 asyncRetryTransactionMiddleware,
25 userAutocompleteValidator,
31 } from '../../../middlewares'
33 ensureCanModerateUser,
34 usersAskResetPasswordValidator,
35 usersBlockingValidator,
36 usersResetPasswordValidator
37 } from '../../../middlewares/validators'
38 import { UserModel } from '../../../models/user/user'
39 import { emailVerificationRouter } from './email-verification'
40 import { meRouter } from './me'
41 import { myAbusesRouter } from './my-abuses'
42 import { myBlocklistRouter } from './my-blocklist'
43 import { myVideosHistoryRouter } from './my-history'
44 import { myNotificationsRouter } from './my-notifications'
45 import { mySubscriptionsRouter } from './my-subscriptions'
46 import { myVideoPlaylistsRouter } from './my-video-playlists'
47 import { registrationsRouter } from './registrations'
48 import { twoFactorRouter } from './two-factor'
50 const auditLogger = auditLoggerFactory('users')
52 const usersRouter = express.Router()
53 usersRouter.use('/', emailVerificationRouter)
54 usersRouter.use('/', registrationsRouter)
55 usersRouter.use('/', twoFactorRouter)
56 usersRouter.use('/', tokensRouter)
57 usersRouter.use('/', myNotificationsRouter)
58 usersRouter.use('/', mySubscriptionsRouter)
59 usersRouter.use('/', myBlocklistRouter)
60 usersRouter.use('/', myVideosHistoryRouter)
61 usersRouter.use('/', myVideoPlaylistsRouter)
62 usersRouter.use('/', myAbusesRouter)
63 usersRouter.use('/', meRouter)
65 usersRouter.get('/autocomplete',
66 userAutocompleteValidator,
67 asyncMiddleware(autocompleteUsers)
72 ensureUserHasRight(UserRight.MANAGE_USERS),
74 adminUsersSortValidator,
78 asyncMiddleware(listUsers)
81 usersRouter.post('/:id/block',
83 ensureUserHasRight(UserRight.MANAGE_USERS),
84 asyncMiddleware(usersBlockingValidator),
85 ensureCanModerateUser,
86 asyncMiddleware(blockUser)
88 usersRouter.post('/:id/unblock',
90 ensureUserHasRight(UserRight.MANAGE_USERS),
91 asyncMiddleware(usersBlockingValidator),
92 ensureCanModerateUser,
93 asyncMiddleware(unblockUser)
96 usersRouter.get('/:id',
98 ensureUserHasRight(UserRight.MANAGE_USERS),
99 asyncMiddleware(usersGetValidator),
103 usersRouter.post('/',
105 ensureUserHasRight(UserRight.MANAGE_USERS),
106 asyncMiddleware(usersAddValidator),
107 asyncRetryTransactionMiddleware(createUser)
110 usersRouter.put('/:id',
112 ensureUserHasRight(UserRight.MANAGE_USERS),
113 asyncMiddleware(usersUpdateValidator),
114 ensureCanModerateUser,
115 asyncMiddleware(updateUser)
118 usersRouter.delete('/:id',
120 ensureUserHasRight(UserRight.MANAGE_USERS),
121 asyncMiddleware(usersRemoveValidator),
122 ensureCanModerateUser,
123 asyncMiddleware(removeUser)
126 usersRouter.post('/ask-reset-password',
127 asyncMiddleware(usersAskResetPasswordValidator),
128 asyncMiddleware(askResetUserPassword)
131 usersRouter.post('/:id/reset-password',
132 asyncMiddleware(usersResetPasswordValidator),
133 asyncMiddleware(resetUserPassword)
136 // ---------------------------------------------------------------------------
142 // ---------------------------------------------------------------------------
144 async function createUser (req: express.Request, res: express.Response) {
145 const body: UserCreate = req.body
147 const userToCreate = buildUser({
148 ...pick(body, [ 'username', 'password', 'email', 'role', 'videoQuota', 'videoQuotaDaily', 'adminFlags' ]),
153 // NB: due to the validator usersAddValidator, password==='' can only be true if we can send the mail.
154 const createPassword = userToCreate.password === ''
155 if (createPassword) {
156 userToCreate.password = await generateRandomString(20)
159 const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
161 channelNames: body.channelName && { name: body.channelName, displayName: body.channelName }
164 auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
165 logger.info('User %s with its channel and account created.', body.username)
167 if (createPassword) {
168 // this will send an email for newly created users, so then can set their first password.
169 logger.info('Sending to user %s a create password email', body.username)
170 const verificationString = await Redis.Instance.setCreatePasswordVerificationString(user.id)
171 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
172 Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url)
175 Hooks.runAction('action:api.user.created', { body, user, account, videoChannel, req, res })
183 } as UserCreateResult
187 async function unblockUser (req: express.Request, res: express.Response) {
188 const user = res.locals.user
190 await changeUserBlock(res, user, false)
192 Hooks.runAction('action:api.user.unblocked', { user, req, res })
194 return res.status(HttpStatusCode.NO_CONTENT_204).end()
197 async function blockUser (req: express.Request, res: express.Response) {
198 const user = res.locals.user
199 const reason = req.body.reason
201 await changeUserBlock(res, user, true, reason)
203 Hooks.runAction('action:api.user.blocked', { user, req, res })
205 return res.status(HttpStatusCode.NO_CONTENT_204).end()
208 function getUser (req: express.Request, res: express.Response) {
209 return res.json(res.locals.user.toFormattedJSON({ withAdminFlags: true }))
212 async function autocompleteUsers (req: express.Request, res: express.Response) {
213 const resultList = await UserModel.autoComplete(req.query.search as string)
215 return res.json(resultList)
218 async function listUsers (req: express.Request, res: express.Response) {
219 const resultList = await UserModel.listForAdminApi({
220 start: req.query.start,
221 count: req.query.count,
222 sort: req.query.sort,
223 search: req.query.search,
224 blocked: req.query.blocked
227 return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true }))
230 async function removeUser (req: express.Request, res: express.Response) {
231 const user = res.locals.user
233 auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
235 await sequelizeTypescript.transaction(async t => {
236 // Use a transaction to avoid inconsistencies with hooks (account/channel deletion & federation)
237 await user.destroy({ transaction: t })
240 Hooks.runAction('action:api.user.deleted', { user, req, res })
242 return res.status(HttpStatusCode.NO_CONTENT_204).end()
245 async function updateUser (req: express.Request, res: express.Response) {
246 const body: UserUpdate = req.body
247 const userToUpdate = res.locals.user
248 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
249 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
251 const keysToUpdate: (keyof UserUpdate)[] = [
262 for (const key of keysToUpdate) {
263 if (body[key] !== undefined) userToUpdate.set(key, body[key])
266 const user = await userToUpdate.save()
268 // Destroy user token to refresh rights
269 if (roleChanged || body.password !== undefined) await OAuthTokenModel.deleteUserToken(userToUpdate.id)
271 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
273 Hooks.runAction('action:api.user.updated', { user, req, res })
275 // Don't need to send this update to followers, these attributes are not federated
277 return res.status(HttpStatusCode.NO_CONTENT_204).end()
280 async function askResetUserPassword (req: express.Request, res: express.Response) {
281 const user = res.locals.user
283 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id)
284 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
285 Emailer.Instance.addPasswordResetEmailJob(user.username, user.email, url)
287 return res.status(HttpStatusCode.NO_CONTENT_204).end()
290 async function resetUserPassword (req: express.Request, res: express.Response) {
291 const user = res.locals.user
292 user.password = req.body.password
295 await Redis.Instance.removePasswordVerificationString(user.id)
297 return res.status(HttpStatusCode.NO_CONTENT_204).end()
300 async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) {
301 const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
304 user.blockedReason = reason || null
306 await sequelizeTypescript.transaction(async t => {
307 await OAuthTokenModel.deleteUserToken(user.id, t)
309 await user.save({ transaction: t })
312 Emailer.Instance.addUserBlockJob(user, block, reason)
314 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)