fix: 添加auth功能
This commit is contained in:
244
python_core/api/auth_api.py
Normal file
244
python_core/api/auth_api.py
Normal file
@@ -0,0 +1,244 @@
|
||||
"""
|
||||
用户认证API接口
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional
|
||||
import json
|
||||
|
||||
from python_core.models.user import LoginRequest, RegisterRequest
|
||||
from python_core.services.auth_service import auth_service
|
||||
from python_core.utils.logger import logger
|
||||
|
||||
|
||||
class AuthAPI:
|
||||
"""用户认证API"""
|
||||
|
||||
def __init__(self):
|
||||
self.auth_service = auth_service
|
||||
logger.info("AuthAPI initialized")
|
||||
|
||||
def register(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
用户注册API
|
||||
|
||||
Args:
|
||||
data: 注册数据
|
||||
{
|
||||
"username": "用户名",
|
||||
"email": "邮箱",
|
||||
"password": "密码",
|
||||
"display_name": "显示名称" (可选)
|
||||
}
|
||||
|
||||
Returns:
|
||||
Dict: API响应
|
||||
"""
|
||||
try:
|
||||
# 创建注册请求对象
|
||||
request = RegisterRequest(
|
||||
username=data.get("username", "").strip(),
|
||||
email=data.get("email", "").strip(),
|
||||
password=data.get("password", ""),
|
||||
display_name=data.get("display_name", "").strip() or None
|
||||
)
|
||||
|
||||
# 执行注册
|
||||
response = self.auth_service.register(request)
|
||||
|
||||
# 返回API响应
|
||||
return {
|
||||
"success": response.success,
|
||||
"message": response.message,
|
||||
"data": {
|
||||
"user": response.user,
|
||||
"token": response.token,
|
||||
"expires_at": response.expires_at
|
||||
} if response.success else None
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Register API error: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": "注册失败,请稍后重试",
|
||||
"data": None
|
||||
}
|
||||
|
||||
def login(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
用户登录API
|
||||
|
||||
Args:
|
||||
data: 登录数据
|
||||
{
|
||||
"username_or_email": "用户名或邮箱",
|
||||
"password": "密码"
|
||||
}
|
||||
|
||||
Returns:
|
||||
Dict: API响应
|
||||
"""
|
||||
try:
|
||||
# 创建登录请求对象
|
||||
request = LoginRequest(
|
||||
username_or_email=data.get("username_or_email", "").strip(),
|
||||
password=data.get("password", "")
|
||||
)
|
||||
|
||||
# 执行登录
|
||||
response = self.auth_service.login(request)
|
||||
|
||||
# 返回API响应
|
||||
return {
|
||||
"success": response.success,
|
||||
"message": response.message,
|
||||
"data": {
|
||||
"user": response.user,
|
||||
"token": response.token,
|
||||
"expires_at": response.expires_at
|
||||
} if response.success else None
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Login API error: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": "登录失败,请稍后重试",
|
||||
"data": None
|
||||
}
|
||||
|
||||
def verify_token(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
验证token API
|
||||
|
||||
Args:
|
||||
data: 验证数据
|
||||
{
|
||||
"token": "JWT token"
|
||||
}
|
||||
|
||||
Returns:
|
||||
Dict: API响应
|
||||
"""
|
||||
try:
|
||||
token = data.get("token", "")
|
||||
if not token:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Token不能为空",
|
||||
"data": None
|
||||
}
|
||||
|
||||
# 验证token
|
||||
user_info = self.auth_service.verify_token(token)
|
||||
|
||||
if user_info:
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Token验证成功",
|
||||
"data": {
|
||||
"user": user_info,
|
||||
"valid": True
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Token无效或已过期",
|
||||
"data": {
|
||||
"valid": False
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Verify token API error: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Token验证失败",
|
||||
"data": None
|
||||
}
|
||||
|
||||
def get_current_user(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
获取当前用户信息API
|
||||
|
||||
Args:
|
||||
data: 请求数据
|
||||
{
|
||||
"token": "JWT token"
|
||||
}
|
||||
|
||||
Returns:
|
||||
Dict: API响应
|
||||
"""
|
||||
try:
|
||||
token = data.get("token", "")
|
||||
if not token:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Token不能为空",
|
||||
"data": None
|
||||
}
|
||||
|
||||
# 获取当前用户
|
||||
user = self.auth_service.get_current_user(token)
|
||||
|
||||
if user:
|
||||
return {
|
||||
"success": True,
|
||||
"message": "获取用户信息成功",
|
||||
"data": {
|
||||
"user": user.to_safe_dict()
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "用户不存在或token无效",
|
||||
"data": None
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Get current user API error: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": "获取用户信息失败",
|
||||
"data": None
|
||||
}
|
||||
|
||||
|
||||
# 创建全局认证API实例
|
||||
auth_api = AuthAPI()
|
||||
|
||||
|
||||
# JSON-RPC风格的便捷函数
|
||||
def register_user(username: str, email: str, password: str, display_name: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""注册用户的便捷函数"""
|
||||
return auth_api.register({
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": password,
|
||||
"display_name": display_name
|
||||
})
|
||||
|
||||
|
||||
def login_user(username_or_email: str, password: str) -> Dict[str, Any]:
|
||||
"""登录用户的便捷函数"""
|
||||
return auth_api.login({
|
||||
"username_or_email": username_or_email,
|
||||
"password": password
|
||||
})
|
||||
|
||||
|
||||
def verify_user_token(token: str) -> Dict[str, Any]:
|
||||
"""验证用户token的便捷函数"""
|
||||
return auth_api.verify_token({
|
||||
"token": token
|
||||
})
|
||||
|
||||
|
||||
def get_user_info(token: str) -> Dict[str, Any]:
|
||||
"""获取用户信息的便捷函数"""
|
||||
return auth_api.get_current_user({
|
||||
"token": token
|
||||
})
|
||||
@@ -11,6 +11,7 @@ import typer
|
||||
from python_core.cli.commands import scene_detect
|
||||
from python_core.cli.commands.template import template_app
|
||||
from python_core.cli.commands.category import category_app
|
||||
from python_core.cli.commands.auth import auth_app
|
||||
|
||||
app = typer.Typer(
|
||||
name="mixvideo",
|
||||
@@ -21,6 +22,7 @@ app = typer.Typer(
|
||||
• 🎯 场景检测 - 智能识别视频场景变化
|
||||
• 📋 模板管理 - 视频模板批量导入、列表查看、详情获取
|
||||
• 🏷️ 分类管理 - 资源分类创建、更新、删除、搜索
|
||||
• 👤 用户认证 - 用户注册、登录、JWT token管理
|
||||
• 📤 媒体管理 - 上传、处理、组织视频文件
|
||||
• ⚙️ 系统管理 - 配置、状态、存储管理
|
||||
""",
|
||||
@@ -32,6 +34,7 @@ app = typer.Typer(
|
||||
app.add_typer(scene_detect, name="scene")
|
||||
app.add_typer(template_app, name="template")
|
||||
app.add_typer(category_app, name="category")
|
||||
app.add_typer(auth_app, name="auth")
|
||||
|
||||
@app.command()
|
||||
def init():
|
||||
|
||||
315
python_core/cli/commands/auth.py
Normal file
315
python_core/cli/commands/auth.py
Normal file
@@ -0,0 +1,315 @@
|
||||
"""
|
||||
用户认证CLI命令
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
import json
|
||||
import getpass
|
||||
|
||||
from python_core.api.auth_api import auth_api
|
||||
from python_core.services.user_storage import user_storage
|
||||
from python_core.utils.jwt_auth import jwt_auth
|
||||
|
||||
console = Console()
|
||||
auth_app = typer.Typer(name="auth", help="用户认证管理命令")
|
||||
|
||||
|
||||
@auth_app.command("register")
|
||||
def register_user(
|
||||
username: str = typer.Argument(..., help="用户名"),
|
||||
email: str = typer.Argument(..., help="邮箱地址"),
|
||||
display_name: Optional[str] = typer.Option(None, "--display-name", "-d", help="显示名称"),
|
||||
password: Optional[str] = typer.Option(None, "--password", "-p", help="密码(不提供则交互式输入)"),
|
||||
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出")
|
||||
):
|
||||
"""注册新用户"""
|
||||
|
||||
try:
|
||||
console.print(f"🆕 [bold blue]用户注册[/bold blue]")
|
||||
console.print(f"用户名: {username}")
|
||||
console.print(f"邮箱: {email}")
|
||||
if display_name:
|
||||
console.print(f"显示名称: {display_name}")
|
||||
|
||||
# 获取密码
|
||||
if not password:
|
||||
password = getpass.getpass("请输入密码: ")
|
||||
if not password:
|
||||
console.print("[red]❌ 密码不能为空[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# 执行注册
|
||||
result = auth_api.register({
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": password,
|
||||
"display_name": display_name
|
||||
})
|
||||
|
||||
if result["success"]:
|
||||
console.print(f"\n✅ [bold green]注册成功![/bold green]")
|
||||
console.print(f"用户ID: {result['data']['user']['id']}")
|
||||
console.print(f"创建时间: {result['data']['user']['created_at']}")
|
||||
|
||||
if verbose:
|
||||
# 显示详细信息
|
||||
user_info = f"""
|
||||
🆔 用户ID: {result['data']['user']['id']}
|
||||
👤 用户名: {result['data']['user']['username']}
|
||||
📧 邮箱: {result['data']['user']['email']}
|
||||
🏷️ 显示名称: {result['data']['user']['display_name']}
|
||||
📅 创建时间: {result['data']['user']['created_at']}
|
||||
🔑 Token: {result['data']['token'][:50]}...
|
||||
⏰ 过期时间: {result['data']['expires_at']}
|
||||
"""
|
||||
|
||||
panel = Panel(user_info.strip(), title="用户详情", border_style="green")
|
||||
console.print(panel)
|
||||
else:
|
||||
console.print(f"[red]❌ 注册失败: {result['message']}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 注册失败: {str(e)}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
@auth_app.command("login")
|
||||
def login_user(
|
||||
username_or_email: str = typer.Argument(..., help="用户名或邮箱"),
|
||||
password: Optional[str] = typer.Option(None, "--password", "-p", help="密码(不提供则交互式输入)"),
|
||||
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出")
|
||||
):
|
||||
"""用户登录"""
|
||||
|
||||
try:
|
||||
console.print(f"🔐 [bold blue]用户登录[/bold blue]")
|
||||
console.print(f"用户名/邮箱: {username_or_email}")
|
||||
|
||||
# 获取密码
|
||||
if not password:
|
||||
password = getpass.getpass("请输入密码: ")
|
||||
if not password:
|
||||
console.print("[red]❌ 密码不能为空[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# 执行登录
|
||||
result = auth_api.login({
|
||||
"username_or_email": username_or_email,
|
||||
"password": password
|
||||
})
|
||||
|
||||
if result["success"]:
|
||||
console.print(f"\n✅ [bold green]登录成功![/bold green]")
|
||||
console.print(f"欢迎回来,{result['data']['user']['display_name']}!")
|
||||
console.print(f"最后登录: {result['data']['user']['last_login']}")
|
||||
|
||||
if verbose:
|
||||
# 显示详细信息
|
||||
user_info = f"""
|
||||
🆔 用户ID: {result['data']['user']['id']}
|
||||
👤 用户名: {result['data']['user']['username']}
|
||||
📧 邮箱: {result['data']['user']['email']}
|
||||
🏷️ 显示名称: {result['data']['user']['display_name']}
|
||||
🕒 最后登录: {result['data']['user']['last_login']}
|
||||
🔑 Token: {result['data']['token'][:50]}...
|
||||
⏰ 过期时间: {result['data']['expires_at']}
|
||||
"""
|
||||
|
||||
panel = Panel(user_info.strip(), title="登录信息", border_style="green")
|
||||
console.print(panel)
|
||||
else:
|
||||
console.print(f"[red]❌ 登录失败: {result['message']}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 登录失败: {str(e)}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
@auth_app.command("verify")
|
||||
def verify_token(
|
||||
token: str = typer.Argument(..., help="JWT token"),
|
||||
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出")
|
||||
):
|
||||
"""验证JWT token"""
|
||||
|
||||
try:
|
||||
console.print(f"🔍 [bold blue]验证Token[/bold blue]")
|
||||
|
||||
# 验证token
|
||||
result = auth_api.verify_token({
|
||||
"token": token
|
||||
})
|
||||
|
||||
if result["success"]:
|
||||
console.print(f"\n✅ [bold green]Token有效![/bold green]")
|
||||
user = result['data']['user']
|
||||
console.print(f"用户: {user['display_name']} ({user['username']})")
|
||||
console.print(f"邮箱: {user['email']}")
|
||||
|
||||
if verbose:
|
||||
# 显示token详细信息
|
||||
token_info = jwt_auth.get_token_info(token)
|
||||
|
||||
info_text = f"""
|
||||
👤 用户信息:
|
||||
ID: {user['user_id']}
|
||||
用户名: {user['username']}
|
||||
邮箱: {user['email']}
|
||||
显示名称: {user['display_name']}
|
||||
|
||||
🔑 Token信息:
|
||||
有效性: {'有效' if token_info['valid'] else '无效'}
|
||||
签发时间: {token_info.get('issued_at', 'Unknown')}
|
||||
过期时间: {token_info.get('expires_at', 'Unknown')}
|
||||
剩余时间: {token_info.get('time_remaining', 'Unknown')}
|
||||
"""
|
||||
|
||||
panel = Panel(info_text.strip(), title="Token详情", border_style="green")
|
||||
console.print(panel)
|
||||
else:
|
||||
console.print(f"[red]❌ Token无效: {result['message']}[/red]")
|
||||
|
||||
if verbose:
|
||||
# 显示token信息(即使无效)
|
||||
token_info = jwt_auth.get_token_info(token)
|
||||
if token_info.get('error'):
|
||||
console.print(f"错误: {token_info['error']}")
|
||||
else:
|
||||
console.print(f"Token状态: {'过期' if token_info.get('is_expired') else '无效'}")
|
||||
|
||||
raise typer.Exit(1)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ Token验证失败: {str(e)}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
@auth_app.command("list")
|
||||
def list_users(
|
||||
include_inactive: bool = typer.Option(False, "--include-inactive", "-i", help="包含非活跃用户"),
|
||||
limit: int = typer.Option(20, "--limit", "-l", help="显示数量限制"),
|
||||
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出")
|
||||
):
|
||||
"""列出所有用户"""
|
||||
|
||||
try:
|
||||
console.print(f"👥 [bold blue]用户列表[/bold blue]")
|
||||
|
||||
# 获取用户列表
|
||||
users = user_storage.get_all_users(include_inactive)
|
||||
|
||||
if not users:
|
||||
console.print("📭 没有用户")
|
||||
return
|
||||
|
||||
# 限制显示数量
|
||||
total_count = len(users)
|
||||
if len(users) > limit:
|
||||
users = users[:limit]
|
||||
console.print(f"📊 显示前 {limit} 个用户(共 {total_count} 个)")
|
||||
else:
|
||||
console.print(f"📊 共 {total_count} 个用户")
|
||||
|
||||
# 创建表格
|
||||
table = Table(title="用户列表")
|
||||
table.add_column("ID", style="cyan", width=8)
|
||||
table.add_column("用户名", style="green")
|
||||
table.add_column("邮箱", style="yellow")
|
||||
table.add_column("显示名称", style="magenta")
|
||||
table.add_column("状态", style="blue")
|
||||
table.add_column("创建时间", style="dim")
|
||||
|
||||
if verbose:
|
||||
table.add_column("最后登录", style="dim")
|
||||
|
||||
for user in users:
|
||||
# 状态
|
||||
status = "✅ 活跃" if user.is_active else "❌ 禁用"
|
||||
|
||||
# 格式化创建时间
|
||||
created_at = user.created_at
|
||||
if 'T' in created_at:
|
||||
created_at = created_at.split('T')[0] + ' ' + created_at.split('T')[1][:8]
|
||||
|
||||
row = [
|
||||
user.id[:8],
|
||||
user.username,
|
||||
user.email,
|
||||
user.display_name,
|
||||
status,
|
||||
created_at
|
||||
]
|
||||
|
||||
if verbose:
|
||||
last_login = user.last_login or "从未登录"
|
||||
if last_login != "从未登录" and 'T' in last_login:
|
||||
last_login = last_login.split('T')[0] + ' ' + last_login.split('T')[1][:8]
|
||||
row.append(last_login)
|
||||
|
||||
table.add_row(*row)
|
||||
|
||||
console.print(table)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 获取用户列表失败: {str(e)}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
@auth_app.command("stats")
|
||||
def show_stats():
|
||||
"""显示用户统计信息"""
|
||||
|
||||
try:
|
||||
console.print(f"📊 [bold blue]用户统计信息[/bold blue]")
|
||||
|
||||
# 获取统计信息
|
||||
stats = user_storage.get_user_count()
|
||||
|
||||
# 创建统计面板
|
||||
stats_content = f"""
|
||||
📈 [bold green]用户统计[/bold green]
|
||||
总用户数: {stats['total']}
|
||||
活跃用户: {stats['active']}
|
||||
禁用用户: {stats['inactive']}
|
||||
"""
|
||||
|
||||
stats_panel = Panel(stats_content.strip(), title="用户统计", border_style="green")
|
||||
console.print(stats_panel)
|
||||
|
||||
# 显示最近注册的用户
|
||||
recent_users = user_storage.get_all_users()[:5]
|
||||
|
||||
if recent_users:
|
||||
console.print(f"\n🕒 [bold green]最近注册的用户[/bold green]")
|
||||
|
||||
recent_table = Table()
|
||||
recent_table.add_column("用户名", style="green")
|
||||
recent_table.add_column("邮箱", style="yellow")
|
||||
recent_table.add_column("注册时间", style="dim")
|
||||
|
||||
for user in recent_users:
|
||||
created_at = user.created_at
|
||||
if 'T' in created_at:
|
||||
created_at = created_at.split('T')[0] + ' ' + created_at.split('T')[1][:8]
|
||||
|
||||
recent_table.add_row(
|
||||
user.username,
|
||||
user.email,
|
||||
created_at
|
||||
)
|
||||
|
||||
console.print(recent_table)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 获取统计信息失败: {str(e)}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
auth_app()
|
||||
@@ -2,6 +2,23 @@ from dataclasses import dataclass
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class ResourceCategory:
|
||||
"""素材分类数据结构"""
|
||||
id: str
|
||||
title: str # 分类标题
|
||||
ai_prompt: str # AI识别提示词
|
||||
color: str # 展示颜色 (hex格式,如 #FF5733)
|
||||
created_at: str
|
||||
updated_at: str
|
||||
is_active: bool = True
|
||||
|
||||
# is_cloud false 代表本地资源 那么只加载自己的 如果是 true 就是公用的
|
||||
is_cloud: bool
|
||||
# 创建用户
|
||||
user_id: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class MaterialInfo:
|
||||
"""Material information structure"""
|
||||
@@ -13,6 +30,11 @@ class MaterialInfo:
|
||||
relative_path: str
|
||||
duration: Optional[int] = None
|
||||
size: Optional[int] = None
|
||||
|
||||
# is_cloud false 代表本地资源 那么只加载自己的 如果是 true 就是公用的
|
||||
is_cloud: bool
|
||||
# 创建用户
|
||||
user_id: str
|
||||
|
||||
@dataclass
|
||||
class TemplateInfo:
|
||||
@@ -29,7 +51,7 @@ class TemplateInfo:
|
||||
material_count: int
|
||||
track_count: int
|
||||
tags: List[str]
|
||||
# local/cloud 如果类型是 local 那么只加载自己的 如果是 cloud 就是公用的
|
||||
type: str
|
||||
# is_cloud false 代表本地资源 那么只加载自己的 如果是 true 就是公用的
|
||||
is_cloud: bool
|
||||
# 创建用户
|
||||
user_id: str
|
||||
8
python_core/database/user.py
Normal file
8
python_core/database/user.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# 用户表
|
||||
|
||||
from python_core.kv import kv
|
||||
from .db import Db
|
||||
class UserDb(Db):
|
||||
def __init__(self):
|
||||
self.key = "model"
|
||||
pass
|
||||
96
python_core/models/user.py
Normal file
96
python_core/models/user.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
用户数据模型
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
import uuid
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
"""用户数据模型"""
|
||||
id: str
|
||||
username: str
|
||||
email: str
|
||||
password_hash: str
|
||||
display_name: str
|
||||
avatar_url: Optional[str] = None
|
||||
is_active: bool = True
|
||||
created_at: str = ""
|
||||
updated_at: str = ""
|
||||
last_login: Optional[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.created_at:
|
||||
self.created_at = datetime.now().isoformat()
|
||||
if not self.updated_at:
|
||||
self.updated_at = datetime.now().isoformat()
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
return asdict(self)
|
||||
|
||||
def to_safe_dict(self) -> Dict[str, Any]:
|
||||
"""转换为安全字典(不包含密码)"""
|
||||
data = self.to_dict()
|
||||
data.pop('password_hash', None)
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'User':
|
||||
"""从字典创建用户对象"""
|
||||
return cls(**data)
|
||||
|
||||
def update_last_login(self):
|
||||
"""更新最后登录时间"""
|
||||
self.last_login = datetime.now().isoformat()
|
||||
self.updated_at = datetime.now().isoformat()
|
||||
|
||||
|
||||
@dataclass
|
||||
class LoginRequest:
|
||||
"""登录请求数据"""
|
||||
username_or_email: str
|
||||
password: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class RegisterRequest:
|
||||
"""注册请求数据"""
|
||||
username: str
|
||||
email: str
|
||||
password: str
|
||||
display_name: Optional[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.display_name:
|
||||
self.display_name = self.username
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthResponse:
|
||||
"""认证响应数据"""
|
||||
success: bool
|
||||
message: str
|
||||
user: Optional[Dict[str, Any]] = None
|
||||
token: Optional[str] = None
|
||||
expires_at: Optional[str] = None
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
"""密码哈希"""
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
|
||||
def verify_password(password: str, password_hash: str) -> bool:
|
||||
"""验证密码"""
|
||||
return hash_password(password) == password_hash
|
||||
|
||||
|
||||
def generate_user_id() -> str:
|
||||
"""生成用户ID"""
|
||||
return str(uuid.uuid4())
|
||||
@@ -30,4 +30,5 @@ rich
|
||||
langgraph
|
||||
json-rpc
|
||||
langchain_core
|
||||
langchain_anthropic
|
||||
langchain_anthropic
|
||||
PyJWT
|
||||
263
python_core/services/auth_service.py
Normal file
263
python_core/services/auth_service.py
Normal file
@@ -0,0 +1,263 @@
|
||||
"""
|
||||
用户认证服务
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
from python_core.models.user import (
|
||||
User, LoginRequest, RegisterRequest, AuthResponse,
|
||||
hash_password, verify_password
|
||||
)
|
||||
from python_core.services.user_storage import user_storage
|
||||
from python_core.utils.jwt_auth import generate_access_token, verify_access_token
|
||||
from python_core.utils.logger import logger
|
||||
|
||||
|
||||
class AuthService:
|
||||
"""用户认证服务"""
|
||||
|
||||
def __init__(self):
|
||||
self.user_storage = user_storage
|
||||
logger.info("AuthService initialized")
|
||||
|
||||
def register(self, request: RegisterRequest) -> AuthResponse:
|
||||
"""
|
||||
用户注册
|
||||
|
||||
Args:
|
||||
request: 注册请求数据
|
||||
|
||||
Returns:
|
||||
AuthResponse: 注册响应
|
||||
"""
|
||||
try:
|
||||
# 验证输入数据
|
||||
validation_error = self._validate_register_request(request)
|
||||
if validation_error:
|
||||
return AuthResponse(
|
||||
success=False,
|
||||
message=validation_error
|
||||
)
|
||||
|
||||
# 密码哈希
|
||||
password_hash = hash_password(request.password)
|
||||
|
||||
# 创建用户
|
||||
user = self.user_storage.create_user(
|
||||
username=request.username,
|
||||
email=request.email,
|
||||
password_hash=password_hash,
|
||||
display_name=request.display_name
|
||||
)
|
||||
|
||||
# 生成JWT token
|
||||
token_info = generate_access_token(
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
email=user.email
|
||||
)
|
||||
|
||||
logger.info(f"User registered successfully: {user.username}")
|
||||
|
||||
return AuthResponse(
|
||||
success=True,
|
||||
message="注册成功",
|
||||
user=user.to_safe_dict(),
|
||||
token=token_info["token"],
|
||||
expires_at=token_info["expires_at"]
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
logger.warning(f"Registration failed: {e}")
|
||||
return AuthResponse(
|
||||
success=False,
|
||||
message=str(e)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Registration error: {e}")
|
||||
return AuthResponse(
|
||||
success=False,
|
||||
message="注册失败,请稍后重试"
|
||||
)
|
||||
|
||||
def login(self, request: LoginRequest) -> AuthResponse:
|
||||
"""
|
||||
用户登录
|
||||
|
||||
Args:
|
||||
request: 登录请求数据
|
||||
|
||||
Returns:
|
||||
AuthResponse: 登录响应
|
||||
"""
|
||||
try:
|
||||
# 验证输入数据
|
||||
validation_error = self._validate_login_request(request)
|
||||
if validation_error:
|
||||
return AuthResponse(
|
||||
success=False,
|
||||
message=validation_error
|
||||
)
|
||||
|
||||
# 查找用户
|
||||
user = self.user_storage.get_user_by_username_or_email(request.username_or_email)
|
||||
if not user:
|
||||
return AuthResponse(
|
||||
success=False,
|
||||
message="用户名或密码错误"
|
||||
)
|
||||
|
||||
# 检查用户状态
|
||||
if not user.is_active:
|
||||
return AuthResponse(
|
||||
success=False,
|
||||
message="账户已被禁用"
|
||||
)
|
||||
|
||||
# 验证密码
|
||||
if not verify_password(request.password, user.password_hash):
|
||||
return AuthResponse(
|
||||
success=False,
|
||||
message="用户名或密码错误"
|
||||
)
|
||||
|
||||
# 更新最后登录时间
|
||||
user.update_last_login()
|
||||
self.user_storage.update_user(user)
|
||||
|
||||
# 生成JWT token
|
||||
token_info = generate_access_token(
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
email=user.email
|
||||
)
|
||||
|
||||
logger.info(f"User logged in successfully: {user.username}")
|
||||
|
||||
return AuthResponse(
|
||||
success=True,
|
||||
message="登录成功",
|
||||
user=user.to_safe_dict(),
|
||||
token=token_info["token"],
|
||||
expires_at=token_info["expires_at"]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Login error: {e}")
|
||||
return AuthResponse(
|
||||
success=False,
|
||||
message="登录失败,请稍后重试"
|
||||
)
|
||||
|
||||
def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
验证JWT token
|
||||
|
||||
Args:
|
||||
token: JWT token字符串
|
||||
|
||||
Returns:
|
||||
Dict: 用户信息,验证失败返回None
|
||||
"""
|
||||
try:
|
||||
# 验证token
|
||||
payload = verify_access_token(token)
|
||||
if not payload:
|
||||
return None
|
||||
|
||||
# 获取用户信息
|
||||
user = self.user_storage.get_user_by_id(payload["user_id"])
|
||||
if not user or not user.is_active:
|
||||
return None
|
||||
|
||||
return {
|
||||
"user_id": user.id,
|
||||
"username": user.username,
|
||||
"email": user.email,
|
||||
"display_name": user.display_name,
|
||||
"avatar_url": user.avatar_url,
|
||||
"last_login": user.last_login
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Token verification error: {e}")
|
||||
return None
|
||||
|
||||
def get_current_user(self, token: str) -> Optional[User]:
|
||||
"""
|
||||
根据token获取当前用户
|
||||
|
||||
Args:
|
||||
token: JWT token字符串
|
||||
|
||||
Returns:
|
||||
User: 用户对象,失败返回None
|
||||
"""
|
||||
try:
|
||||
payload = verify_access_token(token)
|
||||
if not payload:
|
||||
return None
|
||||
|
||||
user = self.user_storage.get_user_by_id(payload["user_id"])
|
||||
if not user or not user.is_active:
|
||||
return None
|
||||
|
||||
return user
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Get current user error: {e}")
|
||||
return None
|
||||
|
||||
def _validate_register_request(self, request: RegisterRequest) -> Optional[str]:
|
||||
"""验证注册请求数据"""
|
||||
|
||||
# 验证用户名
|
||||
if not request.username or len(request.username.strip()) < 3:
|
||||
return "用户名至少需要3个字符"
|
||||
|
||||
if len(request.username) > 50:
|
||||
return "用户名不能超过50个字符"
|
||||
|
||||
if not re.match(r'^[a-zA-Z0-9_]+$', request.username):
|
||||
return "用户名只能包含字母、数字和下划线"
|
||||
|
||||
# 验证邮箱
|
||||
if not request.email:
|
||||
return "邮箱地址不能为空"
|
||||
|
||||
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
||||
if not re.match(email_pattern, request.email):
|
||||
return "邮箱地址格式不正确"
|
||||
|
||||
# 验证密码
|
||||
if not request.password:
|
||||
return "密码不能为空"
|
||||
|
||||
if len(request.password) < 6:
|
||||
return "密码至少需要6个字符"
|
||||
|
||||
if len(request.password) > 100:
|
||||
return "密码不能超过100个字符"
|
||||
|
||||
# 验证显示名称
|
||||
if request.display_name and len(request.display_name) > 100:
|
||||
return "显示名称不能超过100个字符"
|
||||
|
||||
return None
|
||||
|
||||
def _validate_login_request(self, request: LoginRequest) -> Optional[str]:
|
||||
"""验证登录请求数据"""
|
||||
|
||||
if not request.username_or_email:
|
||||
return "用户名或邮箱不能为空"
|
||||
|
||||
if not request.password:
|
||||
return "密码不能为空"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# 创建全局认证服务实例
|
||||
auth_service = AuthService()
|
||||
@@ -144,91 +144,3 @@ class ResourceCategoryManager:
|
||||
results.append(asdict(category))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# 全局实例
|
||||
resource_category_manager = ResourceCategoryManager()
|
||||
|
||||
|
||||
def main():
|
||||
"""命令行接口 - 使用JSON-RPC协议"""
|
||||
import sys
|
||||
import json
|
||||
|
||||
# 创建响应处理器
|
||||
rpc = create_response_handler()
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
rpc.error("INVALID_REQUEST", "No command specified")
|
||||
return
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
try:
|
||||
if command == "get_all_categories":
|
||||
categories = resource_category_manager.get_all_categories()
|
||||
rpc.success(categories)
|
||||
|
||||
elif command == "get_category_by_id":
|
||||
if len(sys.argv) < 3:
|
||||
rpc.error("INVALID_REQUEST", "Category ID required")
|
||||
return
|
||||
category_id = sys.argv[2]
|
||||
category = resource_category_manager.get_category_by_id(category_id)
|
||||
if category:
|
||||
rpc.success(category)
|
||||
else:
|
||||
rpc.error("NOT_FOUND", "Category not found")
|
||||
|
||||
elif command == "create_category":
|
||||
if len(sys.argv) < 5:
|
||||
rpc.error("INVALID_REQUEST", "Title, prompt and color required")
|
||||
return
|
||||
title, ai_prompt, color = sys.argv[2], sys.argv[3], sys.argv[4]
|
||||
result = resource_category_manager.create_category(title, ai_prompt, color)
|
||||
rpc.success(result)
|
||||
|
||||
elif command == "update_category":
|
||||
if len(sys.argv) < 4:
|
||||
rpc.error("INVALID_REQUEST", "Category ID and update data required")
|
||||
return
|
||||
category_id = sys.argv[2]
|
||||
update_data = json.loads(sys.argv[3])
|
||||
result = resource_category_manager.update_category(
|
||||
category_id,
|
||||
update_data.get('title'),
|
||||
update_data.get('ai_prompt'),
|
||||
update_data.get('color'),
|
||||
update_data.get('is_active')
|
||||
)
|
||||
if result:
|
||||
rpc.success(result)
|
||||
else:
|
||||
rpc.error("NOT_FOUND", "Category not found")
|
||||
|
||||
elif command == "delete_category":
|
||||
if len(sys.argv) < 3:
|
||||
rpc.error("INVALID_REQUEST", "Category ID required")
|
||||
return
|
||||
category_id = sys.argv[2]
|
||||
success = resource_category_manager.delete_category(category_id)
|
||||
rpc.success(success)
|
||||
|
||||
elif command == "search_categories":
|
||||
if len(sys.argv) < 3:
|
||||
rpc.error("INVALID_REQUEST", "Search keyword required")
|
||||
return
|
||||
keyword = sys.argv[2]
|
||||
results = resource_category_manager.search_categories(keyword)
|
||||
rpc.success(results)
|
||||
|
||||
else:
|
||||
rpc.error("INVALID_REQUEST", f"Unknown command: {command}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Command execution failed: {e}")
|
||||
rpc.error("INTERNAL_ERROR", str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
249
python_core/services/user_storage.py
Normal file
249
python_core/services/user_storage.py
Normal file
@@ -0,0 +1,249 @@
|
||||
"""
|
||||
用户数据存储服务
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
from python_core.models.user import User, generate_user_id
|
||||
from python_core.config import settings
|
||||
from python_core.utils.logger import logger
|
||||
|
||||
|
||||
class UserStorage:
|
||||
"""用户数据存储服务"""
|
||||
|
||||
def __init__(self):
|
||||
# 用户数据文件路径
|
||||
self.users_file = Path(settings.temp_dir) / "cache" / "users.json"
|
||||
self.users_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 内存中的用户数据
|
||||
self._users: Dict[str, User] = {}
|
||||
|
||||
# 加载用户数据
|
||||
self._load_users()
|
||||
|
||||
logger.info(f"UserStorage initialized with {len(self._users)} users")
|
||||
|
||||
def _load_users(self):
|
||||
"""从文件加载用户数据"""
|
||||
try:
|
||||
if self.users_file.exists():
|
||||
with open(self.users_file, 'r', encoding='utf-8') as f:
|
||||
users_data = json.load(f)
|
||||
|
||||
self._users = {}
|
||||
for user_data in users_data:
|
||||
user = User.from_dict(user_data)
|
||||
self._users[user.id] = user
|
||||
|
||||
logger.info(f"Loaded {len(self._users)} users from {self.users_file}")
|
||||
else:
|
||||
self._users = {}
|
||||
logger.info("No existing users file found, starting with empty user database")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load users: {e}")
|
||||
self._users = {}
|
||||
|
||||
def _save_users(self):
|
||||
"""保存用户数据到文件"""
|
||||
try:
|
||||
users_data = [user.to_dict() for user in self._users.values()]
|
||||
|
||||
with open(self.users_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(users_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.debug(f"Saved {len(self._users)} users to {self.users_file}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save users: {e}")
|
||||
raise
|
||||
|
||||
def create_user(self, username: str, email: str, password_hash: str, display_name: str) -> User:
|
||||
"""
|
||||
创建新用户
|
||||
|
||||
Args:
|
||||
username: 用户名
|
||||
email: 邮箱
|
||||
password_hash: 密码哈希
|
||||
display_name: 显示名称
|
||||
|
||||
Returns:
|
||||
User: 创建的用户对象
|
||||
|
||||
Raises:
|
||||
ValueError: 用户名或邮箱已存在
|
||||
"""
|
||||
# 检查用户名是否已存在
|
||||
if self.get_user_by_username(username):
|
||||
raise ValueError(f"Username '{username}' already exists")
|
||||
|
||||
# 检查邮箱是否已存在
|
||||
if self.get_user_by_email(email):
|
||||
raise ValueError(f"Email '{email}' already exists")
|
||||
|
||||
# 创建新用户
|
||||
user = User(
|
||||
id=generate_user_id(),
|
||||
username=username,
|
||||
email=email,
|
||||
password_hash=password_hash,
|
||||
display_name=display_name
|
||||
)
|
||||
|
||||
# 保存到内存和文件
|
||||
self._users[user.id] = user
|
||||
self._save_users()
|
||||
|
||||
logger.info(f"Created new user: {username} ({email})")
|
||||
return user
|
||||
|
||||
def get_user_by_id(self, user_id: str) -> Optional[User]:
|
||||
"""根据用户ID获取用户"""
|
||||
return self._users.get(user_id)
|
||||
|
||||
def get_user_by_username(self, username: str) -> Optional[User]:
|
||||
"""根据用户名获取用户"""
|
||||
for user in self._users.values():
|
||||
if user.username == username:
|
||||
return user
|
||||
return None
|
||||
|
||||
def get_user_by_email(self, email: str) -> Optional[User]:
|
||||
"""根据邮箱获取用户"""
|
||||
for user in self._users.values():
|
||||
if user.email == email:
|
||||
return user
|
||||
return None
|
||||
|
||||
def get_user_by_username_or_email(self, username_or_email: str) -> Optional[User]:
|
||||
"""根据用户名或邮箱获取用户"""
|
||||
# 先尝试用户名
|
||||
user = self.get_user_by_username(username_or_email)
|
||||
if user:
|
||||
return user
|
||||
|
||||
# 再尝试邮箱
|
||||
return self.get_user_by_email(username_or_email)
|
||||
|
||||
def update_user(self, user: User) -> bool:
|
||||
"""
|
||||
更新用户信息
|
||||
|
||||
Args:
|
||||
user: 用户对象
|
||||
|
||||
Returns:
|
||||
bool: 更新是否成功
|
||||
"""
|
||||
try:
|
||||
if user.id not in self._users:
|
||||
logger.warning(f"User {user.id} not found for update")
|
||||
return False
|
||||
|
||||
# 更新时间戳
|
||||
user.updated_at = datetime.now().isoformat()
|
||||
|
||||
# 保存到内存和文件
|
||||
self._users[user.id] = user
|
||||
self._save_users()
|
||||
|
||||
logger.info(f"Updated user: {user.username}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update user {user.id}: {e}")
|
||||
return False
|
||||
|
||||
def delete_user(self, user_id: str) -> bool:
|
||||
"""
|
||||
删除用户
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
bool: 删除是否成功
|
||||
"""
|
||||
try:
|
||||
if user_id not in self._users:
|
||||
logger.warning(f"User {user_id} not found for deletion")
|
||||
return False
|
||||
|
||||
user = self._users[user_id]
|
||||
del self._users[user_id]
|
||||
self._save_users()
|
||||
|
||||
logger.info(f"Deleted user: {user.username}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete user {user_id}: {e}")
|
||||
return False
|
||||
|
||||
def get_all_users(self, include_inactive: bool = False) -> List[User]:
|
||||
"""
|
||||
获取所有用户
|
||||
|
||||
Args:
|
||||
include_inactive: 是否包含非活跃用户
|
||||
|
||||
Returns:
|
||||
List[User]: 用户列表
|
||||
"""
|
||||
users = list(self._users.values())
|
||||
|
||||
if not include_inactive:
|
||||
users = [user for user in users if user.is_active]
|
||||
|
||||
# 按创建时间排序
|
||||
users.sort(key=lambda x: x.created_at, reverse=True)
|
||||
|
||||
return users
|
||||
|
||||
def get_user_count(self) -> Dict[str, int]:
|
||||
"""获取用户统计信息"""
|
||||
all_users = list(self._users.values())
|
||||
active_users = [user for user in all_users if user.is_active]
|
||||
|
||||
return {
|
||||
"total": len(all_users),
|
||||
"active": len(active_users),
|
||||
"inactive": len(all_users) - len(active_users)
|
||||
}
|
||||
|
||||
def search_users(self, keyword: str) -> List[User]:
|
||||
"""
|
||||
搜索用户
|
||||
|
||||
Args:
|
||||
keyword: 搜索关键词
|
||||
|
||||
Returns:
|
||||
List[User]: 匹配的用户列表
|
||||
"""
|
||||
keyword = keyword.lower().strip()
|
||||
if not keyword:
|
||||
return self.get_all_users()
|
||||
|
||||
matching_users = []
|
||||
for user in self._users.values():
|
||||
if (keyword in user.username.lower() or
|
||||
keyword in user.email.lower() or
|
||||
keyword in user.display_name.lower()):
|
||||
matching_users.append(user)
|
||||
|
||||
# 按创建时间排序
|
||||
matching_users.sort(key=lambda x: x.created_at, reverse=True)
|
||||
|
||||
return matching_users
|
||||
|
||||
|
||||
# 创建全局用户存储实例
|
||||
user_storage = UserStorage()
|
||||
205
python_core/utils/jwt_auth.py
Normal file
205
python_core/utils/jwt_auth.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
JWT认证工具类
|
||||
"""
|
||||
|
||||
import jwt
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any
|
||||
from pathlib import Path
|
||||
|
||||
from python_core.utils.logger import logger
|
||||
|
||||
|
||||
class JWTAuth:
|
||||
"""JWT认证工具类"""
|
||||
|
||||
def __init__(self, secret_key: str = "mixvideo_secret_key_2024"):
|
||||
self.secret_key = secret_key
|
||||
self.algorithm = "HS256"
|
||||
# JWT过期时间:6个月
|
||||
self.expires_delta = timedelta(days=180)
|
||||
|
||||
def generate_token(self, user_id: str, username: str, email: str) -> Dict[str, Any]:
|
||||
"""
|
||||
生成JWT token
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
username: 用户名
|
||||
email: 邮箱
|
||||
|
||||
Returns:
|
||||
Dict: 包含token和过期时间的字典
|
||||
"""
|
||||
try:
|
||||
# 计算过期时间
|
||||
expires_at = datetime.utcnow() + self.expires_delta
|
||||
|
||||
# JWT payload
|
||||
payload = {
|
||||
"user_id": user_id,
|
||||
"username": username,
|
||||
"email": email,
|
||||
"iat": datetime.utcnow(), # 签发时间
|
||||
"exp": expires_at, # 过期时间
|
||||
"iss": "mixvideo", # 签发者
|
||||
"sub": user_id # 主题(用户ID)
|
||||
}
|
||||
|
||||
# 生成token
|
||||
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
|
||||
|
||||
logger.info(f"Generated JWT token for user: {username}")
|
||||
|
||||
return {
|
||||
"token": token,
|
||||
"expires_at": expires_at.isoformat(),
|
||||
"expires_in": int(self.expires_delta.total_seconds())
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to generate JWT token: {e}")
|
||||
raise Exception(f"Token generation failed: {str(e)}")
|
||||
|
||||
def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
验证JWT token
|
||||
|
||||
Args:
|
||||
token: JWT token字符串
|
||||
|
||||
Returns:
|
||||
Dict: 解码后的payload,验证失败返回None
|
||||
"""
|
||||
try:
|
||||
# 解码并验证token
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
self.secret_key,
|
||||
algorithms=[self.algorithm],
|
||||
options={"verify_exp": True} # 验证过期时间
|
||||
)
|
||||
|
||||
logger.debug(f"Token verified for user: {payload.get('username')}")
|
||||
return payload
|
||||
|
||||
except jwt.ExpiredSignatureError:
|
||||
logger.warning("JWT token has expired")
|
||||
return None
|
||||
except jwt.InvalidTokenError as e:
|
||||
logger.warning(f"Invalid JWT token: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"JWT token verification failed: {e}")
|
||||
return None
|
||||
|
||||
def refresh_token(self, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
刷新JWT token
|
||||
|
||||
Args:
|
||||
token: 当前的JWT token
|
||||
|
||||
Returns:
|
||||
Dict: 新的token信息,失败返回None
|
||||
"""
|
||||
try:
|
||||
# 验证当前token(忽略过期时间)
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
self.secret_key,
|
||||
algorithms=[self.algorithm],
|
||||
options={"verify_exp": False} # 不验证过期时间
|
||||
)
|
||||
|
||||
# 生成新token
|
||||
new_token_info = self.generate_token(
|
||||
user_id=payload["user_id"],
|
||||
username=payload["username"],
|
||||
email=payload["email"]
|
||||
)
|
||||
|
||||
logger.info(f"Token refreshed for user: {payload['username']}")
|
||||
return new_token_info
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Token refresh failed: {e}")
|
||||
return None
|
||||
|
||||
def decode_token_without_verification(self, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
解码token但不验证(用于获取过期token的信息)
|
||||
|
||||
Args:
|
||||
token: JWT token字符串
|
||||
|
||||
Returns:
|
||||
Dict: 解码后的payload
|
||||
"""
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
options={"verify_signature": False, "verify_exp": False}
|
||||
)
|
||||
return payload
|
||||
except Exception as e:
|
||||
logger.error(f"Token decode failed: {e}")
|
||||
return None
|
||||
|
||||
def get_token_info(self, token: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取token信息
|
||||
|
||||
Args:
|
||||
token: JWT token字符串
|
||||
|
||||
Returns:
|
||||
Dict: token信息
|
||||
"""
|
||||
payload = self.decode_token_without_verification(token)
|
||||
if not payload:
|
||||
return {"valid": False, "error": "Invalid token format"}
|
||||
|
||||
try:
|
||||
exp_timestamp = payload.get("exp")
|
||||
if exp_timestamp:
|
||||
exp_datetime = datetime.fromtimestamp(exp_timestamp)
|
||||
is_expired = datetime.utcnow() > exp_datetime
|
||||
time_remaining = exp_datetime - datetime.utcnow()
|
||||
else:
|
||||
is_expired = True
|
||||
time_remaining = timedelta(0)
|
||||
|
||||
return {
|
||||
"valid": not is_expired,
|
||||
"user_id": payload.get("user_id"),
|
||||
"username": payload.get("username"),
|
||||
"email": payload.get("email"),
|
||||
"issued_at": payload.get("iat"),
|
||||
"expires_at": payload.get("exp"),
|
||||
"is_expired": is_expired,
|
||||
"time_remaining": str(time_remaining) if not is_expired else "Expired"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {"valid": False, "error": str(e)}
|
||||
|
||||
|
||||
# 创建全局JWT认证实例
|
||||
jwt_auth = JWTAuth()
|
||||
|
||||
|
||||
def generate_access_token(user_id: str, username: str, email: str) -> Dict[str, Any]:
|
||||
"""生成访问token的便捷函数"""
|
||||
return jwt_auth.generate_token(user_id, username, email)
|
||||
|
||||
|
||||
def verify_access_token(token: str) -> Optional[Dict[str, Any]]:
|
||||
"""验证访问token的便捷函数"""
|
||||
return jwt_auth.verify_token(token)
|
||||
|
||||
|
||||
def refresh_access_token(token: str) -> Optional[Dict[str, Any]]:
|
||||
"""刷新访问token的便捷函数"""
|
||||
return jwt_auth.refresh_token(token)
|
||||
316
test_auth_system.py
Normal file
316
test_auth_system.py
Normal file
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试Python用户认证系统
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, '/root/projects/mixvideo_v2')
|
||||
|
||||
from python_core.api.auth_api import auth_api
|
||||
from python_core.utils.jwt_auth import jwt_auth
|
||||
from python_core.services.user_storage import user_storage
|
||||
|
||||
|
||||
def test_user_registration():
|
||||
"""测试用户注册"""
|
||||
print("🧪 测试用户注册...")
|
||||
|
||||
try:
|
||||
# 注册测试用户
|
||||
result = auth_api.register({
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"password": "password123",
|
||||
"display_name": "测试用户"
|
||||
})
|
||||
|
||||
if result["success"]:
|
||||
print(f"✅ 注册成功: {result['data']['user']['username']}")
|
||||
print(f" 用户ID: {result['data']['user']['id']}")
|
||||
print(f" Token: {result['data']['token'][:50]}...")
|
||||
return result['data']['token']
|
||||
else:
|
||||
print(f"❌ 注册失败: {result['message']}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 注册测试失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def test_user_login():
|
||||
"""测试用户登录"""
|
||||
print("\n🧪 测试用户登录...")
|
||||
|
||||
try:
|
||||
# 登录测试用户
|
||||
result = auth_api.login({
|
||||
"username_or_email": "testuser",
|
||||
"password": "password123"
|
||||
})
|
||||
|
||||
if result["success"]:
|
||||
print(f"✅ 登录成功: {result['data']['user']['username']}")
|
||||
print(f" 显示名称: {result['data']['user']['display_name']}")
|
||||
print(f" Token: {result['data']['token'][:50]}...")
|
||||
return result['data']['token']
|
||||
else:
|
||||
print(f"❌ 登录失败: {result['message']}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 登录测试失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def test_token_verification(token):
|
||||
"""测试Token验证"""
|
||||
print("\n🧪 测试Token验证...")
|
||||
|
||||
try:
|
||||
# 验证token
|
||||
result = auth_api.verify_token({
|
||||
"token": token
|
||||
})
|
||||
|
||||
if result["success"]:
|
||||
user = result['data']['user']
|
||||
print(f"✅ Token验证成功: {user['username']}")
|
||||
print(f" 用户ID: {user['user_id']}")
|
||||
print(f" 邮箱: {user['email']}")
|
||||
|
||||
# 获取token详细信息
|
||||
token_info = jwt_auth.get_token_info(token)
|
||||
print(f" Token有效性: {token_info['valid']}")
|
||||
print(f" 剩余时间: {token_info['time_remaining']}")
|
||||
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Token验证失败: {result['message']}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Token验证测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_get_current_user(token):
|
||||
"""测试获取当前用户"""
|
||||
print("\n🧪 测试获取当前用户...")
|
||||
|
||||
try:
|
||||
# 获取当前用户
|
||||
result = auth_api.get_current_user({
|
||||
"token": token
|
||||
})
|
||||
|
||||
if result["success"]:
|
||||
user = result['data']['user']
|
||||
print(f"✅ 获取用户成功: {user['username']}")
|
||||
print(f" 显示名称: {user['display_name']}")
|
||||
print(f" 创建时间: {user['created_at']}")
|
||||
print(f" 最后登录: {user['last_login']}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 获取用户失败: {result['message']}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 获取用户测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_user_storage():
|
||||
"""测试用户存储"""
|
||||
print("\n🧪 测试用户存储...")
|
||||
|
||||
try:
|
||||
# 获取所有用户
|
||||
users = user_storage.get_all_users()
|
||||
print(f"✅ 获取到 {len(users)} 个用户")
|
||||
|
||||
# 获取用户统计
|
||||
stats = user_storage.get_user_count()
|
||||
print(f" 总用户数: {stats['total']}")
|
||||
print(f" 活跃用户: {stats['active']}")
|
||||
print(f" 禁用用户: {stats['inactive']}")
|
||||
|
||||
# 搜索用户
|
||||
search_results = user_storage.search_users("test")
|
||||
print(f" 搜索'test'找到 {len(search_results)} 个用户")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 用户存储测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_duplicate_registration():
|
||||
"""测试重复注册"""
|
||||
print("\n🧪 测试重复注册...")
|
||||
|
||||
try:
|
||||
# 尝试重复注册
|
||||
result = auth_api.register({
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"password": "password123",
|
||||
"display_name": "重复用户"
|
||||
})
|
||||
|
||||
if not result["success"]:
|
||||
print(f"✅ 正确阻止重复注册: {result['message']}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 未能阻止重复注册")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 重复注册测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_invalid_login():
|
||||
"""测试无效登录"""
|
||||
print("\n🧪 测试无效登录...")
|
||||
|
||||
try:
|
||||
# 尝试错误密码登录
|
||||
result = auth_api.login({
|
||||
"username_or_email": "testuser",
|
||||
"password": "wrongpassword"
|
||||
})
|
||||
|
||||
if not result["success"]:
|
||||
print(f"✅ 正确拒绝错误密码: {result['message']}")
|
||||
else:
|
||||
print(f"❌ 未能拒绝错误密码")
|
||||
return False
|
||||
|
||||
# 尝试不存在的用户登录
|
||||
result = auth_api.login({
|
||||
"username_or_email": "nonexistentuser",
|
||||
"password": "password123"
|
||||
})
|
||||
|
||||
if not result["success"]:
|
||||
print(f"✅ 正确拒绝不存在用户: {result['message']}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 未能拒绝不存在用户")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 无效登录测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_jwt_features():
|
||||
"""测试JWT特性"""
|
||||
print("\n🧪 测试JWT特性...")
|
||||
|
||||
try:
|
||||
# 生成token
|
||||
token_info = jwt_auth.generate_token("test_user_id", "testuser", "test@example.com")
|
||||
token = token_info["token"]
|
||||
print(f"✅ 生成Token成功")
|
||||
print(f" 过期时间: {token_info['expires_at']}")
|
||||
print(f" 有效期: {token_info['expires_in']} 秒")
|
||||
|
||||
# 验证token
|
||||
payload = jwt_auth.verify_token(token)
|
||||
if payload:
|
||||
print(f"✅ Token验证成功")
|
||||
print(f" 用户ID: {payload['user_id']}")
|
||||
print(f" 用户名: {payload['username']}")
|
||||
print(f" 签发者: {payload['iss']}")
|
||||
else:
|
||||
print(f"❌ Token验证失败")
|
||||
return False
|
||||
|
||||
# 获取token信息
|
||||
info = jwt_auth.get_token_info(token)
|
||||
print(f"✅ Token信息获取成功")
|
||||
print(f" 有效性: {info['valid']}")
|
||||
print(f" 剩余时间: {info['time_remaining']}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ JWT特性测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 开始测试Python用户认证系统...")
|
||||
|
||||
test_results = []
|
||||
|
||||
# 测试JWT特性
|
||||
test_results.append(("JWT特性", test_jwt_features()))
|
||||
|
||||
# 测试用户注册
|
||||
token = test_user_registration()
|
||||
test_results.append(("用户注册", token is not None))
|
||||
|
||||
if token:
|
||||
# 测试Token验证
|
||||
test_results.append(("Token验证", test_token_verification(token)))
|
||||
|
||||
# 测试获取当前用户
|
||||
test_results.append(("获取当前用户", test_get_current_user(token)))
|
||||
|
||||
# 测试用户登录
|
||||
login_token = test_user_login()
|
||||
test_results.append(("用户登录", login_token is not None))
|
||||
|
||||
# 测试用户存储
|
||||
test_results.append(("用户存储", test_user_storage()))
|
||||
|
||||
# 测试重复注册
|
||||
test_results.append(("重复注册检查", test_duplicate_registration()))
|
||||
|
||||
# 测试无效登录
|
||||
test_results.append(("无效登录检查", test_invalid_login()))
|
||||
|
||||
# 显示测试结果
|
||||
print("\n📊 测试结果汇总:")
|
||||
print("=" * 50)
|
||||
|
||||
passed = 0
|
||||
total = len(test_results)
|
||||
|
||||
for test_name, result in test_results:
|
||||
status = "✅ 通过" if result else "❌ 失败"
|
||||
print(f"{test_name:20} {status}")
|
||||
if result:
|
||||
passed += 1
|
||||
|
||||
print("=" * 50)
|
||||
print(f"总计: {passed}/{total} 个测试通过")
|
||||
|
||||
if passed == total:
|
||||
print("\n🎉 所有测试通过!Python用户认证系统工作正常!")
|
||||
|
||||
print("\n📖 使用方法:")
|
||||
print(" # 注册用户")
|
||||
print(" python3 -m python_core.cli auth register username email@example.com")
|
||||
print(" # 登录用户")
|
||||
print(" python3 -m python_core.cli auth login username")
|
||||
print(" # 验证Token")
|
||||
print(" python3 -m python_core.cli auth verify <token>")
|
||||
print(" # 查看用户列表")
|
||||
print(" python3 -m python_core.cli auth list")
|
||||
print(" # 查看统计信息")
|
||||
print(" python3 -m python_core.cli auth stats")
|
||||
|
||||
else:
|
||||
print(f"\n❌ {total - passed} 个测试失败!")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user