from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import JSONResponse from sqlalchemy.ext.asyncio import AsyncSession from core.secrets import hash_password, hash_refresh_token, verify_password from db.session import get_db from repositories.sessions import SessionsRepository from repositories.users import UserRepository from schemas.login import UserLogin, UserLoginData, UserTokens from schemas.providers import ProvidersType from schemas.registration import UserRegistration from schemas.user import UserInfo from services.sessions import refresh_token_rotation from services.users import authorize_user router = APIRouter(prefix="/auth") @router.post("/signup") async def signup(req: UserRegistration, session: AsyncSession = Depends(get_db)): users_repo = UserRepository(session) if req.provider == "credentials": if not req.username or not req.password: raise HTTPException(status_code=400, detail="Username or password is not provided") user = await users_repo.get_user_by_username(req.username) if user: raise HTTPException(status_code=409, detail="User already exists") password_hash = hash_password(req.password) referal_id = None if req.referal_code: referal = await users_repo.get_user_by_ref_code(req.referal_code) referal_id = referal.id if referal else None user = await users_repo.create( username=req.username, hashed_password=password_hash, referal_id=referal_id ) return JSONResponse( UserInfo( username=user.username, telegram_id=user.telegram_id, subscription=None, referal_code=user.referal_code, ).model_dump(), status_code=201, ) raise HTTPException(status_code=400, detail="Unsupported provider") @router.post("/login", response_model=UserLogin) async def login(req: UserLoginData, session: AsyncSession = Depends(get_db)): users_repo = UserRepository(session) sessions_repo = SessionsRepository(session) if req.provider == "credentials": if not req.username or not req.password: raise HTTPException(status_code=400, detail="Username or password is not provided.") user = await users_repo.get_user_by_username(req.username) if not user: raise HTTPException(status_code=401, detail="User doesn't exist.") if not verify_password(user.hashed_password, req.password): raise HTTPException(status_code=401, detail="Invalid password") data = await authorize_user(sessions_repo, user, req.provider) return data if req.provider == "telegram": raise HTTPException(status_code=503, detail="Under development :)") else: raise HTTPException(status_code=400, detail="Unknown provider.") @router.post("/refresh", response_model=UserTokens) async def refresh(refresh_token: str, iss: ProvidersType, session: AsyncSession = Depends(get_db)): sessions_repo = SessionsRepository(session) token_hash = hash_refresh_token(refresh_token) token_entry = await sessions_repo.get_session_by_hash(token_hash) if not token_entry: raise HTTPException(status_code=401, detail="Refresh token is invalid.") key_pair = await refresh_token_rotation(sessions_repo, token_entry, iss) return UserTokens( access_token=key_pair.access_token, refresh_token=key_pair.refresh_token, expires_at=key_pair.expires_at, )