diff --git a/packages/server/src/migrations/20210703155118_lowercase_emails.ts b/packages/server/src/migrations/20210703155118_lowercase_emails.ts new file mode 100644 index 000000000..838b73546 --- /dev/null +++ b/packages/server/src/migrations/20210703155118_lowercase_emails.ts @@ -0,0 +1,9 @@ +import { DbConnection } from '../db'; + +export async function up(db: DbConnection): Promise { + await db('users').update({ email: db.raw('LOWER(email)') }); +} + +export async function down(_db: DbConnection): Promise { + +} diff --git a/packages/server/src/models/UserModel.ts b/packages/server/src/models/UserModel.ts index 97513da89..21a0c7286 100644 --- a/packages/server/src/models/UserModel.ts +++ b/packages/server/src/models/UserModel.ts @@ -84,7 +84,7 @@ export default class UserModel extends BaseModel { } public async loadByEmail(email: string): Promise { - const user: User = { email: email }; + const user: User = this.formatValues({ email: email }); return this.db(this.tableName).where(user).first(); } @@ -251,6 +251,12 @@ export default class UserModel extends BaseModel { }); } + private formatValues(user: User): User { + const output: User = { ...user }; + if ('email' in output) output.email = user.email.trim().toLowerCase(); + return output; + } + // Note that when the "password" property is provided, it is going to be // hashed automatically. It means that it is not safe to do: // @@ -259,7 +265,7 @@ export default class UserModel extends BaseModel { // // Because the password would be hashed twice. public async save(object: User, options: SaveOptions = {}): Promise { - const user = { ...object }; + const user = this.formatValues(object); if (user.password) user.password = auth.hashPassword(user.password); diff --git a/packages/server/src/routes/index/users.test.ts b/packages/server/src/routes/index/users.test.ts index f15af964a..a354aa6a2 100644 --- a/packages/server/src/routes/index/users.test.ts +++ b/packages/server/src/routes/index/users.test.ts @@ -127,6 +127,17 @@ describe('index/users', function() { expect(loggedInUser.email).toBe('test@example.com'); }); + test('should format the email when saving it', async function() { + const email = 'ILikeUppercaseAndSpaces@Example.COM '; + + const { session } = await createUserAndSession(1, true); + + await postUser(session.id, email, '123456'); + const loggedInUser = await models().user().login(email, '123456'); + expect(!!loggedInUser).toBe(true); + expect(loggedInUser.email).toBe('ilikeuppercaseandspaces@example.com'); + }); + test('should not create anything if user creation fail', async function() { const { session } = await createUserAndSession(1, true); @@ -324,6 +335,7 @@ describe('index/users', function() { // non-admin cannot change max_item_size await expectHttpError(async () => patchUser(session1.id, { id: user1.id, max_item_size: 1000 }), ErrorForbidden.httpCode); + await expectHttpError(async () => patchUser(session1.id, { id: user1.id, max_total_item_size: 1000 }), ErrorForbidden.httpCode); // non-admin cannot change can_share_folder await models().user().save({ id: user1.id, can_share_folder: 0 });