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