refactor: 💡 refact recovery code

This commit is contained in:
Jin 2025-03-29 01:40:17 +01:00
parent 2eeb376d24
commit 77f62b94cc
5 changed files with 37 additions and 32 deletions

View File

@ -126,9 +126,9 @@ interface TOTPStatus {
interface RecoveryKeysResponse {
success: boolean;
recoveryCodes?: string;
recoveryCodes?: string[];
keysExist?: boolean;
usedRecoveryCodes?: string;
usedRecoveryCodes?: string[];
}
export default class MultiFactorAuthenticationOptions extends OptionsWidget {
@ -231,6 +231,7 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
}
const usedResult = await server.get<RecoveryKeysResponse>("totp_recovery/used");
if (usedResult.usedRecoveryCodes) {
this.keyFiller(usedResult.usedRecoveryCodes);
this.$generateRecoveryCodeButton.text(t("multi_factor_authentication.recovery_keys_regenerate"));
@ -239,14 +240,19 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
}
}
private keyFiller(values: string) {
const keys = values.split(',').slice(0, 8);
private keyFiller(values: string[]) {
this.fillKeys("");
keys.forEach((key, index) => {
if (index < 8 && key && typeof key === 'string') {
this.$recoveryKeys[index].text(key.trim());
values.forEach((key, index) => {
if (typeof key === 'string') {
const date = new Date(key.replace(/\//g, '-'));
if (isNaN(date.getTime())) {
this.$recoveryKeys[index].text(key);
} else {
this.$recoveryKeys[index].text(t("multi_factor_authentication.recovery_keys_used", { date: key.replace(/\//g, '-') }));
}
} else {
this.$recoveryKeys[index].text(t("multi_factor_authentication.recovery_keys_unused", { index: key }));
}
});
}

View File

@ -1322,6 +1322,8 @@
"recovery_keys_no_key_set": "未设置恢复代码",
"recovery_keys_generate": "生成恢复代码",
"recovery_keys_regenerate": "重新生成恢复代码",
"recovery_keys_used": "已使用: {{date}}",
"recovery_keys_unused": "恢复代码 {{index}} 未使用",
"oauth_title": "OAuth/OpenID 认证",
"oauth_description": "OpenID 是一种标准化方式,允许您使用其他服务(如 Google的账户登录网站以验证您的身份。请参阅这些 <a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">指南</a> 通过 Google 设置 OpenID 服务。",
"oauth_description_warning": "要启用 OAuth/OpenID您需要设置 config.ini 文件中的 OAuth/OpenID 基础 URL、客户端 ID 和客户端密钥,并重新启动应用程序。",

View File

@ -1333,6 +1333,8 @@
"recovery_keys_no_key_set": "No recovery codes set",
"recovery_keys_generate": "Generate Recovery Codes",
"recovery_keys_regenerate": "Regenerate Recovery Codes",
"recovery_keys_used": "Used: {{date}}",
"recovery_keys_unused": "Recovery code {{index}} is unused",
"oauth_title": "OAuth/OpenID",
"oauth_description": "OpenID is a standardized way to let you log into websites using an account from another service, like Google, to verify your identity. Follow these <a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">instructions</a> to setup an OpenID service through Google.",
"oauth_description_warning": "To enable OAuth/OpenID, you need to set the OAuth/OpenID base URL, client ID and client secret in the config.ini file and restart the application.",

View File

@ -3,7 +3,7 @@ import type { Request } from 'express';
import { randomBytes } from 'crypto';
function setRecoveryCodes(req: Request) {
const success = recovery_codes.setRecoveryCodes(req.body.recoveryCodes);
const success = recovery_codes.setRecoveryCodes(req.body.recoveryCodes.join(','));
return { success: success, message: 'Recovery codes set!' };
}
@ -31,15 +31,28 @@ function generateRecoveryCodes() {
randomBytes(16).toString('base64')
];
recovery_codes.setRecoveryCodes(recoveryKeys.toString());
recovery_codes.setRecoveryCodes(recoveryKeys.join(','));
return { success: true, recoveryCodes: recoveryKeys.toString() };
return { success: true, recoveryCodes: recoveryKeys };
}
function getUsedRecoveryCodes() {
if (!recovery_codes.isRecoveryCodeSet()) {
return []
}
const dateRegex = RegExp(/^\d{4}\/\d{2}\/\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/gm);
const recoveryCodes = recovery_codes.getRecoveryCodes();
const usedStatus: string[] = [];
recoveryCodes.forEach((recoveryKey: string) => {
if (dateRegex.test(recoveryKey)) usedStatus.push(recoveryKey);
else usedStatus.push(recoveryCodes.indexOf(recoveryKey));
});
return {
success: true,
usedRecoveryCodes: recovery_codes.getUsedRecoveryCodes().toString()
usedRecoveryCodes: usedStatus
};
}

View File

@ -1,5 +1,3 @@
'use strict';
import sql from '../sql.js';
import optionService from '../options.js';
import crypto from 'crypto';
@ -26,7 +24,7 @@ function setRecoveryCodes(recoveryCodes: string) {
function getRecoveryCodes() {
if (!isRecoveryCodeSet()) {
return Array(8).fill("Keys not set")
return []
}
return sql.transactional(() => {
@ -67,25 +65,9 @@ function verifyRecoveryCode(recoveryCodeGuess: string) {
return loginSuccess;
}
function getUsedRecoveryCodes() {
if (!isRecoveryCodeSet()) {
return Array(8).fill("Recovery code not set")
}
const dateRegex = RegExp(/^\d{4}\/\d{2}\/\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/gm);
const recoveryCodes = getRecoveryCodes();
const usedStatus: string[] = [];
recoveryCodes.forEach((recoveryKey: string) => {
if (dateRegex.test(recoveryKey)) usedStatus.push('Used: ' + recoveryKey);
else usedStatus.push('Recovery code ' + recoveryCodes.indexOf(recoveryKey) + ' is unused');
});
return usedStatus;
}
export default {
setRecoveryCodes,
getRecoveryCodes,
verifyRecoveryCode,
getUsedRecoveryCodes,
isRecoveryCodeSet
};