from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import JSONResponse from core.secrets import ( estimate_password_strength, hash_password, hash_refresh_token, verify_password, ) from db.session import UnitOfWork, get_uow 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, uow: UnitOfWork = Depends(get_uow)): users_repo = UserRepository(uow) 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") if not estimate_password_strength(req.password): raise HTTPException(422, detail="Password is not secure.") 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 ) await uow.commit() return JSONResponse( UserInfo( username=user.username, telegram_id=user.telegram_id, referal_code=user.referal_code, bonus_balance=user.balance, ).model_dump(), status_code=201, ) raise HTTPException(status_code=400, detail="Unsupported provider") @router.post("/login", response_model=UserLogin) async def login(req: UserLoginData, uow: UnitOfWork = Depends(get_uow)): users_repo = UserRepository(uow) sessions_repo = SessionsRepository(uow) 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) await uow.commit() 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, uow: UnitOfWork = Depends(get_uow)): sessions_repo = SessionsRepository(uow) 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) await uow.commit() return UserTokens( access_token=key_pair.access_token, refresh_token=key_pair.refresh_token, expires_at=key_pair.expires_at, )