fix: 아키텍처 리뷰 이슈 3건 수정
Some checks failed
CI/CD / test (push) Has been cancelled
CI/CD / release (push) Has been cancelled

- 명령줄 토큰 노출 제거 — exec.Command에서 -token 인자 제거, 환경변수(A301_TOKEN)만 사용
- redeemTicket 재시도 추가 — 3회 exponential backoff, 4xx는 즉시 실패
- 임시 추출 디렉토리 defer os.RemoveAll 추가 — 중복 정리 코드 제거

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 15:57:02 +09:00
parent b026520b35
commit 0932ecd39e
3 changed files with 23 additions and 6 deletions

View File

@@ -191,17 +191,15 @@ func doDownload(downloadURL, destDir string) error {
if err != nil { if err != nil {
return fmt.Errorf("임시 추출 디렉토리 생성 실패: %w", err) return fmt.Errorf("임시 추출 디렉토리 생성 실패: %w", err)
} }
defer os.RemoveAll(tmpExtractDir)
if err := extractZip(tmpPath, tmpExtractDir); err != nil { if err := extractZip(tmpPath, tmpExtractDir); err != nil {
os.RemoveAll(tmpExtractDir)
return err return err
} }
if err := moveContents(tmpExtractDir, destDir); err != nil { if err := moveContents(tmpExtractDir, destDir); err != nil {
os.RemoveAll(tmpExtractDir)
return fmt.Errorf("파일 이동 실패: %w", err) return fmt.Errorf("파일 이동 실패: %w", err)
} }
os.RemoveAll(tmpExtractDir)
return nil return nil
} }

View File

@@ -58,7 +58,7 @@ func handleURI(rawURI string) error {
if _, statErr := os.Stat(gamePath); statErr == nil { if _, statErr := os.Stat(gamePath); statErr == nil {
ret := msgBox("One of the plans", "서버에 연결할 수 없습니다.\n설치된 게임을 실행하시겠습니까?\n(업데이트 확인 불가)", mbYesNo|mbQ) ret := msgBox("One of the plans", "서버에 연결할 수 없습니다.\n설치된 게임을 실행하시겠습니까?\n(업데이트 확인 불가)", mbYesNo|mbQ)
if ret == idYes { if ret == idYes {
cmd := exec.Command(gamePath, "-token", token) cmd := exec.Command(gamePath)
cmd.Dir = gameDir cmd.Dir = gameDir
cmd.Env = append(os.Environ(), "A301_TOKEN="+token) cmd.Env = append(os.Environ(), "A301_TOKEN="+token)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
@@ -86,7 +86,7 @@ func handleURI(rawURI string) error {
return err return err
} }
cmd := exec.Command(gamePath, "-token", token) cmd := exec.Command(gamePath)
cmd.Dir = gameDir cmd.Dir = gameDir
cmd.Env = append(os.Environ(), "A301_TOKEN="+token) cmd.Env = append(os.Environ(), "A301_TOKEN="+token)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {

View File

@@ -156,8 +156,24 @@ func fetchServerInfo() (*downloadInfo, error) {
} }
// redeemTicket exchanges a one-time launch ticket for a fresh JWT access token. // redeemTicket exchanges a one-time launch ticket for a fresh JWT access token.
// Retries up to 3 times with exponential backoff on transient errors.
func redeemTicket(ticket string) (string, error) { func redeemTicket(ticket string) (string, error) {
return redeemTicketFrom(redeemTicketURL, ticket) const maxRetries = 3
var lastErr error
for i := range maxRetries {
token, err := redeemTicketFrom(redeemTicketURL, ticket)
if err == nil {
return token, nil
}
lastErr = err
// HTTP 4xx errors should not be retried
var noRetry *errNoRetry
if errors.As(err, &noRetry) {
return "", err
}
time.Sleep(time.Duration(1<<i) * time.Second)
}
return "", fmt.Errorf("인증 실패 (%d회 재시도): %w", maxRetries, lastErr)
} }
func redeemTicketFrom(url, ticket string) (string, error) { func redeemTicketFrom(url, ticket string) (string, error) {
@@ -173,6 +189,9 @@ func redeemTicketFrom(url, ticket string) (string, error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode >= 400 && resp.StatusCode < 500 {
return "", &errNoRetry{fmt.Errorf("런처 인증에 실패했습니다 (HTTP %d)", resp.StatusCode)}
}
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("런처 인증에 실패했습니다 (HTTP %d)", resp.StatusCode) return "", fmt.Errorf("런처 인증에 실패했습니다 (HTTP %d)", resp.StatusCode)
} }