aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/users/index.ts7
-rw-r--r--server/middlewares/validators/users.ts72
-rw-r--r--server/tests/api/check-params/users.ts166
3 files changed, 181 insertions, 64 deletions
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 63747a0a9..ae40e86f8 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -31,7 +31,8 @@ import {
31 usersAskSendVerifyEmailValidator, 31 usersAskSendVerifyEmailValidator,
32 usersBlockingValidator, 32 usersBlockingValidator,
33 usersResetPasswordValidator, 33 usersResetPasswordValidator,
34 usersVerifyEmailValidator 34 usersVerifyEmailValidator,
35 ensureCanManageUser
35} from '../../../middlewares/validators' 36} from '../../../middlewares/validators'
36import { UserModel } from '../../../models/account/user' 37import { UserModel } from '../../../models/account/user'
37import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 38import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
@@ -97,12 +98,14 @@ usersRouter.post('/:id/block',
97 authenticate, 98 authenticate,
98 ensureUserHasRight(UserRight.MANAGE_USERS), 99 ensureUserHasRight(UserRight.MANAGE_USERS),
99 asyncMiddleware(usersBlockingValidator), 100 asyncMiddleware(usersBlockingValidator),
101 ensureCanManageUser,
100 asyncMiddleware(blockUser) 102 asyncMiddleware(blockUser)
101) 103)
102usersRouter.post('/:id/unblock', 104usersRouter.post('/:id/unblock',
103 authenticate, 105 authenticate,
104 ensureUserHasRight(UserRight.MANAGE_USERS), 106 ensureUserHasRight(UserRight.MANAGE_USERS),
105 asyncMiddleware(usersBlockingValidator), 107 asyncMiddleware(usersBlockingValidator),
108 ensureCanManageUser,
106 asyncMiddleware(unblockUser) 109 asyncMiddleware(unblockUser)
107) 110)
108 111
@@ -132,6 +135,7 @@ usersRouter.put('/:id',
132 authenticate, 135 authenticate,
133 ensureUserHasRight(UserRight.MANAGE_USERS), 136 ensureUserHasRight(UserRight.MANAGE_USERS),
134 asyncMiddleware(usersUpdateValidator), 137 asyncMiddleware(usersUpdateValidator),
138 ensureCanManageUser,
135 asyncMiddleware(updateUser) 139 asyncMiddleware(updateUser)
136) 140)
137 141
@@ -139,6 +143,7 @@ usersRouter.delete('/:id',
139 authenticate, 143 authenticate,
140 ensureUserHasRight(UserRight.MANAGE_USERS), 144 ensureUserHasRight(UserRight.MANAGE_USERS),
141 asyncMiddleware(usersRemoveValidator), 145 asyncMiddleware(usersRemoveValidator),
146 ensureCanManageUser,
142 asyncMiddleware(removeUser) 147 asyncMiddleware(removeUser)
143) 148)
144 149
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index da92c715d..16d297047 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -30,6 +30,7 @@ import { UserRegister } from '../../../shared/models/users/user-register.model'
30import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 30import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
31import { isThemeRegistered } from '../../lib/plugins/theme-utils' 31import { isThemeRegistered } from '../../lib/plugins/theme-utils'
32import { doesVideoExist } from '../../helpers/middlewares' 32import { doesVideoExist } from '../../helpers/middlewares'
33import { UserRole } from '../../../shared/models/users'
33 34
34const usersAddValidator = [ 35const usersAddValidator = [
35 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), 36 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -46,6 +47,12 @@ const usersAddValidator = [
46 if (areValidationErrors(req, res)) return 47 if (areValidationErrors(req, res)) return
47 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return 48 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
48 49
50 const authUser = res.locals.oauth.token.User
51 if (authUser.role !== UserRole.ADMINISTRATOR && req.body.role !== UserRole.USER) {
52 return res.status(403)
53 .json({ error: 'You can only create users (and not administrators or moderators' })
54 }
55
49 return next() 56 return next()
50 } 57 }
51] 58]
@@ -75,21 +82,18 @@ const usersRegisterValidator = [
75 if (body.channel) { 82 if (body.channel) {
76 if (!body.channel.name || !body.channel.displayName) { 83 if (!body.channel.name || !body.channel.displayName) {
77 return res.status(400) 84 return res.status(400)
78 .send({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' }) 85 .json({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
79 .end()
80 } 86 }
81 87
82 if (body.channel.name === body.username) { 88 if (body.channel.name === body.username) {
83 return res.status(400) 89 return res.status(400)
84 .send({ error: 'Channel name cannot be the same than user username.' }) 90 .json({ error: 'Channel name cannot be the same than user username.' })
85 .end()
86 } 91 }
87 92
88 const existing = await ActorModel.loadLocalByName(body.channel.name) 93 const existing = await ActorModel.loadLocalByName(body.channel.name)
89 if (existing) { 94 if (existing) {
90 return res.status(409) 95 return res.status(409)
91 .send({ error: `Channel with name ${body.channel.name} already exists.` }) 96 .json({ error: `Channel with name ${body.channel.name} already exists.` })
92 .end()
93 } 97 }
94 } 98 }
95 99
@@ -109,8 +113,7 @@ const usersRemoveValidator = [
109 const user = res.locals.user 113 const user = res.locals.user
110 if (user.username === 'root') { 114 if (user.username === 'root') {
111 return res.status(400) 115 return res.status(400)
112 .send({ error: 'Cannot remove the root user' }) 116 .json({ error: 'Cannot remove the root user' })
113 .end()
114 } 117 }
115 118
116 return next() 119 return next()
@@ -130,8 +133,7 @@ const usersBlockingValidator = [
130 const user = res.locals.user 133 const user = res.locals.user
131 if (user.username === 'root') { 134 if (user.username === 'root') {
132 return res.status(400) 135 return res.status(400)
133 .send({ error: 'Cannot block the root user' }) 136 .json({ error: 'Cannot block the root user' })
134 .end()
135 } 137 }
136 138
137 return next() 139 return next()
@@ -143,7 +145,7 @@ const deleteMeValidator = [
143 const user = res.locals.oauth.token.User 145 const user = res.locals.oauth.token.User
144 if (user.username === 'root') { 146 if (user.username === 'root') {
145 return res.status(400) 147 return res.status(400)
146 .send({ error: 'You cannot delete your root account.' }) 148 .json({ error: 'You cannot delete your root account.' })
147 .end() 149 .end()
148 } 150 }
149 151
@@ -170,8 +172,7 @@ const usersUpdateValidator = [
170 const user = res.locals.user 172 const user = res.locals.user
171 if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) { 173 if (user.username === 'root' && req.body.role !== undefined && user.role !== req.body.role) {
172 return res.status(400) 174 return res.status(400)
173 .send({ error: 'Cannot change root role.' }) 175 .json({ error: 'Cannot change root role.' })
174 .end()
175 } 176 }
176 177
177 return next() 178 return next()
@@ -216,15 +217,14 @@ const usersUpdateMeValidator = [
216 if (req.body.password || req.body.email) { 217 if (req.body.password || req.body.email) {
217 if (!req.body.currentPassword) { 218 if (!req.body.currentPassword) {
218 return res.status(400) 219 return res.status(400)
219 .send({ error: 'currentPassword parameter is missing.' }) 220 .json({ error: 'currentPassword parameter is missing.' })
220 .end() 221 .end()
221 } 222 }
222 223
223 const user = res.locals.oauth.token.User 224 const user = res.locals.oauth.token.User
224 if (await user.isPasswordMatch(req.body.currentPassword) !== true) { 225 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
225 return res.status(401) 226 return res.status(401)
226 .send({ error: 'currentPassword is invalid.' }) 227 .json({ error: 'currentPassword is invalid.' })
227 .end()
228 } 228 }
229 } 229 }
230 230
@@ -265,8 +265,7 @@ const ensureUserRegistrationAllowed = [
265 const allowed = await isSignupAllowed() 265 const allowed = await isSignupAllowed()
266 if (allowed === false) { 266 if (allowed === false) {
267 return res.status(403) 267 return res.status(403)
268 .send({ error: 'User registration is not enabled or user limit is reached.' }) 268 .json({ error: 'User registration is not enabled or user limit is reached.' })
269 .end()
270 } 269 }
271 270
272 return next() 271 return next()
@@ -279,8 +278,7 @@ const ensureUserRegistrationAllowedForIP = [
279 278
280 if (allowed === false) { 279 if (allowed === false) {
281 return res.status(403) 280 return res.status(403)
282 .send({ error: 'You are not on a network authorized for registration.' }) 281 .json({ error: 'You are not on a network authorized for registration.' })
283 .end()
284 } 282 }
285 283
286 return next() 284 return next()
@@ -323,8 +321,7 @@ const usersResetPasswordValidator = [
323 if (redisVerificationString !== req.body.verificationString) { 321 if (redisVerificationString !== req.body.verificationString) {
324 return res 322 return res
325 .status(403) 323 .status(403)
326 .send({ error: 'Invalid verification string.' }) 324 .json({ error: 'Invalid verification string.' })
327 .end()
328 } 325 }
329 326
330 return next() 327 return next()
@@ -371,8 +368,7 @@ const usersVerifyEmailValidator = [
371 if (redisVerificationString !== req.body.verificationString) { 368 if (redisVerificationString !== req.body.verificationString) {
372 return res 369 return res
373 .status(403) 370 .status(403)
374 .send({ error: 'Invalid verification string.' }) 371 .json({ error: 'Invalid verification string.' })
375 .end()
376 } 372 }
377 373
378 return next() 374 return next()
@@ -389,14 +385,26 @@ const ensureAuthUserOwnsAccountValidator = [
389 385
390 if (res.locals.account.id !== user.Account.id) { 386 if (res.locals.account.id !== user.Account.id) {
391 return res.status(403) 387 return res.status(403)
392 .send({ error: 'Only owner can access ratings list.' }) 388 .json({ error: 'Only owner can access ratings list.' })
393 .end()
394 } 389 }
395 390
396 return next() 391 return next()
397 } 392 }
398] 393]
399 394
395const ensureCanManageUser = [
396 (req: express.Request, res: express.Response, next: express.NextFunction) => {
397 const authUser = res.locals.oauth.token.User
398 const onUser = res.locals.user
399
400 if (authUser.role === UserRole.ADMINISTRATOR) return next()
401 if (authUser.role === UserRole.MODERATOR && onUser.role === UserRole.USER) return next()
402
403 return res.status(403)
404 .json({ error: 'A moderator can only manager users.' })
405 }
406]
407
400// --------------------------------------------------------------------------- 408// ---------------------------------------------------------------------------
401 409
402export { 410export {
@@ -416,7 +424,8 @@ export {
416 usersAskSendVerifyEmailValidator, 424 usersAskSendVerifyEmailValidator,
417 usersVerifyEmailValidator, 425 usersVerifyEmailValidator,
418 userAutocompleteValidator, 426 userAutocompleteValidator,
419 ensureAuthUserOwnsAccountValidator 427 ensureAuthUserOwnsAccountValidator,
428 ensureCanManageUser
420} 429}
421 430
422// --------------------------------------------------------------------------- 431// ---------------------------------------------------------------------------
@@ -434,16 +443,14 @@ async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email:
434 443
435 if (user) { 444 if (user) {
436 res.status(409) 445 res.status(409)
437 .send({ error: 'User with this username or email already exists.' }) 446 .json({ error: 'User with this username or email already exists.' })
438 .end()
439 return false 447 return false
440 } 448 }
441 449
442 const actor = await ActorModel.loadLocalByName(username) 450 const actor = await ActorModel.loadLocalByName(username)
443 if (actor) { 451 if (actor) {
444 res.status(409) 452 res.status(409)
445 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' }) 453 .json({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
446 .end()
447 return false 454 return false
448 } 455 }
449 456
@@ -456,8 +463,7 @@ async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.R
456 if (!user) { 463 if (!user) {
457 if (abortResponse === true) { 464 if (abortResponse === true) {
458 res.status(404) 465 res.status(404)
459 .send({ error: 'User not found' }) 466 .json({ error: 'User not found' })
460 .end()
461 } 467 }
462 468
463 return false 469 return false
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 5b788e328..939b919ed 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -3,7 +3,7 @@
3import { omit } from 'lodash' 3import { omit } from 'lodash'
4import 'mocha' 4import 'mocha'
5import { join } from 'path' 5import { join } from 'path'
6import { UserRole, VideoImport, VideoImportState } from '../../../../shared' 6import { User, UserRole, VideoImport, VideoImportState } from '../../../../shared'
7 7
8import { 8import {
9 addVideoChannel, 9 addVideoChannel,
@@ -44,35 +44,79 @@ describe('Test users API validators', function () {
44 const path = '/api/v1/users/' 44 const path = '/api/v1/users/'
45 let userId: number 45 let userId: number
46 let rootId: number 46 let rootId: number
47 let moderatorId: number
47 let videoId: number 48 let videoId: number
48 let server: ServerInfo 49 let server: ServerInfo
49 let serverWithRegistrationDisabled: ServerInfo 50 let serverWithRegistrationDisabled: ServerInfo
50 let userAccessToken = '' 51 let userAccessToken = ''
52 let moderatorAccessToken = ''
51 let channelId: number 53 let channelId: number
52 const user = {
53 username: 'user1',
54 password: 'my super password'
55 }
56 54
57 // --------------------------------------------------------------- 55 // ---------------------------------------------------------------
58 56
59 before(async function () { 57 before(async function () {
60 this.timeout(30000) 58 this.timeout(30000)
61 59
62 server = await flushAndRunServer(1) 60 {
63 serverWithRegistrationDisabled = await flushAndRunServer(2) 61 const res = await Promise.all([
62 flushAndRunServer(1, { signup: { limit: 7 } }),
63 flushAndRunServer(2)
64 ])
64 65
65 await setAccessTokensToServers([ server ]) 66 server = res[0]
67 serverWithRegistrationDisabled = res[1]
66 68
67 const videoQuota = 42000000 69 await setAccessTokensToServers([ server ])
68 await createUser({ 70 }
69 url: server.url, 71
70 accessToken: server.accessToken, 72 {
71 username: user.username, 73 const user = {
72 password: user.password, 74 username: 'user1',
73 videoQuota: videoQuota 75 password: 'my super password'
74 }) 76 }
75 userAccessToken = await userLogin(server, user) 77
78 const videoQuota = 42000000
79 await createUser({
80 url: server.url,
81 accessToken: server.accessToken,
82 username: user.username,
83 password: user.password,
84 videoQuota: videoQuota
85 })
86 userAccessToken = await userLogin(server, user)
87 }
88
89 {
90 const moderator = {
91 username: 'moderator1',
92 password: 'super password'
93 }
94
95 await createUser({
96 url: server.url,
97 accessToken: server.accessToken,
98 username: moderator.username,
99 password: moderator.password,
100 role: UserRole.MODERATOR
101 })
102
103 moderatorAccessToken = await userLogin(server, moderator)
104 }
105
106 {
107 const moderator = {
108 username: 'moderator2',
109 password: 'super password'
110 }
111
112 await createUser({
113 url: server.url,
114 accessToken: server.accessToken,
115 username: moderator.username,
116 password: moderator.password,
117 role: UserRole.MODERATOR
118 })
119 }
76 120
77 { 121 {
78 const res = await getMyUserInformation(server.url, server.accessToken) 122 const res = await getMyUserInformation(server.url, server.accessToken)
@@ -83,6 +127,15 @@ describe('Test users API validators', function () {
83 const res = await uploadVideo(server.url, server.accessToken, {}) 127 const res = await uploadVideo(server.url, server.accessToken, {})
84 videoId = res.body.video.id 128 videoId = res.body.video.id
85 } 129 }
130
131 {
132 const res = await getUsersList(server.url, server.accessToken)
133 const users: User[] = res.body.data
134
135 userId = users.find(u => u.username === 'user1').id
136 rootId = users.find(u => u.username === 'root').id
137 moderatorId = users.find(u => u.username === 'moderator2').id
138 }
86 }) 139 })
87 140
88 describe('When listing users', function () { 141 describe('When listing users', function () {
@@ -251,6 +304,32 @@ describe('Test users API validators', function () {
251 }) 304 })
252 }) 305 })
253 306
307 it('Should fail to create a moderator or an admin with a moderator', async function () {
308 for (const role of [ UserRole.MODERATOR, UserRole.ADMINISTRATOR ]) {
309 const fields = immutableAssign(baseCorrectParams, { role })
310
311 await makePostBodyRequest({
312 url: server.url,
313 path,
314 token: moderatorAccessToken,
315 fields,
316 statusCodeExpected: 403
317 })
318 }
319 })
320
321 it('Should succeed to create a user with a moderator', async function () {
322 const fields = immutableAssign(baseCorrectParams, { username: 'a4656', email: 'a4656@example.com', role: UserRole.USER })
323
324 await makePostBodyRequest({
325 url: server.url,
326 path,
327 token: moderatorAccessToken,
328 fields,
329 statusCodeExpected: 200
330 })
331 })
332
254 it('Should succeed with the correct params', async function () { 333 it('Should succeed with the correct params', async function () {
255 await makePostBodyRequest({ 334 await makePostBodyRequest({
256 url: server.url, 335 url: server.url,
@@ -468,11 +547,6 @@ describe('Test users API validators', function () {
468 }) 547 })
469 548
470 describe('When getting a user', function () { 549 describe('When getting a user', function () {
471 before(async function () {
472 const res = await getUsersList(server.url, server.accessToken)
473
474 userId = res.body.data[1].id
475 })
476 550
477 it('Should fail with an non authenticated user', async function () { 551 it('Should fail with an non authenticated user', async function () {
478 await makeGetRequest({ url: server.url, path: path + userId, token: 'super token', statusCodeExpected: 401 }) 552 await makeGetRequest({ url: server.url, path: path + userId, token: 'super token', statusCodeExpected: 401 })
@@ -489,13 +563,6 @@ describe('Test users API validators', function () {
489 563
490 describe('When updating a user', function () { 564 describe('When updating a user', function () {
491 565
492 before(async function () {
493 const res = await getUsersList(server.url, server.accessToken)
494
495 userId = res.body.data[1].id
496 rootId = res.body.data[2].id
497 })
498
499 it('Should fail with an invalid email attribute', async function () { 566 it('Should fail with an invalid email attribute', async function () {
500 const fields = { 567 const fields = {
501 email: 'blabla' 568 email: 'blabla'
@@ -565,7 +632,35 @@ describe('Test users API validators', function () {
565 it('Should fail with invalid admin flags', async function () { 632 it('Should fail with invalid admin flags', async function () {
566 const fields = { adminFlags: 'toto' } 633 const fields = { adminFlags: 'toto' }
567 634
568 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 635 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
636 })
637
638 it('Should fail to update an admin with a moderator', async function () {
639 const fields = {
640 videoQuota: 42
641 }
642
643 await makePutBodyRequest({
644 url: server.url,
645 path: path + moderatorId,
646 token: moderatorAccessToken,
647 fields,
648 statusCodeExpected: 403
649 })
650 })
651
652 it('Should succeed to update a user with a moderator', async function () {
653 const fields = {
654 videoQuota: 42
655 }
656
657 await makePutBodyRequest({
658 url: server.url,
659 path: path + userId,
660 token: moderatorAccessToken,
661 fields,
662 statusCodeExpected: 204
663 })
569 }) 664 })
570 665
571 it('Should succeed with the correct params', async function () { 666 it('Should succeed with the correct params', async function () {
@@ -664,6 +759,17 @@ describe('Test users API validators', function () {
664 await blockUser(server.url, userId, userAccessToken, 403) 759 await blockUser(server.url, userId, userAccessToken, 403)
665 await unblockUser(server.url, userId, userAccessToken, 403) 760 await unblockUser(server.url, userId, userAccessToken, 403)
666 }) 761 })
762
763 it('Should fail on a moderator with a moderator', async function () {
764 await removeUser(server.url, moderatorId, moderatorAccessToken, 403)
765 await blockUser(server.url, moderatorId, moderatorAccessToken, 403)
766 await unblockUser(server.url, moderatorId, moderatorAccessToken, 403)
767 })
768
769 it('Should succeed on a user with a moderator', async function () {
770 await blockUser(server.url, userId, moderatorAccessToken)
771 await unblockUser(server.url, userId, moderatorAccessToken)
772 })
667 }) 773 })
668 774
669 describe('When deleting our account', function () { 775 describe('When deleting our account', function () {