-
Notifications
You must be signed in to change notification settings - Fork 2
/
yggdrasil.go
306 lines (252 loc) · 7.94 KB
/
yggdrasil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// Package yggdrasil provides methods to utilize Mojang's Yggdrasil API.
package yggdrasil
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
)
const authServer = "https://authserver.mojang.com"
// Client holds an access token and a client token.
// After a successful authentication, it will also hold the currently selected profile and the current user.
type Client struct {
AccessToken string
ClientToken string
SelectedProfile Profile
User User
}
// Error holds data about a Yggdrasil or internal error.
type Error struct {
Error string `json:"error"`
ErrorMessage string `json:"errorMessage"`
Cause string `json:"cause"`
StatusCode int
FuncError error
}
// AuthenticationRequest holds data used to make an authentication request.
type AuthenticationRequest struct {
Agent Agent `json:"agent"`
Username string `json:"username"`
Password string `json:"password"`
ClientToken string `json:"clientToken"`
RequestUser bool `json:"requestUser"`
}
// AuthenticationResponse holds data returned from a successful authentication request.
type AuthenticationResponse struct {
AccessToken string `json:"accessToken"`
ClientToken string `json:"clientToken"`
AvailableProfiles []Profile `json:"availableProfiles"`
SelectedProfile Profile `json:"selectedProfile"`
User User `json:"user"`
}
// RefreshRequest holds data used to make a refresh request.
type RefreshRequest struct {
AccessToken string `json:"accessToken"`
ClientToken string `json:"clientToken"`
SelectedProfile Profile `json:"selectedProfile"`
RequestUser bool `json:"requestUser"`
}
// RefreshResponse holds data returned from a successful refresh request.
type RefreshResponse struct {
AccessToken string `json:"accessToken"`
ClientToken string `json:"clientToken"`
SelectedProfile Profile `json:"selectedProfile"`
User User `json:"user"`
}
// ValidateRequest holds data used to make a validate request.
type ValidateRequest struct {
AccessToken string `json:"accessToken"`
ClientToken string `json:"clientToken"`
}
// SignoutRequest holds data used to make a signout request.
type SignoutRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
// InvalidateRequest holds data used to make an invalidate request.
type InvalidateRequest struct {
AccessToken string `json:"accessToken"`
ClientToken string `json:"clientToken"`
}
// Agent holds data about the game that was authenticated for.
type Agent struct {
Name string `json:"name"`
Version int `json:"version"`
}
// Profile holds data about an authenticated user's profile.
type Profile struct {
ID string `json:"id"`
Name string `json:"name"`
Legacy bool `json:"legacy"`
}
// User holds data about an authenticated user.
type User struct {
ID string `json:"id"`
Properties []Property `json:"properties"`
}
// Property holds data about an authenticated user's property.
type Property struct {
Name string `json:"name"`
Value string `json:"value"`
}
// Authenticate attempts to authenticate with Yggdrasil.
func (client *Client) Authenticate(username, password, gameName string, gameVersion int) (*AuthenticationResponse, *Error) {
authRequest := &AuthenticationRequest{
Agent: Agent{
Name: gameName,
Version: gameVersion},
Username: username,
Password: password,
ClientToken: client.ClientToken,
RequestUser: true}
response, err := postJSONRequest("/authenticate", authRequest)
if err != nil {
return nil, &Error{FuncError: err}
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, &Error{FuncError: err}
}
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode > 200 {
var errorResponse *Error
err = json.Unmarshal(body, &errorResponse)
if err != nil {
return nil, &Error{FuncError: err}
}
errorResponse.StatusCode = response.StatusCode
return nil, errorResponse
}
var authResponse *AuthenticationResponse
err = json.Unmarshal(body, &authResponse)
if err != nil {
return nil, &Error{FuncError: err}
}
client.AccessToken = authResponse.AccessToken
client.SelectedProfile = authResponse.SelectedProfile
client.User = authResponse.User
return authResponse, nil
}
// Refresh attempts to refresh an existing access/client token pair to get a new valid access token.
func (client *Client) Refresh() (*RefreshResponse, *Error) {
refreshRequest := &RefreshRequest{
AccessToken: client.AccessToken,
ClientToken: client.ClientToken,
RequestUser: true}
response, err := postJSONRequest("/refresh", refreshRequest)
if err != nil {
return nil, &Error{FuncError: err}
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, &Error{FuncError: err}
}
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode > 200 {
var errorResponse *Error
err = json.Unmarshal(body, &errorResponse)
if err != nil {
return nil, &Error{FuncError: err}
}
errorResponse.StatusCode = response.StatusCode
return nil, errorResponse
}
var refreshResponse *RefreshResponse
err = json.Unmarshal(body, &refreshResponse)
if err != nil {
return nil, &Error{FuncError: err}
}
client.AccessToken = refreshResponse.AccessToken
client.SelectedProfile = refreshResponse.SelectedProfile
client.User = refreshResponse.User
return refreshResponse, nil
}
// Validate attempts to check whether or not an existing access/client token pair is valid.
func (client *Client) Validate() (bool, *Error) {
validateRequest := &ValidateRequest{
AccessToken: client.AccessToken,
ClientToken: client.ClientToken}
response, err := postJSONRequest("/validate", validateRequest)
if err != nil {
return false, &Error{FuncError: err}
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return false, &Error{FuncError: err}
}
defer response.Body.Close()
if response.StatusCode == 204 {
return true, nil
} else if response.StatusCode == 403 {
var errorResponse *Error
err = json.Unmarshal(body, &errorResponse)
if err != nil {
return false, &Error{FuncError: err}
}
errorResponse.StatusCode = response.StatusCode
return false, errorResponse
}
return false, nil
}
// Signout attempts to signout of a legacy Minecraft account.
func (client *Client) Signout(username, password string) (bool, *Error) {
signoutRequest := &SignoutRequest{
Username: username,
Password: password}
response, err := postJSONRequest("/signout", signoutRequest)
if err != nil {
return false, &Error{FuncError: err}
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return false, &Error{FuncError: err}
}
defer response.Body.Close()
if len(body) == 0 {
return true, nil
}
var errorResponse *Error
err = json.Unmarshal(body, &errorResponse)
if err != nil {
return false, &Error{FuncError: err}
}
errorResponse.StatusCode = response.StatusCode
return false, errorResponse
}
// Invalidate attempts to invalidate an existing access/client token pair.
func (client *Client) Invalidate() *Error {
invalidateRequest := &InvalidateRequest{
AccessToken: client.AccessToken,
ClientToken: client.ClientToken}
response, err := postJSONRequest("/invalidate", invalidateRequest)
if err != nil {
return &Error{FuncError: err}
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return &Error{FuncError: err}
}
defer response.Body.Close()
if len(body) == 0 {
return nil
}
var errorResponse *Error
err = json.Unmarshal(body, &errorResponse)
if err != nil {
return &Error{FuncError: err}
}
errorResponse.StatusCode = response.StatusCode
return errorResponse
}
func postJSONRequest(endpoint string, v interface{}) (*http.Response, error) {
body, err := json.Marshal(v)
if err != nil {
return nil, err
}
request, err := http.NewRequest("POST", authServer+endpoint, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
request.Header.Set("User-Agent", "go-yggdrasil/1.0")
return http.DefaultClient.Do(request)
}