fix(core): Make email for UM case insensitive (#3078)
* 🚧 lowercasing email * ✅ add tests for case insensitive email * 🐘 add migration to lowercase email * 🚚 rename migration * 🐛 fix package.lock * 🐛 fix double import * 📋 add todo
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { hashSync, genSaltSync } from 'bcryptjs';
|
||||
import express from 'express';
|
||||
import validator from 'validator';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@@ -60,38 +59,47 @@ afterAll(async () => {
|
||||
test('POST /login should log user in', async () => {
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
const response = await authlessAgent.post('/login').send({
|
||||
email: TEST_USER.email,
|
||||
password: TEST_USER.password,
|
||||
});
|
||||
await Promise.all(
|
||||
[
|
||||
{
|
||||
email: TEST_USER.email,
|
||||
password: TEST_USER.password,
|
||||
},
|
||||
{
|
||||
email: TEST_USER.email.toUpperCase(),
|
||||
password: TEST_USER.password,
|
||||
},
|
||||
].map(async (payload) => {
|
||||
const response = await authlessAgent.post('/login').send(payload);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(TEST_USER.email);
|
||||
expect(firstName).toBe(TEST_USER.firstName);
|
||||
expect(lastName).toBe(TEST_USER.lastName);
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(TEST_USER.email);
|
||||
expect(firstName).toBe(TEST_USER.firstName);
|
||||
expect(lastName).toBe(TEST_USER.lastName);
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('GET /login should receive logged in user', async () => {
|
||||
|
||||
@@ -91,7 +91,7 @@ describe('Owner shell', () => {
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(validPayload.email);
|
||||
expect(email).toBe(validPayload.email.toLowerCase());
|
||||
expect(firstName).toBe(validPayload.firstName);
|
||||
expect(lastName).toBe(validPayload.lastName);
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
@@ -103,7 +103,7 @@ describe('Owner shell', () => {
|
||||
|
||||
const storedOwnerShell = await Db.collections.User!.findOneOrFail(id);
|
||||
|
||||
expect(storedOwnerShell.email).toBe(validPayload.email);
|
||||
expect(storedOwnerShell.email).toBe(validPayload.email.toLowerCase());
|
||||
expect(storedOwnerShell.firstName).toBe(validPayload.firstName);
|
||||
expect(storedOwnerShell.lastName).toBe(validPayload.lastName);
|
||||
}
|
||||
@@ -245,7 +245,7 @@ describe('Member', () => {
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(validPayload.email);
|
||||
expect(email).toBe(validPayload.email.toLowerCase());
|
||||
expect(firstName).toBe(validPayload.firstName);
|
||||
expect(lastName).toBe(validPayload.lastName);
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
@@ -257,7 +257,7 @@ describe('Member', () => {
|
||||
|
||||
const storedMember = await Db.collections.User!.findOneOrFail(id);
|
||||
|
||||
expect(storedMember.email).toBe(validPayload.email);
|
||||
expect(storedMember.email).toBe(validPayload.email.toLowerCase());
|
||||
expect(storedMember.firstName).toBe(validPayload.firstName);
|
||||
expect(storedMember.lastName).toBe(validPayload.lastName);
|
||||
}
|
||||
@@ -400,7 +400,7 @@ describe('Owner', () => {
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(validPayload.email);
|
||||
expect(email).toBe(validPayload.email.toLowerCase());
|
||||
expect(firstName).toBe(validPayload.firstName);
|
||||
expect(lastName).toBe(validPayload.lastName);
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
@@ -412,19 +412,13 @@ describe('Owner', () => {
|
||||
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail(id);
|
||||
|
||||
expect(storedOwner.email).toBe(validPayload.email);
|
||||
expect(storedOwner.email).toBe(validPayload.email.toLowerCase());
|
||||
expect(storedOwner.firstName).toBe(validPayload.firstName);
|
||||
expect(storedOwner.lastName).toBe(validPayload.lastName);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const TEST_USER = {
|
||||
email: randomEmail(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
};
|
||||
|
||||
const SURVEY = [
|
||||
'codingSkill',
|
||||
'companyIndustry',
|
||||
@@ -444,7 +438,7 @@ const VALID_PATCH_ME_PAYLOADS = [
|
||||
password: randomValidPassword(),
|
||||
},
|
||||
{
|
||||
email: randomEmail(),
|
||||
email: randomEmail().toUpperCase(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
|
||||
@@ -30,6 +30,12 @@ beforeAll(async () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.mock('../../config');
|
||||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
});
|
||||
|
||||
@@ -88,6 +94,29 @@ test('POST /owner should create owner and enable isInstanceOwnerSetUp', async ()
|
||||
expect(isInstanceOwnerSetUpSetting).toBe(true);
|
||||
});
|
||||
|
||||
test('POST /owner should create owner with lowercased email', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
const newOwnerData = {
|
||||
email: randomEmail().toUpperCase(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
password: randomValidPassword(),
|
||||
};
|
||||
|
||||
const response = await authOwnerAgent.post('/owner').send(newOwnerData);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const { id, email } = response.body.data;
|
||||
|
||||
expect(email).toBe(newOwnerData.email.toLowerCase());
|
||||
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail(id);
|
||||
expect(storedOwner.email).toBe(newOwnerData.email.toLowerCase());
|
||||
});
|
||||
|
||||
test('POST /owner should fail with invalid inputs', async () => {
|
||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||
|
||||
@@ -19,6 +19,7 @@ jest.mock('../../src/telemetry');
|
||||
let app: express.Application;
|
||||
let testDbName = '';
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = utils.initTestServer({ endpointGroups: ['passwordReset'], applyAuth: true });
|
||||
@@ -26,6 +27,7 @@ beforeAll(async () => {
|
||||
testDbName = initResult.testDbName;
|
||||
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
|
||||
utils.initTestTelemetry();
|
||||
utils.initTestLogger();
|
||||
@@ -50,17 +52,22 @@ test('POST /forgot-password should send password reset email', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
const member = await testDb.createUser({ email: 'test@test.com', globalRole: globalMemberRole });
|
||||
|
||||
await utils.configureSmtp();
|
||||
|
||||
const response = await authlessAgent.post('/forgot-password').send({ email: owner.email });
|
||||
await Promise.all(
|
||||
[{ email: owner.email }, { email: member.email.toUpperCase() }].map(async (payload) => {
|
||||
const response = await authlessAgent.post('/forgot-password').send(payload);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({});
|
||||
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail({ email: owner.email });
|
||||
expect(storedOwner.resetPasswordToken).toBeDefined();
|
||||
expect(storedOwner.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
||||
const user = await Db.collections.User!.findOneOrFail({ email: payload.email });
|
||||
expect(user.resetPasswordToken).toBeDefined();
|
||||
expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('POST /forgot-password should fail if emailing is not set up', async () => {
|
||||
|
||||
@@ -481,13 +481,15 @@ test('POST /users should fail if user management is disabled', async () => {
|
||||
expect(response.statusCode).toBe(500);
|
||||
});
|
||||
|
||||
test('POST /users should email invites and create user shells', async () => {
|
||||
test('POST /users should email invites and create user shells but ignore existing', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
await utils.configureSmtp();
|
||||
|
||||
const testEmails = [randomEmail(), randomEmail(), randomEmail()];
|
||||
const testEmails = [randomEmail(), randomEmail().toUpperCase(), memberShell.email, member.email];
|
||||
|
||||
const payload = testEmails.map((e) => ({ email: e }));
|
||||
|
||||
@@ -495,27 +497,31 @@ test('POST /users should email invites and create user shells', async () => {
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
await Promise.all(
|
||||
response.body.data.map(async ({ user, error }: { user: User; error: Error }) => {
|
||||
const { id, email: receivedEmail } = user;
|
||||
for (const {
|
||||
user: { id, email: receivedEmail },
|
||||
error,
|
||||
} of response.body.data) {
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(id).not.toBe(member.id);
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(testEmails.some((e) => e === receivedEmail)).toBe(true);
|
||||
if (error) {
|
||||
expect(error).toBe('Email could not be sent');
|
||||
}
|
||||
const lowerCasedEmail = receivedEmail.toLowerCase();
|
||||
expect(receivedEmail).toBe(lowerCasedEmail);
|
||||
expect(payload.some(({ email }) => email.toLowerCase() === lowerCasedEmail)).toBe(true);
|
||||
|
||||
const storedUser = await Db.collections.User!.findOneOrFail(id);
|
||||
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } =
|
||||
storedUser;
|
||||
if (error) {
|
||||
expect(error).toBe('Email could not be sent');
|
||||
}
|
||||
|
||||
expect(firstName).toBeNull();
|
||||
expect(lastName).toBeNull();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeNull();
|
||||
expect(resetPasswordToken).toBeNull();
|
||||
}),
|
||||
);
|
||||
const storedUser = await Db.collections.User!.findOneOrFail(id);
|
||||
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } =
|
||||
storedUser;
|
||||
|
||||
expect(firstName).toBeNull();
|
||||
expect(lastName).toBeNull();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeNull();
|
||||
expect(resetPasswordToken).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
test('POST /users should fail with invalid inputs', async () => {
|
||||
|
||||
Reference in New Issue
Block a user