Skip to content

Add plugin MFA Authenticator v1.3.0#143

Open
b521626 wants to merge 1 commit intoZToolsCenter:mainfrom
b521626:plugin/ztools-mfa-authenticator
Open

Add plugin MFA Authenticator v1.3.0#143
b521626 wants to merge 1 commit intoZToolsCenter:mainfrom
b521626:plugin/ztools-mfa-authenticator

Conversation

@b521626
Copy link
Copy Markdown

@b521626 b521626 commented Apr 28, 2026

插件信息

  • 名称: MFA Authenticator
  • 插件ID: ztools-mfa-authenticator
  • 版本: 1.3.0
  • 描述: TOTP/HOTP/Steam 验证码生成器,支持智能填充、自动解锁、密钥查看保护、主密码管理、实时倒计时(环形/条形切换)、下一周期预览、拼音搜索(含双拼方案)、置顶排序、拖拽排序、一键复制。支持手动添加、otpauth:// URI 导入、二维码图片识别导入、批量导入,以及 TXT/CSV/JSON/Excel 文件导入导出与加密同步。
  • 作者: alexliang
  • 类型: 新增

本次变更

Added

  • Steam Guard 令牌支持(5 位字母数字验证码)
  • 智能填充:输入发行方自动匹配预设参数(GitHub、Google、AWS、Steam 等 17+ 服务)
  • 条形倒计时指示器,可与环形指示器一键切换
  • 双拼搜索:支持自然码、小鹤、拼音加加、微软、搜狗 5 种方案
  • 二维码图片识别导入

Changed

  • 倒计时指示器支持环形/条形切换,偏好自动保存
  • 搜索支持全拼与双拼混合匹配

截图 / 演示

自检清单

  • plugin.json 的 name / title / version / description / author 字段均已检查
  • 已移除调试日志、未使用文件、敏感信息(.env、token、密钥等)
  • 本次 PR 的 diff 仅涉及 plugins/ztools-mfa-authenticator/ 目录
  • 已在本地 ZTools 客户端实际加载并测试过此插件,主要功能正常
  • 同意以仓库声明的开源协议发布此插件

此 PR 由 ztools-plugin-cli 自动管理:每次 ztools publish 在分支上追加一个 commit,PR 链接保持不变。

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the ztools-mfa-authenticator plugin, a feature-rich MFA manager supporting TOTP, HOTP, and Steam Guard protocols with capabilities for QR code recognition and Pinyin-based searching. The code review highlights significant security vulnerabilities, specifically the insecure co-location of encryption keys and data for the auto-unlock feature and the inclusion of a vulnerable version of the xlsx library. Further feedback addresses robustness and performance, noting a lack of error handling in storage parsing, inefficient Base64 encoding for large buffers, and logic errors in the CSV and TXT import implementations.

Comment on lines +12 to +33
async function save(password: string): Promise<void> {
const rawKey = crypto.getRandomValues(new Uint8Array(32))
const aesKey = await crypto.subtle.importKey('raw', rawKey, 'AES-GCM', false, [
'encrypt',
])

const iv = crypto.getRandomValues(new Uint8Array(12))
const encoded = new TextEncoder().encode(password)
const cipherBuf = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, aesKey, encoded)

const toB64 = (buf: ArrayBuffer) => {
const bytes = new Uint8Array(buf)
let bin = ''
for (let i = 0; i < bytes.byteLength; i++) bin += String.fromCharCode(bytes[i])
return btoa(bin)
}

window.ztools.dbStorage.setItem(KEY_STORAGE, toB64(rawKey.buffer as ArrayBuffer))
window.ztools.dbStorage.setItem(
DATA_STORAGE,
JSON.stringify({ ciphertext: toB64(cipherBuf), iv: toB64(iv.buffer as ArrayBuffer) }),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

“记住密码”功能的安全性设计存在缺陷。代码虽然使用了 AES-GCM 加密主密码,但加密密钥 (auto_unlock_key) 与密文 (auto_unlock_data) 被存储在同一个本地存储中。这在安全性上等同于明文存储,任何能访问插件存储的人都可以轻松解密。如果平台不提供硬件级别的安全存储(如 Electron 的 safeStorage),建议在文档中明确告知用户此风险,或考虑使用更安全的派生方式。

"jsqr": "^1.4.0",
"otpauth": "^9.3.0",
"pinyin-match": "^1.2.10",
"vue": "^3.5.0",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

xlsx (SheetJS) 0.18.5 版本较旧且存在已知安全漏洞(如 CVE-2023-30535)。建议迁移到官方维护的 @sheetjs/xlsx 最新版本以提高安全性。

return
}

const ids: string[] = JSON.parse(raw)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

JSON.parse(raw) 在此处缺少错误处理。如果本地存储的数据损坏或被手动篡改,调用此方法会抛出异常并导致插件初始化失败(白屏)。建议增加 try-catch 块以提高健壮性。

    let ids: string[] = []
    try {
      ids = JSON.parse(raw)
    } catch (e) {
      console.error('[MFA] Failed to parse account IDs:', e)
      accounts.value = []
      return
    }

Comment on lines +73 to +80
export function toBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer)
let binary = ''
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i])
}
return btoa(binary)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

当前的 toBase64 实现使用字符串累加,在处理较大 buffer(如导出的 Excel 文件)时性能非常低下,且可能导致长时间阻塞主线程。建议使用分块处理(Chunking)的方式来优化性能。

Suggested change
export function toBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer)
let binary = ''
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i])
}
return btoa(binary)
}
export function toBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer)
let binary = ''
const len = bytes.byteLength
for (let i = 0; i < len; i += 8192) {
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192) as any)
}
return btoa(binary)
}

Comment on lines +113 to +129
const parseCsvLine = (line: string): string[] => {
const result: string[] = []
let current = ''
let inQuotes = false
for (const ch of line) {
if (ch === '"') {
inQuotes = !inQuotes
} else if (ch === ',' && !inQuotes) {
result.push(current.trim())
current = ''
} else {
current += ch
}
}
result.push(current.trim())
return result
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

CSV 解析逻辑存在两个问题:

  1. 无法正确处理转义的双引号(例如 "" 应解析为单个 ")。
  2. 在第 110 行预先使用 split('\n') 会导致包含换行符的引用字段被错误拆分。
    建议改进解析逻辑以符合 RFC 4180 标准。
  const parseCsvLine = (line: string): string[] => {
    const result: string[] = []
    let current = ''
    let inQuotes = false
    for (let i = 0; i < line.length; i++) {
      const ch = line[i]
      if (ch === '"') {
        if (inQuotes && line[i + 1] === '"') {
          current += '"'
          i++
        } else {
          inQuotes = !inQuotes
        }
      } else if (ch === ',' && !inQuotes) {
        result.push(current.trim())
        current = ''
      } else {
        current += ch
      }
    }
    result.push(current.trim())
    return result
  }

.map((line) => line.trim())
.filter(Boolean)
.map((line) => {
const [issuer, label, secret, algorithm, digits, period, type, counter] = line.split(':')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

TXT 导入使用 split(':') 分隔字段。由于 issuerlabel 字段中很可能包含冒号(例如 Google:user@gmail.com),这种简单的拆分会导致解析错位。建议使用更健壮的分隔逻辑或限制字段内容。

@b521626
Copy link
Copy Markdown
Author

b521626 commented Apr 28, 2026

image image image

@b521626 b521626 marked this pull request as ready for review April 28, 2026 03:42
@lzx8589561
Copy link
Copy Markdown
Contributor

执行npm build后dist目录构建的不是完整的插件,有些文件需要放到public中,可以参考ai-helper插件

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants