complete yggdrasil package
This commit is contained in:
15
.idea/inspectionProfiles/Project_Default.xml
generated
15
.idea/inspectionProfiles/Project_Default.xml
generated
@ -3,5 +3,20 @@
|
|||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="GoCommentStart" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
<inspection_tool class="GoCommentStart" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="GoExportedElementShouldHaveComment" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
<inspection_tool class="GoExportedElementShouldHaveComment" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="GoUnhandledErrorResult" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<methods>
|
||||||
|
<method importPath="hash" receiver="Hash" name="Write" />
|
||||||
|
<method importPath="strings" receiver="*Builder" name="Write" />
|
||||||
|
<method importPath="strings" receiver="*Builder" name="WriteByte" />
|
||||||
|
<method importPath="bytes" receiver="*Buffer" name="WriteRune" />
|
||||||
|
<method importPath="bytes" receiver="*Buffer" name="Write" />
|
||||||
|
<method importPath="bytes" receiver="*Buffer" name="WriteString" />
|
||||||
|
<method importPath="strings" receiver="*Builder" name="WriteString" />
|
||||||
|
<method importPath="bytes" receiver="*Buffer" name="WriteByte" />
|
||||||
|
<method importPath="strings" receiver="*Builder" name="WriteRune" />
|
||||||
|
<method importPath="math/rand" receiver="*Rand" name="Read" />
|
||||||
|
<method importPath="io" receiver="ReadCloser" name="Close" />
|
||||||
|
</methods>
|
||||||
|
</inspection_tool>
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
@ -1,7 +1,6 @@
|
|||||||
package yggdrasil
|
package yggdrasil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,6 +15,16 @@ type agent struct {
|
|||||||
Version int `json:"version"`
|
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{
|
var defaultAgent = agent{
|
||||||
Name: "Minecraft",
|
Name: "Minecraft",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
@ -23,48 +32,44 @@ var defaultAgent = agent{
|
|||||||
|
|
||||||
// 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"`
|
proof
|
||||||
Password string `json:"password"`
|
|
||||||
ClientToken string `json:"clientToken"`
|
ClientToken string `json:"clientToken"`
|
||||||
RequestUser bool `json:"requestUser"`
|
RequestUser bool `json:"requestUser"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// authResp is the response from Mojang's auth server
|
// authResp is the response from Mojang's auth server
|
||||||
type authResp struct {
|
type authResp struct {
|
||||||
Error string `json:"error"`
|
tokens
|
||||||
ErrorMessage string `json:"errorMessage"`
|
AvailableProfiles []Profile `json:"availableProfiles"` // only present if the agent field was received
|
||||||
Cause string `json:"cause"`
|
|
||||||
|
|
||||||
AccessToken string `json:"accessToken"`
|
SelectedProfile Profile `json:"selectedProfile"` // only present if the agent field was received
|
||||||
ClientToken string `json:"clientToken"` // identical to the one received
|
User struct { // only present if requestUser was true in the request authPayload
|
||||||
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
|
|
||||||
ID string `json:"id"` // hexadecimal
|
ID string `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"`
|
||||||
|
|
||||||
|
*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.
|
// Authenticate authenticates a user using their password.
|
||||||
func Authenticate(user, password string) (*Access, error) {
|
func Authenticate(user, password string) (*Access, error) {
|
||||||
// Payload
|
// Payload
|
||||||
pl := authPayload{
|
pl := authPayload{
|
||||||
Agent: defaultAgent,
|
Agent: defaultAgent,
|
||||||
UserName: user,
|
proof: proof{
|
||||||
Password: password,
|
UserName: user,
|
||||||
|
Password: password,
|
||||||
|
},
|
||||||
ClientToken: uuid.New().String(),
|
ClientToken: uuid.New().String(),
|
||||||
RequestUser: true,
|
RequestUser: true,
|
||||||
}
|
}
|
||||||
@ -77,10 +82,8 @@ func Authenticate(user, password string) (*Access, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ar.Error != "" {
|
if ar.Error != nil {
|
||||||
err = fmt.Errorf("auth fail: %s: %s, %s}",
|
return nil, *ar.Error
|
||||||
ar.Error, ar.ErrorMessage, ar.Cause)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Access{ar: ar, ct: pl.ClientToken}, nil
|
return &Access{ar: ar, ct: pl.ClientToken}, nil
|
||||||
@ -93,3 +96,7 @@ func (a *Access) SelectedProfile() (ID, Name string) {
|
|||||||
func (a *Access) AccessToken() string {
|
func (a *Access) AccessToken() string {
|
||||||
return a.ar.AccessToken
|
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
|
package yggdrasil
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
// Validate checks if an accessToken is usable for authentication with a Minecraft server.
|
// Validate checks if an accessToken is usable for authentication with a Minecraft server.
|
||||||
func (a *Access) Validate() (bool, error) {
|
func (a *Access) Validate() (bool, error) {
|
||||||
pl := struct {
|
pl := a.ar.tokens
|
||||||
AccessToken string `json:"accessToken"`
|
|
||||||
ClientToken string `json:"clientToken"`
|
|
||||||
}{
|
|
||||||
AccessToken: a.ar.AccessToken,
|
|
||||||
ClientToken: a.ar.ClientToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := rowPost("/validate", pl)
|
resp, err := rowPost("/validate", pl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -19,3 +16,21 @@ func (a *Access) Validate() (bool, error) {
|
|||||||
|
|
||||||
return resp.StatusCode == 204, resp.Body.Close()
|
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"
|
"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 AuthURL = "https://authserver.mojang.com"
|
||||||
|
|
||||||
var client http.Client
|
var client http.Client
|
||||||
|
|
||||||
func post(endpoint string, payload interface{}, resp interface{}) error {
|
func post(endpoint string, payload interface{}, resp interface{}) error {
|
||||||
rowResp,err:=rowPost(endpoint,payload)
|
rowResp, err := rowPost(endpoint, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("request fail: %v",err)
|
return fmt.Errorf("request fail: %v", err)
|
||||||
}
|
}
|
||||||
defer rowResp.Body.Close()
|
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