mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2026-08-28 22:30:37 +02:00
feat(core): Invalidate all sessions when MFA is enabled/disabled (#15524)
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { UserUpdateRequestDto } from '@n8n/api-types';
|
||||
import type { User } from '@n8n/db';
|
||||
import type { PublicUser } from '@n8n/db';
|
||||
import { AuthUserRepository } from '@n8n/db';
|
||||
import { InvalidAuthTokenRepository } from '@n8n/db';
|
||||
import { UserRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
@@ -30,7 +29,6 @@ describe('MeController', () => {
|
||||
const userService = mockInstance(UserService);
|
||||
const userRepository = mockInstance(UserRepository);
|
||||
const mockMfaService = mockInstance(MfaService);
|
||||
mockInstance(AuthUserRepository);
|
||||
mockInstance(InvalidAuthTokenRepository);
|
||||
mockInstance(License).isWithinUsersLimit.mockReturnValue(true);
|
||||
const controller = Container.get(MeController);
|
||||
@@ -171,6 +169,7 @@ describe('MeController', () => {
|
||||
authIdentities: [],
|
||||
role: 'global:owner',
|
||||
mfaEnabled: true,
|
||||
mfaSecret: 'secret',
|
||||
});
|
||||
const req = mock<AuthenticatedRequest>({ user, browserId });
|
||||
const res = mock<Response>();
|
||||
@@ -316,7 +315,7 @@ describe('MeController', () => {
|
||||
|
||||
it('should succeed when mfa code is correct', async () => {
|
||||
const req = mock<AuthenticatedRequest>({
|
||||
user: mock({ password: passwordHash, mfaEnabled: true }),
|
||||
user: mock({ password: passwordHash, mfaEnabled: true, mfaSecret: 'secret' }),
|
||||
browserId,
|
||||
});
|
||||
const res = mock<Response>();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PushMessage } from '@n8n/api-types';
|
||||
import type { BooleanLicenseFeature, NumericLicenseFeature } from '@n8n/constants';
|
||||
import { LICENSE_FEATURES, LICENSE_QUOTAS, UNLIMITED_LICENSE_QUOTA } from '@n8n/constants';
|
||||
import { AuthUserRepository, SettingsRepository, UserRepository } from '@n8n/db';
|
||||
import { SettingsRepository, UserRepository } from '@n8n/db';
|
||||
import { Patch, Post, RestController } from '@n8n/decorators';
|
||||
import { Container } from '@n8n/di';
|
||||
import { Request } from 'express';
|
||||
@@ -149,7 +149,6 @@ export class E2EController {
|
||||
private readonly passwordUtility: PasswordUtility,
|
||||
private readonly eventBus: MessageEventBus,
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly authUserRepository: AuthUserRepository,
|
||||
) {
|
||||
license.isLicensed = (feature: BooleanLicenseFeature) => this.enabledFeatures[feature] ?? false;
|
||||
|
||||
@@ -280,7 +279,7 @@ export class E2EController {
|
||||
const { encryptedRecoveryCodes, encryptedSecret } =
|
||||
this.mfaService.encryptSecretAndRecoveryCodes(owner.mfaSecret, owner.mfaRecoveryCodes);
|
||||
|
||||
await this.authUserRepository.update(newOwner.user.id, {
|
||||
await this.userRepository.update(newOwner.user.id, {
|
||||
mfaSecret: encryptedSecret,
|
||||
mfaRecoveryCodes: encryptedRecoveryCodes,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { UserRepository } from '@n8n/db';
|
||||
import { Get, Post, RestController } from '@n8n/decorators';
|
||||
import { Response } from 'express';
|
||||
|
||||
import { AuthService } from '@/auth/auth.service';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { ExternalHooks } from '@/external-hooks';
|
||||
import { MfaService } from '@/mfa/mfa.service';
|
||||
@@ -10,6 +13,8 @@ export class MFAController {
|
||||
constructor(
|
||||
private mfaService: MfaService,
|
||||
private externalHooks: ExternalHooks,
|
||||
private authService: AuthService,
|
||||
private userRepository: UserRepository,
|
||||
) {}
|
||||
|
||||
@Post('/can-enable')
|
||||
@@ -59,7 +64,7 @@ export class MFAController {
|
||||
}
|
||||
|
||||
@Post('/enable', { rateLimit: true })
|
||||
async activateMFA(req: MFA.Activate) {
|
||||
async activateMFA(req: MFA.Activate, res: Response) {
|
||||
const { mfaCode = null } = req.body;
|
||||
const { id, mfaEnabled } = req.user;
|
||||
|
||||
@@ -81,11 +86,13 @@ export class MFAController {
|
||||
if (!verified)
|
||||
throw new BadRequestError('MFA code expired. Close the modal and enable MFA again', 997);
|
||||
|
||||
await this.mfaService.enableMfa(id);
|
||||
const updatedUser = await this.mfaService.enableMfa(id);
|
||||
|
||||
this.authService.issueCookie(res, updatedUser, req.browserId);
|
||||
}
|
||||
|
||||
@Post('/disable', { rateLimit: true })
|
||||
async disableMFA(req: MFA.Disable) {
|
||||
async disableMFA(req: MFA.Disable, res: Response) {
|
||||
const { id: userId } = req.user;
|
||||
|
||||
const { mfaCode, mfaRecoveryCode } = req.body;
|
||||
@@ -105,6 +112,10 @@ export class MFAController {
|
||||
} else if (mfaRecoveryCodeDefined) {
|
||||
await this.mfaService.disableMfaWithRecoveryCode(userId, mfaRecoveryCode);
|
||||
}
|
||||
|
||||
const updatedUser = await this.userRepository.findOneByOrFail({ id: userId });
|
||||
|
||||
this.authService.issueCookie(res, updatedUser, req.browserId);
|
||||
}
|
||||
|
||||
@Post('/verify', { rateLimit: true })
|
||||
|
||||
Reference in New Issue
Block a user