202 lines
5.2 KiB
Python
202 lines
5.2 KiB
Python
"""
|
||
认证API
|
||
"""
|
||
from fastapi import APIRouter, HTTPException, Depends, Request, Response
|
||
from app.models.auth import (
|
||
SendCodeRequest, SendCodeResponse,
|
||
LoginRequest, LoginResponse,
|
||
RefreshTokenResponse, UserInfo
|
||
)
|
||
from app.models.database import User
|
||
from app.services.auth_service import auth_service
|
||
from app.services.jwt_service import jwt_service
|
||
from app.services.db_service import db_service
|
||
from app.middleware.auth_middleware import get_current_user, get_client_ip
|
||
from app.middleware.console_auth import (
|
||
CONSOLE_AUTH_COOKIE,
|
||
create_console_access_token,
|
||
require_console_access,
|
||
)
|
||
from app.config import get_settings
|
||
from app.utils.logger import logger
|
||
|
||
router = APIRouter(prefix="/api/auth", tags=["认证"])
|
||
|
||
|
||
@router.post("/console-login")
|
||
async def console_login(request: Request, response: Response):
|
||
try:
|
||
body = await request.json()
|
||
except Exception:
|
||
body = {}
|
||
|
||
password = str(body.get("password") or "")
|
||
settings = get_settings()
|
||
if not password or password != settings.console_access_password:
|
||
raise HTTPException(status_code=403, detail="访问密码错误")
|
||
|
||
token = create_console_access_token()
|
||
max_age = max(1, int(settings.console_access_expire_days or 30)) * 24 * 60 * 60
|
||
response.set_cookie(
|
||
key=CONSOLE_AUTH_COOKIE,
|
||
value=token,
|
||
max_age=max_age,
|
||
httponly=True,
|
||
samesite="lax",
|
||
secure=False,
|
||
path="/",
|
||
)
|
||
return {"success": True, "message": "总控台登录成功"}
|
||
|
||
|
||
@router.post("/console-logout")
|
||
async def console_logout(response: Response):
|
||
response.delete_cookie(CONSOLE_AUTH_COOKIE, path="/")
|
||
return {"success": True, "message": "已退出总控台"}
|
||
|
||
|
||
@router.get("/console-session")
|
||
async def get_console_session(_: dict = Depends(require_console_access)):
|
||
return {"success": True, "authenticated": True}
|
||
|
||
|
||
@router.post("/send-code", response_model=SendCodeResponse)
|
||
async def send_verification_code(
|
||
request_data: SendCodeRequest,
|
||
request: Request
|
||
):
|
||
"""
|
||
发送验证码
|
||
|
||
- 同一手机号60秒内只能发送一次
|
||
- 同一IP每小时最多发送10次
|
||
- 验证码5分钟有效期
|
||
"""
|
||
try:
|
||
client_ip = get_client_ip(request)
|
||
db = db_service.get_session()
|
||
|
||
try:
|
||
result = await auth_service.send_verification_code(
|
||
db=db,
|
||
phone=request_data.phone,
|
||
ip_address=client_ip
|
||
)
|
||
|
||
return SendCodeResponse(**result)
|
||
|
||
finally:
|
||
db.close()
|
||
|
||
except Exception as e:
|
||
logger.error(f"发送验证码失败: {e}")
|
||
raise HTTPException(status_code=500, detail="发送验证码失败")
|
||
|
||
|
||
@router.post("/login", response_model=LoginResponse)
|
||
async def login_with_code(
|
||
request_data: LoginRequest,
|
||
request: Request
|
||
):
|
||
"""
|
||
验证码登录
|
||
|
||
- 验证验证码是否正确且未过期
|
||
- 用户不存在则自动注册
|
||
- 生成JWT token(7天有效期)
|
||
- 更新最后登录时间
|
||
"""
|
||
try:
|
||
client_ip = get_client_ip(request)
|
||
db = db_service.get_session()
|
||
|
||
try:
|
||
result = await auth_service.login_with_code(
|
||
db=db,
|
||
phone=request_data.phone,
|
||
code=request_data.code,
|
||
ip_address=client_ip
|
||
)
|
||
|
||
if not result["success"]:
|
||
return LoginResponse(
|
||
success=False,
|
||
message=result["message"]
|
||
)
|
||
|
||
return LoginResponse(
|
||
success=True,
|
||
token=result["token"],
|
||
user=UserInfo(**result["user"])
|
||
)
|
||
|
||
finally:
|
||
db.close()
|
||
|
||
except Exception as e:
|
||
logger.error(f"登录失败: {e}")
|
||
raise HTTPException(status_code=500, detail="登录失败")
|
||
|
||
|
||
@router.post("/refresh", response_model=RefreshTokenResponse)
|
||
async def refresh_token(
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
刷新Token
|
||
|
||
需要提供有效的JWT token
|
||
"""
|
||
try:
|
||
# 生成新的token
|
||
new_token = jwt_service.create_access_token(
|
||
current_user.id,
|
||
current_user.phone
|
||
)
|
||
|
||
return RefreshTokenResponse(
|
||
success=True,
|
||
token=new_token
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"刷新token失败: {e}")
|
||
raise HTTPException(status_code=500, detail="刷新token失败")
|
||
|
||
|
||
@router.get("/me", response_model=UserInfo)
|
||
async def get_current_user_info(
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
获取当前用户信息
|
||
|
||
需要提供有效的JWT token
|
||
"""
|
||
# 手机号脱敏
|
||
masked_phone = f"{current_user.phone[:3]}****{current_user.phone[-4:]}"
|
||
|
||
return UserInfo(
|
||
id=current_user.id,
|
||
phone=masked_phone,
|
||
created_at=current_user.created_at,
|
||
last_login_at=current_user.last_login_at
|
||
)
|
||
|
||
|
||
@router.post("/logout")
|
||
async def logout(
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
登出
|
||
|
||
主要在前端清除token,后端记录日志
|
||
"""
|
||
logger.info(f"用户登出: {current_user.phone}")
|
||
|
||
return {
|
||
"success": True,
|
||
"message": "已登出"
|
||
}
|