Preserve local user table for superDream-specific features while syncing user lifecycle, API key CRUD and usage queries through sub2api. Admin token handles reads and user lifecycle; per-user tokens (Fernet-encrypted in DB) handle key writes that admin endpoints do not expose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
82 lines
2.9 KiB
Python
82 lines
2.9 KiB
Python
from fastapi import APIRouter, Depends
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.database import get_db
|
|
from app.core.dependencies import get_current_user
|
|
from app.datamodels.schemas import (
|
|
ForgotPasswordRequest,
|
|
LoginRequest,
|
|
MessageResponse,
|
|
RefreshRequest,
|
|
RegisterRequest,
|
|
ResetPasswordRequest,
|
|
TokenResponse,
|
|
UserResponse,
|
|
)
|
|
from app.integrations.sub2api import admin as sub2api_admin
|
|
from app.integrations.sub2api.client import Sub2APIError, Sub2APITransportError
|
|
from app.models import User
|
|
from app.services.auth_service import AuthService
|
|
|
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
|
|
|
|
|
@router.post("/register", response_model=UserResponse)
|
|
async def register(body: RegisterRequest, db: AsyncSession = Depends(get_db)):
|
|
user = await AuthService.register(db, body.email, body.password)
|
|
return _user_to_response(user)
|
|
|
|
|
|
@router.post("/login", response_model=TokenResponse)
|
|
async def login(body: LoginRequest, db: AsyncSession = Depends(get_db)):
|
|
return await AuthService.login(db, body.email, body.password)
|
|
|
|
|
|
@router.post("/refresh", response_model=TokenResponse)
|
|
async def refresh(body: RefreshRequest, db: AsyncSession = Depends(get_db)):
|
|
return await AuthService.refresh(db, body.refresh_token)
|
|
|
|
|
|
@router.post("/logout", response_model=MessageResponse)
|
|
async def logout():
|
|
return {"message": "Logged out successfully"}
|
|
|
|
|
|
@router.post("/forgot-password", response_model=MessageResponse)
|
|
async def forgot_password(body: ForgotPasswordRequest, db: AsyncSession = Depends(get_db)):
|
|
token = await AuthService.forgot_password(db, body.email)
|
|
if token:
|
|
# MVP: print to console; production should send via email
|
|
print(f"[Password Reset] email={body.email} token={token}")
|
|
return {"message": "If the email exists, a reset link has been sent"}
|
|
|
|
|
|
@router.post("/reset-password", response_model=MessageResponse)
|
|
async def reset_password(body: ResetPasswordRequest, db: AsyncSession = Depends(get_db)):
|
|
await AuthService.reset_password(db, body.token, body.new_password)
|
|
return {"message": "Password reset successfully"}
|
|
|
|
|
|
@router.get("/me", response_model=UserResponse)
|
|
async def me(user: User = Depends(get_current_user)):
|
|
"""Merge the local row with sub2api's live balance when available."""
|
|
response = _user_to_response(user)
|
|
if user.sub2api_user_id:
|
|
try:
|
|
remote = await sub2api_admin.get_user(user.sub2api_user_id)
|
|
response.balance = float(remote.get("balance") or 0)
|
|
except (Sub2APIError, Sub2APITransportError):
|
|
# non-fatal; fall back to 0
|
|
pass
|
|
return response
|
|
|
|
|
|
def _user_to_response(user: User) -> UserResponse:
|
|
return UserResponse(
|
|
id=user.id,
|
|
email=user.email,
|
|
status=user.status,
|
|
created_at=user.created_at,
|
|
sub2api_user_id=user.sub2api_user_id,
|
|
balance=0.0,
|
|
) |