Files
Automata/packages/cli/src/Mfa/mfa.service.ts
कारतोफ्फेलस्क्रिप्ट™ 5887ed6498 refactor(core): Extract all Auth-related User columns into a separate entity (#9557)
Co-authored-by: Ricardo Espinoza <ricardo@n8n.io>
2024-05-31 09:40:19 +02:00

90 lines
2.9 KiB
TypeScript

import { v4 as uuid } from 'uuid';
import { Service } from 'typedi';
import { Cipher } from 'n8n-core';
import { AuthUserRepository } from '@db/repositories/authUser.repository';
import { TOTPService } from './totp.service';
@Service()
export class MfaService {
constructor(
private authUserRepository: AuthUserRepository,
public totp: TOTPService,
private cipher: Cipher,
) {}
generateRecoveryCodes(n = 10) {
return Array.from(Array(n)).map(() => uuid());
}
async saveSecretAndRecoveryCodes(userId: string, secret: string, recoveryCodes: string[]) {
const { encryptedSecret, encryptedRecoveryCodes } = this.encryptSecretAndRecoveryCodes(
secret,
recoveryCodes,
);
const user = await this.authUserRepository.findOneByOrFail({ id: userId });
user.mfaSecret = encryptedSecret;
user.mfaRecoveryCodes = encryptedRecoveryCodes;
await this.authUserRepository.save(user);
}
encryptSecretAndRecoveryCodes(rawSecret: string, rawRecoveryCodes: string[]) {
const encryptedSecret = this.cipher.encrypt(rawSecret),
encryptedRecoveryCodes = rawRecoveryCodes.map((code) => this.cipher.encrypt(code));
return {
encryptedRecoveryCodes,
encryptedSecret,
};
}
private decryptSecretAndRecoveryCodes(mfaSecret: string, mfaRecoveryCodes: string[]) {
return {
decryptedSecret: this.cipher.decrypt(mfaSecret),
decryptedRecoveryCodes: mfaRecoveryCodes.map((code) => this.cipher.decrypt(code)),
};
}
async getSecretAndRecoveryCodes(userId: string) {
const { mfaSecret, mfaRecoveryCodes } = await this.authUserRepository.findOneByOrFail({
id: userId,
});
return this.decryptSecretAndRecoveryCodes(mfaSecret ?? '', mfaRecoveryCodes ?? []);
}
async validateMfa(
userId: string,
mfaToken: string | undefined,
mfaRecoveryCode: string | undefined,
) {
const user = await this.authUserRepository.findOneByOrFail({ id: userId });
if (mfaToken) {
const decryptedSecret = this.cipher.decrypt(user.mfaSecret!);
return this.totp.verifySecret({ secret: decryptedSecret, token: mfaToken });
} else if (mfaRecoveryCode) {
const validCodes = user.mfaRecoveryCodes.map((code) => this.cipher.decrypt(code));
const index = validCodes.indexOf(mfaRecoveryCode);
if (index === -1) return false;
// remove used recovery code
validCodes.splice(index, 1);
user.mfaRecoveryCodes = validCodes.map((code) => this.cipher.encrypt(code));
await this.authUserRepository.save(user);
return true;
}
return false;
}
async enableMfa(userId: string) {
const user = await this.authUserRepository.findOneByOrFail({ id: userId });
user.mfaEnabled = true;
return await this.authUserRepository.save(user);
}
async disableMfa(userId: string) {
const user = await this.authUserRepository.findOneByOrFail({ id: userId });
user.mfaEnabled = false;
user.mfaSecret = null;
user.mfaRecoveryCodes = [];
return await this.authUserRepository.save(user);
}
}