Notes/src/services/encryption/recovery_codes.ts

73 lines
2.4 KiB
TypeScript

import crypto from 'crypto';
import optionService from '../options.js';
import sql from '../sql.js';
function isRecoveryCodeSet() {
return optionService.getOptionBool('encryptedRecoveryCodes');
}
function setRecoveryCodes(recoveryCodes: string) {
const iv = crypto.randomBytes(16);
const securityKey = crypto.randomBytes(32);
const cipher = crypto.createCipheriv('aes-256-cbc', securityKey, iv);
const encryptedRecoveryCodes = cipher.update(recoveryCodes, 'utf-8', 'hex');
sql.transactional(() => {
optionService.setOption('recoveryCodeInitialVector', iv.toString('hex'));
optionService.setOption('recoveryCodeSecurityKey', securityKey.toString('hex'));
optionService.setOption('recoveryCodesEncrypted', encryptedRecoveryCodes + cipher.final('hex'));
optionService.setOption('encryptedRecoveryCodes', 'true');
return true;
});
return false;
}
function getRecoveryCodes() {
if (!isRecoveryCodeSet()) {
return []
}
return sql.transactional<string[]>(() => {
const iv = Buffer.from(optionService.getOption('recoveryCodeInitialVector'), 'hex');
const securityKey = Buffer.from(optionService.getOption('recoveryCodeSecurityKey'), 'hex');
const encryptedRecoveryCodes = optionService.getOption('recoveryCodesEncrypted');
const decipher = crypto.createDecipheriv('aes-256-cbc', securityKey, iv);
const decryptedData = decipher.update(encryptedRecoveryCodes, 'hex', 'utf-8');
const decryptedString = decryptedData + decipher.final('utf-8');
return decryptedString.split(',');
});
}
function removeRecoveryCode(usedCode: string) {
const oldCodes = getRecoveryCodes();
const today = new Date();
oldCodes[oldCodes.indexOf(usedCode)] = today.toJSON().replace(/-/g, '/');
setRecoveryCodes(oldCodes.toString());
}
function verifyRecoveryCode(recoveryCodeGuess: string) {
const recoveryCodeRegex = RegExp(/^.{22}==$/gm);
if (!recoveryCodeRegex.test(recoveryCodeGuess)) {
return false;
}
const recoveryCodes = getRecoveryCodes();
let loginSuccess = false;
recoveryCodes.forEach((recoveryCode) => {
if (recoveryCodeGuess === recoveryCode) {
removeRecoveryCode(recoveryCode);
loginSuccess = true;
return;
}
});
return loginSuccess;
}
export default {
setRecoveryCodes,
getRecoveryCodes,
verifyRecoveryCode,
isRecoveryCodeSet
};