diff --git a/main.go b/main.go index 0fccfef..6f4617e 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "archive/zip" + "bytes" "crypto/sha256" "encoding/hex" "encoding/json" @@ -27,7 +28,8 @@ import ( const ( protocolName = "a301" gameExeName = "A301.exe" - serverInfoURL = "https://a301.api.tolelom.xyz/api/download/info" + serverInfoURL = "https://a301.api.tolelom.xyz/api/download/info" + redeemTicketURL = "https://a301.api.tolelom.xyz/api/auth/redeem-ticket" ) const maxDownloadSize = 2 << 30 // 2GB @@ -883,6 +885,38 @@ func ensureGame(gameDir, gamePath string, serverInfo *downloadInfo) error { return nil } +// ── Ticket redemption ──────────────────────────────────────────────────────── + +func redeemTicket(ticket string) (string, error) { + body, err := json.Marshal(map[string]string{"ticket": ticket}) + if err != nil { + return "", fmt.Errorf("요청 생성 실패: %w", err) + } + resp, err := apiClient.Post(redeemTicketURL, "application/json", bytes.NewReader(body)) + if err != nil { + return "", fmt.Errorf("서버 연결 실패: %w", err) + } + defer resp.Body.Close() + + var result struct { + Token string `json:"token"` + Error string `json:"error"` + } + if err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&result); err != nil { + return "", fmt.Errorf("서버 응답 파싱 실패: %w", err) + } + if resp.StatusCode != 200 { + if result.Error != "" { + return "", fmt.Errorf("티켓 교환 실패: %s", result.Error) + } + return "", fmt.Errorf("티켓 교환 실패 (HTTP %d)", resp.StatusCode) + } + if result.Token == "" { + return "", fmt.Errorf("서버에서 토큰을 받지 못했습니다") + } + return result.Token, nil +} + // ── URI handler ────────────────────────────────────────────────────────────── func handleURI(rawURI string) error { @@ -895,13 +929,27 @@ func handleURI(rawURI string) error { return fmt.Errorf("지원하지 않는 URI 스킴: %s", parsed.Scheme) } - token := parsed.Query().Get("token") - if token == "" { + // ticket 파라미터로 일회용 티켓을 받아 서버에서 JWT로 교환 + ticket := parsed.Query().Get("ticket") + if ticket == "" { + // 하위 호환: token 파라미터도 지원 + ticket = parsed.Query().Get("token") + } + if ticket == "" { return fmt.Errorf("토큰이 없습니다") } - // JWT는 점(.)으로 구분된 3파트 형식이어야 함 - if parts := strings.Split(token, "."); len(parts) != 3 { - return fmt.Errorf("유효하지 않은 토큰 형식입니다") + + var token string + if strings.Count(ticket, ".") == 2 { + // 이미 JWT 형식이면 그대로 사용 + token = ticket + } else { + // 일회용 티켓 → 서버에서 JWT로 교환 + t, err := redeemTicket(ticket) + if err != nil { + return fmt.Errorf("인증 실패: %w", err) + } + token = t } gameDir, err := installDir() @@ -990,7 +1038,7 @@ func main() { case strings.HasPrefix(arg, protocolName+"://"): if err := handleURI(arg); err != nil { - msgBox("One of the plans 런처 - 오류", fmt.Sprintf("실행 실패:\n%v", err), mbOK|mbError) + msgBox("One of the plans 런처 - 오류", fmt.Sprintf("실행 실패:\n%v\n\n수신된 URI:\n%s", err, arg), mbOK|mbError) os.Exit(1) }