Spaces:
Sleeping
Sleeping
File size: 9,077 Bytes
7d67a69 51c5a22 7d67a69 51c5a22 97a461c 51c5a22 97a461c 51c5a22 7d67a69 51c5a22 7d67a69 14640e3 7d67a69 14640e3 7d67a69 14640e3 7d67a69 14640e3 7d67a69 fb04509 97a461c fb04509 7d67a69 14640e3 7d67a69 14640e3 7d67a69 14640e3 7d67a69 97a461c 51c5a22 7d67a69 51c5a22 7d67a69 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | # 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) |