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