144 lines
4.0 KiB
Go
144 lines
4.0 KiB
Go
|
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()
|
||
|
}
|
||
|
}
|