add "check Validate" support
This commit is contained in:
25
cmd/luncher/luncher.go
Normal file
25
cmd/luncher/luncher.go
Normal 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())
|
||||||
|
}
|
@ -374,7 +374,7 @@ func (c *Client) encryptionResponse() ([]byte, []byte, error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if p.ID != 0x01 {
|
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 (
|
var (
|
||||||
|
@ -1,83 +1,32 @@
|
|||||||
package yggdrasil
|
package yggdrasil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"github.com/google/uuid"
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Agent is a struct of auth
|
type Access struct {
|
||||||
type Agent struct {
|
ar authResp
|
||||||
|
ct string
|
||||||
|
}
|
||||||
|
|
||||||
|
// agent is a struct of auth
|
||||||
|
type agent struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthPayload is a yggdrasil request struct
|
// authPayload is a yggdrasil request struct
|
||||||
type AuthPayload struct {
|
type authPayload struct {
|
||||||
Agent Agent `json:"agent"`
|
Agent agent `json:"agent"`
|
||||||
UserName string `json:"username"`
|
UserName string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
ClientToken string `json:"clientToken"`
|
ClientToken string `json:"clientToken"`
|
||||||
RequestUser bool `json:"requestUser"`
|
RequestUser bool `json:"requestUser"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate authenticates a user using their password.
|
// authResp is the response from Mojang's auth server
|
||||||
func Authenticate(user, password string) (respData AuthResp, err error) {
|
type authResp struct {
|
||||||
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 {
|
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
ErrorMessage string `json:"errorMessage"`
|
ErrorMessage string `json:"errorMessage"`
|
||||||
Cause string `json:"cause"`
|
Cause string `json:"cause"`
|
||||||
@ -85,21 +34,60 @@ type AuthResp struct {
|
|||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
ClientToken string `json:"clientToken"` // identical to the one received
|
ClientToken string `json:"clientToken"` // identical to the one received
|
||||||
AvailableProfiles []struct {
|
AvailableProfiles []struct {
|
||||||
ID string `json:"ID"` // hexadecimal
|
ID uuid.UUID `json:"ID"` // hexadecimal
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Legacy bool `json:"legacy"` // In practice, this field only appears in the response if true. Default to false.
|
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
|
} `json:"availableProfiles"` // only present if the agent field was received
|
||||||
|
|
||||||
SelectedProfile struct { // only present if the Agent field was received
|
SelectedProfile struct { // only present if the agent field was received
|
||||||
ID string `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Legacy bool `json:"legacy"`
|
Legacy bool `json:"legacy"`
|
||||||
} `json:"selectedProfile"`
|
} `json:"selectedProfile"`
|
||||||
User struct { // only present if requestUser was true in the request AuthPayload
|
User struct { // only present if requestUser was true in the request authPayload
|
||||||
ID string `json:"id"` // hexadecimal
|
ID uuid.UUID `json:"id"` // hexadecimal
|
||||||
Properties []struct {
|
Properties []struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
} `json:"user"`
|
} `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
|
||||||
|
}
|
@ -7,12 +7,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestEncodingPayload(t *testing.T) {
|
func TestEncodingPayload(t *testing.T) {
|
||||||
j, err := json.Marshal(AuthPayload{
|
j, err := json.Marshal(authPayload{
|
||||||
Agent: Agent{
|
Agent: agent{
|
||||||
Name: "Minecraft",
|
Name: "Minecraft",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
},
|
},
|
||||||
UserName: "mojang account name",
|
UserName: "mojang account email or name",
|
||||||
Password: "mojang account password",
|
Password: "mojang account password",
|
||||||
ClientToken: "client identifier",
|
ClientToken: "client identifier",
|
||||||
RequestUser: true,
|
RequestUser: true,
|
||||||
|
21
yggdrasil/validate.go
Normal file
21
yggdrasil/validate.go
Normal 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()
|
||||||
|
}
|
@ -7,4 +7,50 @@
|
|||||||
// but credentials should never be collected from users. ----- https://wiki.vg
|
// but credentials should never be collected from users. ----- https://wiki.vg
|
||||||
package yggdrasil
|
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)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user