From 4c9fad22620dbead7f46f631b41090bfba22da0e Mon Sep 17 00:00:00 2001 From: Karl Tiedt Date: Sat, 2 May 2026 22:11:50 -0500 Subject: [PATCH] feat: implement EOS device authorization flow --- egs.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/egs.go b/egs.go index 14bb279..f4da4fd 100644 --- a/egs.go +++ b/egs.go @@ -18,6 +18,7 @@ const ( egsOAuthURL = "account-public-service-prod03.ol.epicgames.com" eosAuthHeader = "eHl6YTc4OTFwNUQ3czlSNkdtNm1vVEhXR2xvZXJwN0I6S25oMThkdTROVmxGcyszdVErWlBwRENWdG8wV1lmNHlYUDgrT2N3VnQxbw==" eosDeploymentID = "da32ae9c12ae40e8a112c52e1f17f3ba" // Rocket League + eosClientID = "xyza7891p5D7s9R6Gm6moTHWGloerp7B" ) type TokenResponse struct { @@ -55,6 +56,14 @@ type EOSTokenResponse struct { AuthTime string `json:"auth_time"` } +type DeviceAuthResponse struct { + UserCode string `json:"user_code"` + DeviceCode string `json:"device_code"` + VerificationURI string `json:"verification_uri"` + ExpiresIn int `json:"expires_in"` + Interval int `json:"interval"` +} + // EGS provides an authentication layer for Epic Games Store -- largely adapted from https://github.com/derrod/legendary type EGS struct { client *http.Client @@ -263,3 +272,45 @@ func (e *EGS) RevokeEOSToken(accessToken string) error { return nil } + +// AuthenticateWithDeviceCode initiates the EOS device authorization flow +func (e *EGS) AuthenticateWithDeviceCode() (*DeviceAuthResponse, error) { + req, err := http.NewRequest("POST", "https://api.epicgames.dev/epic/oauth/v2/deviceAuthorization", strings.NewReader("client_id="+eosClientID)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Authorization", "Basic "+eosAuthHeader) + req.Header.Set("User-Agent", egsUserAgent) + + resp, err := e.client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %s: %s", resp.Status, string(body)) + } + + var deviceAuthResp DeviceAuthResponse + if err := json.Unmarshal(body, &deviceAuthResp); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + return &deviceAuthResp, nil +} + +// PollEOSToken polls the token endpoint with a device code to get an EOS token +func (e *EGS) PollEOSToken(deviceCode string) (*EOSTokenResponse, error) { + return e.requestEOSToken(map[string]string{ + "grant_type": "device_code", + "device_code": deviceCode, + }) +}