You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

201 lines
7.0 KiB

# -*- coding: utf-8 -*-
"""
This module simplifies the process of obtaining an authenticated session for mobile steam websites.
After authentication is complete, a :class:`requests.Session` is created containing the auth and mobile specific cookies.
The session can be used to access ``steamcommunity.com``, ``store.steampowered.com``, and ``help.steampowered.com``.
The main purpose of a mobile session, is to access the mobile trade confirmations page and to easily access the
ITwoFactorService api, but can also used to access all 'normal' steam pages.
.. warning::
A web session may expire randomly, or when you login from different IP address.
Some pages will return status code `401` when that happens.
Keep in mind if you are trying to write robust code.
Example usage:
.. code:: python
import steam.mobileauth as ma
user = ma.WebAuth('username', 'password')
try:
user.login()
except wa.CaptchaRequired:
print user.captcha_url
# ask a human to solve captcha
user.login(captcha='ABC123')
except wa.EmailCodeRequired:
user.login(email_code='ZXC123')
except wa.TwoFactorCodeRequired:
user.login(twofactor_code='ZXC123')
user.session.get('https://store.steampowered.com/account/history/')
# OR
session = user.login()
session.get('https://store.steampowered.com/account/history')
Alternatively, if Steam Guard is not enabled on the account:
.. code:: python
try:
session = wa.WebAuth('username', 'password').login()
except wa.HTTPError:
pass
The :class:`MobileAuth` instance should be discarded once a session is obtained
as it is not reusable.
"""
import json
from time import time
import sys
from base64 import b64encode
import requests
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from steam.core.crypto import backend
from steam.util.web import make_requests_session
from steam import SteamID
if sys.version_info < (3,):
intBase = long
else:
intBase = int
class MobileAuth(object):
key = None
complete = False #: whether authentication has been completed successfully
session = None #: :class:`requests.Session` (with auth cookies after auth is complete)
captcha_gid = -1
steamid = None #: :class:`steam.steamid.SteamID` (after auth is complete)
oauth = {}
def __init__(self, username, password):
self.__dict__.update(locals())
self.session = make_requests_session()
@property
def captcha_url(self):
if self.captcha_gid == -1:
return None
else:
return "https://store.steampowered.com/login/rendercaptcha/?gid=%s" % self.captcha_gid
def get_rsa_key(self, username):
try:
resp = self.session.post('https://steamcommunity.com/mobilelogin/getrsakey/',
timeout=15,
data={
'username': username,
'donotchache': int(time() * 1000),
},
).json()
except requests.exceptions.RequestException as e:
raise HTTPError(str(e))
return resp
def _load_key(self):
if not self.key:
resp = self.get_rsa_key(self.username)
nums = RSAPublicNumbers(intBase(resp['publickey_exp'], 16),
intBase(resp['publickey_mod'], 16),
)
self.key = backend.load_rsa_public_numbers(nums)
self.timestamp = resp['timestamp']
def login(self, captcha='', email_code='', twofactor_code='', language='english'):
if self.complete:
return self.session
for domain in ['store.steampowered.com', 'help.steampowered.com', 'steamcommunity.com']:
self.session.cookies.set('forceMobile', '1', domain=domain, secure=False)
self.session.cookies.set('mobileClientVersion', '0 (2.1.3)', domain=domain, secure=False)
self.session.cookies.set('mobileClient', 'android', domain=domain, secure=False)
self.session.cookies.set('Steam_Language', 'english', domain=domain, secure=False)
self.session.cookies.set('dob', '', domain=domain, secure=False)
self._load_key()
data = {
'username': self.username,
"password": b64encode(self.key.encrypt(self.password.encode('ascii'), PKCS1v15())),
"emailauth": email_code,
"emailsteamid": str(self.steamid) if email_code else '',
"twofactorcode": twofactor_code,
"captchagid": self.captcha_gid,
"captcha_text": captcha,
"loginfriendlyname": "python-steam webauth",
"rsatimestamp": self.timestamp,
"remember_login": 'true',
"donotcache": int(time() * 100000),
}
data['oauth_client_id'] = 'DE45CD61'
data['oauth_scope'] = 'read_profile write_profile read_client write_client'
data['loginfriendlyname'] = '#login_emailauth_friendlyname_mobile'
try:
resp = self.session.post('https://steamcommunity.com/mobilelogin/dologin/', data=data, timeout=15).json()
except requests.exceptions.RequestException as e:
raise HTTPError(str(e))
self.captcha_gid = -1
if resp['success'] and resp['login_complete']:
self.complete = True
self.password = None
resp['oauth'] = json.loads(resp['oauth'])
self.steamid = SteamID(resp['oauth']['steamid'])
self.oauth = resp['oauth']
for domain in ['store.steampowered.com', 'help.steampowered.com', 'steamcommunity.com']:
self.session.cookies.set('steamLogin', '%s||%s' % (self.steamid, resp['oauth']['wgtoken']),
domain=domain, secure=False)
self.session.cookies.set('steamLoginSecure', '%s||%s' % (self.steamid, resp['oauth']['wgtoken_secure']),
domain=domain, secure=True)
return resp
else:
if resp.get('captcha_needed', False):
self.captcha_gid = resp['captcha_gid']
raise CaptchaRequired(resp['message'])
elif resp.get('emailauth_needed', False):
self.steamid = SteamID(resp['emailsteamid'])
raise EmailCodeRequired(resp['message'])
elif resp.get('requires_twofactor', False):
raise TwoFactorCodeRequired(resp['message'])
else:
raise LoginIncorrect(resp['message'])
return None
class MobileWebAuthException(Exception):
pass
class HTTPError(MobileWebAuthException):
pass
class LoginIncorrect(MobileWebAuthException):
pass
class CaptchaRequired(MobileWebAuthException):
pass
class EmailCodeRequired(MobileWebAuthException):
pass
class TwoFactorCodeRequired(MobileWebAuthException):
pass