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
|
package steam
import (
"crypto/aes"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"github.com/Philipp15b/go-steam/cryptoutil"
. "github.com/Philipp15b/go-steam/protocol"
. "github.com/Philipp15b/go-steam/protocol/protobuf"
. "github.com/Philipp15b/go-steam/protocol/steamlang"
"github.com/golang/protobuf/proto"
"net/http"
"net/url"
"strconv"
"sync/atomic"
)
type Web struct {
// 64 bit alignment
relogOnNonce uint32
// The `sessionid` cookie required to use the steam website.
// This cookie may contain a characters that will need to be URL-escaped, otherwise
// Steam (probably) interprets is as a string.
// When used as an URL paramter this is automatically escaped by the Go HTTP package.
SessionId string
// The `steamLogin` cookie required to use the steam website. Already URL-escaped.
// This is only available after calling LogOn().
SteamLogin string
// The `steamLoginSecure` cookie required to use the steam website over HTTPs. Already URL-escaped.
// This is only availbile after calling LogOn().
SteamLoginSecure string
webLoginKey string
client *Client
}
func (w *Web) HandlePacket(packet *Packet) {
switch packet.EMsg {
case EMsg_ClientNewLoginKey:
w.handleNewLoginKey(packet)
case EMsg_ClientRequestWebAPIAuthenticateUserNonceResponse:
w.handleAuthNonceResponse(packet)
}
}
// Fetches the `steamLogin` cookie. This may only be called after the first
// WebSessionIdEvent or it will panic.
func (w *Web) LogOn() {
if w.webLoginKey == "" {
panic("Web: webLoginKey not initialized!")
}
go func() {
// retry three times. yes, I know about loops.
err := w.apiLogOn()
if err != nil {
err = w.apiLogOn()
if err != nil {
err = w.apiLogOn()
}
}
if err != nil {
w.client.Emit(WebLogOnErrorEvent(err))
return
}
}()
}
func (w *Web) apiLogOn() error {
sessionKey := make([]byte, 32)
rand.Read(sessionKey)
cryptedSessionKey := cryptoutil.RSAEncrypt(GetPublicKey(EUniverse_Public), sessionKey)
ciph, _ := aes.NewCipher(sessionKey)
cryptedLoginKey := cryptoutil.SymmetricEncrypt(ciph, []byte(w.webLoginKey))
data := make(url.Values)
data.Add("format", "json")
data.Add("steamid", strconv.FormatUint(w.client.SteamId().ToUint64(), 10))
data.Add("sessionkey", string(cryptedSessionKey))
data.Add("encrypted_loginkey", string(cryptedLoginKey))
resp, err := http.PostForm("https://api.steampowered.com/ISteamUserAuth/AuthenticateUser/v0001", data)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 401 {
// our web login key has expired, request a new one
atomic.StoreUint32(&w.relogOnNonce, 1)
w.client.Write(NewClientMsgProtobuf(EMsg_ClientRequestWebAPIAuthenticateUserNonce, new(CMsgClientRequestWebAPIAuthenticateUserNonce)))
return nil
} else if resp.StatusCode != 200 {
return errors.New("steam.Web.apiLogOn: request failed with status " + resp.Status)
}
result := new(struct {
Authenticateuser struct {
Token string
TokenSecure string
}
})
err = json.NewDecoder(resp.Body).Decode(result)
if err != nil {
return err
}
w.SteamLogin = result.Authenticateuser.Token
w.SteamLoginSecure = result.Authenticateuser.TokenSecure
w.client.Emit(new(WebLoggedOnEvent))
return nil
}
func (w *Web) handleNewLoginKey(packet *Packet) {
msg := new(CMsgClientNewLoginKey)
packet.ReadProtoMsg(msg)
w.client.Write(NewClientMsgProtobuf(EMsg_ClientNewLoginKeyAccepted, &CMsgClientNewLoginKeyAccepted{
UniqueId: proto.Uint32(msg.GetUniqueId()),
}))
// number -> string -> bytes -> base64
w.SessionId = base64.StdEncoding.EncodeToString([]byte(strconv.FormatUint(uint64(msg.GetUniqueId()), 10)))
w.client.Emit(new(WebSessionIdEvent))
}
func (w *Web) handleAuthNonceResponse(packet *Packet) {
// this has to be the best name for a message yet.
msg := new(CMsgClientRequestWebAPIAuthenticateUserNonceResponse)
packet.ReadProtoMsg(msg)
w.webLoginKey = msg.GetWebapiAuthenticateUserNonce()
// if the nonce was specifically requested in apiLogOn(),
// don't emit an event.
if atomic.CompareAndSwapUint32(&w.relogOnNonce, 1, 0) {
w.LogOn()
}
}
|