From 27fd768deb9a1fc4e38cc8a524d224049c210da8 Mon Sep 17 00:00:00 2001 From: Andreas Fitzek Date: Tue, 4 Nov 2025 16:26:35 +0100 Subject: [PATCH] fix(core): Include role in user-invite-email-click (#21546) --- .../db/src/repositories/user.repository.ts | 8 ++++- packages/@n8n/decorators/src/redactable.ts | 4 +-- .../cli/src/controllers/auth.controller.ts | 4 ++- .../cli/src/events/maps/relay.event-map.ts | 2 +- .../cli/test/integration/auth.api.test.ts | 32 +++++++++++++++++++ 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/packages/@n8n/db/src/repositories/user.repository.ts b/packages/@n8n/db/src/repositories/user.repository.ts index 0ebe9ef2b7d..f20e992fd46 100644 --- a/packages/@n8n/db/src/repositories/user.repository.ts +++ b/packages/@n8n/db/src/repositories/user.repository.ts @@ -12,9 +12,15 @@ export class UserRepository extends Repository { super(User, dataSource.manager); } - async findManyByIds(userIds: string[]) { + async findManyByIds( + userIds: string[], + options?: { + includeRole: boolean; + }, + ) { return await this.find({ where: { id: In(userIds) }, + relations: options?.includeRole ? ['role'] : undefined, }); } diff --git a/packages/@n8n/decorators/src/redactable.ts b/packages/@n8n/decorators/src/redactable.ts index 3f54253a031..cc9b1d6dae2 100644 --- a/packages/@n8n/decorators/src/redactable.ts +++ b/packages/@n8n/decorators/src/redactable.ts @@ -5,7 +5,7 @@ type UserLike = { email?: string; firstName?: string; lastName?: string; - role: { + role?: { slug: string; }; }; @@ -24,7 +24,7 @@ function toRedactable(userLike: UserLike) { _email: userLike.email, _firstName: userLike.firstName, _lastName: userLike.lastName, - globalRole: userLike.role.slug, + globalRole: userLike.role?.slug, }; } diff --git a/packages/cli/src/controllers/auth.controller.ts b/packages/cli/src/controllers/auth.controller.ts index 9892bf0d106..617614c3d01 100644 --- a/packages/cli/src/controllers/auth.controller.ts +++ b/packages/cli/src/controllers/auth.controller.ts @@ -150,7 +150,9 @@ export class AuthController { throw new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED); } - const users = await this.userRepository.findManyByIds([inviterId, inviteeId]); + const users = await this.userRepository.findManyByIds([inviterId, inviteeId], { + includeRole: true, + }); if (users.length !== 2) { this.logger.debug( diff --git a/packages/cli/src/events/maps/relay.event-map.ts b/packages/cli/src/events/maps/relay.event-map.ts index 6de204395f6..b9ed1513952 100644 --- a/packages/cli/src/events/maps/relay.event-map.ts +++ b/packages/cli/src/events/maps/relay.event-map.ts @@ -16,7 +16,7 @@ export type UserLike = { email?: string; firstName?: string; lastName?: string; - role: { + role?: { slug: string; }; }; diff --git a/packages/cli/test/integration/auth.api.test.ts b/packages/cli/test/integration/auth.api.test.ts index 3412daf080d..b4f0aa87e50 100644 --- a/packages/cli/test/integration/auth.api.test.ts +++ b/packages/cli/test/integration/auth.api.test.ts @@ -12,6 +12,8 @@ import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants'; import { createUser, createUserShell } from './shared/db/users'; import type { SuperAgentTest } from './shared/types'; import * as utils from './shared/utils/'; +import { EventService } from '@/events/event.service'; +import type { RelayEventMap } from '@/events/maps/relay.event-map'; let owner: User; let authOwnerAgent: SuperAgentTest; @@ -392,6 +394,36 @@ describe('GET /resolve-signup-token', () => { expect(response.statusCode).toBe(400); } }); + + test('should send roles for user-invite-email-click event', async () => { + const memberShell = await createUserShell(GLOBAL_MEMBER_ROLE); + + const eventService = Container.get(EventService); + const emitSpy = jest.spyOn(eventService, 'emit'); + + await authOwnerAgent + .get('/resolve-signup-token') + .query({ inviterId: owner.id }) + .query({ inviteeId: memberShell.id }) + .expect(200); + + // Check all emitted events + let foundEvent = false; + for (const [eventName, payload] of emitSpy.mock.calls) { + if (eventName === 'user-invite-email-click') { + foundEvent = true; + expect(payload).toBeDefined(); + const { invitee, inviter } = payload as RelayEventMap['user-invite-email-click']; + expect(invitee.role).toBeDefined(); + expect(invitee.role?.slug).toBe('global:member'); + expect(inviter.role).toBeDefined(); + expect(inviter.role?.slug).toBe('global:owner'); + } + } + + expect(foundEvent).toBe(true); + emitSpy.mockRestore(); + }); }); describe('POST /logout', () => {