]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/controllers/api/users.ts
6540adb1c1a797f9199a9309bdbc2258bf5c58bd
[github/Chocobozzz/PeerTube.git] / server / controllers / api / users.ts
1 import * as express from 'express'
2 import 'multer'
3 import { extname, join } from 'path'
4 import * as uuidv4 from 'uuid/v4'
5 import * as RateLimit from 'express-rate-limit'
6 import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
7 import { retryTransactionWrapper } from '../../helpers/database-utils'
8 import { processImage } from '../../helpers/image-utils'
9 import { logger } from '../../helpers/logger'
10 import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
11 import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers'
12 import { updateActorAvatarInstance } from '../../lib/activitypub'
13 import { sendUpdateActor } from '../../lib/activitypub/send'
14 import { Emailer } from '../../lib/emailer'
15 import { Redis } from '../../lib/redis'
16 import { createUserAccountAndChannel } from '../../lib/user'
17 import {
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
34 } from '../../middlewares'
35 import {
36 usersAskResetPasswordValidator,
37 usersResetPasswordValidator,
38 usersUpdateMyAvatarValidator,
39 videosSortValidator
40 } from '../../middlewares/validators'
41 import { AccountVideoRateModel } from '../../models/account/account-video-rate'
42 import { UserModel } from '../../models/account/user'
43 import { OAuthTokenModel } from '../../models/oauth/oauth-token'
44 import { VideoModel } from '../../models/video/video'
45 import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type'
46
47 const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
48 const loginRateLimiter = new RateLimit({
49 windowMs: RATES_LIMIT.LOGIN.WINDOW_MS,
50 max: RATES_LIMIT.LOGIN.MAX,
51 delayMs: 0
52 })
53
54 const usersRouter = express.Router()
55
56 usersRouter.get('/me',
57 authenticate,
58 asyncMiddleware(getUserInformation)
59 )
60
61 usersRouter.get('/me/video-quota-used',
62 authenticate,
63 asyncMiddleware(getUserVideoQuotaUsed)
64 )
65
66 usersRouter.get('/me/videos',
67 authenticate,
68 paginationValidator,
69 videosSortValidator,
70 setDefaultSort,
71 setDefaultPagination,
72 asyncMiddleware(getUserVideos)
73 )
74
75 usersRouter.get('/me/videos/:videoId/rating',
76 authenticate,
77 asyncMiddleware(usersVideoRatingValidator),
78 asyncMiddleware(getUserVideoRating)
79 )
80
81 usersRouter.get('/',
82 authenticate,
83 ensureUserHasRight(UserRight.MANAGE_USERS),
84 paginationValidator,
85 usersSortValidator,
86 setDefaultSort,
87 setDefaultPagination,
88 asyncMiddleware(listUsers)
89 )
90
91 usersRouter.get('/:id',
92 authenticate,
93 ensureUserHasRight(UserRight.MANAGE_USERS),
94 asyncMiddleware(usersGetValidator),
95 getUser
96 )
97
98 usersRouter.post('/',
99 authenticate,
100 ensureUserHasRight(UserRight.MANAGE_USERS),
101 asyncMiddleware(usersAddValidator),
102 asyncMiddleware(createUserRetryWrapper)
103 )
104
105 usersRouter.post('/register',
106 asyncMiddleware(ensureUserRegistrationAllowed),
107 asyncMiddleware(usersRegisterValidator),
108 asyncMiddleware(registerUserRetryWrapper)
109 )
110
111 usersRouter.put('/me',
112 authenticate,
113 usersUpdateMeValidator,
114 asyncMiddleware(updateMe)
115 )
116
117 usersRouter.post('/me/avatar/pick',
118 authenticate,
119 reqAvatarFile,
120 usersUpdateMyAvatarValidator,
121 asyncMiddleware(updateMyAvatar)
122 )
123
124 usersRouter.put('/:id',
125 authenticate,
126 ensureUserHasRight(UserRight.MANAGE_USERS),
127 asyncMiddleware(usersUpdateValidator),
128 asyncMiddleware(updateUser)
129 )
130
131 usersRouter.delete('/:id',
132 authenticate,
133 ensureUserHasRight(UserRight.MANAGE_USERS),
134 asyncMiddleware(usersRemoveValidator),
135 asyncMiddleware(removeUser)
136 )
137
138 usersRouter.post('/ask-reset-password',
139 asyncMiddleware(usersAskResetPasswordValidator),
140 asyncMiddleware(askResetUserPassword)
141 )
142
143 usersRouter.post('/:id/reset-password',
144 asyncMiddleware(usersResetPasswordValidator),
145 asyncMiddleware(resetUserPassword)
146 )
147
148 usersRouter.post('/token',
149 loginRateLimiter,
150 token,
151 success
152 )
153 // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route
154
155 // ---------------------------------------------------------------------------
156
157 export {
158 usersRouter
159 }
160
161 // ---------------------------------------------------------------------------
162
163 async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
164 const user = res.locals.oauth.token.User as UserModel
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 )
172
173 return res.json(getFormattedObjects(resultList.data, resultList.total))
174 }
175
176 async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
177 const options = {
178 arguments: [ req ],
179 errorMessage: 'Cannot insert the user with many retries.'
180 }
181
182 const { user, account } = await retryTransactionWrapper(createUser, options)
183
184 return res.json({
185 user: {
186 id: user.id,
187 uuid: account.uuid
188 }
189 }).end()
190 }
191
192 async function createUser (req: express.Request) {
193 const body: UserCreate = req.body
194 const userToCreate = new UserModel({
195 username: body.username,
196 password: body.password,
197 email: body.email,
198 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
199 autoPlayVideo: true,
200 role: body.role,
201 videoQuota: body.videoQuota
202 })
203
204 const { user, account } = await createUserAccountAndChannel(userToCreate)
205
206 logger.info('User %s with its channel and account created.', body.username)
207
208 return { user, account }
209 }
210
211 async 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
222 async function registerUser (req: express.Request) {
223 const body: UserCreate = req.body
224
225 const user = new UserModel({
226 username: body.username,
227 password: body.password,
228 email: body.email,
229 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
230 autoPlayVideo: true,
231 role: UserRole.USER,
232 videoQuota: CONFIG.USER.VIDEO_QUOTA
233 })
234
235 await createUserAccountAndChannel(user)
236
237 logger.info('User %s with its channel and account registered.', body.username)
238 }
239
240 async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
241 // We did not load channels in res.locals.user
242 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
243
244 return res.json(user.toFormattedJSON())
245 }
246
247 async 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
257 function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
258 return res.json((res.locals.user as UserModel).toFormattedJSON())
259 }
260
261 async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
262 const videoId = +req.params.videoId
263 const accountId = +res.locals.oauth.token.User.Account.id
264
265 const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)
266 const rating = ratingObj ? ratingObj.type : 'none'
267
268 const json: FormattedUserVideoRate = {
269 videoId,
270 rating
271 }
272 res.json(json)
273 }
274
275 async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
276 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort)
277
278 return res.json(getFormattedObjects(resultList.data, resultList.total))
279 }
280
281 async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
282 const user = await UserModel.loadById(req.params.id)
283
284 await user.destroy()
285
286 return res.sendStatus(204)
287 }
288
289 async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
290 const body: UserUpdateMe = req.body
291
292 const user: UserModel = res.locals.oauth.token.user
293
294 if (body.password !== undefined) user.password = body.password
295 if (body.email !== undefined) user.email = body.email
296 if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
297 if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
298
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 })
307
308 return res.sendStatus(204)
309 }
310
311 async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
312 const avatarPhysicalFile = req.files['avatarfile'][0]
313 const user = res.locals.oauth.token.user
314 const actor = user.Account.Actor
315
316 const extension = extname(avatarPhysicalFile.filename)
317 const avatarName = uuidv4() + extension
318 const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
319 await processImage(avatarPhysicalFile, destination, AVATARS_SIZE)
320
321 const avatar = await sequelizeTypescript.transaction(async t => {
322 const updatedActor = await updateActorAvatarInstance(actor, avatarName, t)
323 await updatedActor.save({ transaction: t })
324
325 await sendUpdateActor(user.Account, t)
326
327 return updatedActor.Avatar
328 })
329
330 return res
331 .json({
332 avatar: avatar.toFormattedJSON()
333 })
334 .end()
335 }
336
337 async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
338 const body: UserUpdate = req.body
339 const user = res.locals.user as UserModel
340 const roleChanged = body.role !== undefined && body.role !== user.role
341
342 if (body.email !== undefined) user.email = body.email
343 if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
344 if (body.role !== undefined) user.role = body.role
345
346 await user.save()
347
348 // Destroy user token to refresh rights
349 if (roleChanged) {
350 await OAuthTokenModel.deleteUserToken(user.id)
351 }
352
353 // Don't need to send this update to followers, these attributes are not propagated
354
355 return res.sendStatus(204)
356 }
357
358 async 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
368 async 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
377 function success (req: express.Request, res: express.Response, next: express.NextFunction) {
378 res.end()
379 }