Files
ewoooc/services/password_service.py
ogt 1b4f3a7bbe
Some checks failed
CD Pipeline / deploy (push) Failing after 59s
feat: EwoooC 初始化 — 完整專案推版至 Gitea
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml)
- 部署模式: rsync Python 檔案至 188 → docker restart (volume mount)
- Dockerfile/requirements 變動時自動重建 Docker image
- 部署通知: Telegram (開始/成功/失敗)
- 健康檢查: https://mo.wooo.work/health (最多 5 次重試)
- 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 01:21:13 +08:00

180 lines
4.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
密碼服務模組
提供:
- 密碼複雜度驗證
- 密碼雜湊與驗證
- 密碼過期檢查
"""
import re
from datetime import datetime, timedelta
from werkzeug.security import generate_password_hash, check_password_hash
from config import (
PASSWORD_MIN_LENGTH,
PASSWORD_REQUIRE_UPPERCASE,
PASSWORD_REQUIRE_LOWERCASE,
PASSWORD_REQUIRE_DIGIT,
PASSWORD_REQUIRE_SPECIAL,
PASSWORD_SPECIAL_CHARS,
PASSWORD_EXPIRY_DAYS,
)
def validate_password_complexity(password):
"""
驗證密碼複雜度
根據 config.py 中的設定檢查密碼是否符合要求:
- 最小長度
- 大寫字母
- 小寫字母
- 數字
- 特殊符號
Args:
password: 要驗證的密碼
Returns:
tuple: (is_valid, error_message)
- is_valid: 是否符合要求
- error_message: 錯誤訊息(符合時為 None
"""
if not password:
return False, "密碼不能為空"
errors = []
# 檢查長度
if len(password) < PASSWORD_MIN_LENGTH:
errors.append(f"密碼長度至少需要 {PASSWORD_MIN_LENGTH} 個字元")
# 檢查大寫字母
if PASSWORD_REQUIRE_UPPERCASE and not re.search(r'[A-Z]', password):
errors.append("密碼必須包含至少一個大寫字母")
# 檢查小寫字母
if PASSWORD_REQUIRE_LOWERCASE and not re.search(r'[a-z]', password):
errors.append("密碼必須包含至少一個小寫字母")
# 檢查數字
if PASSWORD_REQUIRE_DIGIT and not re.search(r'\d', password):
errors.append("密碼必須包含至少一個數字")
# 檢查特殊符號
if PASSWORD_REQUIRE_SPECIAL:
# 轉義特殊字元用於正則表達式
escaped_chars = re.escape(PASSWORD_SPECIAL_CHARS)
if not re.search(f'[{escaped_chars}]', password):
errors.append(f"密碼必須包含至少一個特殊符號 ({PASSWORD_SPECIAL_CHARS})")
if errors:
return False, "".join(errors)
return True, None
def get_password_requirements():
"""
取得密碼複雜度要求說明
Returns:
list: 密碼要求的說明列表
"""
requirements = [f"至少 {PASSWORD_MIN_LENGTH} 個字元"]
if PASSWORD_REQUIRE_UPPERCASE:
requirements.append("至少一個大寫字母 (A-Z)")
if PASSWORD_REQUIRE_LOWERCASE:
requirements.append("至少一個小寫字母 (a-z)")
if PASSWORD_REQUIRE_DIGIT:
requirements.append("至少一個數字 (0-9)")
if PASSWORD_REQUIRE_SPECIAL:
requirements.append(f"至少一個特殊符號 ({PASSWORD_SPECIAL_CHARS})")
return requirements
def hash_password(password):
"""
將密碼進行雜湊處理
Args:
password: 明文密碼
Returns:
str: 雜湊後的密碼
"""
return generate_password_hash(password, method='pbkdf2:sha256')
def verify_password(password_hash, password):
"""
驗證密碼是否正確
Args:
password_hash: 資料庫中儲存的雜湊密碼
password: 用戶輸入的明文密碼
Returns:
bool: 密碼是否正確
"""
return check_password_hash(password_hash, password)
def is_password_expired(password_changed_at):
"""
檢查密碼是否已過期
Args:
password_changed_at: 密碼上次變更時間 (datetime 或 None)
Returns:
tuple: (is_expired, days_until_expiry)
- is_expired: 是否已過期
- days_until_expiry: 距離過期還有幾天(已過期時為負數)
"""
# 如果沒有設定過期天數0 或 None則永不過期
if not PASSWORD_EXPIRY_DAYS:
return False, None
# 如果沒有密碼變更時間,視為需要立即變更
if not password_changed_at:
return True, 0
expiry_date = password_changed_at + timedelta(days=PASSWORD_EXPIRY_DAYS)
now = datetime.now()
days_until_expiry = (expiry_date - now).days
if days_until_expiry < 0:
return True, days_until_expiry
else:
return False, days_until_expiry
def get_password_expiry_warning(password_changed_at, warning_days=14):
"""
取得密碼過期警告訊息
Args:
password_changed_at: 密碼上次變更時間
warning_days: 幾天前開始警告
Returns:
str or None: 警告訊息,若不需警告則為 None
"""
is_expired, days_until = is_password_expired(password_changed_at)
if is_expired:
if days_until is None:
return None # 永不過期
return f"您的密碼已過期,請立即變更密碼。"
if days_until is not None and days_until <= warning_days:
return f"您的密碼將在 {days_until} 天後過期,請及早變更密碼。"
return None
print("✅ Password service 已載入")