fix: 아키텍처 리뷰 HIGH/MEDIUM 이슈 10건 수정
HIGH (3건): - 런처 파일 업로드 시 PE 헤더 검증 + 500MB 크기 제한 추가 - 체인 노드 URL 파싱 시 scheme/host 유효성 검증 - Dockerfile 비루트 사용자(app:1000) 실행 MEDIUM (7건): - SSAFY username 충돌 시 랜덤 suffix로 최대 3회 재시도 - 내부 API username 검증 validID(256자) → validUsername(3~50자) 분리 - 동시 업로드 경합 방지 sync.Mutex 추가 - 프로덕션 환경변수 검증 강화 (DB_PASSWORD, OPERATOR_KEY_HEX, INTERNAL_API_KEY) - Redis 에러 시 멱등성 요청 통과 → 503 거부로 변경 - CORS AllowOrigins 환경변수화 (CORS_ALLOW_ORIGINS) - Refresh 엔드포인트 rate limiting 추가 (IP당 5 req/min) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -426,9 +426,6 @@ func (s *Service) SSAFYLogin(code, state string) (accessToken, refreshToken stri
|
||||
|
||||
ssafyID := userInfo.UserID
|
||||
// SSAFY ID에서 영문 소문자+숫자만 추출하여 안전한 username 생성
|
||||
// NOTE: Username collision is handled by the DB unique constraint.
|
||||
// If collision occurs, the transaction will rollback and return a generic error.
|
||||
// A retry with random suffix could improve UX but is not critical.
|
||||
safeID := sanitizeForUsername(ssafyID)
|
||||
if safeID == "" {
|
||||
safeID = hex.EncodeToString(randomBytes[:8])
|
||||
@@ -437,32 +434,47 @@ func (s *Service) SSAFYLogin(code, state string) (accessToken, refreshToken stri
|
||||
if len(username) > 50 {
|
||||
username = username[:50]
|
||||
}
|
||||
// DB unique constraint 충돌 시 랜덤 suffix로 최대 3회 재시도
|
||||
maxRetries := 3
|
||||
baseUsername := username
|
||||
|
||||
err = s.repo.Transaction(func(txRepo *Repository) error {
|
||||
user = &User{
|
||||
Username: username,
|
||||
PasswordHash: string(hash),
|
||||
Role: RoleUser,
|
||||
SsafyID: &ssafyID,
|
||||
}
|
||||
if err := txRepo.Create(user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.walletCreator != nil {
|
||||
if err := s.walletCreator(user.ID); err != nil {
|
||||
return fmt.Errorf("wallet creation failed: %w", err)
|
||||
for attempt := 0; attempt < maxRetries; attempt++ {
|
||||
if attempt > 0 {
|
||||
suffix := hex.EncodeToString(randomBytes[attempt*2 : attempt*2+4])
|
||||
username = baseUsername + "_" + suffix
|
||||
if len(username) > 50 {
|
||||
username = username[:50]
|
||||
}
|
||||
}
|
||||
if s.profileCreator != nil {
|
||||
if err := s.profileCreator(user.ID); err != nil {
|
||||
return fmt.Errorf("profile creation failed: %w", err)
|
||||
err = s.repo.Transaction(func(txRepo *Repository) error {
|
||||
user = &User{
|
||||
Username: username,
|
||||
PasswordHash: string(hash),
|
||||
Role: RoleUser,
|
||||
SsafyID: &ssafyID,
|
||||
}
|
||||
if err := txRepo.Create(user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.walletCreator != nil {
|
||||
if err := s.walletCreator(user.ID); err != nil {
|
||||
return fmt.Errorf("wallet creation failed: %w", err)
|
||||
}
|
||||
}
|
||||
if s.profileCreator != nil {
|
||||
if err := s.profileCreator(user.ID); err != nil {
|
||||
return fmt.Errorf("profile creation failed: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
return nil
|
||||
})
|
||||
log.Printf("SSAFY user creation attempt %d failed: %v", attempt+1, err)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("SSAFY user creation transaction failed: %v", err)
|
||||
return "", "", nil, fmt.Errorf("계정 생성 실패: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user