package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "time" wruntime "github.com/wailsapp/wails/v2/pkg/runtime" ) type LauncherAuth struct { Id string Name string Token string } type McProfile struct { Id string Name string } type devcodeResp struct { User_code string Device_code string Verification_uri string Expires_in string Interval int Message string } type authenticationResp struct { Access_token string Token_type string Refresh_token string Expires_in string Error string Error_description string } type xboxAuthProperties struct { AuthMethod string SiteName string RpsTicket string } type xboxAuthRequest struct { Properties xboxAuthProperties RelyingParty string TokenType string } type xboxDisplayClaim struct { Uhs string Gtg string Xid string Agg string Usr string Utr string Prv string } type xboxDisplayClaims struct { Xui []xboxDisplayClaim } type xboxAuthResponse struct { IssueInstant time.Time NotAfter time.Time Token string DisplayClaims xboxDisplayClaims } type XSTSProperties struct { SandboxId string UserTokens []string } type XSTSRequest struct { Properties XSTSProperties RelyingParty string TokenType string } type McAuthRequest struct { Xtoken string `json:"xtoken"` Platform string `json:"platform"` } type McAuthResponse struct { Username string Access_token string Expires_in string Token_type string } func AuthCode(a App) (authenticationResp, error) { authentication := authenticationResp{} resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", url.Values{ "client_id": {client_id}, "scope": {"XboxLive.SignIn XboxLive.offline_access"}, }) if err != nil { return authentication, fmt.Errorf("Unable to request device code: %e\n", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return authentication, fmt.Errorf("Unable to request device code: %s\n", resp.Status) } data, _ := io.ReadAll(resp.Body) codeResp := devcodeResp{} json.Unmarshal(data, &codeResp) //display message fmt.Printf("resp: %s\n", data) openbrowser(codeResp.Verification_uri) wruntime.MessageDialog(a.Ctx, wruntime.MessageDialogOptions{Type: wruntime.InfoDialog, Title: "Authentication", Message: codeResp.Message}) ticker := time.NewTicker(time.Second * time.Duration(codeResp.Interval)) for range ticker.C { resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", url.Values{ "client_id": {client_id}, "grant_type": {"urn:ietf:params:oauth:grant-type:device_code"}, "device_code": {codeResp.Device_code}, }) if err != nil { return authentication, fmt.Errorf("Authentication request error %e\n", err) } defer resp.Body.Close() data, _ := io.ReadAll(resp.Body) json.Unmarshal(data, &authentication) if authentication.Error == "" { return authentication, nil } } return authentication, fmt.Errorf("Unknown error") } func TokenRefresh(app App, auth authenticationResp) (authenticationResp, error) { resp, err := http.PostForm("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", url.Values{ "client_id": {client_id}, "grant_type": {"refresh_token"}, "refresh_token": {auth.Refresh_token}, "scope": {"XboxLive.SignIn XboxLive.offline_access"}, }) if err != nil { return authenticationResp{}, fmt.Errorf("unable to request refresh token: %e\n", err) } defer resp.Body.Close() data, _ := io.ReadAll(resp.Body) authResp := authenticationResp{} json.Unmarshal(data, &authResp) if authResp.Error != "" { return authResp, fmt.Errorf("unable to request new token: %s", authResp.Error_description) } return authResp, nil } func MicrosoftAuth(auth authenticationResp) (LauncherAuth, error) { //Xbox Live Auth req, _ := json.Marshal(xboxAuthRequest{Properties: xboxAuthProperties{AuthMethod: "RPS", SiteName: "user.auth.xboxlive.com", RpsTicket: "d=" + auth.Access_token}, RelyingParty: "http://auth.xboxlive.com", TokenType: "JWT"}) client := http.Client{} httpreq, _ := http.NewRequest("POST", "https://user.auth.xboxlive.com/user/authenticate", bytes.NewBuffer(req)) httpreq.Header.Add("x-xbl-contract-version", "1") httpreq.Header.Add("Content-Type", "application/json") httpreq.Header.Add("Accept", "application/json") httpResp, err := client.Do(httpreq) if err != nil { return LauncherAuth{}, fmt.Errorf("unable to obtain xbox live token: %e\n", err) } defer httpResp.Body.Close() if httpResp.StatusCode != 200 { return LauncherAuth{}, fmt.Errorf("unable to obtain xbox live token: %s\n", httpResp.Status) } d, _ := io.ReadAll(httpResp.Body) xblAuth := xboxAuthResponse{} json.Unmarshal(d, &xblAuth) xstsData, _ := json.Marshal(XSTSRequest{Properties: XSTSProperties{SandboxId: "RETAIL", UserTokens: []string{xblAuth.Token}}, RelyingParty: "rp://api.minecraftservices.com/", TokenType: "JWT"}) httpXstsReq, _ := http.NewRequest("POST", "https://xsts.auth.xboxlive.com/xsts/authorize", bytes.NewBuffer(xstsData)) httpXstsReq.Header.Add("Content-Type", "application/json") httpResp, err = client.Do(httpXstsReq) if err != nil { return LauncherAuth{}, fmt.Errorf("unable to obtain minecraft sts token: %e\n", err) } defer httpResp.Body.Close() if httpResp.StatusCode != 200 { return LauncherAuth{}, fmt.Errorf("unable to obtain minecraft sts token: %s\n", httpResp.Status) } d, _ = io.ReadAll(httpResp.Body) mcApi := xboxAuthResponse{} json.Unmarshal(d, &mcApi) mcAuthData, _ := json.Marshal(McAuthRequest{Xtoken: "XBL 3.0 x=" + mcApi.DisplayClaims.Xui[0].Uhs + ";" + mcApi.Token, Platform: "PC_LAUNCHER"}) httpReqMC, _ := http.NewRequest("POST", "https://api.minecraftservices.com/launcher/login", bytes.NewBuffer(mcAuthData)) httpReqMC.Header.Add("Content-Type", "application/json") httpReqMC.Header.Add("Accept", "application/json") resp, err := client.Do(httpReqMC) if err != nil { return LauncherAuth{}, fmt.Errorf("unable to obtain mojang auth token: %e\n", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return LauncherAuth{}, fmt.Errorf("unable to obtain mojang auth token: %s\n", resp.Status) } d, _ = io.ReadAll(resp.Body) mcAuth := McAuthResponse{} json.Unmarshal(d, &mcAuth) httpreq, err = http.NewRequest("GET", "https://api.minecraftservices.com/minecraft/profile", new(bytes.Buffer)) httpreq.Header.Add("Content-Type", "application/json") httpreq.Header.Add("Accept", "application/json") httpreq.Header.Add("Authorization", "Bearer "+mcAuth.Access_token) resp, _ = client.Do(httpreq) if err != nil { return LauncherAuth{}, fmt.Errorf("unable to get profile data: %e\n", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return LauncherAuth{}, fmt.Errorf("unable to get profile data: %s\n", resp.Status) } data, _ := io.ReadAll(resp.Body) profile := McProfile{} json.Unmarshal(data, &profile) return LauncherAuth{Id: profile.Id, Name: profile.Name, Token: mcAuth.Access_token}, nil }