Add action hooks to user routes
authorChocobozzz <me@florianbigard.com>
Fri, 6 Dec 2019 14:59:12 +0000 (15:59 +0100)
committerChocobozzz <me@florianbigard.com>
Fri, 6 Dec 2019 14:59:12 +0000 (15:59 +0100)
server/controllers/api/users/index.ts
server/middlewares/oauth.ts
server/tests/fixtures/peertube-plugin-test/main.js
server/tests/plugins/action-hooks.ts
shared/extra-utils/users/users.ts
shared/models/plugins/server-hook.model.ts

index 27351c1a954b8d68ed648fac005e5462bacc0962..b960e80c18da3a309847c2f23fe9e568ac6e4f32 100644 (file)
@@ -49,6 +49,7 @@ import { sequelizeTypescript } from '../../../initializers/database'
 import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
 import { UserRegister } from '../../../../shared/models/users/user-register.model'
 import { MUser, MUserAccountDefault } from '@server/typings/models'
+import { Hooks } from '@server/lib/plugins/hooks'
 
 const auditLogger = auditLoggerFactory('users')
 
@@ -172,7 +173,7 @@ usersRouter.post('/:id/verify-email',
 usersRouter.post('/token',
   loginRateLimiter,
   token,
-  success
+  tokenSuccess
 )
 // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route
 
@@ -198,11 +199,13 @@ async function createUser (req: express.Request, res: express.Response) {
     adminFlags: body.adminFlags || UserAdminFlag.NONE
   }) as MUser
 
-  const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate })
+  const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate })
 
   auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
   logger.info('User %s with its channel and account created.', body.username)
 
