diff --git a/src/routes/profile.js b/src/routes/profile.js index 671370075..a89f2866c 100644 --- a/src/routes/profile.js +++ b/src/routes/profile.js @@ -26,10 +26,10 @@ const assert = require('assert'), async function authorize(req, res, next) { assert.strictEqual(typeof req.user, 'object'); - const [error, directoryConfig] = await safe(settings.getDirectoryConfig()); + const [error, profileConfig] = await safe(settings.getProfileConfig()); if (error) return next(BoxError.toHttpError(error)); - if (directoryConfig.lockUserProfiles) return next(new HttpError(403, 'admin has disallowed users from editing profiles')); + if (profileConfig.lockUserProfiles) return next(new HttpError(403, 'admin has disallowed users from editing profiles')); next(); } diff --git a/src/routes/test/cloudron-test.js b/src/routes/test/cloudron-test.js index 15ba31e32..ac4ad6529 100644 --- a/src/routes/test/cloudron-test.js +++ b/src/routes/test/cloudron-test.js @@ -100,13 +100,13 @@ describe('Cloudron API', function () { const USER = { email: 'setup2@account.com', password: 'test?!3434543534', - username: 'setupuser2', + username: 'presetup2', displayName: 'setup user2', }; const response = await superagent.post(`${serverUrl}/api/v1/users`) .query({ access_token: owner.token }) - .send({ email: USER.email, username: 'presetup', displayName: 'pre setup' }); + .send({ email: USER.email, username: 'presetup2', displayName: 'pre setup' }); expect(response.statusCode).to.equal(201); USER.id = response.body.id; @@ -119,24 +119,33 @@ describe('Cloudron API', function () { .send({ inviteToken: require('url').parse(response2.body.inviteLink, true).query.inviteToken, password: USER.password, - username: USER.username, + username: 'setupuser2', // this will cause a conflict. cannot change username displayName: USER.displayName }) .ok(() => true); - expect(response3.statusCode).to.equal(201); - expect(response3.body.accessToken).to.be.a('string'); + expect(response3.statusCode).to.equal(409); - const response4 = await superagent.get(`${serverUrl}/api/v1/users/${USER.id}`) + const response4 = await superagent.post(`${serverUrl}/api/v1/cloudron/setup_account`) + .send({ + inviteToken: require('url').parse(response2.body.inviteLink, true).query.inviteToken, + password: USER.password, + displayName: USER.displayName + }) + .ok(() => true); + expect(response4.statusCode).to.equal(201); + expect(response4.body.accessToken).to.be.a('string'); + + const response5 = await superagent.get(`${serverUrl}/api/v1/users/${USER.id}`) .query({ access_token: owner.token }) .ok(() => true); - expect(response4.statusCode).to.equal(200); - expect(response4.body.username).to.equal(USER.username); - expect(response4.body.displayName).to.equal(USER.displayName); - - const response5 = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) - .send({ username: USER.username, password: USER.password }); expect(response5.statusCode).to.equal(200); + expect(response5.body.username).to.equal(USER.username); + expect(response5.body.displayName).to.equal(USER.displayName); + + const response6 = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) + .send({ username: USER.username, password: USER.password }); + expect(response6.statusCode).to.equal(200); }); it('succeeds and does not overwrite pre-set username and display name if profiles are locked', async function () { @@ -147,14 +156,14 @@ describe('Cloudron API', function () { displayName: 'setup user3', }; - const response0 = await superagent.post(`${serverUrl}/api/v1/settings/directory_config`) + const response0 = await superagent.post(`${serverUrl}/api/v1/settings/profile_config`) .query({ access_token: owner.token }) .send({ lockUserProfiles: true, mandatory2FA: false }); expect(response0.statusCode).to.equal(200); const response = await superagent.post(`${serverUrl}/api/v1/users`) .query({ access_token: owner.token }) - .send({ email: USER.email, username: 'presetup', displayName: 'pre setup' }); + .send({ email: USER.email, username: 'presetup3', displayName: 'pre setup3' }); expect(response.statusCode).to.equal(201); USER.id = response.body.id; @@ -167,8 +176,8 @@ describe('Cloudron API', function () { .send({ inviteToken: require('url').parse(response2.body.inviteLink, true).query.inviteToken, password: USER.password, - username: USER.username, - displayName: USER.displayName + username: USER.username, // ignored + displayName: USER.displayName // ignored }) .ok(() => true); expect(response3.statusCode).to.equal(201); @@ -179,11 +188,11 @@ describe('Cloudron API', function () { .ok(() => true); expect(response4.statusCode).to.equal(200); - expect(response4.body.username).to.equal('presetup'); - expect(response4.body.displayName).to.equal('pre setup'); + expect(response4.body.username).to.equal('presetup3'); // what the admin provided + expect(response4.body.displayName).to.equal('pre setup3'); // what the admin provided const response5 = await superagent.post(`${serverUrl}/api/v1/cloudron/login`) - .send({ username: 'presetup', password: USER.password }); + .send({ username: 'presetup3', password: USER.password }); expect(response5.statusCode).to.equal(200); }); }); diff --git a/src/test/settings-test.js b/src/test/settings-test.js index 5bd2f23d4..b4b234a70 100644 --- a/src/test/settings-test.js +++ b/src/test/settings-test.js @@ -66,18 +66,18 @@ describe('Settings', function () { expect(enabled).to.be(true); }); - it('can get default directory config', async function () { - const directoryConfig = await settings.getDirectoryConfig(); - expect(directoryConfig.lockUserProfiles).to.be(false); - expect(directoryConfig.mandatory2FA).to.be(false); + it('can get default profile config', async function () { + const profileConfig = await settings.getProfileConfig(); + expect(profileConfig.lockUserProfiles).to.be(false); + expect(profileConfig.mandatory2FA).to.be(false); }); - it('can set default directory config', async function () { + it('can set default profile config', async function () { await tokens.add({ name: 'token1', identifier: admin.id, clientId: tokens.ID_WEBADMIN, expires: Number.MAX_SAFE_INTEGER, lastUsedTime: null, scope: 'unused' }); let result = await tokens.listByUserId(admin.id); expect(result.length).to.be(1); // just confirm the token was really added! - await settings.setDirectoryConfig({ mandatory2FA: true, lockUserProfiles: true }); + await settings.setProfileConfig({ mandatory2FA: true, lockUserProfiles: true }); result = await tokens.listByUserId(admin.id); expect(result.length).to.be(0); // should have been removed by mandatory 2fa setting change }); diff --git a/src/test/users-test.js b/src/test/users-test.js index 7283b6f5d..b1209b417 100644 --- a/src/test/users-test.js +++ b/src/test/users-test.js @@ -226,10 +226,10 @@ describe('User', function () { expect(error.message).to.equal('email already exists'); }); - it('can update the user with already existing username', async function () { + it('cannot update username', async function () { const [error] = await safe(users.update(admin, { username: user.username }, auditSource)); - expect(error.reason).to.be(BoxError.ALREADY_EXISTS); - expect(error.message).to.equal('username already exists'); + expect(error.reason).to.be(BoxError.CONFLICT); + expect(error.message).to.equal('Username cannot be changed'); }); it('can update the user', async function () { diff --git a/src/users.js b/src/users.js index 9ff01aa3a..5d991fa05 100644 --- a/src/users.js +++ b/src/users.js @@ -546,7 +546,9 @@ async function update(user, data, auditSource) { if (_.isEmpty(data)) return; if (data.username) { - if (user.username) throw new BoxError(BoxError.BAD_FIELD, 'Username cannot be changed'); + // regardless of "account setup", username cannot be changed because admin could have logged in with temp password and apps + // already know about it + if (user.username) throw new BoxError(BoxError.CONFLICT, 'Username cannot be changed'); data.username = data.username.toLowerCase(); error = validateUsername(data.username); if (error) throw error; @@ -757,7 +759,7 @@ async function getInviteLink(user, auditSource) { if (user.source) throw new BoxError(BoxError.CONFLICT, 'User is from an external directory'); if (!user.inviteToken) throw new BoxError(BoxError.BAD_STATE, 'User already used invite link'); - const directoryConfig = await settings.getDirectoryConfig(); + const directoryConfig = await settings.getProfileConfig(); let inviteLink = `${settings.dashboardOrigin()}/setupaccount.html?inviteToken=${user.inviteToken}&email=${encodeURIComponent(user.email)}`; if (user.username) inviteLink += `&username=${encodeURIComponent(user.username)}`; @@ -784,13 +786,15 @@ async function setupAccount(user, data, auditSource) { assert.strictEqual(typeof data, 'object'); assert(auditSource && typeof auditSource === 'object'); - const directoryConfig = await settings.getDirectoryConfig(); + const profileConfig = await settings.getProfileConfig(); var tmp = { inviteToken: '' }; - if (!directoryConfig.lockUserProfiles) { - tmp.username = data.username; - tmp.displayName = data.displayName; + if (profileConfig.lockUserProfiles) { + if (!user.username) throw new BoxError(BoxError.CONFLICT, 'Account cannot be setup without a username'); // error out if admin has not provided a username + } else { + if (data.username) tmp.username = data.username; + if (data.displayName) tmp.displayName = data.displayName; } await update(user, tmp, auditSource);