forked from lug/matterbridge
		
	
		
			
				
	
	
		
			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()
 | |
| 	}
 | |
| }
 | 