+  Hooks.runAction('action:api.user.created', { body, user, account, videoChannel })
+
   return res.json({
     user: {
       id: user.id,
@@ -228,7 +231,7 @@ async function registerUser (req: express.Request, res: express.Response) {
     emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
   })
 
-  const { user } = await createUserAccountAndChannelAndPlaylist({
+  const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
     userToCreate: userToCreate,
     userDisplayName: body.displayName || undefined,
     channelNames: body.channel
@@ -243,6 +246,8 @@ async function registerUser (req: express.Request, res: express.Response) {
 
   Notifier.Instance.notifyOnNewUserRegistration(user)
 
+  Hooks.runAction('action:api.user.registered', { body, user, account, videoChannel })
+
   return res.type('json').status(204).end()
 }
 
@@ -251,6 +256,8 @@ async function unblockUser (req: express.Request, res: express.Response) {
 
   await changeUserBlock(res, user, false)
 
+  Hooks.runAction('action:api.user.unblocked', { user })
+
   return res.status(204).end()
 }
 
@@ -260,6 +267,8 @@ async function blockUser (req: express.Request, res: express.Response) {
 
   await changeUserBlock(res, user, true, reason)
 
+  Hooks.runAction('action:api.user.blocked', { user })
+
   return res.status(204).end()
 }
 
@@ -286,6 +295,8 @@ async function removeUser (req: express.Request, res: express.Response) {
 
   auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
 
+  Hooks.runAction('action:api.user.deleted', { user })
+
   return res.sendStatus(204)
 }
 
@@ -310,6 +321,8 @@ async function updateUser (req: express.Request, res: express.Response) {
 
   auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
 
+  Hooks.runAction('action:api.user.updated', { user })
+
   // Don't need to send this update to followers, these attributes are not federated
 
   return res.sendStatus(204)
@@ -356,8 +369,10 @@ async function verifyUserEmail (req: express.Request, res: express.Response) {
   return res.status(204).end()
 }
 
-function success (req: express.Request, res: express.Response) {
-  res.end()
+function tokenSuccess (req: express.Request) {
+  const username = req.body.username
+
+  Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip })
 }
 
 async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) {
index bb90dac470db51df2e5b7f7fc4a7bb6b9a527bcb..749f5cccd9b3f56e59f75ca36ca175233914fe2f 100644 (file)
@@ -9,6 +9,7 @@ const oAuthServer = new OAuthServer({
   useErrorHandler: true,
   accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN,
   refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN,
+  continueMiddleware: true,
   model: require('../lib/oauth-model')
 })
 
index 055884d29fbb0f312b83051951b1386904e00bc3..69796ab07fd10af113ddf3684e6c5af2f2d14229 100644 (file)
@@ -9,7 +9,15 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
 
     'action:api.video-thread.created',
     'action:api.video-comment-reply.created',
-    'action:api.video-comment.deleted'
+    'action:api.video-comment.deleted',
+
+    'action:api.user.blocked',
+    'action:api.user.unblocked',
+    'action:api.user.registered',
+    'action:api.user.created',
+    'action:api.user.deleted',
+    'action:api.user.updated',
+    'action:api.user.oauth2-got-token'
   ]
 
   for (const h of actionHooks) {
index e28732caca079ec3367edeb6448a0695f5d6c229..510ec315197c9ad28b855d1c8f5b47eb181a95d7 100644 (file)
@@ -5,19 +5,26 @@ import 'mocha'
 import {
   cleanupTests,
   flushAndRunMultipleServers,
-  flushAndRunServer, killallServers, reRunServer,
+  killallServers,
+  reRunServer,
   ServerInfo,
   waitUntilLog
 } from '../../../shared/extra-utils/server/servers'
 import {
   addVideoCommentReply,
-  addVideoCommentThread, deleteVideoComment,
+  addVideoCommentThread,
+  blockUser,
+  createUser,
+  deleteVideoComment,
   getPluginTestPath,
-  installPlugin, removeVideo,
+  installPlugin, login,
+  registerUser, removeUser,
   setAccessTokensToServers,
+  unblockUser, updateUser,
   updateVideo,
   uploadVideo,
-  viewVideo
+  viewVideo,
+  userLogin
 } from '../../../shared/extra-utils'
 
 const expect = chai.expect
@@ -48,52 +55,104 @@ describe('Test plugin action hooks', function () {
     await reRunServer(servers[0])
   })
 
-  it('Should run action:application.listening', async function () {
-    await checkHook('action:application.listening')
+  describe('Application hooks', function () {
+    it('Should run action:application.listening', async function () {
+      await checkHook('action:application.listening')
+    })
   })
 
-  it('Should run action:api.video.uploaded', async function () {
-    const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' })
-    videoUUID = res.body.video.uuid
+  describe('Videos hooks', function () {
+    it('Should run action:api.video.uploaded', async function () {
+      const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' })
+      videoUUID = res.body.video.uuid
 
-    await checkHook('action:api.video.uploaded')
-  })
+      await checkHook('action:api.video.uploaded')
+    })
 
-  it('Should run action:api.video.updated', async function () {
-    await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video updated' })
+    it('Should run action:api.video.updated', async function () {
+      await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video updated' })
 
-    await checkHook('action:api.video.updated')
-  })
+      await checkHook('action:api.video.updated')
+    })
 
-  it('Should run action:api.video.viewed', async function () {
-    await viewVideo(servers[0].url, videoUUID)
+    it('Should run action:api.video.viewed', async function () {
+      await viewVideo(servers[0].url, videoUUID)
 
-    await checkHook('action:api.video.viewed')
+      await checkHook('action:api.video.viewed')
+    })
   })
 
-  it('Should run action:api.video-thread.created', async function () {
-    const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread')
-    threadId = res.body.comment.id
+  describe('Comments hooks', function () {
+    it('Should run action:api.video-thread.created', async function () {
+      const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread')
+      threadId = res.body.comment.id
 
-    await checkHook('action:api.video-thread.created')
-  })
+      await checkHook('action:api.video-thread.created')
+    })
 
-  it('Should run action:api.video-comment-reply.created', async function () {
-    await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID, threadId, 'reply')
+    it('Should run action:api.video-comment-reply.created', async function () {
+      await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID, threadId, 'reply')
 
-    await checkHook('action:api.video-comment-reply.created')
-  })
+      await checkHook('action:api.video-comment-reply.created')
+    })
 
-  it('Should run action:api.video-comment.deleted', async function () {
-    await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId)
+    it('Should run action:api.video-comment.deleted', async function () {
+      await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId)
 
-    await checkHook('action:api.video-comment.deleted')
+      await checkHook('action:api.video-comment.deleted')
+    })
   })
 
-  it('Should run action:api.video.deleted', async function () {
-    await removeVideo(servers[0].url, servers[0].accessToken, videoUUID)
+  describe('Users hooks', function () {
+    let userId: number
+
+    it('Should run action:api.user.registered', async function () {
+      await registerUser(servers[0].url, 'registered_user', 'super_password')
 
-    await checkHook('action:api.video.deleted')
+      await checkHook('action:api.user.registered')
+    })
+
+    it('Should run action:api.user.created', async function () {
+      const res = await createUser({
+        url: servers[0].url,
+        accessToken: servers[0].accessToken,
+        username: 'created_user',
+        password: 'super_password'
+      })
+      userId = res.body.user.id
+
+      await checkHook('action:api.user.created')
+    })
+
+    it('Should run action:api.user.oauth2-got-token', async function () {
+      await userLogin(servers[0], { username: 'created_user', password: 'super_password' })
+
+      await checkHook('action:api.user.oauth2-got-token')
+    })
+
+    it('Should run action:api.user.blocked', async function () {
+      await blockUser(servers[0].url, userId, servers[0].accessToken)
+
+      await checkHook('action:api.user.blocked')
+    })
+
+    it('Should run action:api.user.unblocked', async function () {
+      await unblockUser(servers[0].url, userId, servers[0].accessToken)
+
+      await checkHook('action:api.user.unblocked')
+    })
+
+    it('Should run action:api.user.updated', async function () {
+      await updateUser({ url: servers[0].url, accessToken: servers[0].accessToken, userId, videoQuota: 50 })
+
+      await checkHook('action:api.user.updated')
+    })
+
+    it('Should run action:api.user.deleted', async function () {
+      await removeUser(servers[0].url, userId, servers[0].accessToken)
+
+      await checkHook('action:api.user.deleted')
+    })
   })
 
   after(async function () {
index 9959fd0745b0625b4b72ab0cd9a075a625539f33..2fe0e55c219e32e62e0dde89f84ab1f0f1a5f843 100644 (file)
@@ -8,7 +8,8 @@ import { userLogin } from './login'
 import { UserUpdateMe } from '../../models/users'
 import { omit } from 'lodash'
 
-type CreateUserArgs = { url: string,
+type CreateUserArgs = {
+  url: string,
   accessToken: string,
   username: string,
   password: string,
index 41ee28097f7dbd3f64bf0e3a286e31b1fbf384d7..80ecd9e24d609a5f78fc7e83fd70d9d817d8ac0e 100644 (file)
@@ -55,7 +55,23 @@ export const serverActionHookObject = {
   // Fired when a reply to a thread is created
   'action:api.video-comment-reply.created': true,
   // Fired when a comment (thread or reply) is deleted
-  'action:api.video-comment.deleted': true
+  'action:api.video-comment.deleted': true,
+
+  // Fired when a user is blocked (banned)
+  'action:api.user.blocked': true,
+  // Fired when a user is unblocked (unbanned)
+  'action:api.user.unblocked': true,
+  // Fired when a user registered on the instance
+  'action:api.user.registered': true,
+  // Fired when an admin/moderator created a user
+  'action:api.user.created': true,
+  // Fired when a user is removed by an admin/moderator
+  'action:api.user.deleted': true,
+  // Fired when a user is updated by an admin/moderator
+  'action:api.user.updated': true,
+
+   // Fired when a user got a new oauth2 token
+  'action:api.user.oauth2-got-token': true
 }
 
 export type ServerActionHookName = keyof typeof serverActionHookObject