mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
chore(core): Expose mfaEnabled field to users list endpoint (#16654)
This commit is contained in:
@@ -11,6 +11,8 @@ const USERS_LIST_SORT_OPTIONS = [
|
||||
'lastName:desc',
|
||||
'role:asc', // ascending order by role is Owner, Admin, Member
|
||||
'role:desc',
|
||||
'mfaEnabled:asc',
|
||||
'mfaEnabled:desc',
|
||||
// 'lastActive:asc',
|
||||
// 'lastActive:desc',
|
||||
] as const;
|
||||
@@ -32,6 +34,7 @@ const userFilterSchema = z.object({
|
||||
firstName: z.string().optional(),
|
||||
lastName: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
mfaEnabled: z.boolean().optional(),
|
||||
fullText: z.string().optional(), // Full text search across firstName, lastName, and email
|
||||
});
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export const userListItemSchema = z.object({
|
||||
personalizationAnswers: z.object({}).passthrough().nullable().optional(),
|
||||
lastActive: z.string().optional(),
|
||||
projectRelations: z.array(userProjectSchema).nullable().optional(),
|
||||
mfaEnabled: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const usersListSchema = z.object({
|
||||
|
||||
@@ -182,6 +182,12 @@ export class UserRepository extends Repository<User> {
|
||||
});
|
||||
}
|
||||
|
||||
if (filter?.mfaEnabled !== undefined) {
|
||||
queryBuilder.andWhere('user.mfaEnabled = :mfaEnabled', {
|
||||
mfaEnabled: filter.mfaEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
if (filter?.isOwner !== undefined) {
|
||||
if (filter.isOwner) {
|
||||
queryBuilder.andWhere('user.role = :role', {
|
||||
@@ -240,13 +246,13 @@ export class UserRepository extends Repository<User> {
|
||||
if (sortBy) {
|
||||
for (const sort of sortBy) {
|
||||
const [field, order] = sort.split(':');
|
||||
if (field === 'firstName' || field === 'lastName') {
|
||||
queryBuilder.addOrderBy(`user.${field}`, order.toUpperCase() as 'ASC' | 'DESC');
|
||||
} else if (field === 'role') {
|
||||
if (field === 'role') {
|
||||
queryBuilder.addOrderBy(
|
||||
"CASE WHEN user.role='global:owner' THEN 0 WHEN user.role='global:admin' THEN 1 ELSE 2 END",
|
||||
order.toUpperCase() as 'ASC' | 'DESC',
|
||||
);
|
||||
} else {
|
||||
queryBuilder.addOrderBy(`user.${field}`, order.toUpperCase() as 'ASC' | 'DESC');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const validateUser = (user: PublicUser) => {
|
||||
expect(user.personalizationAnswers).toBeNull();
|
||||
expect(user.password).toBeUndefined();
|
||||
expect(user.role).toBeDefined();
|
||||
expect(typeof (user as any).mfaEnabled).toBe('boolean');
|
||||
};
|
||||
|
||||
export type UserInvitationResult = {
|
||||
|
||||
@@ -61,6 +61,7 @@ describe('GET /users', () => {
|
||||
email: 'member1@n8n.io',
|
||||
firstName: 'Member1FirstName',
|
||||
lastName: 'Member1LastName',
|
||||
mfaEnabled: true,
|
||||
});
|
||||
await createUser({
|
||||
role: 'global:member',
|
||||
@@ -73,6 +74,7 @@ describe('GET /users', () => {
|
||||
email: 'admin@n8n.io',
|
||||
firstName: 'AdminFirstName',
|
||||
lastName: 'AdminLastName',
|
||||
mfaEnabled: true,
|
||||
});
|
||||
|
||||
ownerAgent = testServer.authAgentFor(owner);
|
||||
@@ -206,6 +208,38 @@ describe('GET /users', () => {
|
||||
expect(_user.isOwner).toBe(false);
|
||||
});
|
||||
|
||||
test('should filter users by mfaEnabled field', async () => {
|
||||
const response = await ownerAgent
|
||||
.get('/users')
|
||||
.query('filter={ "mfaEnabled": true }')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data).toEqual({
|
||||
count: 2,
|
||||
items: expect.arrayContaining([]),
|
||||
});
|
||||
expect(response.body.data.items).toHaveLength(2);
|
||||
|
||||
const [user] = response.body.data.items;
|
||||
|
||||
expect(user.mfaEnabled).toBe(true);
|
||||
|
||||
const _response = await ownerAgent
|
||||
.get('/users')
|
||||
.query('filter={ "mfaEnabled": false }')
|
||||
.expect(200);
|
||||
|
||||
expect(_response.body.data).toEqual({
|
||||
count: 2,
|
||||
items: expect.arrayContaining([]),
|
||||
});
|
||||
expect(_response.body.data.items).toHaveLength(2);
|
||||
|
||||
const [_user] = _response.body.data.items;
|
||||
|
||||
expect(_user.mfaEnabled).toBe(false);
|
||||
});
|
||||
|
||||
test('should filter users by field: fullText', async () => {
|
||||
const response = await ownerAgent
|
||||
.get('/users')
|
||||
@@ -574,6 +608,32 @@ describe('GET /users', () => {
|
||||
expect(response.body.data.items[3].lastName).toBe('AdminLastName');
|
||||
});
|
||||
|
||||
test('should sort by mfaEnabled:asc', async () => {
|
||||
const response = await ownerAgent
|
||||
.get('/users')
|
||||
.query('sortBy[]=mfaEnabled:asc')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.items).toHaveLength(4);
|
||||
expect(response.body.data.items[0].mfaEnabled).toBe(false);
|
||||
expect(response.body.data.items[1].mfaEnabled).toBe(false);
|
||||
expect(response.body.data.items[2].mfaEnabled).toBe(true);
|
||||
expect(response.body.data.items[3].mfaEnabled).toBe(true);
|
||||
});
|
||||
|
||||
test('should sort by mfaEnabled:desc', async () => {
|
||||
const response = await ownerAgent
|
||||
.get('/users')
|
||||
.query('sortBy[]=mfaEnabled:desc')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.items).toHaveLength(4);
|
||||
expect(response.body.data.items[0].mfaEnabled).toBe(true);
|
||||
expect(response.body.data.items[1].mfaEnabled).toBe(true);
|
||||
expect(response.body.data.items[2].mfaEnabled).toBe(false);
|
||||
expect(response.body.data.items[3].mfaEnabled).toBe(false);
|
||||
});
|
||||
|
||||
test('should sort by firstName and lastName combined', async () => {
|
||||
const user1 = await createUser({
|
||||
role: 'global:member',
|
||||
|
||||
Reference in New Issue
Block a user