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:
+35
-1
@@ -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
@@ -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(){
|
||||
|
||||
Reference in New Issue
Block a user