BrianChuan
新增更多使用者與管理者帳號,調整API呼叫行為,mail內文調整
97a461c
Raw
History Blame Contribute Delete
9.08 kB
# auth.py
# 此檔案負責身份驗證相關功能
# 包含登入處理、會話管理、角色切換等
import datetime
import threading
import secrets
from config import USER_DB, FIXED_TEST_EMAIL
from models import record_log, global_stats
from utils import get_current_time
import gradio as gr
# --- Identity verification session cache ---
# key: user_email
# value: { status(active/verifying/blocked), pending_token, anomaly_timestamp }
session_cache = {}
session_cache_lock = threading.Lock()
def _get_or_create_session_entry(user_email: str):
"""獲取或創建會話條目"""
user_email = user_email.strip().replace("\xa0", " ")
with session_cache_lock:
if user_email not in session_cache:
session_cache[user_email] = {
"status": "active",
"pending_token": None,
"anomaly_timestamp": None,
}
return session_cache[user_email]
def check_session_timeouts(timeout_seconds=300):
"""檢查會話超時,如果 verifying 超過 timeout_seconds 則改為 blocked"""
now = datetime.datetime.now()
with session_cache_lock:
for user_email, entry in session_cache.items():
if entry.get("status") == "verifying" and entry.get("anomaly_timestamp"):
try:
# 嘗試解析多種可能的格式
ts_str = entry["anomaly_timestamp"]
try:
ts = datetime.datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S")
except ValueError:
# 處理可能帶有時區或其他格式的情況
import pandas as pd
ts = pd.to_datetime(ts_str).to_pydatetime().replace(tzinfo=None)
if (now - ts).total_seconds() > timeout_seconds:
entry["status"] = "blocked"
entry["pending_token"] = None
print(f"⏰ [Timeout] User {user_email} status changed to blocked due to verification timeout.")
except Exception as e:
print(f"❌ [Timeout Check Error] {e}")
def get_user_status(user_email: str):
"""獲取用戶當前狀態"""
user_email = user_email.strip().replace("\xa0", " ")
with session_cache_lock:
if user_email not in session_cache:
return "active"
return session_cache[user_email].get("status", "active")
def unblock_user(user_email: str):
"""手動解封用戶(以帳戶名稱作為 key)"""
user_email = user_email.strip().replace("\xa0", " ")
with session_cache_lock:
if user_email in session_cache:
session_cache[user_email]["status"] = "active"
session_cache[user_email]["pending_token"] = None
session_cache[user_email]["anomaly_timestamp"] = None
return True
else:
# 就算 session_cache 中沒有此 key,也視為解鎖成功(帳戶原本就是 active)
return True
def _verify_token_to_user(token: str):
"""根據 token 驗證用戶"""
token = token.strip()
with session_cache_lock:
for user_email, entry in session_cache.items():
if entry.get("pending_token") == token:
return user_email
return None
def process_login(ip, cookie, account, password, captcha, time_mode, custom_time):
"""處理登入邏輯"""
def fail_response(msg, df_all, df_abn, inp_upd):
return gr.update(selected="login_route"), gr.update(), msg, gr.update(), df_all, df_abn, inp_upd
if account not in USER_DB:
df_all, df_abn, inp_upd = record_log(ip, cookie, account, "Unknown", "login_attempt", "failed_no_account", time_mode, custom_time)
return fail_response("帳號不存在!", df_all, df_abn, inp_upd)
role, name = USER_DB[account][1], USER_DB[account][2]
if captcha.upper() != "FEIK":
df_all, df_abn, inp_upd = record_log(ip, cookie, account, role, "login_attempt", "failed_captcha", time_mode, custom_time)
return fail_response("驗證碼錯誤!", df_all, df_abn, inp_upd)
if USER_DB[account][0] == password:
df_all, df_abn, inp_upd = record_log(ip, cookie, account, role, "login_attempt", "success", time_mode, custom_time)
return gr.update(selected="dashboard_route"), gr.update(selected="student_tab" if role == "學生" else "admin_tab"), "", f"**{name}** 歡迎您 | 線上人數: 938", df_all, df_abn, inp_upd
else:
df_all, df_abn, inp_upd = record_log(ip, cookie, account, role, "login_attempt", "failed_password", time_mode, custom_time)
return fail_response("密碼錯誤!", df_all, df_abn, inp_upd)
def internal_process_login(ip, cookie, account, password, captcha, time_mode, custom_time):
"""內部處理登入邏輯,回傳 JSON 友善格式"""
if account not in USER_DB:
df_all, df_abn, inp_upd = record_log(ip, cookie, account, "Unknown", "login_attempt", "failed_no_account", time_mode, custom_time)
return {"status": "error", "message": "帳號不存在!"}
if get_user_status(account) == "blocked":
df_all, df_abn, inp_upd = record_log(ip, cookie, account, USER_DB[account][1], "login_attempt", "failed_blocked", time_mode, custom_time)
return {"status": "error", "message": "此帳號已被封鎖,無法登入!"}
role, name = USER_DB[account][1], USER_DB[account][2]
if captcha.upper() != "FEIK":
df_all, df_abn, inp_upd = record_log(ip, cookie, account, role, "login_attempt", "failed_captcha", time_mode, custom_time)
return {"status": "error", "message": "驗證碼錯誤!"}
if USER_DB[account][0] == password:
df_all, df_abn, inp_upd = record_log(ip, cookie, account, role, "login_attempt", "success", time_mode, custom_time)
return {
"status": "success",
"role": "admin" if role == "管理" else "student",
"name": name,
"message": "登入成功"
}
else:
df_all, df_abn, inp_upd = record_log(ip, cookie, account, role, "login_attempt", "failed_password", time_mode, custom_time)
return {"status": "error", "message": "密碼錯誤!"}
def internal_logout(ip, cookie, account, time_mode, custom_time):
"""內部處理登出邏輯"""
role = USER_DB.get(account, ["", "Unknown"])[1]
record_log(ip, cookie, account, role, "logout", "success", time_mode, custom_time)
return {"status": "success", "message": "已登出"}
def fast_login(target_account, ip, cookie, time_mode, custom_time):
"""快速登入"""
role, name = USER_DB[target_account][1], USER_DB[target_account][2]
df_all, df_abn, inp_upd = record_log(ip, cookie, target_account, role, "login_attempt", "success", time_mode, custom_time)
target_tab = "student_tab" if role == "學生" else "admin_tab"
return gr.update(selected="dashboard_route"), gr.update(selected=target_tab), "", f"**{name}** 歡迎您 | 線上人數: 938 (⚡快速登入模式)", df_all, df_abn, inp_upd, target_account
def toggle_role(current_account, ip, cookie, time_mode, custom_time):
"""切換角色"""
target_account = "student" if current_account == "admin" else "admin"
role, name = USER_DB[target_account][1], USER_DB[target_account][2]
df_all, df_abn, inp_upd = record_log(ip, cookie, target_account, role, "login_attempt", "success", time_mode, custom_time)
return gr.update(selected="student_tab" if role == "學生" else "admin_tab"), f"**{name}** 歡迎您 | 線上人數: 938 (⚡單鍵切換模式)", df_all, df_abn, inp_upd, target_account
def logout(ip, cookie, account, time_mode, custom_time):
"""登出"""
role = USER_DB.get(account, ["", "Unknown"])[1]
df_all, df_abn, inp_upd = record_log(ip, cookie, account, role, "logout", "success", time_mode, custom_time)
return gr.update(selected="login_route"), "", "", "", "", df_all, df_abn, inp_upd
def simulate_click(ip, cookie, account, button_name, time_mode, custom_time):
"""模擬點擊操作"""
# 以帳戶名稱查詢封鎖狀態(不再使用全域 FIXED_TEST_EMAIL 作為 key)
status = get_user_status(account)
if status == "verifying":
gr.Info("偵測到異常,請於5分鐘內確認 Email 以繼續使用")
return gr.update(), gr.update(), gr.update()
elif status == "blocked":
gr.Warning("帳號已被封鎖,請聯絡系統管理員或寄驗證信進行解封")
return gr.update(), gr.update(), gr.update()
role = USER_DB.get(account, ["", "Unknown"])[1]
return record_log(ip, cookie, account, role, button_name, "success", time_mode, custom_time)
def test_anomaly(ip, cookie, account, action_name, test_time_str):
"""測試異常偵測"""
# 測試異常時不阻擋,以便觀察結果
role = USER_DB.get(account, ["", "Unknown"])[1]
return record_log(ip, cookie, account, role, action_name, "success", "自訂時間 (模擬過去/未來)", test_time_str)