mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import { Authorized, Delete, Get, Post, RestController } from '@/decorators';
|
|
import { AuthenticatedRequest, MFA } from '@/requests';
|
|
import { MfaService } from '@/Mfa/mfa.service';
|
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
|
|
|
@Authorized()
|
|
@RestController('/mfa')
|
|
export class MFAController {
|
|
constructor(private mfaService: MfaService) {}
|
|
|
|
@Get('/qr')
|
|
async getQRCode(req: AuthenticatedRequest) {
|
|
const { email, id, mfaEnabled } = req.user;
|
|
|
|
if (mfaEnabled)
|
|
throw new BadRequestError(
|
|
'MFA already enabled. Disable it to generate new secret and recovery codes',
|
|
);
|
|
|
|
const { decryptedSecret: secret, decryptedRecoveryCodes: recoveryCodes } =
|
|
await this.mfaService.getSecretAndRecoveryCodes(id);
|
|
|
|
if (secret && recoveryCodes.length) {
|
|
const qrCode = this.mfaService.totp.generateTOTPUri({
|
|
secret,
|
|
label: email,
|
|
});
|
|
|
|
return {
|
|
secret,
|
|
recoveryCodes,
|
|
qrCode,
|
|
};
|
|
}
|
|
|
|
const newRecoveryCodes = this.mfaService.generateRecoveryCodes();
|
|
|
|
const newSecret = this.mfaService.totp.generateSecret();
|
|
|
|
const qrCode = this.mfaService.totp.generateTOTPUri({ secret: newSecret, label: email });
|
|
|
|
await this.mfaService.saveSecretAndRecoveryCodes(id, newSecret, newRecoveryCodes);
|
|
|
|
return {
|
|
secret: newSecret,
|
|
qrCode,
|
|
recoveryCodes: newRecoveryCodes,
|
|
};
|
|
}
|
|
|
|
@Post('/enable')
|
|
async activateMFA(req: MFA.Activate) {
|
|
const { token = null } = req.body;
|
|
const { id, mfaEnabled } = req.user;
|
|
|
|
const { decryptedSecret: secret, decryptedRecoveryCodes: recoveryCodes } =
|
|
await this.mfaService.getSecretAndRecoveryCodes(id);
|
|
|
|
if (!token) throw new BadRequestError('Token is required to enable MFA feature');
|
|
|
|
if (mfaEnabled) throw new BadRequestError('MFA already enabled');
|
|
|
|
if (!secret || !recoveryCodes.length) {
|
|
throw new BadRequestError('Cannot enable MFA without generating secret and recovery codes');
|
|
}
|
|
|
|
const verified = this.mfaService.totp.verifySecret({ secret, token, window: 10 });
|
|
|
|
if (!verified)
|
|
throw new BadRequestError('MFA token expired. Close the modal and enable MFA again', 997);
|
|
|
|
await this.mfaService.enableMfa(id);
|
|
}
|
|
|
|
@Delete('/disable')
|
|
async disableMFA(req: AuthenticatedRequest) {
|
|
const { id } = req.user;
|
|
|
|
await this.mfaService.disableMfa(id);
|
|
}
|
|
|
|
@Post('/verify')
|
|
async verifyMFA(req: MFA.Verify) {
|
|
const { id } = req.user;
|
|
const { token } = req.body;
|
|
|
|
const { decryptedSecret: secret } = await this.mfaService.getSecretAndRecoveryCodes(id);
|
|
|
|
if (!token) throw new BadRequestError('Token is required to enable MFA feature');
|
|
|
|
if (!secret) throw new BadRequestError('No MFA secret se for this user');
|
|
|
|
const verified = this.mfaService.totp.verifySecret({ secret, token });
|
|
|
|
if (!verified) throw new BadRequestError('MFA secret could not be verified');
|
|
}
|
|
}
|