Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit 77f62b9

Browse files
committed
refactor: 💡 refact recovery code
1 parent 2eeb376 commit 77f62b9

File tree

5 files changed

+37
-32
lines changed

5 files changed

+37
-32
lines changed

src/public/app/widgets/type_widgets/options/multi_factor_authentication.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ interface TOTPStatus {
126126

127127
interface RecoveryKeysResponse {
128128
success: boolean;
129-
recoveryCodes?: string;
129+
recoveryCodes?: string[];
130130
keysExist?: boolean;
131-
usedRecoveryCodes?: string;
131+
usedRecoveryCodes?: string[];
132132
}
133133

134134
export default class MultiFactorAuthenticationOptions extends OptionsWidget {
@@ -231,6 +231,7 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
231231
}
232232

233233
const usedResult = await server.get<RecoveryKeysResponse>("totp_recovery/used");
234+
234235
if (usedResult.usedRecoveryCodes) {
235236
this.keyFiller(usedResult.usedRecoveryCodes);
236237
this.$generateRecoveryCodeButton.text(t("multi_factor_authentication.recovery_keys_regenerate"));
@@ -239,14 +240,19 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
239240
}
240241
}
241242

242-
private keyFiller(values: string) {
243-
const keys = values.split(',').slice(0, 8);
244-
243+
private keyFiller(values: string[]) {
245244
this.fillKeys("");
246245

247-
keys.forEach((key, index) => {
248-
if (index < 8 && key && typeof key === 'string') {
249-
this.$recoveryKeys[index].text(key.trim());
246+
values.forEach((key, index) => {
247+
if (typeof key === 'string') {
248+
const date = new Date(key.replace(/\//g, '-'));
249+
if (isNaN(date.getTime())) {
250+
this.$recoveryKeys[index].text(key);
251+
} else {
252+
this.$recoveryKeys[index].text(t("multi_factor_authentication.recovery_keys_used", { date: key.replace(/\//g, '-') }));
253+
}
254+
} else {
255+
this.$recoveryKeys[index].text(t("multi_factor_authentication.recovery_keys_unused", { index: key }));
250256
}
251257
});
252258
}

src/public/translations/cn/translation.json

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

src/public/translations/en/translation.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,8 @@
13331333
"recovery_keys_no_key_set": "No recovery codes set",
13341334
"recovery_keys_generate": "Generate Recovery Codes",
13351335
"recovery_keys_regenerate": "Regenerate Recovery Codes",
1336+
"recovery_keys_used": "Used: {{date}}",
1337+
"recovery_keys_unused": "Recovery code {{index}} is unused",
13361338
"oauth_title": "OAuth/OpenID",
13371339
"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.",
13381340
"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.",

src/routes/api/recovery_codes.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Request } from 'express';
33
import { randomBytes } from 'crypto';
44

55
function setRecoveryCodes(req: Request) {
6-
const success = recovery_codes.setRecoveryCodes(req.body.recoveryCodes);
6+
const success = recovery_codes.setRecoveryCodes(req.body.recoveryCodes.join(','));
77
return { success: success, message: 'Recovery codes set!' };
88
}
99

@@ -31,15 +31,28 @@ function generateRecoveryCodes() {
3131
randomBytes(16).toString('base64')
3232
];
3333

34-
recovery_codes.setRecoveryCodes(recoveryKeys.toString());
34+
recovery_codes.setRecoveryCodes(recoveryKeys.join(','));
3535

36-
return { success: true, recoveryCodes: recoveryKeys.toString() };
36+
return { success: true, recoveryCodes: recoveryKeys };
3737
}
3838

3939
function getUsedRecoveryCodes() {
40+
if (!recovery_codes.isRecoveryCodeSet()) {
41+
return []
42+
}
43+
44+
const dateRegex = RegExp(/^\d{4}\/\d{2}\/\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/gm);
45+
const recoveryCodes = recovery_codes.getRecoveryCodes();
46+
const usedStatus: string[] = [];
47+
48+
recoveryCodes.forEach((recoveryKey: string) => {
49+
if (dateRegex.test(recoveryKey)) usedStatus.push(recoveryKey);
50+
else usedStatus.push(recoveryCodes.indexOf(recoveryKey));
51+
});
52+
4053
return {
4154
success: true,
42-
usedRecoveryCodes: recovery_codes.getUsedRecoveryCodes().toString()
55+
usedRecoveryCodes: usedStatus
4356
};
4457
}
4558

src/services/encryption/recovery_codes.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict';
2-
31
import sql from '../sql.js';
42
import optionService from '../options.js';
53
import crypto from 'crypto';
@@ -26,7 +24,7 @@ function setRecoveryCodes(recoveryCodes: string) {
2624

2725
function getRecoveryCodes() {
2826
if (!isRecoveryCodeSet()) {
29-
return Array(8).fill("Keys not set")
27+
return []
3028
}
3129

3230
return sql.transactional(() => {
@@ -67,25 +65,9 @@ function verifyRecoveryCode(recoveryCodeGuess: string) {
6765
return loginSuccess;
6866
}
6967

70-
function getUsedRecoveryCodes() {
71-
if (!isRecoveryCodeSet()) {
72-
return Array(8).fill("Recovery code not set")
73-
}
74-
75-
const dateRegex = RegExp(/^\d{4}\/\d{2}\/\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/gm);
76-
const recoveryCodes = getRecoveryCodes();
77-
const usedStatus: string[] = [];
78-
79-
recoveryCodes.forEach((recoveryKey: string) => {
80-
if (dateRegex.test(recoveryKey)) usedStatus.push('Used: ' + recoveryKey);
81-
else usedStatus.push('Recovery code ' + recoveryCodes.indexOf(recoveryKey) + ' is unused');
82-
});
83-
return usedStatus;
84-
}
85-
8668
export default {
8769
setRecoveryCodes,
70+
getRecoveryCodes,
8871
verifyRecoveryCode,
89-
getUsedRecoveryCodes,
9072
isRecoveryCodeSet
9173
};

0 commit comments

Comments
 (0)