From 288dc4b085468d4b6c8fbb0b4bac4f22daee7fb0 Mon Sep 17 00:00:00 2001 From: smail Date: Tue, 6 Oct 2026 11:08:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=89=E5=85=A8=E7=A0=81?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=96=87=E4=BB=B6=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/recoveryCodeDownload.js | 15 +++++++++++++-- .../SecuritySettings/SecuritySettings.js | 2 +- test/frontend/security-code-save.test.mjs | 15 +++++++++++---- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/frontend/src/utils/recoveryCodeDownload.js b/frontend/src/utils/recoveryCodeDownload.js index c67805d..5395da5 100644 --- a/frontend/src/utils/recoveryCodeDownload.js +++ b/frontend/src/utils/recoveryCodeDownload.js @@ -1,4 +1,15 @@ -export function downloadRecoveryCodes(codes, documentRef = document, urlApi = URL) { +export function buildRecoveryCodesFilename(username) { + const normalizedUsername = String(username || '') + .trim() + .replace(/[<>:"/\\|?*\u0000-\u001F]/g, '-') + .replace(/^[. -]+/g, '') + .replace(/-{2,}/g, '-') + .replace(/[. ]+$/g, '') + + return `smail-${normalizedUsername || 'recovery-codes'}.txt` +} + +export function downloadRecoveryCodes(codes, username, documentRef = document, urlApi = URL) { if (!Array.isArray(codes) || codes.length === 0) return false const blob = new Blob([`${codes.join('\n')}\n`], { @@ -8,7 +19,7 @@ export function downloadRecoveryCodes(codes, documentRef = document, urlApi = UR const anchor = documentRef.createElement('a') anchor.href = objectUrl - anchor.download = 'smail-recovery-codes.txt' + anchor.download = buildRecoveryCodesFilename(username) anchor.style.display = 'none' documentRef.body.appendChild(anchor) diff --git a/frontend/src/views/components/SecuritySettings/SecuritySettings.js b/frontend/src/views/components/SecuritySettings/SecuritySettings.js index a1789d2..bb0c3b6 100644 --- a/frontend/src/views/components/SecuritySettings/SecuritySettings.js +++ b/frontend/src/views/components/SecuritySettings/SecuritySettings.js @@ -300,7 +300,7 @@ export function usePage() { const saveRecoveryCodes = () => { if (!generatedCodes.value.length) return - downloadRecoveryCodes(generatedCodes.value) + downloadRecoveryCodes(generatedCodes.value, globalStatus.userInfos.account) notify('安全码已保存到本地文件,文件不会上传服务器', 'success') } diff --git a/test/frontend/security-code-save.test.mjs b/test/frontend/security-code-save.test.mjs index be1e2c9..92e916c 100644 --- a/test/frontend/security-code-save.test.mjs +++ b/test/frontend/security-code-save.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict' import test from 'node:test' import { readFileSync } from 'node:fs' -import { downloadRecoveryCodes } from '../../frontend/src/utils/recoveryCodeDownload.js' +import { buildRecoveryCodesFilename, downloadRecoveryCodes } from '../../frontend/src/utils/recoveryCodeDownload.js' import { RECOVERY_CODE_DISPLAY_MS } from '../../frontend/src/utils/recoveryCodeDisplay.js' const securitySettingsSource = readFileSync(new URL('../../frontend/src/views/components/SecuritySettings/SecuritySettings.js', import.meta.url), 'utf8') @@ -50,7 +50,7 @@ test('没有安全码时不生成下载文件', () => { revokeObjectURL() {}, } - assert.equal(downloadRecoveryCodes([], fixture.documentRef, urlApi), false) + assert.equal(downloadRecoveryCodes([], 'xuans', fixture.documentRef, urlApi), false) assert.equal(created, 0) assert.equal(fixture.anchors.length, 0) }) @@ -70,14 +70,21 @@ test('安全码只在浏览器本地生成下载,不产生后端请求', async } assert.equal( - downloadRecoveryCodes(['ABCD-1234', 'EFGH-5678'], fixture.documentRef, urlApi), + downloadRecoveryCodes(['ABCD-1234', 'EFGH-5678'], 'xuans', fixture.documentRef, urlApi), true ) assert.equal(fixture.anchors.length, 1) assert.equal(fixture.anchors[0].href, 'blob:recovery-codes') - assert.equal(fixture.anchors[0].download, 'smail-recovery-codes.txt') + assert.equal(fixture.anchors[0].download, 'smail-xuans.txt') assert.equal(fixture.anchors[0].clicked, true) assert.equal(fixture.anchors[0].removed, true) assert.deepEqual(revoked, ['blob:recovery-codes']) assert.equal(await blobs[0].text(), 'ABCD-1234\nEFGH-5678\n') }) + +test('导出文件名使用用户名并清理非法路径字符', () => { + assert.equal(buildRecoveryCodesFilename('xuans'), 'smail-xuans.txt') + assert.equal(buildRecoveryCodesFilename('user@example.com'), 'smail-user@example.com.txt') + assert.equal(buildRecoveryCodesFilename('../xuans/secret'), 'smail-xuans-secret.txt') + assert.equal(buildRecoveryCodesFilename(''), 'smail-recovery-codes.txt') +})