api schema check
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-09-26 07:28:07 +09:00
parent e5aa933cf9
commit ed8eb75bac
96 changed files with 24213 additions and 13 deletions

View File

@@ -247,19 +247,55 @@ async def register_user(user_create: UserCreate, request: Request):
@app.post("/api/v1/auth/login", response_model=Token, tags=["Authentication"], summary="Login user")
async def login_user(user_login: UserLogin, request: Request):
"""Login user"""
client_ip = get_client_ip(request)
print(f"Login request from {client_ip}: {user_login.model_dump(exclude={'password'})}")
async with httpx.AsyncClient(timeout=30.0) as client:
try:
login_data = user_login.model_dump()
print(f"Sending login data to user service: {login_data}")
response = await client.post(
f"{SERVICES['users']}/api/v1/auth/login",
json=user_login.model_dump(),
json=login_data,
headers={
"Content-Type": "application/json",
"Accept": "application/json"
}
)
print(f"User service response: status={response.status_code}")
if response.status_code >= 400:
print(f"Error response body: {response.text}")
if response.status_code == 200:
return response.json()
elif response.status_code == 422:
# Detailed handling for validation errors
try:
error_json = response.json()
print(f"Validation error details: {error_json}")
# Return more detailed validation errors
if "detail" in error_json:
detail = error_json["detail"]
if isinstance(detail, list):
# FastAPI validation errors
formatted_errors = []
for error in detail:
field = error.get("loc", ["unknown"])[-1]
msg = error.get("msg", "Invalid value")
formatted_errors.append(f"{field}: {msg}")
raise HTTPException(
status_code=422,
detail=f"Validation errors: {'; '.join(formatted_errors)}"
)
else:
raise HTTPException(status_code=422, detail=detail)
else:
raise HTTPException(status_code=422, detail="Invalid input data")
except ValueError as ve:
print(f"JSON parse error: {ve}")
raise HTTPException(status_code=422, detail="Invalid request format")
else:
error_detail = response.text
try:
@@ -272,6 +308,7 @@ async def login_user(user_login: UserLogin, request: Request):
except HTTPException:
raise
except Exception as e:
print(f"Login service error: {str(e)}")
raise HTTPException(status_code=500, detail=f"Login error: {str(e)}")

View File

@@ -134,36 +134,65 @@ async def register_user(user_data: UserCreate, db: AsyncSession = Depends(get_db
@app.post("/api/v1/auth/login", response_model=Token)
async def login(user_credentials: UserLogin, db: AsyncSession = Depends(get_db)):
"""Authenticate user and return token"""
print(f"Login attempt: email={user_credentials.email}, username={user_credentials.username}")
# Проверка валидности входных данных
if not user_credentials.email and not user_credentials.username:
print("Error: Neither email nor username provided")
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Either email or username must be provided",
)
if not user_credentials.password:
print("Error: Password not provided")
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Password is required",
)
# Определяем, по какому полю ищем пользователя
user = None
if user_credentials.email:
result = await db.execute(select(User).filter(User.email == user_credentials.email))
user = result.scalars().first()
elif user_credentials.username:
result = await db.execute(select(User).filter(User.username == user_credentials.username))
user = result.scalars().first()
else:
try:
if user_credentials.email:
print(f"Looking up user by email: {user_credentials.email}")
result = await db.execute(select(User).filter(User.email == user_credentials.email))
user = result.scalars().first()
elif user_credentials.username:
print(f"Looking up user by username: {user_credentials.username}")
result = await db.execute(select(User).filter(User.username == user_credentials.username))
user = result.scalars().first()
except Exception as e:
print(f"Database error during user lookup: {str(e)}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Either email or username must be provided",
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Database error during authentication",
)
# Проверяем наличие пользователя и правильность пароля
if not user:
print(f"User not found: email={user_credentials.email}, username={user_credentials.username}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
)
print(f"User found: id={user.id}, email={user.email}")
# Проверка пароля
try:
if not verify_password(user_credentials.password, str(user.password_hash)):
password_valid = verify_password(user_credentials.password, str(user.password_hash))
print(f"Password verification result: {password_valid}")
if not password_valid:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
)
except Exception:
except HTTPException:
raise
except Exception as e:
# Если произошла ошибка при проверке пароля, то считаем, что пароль неверный
print(f"Password verification error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
@@ -172,21 +201,25 @@ async def login(user_credentials: UserLogin, db: AsyncSession = Depends(get_db))
# Проверка активности аккаунта
try:
is_active = bool(user.is_active)
print(f"User active status: {is_active}")
if not is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Account is inactive",
)
except Exception:
except Exception as e:
# Если произошла ошибка при проверке активности, считаем аккаунт активным
print(f"Error checking user active status: {str(e)}")
pass
print("Creating access token...")
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": str(user.id), "email": user.email},
expires_delta=access_token_expires,
)
print("Login successful")
return {"access_token": access_token, "token_type": "bearer"}

View File

@@ -108,10 +108,21 @@ class UserLogin(BaseModel):
@classmethod
def validate_password_bytes(cls, v):
"""Ensure password doesn't exceed bcrypt's 72-byte limit."""
if not v or len(v.strip()) == 0:
raise ValueError("Password cannot be empty")
password_bytes = v.encode('utf-8')
if len(password_bytes) > 72:
raise ValueError("Password is too long when encoded as UTF-8 (max 72 bytes for bcrypt)")
return v
@field_validator("username")
@classmethod
def validate_login_fields(cls, v, info):
"""Ensure at least email or username is provided."""
email = info.data.get('email')
if not email and not v:
raise ValueError("Either email or username must be provided")
return v
class Token(BaseModel):