From 18965494d9ea1fc81c6753a54b4364aa7e02e05a Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Wed, 16 Jun 2021 15:24:15 +0100 Subject: [PATCH] Server: Allow creating a new user with no password, which must be set via email confirmation --- packages/server/src/models/UserModel.ts | 4 +++- packages/server/src/routes/index/users.test.ts | 13 +++++++++++++ packages/server/src/routes/index/users.ts | 6 ++++++ packages/server/src/views/index/user.mustache | 6 +++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/server/src/models/UserModel.ts b/packages/server/src/models/UserModel.ts index 876549cece..f243816678 100644 --- a/packages/server/src/models/UserModel.ts +++ b/packages/server/src/models/UserModel.ts @@ -93,6 +93,7 @@ export default class UserModel extends BaseModel { if ('max_item_size' in object) user.max_item_size = object.max_item_size; if ('can_share' in object) user.can_share = object.can_share; if ('account_type' in object) user.account_type = object.account_type; + if ('must_set_password' in object) user.must_set_password = object.must_set_password; return user; } @@ -122,6 +123,7 @@ export default class UserModel extends BaseModel { if ('max_item_size' in resource && !user.is_admin && resource.max_item_size !== previousResource.max_item_size) throw new ErrorForbidden('non-admin user cannot change max_item_size'); if ('can_share' in resource && !user.is_admin && resource.can_share !== previousResource.can_share) throw new ErrorForbidden('non-admin user cannot change can_share'); if ('account_type' in resource && !user.is_admin && resource.account_type !== previousResource.account_type) throw new ErrorForbidden('non-admin user cannot change account_type'); + if ('must_set_password' in resource && !user.is_admin && resource.must_set_password !== previousResource.must_set_password) throw new ErrorForbidden('non-admin user cannot change must_set_password'); } if (action === AclAction.Delete) { @@ -174,7 +176,7 @@ export default class UserModel extends BaseModel { if (options.isNew) { if (!user.email) throw new ErrorUnprocessableEntity('email must be set'); - if (!user.password) throw new ErrorUnprocessableEntity('password must be set'); + if (!user.password && !user.must_set_password) throw new ErrorUnprocessableEntity('password must be set'); } else { if ('email' in user && !user.email) throw new ErrorUnprocessableEntity('email must be set'); if ('password' in user && !user.password) throw new ErrorUnprocessableEntity('password must be set'); diff --git a/packages/server/src/routes/index/users.test.ts b/packages/server/src/routes/index/users.test.ts index fef44f6cbe..e346deb60e 100644 --- a/packages/server/src/routes/index/users.test.ts +++ b/packages/server/src/routes/index/users.test.ts @@ -85,6 +85,7 @@ describe('index_users', function() { expect(!!newUser.is_admin).toBe(false); expect(!!newUser.email).toBe(true); expect(newUser.max_item_size).toBe(0); + expect(newUser.must_set_password).toBe(0); const userModel = models().user(); const userFromModel: User = await userModel.load(newUser.id); @@ -93,6 +94,18 @@ describe('index_users', function() { expect(userFromModel.password === '123456').toBe(false); // Password has been hashed }); + test('should ask user to set password if not set on creation', async function() { + const { session } = await createUserAndSession(1, true); + + await postUser(session.id, 'test@example.com', '', { + max_item_size: '', + }); + const newUser = await models().user().loadByEmail('test@example.com'); + + expect(newUser.must_set_password).toBe(1); + expect(!!newUser.password).toBe(true); + }); + test('new user should be able to login', async function() { const { session } = await createUserAndSession(1, true); diff --git a/packages/server/src/routes/index/users.ts b/packages/server/src/routes/index/users.ts index d1b5f9e381..1ad2e1aea8 100644 --- a/packages/server/src/routes/index/users.ts +++ b/packages/server/src/routes/index/users.ts @@ -12,6 +12,7 @@ import { AclAction } from '../../models/BaseModel'; import { NotificationKey } from '../../models/NotificationModel'; import { formatBytes } from '../../utils/bytes'; import { accountTypeOptions, accountTypeProperties } from '../../models/UserModel'; +import uuidgen from '../../utils/uuidgen'; interface CheckPasswordInput { password: string; @@ -51,6 +52,11 @@ function makeUser(isNew: boolean, fields: any): User { if (!isNew) user.id = fields.id; + if (isNew) { + user.must_set_password = user.password ? 0 : 1; + user.password = user.password ? user.password : uuidgen(); + } + return user; } diff --git a/packages/server/src/views/index/user.mustache b/packages/server/src/views/index/user.mustache index bdb564bb5d..c1abeaf02c 100644 --- a/packages/server/src/views/index/user.mustache +++ b/packages/server/src/views/index/user.mustache @@ -25,6 +25,7 @@ {{/accountTypes}} +

If the account type is anything other than Default, the account-specific properties will be applied.

{{/showAccountTypes}} @@ -52,7 +53,10 @@
- + {{#global.owner.is_admin}} +

When creating a new user, if no password is specified the user will have to set it by following the link in their email.

+ {{/global.owner.is_admin}} +
{{#showDeleteButton}}