summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Philipp15b/go-steam/web.go
blob: 0b14db2c744dc4d6d86a1ea648bbcc52b7737815 (plain) (blame)
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()
	}
}