add "check Validate" support

This commit is contained in:
Tnze
2019-08-09 00:36:00 +08:00
parent bf46650ffc
commit 8d9d2a7695
6 changed files with 159 additions and 79 deletions

25
cmd/luncher/luncher.go Normal file
View File

@ -0,0 +1,25 @@
package main
import (
"flag"
"fmt"
"github.com/Tnze/go-mc/yggdrasil"
"os"
)
var user = flag.String("user", "", "Can be an email address or player name for unmigrated accounts")
var pswd = flag.String("password", "", "Your password")
func main() {
flag.Parse()
resp, err := yggdrasil.Authenticate(*user, *pswd)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
id, name := resp.SelectedProfile()
fmt.Println("user:", name)
fmt.Println("uuid:", id)
fmt.Println("astk:", resp.AccessToken())
}

View File

@ -374,7 +374,7 @@ func (c *Client) encryptionResponse() ([]byte, []byte, error) {
return nil, nil, err
}
if p.ID != 0x01 {
return nil, nil, fmt.Errorf("0x%02X is not Encryption AuthResp", p.ID)
return nil, nil, fmt.Errorf("0x%02X is not Encryption authResp", p.ID)
}
var (

View File

@ -1,83 +1,32 @@
package yggdrasil
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/google/uuid"
)
// Agent is a struct of auth
type Agent struct {
type Access struct {
ar authResp
ct string
}
// agent is a struct of auth
type agent struct {
Name string `json:"name"`
Version int `json:"version"`
}
// AuthPayload is a yggdrasil request struct
type AuthPayload struct {
Agent Agent `json:"agent"`
// authPayload is a yggdrasil request struct
type authPayload struct {
Agent agent `json:"agent"`
UserName string `json:"username"`
Password string `json:"password"`
ClientToken string `json:"clientToken"`
RequestUser bool `json:"requestUser"`
}
// Authenticate authenticates a user using their password.
func Authenticate(user, password string) (respData AuthResp, err error) {
j, err := json.Marshal(AuthPayload{
Agent: Agent{
Name: "Minecraft",
Version: 1,
},
UserName: user,
Password: password,
ClientToken: "go-mc",
RequestUser: true,
})
if err != nil {
err = fmt.Errorf("encoding json fail: %v", err)
return
}
//Post
client := http.Client{}
PostRequest, err := http.NewRequest(http.MethodPost, "https://authserver.mojang.com/yggdrasil",
bytes.NewReader(j))
if err != nil {
err = fmt.Errorf("make request error: %v", err)
return
}
PostRequest.Header.Set("User-Agent", "go-mc")
PostRequest.Header.Set("Connection", "keep-alive")
PostRequest.Header.Set("Content-Type", "application/json")
resp, err := client.Do(PostRequest)
if err != nil {
err = fmt.Errorf("post yggdrasil fail: %v", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("read yggdrasil resp fail: %v", err)
return
}
err = json.Unmarshal(body, &respData)
if err != nil {
err = fmt.Errorf("unmarshal json data fail: %v", err)
return
}
if respData.Error != "" {
err = fmt.Errorf("yggdrasil fail: {error: %q, errorMessage: %q, cause: %q}",
respData.Error, respData.ErrorMessage, respData.Cause)
return
}
return
}
// AuthResp is the response from Mojang's auth server
type AuthResp struct {
// authResp is the response from Mojang's auth server
type authResp struct {
Error string `json:"error"`
ErrorMessage string `json:"errorMessage"`
Cause string `json:"cause"`
@ -85,21 +34,60 @@ type AuthResp struct {
AccessToken string `json:"accessToken"`
ClientToken string `json:"clientToken"` // identical to the one received
AvailableProfiles []struct {
ID string `json:"ID"` // hexadecimal
Name string `json:"name"`
Legacy bool `json:"legacy"` // In practice, this field only appears in the response if true. Default to false.
} `json:"availableProfiles"` // only present if the Agent field was received
ID uuid.UUID `json:"ID"` // hexadecimal
Name string `json:"name"`
Legacy bool `json:"legacy"` // In practice, this field only appears in the response if true. Default to false.
} `json:"availableProfiles"` // only present if the agent field was received
SelectedProfile struct { // only present if the Agent field was received
ID string `json:"id"`
Name string `json:"name"`
Legacy bool `json:"legacy"`
SelectedProfile struct { // only present if the agent field was received
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Legacy bool `json:"legacy"`
} `json:"selectedProfile"`
User struct { // only present if requestUser was true in the request AuthPayload
ID string `json:"id"` // hexadecimal
User struct { // only present if requestUser was true in the request authPayload
ID uuid.UUID `json:"id"` // hexadecimal
Properties []struct {
Name string `json:"name"`
Value string `json:"value"`
}
} `json:"user"`
}
// Authenticate authenticates a user using their password.
func Authenticate(user, password string) (*Access, error) {
// Payload
pl := authPayload{
Agent: agent{
Name: "Minecraft",
Version: 1,
},
UserName: user,
Password: password,
ClientToken: uuid.New().String(),
RequestUser: true,
}
// Resp
var ar authResp
// Request
err := post("/authenticate", pl, &ar)
if err != nil {
return nil, err
}
if ar.Error != "" {
err = fmt.Errorf("auth fail: %s: %s, %s}",
ar.Error, ar.ErrorMessage, ar.Cause)
return nil, err
}
return &Access{ar: ar, ct: pl.ClientToken}, nil
}
func (a *Access) SelectedProfile() (ID uuid.UUID, Name string) {
return a.ar.SelectedProfile.ID, a.ar.SelectedProfile.Name
}
func (a *Access) AccessToken()string{
return a.ar.AccessToken
}

View File

@ -7,12 +7,12 @@ import (
)
func TestEncodingPayload(t *testing.T) {
j, err := json.Marshal(AuthPayload{
Agent: Agent{
j, err := json.Marshal(authPayload{
Agent: agent{
Name: "Minecraft",
Version: 1,
},
UserName: "mojang account name",
UserName: "mojang account email or name",
Password: "mojang account password",
ClientToken: "client identifier",
RequestUser: true,

21
yggdrasil/validate.go Normal file
View File

@ -0,0 +1,21 @@
package yggdrasil
import "fmt"
// Validate checks if an accessToken is usable for authentication with a Minecraft server.
func (a *Access) Validate() (bool, error) {
pl := struct {
AccessToken string `json:"accessToken"`
ClientToken string `json:"clientToken"`
}{
AccessToken: a.ar.AccessToken,
ClientToken: a.ar.ClientToken,
}
resp, err := rowPost("/validate", pl)
if err != nil {
return false, fmt.Errorf("request fail: %v", err)
}
return resp.StatusCode == 204, resp.Body.Close()
}

View File

@ -7,4 +7,50 @@
// but credentials should never be collected from users. ----- https://wiki.vg
package yggdrasil
var Server = ""
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
var AuthURL = "https://authserver.mojang.com"
var client http.Client
func post(endpoint string, payload interface{}, resp interface{}) error {
rowResp,err:=rowPost(endpoint,payload)
if err != nil {
return fmt.Errorf("request fail: %v",err)
}
defer rowResp.Body.Close()
err = json.NewDecoder(rowResp.Body).Decode(resp)
if err != nil {
return fmt.Errorf("parse resp fail: %v", err)
}
return nil
}
func rowPost(endpoint string, payload interface{}) (*http.Response, error) {
data, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("marshal payload fail: %v", err)
}
PostRequest, err := http.NewRequest(
http.MethodPost,
AuthURL+endpoint,
bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("make request error: %v", err)
}
PostRequest.Header.Set("User-agent", "go-mc")
PostRequest.Header.Set("Connection", "keep-alive")
PostRequest.Header.Set("Content-Type", "application/json")
// Do
return client.Do(PostRequest)
}