From 5213def150c01c7e6948d40b6f50cf90fd3e4d29 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 30 May 2026 21:12:41 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Telegram=20Bot=20Token=20=E8=84=B1?= =?UTF-8?q?=E6=95=8F=20=E2=80=94=20GET=20=E4=B8=8D=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=98=8E=E6=96=87=EF=BC=8Creveal=20=E9=9C=80=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 SENSITIVE_KEYS 加入 telegram_bot_token,GET /settings/ 返回脱敏值 - 新增 POST /settings/telegram/reveal-token 端点(密码验证 + 审计日志) - 前端 Telegram 区域独立渲染:Bot Token 默认脱敏,点"显示"需输入密码 - Chat ID 保持可编辑(非敏感信息) - Bot Token 输入框默认 type=password,可切换明文/遮掩 --- server/api/settings.py | 36 +++++++++++++++++++++++- web/app/settings.html | 63 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/server/api/settings.py b/server/api/settings.py index f9af35f1..f1dd499a 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -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), diff --git a/web/app/settings.html b/web/app/settings.html index 40a29354..d1201db1 100644 --- a/web/app/settings.html +++ b/web/app/settings.html @@ -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=` +
+ + ${tokenMasked + ?`${esc(tokenVal)} + ` + :` + + ` + } +
+
+ + + +
`; + } + + 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=` + + + + `; + _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(){