fix: fully mask web secret previews

This commit is contained in:
CJACK
2026-04-26 00:10:59 +08:00
parent 131ca7d398
commit f1ba805173
12 changed files with 123 additions and 24 deletions

View File

@@ -1,12 +1,15 @@
import { X } from 'lucide-react'
import { v4 as uuidv4 } from 'uuid'
import { maskSecret } from '../../utils/maskSecret'
export default function AddKeyModal({ show, t, editingKey, newKey, setNewKey, loading, onClose, onAdd }) {
if (!show) {
return null
}
const isEditing = Boolean(editingKey?.key)
const displayKey = isEditing ? maskSecret(editingKey?.key || newKey.key) : newKey.key
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4 animate-in fade-in">
@@ -25,7 +28,7 @@ export default function AddKeyModal({ show, t, editingKey, newKey, setNewKey, lo
type="text"
className={isEditing ? "input-field bg-muted/30 flex-1 cursor-not-allowed" : "input-field bg-[#09090b] flex-1"}
placeholder={isEditing ? t('accountManager.keyReadonlyPlaceholder') : t('accountManager.newKeyPlaceholder')}
value={newKey.key}
value={displayKey}
onChange={e => setNewKey({ ...newKey, key: e.target.value })}
autoFocus={!isEditing}
readOnly={isEditing}

View File

@@ -2,6 +2,8 @@ import { useState } from 'react'
import { Check, ChevronDown, Copy, Pencil, Plus, Trash2 } from 'lucide-react'
import clsx from 'clsx'
import { maskSecret } from '../../utils/maskSecret'
function fallbackCopyText(text) {
const textArea = document.createElement('textarea')
textArea.value = text
@@ -102,7 +104,7 @@ export default function ApiKeysPanel({
className="font-mono text-sm bg-muted/50 px-3 py-1 rounded inline-block hover:bg-muted transition-colors"
title={t('accountManager.copyKeyTitle')}
>
{(item.key || '').slice(0, 16)}****
{maskSecret(item.key)}
</button>
<div className="text-sm text-muted-foreground truncate">{item.remark || '-'}</div>
{copiedKey === item.key && (

View File

@@ -10,6 +10,8 @@ import {
} from 'lucide-react'
import clsx from 'clsx'
import { maskSecret } from '../../utils/maskSecret'
export default function ConfigPanel({
t,
configExpanded,
@@ -40,6 +42,7 @@ export default function ConfigPanel({
}
const selectedModel = models.find(m => m.id === model) || models[0]
const SelectedModelIcon = selectedModel ? (iconMap[selectedModel.icon] || MessageSquare) : MessageSquare
const defaultKeyPreview = maskSecret(config.keys?.[0])
return (
<div className={clsx(
@@ -158,7 +161,7 @@ export default function ConfigPanel({
autoComplete="off"
spellCheck={false}
className="w-full h-10 px-3 bg-muted/30 border border-border rounded-lg text-sm font-mono placeholder:text-muted-foreground/40 focus:outline-none focus:ring-1 focus:ring-ring focus:border-ring transition-all"
placeholder={config.keys?.[0] ? t('apiTester.apiKeyDefault', { suffix: config.keys[0].slice(-6) }) : t('apiTester.apiKeyPlaceholder')}
placeholder={defaultKeyPreview ? t('apiTester.apiKeyDefault', { preview: defaultKeyPreview }) : t('apiTester.apiKeyPlaceholder')}
value={apiKey}
onChange={e => setApiKey(e.target.value)}
/>

View File

@@ -238,7 +238,7 @@
"accountSelector": "Account",
"autoRandom": "🤖 Auto / Random",
"apiKeyOptional": "API Key (optional)",
"apiKeyDefault": "Default: ...{suffix}",
"apiKeyDefault": "Default: {preview}",
"apiKeyPlaceholder": "Enter a custom key",
"modeManaged": "Managed key mode (uses account pool).",
"modeDirect": "Direct token mode (requires a valid DeepSeek token).",

View File

@@ -238,7 +238,7 @@
"accountSelector": "选择账号",
"autoRandom": "🤖 自动 / 随机",
"apiKeyOptional": "API 密钥 (可选)",
"apiKeyDefault": "默认: ...{suffix}",
"apiKeyDefault": "默认: {preview}",
"apiKeyPlaceholder": "输入自定义密钥",
"modeManaged": "当前使用托管 key 模式(会走账号池)。",
"modeDirect": "当前使用直通 token 模式(需填写有效 DeepSeek token。",

View File

@@ -0,0 +1,10 @@
export function maskSecret(secret) {
const value = String(secret ?? '')
if (!value) {
return ''
}
if (value.length <= 4) {
return '*'.repeat(value.length)
}
return `${value.slice(0, 2)}****${value.slice(-2)}`
}