初次提交

This commit is contained in:
2025-10-14 20:05:29 +08:00
commit 6e4e48fdd2
673 changed files with 437006 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
from datetime import datetime, timedelta
from typing import Optional, Union
from jose import JWTError, jwt
import bcrypt
from app.core.config import settings
from app.core.token_blacklist import token_blacklist
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""创建访问令牌"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=settings.JWT_EXPIRE_MINUTES)
to_encode.update({"exp": expire, "type": "access"})
encoded_jwt = jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM)
return encoded_jwt
def create_refresh_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""创建刷新令牌"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(days=settings.JWT_REFRESH_EXPIRE_DAYS)
to_encode.update({"exp": expire, "type": "refresh"})
encoded_jwt = jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM)
return encoded_jwt
def verify_token(token: str, token_type: str = "access") -> Optional[dict]:
"""验证令牌"""
try:
# 首先检查令牌是否在黑名单中
if token_blacklist.is_blacklisted(token):
return None
payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
# 检查令牌类型
if payload.get("type") != token_type:
return None
return payload
except JWTError:
return None
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""验证密码"""
try:
return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
except:
return False
def get_password_hash(password: str) -> str:
"""获取密码哈希"""
# bcrypt 限制密码长度为72字节如果超过则截断
if len(password.encode('utf-8')) > 72:
password = password.encode('utf-8')[:72].decode('utf-8', errors='ignore')
salt = bcrypt.gensalt()
return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')
def create_password_reset_token(email: str) -> str:
"""创建密码重置令牌"""
delta = timedelta(hours=1) # 1小时有效期
now = datetime.utcnow()
expires = now + delta
exp = expires.timestamp()
encoded_jwt = jwt.encode(
{"exp": exp, "nbf": now, "sub": email, "type": "password_reset"},
settings.JWT_SECRET_KEY,
algorithm=settings.JWT_ALGORITHM,
)
return encoded_jwt
def verify_password_reset_token(token: str) -> Optional[str]:
"""验证密码重置令牌"""
try:
payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
# 检查令牌类型
if payload.get("type") != "password_reset":
return None
return payload["sub"]
except JWTError:
return None
# 密码强度验证
def validate_password_strength(password: str) -> dict:
"""验证密码强度"""
errors = []
if len(password) < 8:
errors.append("密码长度至少8位")
if len(password) > 128:
errors.append("密码长度不能超过128位")
if not any(c.islower() for c in password):
errors.append("密码必须包含至少一个小写字母")
if not any(c.isupper() for c in password):
errors.append("密码必须包含至少一个大写字母")
if not any(c.isdigit() for c in password):
errors.append("密码必须包含至少一个数字")
# 检查特殊字符
special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?"
if not any(c in special_chars for c in password):
errors.append("密码必须包含至少一个特殊字符")
return {
"is_valid": len(errors) == 0,
"errors": errors,
"strength": calculate_password_strength(password)
}
def calculate_password_strength(password: str) -> str:
"""计算密码强度"""
score = 0
# 长度评分
if len(password) >= 8:
score += 1
if len(password) >= 12:
score += 1
if len(password) >= 16:
score += 1
# 字符类型评分
if any(c.islower() for c in password):
score += 1
if any(c.isupper() for c in password):
score += 1
if any(c.isdigit() for c in password):
score += 1
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
score += 1
# 根据评分返回强度等级
if score <= 2:
return ""
elif score <= 4:
return "中等"
elif score <= 6:
return ""
else:
return "非常强"