fix: Telegram Bot Token 脱敏 — GET 不返回明文,reveal 需密码验证

- 后端 SENSITIVE_KEYS 加入 telegram_bot_token,GET /settings/ 返回脱敏值
- 新增 POST /settings/telegram/reveal-token 端点(密码验证 + 审计日志)
- 前端 Telegram 区域独立渲染:Bot Token 默认脱敏,点"显示"需输入密码
- Chat ID 保持可编辑(非敏感信息)
- Bot Token 输入框默认 type=password,可切换明文/遮掩
This commit is contained in:
Your Name
2026-05-30 21:12:41 +08:00
parent 5166660f3a
commit 5213def150
2 changed files with 97 additions and 2 deletions
+35 -1
View File
@@ -27,7 +27,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
IMMUTABLE_KEYS = {"secret_key", "api_key", "encryption_key", "database_url"}
# Settings whose values are masked in GET responses
SENSITIVE_KEYS = {"secret_key", "api_key", "encryption_key"}
SENSITIVE_KEYS = {"secret_key", "api_key", "encryption_key", "telegram_bot_token"}
router = APIRouter(prefix="/api/settings", tags=["settings"])
@@ -702,6 +702,40 @@ async def telegram_test_send(
raise HTTPException(status_code=502, detail="Telegram API 返回错误,请检查 Bot Token 和 Chat ID")
@router.post("/telegram/reveal-token", response_model=dict)
async def reveal_telegram_token(
payload: ApiKeyRevealRequest,
request: Request,
admin: Admin = Depends(get_current_admin),
db: AsyncSession = Depends(get_db),
):
"""Reveal Telegram Bot Token after re-entering account password."""
from server.infrastructure.database.admin_repo import AdminRepositoryImpl
admin_repo = AdminRepositoryImpl(db)
current = await admin_repo.get_by_id(admin.id)
if not current or not bcrypt.checkpw(
payload.current_password.encode(),
current.password_hash.encode(),
):
raise HTTPException(status_code=400, detail="当前密码错误")
repo = SettingRepositoryImpl(db)
value = await repo.get("telegram_bot_token")
if value is None:
raise HTTPException(status_code=404, detail="telegram_bot_token not found")
audit_repo = AuditLogRepositoryImpl(db)
await audit_repo.create(AuditLog(
admin_username=admin.username,
action="reveal_telegram_token",
target_type="setting",
detail="查看 Telegram Bot Token",
ip_address=request.client.host if request.client else "",
))
return {"key": "telegram_bot_token", "value": value}
@router.get("/telegram/chats", response_model=dict)
async def telegram_get_chats(
admin: Admin = Depends(get_current_admin),
+62 -1
View File
@@ -283,7 +283,7 @@
brand:{keys:['system_name','system_title'],labels:{system_name:'系统名称',system_title:'页面标题'}},
alert:{keys:['cpu_alert_threshold','mem_alert_threshold','disk_alert_threshold'],labels:{cpu_alert_threshold:'CPU 告警阈值',mem_alert_threshold:'内存告警阈值',disk_alert_threshold:'磁盘告警阈值'}},
infra:{keys:['api_base_url','db_pool_size','db_max_overflow','heartbeat_timeout','redis_url'],labels:{api_base_url:'主站对外 URL',db_pool_size:'连接池大小',db_max_overflow:'最大溢出',heartbeat_timeout:'心跳超时(秒)',redis_url:'Redis URL'}},
telegram:{keys:['telegram_bot_token','telegram_chat_id'],labels:{telegram_bot_token:'Bot Token',telegram_chat_id:'Chat ID'}},
// Telegram rendered separately — Bot Token needs reveal-with-password
};
let _allSettings={};
let _currentAdminId=null;
@@ -319,6 +319,9 @@
// API Key section (read-only + copy)
renderApiKeySection();
// Telegram section (Bot Token masked + reveal, Chat ID editable)
renderTelegramSection();
// Notification toggles
renderNotifyToggles(_allSettings);
@@ -350,6 +353,64 @@
}
}
function renderTelegramSection(){
const tokenVal=_allSettings['telegram_bot_token']||'';
const chatVal=_allSettings['telegram_chat_id']||'';
const tokenMasked=tokenVal.includes('***')||tokenVal.includes('...');
const el=document.getElementById('telegramSection');
el.innerHTML=`
<div class="flex items-center gap-3" id="telegramTokenRow">
<label class="text-slate-400 text-sm w-36 shrink-0">Bot Token</label>
${tokenMasked
?`<span id="tgTokenDisplay" class="flex-1 px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg text-slate-500 text-sm font-mono select-none">${esc(tokenVal)}</span>
<button onclick="revealTelegramToken()" id="tgTokenRevealBtn" class="px-3 py-2 bg-slate-800 hover:bg-slate-700 text-white text-xs rounded-lg shrink-0 transition">显示</button>`
:`<input id="set_telegram_bot_token" value="${esc(tokenVal)}" type="password" class="flex-1 px-3 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white text-sm font-mono">
<button type="button" onclick="toggleTgTokenVis()" id="tgTokenToggleBtn" class="px-3 py-2 bg-slate-800 hover:bg-slate-700 text-white text-xs rounded-lg shrink-0 transition">显示</button>
<button type="button" data-setting-key="telegram_bot_token" class="save-setting-btn px-3 py-2 bg-brand hover:bg-brand-dark text-white text-xs rounded-lg shrink-0 transition">保存</button>`
}
</div>
<div class="flex items-center gap-3">
<label class="text-slate-400 text-sm w-36 shrink-0">Chat ID</label>
<input id="set_telegram_chat_id" value="${esc(chatVal)}" class="flex-1 px-3 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white text-sm">
<button type="button" data-setting-key="telegram_chat_id" class="save-setting-btn px-3 py-2 bg-brand hover:bg-brand-dark text-white text-xs rounded-lg shrink-0 transition">保存</button>
</div>`;
}
async function revealTelegramToken(){
const pwd=prompt('请输入当前管理员密码以查看 Bot Token');
if(!pwd)return;
try{
const r=await apiFetch(API+'/settings/telegram/reveal-token',{
method:'POST',
headers:apiHeadersJSON(),
body:JSON.stringify({current_password:pwd}),
});
if(!r)return;
const data=await r.json();
const realToken=data.value||'';
// Replace masked display with editable input
const row=document.getElementById('telegramTokenRow');
row.innerHTML=`
<label class="text-slate-400 text-sm w-36 shrink-0">Bot Token</label>
<input id="set_telegram_bot_token" value="${esc(realToken)}" type="password" class="flex-1 px-3 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white text-sm font-mono">
<button type="button" onclick="toggleTgTokenVis()" id="tgTokenToggleBtn" class="px-3 py-2 bg-slate-800 hover:bg-slate-700 text-white text-xs rounded-lg shrink-0 transition">显示</button>
<button type="button" data-setting-key="telegram_bot_token" class="save-setting-btn px-3 py-2 bg-brand hover:bg-brand-dark text-white text-xs rounded-lg shrink-0 transition">保存</button>`;
_allSettings['telegram_bot_token']=realToken;
toast('success','Bot Token 已显示');
}catch(e){}
}
function toggleTgTokenVis(){
const input=document.getElementById('set_telegram_bot_token');
const btn=document.getElementById('tgTokenToggleBtn');
if(!input)return;
if(input.type==='password'){
input.type='text';btn.textContent='隐藏';
}else{
input.type='password';btn.textContent='显示';
}
}
let _apiKeyRaw='';
async function toggleApiKeyVisibility(){