mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
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:
parent
d3fecb9f6d
commit
8532b0030d
|
@ -12,6 +12,7 @@ import {
|
||||||
} from './Interfaces';
|
} from './Interfaces';
|
||||||
import { NodeMailer } from './NodeMailer';
|
import { NodeMailer } from './NodeMailer';
|
||||||
|
|
||||||
|
// TODO: make function fully async (remove sync functions)
|
||||||
async function getTemplate(configKeyName: string, defaultFilename: string) {
|
async function getTemplate(configKeyName: string, defaultFilename: string) {
|
||||||
const templateOverride = (await GenericHelpers.getConfigValue(
|
const templateOverride = (await GenericHelpers.getConfigValue(
|
||||||
`userManagement.emails.templates.${configKeyName}`,
|
`userManagement.emails.templates.${configKeyName}`,
|
||||||
|
|
|
@ -104,7 +104,7 @@ export function usersNamespace(this: N8nApp): void {
|
||||||
400,
|
400,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
createUsers[invite.email] = null;
|
createUsers[invite.email.toLowerCase()] = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const role = await Db.collections.Role.findOne({ scope: 'global', name: 'member' });
|
const role = await Db.collections.Role.findOne({ scope: 'global', name: 'member' });
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
|
BeforeInsert,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { IsEmail, IsString, Length } from 'class-validator';
|
import { IsEmail, IsString, Length } from 'class-validator';
|
||||||
import * as config from '../../../config';
|
import * as config from '../../../config';
|
||||||
|
@ -20,7 +21,7 @@ import { Role } from './Role';
|
||||||
import { SharedWorkflow } from './SharedWorkflow';
|
import { SharedWorkflow } from './SharedWorkflow';
|
||||||
import { SharedCredentials } from './SharedCredentials';
|
import { SharedCredentials } from './SharedCredentials';
|
||||||
import { NoXss } from '../utils/customValidators';
|
import { NoXss } from '../utils/customValidators';
|
||||||
import { answersFormatter } from '../utils/transformers';
|
import { answersFormatter, lowerCaser } from '../utils/transformers';
|
||||||
|
|
||||||
export const MIN_PASSWORD_LENGTH = 8;
|
export const MIN_PASSWORD_LENGTH = 8;
|
||||||
|
|
||||||
|
@ -62,7 +63,11 @@ export class User {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@Column({ length: 254, nullable: true })
|
@Column({
|
||||||
|
length: 254,
|
||||||
|
nullable: true,
|
||||||
|
transformer: lowerCaser,
|
||||||
|
})
|
||||||
@Index({ unique: true })
|
@Index({ unique: true })
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
email: string;
|
email: string;
|
||||||
|
@ -119,8 +124,10 @@ export class User {
|
||||||
})
|
})
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@BeforeInsert()
|
||||||
@BeforeUpdate()
|
@BeforeUpdate()
|
||||||
setUpdateDate(): void {
|
preUpsertHook(): void {
|
||||||
|
this.email = this.email?.toLowerCase();
|
||||||
this.updatedAt = new Date();
|
this.updatedAt = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
import config = require('../../../../config');
|
||||||
|
|
||||||
|
export class LowerCaseUserEmail1648740597343 implements MigrationInterface {
|
||||||
|
name = 'LowerCaseUserEmail1648740597343';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
const tablePrefix = config.get('database.tablePrefix');
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE ${tablePrefix}user
|
||||||
|
SET email = LOWER(email);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import { AddWaitColumnId1626183952959 } from './1626183952959-AddWaitColumn';
|
||||||
import { UpdateWorkflowCredentials1630451444017 } from './1630451444017-UpdateWorkflowCredentials';
|
import { UpdateWorkflowCredentials1630451444017 } from './1630451444017-UpdateWorkflowCredentials';
|
||||||
import { AddExecutionEntityIndexes1644424784709 } from './1644424784709-AddExecutionEntityIndexes';
|
import { AddExecutionEntityIndexes1644424784709 } from './1644424784709-AddExecutionEntityIndexes';
|
||||||
import { CreateUserManagement1646992772331 } from './1646992772331-CreateUserManagement';
|
import { CreateUserManagement1646992772331 } from './1646992772331-CreateUserManagement';
|
||||||
|
import { LowerCaseUserEmail1648740597343 } from './1648740597343-LowerCaseUserEmail';
|
||||||
|
|
||||||
export const mysqlMigrations = [
|
export const mysqlMigrations = [
|
||||||
InitialMigration1588157391238,
|
InitialMigration1588157391238,
|
||||||
|
@ -28,4 +29,5 @@ export const mysqlMigrations = [
|
||||||
UpdateWorkflowCredentials1630451444017,
|
UpdateWorkflowCredentials1630451444017,
|
||||||
AddExecutionEntityIndexes1644424784709,
|
AddExecutionEntityIndexes1644424784709,
|
||||||
CreateUserManagement1646992772331,
|
CreateUserManagement1646992772331,
|
||||||
|
LowerCaseUserEmail1648740597343,
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
import config = require('../../../../config');
|
||||||
|
|
||||||
|
export class LowerCaseUserEmail1648740597343 implements MigrationInterface {
|
||||||
|
name = 'LowerCaseUserEmail1648740597343';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
let tablePrefix = config.get('database.tablePrefix');
|
||||||
|
const schema = config.get('database.postgresdb.schema');
|
||||||
|
if (schema) {
|
||||||
|
tablePrefix = schema + '.' + tablePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE ${tablePrefix}user
|
||||||
|
SET email = LOWER(email);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import { UpdateWorkflowCredentials1630419189837 } from './1630419189837-UpdateWo
|
||||||
import { AddExecutionEntityIndexes1644422880309 } from './1644422880309-AddExecutionEntityIndexes';
|
import { AddExecutionEntityIndexes1644422880309 } from './1644422880309-AddExecutionEntityIndexes';
|
||||||
import { IncreaseTypeVarcharLimit1646834195327 } from './1646834195327-IncreaseTypeVarcharLimit';
|
import { IncreaseTypeVarcharLimit1646834195327 } from './1646834195327-IncreaseTypeVarcharLimit';
|
||||||
import { CreateUserManagement1646992772331 } from './1646992772331-CreateUserManagement';
|
import { CreateUserManagement1646992772331 } from './1646992772331-CreateUserManagement';
|
||||||
|
import { LowerCaseUserEmail1648740597343 } from './1648740597343-LowerCaseUserEmail';
|
||||||
|
|
||||||
export const postgresMigrations = [
|
export const postgresMigrations = [
|
||||||
InitialMigration1587669153312,
|
InitialMigration1587669153312,
|
||||||
|
@ -24,4 +25,5 @@ export const postgresMigrations = [
|
||||||
AddExecutionEntityIndexes1644422880309,
|
AddExecutionEntityIndexes1644422880309,
|
||||||
IncreaseTypeVarcharLimit1646834195327,
|
IncreaseTypeVarcharLimit1646834195327,
|
||||||
CreateUserManagement1646992772331,
|
CreateUserManagement1646992772331,
|
||||||
|
LowerCaseUserEmail1648740597343,
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
import config = require('../../../../config');
|
||||||
|
import { logMigrationEnd, logMigrationStart } from '../../utils/migrationHelpers';
|
||||||
|
|
||||||
|
export class LowerCaseUserEmail1648740597343 implements MigrationInterface {
|
||||||
|
name = 'LowerCaseUserEmail1648740597343';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
logMigrationStart(this.name);
|
||||||
|
|
||||||
|
const tablePrefix = config.get('database.tablePrefix');
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE "${tablePrefix}user"
|
||||||
|
SET email = LOWER(email);
|
||||||
|
`);
|
||||||
|
|
||||||
|
logMigrationEnd(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import { AddWaitColumn1621707690587 } from './1621707690587-AddWaitColumn';
|
||||||
import { UpdateWorkflowCredentials1630330987096 } from './1630330987096-UpdateWorkflowCredentials';
|
import { UpdateWorkflowCredentials1630330987096 } from './1630330987096-UpdateWorkflowCredentials';
|
||||||
import { AddExecutionEntityIndexes1644421939510 } from './1644421939510-AddExecutionEntityIndexes';
|
import { AddExecutionEntityIndexes1644421939510 } from './1644421939510-AddExecutionEntityIndexes';
|
||||||
import { CreateUserManagement1646992772331 } from './1646992772331-CreateUserManagement';
|
import { CreateUserManagement1646992772331 } from './1646992772331-CreateUserManagement';
|
||||||
|
import { LowerCaseUserEmail1648740597343 } from './1648740597343-LowerCaseUserEmail';
|
||||||
|
|
||||||
const sqliteMigrations = [
|
const sqliteMigrations = [
|
||||||
InitialMigration1588102412422,
|
InitialMigration1588102412422,
|
||||||
|
@ -24,6 +25,7 @@ const sqliteMigrations = [
|
||||||
UpdateWorkflowCredentials1630330987096,
|
UpdateWorkflowCredentials1630330987096,
|
||||||
AddExecutionEntityIndexes1644421939510,
|
AddExecutionEntityIndexes1644421939510,
|
||||||
CreateUserManagement1646992772331,
|
CreateUserManagement1646992772331,
|
||||||
|
LowerCaseUserEmail1648740597343,
|
||||||
];
|
];
|
||||||
|
|
||||||
export { sqliteMigrations };
|
export { sqliteMigrations };
|
||||||
|
|
|
@ -2,8 +2,13 @@
|
||||||
import { IPersonalizationSurveyAnswers } from '../../Interfaces';
|
import { IPersonalizationSurveyAnswers } from '../../Interfaces';
|
||||||
|
|
||||||
export const idStringifier = {
|
export const idStringifier = {
|
||||||
from: (value: number): string | number => (value ? value.toString() : value),
|
from: (value: number): string | number => (typeof value === 'number' ? value.toString() : value),
|
||||||
to: (value: string): number | string => (value ? Number(value) : value),
|
to: (value: string): number | string => (typeof value === 'string' ? Number(value) : value),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lowerCaser = {
|
||||||
|
from: (value: string): string => value,
|
||||||
|
to: (value: string): string => (typeof value === 'string' ? value.toLowerCase() : value),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { hashSync, genSaltSync } from 'bcryptjs';
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
@ -60,38 +59,47 @@ afterAll(async () => {
|
||||||
test('POST /login should log user in', async () => {
|
test('POST /login should log user in', async () => {
|
||||||
const authlessAgent = utils.createAgent(app);
|
const authlessAgent = utils.createAgent(app);
|
||||||
|
|
||||||
const response = await authlessAgent.post('/login').send({
|
await Promise.all(
|
||||||
email: TEST_USER.email,
|
[
|
||||||
password: TEST_USER.password,
|
{
|
||||||
});
|
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 {
|
const {
|
||||||
id,
|
id,
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
password,
|
password,
|
||||||
personalizationAnswers,
|
personalizationAnswers,
|
||||||
globalRole,
|
globalRole,
|
||||||
resetPasswordToken,
|
resetPasswordToken,
|
||||||
} = response.body.data;
|
} = response.body.data;
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
expect(email).toBe(TEST_USER.email);
|
expect(email).toBe(TEST_USER.email);
|
||||||
expect(firstName).toBe(TEST_USER.firstName);
|
expect(firstName).toBe(TEST_USER.firstName);
|
||||||
expect(lastName).toBe(TEST_USER.lastName);
|
expect(lastName).toBe(TEST_USER.lastName);
|
||||||
expect(password).toBeUndefined();
|
expect(password).toBeUndefined();
|
||||||
expect(personalizationAnswers).toBeNull();
|
expect(personalizationAnswers).toBeNull();
|
||||||
expect(password).toBeUndefined();
|
expect(resetPasswordToken).toBeUndefined();
|
||||||
expect(resetPasswordToken).toBeUndefined();
|
expect(globalRole).toBeDefined();
|
||||||
expect(globalRole).toBeDefined();
|
expect(globalRole.name).toBe('owner');
|
||||||
expect(globalRole.name).toBe('owner');
|
expect(globalRole.scope).toBe('global');
|
||||||
expect(globalRole.scope).toBe('global');
|
|
||||||
|
|
||||||
const authToken = utils.getAuthToken(response);
|
const authToken = utils.getAuthToken(response);
|
||||||
expect(authToken).toBeDefined();
|
expect(authToken).toBeDefined();
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /login should receive logged in user', async () => {
|
test('GET /login should receive logged in user', async () => {
|
||||||
|
|
|
@ -91,7 +91,7 @@ describe('Owner shell', () => {
|
||||||
} = response.body.data;
|
} = response.body.data;
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
expect(email).toBe(validPayload.email);
|
expect(email).toBe(validPayload.email.toLowerCase());
|
||||||
expect(firstName).toBe(validPayload.firstName);
|
expect(firstName).toBe(validPayload.firstName);
|
||||||
expect(lastName).toBe(validPayload.lastName);
|
expect(lastName).toBe(validPayload.lastName);
|
||||||
expect(personalizationAnswers).toBeNull();
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
@ -103,7 +103,7 @@ describe('Owner shell', () => {
|
||||||
|
|
||||||
const storedOwnerShell = await Db.collections.User!.findOneOrFail(id);
|
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.firstName).toBe(validPayload.firstName);
|
||||||
expect(storedOwnerShell.lastName).toBe(validPayload.lastName);
|
expect(storedOwnerShell.lastName).toBe(validPayload.lastName);
|
||||||
}
|
}
|
||||||
|
@ -245,7 +245,7 @@ describe('Member', () => {
|
||||||
} = response.body.data;
|
} = response.body.data;
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
expect(email).toBe(validPayload.email);
|
expect(email).toBe(validPayload.email.toLowerCase());
|
||||||
expect(firstName).toBe(validPayload.firstName);
|
expect(firstName).toBe(validPayload.firstName);
|
||||||
expect(lastName).toBe(validPayload.lastName);
|
expect(lastName).toBe(validPayload.lastName);
|
||||||
expect(personalizationAnswers).toBeNull();
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
@ -257,7 +257,7 @@ describe('Member', () => {
|
||||||
|
|
||||||
const storedMember = await Db.collections.User!.findOneOrFail(id);
|
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.firstName).toBe(validPayload.firstName);
|
||||||
expect(storedMember.lastName).toBe(validPayload.lastName);
|
expect(storedMember.lastName).toBe(validPayload.lastName);
|
||||||
}
|
}
|
||||||
|
@ -400,7 +400,7 @@ describe('Owner', () => {
|
||||||
} = response.body.data;
|
} = response.body.data;
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
expect(email).toBe(validPayload.email);
|
expect(email).toBe(validPayload.email.toLowerCase());
|
||||||
expect(firstName).toBe(validPayload.firstName);
|
expect(firstName).toBe(validPayload.firstName);
|
||||||
expect(lastName).toBe(validPayload.lastName);
|
expect(lastName).toBe(validPayload.lastName);
|
||||||
expect(personalizationAnswers).toBeNull();
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
@ -412,19 +412,13 @@ describe('Owner', () => {
|
||||||
|
|
||||||
const storedOwner = await Db.collections.User!.findOneOrFail(id);
|
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.firstName).toBe(validPayload.firstName);
|
||||||
expect(storedOwner.lastName).toBe(validPayload.lastName);
|
expect(storedOwner.lastName).toBe(validPayload.lastName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const TEST_USER = {
|
|
||||||
email: randomEmail(),
|
|
||||||
firstName: randomName(),
|
|
||||||
lastName: randomName(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const SURVEY = [
|
const SURVEY = [
|
||||||
'codingSkill',
|
'codingSkill',
|
||||||
'companyIndustry',
|
'companyIndustry',
|
||||||
|
@ -444,7 +438,7 @@ const VALID_PATCH_ME_PAYLOADS = [
|
||||||
password: randomValidPassword(),
|
password: randomValidPassword(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
email: randomEmail(),
|
email: randomEmail().toUpperCase(),
|
||||||
firstName: randomName(),
|
firstName: randomName(),
|
||||||
lastName: randomName(),
|
lastName: randomName(),
|
||||||
password: randomValidPassword(),
|
password: randomValidPassword(),
|
||||||
|
|
|
@ -30,6 +30,12 @@ beforeAll(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
jest.mock('../../config');
|
||||||
|
|
||||||
|
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
await testDb.truncate(['User'], testDbName);
|
await testDb.truncate(['User'], testDbName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,6 +94,29 @@ test('POST /owner should create owner and enable isInstanceOwnerSetUp', async ()
|
||||||
expect(isInstanceOwnerSetUpSetting).toBe(true);
|
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 () => {
|
test('POST /owner should fail with invalid inputs', async () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
const authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
|
||||||
|
|
|
@ -19,6 +19,7 @@ jest.mock('../../src/telemetry');
|
||||||
let app: express.Application;
|
let app: express.Application;
|
||||||
let testDbName = '';
|
let testDbName = '';
|
||||||
let globalOwnerRole: Role;
|
let globalOwnerRole: Role;
|
||||||
|
let globalMemberRole: Role;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = utils.initTestServer({ endpointGroups: ['passwordReset'], applyAuth: true });
|
app = utils.initTestServer({ endpointGroups: ['passwordReset'], applyAuth: true });
|
||||||
|
@ -26,6 +27,7 @@ beforeAll(async () => {
|
||||||
testDbName = initResult.testDbName;
|
testDbName = initResult.testDbName;
|
||||||
|
|
||||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||||
|
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||||
|
|
||||||
utils.initTestTelemetry();
|
utils.initTestTelemetry();
|
||||||
utils.initTestLogger();
|
utils.initTestLogger();
|
||||||
|
@ -50,17 +52,22 @@ test('POST /forgot-password should send password reset email', async () => {
|
||||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||||
|
|
||||||
const authlessAgent = utils.createAgent(app);
|
const authlessAgent = utils.createAgent(app);
|
||||||
|
const member = await testDb.createUser({ email: 'test@test.com', globalRole: globalMemberRole });
|
||||||
|
|
||||||
await utils.configureSmtp();
|
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.statusCode).toBe(200);
|
||||||
expect(response.body).toEqual({});
|
expect(response.body).toEqual({});
|
||||||
|
|
||||||
const storedOwner = await Db.collections.User!.findOneOrFail({ email: owner.email });
|
const user = await Db.collections.User!.findOneOrFail({ email: payload.email });
|
||||||
expect(storedOwner.resetPasswordToken).toBeDefined();
|
expect(user.resetPasswordToken).toBeDefined();
|
||||||
expect(storedOwner.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /forgot-password should fail if emailing is not set up', async () => {
|
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);
|
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 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 });
|
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||||
|
|
||||||
await utils.configureSmtp();
|
await utils.configureSmtp();
|
||||||
|
|
||||||
const testEmails = [randomEmail(), randomEmail(), randomEmail()];
|
const testEmails = [randomEmail(), randomEmail().toUpperCase(), memberShell.email, member.email];
|
||||||
|
|
||||||
const payload = testEmails.map((e) => ({ email: e }));
|
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);
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
await Promise.all(
|
for (const {
|
||||||
response.body.data.map(async ({ user, error }: { user: User; error: Error }) => {
|
user: { id, email: receivedEmail },
|
||||||
const { id, email: receivedEmail } = user;
|
error,
|
||||||
|
} of response.body.data) {
|
||||||
|
expect(validator.isUUID(id)).toBe(true);
|
||||||
|
expect(id).not.toBe(member.id);
|
||||||
|
|
||||||
expect(validator.isUUID(id)).toBe(true);
|
const lowerCasedEmail = receivedEmail.toLowerCase();
|
||||||
expect(testEmails.some((e) => e === receivedEmail)).toBe(true);
|
expect(receivedEmail).toBe(lowerCasedEmail);
|
||||||
if (error) {
|
expect(payload.some(({ email }) => email.toLowerCase() === lowerCasedEmail)).toBe(true);
|
||||||
expect(error).toBe('Email could not be sent');
|
|
||||||
}
|
|
||||||
|
|
||||||
const storedUser = await Db.collections.User!.findOneOrFail(id);
|
if (error) {
|
||||||
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } =
|
expect(error).toBe('Email could not be sent');
|
||||||
storedUser;
|
}
|
||||||
|
|
||||||
expect(firstName).toBeNull();
|
const storedUser = await Db.collections.User!.findOneOrFail(id);
|
||||||
expect(lastName).toBeNull();
|
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } =
|
||||||
expect(personalizationAnswers).toBeNull();
|
storedUser;
|
||||||
expect(password).toBeNull();
|
|
||||||
expect(resetPasswordToken).toBeNull();
|
expect(firstName).toBeNull();
|
||||||
}),
|
expect(lastName).toBeNull();
|
||||||
);
|
expect(personalizationAnswers).toBeNull();
|
||||||
|
expect(password).toBeNull();
|
||||||
|
expect(resetPasswordToken).toBeNull();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /users should fail with invalid inputs', async () => {
|
test('POST /users should fail with invalid inputs', async () => {
|
||||||
|
|
Loading…
Reference in a new issue