]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/api/users.ts
Bold dependencies part in production guide
[github/Chocobozzz/PeerTube.git] / server / controllers / api / users.ts
CommitLineData
4d4e5cd4 1import * as express from 'express'
ac81d1a0 2import 'multer'
c5911fd3
C
3import { extname, join } from 'path'
4import * as uuidv4 from 'uuid/v4'
490b595a 5import * as RateLimit from 'express-rate-limit'
571389d4 6import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
da854ddd 7import { retryTransactionWrapper } from '../../helpers/database-utils'
ac81d1a0 8import { processImage } from '../../helpers/image-utils'
da854ddd 9import { logger } from '../../helpers/logger'
f076daa7 10import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
490b595a 11import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers'
a5625b41 12import { updateActorAvatarInstance } from '../../lib/activitypub'
2422c46b 13import { sendUpdateActor } from '../../lib/activitypub/send'
ecb4e35f 14import { Emailer } from '../../lib/emailer'
ecb4e35f 15import { Redis } from '../../lib/redis'
50d6de9c 16import { createUserAccountAndChannel } from '../../lib/user'
65fcc311 17import {
f076daa7
C
18 asyncMiddleware,
19 authenticate,
20 ensureUserHasRight,
21 ensureUserRegistrationAllowed,
22 paginationValidator,
23 setDefaultPagination,
24 setDefaultSort,
25 token,
26 usersAddValidator,
27 usersGetValidator,
28 usersRegisterValidator,
29 usersRemoveValidator,
30 usersSortValidator,
31 usersUpdateMeValidator,
32 usersUpdateValidator,
33 usersVideoRatingValidator
65fcc311 34} from '../../middlewares'
ecb4e35f 35import {
f076daa7
C
36 usersAskResetPasswordValidator,
37 usersResetPasswordValidator,
38 usersUpdateMyAvatarValidator,
ecb4e35f
C
39 videosSortValidator
40} from '../../middlewares/validators'
3fd3ab2d
C
41import { AccountVideoRateModel } from '../../models/account/account-video-rate'
42import { UserModel } from '../../models/account/user'
f8b8c36b 43import { OAuthTokenModel } from '../../models/oauth/oauth-token'
3fd3ab2d 44import { VideoModel } from '../../models/video/video'
0883b324 45import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type'
65fcc311 46
ac81d1a0 47const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
490b595a
C
48const loginRateLimiter = new RateLimit({
49 windowMs: RATES_LIMIT.LOGIN.WINDOW_MS,
50 max: RATES_LIMIT.LOGIN.MAX,
51 delayMs: 0
52})
c5911fd3 53
65fcc311
C
54const usersRouter = express.Router()
55
56usersRouter.get('/me',
57 authenticate,
eb080476 58 asyncMiddleware(getUserInformation)
d38b8281
C
59)
60
ce5496d6
C
61usersRouter.get('/me/video-quota-used',
62 authenticate,
63 asyncMiddleware(getUserVideoQuotaUsed)
64)
65
fd45e8f4
C
66usersRouter.get('/me/videos',
67 authenticate,
68 paginationValidator,
69 videosSortValidator,
1174a847 70 setDefaultSort,
f05a1c30 71 setDefaultPagination,
fd45e8f4
C
72 asyncMiddleware(getUserVideos)
73)
74
65fcc311
C
75usersRouter.get('/me/videos/:videoId/rating',
76 authenticate,
a2431b7d 77 asyncMiddleware(usersVideoRatingValidator),
eb080476 78 asyncMiddleware(getUserVideoRating)
d38b8281 79)
9bd26629 80
65fcc311 81usersRouter.get('/',
86d13ec2
C
82 authenticate,
83 ensureUserHasRight(UserRight.MANAGE_USERS),
65fcc311
C
84 paginationValidator,
85 usersSortValidator,
1174a847 86 setDefaultSort,
f05a1c30 87 setDefaultPagination,
eb080476 88 asyncMiddleware(listUsers)
5c39adb7
C
89)
90
8094a898 91usersRouter.get('/:id',
94ff4c23
C
92 authenticate,
93 ensureUserHasRight(UserRight.MANAGE_USERS),
a2431b7d 94 asyncMiddleware(usersGetValidator),
8094a898
C
95 getUser
96)
97
65fcc311
C
98usersRouter.post('/',
99 authenticate,
954605a8 100 ensureUserHasRight(UserRight.MANAGE_USERS),
a2431b7d
C
101 asyncMiddleware(usersAddValidator),
102 asyncMiddleware(createUserRetryWrapper)
9bd26629
C
103)
104
65fcc311 105usersRouter.post('/register',
a2431b7d
C
106 asyncMiddleware(ensureUserRegistrationAllowed),
107 asyncMiddleware(usersRegisterValidator),
47e0652b 108 asyncMiddleware(registerUserRetryWrapper)
2c2e9092
C
109)
110
8094a898
C
111usersRouter.put('/me',
112 authenticate,
113 usersUpdateMeValidator,
eb080476 114 asyncMiddleware(updateMe)
8094a898
C
115)
116
c5911fd3
C
117usersRouter.post('/me/avatar/pick',
118 authenticate,
119 reqAvatarFile,
120 usersUpdateMyAvatarValidator,
121 asyncMiddleware(updateMyAvatar)
122)
123
65fcc311
C
124usersRouter.put('/:id',
125 authenticate,
954605a8 126 ensureUserHasRight(UserRight.MANAGE_USERS),
a2431b7d 127 asyncMiddleware(usersUpdateValidator),
eb080476 128 asyncMiddleware(updateUser)
9bd26629
C
129)
130
65fcc311
C
131usersRouter.delete('/:id',
132 authenticate,
954605a8 133 ensureUserHasRight(UserRight.MANAGE_USERS),
a2431b7d 134 asyncMiddleware(usersRemoveValidator),
eb080476 135 asyncMiddleware(removeUser)
9bd26629 136)
6606150c 137
ecb4e35f
C
138usersRouter.post('/ask-reset-password',
139 asyncMiddleware(usersAskResetPasswordValidator),
140 asyncMiddleware(askResetUserPassword)
141)
142
143usersRouter.post('/:id/reset-password',
144 asyncMiddleware(usersResetPasswordValidator),
145 asyncMiddleware(resetUserPassword)
146)
147
490b595a
C
148usersRouter.post('/token',
149 loginRateLimiter,
150 token,
151 success
152)
9bd26629 153// TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route
9457bf88
C
154
155// ---------------------------------------------------------------------------
156
65fcc311
C
157export {
158 usersRouter
159}
9457bf88
C
160
161// ---------------------------------------------------------------------------
162
fd45e8f4 163async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
3fd3ab2d 164 const user = res.locals.oauth.token.User as UserModel
0883b324
C
165 const resultList = await VideoModel.listAccountVideosForApi(
166 user.Account.id,
167 req.query.start as number,
168 req.query.count as number,
169 req.query.sort as VideoSortField,
170 false // Display my NSFW videos
171 )
fd45e8f4
C
172
173 return res.json(getFormattedObjects(resultList.data, resultList.total))
174}
175
eb080476 176async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
72c7248b 177 const options = {
47e0652b 178 arguments: [ req ],
72c7248b
C
179 errorMessage: 'Cannot insert the user with many retries.'
180 }
181
f05a1c30 182 const { user, account } = await retryTransactionWrapper(createUser, options)
eb080476 183
f05a1c30
C
184 return res.json({
185 user: {
186 id: user.id,
187 uuid: account.uuid
188 }
189 }).end()
72c7248b
C
190}
191
47e0652b 192async function createUser (req: express.Request) {
4771e000 193 const body: UserCreate = req.body
f05a1c30 194 const userToCreate = new UserModel({
4771e000
C
195 username: body.username,
196 password: body.password,
197 email: body.email,
0883b324 198 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
7efe153b 199 autoPlayVideo: true,
954605a8 200 role: body.role,
b0f9f39e 201 videoQuota: body.videoQuota
9bd26629
C
202 })
203
f05a1c30 204 const { user, account } = await createUserAccountAndChannel(userToCreate)
eb080476 205
38fa2065 206 logger.info('User %s with its channel and account created.', body.username)
f05a1c30
C
207
208 return { user, account }
9bd26629
C
209}
210
47e0652b
C
211async function registerUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
212 const options = {
213 arguments: [ req ],
214 errorMessage: 'Cannot insert the user with many retries.'
215 }
216
217 await retryTransactionWrapper(registerUser, options)
218
219 return res.type('json').status(204).end()
220}
221
222async function registerUser (req: express.Request) {
77a5501f
C
223 const body: UserCreate = req.body
224
3fd3ab2d 225 const user = new UserModel({
77a5501f
C
226 username: body.username,
227 password: body.password,
228 email: body.email,
0883b324 229 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
7efe153b 230 autoPlayVideo: true,
954605a8 231 role: UserRole.USER,
77a5501f
C
232 videoQuota: CONFIG.USER.VIDEO_QUOTA
233 })
234
38fa2065 235 await createUserAccountAndChannel(user)
47e0652b
C
236
237 logger.info('User %s with its channel and account registered.', body.username)
77a5501f
C
238}
239
eb080476 240async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
fd45e8f4 241 // We did not load channels in res.locals.user
3fd3ab2d 242 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
eb080476
C
243
244 return res.json(user.toFormattedJSON())
99a64bfe
C
245}
246
ce5496d6
C
247async function getUserVideoQuotaUsed (req: express.Request, res: express.Response, next: express.NextFunction) {
248 // We did not load channels in res.locals.user
249 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
250 const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user)
251
252 return res.json({
253 videoQuotaUsed
254 })
255}
256
8094a898 257function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
ce5496d6 258 return res.json((res.locals.user as UserModel).toFormattedJSON())
8094a898
C
259}
260
eb080476 261async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
0a6658fd 262 const videoId = +req.params.videoId
571389d4 263 const accountId = +res.locals.oauth.token.User.Account.id
d38b8281 264
3fd3ab2d 265 const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)
faab3a84
C
266 const rating = ratingObj ? ratingObj.type : 'none'
267
268 const json: FormattedUserVideoRate = {
269 videoId,
270 rating
271 }
272 res.json(json)
d38b8281
C
273}
274
eb080476 275async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
3fd3ab2d 276 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort)
eb080476
C
277
278 return res.json(getFormattedObjects(resultList.data, resultList.total))
9bd26629
C
279}
280
eb080476 281async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
3fd3ab2d 282 const user = await UserModel.loadById(req.params.id)
eb080476
C
283
284 await user.destroy()
285
286 return res.sendStatus(204)
9bd26629
C
287}
288
eb080476 289async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
8094a898 290 const body: UserUpdateMe = req.body
4771e000 291
2422c46b 292 const user: UserModel = res.locals.oauth.token.user
1d49e1e2 293
eb080476
C
294 if (body.password !== undefined) user.password = body.password
295 if (body.email !== undefined) user.email = body.email
0883b324 296 if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
7efe153b 297 if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
eb080476 298
2422c46b
C
299 await sequelizeTypescript.transaction(async t => {
300 await user.save({ transaction: t })
301
302 if (body.description !== undefined) user.Account.description = body.description
303 await user.Account.save({ transaction: t })
304
305 await sendUpdateActor(user.Account, t)
306 })
eb080476 307
d412e80e 308 return res.sendStatus(204)
9bd26629
C
309}
310
c5911fd3
C
311async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
312 const avatarPhysicalFile = req.files['avatarfile'][0]
265ba139
C
313 const user = res.locals.oauth.token.user
314 const actor = user.Account.Actor
c5911fd3 315
c5911fd3
C
316 const extension = extname(avatarPhysicalFile.filename)
317 const avatarName = uuidv4() + extension
ac81d1a0
C
318 const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
319 await processImage(avatarPhysicalFile, destination, AVATARS_SIZE)
c5911fd3 320
a5625b41 321 const avatar = await sequelizeTypescript.transaction(async t => {
bb82394c
C
322 const updatedActor = await updateActorAvatarInstance(actor, avatarName, t)
323 await updatedActor.save({ transaction: t })
c5911fd3 324
2422c46b 325 await sendUpdateActor(user.Account, t)
c5911fd3 326
bb82394c 327 return updatedActor.Avatar
c5911fd3
C
328 })
329
330 return res
331 .json({
332 avatar: avatar.toFormattedJSON()
333 })
334 .end()
335}
336
eb080476 337async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
8094a898 338 const body: UserUpdate = req.body
3fd3ab2d 339 const user = res.locals.user as UserModel
f8b8c36b 340 const roleChanged = body.role !== undefined && body.role !== user.role
8094a898
C
341
342 if (body.email !== undefined) user.email = body.email
343 if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
954605a8 344 if (body.role !== undefined) user.role = body.role
8094a898 345
eb080476
C
346 await user.save()
347
f8b8c36b
C
348 // Destroy user token to refresh rights
349 if (roleChanged) {
350 await OAuthTokenModel.deleteUserToken(user.id)
351 }
352
265ba139
C
353 // Don't need to send this update to followers, these attributes are not propagated
354
eb080476 355 return res.sendStatus(204)
8094a898
C
356}
357
ecb4e35f
C
358async function askResetUserPassword (req: express.Request, res: express.Response, next: express.NextFunction) {
359 const user = res.locals.user as UserModel
360
361 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id)
362 const url = CONFIG.WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
363 await Emailer.Instance.addForgetPasswordEmailJob(user.email, url)
364
365 return res.status(204).end()
366}
367
368async function resetUserPassword (req: express.Request, res: express.Response, next: express.NextFunction) {
369 const user = res.locals.user as UserModel
370 user.password = req.body.password
371
372 await user.save()
373
374 return res.status(204).end()
375}
376
69818c93 377function success (req: express.Request, res: express.Response, next: express.NextFunction) {
9457bf88
C
378 res.end()
379}