diff options
-rw-r--r-- | server/controllers/api/users/index.ts | 25 | ||||
-rw-r--r-- | server/middlewares/oauth.ts | 1 | ||||
-rw-r--r-- | server/tests/fixtures/peertube-plugin-test/main.js | 10 | ||||
-rw-r--r-- | server/tests/plugins/action-hooks.ts | 125 | ||||
-rw-r--r-- | shared/extra-utils/users/users.ts | 3 | ||||
-rw-r--r-- | shared/models/plugins/server-hook.model.ts | 18 |
6 files changed, 141 insertions, 41 deletions
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 27351c1a9..b960e80c1 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -49,6 +49,7 @@ import { sequelizeTypescript } from '../../../initializers/database' | |||
49 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | 49 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' |
50 | import { UserRegister } from '../../../../shared/models/users/user-register.model' | 50 | import { UserRegister } from '../../../../shared/models/users/user-register.model' |
51 | import { MUser, MUserAccountDefault } from '@server/typings/models' | 51 | import { MUser, MUserAccountDefault } from '@server/typings/models' |
52 | import { Hooks } from '@server/lib/plugins/hooks' | ||
52 | 53 | ||
53 | const auditLogger = auditLoggerFactory('users') | 54 | const auditLogger = auditLoggerFactory('users') |
54 | 55 | ||
@@ -172,7 +173,7 @@ usersRouter.post('/:id/verify-email', | |||
172 | usersRouter.post('/token', | 173 | usersRouter.post('/token', |
173 | loginRateLimiter, | 174 | loginRateLimiter, |
174 | token, | 175 | token, |
175 | success | 176 | tokenSuccess |
176 | ) | 177 | ) |
177 | // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route | 178 | // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route |
178 | 179 | ||
@@ -198,11 +199,13 @@ async function createUser (req: express.Request, res: express.Response) { | |||
198 | adminFlags: body.adminFlags || UserAdminFlag.NONE | 199 | adminFlags: body.adminFlags || UserAdminFlag.NONE |
199 | }) as MUser | 200 | }) as MUser |
200 | 201 | ||
201 | const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) | 202 | const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) |
202 | 203 | ||
203 | auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) | 204 | auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) |
204 | logger.info('User %s with its channel and account created.', body.username) | 205 | logger.info('User %s with its channel and account created.', body.username) |
205 | 206 | ||
207 | Hooks.runAction('action:api.user.created', { body, user, account, videoChannel }) | ||
208 | |||
206 | return res.json({ | 209 | return res.json({ |
207 | user: { | 210 | user: { |
208 | id: user.id, | 211 | id: user.id, |
@@ -228,7 +231,7 @@ async function registerUser (req: express.Request, res: express.Response) { | |||
228 | emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null | 231 | emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null |
229 | }) | 232 | }) |
230 | 233 | ||
231 | const { user } = await createUserAccountAndChannelAndPlaylist({ | 234 | const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({ |
232 | userToCreate: userToCreate, | 235 | userToCreate: userToCreate, |
233 | userDisplayName: body.displayName || undefined, | 236 | userDisplayName: body.displayName || undefined, |
234 | channelNames: body.channel | 237 | channelNames: body.channel |
@@ -243,6 +246,8 @@ async function registerUser (req: express.Request, res: express.Response) { | |||
243 | 246 | ||
244 | Notifier.Instance.notifyOnNewUserRegistration(user) | 247 | Notifier.Instance.notifyOnNewUserRegistration(user) |
245 | 248 | ||
249 | Hooks.runAction('action:api.user.registered', { body, user, account, videoChannel }) | ||
250 | |||
246 | return res.type('json').status(204).end() | 251 | return res.type('json').status(204).end() |
247 | } | 252 | } |
248 | 253 | ||
@@ -251,6 +256,8 @@ async function unblockUser (req: express.Request, res: express.Response) { | |||
251 | 256 | ||
252 | await changeUserBlock(res, user, false) | 257 | await changeUserBlock(res, user, false) |
253 | 258 | ||
259 | Hooks.runAction('action:api.user.unblocked', { user }) | ||
260 | |||
254 | return res.status(204).end() | 261 | return res.status(204).end() |
255 | } | 262 | } |
256 | 263 | ||
@@ -260,6 +267,8 @@ async function blockUser (req: express.Request, res: express.Response) { | |||
260 | 267 | ||
261 | await changeUserBlock(res, user, true, reason) | 268 | await changeUserBlock(res, user, true, reason) |
262 | 269 | ||
270 | Hooks.runAction('action:api.user.blocked', { user }) | ||
271 | |||
263 | return res.status(204).end() | 272 | return res.status(204).end() |
264 | } | 273 | } |
265 | 274 | ||
@@ -286,6 +295,8 @@ async function removeUser (req: express.Request, res: express.Response) { | |||
286 | 295 | ||
287 | auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) | 296 | auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) |
288 | 297 | ||
298 | Hooks.runAction('action:api.user.deleted', { user }) | ||
299 | |||
289 | return res.sendStatus(204) | 300 | return res.sendStatus(204) |
290 | } | 301 | } |
291 | 302 | ||
@@ -310,6 +321,8 @@ async function updateUser (req: express.Request, res: express.Response) { | |||
310 | 321 | ||
311 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) | 322 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) |
312 | 323 | ||
324 | Hooks.runAction('action:api.user.updated', { user }) | ||
325 | |||
313 | // Don't need to send this update to followers, these attributes are not federated | 326 | // Don't need to send this update to followers, these attributes are not federated |
314 | 327 | ||
315 | return res.sendStatus(204) | 328 | return res.sendStatus(204) |
@@ -356,8 +369,10 @@ async function verifyUserEmail (req: express.Request, res: express.Response) { | |||
356 | return res.status(204).end() | 369 | return res.status(204).end() |
357 | } | 370 | } |
358 | 371 | ||
359 | function success (req: express.Request, res: express.Response) { | 372 | function tokenSuccess (req: express.Request) { |
360 | res.end() | 373 | const username = req.body.username |
374 | |||
375 | Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip }) | ||
361 | } | 376 | } |
362 | 377 | ||
363 | async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { | 378 | async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { |
diff --git a/server/middlewares/oauth.ts b/server/middlewares/oauth.ts index bb90dac47..749f5cccd 100644 --- a/server/middlewares/oauth.ts +++ b/server/middlewares/oauth.ts | |||
@@ -9,6 +9,7 @@ const oAuthServer = new OAuthServer({ | |||
9 | useErrorHandler: true, | 9 | useErrorHandler: true, |
10 | accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN, | 10 | accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN, |
11 | refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN, | 11 | refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN, |
12 | continueMiddleware: true, | ||
12 | model: require('../lib/oauth-model') | 13 | model: require('../lib/oauth-model') |
13 | }) | 14 | }) |
14 | 15 | ||
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index 055884d29..69796ab07 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js | |||
@@ -9,7 +9,15 @@ async function register ({ registerHook, registerSetting, settingsManager, stora | |||
9 | 9 | ||
10 | 'action:api.video-thread.created', | 10 | 'action:api.video-thread.created', |
11 | 'action:api.video-comment-reply.created', | 11 | 'action:api.video-comment-reply.created', |
12 | 'action:api.video-comment.deleted' | 12 | 'action:api.video-comment.deleted', |
13 | |||
14 | 'action:api.user.blocked', | ||
15 | 'action:api.user.unblocked', | ||
16 | 'action:api.user.registered', | ||
17 | 'action:api.user.created', | ||
18 | 'action:api.user.deleted', | ||
19 | 'action:api.user.updated', | ||
20 | 'action:api.user.oauth2-got-token' | ||
13 | ] | 21 | ] |
14 | 22 | ||
15 | for (const h of actionHooks) { | 23 | for (const h of actionHooks) { |
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts index e28732cac..510ec3151 100644 --- a/server/tests/plugins/action-hooks.ts +++ b/server/tests/plugins/action-hooks.ts | |||
@@ -5,19 +5,26 @@ import 'mocha' | |||
5 | import { | 5 | import { |
6 | cleanupTests, | 6 | cleanupTests, |
7 | flushAndRunMultipleServers, | 7 | flushAndRunMultipleServers, |
8 | flushAndRunServer, killallServers, reRunServer, | 8 | killallServers, |
9 | reRunServer, | ||
9 | ServerInfo, | 10 | ServerInfo, |
10 | waitUntilLog | 11 | waitUntilLog |
11 | } from '../../../shared/extra-utils/server/servers' | 12 | } from '../../../shared/extra-utils/server/servers' |
12 | import { | 13 | import { |
13 | addVideoCommentReply, | 14 | addVideoCommentReply, |
14 | addVideoCommentThread, deleteVideoComment, | 15 | addVideoCommentThread, |
16 | blockUser, | ||
17 | createUser, | ||
18 | deleteVideoComment, | ||
15 | getPluginTestPath, | 19 | getPluginTestPath, |
16 | installPlugin, removeVideo, | 20 | installPlugin, login, |
21 | registerUser, removeUser, | ||
17 | setAccessTokensToServers, | 22 | setAccessTokensToServers, |
23 | unblockUser, updateUser, | ||
18 | updateVideo, | 24 | updateVideo, |
19 | uploadVideo, | 25 | uploadVideo, |
20 | viewVideo | 26 | viewVideo, |
27 | userLogin | ||
21 | } from '../../../shared/extra-utils' | 28 | } from '../../../shared/extra-utils' |
22 | 29 | ||
23 | const expect = chai.expect | 30 | const expect = chai.expect |
@@ -48,52 +55,104 @@ describe('Test plugin action hooks', function () { | |||
48 | await reRunServer(servers[0]) | 55 | await reRunServer(servers[0]) |
49 | }) | 56 | }) |
50 | 57 | ||
51 | it('Should run action:application.listening', async function () { | 58 | describe('Application hooks', function () { |
52 | await checkHook('action:application.listening') | 59 | it('Should run action:application.listening', async function () { |
60 | await checkHook('action:application.listening') | ||
61 | }) | ||
53 | }) | 62 | }) |
54 | 63 | ||
55 | it('Should run action:api.video.uploaded', async function () { | 64 | describe('Videos hooks', function () { |
56 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' }) | 65 | it('Should run action:api.video.uploaded', async function () { |
57 | videoUUID = res.body.video.uuid | 66 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' }) |
67 | videoUUID = res.body.video.uuid | ||
58 | 68 | ||
59 | await checkHook('action:api.video.uploaded') | 69 | await checkHook('action:api.video.uploaded') |
60 | }) | 70 | }) |
61 | 71 | ||
62 | it('Should run action:api.video.updated', async function () { | 72 | it('Should run action:api.video.updated', async function () { |
63 | await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video updated' }) | 73 | await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video updated' }) |
64 | 74 | ||
65 | await checkHook('action:api.video.updated') | 75 | await checkHook('action:api.video.updated') |
66 | }) | 76 | }) |
67 | 77 | ||
68 | it('Should run action:api.video.viewed', async function () { | 78 | it('Should run action:api.video.viewed', async function () { |
69 | await viewVideo(servers[0].url, videoUUID) | 79 | await viewVideo(servers[0].url, videoUUID) |
70 | 80 | ||
71 | await checkHook('action:api.video.viewed') | 81 | await checkHook('action:api.video.viewed') |
82 | }) | ||
72 | }) | 83 | }) |
73 | 84 | ||
74 | it('Should run action:api.video-thread.created', async function () { | 85 | describe('Comments hooks', function () { |
75 | const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread') | 86 | it('Should run action:api.video-thread.created', async function () { |
76 | threadId = res.body.comment.id | 87 | const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread') |
88 | threadId = res.body.comment.id | ||
77 | 89 | ||
78 | await checkHook('action:api.video-thread.created') | 90 | await checkHook('action:api.video-thread.created') |
79 | }) | 91 | }) |
80 | 92 | ||
81 | it('Should run action:api.video-comment-reply.created', async function () { | 93 | it('Should run action:api.video-comment-reply.created', async function () { |
82 | await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID, threadId, 'reply') | 94 | await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID, threadId, 'reply') |
83 | 95 | ||
84 | await checkHook('action:api.video-comment-reply.created') | 96 | await checkHook('action:api.video-comment-reply.created') |
85 | }) | 97 | }) |
86 | 98 | ||
87 | it('Should run action:api.video-comment.deleted', async function () { | 99 | it('Should run action:api.video-comment.deleted', async function () { |
88 | await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId) | 100 | await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId) |
89 | 101 | ||
90 | await checkHook('action:api.video-comment.deleted') | 102 | await checkHook('action:api.video-comment.deleted') |
103 | }) | ||
91 | }) | 104 | }) |
92 | 105 | ||
93 | it('Should run action:api.video.deleted', async function () { | 106 | describe('Users hooks', function () { |
94 | await removeVideo(servers[0].url, servers[0].accessToken, videoUUID) | 107 | let userId: number |
108 | |||
109 | it('Should run action:api.user.registered', async function () { | ||
110 | await registerUser(servers[0].url, 'registered_user', 'super_password') | ||
95 | 111 | ||
96 | await checkHook('action:api.video.deleted') | 112 | await checkHook('action:api.user.registered') |
113 | }) | ||
114 | |||
115 | it('Should run action:api.user.created', async function () { | ||
116 | const res = await createUser({ | ||
117 | url: servers[0].url, | ||
118 | accessToken: servers[0].accessToken, | ||
119 | username: 'created_user', | ||
120 | password: 'super_password' | ||
121 | }) | ||
122 | userId = res.body.user.id | ||
123 | |||
124 | await checkHook('action:api.user.created') | ||
125 | }) | ||
126 | |||
127 | it('Should run action:api.user.oauth2-got-token', async function () { | ||
128 | await userLogin(servers[0], { username: 'created_user', password: 'super_password' }) | ||
129 | |||
130 | await checkHook('action:api.user.oauth2-got-token') | ||
131 | }) | ||
132 | |||
133 | it('Should run action:api.user.blocked', async function () { | ||
134 | await blockUser(servers[0].url, userId, servers[0].accessToken) | ||
135 | |||
136 | await checkHook('action:api.user.blocked') | ||
137 | }) | ||
138 | |||
139 | it('Should run action:api.user.unblocked', async function () { | ||
140 | await unblockUser(servers[0].url, userId, servers[0].accessToken) | ||
141 | |||
142 | await checkHook('action:api.user.unblocked') | ||
143 | }) | ||
144 | |||
145 | it('Should run action:api.user.updated', async function () { | ||
146 | await updateUser({ url: servers[0].url, accessToken: servers[0].accessToken, userId, videoQuota: 50 }) | ||
147 | |||
148 | await checkHook('action:api.user.updated') | ||
149 | }) | ||
150 | |||
151 | it('Should run action:api.user.deleted', async function () { | ||
152 | await removeUser(servers[0].url, userId, servers[0].accessToken) | ||
153 | |||
154 | await checkHook('action:api.user.deleted') | ||
155 | }) | ||
97 | }) | 156 | }) |
98 | 157 | ||
99 | after(async function () { | 158 | after(async function () { |
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts index 9959fd074..2fe0e55c2 100644 --- a/shared/extra-utils/users/users.ts +++ b/shared/extra-utils/users/users.ts | |||
@@ -8,7 +8,8 @@ import { userLogin } from './login' | |||
8 | import { UserUpdateMe } from '../../models/users' | 8 | import { UserUpdateMe } from '../../models/users' |
9 | import { omit } from 'lodash' | 9 | import { omit } from 'lodash' |
10 | 10 | ||
11 | type CreateUserArgs = { url: string, | 11 | type CreateUserArgs = { |
12 | url: string, | ||
12 | accessToken: string, | 13 | accessToken: string, |
13 | username: string, | 14 | username: string, |
14 | password: string, | 15 | password: string, |
diff --git a/shared/models/plugins/server-hook.model.ts b/shared/models/plugins/server-hook.model.ts index 41ee28097..80ecd9e24 100644 --- a/shared/models/plugins/server-hook.model.ts +++ b/shared/models/plugins/server-hook.model.ts | |||
@@ -55,7 +55,23 @@ export const serverActionHookObject = { | |||
55 | // Fired when a reply to a thread is created | 55 | // Fired when a reply to a thread is created |
56 | 'action:api.video-comment-reply.created': true, | 56 | 'action:api.video-comment-reply.created': true, |
57 | // Fired when a comment (thread or reply) is deleted | 57 | // Fired when a comment (thread or reply) is deleted |
58 | 'action:api.video-comment.deleted': true | 58 | 'action:api.video-comment.deleted': true, |
59 | |||
60 | // Fired when a user is blocked (banned) | ||
61 | 'action:api.user.blocked': true, | ||
62 | // Fired when a user is unblocked (unbanned) | ||
63 | 'action:api.user.unblocked': true, | ||
64 | // Fired when a user registered on the instance | ||
65 | 'action:api.user.registered': true, | ||
66 | // Fired when an admin/moderator created a user | ||
67 | 'action:api.user.created': true, | ||
68 | // Fired when a user is removed by an admin/moderator | ||
69 | 'action:api.user.deleted': true, | ||
70 | // Fired when a user is updated by an admin/moderator | ||
71 | 'action:api.user.updated': true, | ||
72 | |||
73 | // Fired when a user got a new oauth2 token | ||
74 | 'action:api.user.oauth2-got-token': true | ||
59 | } | 75 | } |
60 | 76 | ||
61 | export type ServerActionHookName = keyof typeof serverActionHookObject | 77 | export type ServerActionHookName = keyof typeof serverActionHookObject |