mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
feat(core): Add log streaming events for 2FA enable/disable (#23302)
This commit is contained in:
@@ -4,6 +4,7 @@ import { Response } from 'express';
|
||||
|
||||
import { AuthService } from '@/auth/auth.service';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { EventService } from '@/events/event.service';
|
||||
import { ExternalHooks } from '@/external-hooks';
|
||||
import { MfaService } from '@/mfa/mfa.service';
|
||||
import { MFA } from '@/requests';
|
||||
@@ -15,6 +16,7 @@ export class MFAController {
|
||||
private externalHooks: ExternalHooks,
|
||||
private authService: AuthService,
|
||||
private userRepository: UserRepository,
|
||||
private eventService: EventService,
|
||||
) {}
|
||||
|
||||
@Post('/enforce-mfa')
|
||||
@@ -107,6 +109,16 @@ export class MFAController {
|
||||
|
||||
const updatedUser = await this.mfaService.enableMfa(id);
|
||||
|
||||
this.eventService.emit('user-mfa-enabled', {
|
||||
user: {
|
||||
id: req.user.id,
|
||||
email: req.user.email,
|
||||
firstName: req.user.firstName,
|
||||
lastName: req.user.lastName,
|
||||
role: req.user.role,
|
||||
},
|
||||
});
|
||||
|
||||
this.authService.issueCookie(res, updatedUser, verified, req.browserId);
|
||||
}
|
||||
|
||||
@@ -132,6 +144,17 @@ export class MFAController {
|
||||
await this.mfaService.disableMfaWithRecoveryCode(userId, mfaRecoveryCode);
|
||||
}
|
||||
|
||||
this.eventService.emit('user-mfa-disabled', {
|
||||
user: {
|
||||
id: req.user.id,
|
||||
email: req.user.email,
|
||||
firstName: req.user.firstName,
|
||||
lastName: req.user.lastName,
|
||||
role: req.user.role,
|
||||
},
|
||||
disableMethod: mfaCodeDefined ? 'mfaCode' : 'recoveryCode',
|
||||
});
|
||||
|
||||
const updatedUser = await this.userRepository.findOneOrFail({
|
||||
where: { id: userId },
|
||||
relations: ['role'],
|
||||
|
||||
@@ -73,6 +73,8 @@ export const eventNamesAudit = [
|
||||
'n8n.audit.user.credentials.deleted',
|
||||
'n8n.audit.user.api.created',
|
||||
'n8n.audit.user.api.deleted',
|
||||
'n8n.audit.user.mfa.enabled',
|
||||
'n8n.audit.user.mfa.disabled',
|
||||
'n8n.audit.package.installed',
|
||||
'n8n.audit.package.updated',
|
||||
'n8n.audit.package.deleted',
|
||||
|
||||
@@ -657,6 +657,85 @@ describe('LogStreamingEventRelay', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should log on `user-mfa-enabled` event', () => {
|
||||
const event: RelayEventMap['user-mfa-enabled'] = {
|
||||
user: {
|
||||
id: 'user505',
|
||||
email: 'mfauser@example.com',
|
||||
firstName: 'MFA',
|
||||
lastName: 'User',
|
||||
role: { slug: 'global:member' },
|
||||
},
|
||||
};
|
||||
|
||||
eventService.emit('user-mfa-enabled', event);
|
||||
|
||||
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
|
||||
eventName: 'n8n.audit.user.mfa.enabled',
|
||||
payload: {
|
||||
userId: 'user505',
|
||||
_email: 'mfauser@example.com',
|
||||
_firstName: 'MFA',
|
||||
_lastName: 'User',
|
||||
globalRole: 'global:member',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should log on `user-mfa-disabled` event with mfaCode method', () => {
|
||||
const event: RelayEventMap['user-mfa-disabled'] = {
|
||||
user: {
|
||||
id: 'user606',
|
||||
email: 'mfadisable@example.com',
|
||||
firstName: 'Disable',
|
||||
lastName: 'MFA',
|
||||
role: { slug: 'global:member' },
|
||||
},
|
||||
disableMethod: 'mfaCode',
|
||||
};
|
||||
|
||||
eventService.emit('user-mfa-disabled', event);
|
||||
|
||||
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
|
||||
eventName: 'n8n.audit.user.mfa.disabled',
|
||||
payload: {
|
||||
userId: 'user606',
|
||||
_email: 'mfadisable@example.com',
|
||||
_firstName: 'Disable',
|
||||
_lastName: 'MFA',
|
||||
globalRole: 'global:member',
|
||||
disableMethod: 'mfaCode',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should log on `user-mfa-disabled` event with recoveryCode method', () => {
|
||||
const event: RelayEventMap['user-mfa-disabled'] = {
|
||||
user: {
|
||||
id: 'user707',
|
||||
email: 'recovery@example.com',
|
||||
firstName: 'Recovery',
|
||||
lastName: 'User',
|
||||
role: { slug: GLOBAL_OWNER_ROLE.slug },
|
||||
},
|
||||
disableMethod: 'recoveryCode',
|
||||
};
|
||||
|
||||
eventService.emit('user-mfa-disabled', event);
|
||||
|
||||
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
|
||||
eventName: 'n8n.audit.user.mfa.disabled',
|
||||
payload: {
|
||||
userId: 'user707',
|
||||
_email: 'recovery@example.com',
|
||||
_firstName: 'Recovery',
|
||||
_lastName: 'User',
|
||||
globalRole: 'global:owner',
|
||||
disableMethod: 'recoveryCode',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('click events', () => {
|
||||
|
||||
@@ -181,6 +181,15 @@ export type RelayEventMap = {
|
||||
fieldsChanged: string[];
|
||||
};
|
||||
|
||||
'user-mfa-enabled': {
|
||||
user: UserLike;
|
||||
};
|
||||
|
||||
'user-mfa-disabled': {
|
||||
user: UserLike;
|
||||
disableMethod: 'mfaCode' | 'recoveryCode';
|
||||
};
|
||||
|
||||
'user-signed-up': {
|
||||
user: UserLike;
|
||||
userType: AuthProviderType;
|
||||
|
||||
@@ -35,6 +35,8 @@ export class LogStreamingEventRelay extends EventRelay {
|
||||
'user-invited': (event) => this.userInvited(event),
|
||||
'user-reinvited': (event) => this.userReinvited(event),
|
||||
'user-updated': (event) => this.userUpdated(event),
|
||||
'user-mfa-enabled': (event) => this.userMfaEnabled(event),
|
||||
'user-mfa-disabled': (event) => this.userMfaDisabled(event),
|
||||
'user-signed-up': (event) => this.userSignedUp(event),
|
||||
'user-logged-in': (event) => this.userLoggedIn(event),
|
||||
'user-login-failed': (event) => this.userLoginFailed(event),
|
||||
@@ -320,6 +322,22 @@ export class LogStreamingEventRelay extends EventRelay {
|
||||
});
|
||||
}
|
||||
|
||||
@Redactable()
|
||||
private userMfaEnabled({ user }: RelayEventMap['user-mfa-enabled']) {
|
||||
void this.eventBus.sendAuditEvent({
|
||||
eventName: 'n8n.audit.user.mfa.enabled',
|
||||
payload: user,
|
||||
});
|
||||
}
|
||||
|
||||
@Redactable()
|
||||
private userMfaDisabled({ user, disableMethod }: RelayEventMap['user-mfa-disabled']) {
|
||||
void this.eventBus.sendAuditEvent({
|
||||
eventName: 'n8n.audit.user.mfa.disabled',
|
||||
payload: { ...user, disableMethod },
|
||||
});
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Auth
|
||||
|
||||
Reference in New Issue
Block a user