]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/api/users/index.ts
Fix channel sync right check
[github/Chocobozzz/PeerTube.git] / server / controllers / api / users / index.ts
CommitLineData
41fb13c3 1import express from 'express'
edbc9325
C
2import { tokensRouter } from '@server/controllers/api/users/token'
3import { Hooks } from '@server/lib/plugins/hooks'
f43db2f4 4import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
d3d3deaa
C
5import { MUserAccountDefault } from '@server/types/models'
6import { pick } from '@shared/core-utils'
7import { HttpStatusCode, UserCreate, UserCreateResult, UserRegister, UserRight, UserUpdate } from '@shared/models'
edbc9325 8import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
d03cd8bb 9import { logger } from '../../../helpers/logger'
45f1bd72 10import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
edbc9325 11import { CONFIG } from '../../../initializers/config'
c1340a6a 12import { WEBSERVER } from '../../../initializers/constants'
edbc9325 13import { sequelizeTypescript } from '../../../initializers/database'
d03cd8bb 14import { Emailer } from '../../../lib/emailer'
edbc9325 15import { Notifier } from '../../../lib/notifier'
d03cd8bb 16import { Redis } from '../../../lib/redis'
d3d3deaa 17import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
65fcc311 18import {
e5a781ec 19 adminUsersSortValidator,
f076daa7 20 asyncMiddleware,
90d4bb81 21 asyncRetryTransactionMiddleware,
f076daa7 22 authenticate,
e5a781ec 23 buildRateLimiter,
f076daa7
C
24 ensureUserHasRight,
25 ensureUserRegistrationAllowed,
ff2c1fe8 26 ensureUserRegistrationAllowedForIP,
f076daa7
C
27 paginationValidator,
28 setDefaultPagination,
29 setDefaultSort,
74d63469 30 userAutocompleteValidator,
f076daa7
C
31 usersAddValidator,
32 usersGetValidator,
edbc9325 33 usersListValidator,
f076daa7
C
34 usersRegisterValidator,
35 usersRemoveValidator,
d03cd8bb
C
36 usersUpdateValidator
37} from '../../../middlewares'
d9eaee39 38import {
d4d9bbc6 39 ensureCanModerateUser,
993cef4b
C
40 usersAskResetPasswordValidator,
41 usersAskSendVerifyEmailValidator,
42 usersBlockingValidator,
43 usersResetPasswordValidator,
e1c55031 44 usersVerifyEmailValidator
d9eaee39 45} from '../../../middlewares/validators'
7d9ba5c0 46import { UserModel } from '../../../models/user/user'
d03cd8bb 47import { meRouter } from './me'
edbc9325 48import { myAbusesRouter } from './my-abuses'
7ad9b984 49import { myBlocklistRouter } from './my-blocklist'
8b9a525a 50import { myVideosHistoryRouter } from './my-history'
cef534ed 51import { myNotificationsRouter } from './my-notifications'
cf405589 52import { mySubscriptionsRouter } from './my-subscriptions'
edbc9325 53import { myVideoPlaylistsRouter } from './my-video-playlists'
80e36cd9
AB
54
55const auditLogger = auditLoggerFactory('users')
65fcc311 56
e5a781ec 57const signupRateLimiter = buildRateLimiter({
c1340a6a
C
58 windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS,
59 max: CONFIG.RATES_LIMIT.SIGNUP.MAX,
60 skipFailedRequests: true
490b595a 61})
c5911fd3 62
e5a781ec 63const askSendEmailLimiter = buildRateLimiter({
c1340a6a
C
64 windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
65 max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX
288fe385
C
66})
67
65fcc311 68const usersRouter = express.Router()
e1c55031 69usersRouter.use('/', tokensRouter)
cef534ed 70usersRouter.use('/', myNotificationsRouter)
cf405589 71usersRouter.use('/', mySubscriptionsRouter)
7ad9b984 72usersRouter.use('/', myBlocklistRouter)
8b9a525a 73usersRouter.use('/', myVideosHistoryRouter)
f0a39880 74usersRouter.use('/', myVideoPlaylistsRouter)
edbc9325 75usersRouter.use('/', myAbusesRouter)
06a05d5f 76usersRouter.use('/', meRouter)
9bd26629 77
74d63469
GR
78usersRouter.get('/autocomplete',
79 userAutocompleteValidator,
80 asyncMiddleware(autocompleteUsers)
81)
82
65fcc311 83usersRouter.get('/',
86d13ec2
C
84 authenticate,
85 ensureUserHasRight(UserRight.MANAGE_USERS),
65fcc311 86 paginationValidator,
87a0cac6 87 adminUsersSortValidator,
1174a847 88 setDefaultSort,
f05a1c30 89 setDefaultPagination,
ea7337cf 90 usersListValidator,
eb080476 91 asyncMiddleware(listUsers)
5c39adb7
C
92)
93
e6921918
C
94usersRouter.post('/:id/block',
95 authenticate,
96 ensureUserHasRight(UserRight.MANAGE_USERS),
97 asyncMiddleware(usersBlockingValidator),
d4d9bbc6 98 ensureCanModerateUser,
e6921918
C
99 asyncMiddleware(blockUser)
100)
101usersRouter.post('/:id/unblock',
102 authenticate,
103 ensureUserHasRight(UserRight.MANAGE_USERS),
104 asyncMiddleware(usersBlockingValidator),
d4d9bbc6 105 ensureCanModerateUser,
e6921918
C
106 asyncMiddleware(unblockUser)
107)
108
8094a898 109usersRouter.get('/:id',
94ff4c23
C
110 authenticate,
111 ensureUserHasRight(UserRight.MANAGE_USERS),
a2431b7d 112 asyncMiddleware(usersGetValidator),
8094a898
C
113 getUser
114)
115
65fcc311
C
116usersRouter.post('/',
117 authenticate,
954605a8 118 ensureUserHasRight(UserRight.MANAGE_USERS),
a2431b7d 119 asyncMiddleware(usersAddValidator),
90d4bb81 120 asyncRetryTransactionMiddleware(createUser)
9bd26629
C
121)
122
65fcc311 123usersRouter.post('/register',
c1340a6a 124 signupRateLimiter,
a2431b7d 125 asyncMiddleware(ensureUserRegistrationAllowed),
ff2c1fe8 126 ensureUserRegistrationAllowedForIP,
a2431b7d 127 asyncMiddleware(usersRegisterValidator),
90d4bb81 128 asyncRetryTransactionMiddleware(registerUser)
2c2e9092
C
129)
130
65fcc311
C
131usersRouter.put('/:id',
132 authenticate,
954605a8 133 ensureUserHasRight(UserRight.MANAGE_USERS),
a2431b7d 134 asyncMiddleware(usersUpdateValidator),
d4d9bbc6 135 ensureCanModerateUser,
eb080476 136 asyncMiddleware(updateUser)
9bd26629
C
137)
138
65fcc311
C
139usersRouter.delete('/:id',
140 authenticate,
954605a8 141 ensureUserHasRight(UserRight.MANAGE_USERS),
a2431b7d 142 asyncMiddleware(usersRemoveValidator),
d4d9bbc6 143 ensureCanModerateUser,
eb080476 144 asyncMiddleware(removeUser)
9bd26629 145)
6606150c 146
ecb4e35f
C
147usersRouter.post('/ask-reset-password',
148 asyncMiddleware(usersAskResetPasswordValidator),
149 asyncMiddleware(askResetUserPassword)
150)
151
152usersRouter.post('/:id/reset-password',
153 asyncMiddleware(usersResetPasswordValidator),
154 asyncMiddleware(resetUserPassword)
155)
156
d9eaee39 157usersRouter.post('/ask-send-verify-email',
288fe385 158 askSendEmailLimiter,
d9eaee39 159 asyncMiddleware(usersAskSendVerifyEmailValidator),
d1ab89de 160 asyncMiddleware(reSendVerifyUserEmail)
d9eaee39
JM
161)
162
163usersRouter.post('/:id/verify-email',
164 asyncMiddleware(usersVerifyEmailValidator),
165 asyncMiddleware(verifyUserEmail)
166)
167
9457bf88
C
168// ---------------------------------------------------------------------------
169
65fcc311
C
170export {
171 usersRouter
172}
9457bf88
C
173
174// ---------------------------------------------------------------------------
175
90d4bb81 176async function createUser (req: express.Request, res: express.Response) {
4771e000 177 const body: UserCreate = req.body
3d215dc5 178
d3d3deaa
C
179 const userToCreate = buildUser({
180 ...pick(body, [ 'username', 'password', 'email', 'role', 'videoQuota', 'videoQuotaDaily', 'adminFlags' ]),
181
182 emailVerified: null
183 })
9bd26629 184
45f1bd72
JL
185 // NB: due to the validator usersAddValidator, password==='' can only be true if we can send the mail.
186 const createPassword = userToCreate.password === ''
187 if (createPassword) {
188 userToCreate.password = await generateRandomString(20)
189 }
190
3d215dc5 191 const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
192 userToCreate,
69db1470 193 channelNames: body.channelName && { name: body.channelName, displayName: body.channelName }
3d215dc5 194 })
eb080476 195
993cef4b 196 auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
38fa2065 197 logger.info('User %s with its channel and account created.', body.username)
f05a1c30 198
45f1bd72
JL
199 if (createPassword) {
200 // this will send an email for newly created users, so then can set their first password.
201 logger.info('Sending to user %s a create password email', body.username)
202 const verificationString = await Redis.Instance.setCreatePasswordVerificationString(user.id)
203 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
d17c7b4e 204 Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url)
45f1bd72
JL
205 }
206
7226e90f 207 Hooks.runAction('action:api.user.created', { body, user, account, videoChannel, req, res })
6f3fe96f 208
1e904cde 209 return res.json({
90d4bb81
C
210 user: {
211 id: user.id,
212 account: {
57cfff78 213 id: account.id
90d4bb81 214 }
7926c5f9 215 } as UserCreateResult
8795d6f2 216 })
47e0652b
C
217}
218
90d4bb81 219async function registerUser (req: express.Request, res: express.Response) {
e590b4a5 220 const body: UserRegister = req.body
77a5501f 221
d3d3deaa
C
222 const userToCreate = buildUser({
223 ...pick(body, [ 'username', 'password', 'email' ]),
224
d9eaee39 225 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
77a5501f
C
226 })
227
6f3fe96f 228 const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
ba2684ce 229 userToCreate,
1f20622f
C
230 userDisplayName: body.displayName || undefined,
231 channelNames: body.channel
232 })
47e0652b 233
80e36cd9 234 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
47e0652b 235 logger.info('User %s with its channel and account registered.', body.username)
90d4bb81 236
d9eaee39
JM
237 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
238 await sendVerifyUserEmail(user)
239 }
240
f7cc67b4
C
241 Notifier.Instance.notifyOnNewUserRegistration(user)
242
7226e90f 243 Hooks.runAction('action:api.user.registered', { body, user, account, videoChannel, req, res })
6f3fe96f 244
2d53be02 245 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
77a5501f
C
246}
247
dae86118
C
248async function unblockUser (req: express.Request, res: express.Response) {
249 const user = res.locals.user
e6921918
C
250
251 await changeUserBlock(res, user, false)
252
7226e90f 253 Hooks.runAction('action:api.user.unblocked', { user, req, res })
6f3fe96f 254
2d53be02 255 return res.status(HttpStatusCode.NO_CONTENT_204).end()
e6921918
C
256}
257
b426edd4 258async function blockUser (req: express.Request, res: express.Response) {
dae86118 259 const user = res.locals.user
eacb25c4 260 const reason = req.body.reason
e6921918 261
eacb25c4 262 await changeUserBlock(res, user, true, reason)
e6921918 263
7226e90f 264 Hooks.runAction('action:api.user.blocked', { user, req, res })
6f3fe96f 265
2d53be02 266 return res.status(HttpStatusCode.NO_CONTENT_204).end()
e6921918
C
267}
268
b426edd4 269function getUser (req: express.Request, res: express.Response) {
1eddc9a7 270 return res.json(res.locals.user.toFormattedJSON({ withAdminFlags: true }))
8094a898
C
271}
272
b426edd4 273async function autocompleteUsers (req: express.Request, res: express.Response) {
5cf84858 274 const resultList = await UserModel.autoComplete(req.query.search as string)
74d63469
GR
275
276 return res.json(resultList)
277}
278
b426edd4 279async function listUsers (req: express.Request, res: express.Response) {
87a0cac6 280 const resultList = await UserModel.listForAdminApi({
8491293b
RK
281 start: req.query.start,
282 count: req.query.count,
283 sort: req.query.sort,
284 search: req.query.search,
285 blocked: req.query.blocked
286 })
eb080476 287
1eddc9a7 288 return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true }))
9bd26629
C
289}
290
b426edd4 291async function removeUser (req: express.Request, res: express.Response) {
dae86118 292 const user = res.locals.user
eb080476 293
993cef4b 294 auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
80e36cd9 295
fae6e4da
C
296 await sequelizeTypescript.transaction(async t => {
297 // Use a transaction to avoid inconsistencies with hooks (account/channel deletion & federation)
298 await user.destroy({ transaction: t })
299 })
2dbc170d 300
7226e90f 301 Hooks.runAction('action:api.user.deleted', { user, req, res })
6f3fe96f 302
76148b27 303 return res.status(HttpStatusCode.NO_CONTENT_204).end()
9bd26629
C
304}
305
b426edd4 306async function updateUser (req: express.Request, res: express.Response) {
8094a898 307 const body: UserUpdate = req.body
dae86118 308 const userToUpdate = res.locals.user
80e36cd9
AB
309 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
310 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
8094a898 311
16c016e8
C
312 const keysToUpdate: (keyof UserUpdate)[] = [
313 'password',
314 'email',
315 'emailVerified',
316 'videoQuota',
317 'videoQuotaDaily',
318 'role',
319 'adminFlags',
320 'pluginAuth'
321 ]
322
323 for (const key of keysToUpdate) {
324 if (body[key] !== undefined) userToUpdate.set(key, body[key])
325 }
8094a898 326
80e36cd9 327 const user = await userToUpdate.save()
eb080476 328
f8b8c36b 329 // Destroy user token to refresh rights
f43db2f4 330 if (roleChanged || body.password !== undefined) await OAuthTokenModel.deleteUserToken(userToUpdate.id)
f8b8c36b 331
91411dba 332 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
80e36cd9 333
7226e90f 334 Hooks.runAction('action:api.user.updated', { user, req, res })
6f3fe96f 335
b426edd4 336 // Don't need to send this update to followers, these attributes are not federated
265ba139 337
76148b27 338 return res.status(HttpStatusCode.NO_CONTENT_204).end()
8094a898
C
339}
340
dae86118
C
341async function askResetUserPassword (req: express.Request, res: express.Response) {
342 const user = res.locals.user
ecb4e35f
C
343
344 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id)
6dd9de95 345 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
963023ab 346 await Emailer.Instance.addPasswordResetEmailJob(user.username, user.email, url)
ecb4e35f 347
2d53be02 348 return res.status(HttpStatusCode.NO_CONTENT_204).end()
ecb4e35f
C
349}
350
dae86118
C
351async function resetUserPassword (req: express.Request, res: express.Response) {
352 const user = res.locals.user
ecb4e35f
C
353 user.password = req.body.password
354
355 await user.save()
e9c5f123 356 await Redis.Instance.removePasswordVerificationString(user.id)
ecb4e35f 357
2d53be02 358 return res.status(HttpStatusCode.NO_CONTENT_204).end()
ecb4e35f
C
359}
360
d1ab89de 361async function reSendVerifyUserEmail (req: express.Request, res: express.Response) {
dae86118 362 const user = res.locals.user
d9eaee39
JM
363
364 await sendVerifyUserEmail(user)
365
2d53be02 366 return res.status(HttpStatusCode.NO_CONTENT_204).end()
d9eaee39
JM
367}
368
dae86118
C
369async function verifyUserEmail (req: express.Request, res: express.Response) {
370 const user = res.locals.user
d9eaee39
JM
371 user.emailVerified = true
372
d1ab89de
C
373 if (req.body.isPendingEmail === true) {
374 user.email = user.pendingEmail
375 user.pendingEmail = null
376 }
377
d9eaee39
JM
378 await user.save()
379
2d53be02 380 return res.status(HttpStatusCode.NO_CONTENT_204).end()
d9eaee39
JM
381}
382
453e83ea 383async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) {
e6921918
C
384 const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
385
386 user.blocked = block
eacb25c4 387 user.blockedReason = reason || null
e6921918
C
388
389 await sequelizeTypescript.transaction(async t => {
f43db2f4 390 await OAuthTokenModel.deleteUserToken(user.id, t)
e6921918
C
391
392 await user.save({ transaction: t })
393 })
394
eacb25c4
C
395 await Emailer.Instance.addUserBlockJob(user, block, reason)
396
91411dba 397 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
e6921918 398}