index.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. import * as express from 'express'
  2. import * as RateLimit from 'express-rate-limit'
  3. import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
  4. import { logger } from '../../../helpers/logger'
  5. import { getFormattedObjects } from '../../../helpers/utils'
  6. import { WEBSERVER } from '../../../initializers/constants'
  7. import { Emailer } from '../../../lib/emailer'
  8. import { Redis } from '../../../lib/redis'
  9. import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
  10. import {
  11. asyncMiddleware,
  12. asyncRetryTransactionMiddleware,
  13. authenticate,
  14. ensureUserHasRight,
  15. ensureUserRegistrationAllowed,
  16. ensureUserRegistrationAllowedForIP,
  17. paginationValidator,
  18. setDefaultPagination,
  19. setDefaultSort,
  20. token,
  21. userAutocompleteValidator,
  22. usersAddValidator,
  23. usersGetValidator,
  24. usersRegisterValidator,
  25. usersRemoveValidator,
  26. usersSortValidator,
  27. usersUpdateValidator
  28. } from '../../../middlewares'
  29. import {
  30. usersAskResetPasswordValidator,
  31. usersAskSendVerifyEmailValidator,
  32. usersBlockingValidator,
  33. usersResetPasswordValidator,
  34. usersVerifyEmailValidator,
  35. ensureCanManageUser
  36. } from '../../../middlewares/validators'
  37. import { UserModel } from '../../../models/account/user'
  38. import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
  39. import { meRouter } from './me'
  40. import { deleteUserToken } from '../../../lib/oauth-model'
  41. import { myBlocklistRouter } from './my-blocklist'
  42. import { myVideoPlaylistsRouter } from './my-video-playlists'
  43. import { myVideosHistoryRouter } from './my-history'
  44. import { myNotificationsRouter } from './my-notifications'
  45. import { Notifier } from '../../../lib/notifier'
  46. import { mySubscriptionsRouter } from './my-subscriptions'
  47. import { CONFIG } from '../../../initializers/config'
  48. import { sequelizeTypescript } from '../../../initializers/database'
  49. import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
  50. import { UserRegister } from '../../../../shared/models/users/user-register.model'
  51. import { MUser, MUserAccountDefault } from '@server/typings/models'
  52. import { Hooks } from '@server/lib/plugins/hooks'
  53. const auditLogger = auditLoggerFactory('users')
  54. // FIXME: https://github.com/nfriedly/express-rate-limit/issues/138
  55. // @ts-ignore
  56. const loginRateLimiter = RateLimit({
  57. windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
  58. max: CONFIG.RATES_LIMIT.LOGIN.MAX
  59. })
  60. // @ts-ignore
  61. const signupRateLimiter = RateLimit({
  62. windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS,
  63. max: CONFIG.RATES_LIMIT.SIGNUP.MAX,
  64. skipFailedRequests: true
  65. })
  66. // @ts-ignore
  67. const askSendEmailLimiter = new RateLimit({
  68. windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
  69. max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX
  70. })
  71. const usersRouter = express.Router()
  72. usersRouter.use('/', myNotificationsRouter)
  73. usersRouter.use('/', mySubscriptionsRouter)
  74. usersRouter.use('/', myBlocklistRouter)
  75. usersRouter.use('/', myVideosHistoryRouter)
  76. usersRouter.use('/', myVideoPlaylistsRouter)
  77. usersRouter.use('/', meRouter)
  78. usersRouter.get('/autocomplete',
  79. userAutocompleteValidator,
  80. asyncMiddleware(autocompleteUsers)
  81. )
  82. usersRouter.get('/',
  83. authenticate,
  84. ensureUserHasRight(UserRight.MANAGE_USERS),
  85. paginationValidator,
  86. usersSortValidator,
  87. setDefaultSort,
  88. setDefaultPagination,
  89. asyncMiddleware(listUsers)
  90. )
  91. usersRouter.post('/:id/block',
  92. authenticate,
  93. ensureUserHasRight(UserRight.MANAGE_USERS),
  94. asyncMiddleware(usersBlockingValidator),
  95. ensureCanManageUser,
  96. asyncMiddleware(blockUser)
  97. )
  98. usersRouter.post('/:id/unblock',
  99. authenticate,
  100. ensureUserHasRight(UserRight.MANAGE_USERS),
  101. asyncMiddleware(usersBlockingValidator),
  102. ensureCanManageUser,
  103. asyncMiddleware(unblockUser)
  104. )
  105. usersRouter.get('/:id',
  106. authenticate,
  107. ensureUserHasRight(UserRight.MANAGE_USERS),
  108. asyncMiddleware(usersGetValidator),
  109. getUser
  110. )
  111. usersRouter.post('/',
  112. authenticate,
  113. ensureUserHasRight(UserRight.MANAGE_USERS),
  114. asyncMiddleware(usersAddValidator),
  115. asyncRetryTransactionMiddleware(createUser)
  116. )
  117. usersRouter.post('/register',
  118. signupRateLimiter,
  119. asyncMiddleware(ensureUserRegistrationAllowed),
  120. ensureUserRegistrationAllowedForIP,
  121. asyncMiddleware(usersRegisterValidator),
  122. asyncRetryTransactionMiddleware(registerUser)
  123. )
  124. usersRouter.put('/:id',
  125. authenticate,
  126. ensureUserHasRight(UserRight.MANAGE_USERS),
  127. asyncMiddleware(usersUpdateValidator),
  128. ensureCanManageUser,
  129. asyncMiddleware(updateUser)
  130. )
  131. usersRouter.delete('/:id',
  132. authenticate,
  133. ensureUserHasRight(UserRight.MANAGE_USERS),
  134. asyncMiddleware(usersRemoveValidator),
  135. ensureCanManageUser,
  136. asyncMiddleware(removeUser)
  137. )
  138. usersRouter.post('/ask-reset-password',
  139. asyncMiddleware(usersAskResetPasswordValidator),
  140. asyncMiddleware(askResetUserPassword)
  141. )
  142. usersRouter.post('/:id/reset-password',
  143. asyncMiddleware(usersResetPasswordValidator),
  144. asyncMiddleware(resetUserPassword)
  145. )
  146. usersRouter.post('/ask-send-verify-email',
  147. askSendEmailLimiter,
  148. asyncMiddleware(usersAskSendVerifyEmailValidator),
  149. asyncMiddleware(reSendVerifyUserEmail)
  150. )
  151. usersRouter.post('/:id/verify-email',
  152. asyncMiddleware(usersVerifyEmailValidator),
  153. asyncMiddleware(verifyUserEmail)
  154. )
  155. usersRouter.post('/token',
  156. loginRateLimiter,
  157. token,
  158. tokenSuccess
  159. )
  160. // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route
  161. // ---------------------------------------------------------------------------
  162. export {
  163. usersRouter
  164. }
  165. // ---------------------------------------------------------------------------
  166. async function createUser (req: express.Request, res: express.Response) {
  167. const body: UserCreate = req.body
  168. const userToCreate = new UserModel({
  169. username: body.username,
  170. password: body.password,
  171. email: body.email,
  172. nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  173. autoPlayVideo: true,
  174. role: body.role,
  175. videoQuota: body.videoQuota,
  176. videoQuotaDaily: body.videoQuotaDaily,
  177. adminFlags: body.adminFlags || UserAdminFlag.NONE
  178. }) as MUser
  179. const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate })
  180. auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
  181. logger.info('User %s with its channel and account created.', body.username)
  182. Hooks.runAction('action:api.user.created', { body, user, account, videoChannel })
  183. return res.json({
  184. user: {
  185. id: user.id,
  186. account: {
  187. id: account.id
  188. }
  189. }
  190. }).end()
  191. }
  192. async function registerUser (req: express.Request, res: express.Response) {
  193. const body: UserRegister = 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: UserRole.USER,
  201. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  202. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY,
  203. emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
  204. })
  205. const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
  206. userToCreate: userToCreate,
  207. userDisplayName: body.displayName || undefined,
  208. channelNames: body.channel
  209. })
  210. auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
  211. logger.info('User %s with its channel and account registered.', body.username)
  212. if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
  213. await sendVerifyUserEmail(user)
  214. }
  215. Notifier.Instance.notifyOnNewUserRegistration(user)
  216. Hooks.runAction('action:api.user.registered', { body, user, account, videoChannel })
  217. return res.type('json').status(204).end()
  218. }
  219. async function unblockUser (req: express.Request, res: express.Response) {
  220. const user = res.locals.user
  221. await changeUserBlock(res, user, false)
  222. Hooks.runAction('action:api.user.unblocked', { user })
  223. return res.status(204).end()
  224. }
  225. async function blockUser (req: express.Request, res: express.Response) {
  226. const user = res.locals.user
  227. const reason = req.body.reason
  228. await changeUserBlock(res, user, true, reason)
  229. Hooks.runAction('action:api.user.blocked', { user })
  230. return res.status(204).end()
  231. }
  232. function getUser (req: express.Request, res: express.Response) {
  233. return res.json(res.locals.user.toFormattedJSON({ withAdminFlags: true }))
  234. }
  235. async function autocompleteUsers (req: express.Request, res: express.Response) {
  236. const resultList = await UserModel.autoComplete(req.query.search as string)
  237. return res.json(resultList)
  238. }
  239. async function listUsers (req: express.Request, res: express.Response) {
  240. const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search)
  241. return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true }))
  242. }
  243. async function removeUser (req: express.Request, res: express.Response) {
  244. const user = res.locals.user
  245. await user.destroy()
  246. auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
  247. Hooks.runAction('action:api.user.deleted', { user })
  248. return res.sendStatus(204)
  249. }
  250. async function updateUser (req: express.Request, res: express.Response) {
  251. const body: UserUpdate = req.body
  252. const userToUpdate = res.locals.user
  253. const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
  254. const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
  255. if (body.password !== undefined) userToUpdate.password = body.password
  256. if (body.email !== undefined) userToUpdate.email = body.email
  257. if (body.emailVerified !== undefined) userToUpdate.emailVerified = body.emailVerified
  258. if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
  259. if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily
  260. if (body.role !== undefined) userToUpdate.role = body.role
  261. if (body.adminFlags !== undefined) userToUpdate.adminFlags = body.adminFlags
  262. const user = await userToUpdate.save()
  263. // Destroy user token to refresh rights
  264. if (roleChanged || body.password !== undefined) await deleteUserToken(userToUpdate.id)
  265. auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
  266. Hooks.runAction('action:api.user.updated', { user })
  267. // Don't need to send this update to followers, these attributes are not federated
  268. return res.sendStatus(204)
  269. }
  270. async function askResetUserPassword (req: express.Request, res: express.Response) {
  271. const user = res.locals.user
  272. const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id)
  273. const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
  274. await Emailer.Instance.addPasswordResetEmailJob(user.email, url)
  275. return res.status(204).end()
  276. }
  277. async function resetUserPassword (req: express.Request, res: express.Response) {
  278. const user = res.locals.user
  279. user.password = req.body.password
  280. await user.save()
  281. return res.status(204).end()
  282. }
  283. async function reSendVerifyUserEmail (req: express.Request, res: express.Response) {
  284. const user = res.locals.user
  285. await sendVerifyUserEmail(user)
  286. return res.status(204).end()
  287. }
  288. async function verifyUserEmail (req: express.Request, res: express.Response) {
  289. const user = res.locals.user
  290. user.emailVerified = true
  291. if (req.body.isPendingEmail === true) {
  292. user.email = user.pendingEmail
  293. user.pendingEmail = null
  294. }
  295. await user.save()
  296. return res.status(204).end()
  297. }
  298. function tokenSuccess (req: express.Request) {
  299. const username = req.body.username
  300. Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip })
  301. }
  302. async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) {
  303. const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
  304. user.blocked = block
  305. user.blockedReason = reason || null
  306. await sequelizeTypescript.transaction(async t => {
  307. await deleteUserToken(user.id, t)
  308. await user.save({ transaction: t })
  309. })
  310. await Emailer.Instance.addUserBlockJob(user, block, reason)
  311. auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
  312. }