feat(recovery): add support for hashed recovery key on the server

+ legacy
This commit is contained in:
Fabrice Mouhartem 2026-06-12 14:44:50 +02:00
parent 9808cf25c1
commit b04325fdc0
No known key found for this signature in database

View File

@ -12,6 +12,7 @@ const Sessions = require("../storage/sessions");
const BlockStore = require("../storage/block");
const Block = require("../commands/block");
const config = require("../load-config");
const Nacl = require('tweetnacl/nacl-fast');
const Commands = module.exports;
@ -37,6 +38,17 @@ var isValidRecoveryKey = otp => {
/[A-Za-z0-9+\/]{32}/.test(otp);
};
const parseRecoveryKey = recoveryStr => {
let splitted = recoveryStr.split(':');
let type = splitted[0];
// Only allow authorized type. To be upgraded when supporting more recovery modes
if (!['secret', 'hash'].includes(type)) {
type = '';
}
const content = splitted.slice(1).join(':');
return { type, content };
};
// we'll only allow users to set up multi-factor auth
// for keypairs they control which already have blocks
// this check doesn't confirm that their id is valid base64
@ -383,12 +395,24 @@ const revoke = Commands.TOTP_REVOKE = function (Env, body, cb) {
}).nThen(function (w) {
if (!recoveryKey) { return; }
w.abort();
if (!/^secret:/.test(recoveryStored)) {
const recoveryParsed = parseRecoveryKey(recoveryStored);
if (!recoveryParsed.type) {
return void cb("E_NO_RECOVERY_KEY");
}
recoveryStored = recoveryStored.slice(7);
if (recoveryKey !== recoveryStored) {
return void cb("E_WRONG_RECOVERY_KEY");
switch (recoveryParsed.type) {
case 'secret':
if (recoveryKey !== recoveryParsed.content) {
return void cb("E_WRONG_RECOVERY_KEY");
}
break;
case 'hash':
if (Util.encodeBase64(Nacl.hash(Util.decodeBase64(recoveryKey)))
!== recoveryParsed.content) {
return void cb("E_WRONG_RECOVERY_KEY");
}
break;
default:
return void cb("E_WRONG_RECOVERY_KEY");
}
cb();
}).nThen(function () {