Files
ewoooc/routes
OoO d276853e54 fix(post-3.5g): restore _is_authorized fail-closed for callback + message (CRIT-2 + HIGH-3)
從 4349db2~1 撈回 _is_authorized() 並重新套用到 callback 與 message handler。

問題:
- CRIT-2 (callback fail-open):原本只擋 group/supergroup 不匹配,
  private chat 任何人都能觸發 callback 指令(按鈕 menu/await/cmd)。
- HIGH-3 (message short-circuit fail):`if ALLOWED_USERS and _uid not in ALLOWED_USERS`
  在 OPENCLAW_ALLOWED_USERS 環境變數未設時 → ALLOWED_USERS 為空 set →
  `if False and ...` 整段不執行 → 所有 private 訊息都通過。

修法(fail-closed 三檢查):
1. 在頂部 import 區下方還原 `_is_authorized(chat_type, chat_id, user_id)`:
   - group/supergroup:chat_id 必須等於 ALLOWED_GROUP
   - private:user_id 必須在 ALLOWED_USERS(空 set → 全拒)
   - channel / 未知 / 缺欄位 → 拒絕
2. callback handler 替換為 `if not _is_authorized(chat_type, chat_id, cq_from_id)`
   並從 cq.get('from') 取 user_id(之前完全沒取)。
3. message handler 替換為統一檢查,未授權回 403 + 靜默(不回 Telegram 避免偵察)。

驗證:
- AST parse OK
- 模擬測試:999999 私訊 → False;111(在白名單)私訊 → True;
  錯誤群組 → False;channel → False;None → False
- grep 結果:剩下兩處 `_is_authorized` 呼叫(callback 5195, message 5255),
  舊的 `ALLOWED_USERS and _uid not in ALLOWED_USERS` 已移除(只留註解描述歷史)。

Critic findings: CRIT-2 + HIGH-3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:40:22 +08:00
..

路由模組重構說明

概述

此目錄包含從 app.py 中分離出來的路由模組,採用 Flask Blueprint 架構。 這樣的模組化設計使得代碼更易於維護、測試和擴展。

模組清單

模組 狀態 說明 路由
system_routes.py 獨立 系統管理 /settings, /logs, /health, /metrics, /api/backup
edm_routes.py 獨立 EDM 儀表板 /edm, /festival
monthly_routes.py 獨立 月結分析 /monthly_summary_analysis, /api/monthly_summary_data
dashboard_routes.py 獨立 首頁看板 /, /brand_assets
daily_sales_routes.py 獨立 當日業績 /daily_sales, /daily_sales/export*
api_routes.py 已獨立 通用 API /api/run_task, /api/history/*
export_routes.py 已獨立 匯出功能 /api/export/*
import_routes.py 已獨立 匯入功能 /api/import_excel, /api/import/monthly_summary
sales_routes.py 延遲導入 業績分析 /sales_analysis, /growth_analysis, /api/sales_analysis/*

啟用模組

方法一:修改 config.py推薦

config.py 中設定 USE_MODULAR_ROUTES

USE_MODULAR_ROUTES = {
    'system': True,      # 啟用 system_routes
    'edm': True,         # 啟用 edm_routes
    'monthly': True,     # 啟用 monthly_routes
    'dashboard': True,   # 啟用 dashboard_routes
    'daily_sales': True, # 啟用 daily_sales_routes
    'api': False,        # api_routes 有依賴,暫不啟用
    'export': False,     # export_routes 有依賴,暫不啟用
    'import': False,     # import_routes 有依賴,暫不啟用
    'sales': False,      # sales_routes 有依賴,暫不啟用
}

方法二:環境變數

可透過環境變數覆蓋設定(待實作)。

運作原理

  1. Blueprint 註冊routes/__init__.py 中的 register_blueprints() 函數根據 USE_MODULAR_ROUTES 設定決定是否註冊各模組的 Blueprint。

  2. 重複路由清理app.py 底部的 cleanup_duplicate_routes() 函數會在所有路由定義完成後,移除與已啟用 Blueprint 重複的路由。

  3. 優先順序Blueprint 中的路由優先於 app.py 中的同名路由。

模組依賴說明

完全獨立模組

  • system_routes.py
  • edm_routes.py
  • monthly_routes.py
  • dashboard_routes.py - 提供 get_consolidated_data, get_dashboard_stats
  • daily_sales_routes.py

這些模組不依賴 app.py 中的任何函數或變數。

已獨立化的模組(使用 services 或其他獨立模組)

  • api_routes.py
    • 使用 services/task_runner.py 中的 run_momo_task_with_notification
    • 使用 routes/dashboard_routes.py 中的 get_dashboard_stats
  • export_routes.py
    • 使用 routes/dashboard_routes.py 中的 get_consolidated_data
    • 使用 services/cache_service.py 中的快取變數
  • import_routes.py
    • 使用 services/cache_service.py 中的 _SALES_DF_CACHE, _SALES_PROCESSED_CACHE

延遲導入模組

  • sales_routes.py
    • growth_analysis() 已完全獨立
    • 其他 API 函數使用延遲導入從 app.py 取得(函數邏輯複雜,暫不遷移)

開發指南

新增路由模組

  1. routes/ 目錄建立新的 Python 檔案
  2. 定義 Blueprint
    from flask import Blueprint
    my_bp = Blueprint('my_module', __name__)
    
  3. routes/__init__.py 中加入註冊邏輯
  4. config.pyUSE_MODULAR_ROUTES 中加入控制項

從 app.py 遷移路由

  1. 複製路由函數到對應模組
  2. @app.route 改為 @blueprint_name.route
  3. 確認所有導入正確
  4. 測試模組可正常導入:python -c "from routes.xxx import xxx_bp"
  5. MODULAR_ENDPOINTS 中記錄端點名稱

測試

測試模組是否正常運作:

# 測試所有模組導入
python -c "
from routes.system_routes import system_bp
from routes.edm_routes import edm_bp
from routes.monthly_routes import monthly_bp
from routes.dashboard_routes import dashboard_bp
from routes.daily_sales_routes import daily_sales_bp
print('All independent modules imported successfully!')
"

# 測試完整應用(啟用所有獨立模組)
python -c "
import config
config.USE_MODULAR_ROUTES = {
    'system': True, 'edm': True, 'monthly': True,
    'dashboard': True, 'daily_sales': True,
    'api': False, 'export': False, 'import': False, 'sales': False
}
import app
print(f'Total routes: {len(list(app.app.url_map.iter_rules()))}')
"

已知問題

模板 url_for 端點名稱問題

當啟用模組化路由時,端點名稱會從 'index' 變為 'dashboard.index'。 這會導致模板中的 url_for('index') 調用失敗。

影響範圍

  • index.html 中的 url_for('index', ...)
  • edm_dashboard.html 中的 url_for('edm_dashboard', ...)

解決方案(需在啟用模組前執行):

  1. 在模板中使用完整的端點名稱,如 url_for('dashboard.index', ...)
  2. 或在 app.py 中加入端點別名

注意:目前預設所有模組皆禁用(USE_MODULAR_ROUTES 全為 False 因此不會影響正常使用。此問題只會在手動啟用模組後出現。

變更歷史

  • 2026-01-18
    • 初始版本,建立 9 個路由模組5 個獨立模組可直接啟用
    • 修復 url_for 端點名稱問題,新增 register_endpoint_aliases() 函數
    • 處理有依賴的模組 (api, export, import),使用獨立 services 模組
    • 新增 services/task_runner.py 封裝爬蟲任務執行邏輯
    • 所有 9 個模組已啟用並通過測試