complete yggdrasil package
This commit is contained in:
@ -1,7 +1,6 @@
|
||||
package yggdrasil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@ -16,6 +15,16 @@ type agent struct {
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type proof struct {
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type tokens struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
ClientToken string `json:"clientToken"`
|
||||
}
|
||||
|
||||
var defaultAgent = agent{
|
||||
Name: "Minecraft",
|
||||
Version: 1,
|
||||
@ -23,48 +32,44 @@ var defaultAgent = agent{
|
||||
|
||||
// authPayload is a yggdrasil request struct
|
||||
type authPayload struct {
|
||||
Agent agent `json:"agent"`
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Agent agent `json:"agent"`
|
||||
proof
|
||||
ClientToken string `json:"clientToken"`
|
||||
RequestUser bool `json:"requestUser"`
|
||||
}
|
||||
|
||||
// authResp is the response from Mojang's auth server
|
||||
type authResp struct {
|
||||
Error string `json:"error"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
Cause string `json:"cause"`
|
||||
tokens
|
||||
AvailableProfiles []Profile `json:"availableProfiles"` // only present if the agent field was received
|
||||
|
||||
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
|
||||
|
||||
SelectedProfile struct { // only present if the agent field was received
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Legacy bool `json:"legacy"`
|
||||
} `json:"selectedProfile"`
|
||||
User struct { // only present if requestUser was true in the request authPayload
|
||||
SelectedProfile Profile `json:"selectedProfile"` // only present if the agent field was received
|
||||
User struct { // only present if requestUser was true in the request authPayload
|
||||
ID string `json:"id"` // hexadecimal
|
||||
Properties []struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
} `json:"user"`
|
||||
|
||||
*Error
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
//Legacy bool `json:"legacy"` // we don't care
|
||||
}
|
||||
|
||||
// Authenticate authenticates a user using their password.
|
||||
func Authenticate(user, password string) (*Access, error) {
|
||||
// Payload
|
||||
pl := authPayload{
|
||||
Agent: defaultAgent,
|
||||
UserName: user,
|
||||
Password: password,
|
||||
Agent: defaultAgent,
|
||||
proof: proof{
|
||||
UserName: user,
|
||||
Password: password,
|
||||
},
|
||||
ClientToken: uuid.New().String(),
|
||||
RequestUser: true,
|
||||
}
|
||||
@ -77,10 +82,8 @@ func Authenticate(user, password string) (*Access, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ar.Error != "" {
|
||||
err = fmt.Errorf("auth fail: %s: %s, %s}",
|
||||
ar.Error, ar.ErrorMessage, ar.Cause)
|
||||
return nil, err
|
||||
if ar.Error != nil {
|
||||
return nil, *ar.Error
|
||||
}
|
||||
|
||||
return &Access{ar: ar, ct: pl.ClientToken}, nil
|
||||
@ -93,3 +96,7 @@ func (a *Access) SelectedProfile() (ID, Name string) {
|
||||
func (a *Access) AccessToken() string {
|
||||
return a.ar.AccessToken
|
||||
}
|
||||
|
||||
func (a *Access) AvailableProfiles() []Profile {
|
||||
return a.ar.AvailableProfiles
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
package yggdrasil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ExampleAuthenticate() {
|
||||
resp, err := Authenticate("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(resp.SelectedProfile())
|
||||
fmt.Println(resp.AccessToken())
|
||||
}
|
39
yggdrasil/refresh.go
Normal file
39
yggdrasil/refresh.go
Normal file
@ -0,0 +1,39 @@
|
||||
package yggdrasil
|
||||
|
||||
import "fmt"
|
||||
|
||||
type refreshPayload struct {
|
||||
tokens
|
||||
SelectedProfile *Profile `json:"selectedProfile,omitempty"`
|
||||
|
||||
RequestUser bool `json:"requestUser"`
|
||||
}
|
||||
|
||||
// Refresh refreshes a valid accessToken.
|
||||
//
|
||||
// It can be used to keep a user logged in between
|
||||
// gaming sessions and is preferred over storing
|
||||
// the user's password in a file
|
||||
func (a *Access) Refresh(profile *Profile) error {
|
||||
pl := refreshPayload{
|
||||
tokens: a.ar.tokens,
|
||||
SelectedProfile: profile, //used to change profile, don't use now
|
||||
RequestUser: true,
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
*authResp
|
||||
*Error
|
||||
}{authResp: &a.ar}
|
||||
|
||||
err := post("/refresh", pl, &resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("post fail: %v", err)
|
||||
}
|
||||
|
||||
if resp.Error != nil {
|
||||
return resp.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
30
yggdrasil/signout.go
Normal file
30
yggdrasil/signout.go
Normal file
@ -0,0 +1,30 @@
|
||||
package yggdrasil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// SignOut invalidates accessTokens using an account's username and password.
|
||||
func SignOut(user, password string) error {
|
||||
pl := proof{
|
||||
UserName: user,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
resp, err := rowPost("/signout", pl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request fail: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 204 {
|
||||
var err Error
|
||||
if err := json.NewDecoder(resp.Body).Decode(&err); err != nil {
|
||||
return fmt.Errorf("unmarshal error fail: %v", err)
|
||||
}
|
||||
return fmt.Errorf("invalidate error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,16 +1,13 @@
|
||||
package yggdrasil
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// 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,
|
||||
}
|
||||
pl := a.ar.tokens
|
||||
|
||||
resp, err := rowPost("/validate", pl)
|
||||
if err != nil {
|
||||
@ -19,3 +16,21 @@ func (a *Access) Validate() (bool, error) {
|
||||
|
||||
return resp.StatusCode == 204, resp.Body.Close()
|
||||
}
|
||||
|
||||
// Invalidate invalidates accessTokens using a client/access token pair.
|
||||
func (a *Access) Invalidate() error {
|
||||
pl := a.ar.tokens
|
||||
|
||||
resp, err := rowPost("/invalidate", pl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request fail: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 204 {
|
||||
content, _ := ioutil.ReadAll(resp.Body)
|
||||
return fmt.Errorf("invalidate error: %v: %s", resp.Status, content)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -14,14 +14,24 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Err string `json:"error"`
|
||||
ErrMsg string `json:"errorMessage"`
|
||||
Cause string `json:"cause"`
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return e.Err + ": " + e.ErrMsg + ", " + e.Cause
|
||||
}
|
||||
|
||||
var AuthURL = "https://authserver.mojang.com"
|
||||
|
||||
var client http.Client
|
||||
|
||||
func post(endpoint string, payload interface{}, resp interface{}) error {
|
||||
rowResp,err:=rowPost(endpoint,payload)
|
||||
rowResp, err := rowPost(endpoint, payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request fail: %v",err)
|
||||
return fmt.Errorf("request fail: %v", err)
|
||||
}
|
||||
defer rowResp.Body.Close()
|
||||
|
||||
|
72
yggdrasil/yggdrasil_test.go
Normal file
72
yggdrasil/yggdrasil_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
package yggdrasil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ExampleAuthenticate() {
|
||||
resp, err := Authenticate("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(resp.SelectedProfile())
|
||||
fmt.Println(resp.AccessToken())
|
||||
}
|
||||
|
||||
func Example(){
|
||||
var user, password string// set your proof
|
||||
|
||||
// Sign in
|
||||
resp, err := Authenticate(user, password)
|
||||
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())
|
||||
|
||||
// Refresh access token
|
||||
if err := resp.Refresh(nil); 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())
|
||||
|
||||
// Check access token
|
||||
ok, err := resp.Validate()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("at status: ", ok)
|
||||
|
||||
// Invalidate access token
|
||||
err=resp.Invalidate()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check access token
|
||||
ok, err = resp.Validate()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("at status: ", ok)
|
||||
|
||||
// Sign out
|
||||
err = SignOut(user, password)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user