fix: 로그인 요청 모델 분리 — 비밀번호 길이 정책은 가입 시에만 적용

로그인이 회원가입용 Credentials(8자 이상)를 공유해 부트스트랩 admin 계정(짧은 비밀번호)이 422 로 거부됨.
LoginRequest(min_length=1) 신설, 조회 시 username strip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-21 10:32:06 +09:00
parent b4a1fc79ab
commit 3ab8c795f7

View File

@@ -66,6 +66,12 @@ class Credentials(BaseModel):
return v return v
class LoginRequest(BaseModel):
"""로그인 요청 — 길이 정책은 가입 시에만 적용(부트스트랩 시드 계정 등 짧은 비밀번호 허용)."""
username: str = Field(..., min_length=1, max_length=32)
password: str = Field(..., min_length=1, max_length=128)
class LoginResponse(BaseModel): class LoginResponse(BaseModel):
token: str token: str
expires_at: datetime expires_at: datetime
@@ -213,7 +219,7 @@ async def register(body: Credentials, request: Request, db: AsyncSession = Depen
@router.post("/login", response_model=LoginResponse) @router.post("/login", response_model=LoginResponse)
async def login(body: Credentials, request: Request, db: AsyncSession = Depends(get_db)): async def login(body: LoginRequest, request: Request, db: AsyncSession = Depends(get_db)):
ip = _client_ip(request) ip = _client_ip(request)
remaining = _lockout_remaining(ip) remaining = _lockout_remaining(ip)
if remaining > 0: if remaining > 0:
@@ -222,7 +228,7 @@ async def login(body: Credentials, request: Request, db: AsyncSession = Depends(
detail=f"로그인 시도가 너무 많습니다. {remaining}초 후 다시 시도하세요.", detail=f"로그인 시도가 너무 많습니다. {remaining}초 후 다시 시도하세요.",
) )
user = (await db.execute(select(User).where(User.username == body.username))).scalar_one_or_none() user = (await db.execute(select(User).where(User.username == body.username.strip()))).scalar_one_or_none()
# 존재하지 않는 계정도 해시 검증을 수행해 응답 시간 차이로 계정 유무가 드러나지 않게 함 # 존재하지 않는 계정도 해시 검증을 수행해 응답 시간 차이로 계정 유무가 드러나지 않게 함
pw_ok = await _check_password_async(body.password, user.password_hash if user else _DUMMY_HASH) pw_ok = await _check_password_async(body.password, user.password_hash if user else _DUMMY_HASH)