aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/controllers/api/users/index.ts5
-rw-r--r--server/initializers/installer.ts2
-rw-r--r--server/lib/user.ts34
-rw-r--r--server/middlewares/validators/users.ts22
-rw-r--r--server/tests/api/check-params/users.ts28
-rw-r--r--server/tests/api/users/users.ts14
-rw-r--r--shared/extra-utils/users/users.ts27
-rw-r--r--shared/models/users/user-register.model.ts10
8 files changed, 120 insertions, 22 deletions
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 0aafba66e..a04f77841 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -46,6 +46,7 @@ import { mySubscriptionsRouter } from './my-subscriptions'
46import { CONFIG } from '../../../initializers/config' 46import { CONFIG } from '../../../initializers/config'
47import { sequelizeTypescript } from '../../../initializers/database' 47import { sequelizeTypescript } from '../../../initializers/database'
48import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' 48import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
49import { UserRegister } from '../../../../shared/models/users/user-register.model'
49 50
50const auditLogger = auditLoggerFactory('users') 51const auditLogger = auditLoggerFactory('users')
51 52
@@ -197,7 +198,7 @@ async function createUser (req: express.Request, res: express.Response) {
197} 198}
198 199
199async function registerUser (req: express.Request, res: express.Response) { 200async function registerUser (req: express.Request, res: express.Response) {
200 const body: UserCreate = req.body 201 const body: UserRegister = req.body
201 202
202 const userToCreate = new UserModel({ 203 const userToCreate = new UserModel({
203 username: body.username, 204 username: body.username,
@@ -211,7 +212,7 @@ async function registerUser (req: express.Request, res: express.Response) {
211 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null 212 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
212 }) 213 })
213 214
214 const { user } = await createUserAccountAndChannelAndPlaylist(userToCreate) 215 const { user } = await createUserAccountAndChannelAndPlaylist(userToCreate, body.channel)
215 216
216 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON())) 217 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
217 logger.info('User %s with its channel and account registered.', body.username) 218 logger.info('User %s with its channel and account registered.', body.username)
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index 33970f0fa..e14554ede 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -146,7 +146,7 @@ async function createOAuthAdminIfNotExist () {
146 } 146 }
147 const user = new UserModel(userData) 147 const user = new UserModel(userData)
148 148
149 await createUserAccountAndChannelAndPlaylist(user, validatePassword) 149 await createUserAccountAndChannelAndPlaylist(user, undefined, validatePassword)
150 logger.info('Username: ' + username) 150 logger.info('Username: ' + username)
151 logger.info('User password: ' + password) 151 logger.info('User password: ' + password)
152} 152}
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 7badb3e72..d9fd89e15 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -13,7 +13,8 @@ import { UserNotificationSetting, UserNotificationSettingValue } from '../../sha
13import { createWatchLaterPlaylist } from './video-playlist' 13import { createWatchLaterPlaylist } from './video-playlist'
14import { sequelizeTypescript } from '../initializers/database' 14import { sequelizeTypescript } from '../initializers/database'
15 15
16async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, validateUser = true) { 16type ChannelNames = { name: string, displayName: string }
17async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, channelNames?: ChannelNames, validateUser = true) {
17 const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { 18 const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
18 const userOptions = { 19 const userOptions = {
19 transaction: t, 20 transaction: t,
@@ -26,18 +27,8 @@ async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel,
26 const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t) 27 const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t)
27 userCreated.Account = accountCreated 28 userCreated.Account = accountCreated
28 29
29 let channelName = userCreated.username + '_channel' 30 const channelAttributes = await buildChannelAttributes(userCreated, channelNames)
30 31 const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t)
31 // Conflict, generate uuid instead
32 const actor = await ActorModel.loadLocalByName(channelName)
33 if (actor) channelName = uuidv4()
34
35 const videoChannelDisplayName = `Main ${userCreated.username} channel`
36 const videoChannelInfo = {
37 name: channelName,
38 displayName: videoChannelDisplayName
39 }
40 const videoChannel = await createVideoChannel(videoChannelInfo, accountCreated, t)
41 32
42 const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) 33 const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
43 34
@@ -116,3 +107,20 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr
116 107
117 return UserNotificationSettingModel.create(values, { transaction: t }) 108 return UserNotificationSettingModel.create(values, { transaction: t })
118} 109}
110
111async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) {
112 if (channelNames) return channelNames
113
114 let channelName = user.username + '_channel'
115
116 // Conflict, generate uuid instead
117 const actor = await ActorModel.loadLocalByName(channelName)
118 if (actor) channelName = uuidv4()
119
120 const videoChannelDisplayName = `Main ${user.username} channel`
121
122 return {
123 name: channelName,
124 displayName: videoChannelDisplayName
125 }
126}
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 6d8cd7894..b58dcc0d6 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -25,6 +25,10 @@ import { Redis } from '../../lib/redis'
25import { UserModel } from '../../models/account/user' 25import { UserModel } from '../../models/account/user'
26import { areValidationErrors } from './utils' 26import { areValidationErrors } from './utils'
27import { ActorModel } from '../../models/activitypub/actor' 27import { ActorModel } from '../../models/activitypub/actor'
28import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
29import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
30import { UserCreate } from '../../../shared/models/users'
31import { UserRegister } from '../../../shared/models/users/user-register.model'
28 32
29const usersAddValidator = [ 33const usersAddValidator = [
30 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), 34 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -49,6 +53,8 @@ const usersRegisterValidator = [
49 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'), 53 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
50 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'), 54 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
51 body('email').isEmail().withMessage('Should have a valid email'), 55 body('email').isEmail().withMessage('Should have a valid email'),
56 body('channel.name').optional().custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
57 body('channel.displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
52 58
53 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 59 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
54 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') }) 60 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
@@ -56,6 +62,22 @@ const usersRegisterValidator = [
56 if (areValidationErrors(req, res)) return 62 if (areValidationErrors(req, res)) return
57 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return 63 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
58 64
65 const body: UserRegister = req.body
66 if (body.channel) {
67 if (!body.channel.name || !body.channel.displayName) {
68 return res.status(400)
69 .send({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
70 .end()
71 }
72
73 const existing = await ActorModel.loadLocalByName(body.channel.name)
74 if (existing) {
75 return res.status(409)
76 .send({ error: `Channel with name ${body.channel.name} already exists.` })
77 .end()
78 }
79 }
80
59 return next() 81 return next()
60 } 82 }
61] 83]
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 5935104a5..d26032ea5 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -6,6 +6,7 @@ import { join } from 'path'
6import { UserRole, VideoImport, VideoImportState } from '../../../../shared' 6import { UserRole, VideoImport, VideoImportState } from '../../../../shared'
7 7
8import { 8import {
9 addVideoChannel,
9 blockUser, 10 blockUser,
10 cleanupTests, 11 cleanupTests,
11 createUser, 12 createUser,
@@ -638,7 +639,7 @@ describe('Test users API validators', function () {
638 }) 639 })
639 }) 640 })
640 641
641 describe('When register a new user', function () { 642 describe('When registering a new user', function () {
642 const registrationPath = path + '/register' 643 const registrationPath = path + '/register'
643 const baseCorrectParams = { 644 const baseCorrectParams = {
644 username: 'user3', 645 username: 'user3',
@@ -724,12 +725,35 @@ describe('Test users API validators', function () {
724 }) 725 })
725 }) 726 })
726 727
728 it('Should fail with a bad channel name', async function () {
729 const fields = immutableAssign(baseCorrectParams, { channel: { name: '[]azf', displayName: 'toto' } })
730
731 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
732 })
733
734 it('Should fail with a bad channel display name', async function () {
735 const fields = immutableAssign(baseCorrectParams, { channel: { name: 'toto', displayName: '' } })
736
737 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
738 })
739
740 it('Should fail with an existing channel', async function () {
741 const videoChannelAttributesArg = { name: 'existing_channel', displayName: 'hello', description: 'super description' }
742 await addVideoChannel(server.url, server.accessToken, videoChannelAttributesArg)
743
744 const fields = immutableAssign(baseCorrectParams, { channel: { name: 'existing_channel', displayName: 'toto' } })
745
746 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields, statusCodeExpected: 409 })
747 })
748
727 it('Should succeed with the correct params', async function () { 749 it('Should succeed with the correct params', async function () {
750 const fields = immutableAssign(baseCorrectParams, { channel: { name: 'super_channel', displayName: 'toto' } })
751
728 await makePostBodyRequest({ 752 await makePostBodyRequest({
729 url: server.url, 753 url: server.url,
730 path: registrationPath, 754 path: registrationPath,
731 token: server.accessToken, 755 token: server.accessToken,
732 fields: baseCorrectParams, 756 fields: fields,
733 statusCodeExpected: 204 757 statusCodeExpected: 204
734 }) 758 })
735 }) 759 })
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index c1a24b838..9d2ef786f 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -31,7 +31,8 @@ import {
31 updateMyUser, 31 updateMyUser,
32 updateUser, 32 updateUser,
33 uploadVideo, 33 uploadVideo,
34 userLogin 34 userLogin,
35 registerUserWithChannel, getVideoChannel
35} from '../../../../shared/extra-utils' 36} from '../../../../shared/extra-utils'
36import { follow } from '../../../../shared/extra-utils/server/follows' 37import { follow } from '../../../../shared/extra-utils/server/follows'
37import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' 38import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
@@ -617,7 +618,10 @@ describe('Test users', function () {
617 618
618 describe('Registering a new user', function () { 619 describe('Registering a new user', function () {
619 it('Should register a new user', async function () { 620 it('Should register a new user', async function () {
620 await registerUser(server.url, 'user_15', 'my super password') 621 const user = { username: 'user_15', password: 'my super password' }
622 const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' }
623
624 await registerUserWithChannel({ url: server.url, user, channel })
621 }) 625 })
622 626
623 it('Should be able to login with this registered user', async function () { 627 it('Should be able to login with this registered user', async function () {
@@ -636,6 +640,12 @@ describe('Test users', function () {
636 expect(user.videoQuota).to.equal(5 * 1024 * 1024) 640 expect(user.videoQuota).to.equal(5 * 1024 * 1024)
637 }) 641 })
638 642
643 it('Should have created the channel', async function () {
644 const res = await getVideoChannel(server.url, 'my_user_15_channel')
645
646 expect(res.body.displayName).to.equal('my channel rocks')
647 })
648
639 it('Should remove me', async function () { 649 it('Should remove me', async function () {
640 { 650 {
641 const res = await getUsersList(server.url, server.accessToken) 651 const res = await getUsersList(server.url, server.accessToken)
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts
index 2bd37b8be..c00da19e0 100644
--- a/shared/extra-utils/users/users.ts
+++ b/shared/extra-utils/users/users.ts
@@ -1,10 +1,11 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' 2import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
3 3
4import { UserRole } from '../../index' 4import { UserCreate, UserRole } from '../../index'
5import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' 5import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
6import { ServerInfo, userLogin } from '..' 6import { ServerInfo, userLogin } from '..'
7import { UserAdminFlag } from '../../models/users/user-flag.model' 7import { UserAdminFlag } from '../../models/users/user-flag.model'
8import { UserRegister } from '../../models/users/user-register.model'
8 9
9type CreateUserArgs = { url: string, 10type CreateUserArgs = { url: string,
10 accessToken: string, 11 accessToken: string,
@@ -70,6 +71,27 @@ function registerUser (url: string, username: string, password: string, specialS
70 .expect(specialStatus) 71 .expect(specialStatus)
71} 72}
72 73
74function registerUserWithChannel (options: {
75 url: string,
76 user: { username: string, password: string },
77 channel: { name: string, displayName: string }
78}) {
79 const path = '/api/v1/users/register'
80 const body: UserRegister = {
81 username: options.user.username,
82 password: options.user.password,
83 email: options.user.username + '@example.com',
84 channel: options.channel
85 }
86
87 return makePostBodyRequest({
88 url: options.url,
89 path,
90 fields: body,
91 statusCodeExpected: 204
92 })
93}
94
73function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) { 95function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) {
74 const path = '/api/v1/users/me' 96 const path = '/api/v1/users/me'
75 97
@@ -312,6 +334,7 @@ export {
312 getMyUserInformation, 334 getMyUserInformation,
313 getMyUserVideoRating, 335 getMyUserVideoRating,
314 deleteMe, 336 deleteMe,
337 registerUserWithChannel,
315 getMyUserVideoQuotaUsed, 338 getMyUserVideoQuotaUsed,
316 getUsersList, 339 getUsersList,
317 getUsersListPaginationAndSort, 340 getUsersListPaginationAndSort,
diff --git a/shared/models/users/user-register.model.ts b/shared/models/users/user-register.model.ts
new file mode 100644
index 000000000..ce5c9c3d2
--- /dev/null
+++ b/shared/models/users/user-register.model.ts
@@ -0,0 +1,10 @@
1export interface UserRegister {
2 username: string
3 password: string
4 email: string
5
6 channel?: {
7 name: string
8 displayName: string
9 }
10}