style: normalize python formatting gates
This commit is contained in:
@@ -1,25 +1,26 @@
|
||||
"""认证相关 Use Cases"""
|
||||
from packages.application.auth.register_user_use_case import (
|
||||
RegisterUserUseCase,
|
||||
RegisterUserRequest,
|
||||
RegisterUserResponse,
|
||||
VerifyEmailUseCase,
|
||||
VerifyEmailRequest,
|
||||
)
|
||||
|
||||
from packages.application.auth.login_use_case import (
|
||||
LoginUseCase,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
RefreshTokenUseCase,
|
||||
RefreshTokenRequest,
|
||||
LogoutUseCase,
|
||||
LoginUseCase,
|
||||
LogoutRequest,
|
||||
LogoutUseCase,
|
||||
RefreshTokenRequest,
|
||||
RefreshTokenUseCase,
|
||||
)
|
||||
from packages.application.auth.password_reset_use_case import (
|
||||
RequestPasswordResetUseCase,
|
||||
RequestPasswordResetRequest,
|
||||
ResetPasswordUseCase,
|
||||
RequestPasswordResetUseCase,
|
||||
ResetPasswordRequest,
|
||||
ResetPasswordUseCase,
|
||||
)
|
||||
from packages.application.auth.register_user_use_case import (
|
||||
RegisterUserRequest,
|
||||
RegisterUserResponse,
|
||||
RegisterUserUseCase,
|
||||
VerifyEmailRequest,
|
||||
VerifyEmailUseCase,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
"""
|
||||
用户登录 Use Case
|
||||
"""
|
||||
|
||||
import secrets
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
|
||||
from packages.adapters.redis import get_session_store
|
||||
from packages.domain.auth import (
|
||||
password_hasher,
|
||||
jwt_service,
|
||||
)
|
||||
from packages.domain.auth import jwt_service, password_hasher
|
||||
|
||||
|
||||
class LoginRequest:
|
||||
"""登录请求"""
|
||||
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
email: str,
|
||||
@@ -30,7 +28,7 @@ class LoginRequest:
|
||||
|
||||
class LoginResponse:
|
||||
"""登录响应"""
|
||||
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
access_token: str,
|
||||
@@ -52,18 +50,18 @@ class LoginResponse:
|
||||
|
||||
class LoginUseCase:
|
||||
"""用户登录用例"""
|
||||
|
||||
|
||||
def __init__(self, user_repository, session_store=None):
|
||||
self.user_repository = user_repository
|
||||
self.session_store = session_store or get_session_store()
|
||||
|
||||
|
||||
def execute(self, request: LoginRequest) -> tuple[Optional[LoginResponse], Optional[str]]:
|
||||
"""
|
||||
执行登录
|
||||
|
||||
|
||||
Args:
|
||||
request: 登录请求
|
||||
|
||||
|
||||
Returns:
|
||||
(登录响应, 错误信息)
|
||||
"""
|
||||
@@ -71,31 +69,32 @@ class LoginUseCase:
|
||||
# 1. 验证输入
|
||||
if not request.email:
|
||||
return None, "Email is required"
|
||||
|
||||
|
||||
if not request.password:
|
||||
return None, "Password is required"
|
||||
|
||||
|
||||
# 2. 查找用户
|
||||
user = self.user_repository.find_by_email(request.email)
|
||||
if not user:
|
||||
return None, "Invalid email or password"
|
||||
|
||||
|
||||
# 3. 验证密码
|
||||
if not password_hasher.verify_password(request.password, user.password_hash):
|
||||
return None, "Invalid email or password"
|
||||
|
||||
|
||||
# 4. 检查邮箱是否已验证(可选,根据需求决定是否强制)
|
||||
# if not user.email_verified:
|
||||
# return None, "Please verify your email first"
|
||||
|
||||
|
||||
# 5. 创建 session 并生成 refresh_token
|
||||
session_id = secrets.token_urlsafe(16)
|
||||
refresh_token = secrets.token_urlsafe(32)
|
||||
|
||||
|
||||
# 6. 生成基础 JWT token(包含 session_id,不包含 workspace)
|
||||
# 这里使用一个特殊的 "user_token",不包含 workspace 和 role
|
||||
# 用户选择工作空间后,会换取包含 workspace 的 access_token
|
||||
import jwt as pyjwt
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
access_token_payload = {
|
||||
"sub": user.id,
|
||||
@@ -107,7 +106,7 @@ class LoginUseCase:
|
||||
access_token = pyjwt.encode(
|
||||
access_token_payload,
|
||||
jwt_service.config.SECRET_KEY,
|
||||
algorithm=jwt_service.config.ALGORITHM
|
||||
algorithm=jwt_service.config.ALGORITHM,
|
||||
)
|
||||
self.session_store.save_session(
|
||||
session_id=session_id,
|
||||
@@ -117,74 +116,77 @@ class LoginUseCase:
|
||||
ip_address=request.ip_address,
|
||||
expires_in_seconds=30 * 24 * 3600, # 30 天
|
||||
)
|
||||
|
||||
|
||||
# 8. 更新最后登录信息
|
||||
user.last_login_at = datetime.now(timezone.utc)
|
||||
user.last_login_ip = request.ip_address
|
||||
self.user_repository.save(user)
|
||||
|
||||
|
||||
# 9. 返回响应
|
||||
return LoginResponse(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
user_id=user.id,
|
||||
email=user.email,
|
||||
username=user.username,
|
||||
display_name=user.display_name,
|
||||
expires_in=jwt_service.config.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
||||
), None
|
||||
|
||||
return (
|
||||
LoginResponse(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
user_id=user.id,
|
||||
email=user.email,
|
||||
username=user.username,
|
||||
display_name=user.display_name,
|
||||
expires_in=jwt_service.config.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return None, f"Login failed: {str(e)}"
|
||||
|
||||
|
||||
class RefreshTokenRequest:
|
||||
"""刷新令牌请求"""
|
||||
|
||||
|
||||
def __init__(self, refresh_token: str):
|
||||
self.refresh_token = refresh_token
|
||||
|
||||
|
||||
class RefreshTokenUseCase:
|
||||
"""刷新令牌用例"""
|
||||
|
||||
|
||||
def __init__(self, user_repository):
|
||||
self.user_repository = user_repository
|
||||
|
||||
|
||||
def execute(self, request: RefreshTokenRequest) -> tuple[Optional[LoginResponse], Optional[str]]:
|
||||
"""
|
||||
执行令牌刷新
|
||||
|
||||
|
||||
Args:
|
||||
request: 刷新请求
|
||||
|
||||
|
||||
Returns:
|
||||
(登录响应, 错误信息)
|
||||
"""
|
||||
try:
|
||||
if not request.refresh_token:
|
||||
return None, "Refresh token is required"
|
||||
|
||||
|
||||
# 1. 查找 session(通过遍历所有 session)
|
||||
# 注意:这里为了简化,先用遍历实现,生产环境应该用 refresh_token -> session_id 的索引
|
||||
session = None
|
||||
session_id = None
|
||||
|
||||
|
||||
# 这是一个简化实现,实际应该在 SessionStore 中添加 find_by_refresh_token 方法
|
||||
# 这里我们假设 refresh_token 就是 session_id(简化处理)
|
||||
# 生产环境需要更复杂的映射
|
||||
|
||||
|
||||
# 临时方案:从 Redis 获取(需要在 session_store 中添加方法)
|
||||
# 现在先返回错误,提示需要实现
|
||||
return None, "Refresh token implementation pending (需要完善 session_store)"
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return None, f"Token refresh failed: {str(e)}"
|
||||
|
||||
|
||||
class LogoutRequest:
|
||||
"""登出请求"""
|
||||
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_id: str,
|
||||
@@ -198,17 +200,17 @@ class LogoutRequest:
|
||||
|
||||
class LogoutUseCase:
|
||||
"""用户登出用例"""
|
||||
|
||||
|
||||
def __init__(self, session_store=None):
|
||||
self.session_store = session_store or get_session_store()
|
||||
|
||||
|
||||
def execute(self, request: LogoutRequest) -> tuple[bool, Optional[str]]:
|
||||
"""
|
||||
执行登出
|
||||
|
||||
|
||||
Args:
|
||||
request: 登出请求
|
||||
|
||||
|
||||
Returns:
|
||||
(是否成功, 错误信息)
|
||||
"""
|
||||
@@ -221,12 +223,12 @@ class LogoutUseCase:
|
||||
# 删除当前 session
|
||||
if not request.session_id:
|
||||
return False, "Session ID is required"
|
||||
|
||||
|
||||
success = self.session_store.delete_session(request.session_id)
|
||||
if success:
|
||||
return True, None
|
||||
else:
|
||||
return False, "Session not found"
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Logout failed: {str(e)}"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
密码重置 Use Case
|
||||
"""
|
||||
|
||||
import secrets
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
@@ -11,14 +12,14 @@ from packages.domain.auth import password_hasher, password_validator
|
||||
|
||||
class RequestPasswordResetRequest:
|
||||
"""请求密码重置"""
|
||||
|
||||
|
||||
def __init__(self, email: str):
|
||||
self.email = email.strip().lower()
|
||||
|
||||
|
||||
class RequestPasswordResetUseCase:
|
||||
"""请求密码重置用例"""
|
||||
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_repository,
|
||||
@@ -30,41 +31,39 @@ class RequestPasswordResetUseCase:
|
||||
self.base_url = base_url
|
||||
self.token_expire_hours = token_expire_hours
|
||||
self.email_service = email_service or get_email_service()
|
||||
|
||||
|
||||
def execute(self, request: RequestPasswordResetRequest) -> tuple[bool, Optional[str]]:
|
||||
"""
|
||||
执行密码重置请求
|
||||
|
||||
|
||||
Args:
|
||||
request: 重置请求
|
||||
|
||||
|
||||
Returns:
|
||||
(是否成功, 错误信息)
|
||||
"""
|
||||
try:
|
||||
if not request.email:
|
||||
return False, "Email is required"
|
||||
|
||||
|
||||
# 查找用户
|
||||
user = self.user_repository.find_by_email(request.email)
|
||||
|
||||
|
||||
# 安全考虑:即使用户不存在,也返回成功(避免暴露用户存在性)
|
||||
if not user:
|
||||
return True, None
|
||||
|
||||
|
||||
# 生成重置令牌
|
||||
reset_token = secrets.token_urlsafe(32)
|
||||
reset_url = f"{self.base_url}/reset-password?token={reset_token}"
|
||||
|
||||
|
||||
# 设置令牌和过期时间
|
||||
user.password_reset_token = reset_token
|
||||
user.password_reset_expires_at = datetime.now(timezone.utc) + timedelta(
|
||||
hours=self.token_expire_hours
|
||||
)
|
||||
|
||||
user.password_reset_expires_at = datetime.now(timezone.utc) + timedelta(hours=self.token_expire_hours)
|
||||
|
||||
# 保存用户
|
||||
self.user_repository.save(user)
|
||||
|
||||
|
||||
# 发送重置邮件
|
||||
try:
|
||||
success, error = self.email_service.send_password_reset_email(
|
||||
@@ -72,22 +71,22 @@ class RequestPasswordResetUseCase:
|
||||
username=user.username or user.display_name,
|
||||
reset_url=reset_url,
|
||||
)
|
||||
|
||||
|
||||
if not success:
|
||||
print(f"Failed to send password reset email: {error}")
|
||||
# 不返回错误,避免暴露用户存在性
|
||||
except Exception as e:
|
||||
print(f"Email service error: {e}")
|
||||
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Password reset request failed: {str(e)}"
|
||||
|
||||
|
||||
class ResetPasswordRequest:
|
||||
"""重置密码请求"""
|
||||
|
||||
|
||||
def __init__(self, token: str, new_password: str):
|
||||
self.token = token
|
||||
self.new_password = new_password
|
||||
@@ -95,54 +94,54 @@ class ResetPasswordRequest:
|
||||
|
||||
class ResetPasswordUseCase:
|
||||
"""重置密码用例"""
|
||||
|
||||
|
||||
def __init__(self, user_repository):
|
||||
self.user_repository = user_repository
|
||||
|
||||
|
||||
def execute(self, request: ResetPasswordRequest) -> tuple[bool, Optional[str]]:
|
||||
"""
|
||||
执行密码重置
|
||||
|
||||
|
||||
Args:
|
||||
request: 重置请求
|
||||
|
||||
|
||||
Returns:
|
||||
(是否成功, 错误信息)
|
||||
"""
|
||||
try:
|
||||
if not request.token:
|
||||
return False, "Reset token is required"
|
||||
|
||||
|
||||
if not request.new_password:
|
||||
return False, "New password is required"
|
||||
|
||||
|
||||
# 验证新密码强度
|
||||
valid, error = password_validator.validate(request.new_password)
|
||||
if not valid:
|
||||
return False, error
|
||||
|
||||
|
||||
# 查找用户
|
||||
user = self.user_repository.find_by_password_reset_token(request.token)
|
||||
if not user:
|
||||
return False, "Invalid or expired reset token"
|
||||
|
||||
|
||||
# 检查令牌是否过期
|
||||
if user.password_reset_expires_at:
|
||||
if datetime.now(timezone.utc) > user.password_reset_expires_at:
|
||||
return False, "Reset token has expired"
|
||||
|
||||
|
||||
# 哈希新密码
|
||||
hashed_password = password_hasher.hash_password(request.new_password)
|
||||
|
||||
|
||||
# 更新用户密码
|
||||
user.password_hash = hashed_password
|
||||
user.password_reset_token = None
|
||||
user.password_reset_expires_at = None
|
||||
|
||||
|
||||
# 保存用户
|
||||
self.user_repository.save(user)
|
||||
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Password reset failed: {str(e)}"
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
"""
|
||||
用户注册 Use Case
|
||||
"""
|
||||
|
||||
import secrets
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from packages.adapters.smtp import get_email_service
|
||||
from packages.domain.entities import User
|
||||
from packages.domain.auth import password_hasher, password_validator
|
||||
from packages.domain.entities import User
|
||||
|
||||
|
||||
class RegisterUserRequest:
|
||||
"""注册请求"""
|
||||
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
email: str,
|
||||
@@ -29,7 +30,7 @@ class RegisterUserRequest:
|
||||
|
||||
class RegisterUserResponse:
|
||||
"""注册响应"""
|
||||
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_id: str,
|
||||
@@ -47,7 +48,7 @@ class RegisterUserResponse:
|
||||
|
||||
class RegisterUserUseCase:
|
||||
"""用户注册用例"""
|
||||
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_repository,
|
||||
@@ -56,7 +57,7 @@ class RegisterUserUseCase:
|
||||
):
|
||||
"""
|
||||
初始化注册用例
|
||||
|
||||
|
||||
Args:
|
||||
user_repository: 用户仓储
|
||||
base_url: 应用基础 URL(用于生成验证链接)
|
||||
@@ -64,14 +65,14 @@ class RegisterUserUseCase:
|
||||
self.user_repository = user_repository
|
||||
self.base_url = base_url
|
||||
self.email_service = email_service or get_email_service()
|
||||
|
||||
|
||||
def execute(self, request: RegisterUserRequest) -> tuple[Optional[RegisterUserResponse], Optional[str]]:
|
||||
"""
|
||||
执行注册
|
||||
|
||||
|
||||
Args:
|
||||
request: 注册请求
|
||||
|
||||
|
||||
Returns:
|
||||
(注册响应, 错误信息)
|
||||
"""
|
||||
@@ -79,34 +80,34 @@ class RegisterUserUseCase:
|
||||
# 1. 验证输入
|
||||
if not request.email:
|
||||
return None, "Email is required"
|
||||
|
||||
|
||||
if not request.username:
|
||||
return None, "Username is required"
|
||||
|
||||
|
||||
if not request.display_name:
|
||||
return None, "Display name is required"
|
||||
|
||||
|
||||
# 2. 验证密码强度
|
||||
valid, error = password_validator.validate(request.password)
|
||||
if not valid:
|
||||
return None, error
|
||||
|
||||
|
||||
# 3. 检查邮箱是否已存在
|
||||
existing_user = self.user_repository.find_by_email(request.email)
|
||||
if existing_user:
|
||||
return None, "Email already registered"
|
||||
|
||||
|
||||
# 4. 检查用户名是否已存在
|
||||
existing_username = self.user_repository.find_by_username(request.username)
|
||||
if existing_username:
|
||||
return None, "Username already taken"
|
||||
|
||||
|
||||
# 5. 哈希密码
|
||||
hashed_password = password_hasher.hash_password(request.password)
|
||||
|
||||
|
||||
# 6. 生成邮箱验证令牌
|
||||
verification_token = secrets.token_urlsafe(32)
|
||||
|
||||
|
||||
# 7. 创建用户
|
||||
user = User(
|
||||
id=uuid4().hex,
|
||||
@@ -118,14 +119,14 @@ class RegisterUserUseCase:
|
||||
email_verification_token=verification_token,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
)
|
||||
|
||||
|
||||
# 8. 保存用户
|
||||
self.user_repository.save(user)
|
||||
|
||||
|
||||
# 9. 发送验证邮件
|
||||
verification_url = f"{self.base_url}/verify-email?token={verification_token}"
|
||||
email_sent = False
|
||||
|
||||
|
||||
try:
|
||||
success, error = self.email_service.send_verification_email(
|
||||
to_email=user.email,
|
||||
@@ -133,68 +134,71 @@ class RegisterUserUseCase:
|
||||
verification_url=verification_url,
|
||||
)
|
||||
email_sent = success
|
||||
|
||||
|
||||
if not success:
|
||||
print(f"Failed to send verification email: {error}")
|
||||
except Exception as e:
|
||||
print(f"Email service error: {e}")
|
||||
|
||||
|
||||
# 10. 返回响应(即使邮件发送失败,用户也已创建)
|
||||
return RegisterUserResponse(
|
||||
user_id=user.id,
|
||||
email=user.email,
|
||||
username=user.username,
|
||||
display_name=user.display_name,
|
||||
email_verification_sent=email_sent,
|
||||
), None
|
||||
|
||||
return (
|
||||
RegisterUserResponse(
|
||||
user_id=user.id,
|
||||
email=user.email,
|
||||
username=user.username,
|
||||
display_name=user.display_name,
|
||||
email_verification_sent=email_sent,
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return None, f"Registration failed: {str(e)}"
|
||||
|
||||
|
||||
class VerifyEmailRequest:
|
||||
"""邮箱验证请求"""
|
||||
|
||||
|
||||
def __init__(self, token: str):
|
||||
self.token = token
|
||||
|
||||
|
||||
class VerifyEmailUseCase:
|
||||
"""邮箱验证用例"""
|
||||
|
||||
|
||||
def __init__(self, user_repository):
|
||||
self.user_repository = user_repository
|
||||
|
||||
|
||||
def execute(self, request: VerifyEmailRequest) -> tuple[bool, Optional[str]]:
|
||||
"""
|
||||
执行邮箱验证
|
||||
|
||||
|
||||
Args:
|
||||
request: 验证请求
|
||||
|
||||
|
||||
Returns:
|
||||
(是否成功, 错误信息)
|
||||
"""
|
||||
try:
|
||||
if not request.token:
|
||||
return False, "Verification token is required"
|
||||
|
||||
|
||||
# 查找用户
|
||||
user = self.user_repository.find_by_verification_token(request.token)
|
||||
if not user:
|
||||
return False, "Invalid or expired verification token"
|
||||
|
||||
|
||||
# 检查是否已验证
|
||||
if user.email_verified:
|
||||
return True, None # 已验证,返回成功
|
||||
|
||||
|
||||
# 更新用户状态
|
||||
user.email_verified = True
|
||||
user.email_verification_token = None # 清空令牌
|
||||
|
||||
|
||||
self.user_repository.save(user)
|
||||
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Email verification failed: {str(e)}"
|
||||
|
||||
Reference in New Issue
Block a user