Major Features: - ✅ Multi-tenant architecture (tenant isolation) - ✅ Employee CRUD with lifecycle management (onboarding/offboarding) - ✅ Department tree structure with email domain management - ✅ Company info management (single-record editing) - ✅ System functions CRUD (permission management) - ✅ Email account management (multi-account per employee) - ✅ Keycloak SSO integration (auth.lab.taipei) - ✅ Redis session storage (10.1.0.254:6379) - Solves Cookie 4KB limitation - Cross-system session sharing - Sliding expiration (8 hours) - Automatic token refresh Technical Stack: Backend: - FastAPI + SQLAlchemy - PostgreSQL 16 (10.1.0.20:5433) - Keycloak Admin API integration - Docker Mailserver integration (SSH) - Alembic migrations Frontend: - Next.js 14 (App Router) - NextAuth 4 with Keycloak Provider - Redis session storage (ioredis) - Tailwind CSS Infrastructure: - Redis 7 (10.1.0.254:6379) - Session + Cache - Keycloak 26.1.0 (auth.lab.taipei) - Docker Mailserver (10.1.0.254) Architecture Highlights: - Session管理由 Keycloak + Redis 統一控制 - 支援多系統 (HR/WebMail/Calendar/Drive/Office) 共享 session - Token 自動刷新,異質服務整合 - 未來可無縫遷移到雲端 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
212 lines
5.5 KiB
Python
212 lines
5.5 KiB
Python
"""
|
|
密碼產生與驗證工具
|
|
"""
|
|
import secrets
|
|
import string
|
|
import re
|
|
import bcrypt
|
|
|
|
|
|
def generate_secure_password(length: int = 16) -> str:
|
|
"""
|
|
產生安全的隨機密碼
|
|
|
|
Args:
|
|
length: 密碼長度(預設 16 字元)
|
|
|
|
Returns:
|
|
安全的隨機密碼
|
|
|
|
範例:
|
|
>>> pwd = generate_secure_password()
|
|
>>> len(pwd)
|
|
16
|
|
>>> validate_password_strength(pwd)
|
|
True
|
|
"""
|
|
if length < 8:
|
|
raise ValueError("密碼長度至少需要 8 個字元")
|
|
|
|
# 字元集合
|
|
lowercase = string.ascii_lowercase
|
|
uppercase = string.ascii_uppercase
|
|
digits = string.digits
|
|
special = "!@#$%^&*()-_=+[]{}|;:,.<>?"
|
|
|
|
# 確保至少包含每種類型各一個
|
|
password = [
|
|
secrets.choice(lowercase),
|
|
secrets.choice(uppercase),
|
|
secrets.choice(digits),
|
|
secrets.choice(special)
|
|
]
|
|
|
|
# 剩餘字元隨機選擇
|
|
all_chars = lowercase + uppercase + digits + special
|
|
password += [secrets.choice(all_chars) for _ in range(length - 4)]
|
|
|
|
# 打亂順序
|
|
secrets.SystemRandom().shuffle(password)
|
|
|
|
return ''.join(password)
|
|
|
|
|
|
def hash_password(password: str) -> str:
|
|
"""
|
|
使用 bcrypt 加密密碼
|
|
|
|
Args:
|
|
password: 明文密碼
|
|
|
|
Returns:
|
|
加密後的密碼 hash
|
|
"""
|
|
password_bytes = password.encode('utf-8')
|
|
salt = bcrypt.gensalt()
|
|
hashed = bcrypt.hashpw(password_bytes, salt)
|
|
return hashed.decode('utf-8')
|
|
|
|
|
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
"""
|
|
驗證密碼
|
|
|
|
Args:
|
|
plain_password: 明文密碼
|
|
hashed_password: 加密密碼
|
|
|
|
Returns:
|
|
是否匹配
|
|
"""
|
|
return bcrypt.checkpw(
|
|
plain_password.encode('utf-8'),
|
|
hashed_password.encode('utf-8')
|
|
)
|
|
|
|
|
|
def validate_password_strength(password: str) -> tuple[bool, list[str]]:
|
|
"""
|
|
驗證密碼強度
|
|
|
|
Args:
|
|
password: 待驗證的密碼
|
|
|
|
Returns:
|
|
(是否通過, 錯誤訊息列表)
|
|
|
|
範例:
|
|
>>> validate_password_strength("weak")
|
|
(False, ['密碼長度至少需要 8 個字元', ...])
|
|
>>> validate_password_strength("Strong@Pass123")
|
|
(True, [])
|
|
"""
|
|
errors = []
|
|
|
|
# 長度檢查
|
|
if len(password) < 8:
|
|
errors.append("密碼長度至少需要 8 個字元")
|
|
|
|
# 大寫字母
|
|
if not re.search(r'[A-Z]', password):
|
|
errors.append("密碼必須包含至少一個大寫字母")
|
|
|
|
# 小寫字母
|
|
if not re.search(r'[a-z]', password):
|
|
errors.append("密碼必須包含至少一個小寫字母")
|
|
|
|
# 數字
|
|
if not re.search(r'\d', password):
|
|
errors.append("密碼必須包含至少一個數字")
|
|
|
|
# 特殊符號
|
|
if not re.search(r'[!@#$%^&*()\-_=+\[\]{}|;:,.<>?]', password):
|
|
errors.append("密碼必須包含至少一個特殊符號")
|
|
|
|
# 常見弱密碼檢查
|
|
common_weak_passwords = [
|
|
'password', 'password123', '12345678', 'qwerty',
|
|
'admin123', 'letmein', 'welcome', 'monkey'
|
|
]
|
|
if password.lower() in common_weak_passwords:
|
|
errors.append("此密碼過於常見,請使用更安全的密碼")
|
|
|
|
return (len(errors) == 0, errors)
|
|
|
|
|
|
def validate_password_for_user(
|
|
password: str,
|
|
username: str = None,
|
|
name: str = None,
|
|
email: str = None
|
|
) -> tuple[bool, list[str]]:
|
|
"""
|
|
驗證密碼(包含使用者資訊檢查)
|
|
|
|
Args:
|
|
password: 待驗證的密碼
|
|
username: 使用者帳號
|
|
name: 使用者姓名
|
|
email: Email
|
|
|
|
Returns:
|
|
(是否通過, 錯誤訊息列表)
|
|
"""
|
|
# 先檢查基本強度
|
|
is_valid, errors = validate_password_strength(password)
|
|
|
|
# 檢查是否包含使用者資訊
|
|
password_lower = password.lower()
|
|
|
|
if username and username.lower() in password_lower:
|
|
errors.append("密碼不可包含帳號名稱")
|
|
|
|
if name:
|
|
name_parts = name.split()
|
|
for part in name_parts:
|
|
if len(part) >= 3 and part.lower() in password_lower:
|
|
errors.append("密碼不可包含姓名")
|
|
break
|
|
|
|
if email:
|
|
email_user = email.split('@')[0]
|
|
if len(email_user) >= 3 and email_user.lower() in password_lower:
|
|
errors.append("密碼不可包含 Email 使用者名稱")
|
|
|
|
return (len(errors) == 0, errors)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# 測試密碼產生
|
|
print("=== 密碼產生測試 ===")
|
|
for i in range(5):
|
|
pwd = generate_secure_password()
|
|
is_valid, errors = validate_password_strength(pwd)
|
|
print(f"密碼 {i+1}: {pwd} - 有效: {is_valid}")
|
|
|
|
# 測試密碼驗證
|
|
print("\n=== 密碼驗證測試 ===")
|
|
test_cases = [
|
|
("weak", False),
|
|
("WeakPass", False),
|
|
("WeakPass123", False),
|
|
("Strong@Pass123", True),
|
|
("admin@Pass123", True)
|
|
]
|
|
|
|
for password, expected in test_cases:
|
|
is_valid, errors = validate_password_strength(password)
|
|
status = "✓" if is_valid == expected else "✗"
|
|
print(f"{status} {password}: {is_valid}")
|
|
if errors:
|
|
for error in errors:
|
|
print(f" - {error}")
|
|
|
|
# 測試加密與驗證
|
|
print("\n=== 密碼加密測試 ===")
|
|
plain_pwd = "TestPassword@123"
|
|
hashed = hash_password(plain_pwd)
|
|
print(f"明文密碼: {plain_pwd}")
|
|
print(f"加密密碼: {hashed}")
|
|
print(f"驗證正確密碼: {verify_password(plain_pwd, hashed)}")
|
|
print(f"驗證錯誤密碼: {verify_password('WrongPassword', hashed)}")
|