diff --git a/steam/webauth.py b/steam/webauth.py index 58f65d6..e6fdcfa 100644 --- a/steam/webauth.py +++ b/steam/webauth.py @@ -55,7 +55,7 @@ Example usage: """ import json -from time import time +from time import time, sleep from base64 import b64encode from getpass import getpass import six @@ -85,6 +85,8 @@ API_HEADERS = { API_URL = 'https://api.steampowered.com/{}Service/{}/v{}' +SUPPORTED_AUTH_TYPES = [EAuthSessionGuardType.EmailCode, EAuthSessionGuardType.DeviceCode, EAuthSessionGuardType.DeviceConfirmation] + class WebAuth(object): """New WEB Auth class. @@ -219,6 +221,7 @@ class WebAuth(object): self.client_id = resp['response']['client_id'] self.request_id = resp['response']['request_id'] self.steam_id = SteamID(resp['response']['steamid']) + self.allowed_confirmations = [EAuthSessionGuardType(confirm_type['confirmation_type']) for confirm_type in resp['response']['allowed_confirmations']] def _startLoginSession(self): """Starts login session via credentials.""" @@ -243,7 +246,7 @@ class WebAuth(object): self.refresh_token = resp['response']['refresh_token'] self.access_token = resp['response']['access_token'] except KeyError: - raise WebAuthException('Authentication requires 2fa token, which is not provided or invalid') + raise TwoFactorAuthNotProvided('Authentication requires 2fa token, which is not provided or invalid') def _finalizeLogin(self): self.sessionID = generate_session_id() @@ -299,9 +302,11 @@ class WebAuth(object): if self.logged_on: return self.session + username = username or self.username + password = password or self.password + if username == '' or password == '': - if self.username == '' and self.password == '': - raise ValueError("Username or password is provided empty!") + raise LoginIncorrect("Username or password is provided empty!") else: self.username = username self.password = password @@ -370,22 +375,78 @@ class WebAuth(object): If you use email confirm, provide email_required = True, else just provide code. """ - res = self.login( - username, - password, - code, - email_required - ) - if hasattr(res, '__call__'): - while True: - try: - twofactor_code = input('Enter your 2fa/email code: ') - resp = res(twofactor_code) - return resp - except WebAuthException: - pass - else: - return self.session + while True: + try: + res = self.login( + username, + password, + code, + email_required + ) + except LoginIncorrect: + prompt = ("Enter password for %s: " if not password else "Invalid password for %s. Enter password:") + password = getpass(prompt % repr(self.username)) + continue + except TwoFactorAuthNotProvided: + # 2FA handling + allowed = set(self.allowed_confirmations) + if allowed.isdisjoint(SUPPORTED_AUTH_TYPES): + raise AuthTypeNotSupported("Couldn't find a supported auth type for this account.") + + can_confirm_with_app = EAuthSessionGuardType.DeviceConfirmation in allowed + + twofactor_code = '' + while twofactor_code.strip() == '': + prompt = '' + if EAuthSessionGuardType.DeviceCode in allowed: + prompt = 'Enter your Steam Guard code' + elif EAuthSessionGuardType.EmailCode in allowed: + prompt = 'Enter the Steam 2FA code emailed to you' + if can_confirm_with_app: + if prompt == '': + # don't think this is possible, but let's handle it anyways + input("Please confirm Steam Guard on your device and press ENTER to continue") + sleep(10) + else: + prompt += ' (or simply press Enter if approved via app)' + + if prompt != '': + prompt += ': ' + twofactor_code = input(prompt) + + if can_confirm_with_app: + try: + self._pollLoginStatus() # test to see if they've authenticated via app + break + except TwoFactorAuthNotProvided: + # they've not authenticated via the app, let's see if we can use their provided code + pass + + if twofactor_code.strip(): + using_email_code = EAuthSessionGuardType.EmailCode in allowed + self._update_login_token(twofactor_code, EAuthSessionGuardType.EmailCode if using_email_code else EAuthSessionGuardType.DeviceCode) + try: + self._pollLoginStatus() + break + except TwoFactorAuthNotProvided: + print("Invalid auth code. Please try again") + twofactor_code = '' + continue + else: + print("Unauthenticated. Please try again") + self._finalizeLogin() + return self.session + if hasattr(res, '__call__'): + # this should not ever happen at this point as we don't use `email_required` on login() + while True: + try: + twofactor_code = input('Enter your 2fa/email code: ') + resp = res(twofactor_code) + return resp + except WebAuthException: + pass + else: + return self.session #TODO: DEPRECATED, must be rewritten, like WebAuth @@ -506,6 +567,10 @@ class WebAuthException(Exception): pass +class AuthTypeNotSupported(WebAuthException): + pass + + class TwoFactorAuthNotProvided(WebAuthException): pass