Skip to content

Commit

Permalink
authenticator: add --from-secret to import secret for account
Browse files Browse the repository at this point in the history
Allows authenticator to be created from just base64 encoded secret.
It it also verfied by login into Steam

FR #21
  • Loading branch information
rossengeorgiev committed Oct 12, 2020
1 parent ba920db commit 5c9272d
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 9 deletions.
3 changes: 3 additions & 0 deletions steamctl/commands/authenticator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ def print_help(*args, **kwargs):
scp.add_argument('--force', action='store_true',
help='Overwrite existing authenticator.'
)
scp.add_argument('--from-secret', type=str,
help='Provide the authenticator secret directly. Need to be base64 encoded.'
)
scp.add_argument('account', type=str, help='Account name')
scp.set_defaults(_cmd_func=__name__ + '.cmds:cmd_authenticator_add')

Expand Down
58 changes: 49 additions & 9 deletions steamctl/commands/authenticator/cmds.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import logging
from getpass import getpass
from time import time
from base64 import b64decode
from steamctl import __appname__
from steamctl.utils.storage import UserDataFile, UserDataDirectory
from steamctl.utils.prompt import pmt_confirmation, pmt_input
Expand All @@ -19,7 +21,7 @@ class BetterMWA(webauth.MobileWebAuth):
def __init__(self, username):
webauth.MobileWebAuth.__init__(self, username)

def bcli_login(self, password=None, sa_instance=None):
def bcli_login(self, password=None, auto_twofactor=False, sa_instance=None):
email_code = twofactor_code = ''

while True:
Expand All @@ -30,6 +32,9 @@ def bcli_login(self, password=None, sa_instance=None):
except (webauth.LoginIncorrect, webauth.CaptchaRequired) as exp:
email_code = twofactor_code = ''

if auto_twofactor and sa_instance:
twofactor_code = sa_instance.get_code()

if isinstance(exp, webauth.LoginIncorrect):
prompt = ("Enter password for %s: " if not password else
"Invalid password for %s. Enter password: ")
Expand All @@ -52,6 +57,10 @@ def bcli_login(self, password=None, sa_instance=None):
"Incorrect code. Enter email code: ")
email_code, twofactor_code = input(prompt), ''
except webauth.TwoFactorCodeRequired as exp:
if auto_twofactor:
print("Steam did not accept 2FA code")
raise EOFError

if sa_instance:
print("Authenticator available. Leave blank to use it, or manually enter code")

Expand All @@ -77,32 +86,61 @@ def cmd_authenticator_add(args):
return 1 # error
sa = SteamAuthenticator(secrets_file.read_json())

print("To add an authenticator, first we need to login to Steam")
if args.from_secret:
secret = b64decode(args.from_secret)
if len(secret) != 20:
print("Provided secret length is not 20 bytes (got %s)" % len(secret))
return 1 # error

sa = SteamAuthenticator({
'account_name': account,
'shared_secret': args.from_secret,
'token_gid': 'Imported secret',
'server_time': int(time()),
})
print("To import a secret, we need to login to Steam to verify")
else:
print("To add an authenticator, first we need to login to Steam")
print("Account name:", account)

wa = BetterMWA(account)
try:
wa.bcli_login(sa_instance=sa)
wa.bcli_login(sa_instance=sa, auto_twofactor=bool(args.from_secret))
except (KeyboardInterrupt, EOFError):
print("Login interrupted")
return 1 # error

print("Login successful. Checking pre-conditions...")
print("Login successful. Checking status...")

sa = SteamAuthenticator(backend=wa)
if sa:
sa.backend = wa
else:
sa = SteamAuthenticator(backend=wa)

status = sa.status()
_LOG.debug("Authenticator status: %s", status)

if not status['email_validated']:
print("Account needs a verified email address")
return 1 # error
if args.from_secret:
if status['state'] == 1:
sa.secrets['token_gid'] = status['token_gid']
sa.secrets['server_time'] = status['time_created']
sa.secrets['state'] = status['state']
secrets_file.write_json(sa.secrets)
print("Authenticator added successfully")
return
else:
print("No authenticator on account, but we logged in with 2FA? This is impossible")
return 1 # error

if status['state'] == 1:
print("This account already has an authenticator.")
print("You need to remove it first, before proceeding")
return 1 # error

if not status['email_validated']:
print("Account needs a verified email address")
return 1 # error

if not status['authenticator_allowed']:
print("This account is now allowed to have authenticator")
return 1 # error
Expand Down Expand Up @@ -248,14 +286,16 @@ def cmd_authenticator_list(args):
for secrets_file in UserDataDirectory('authenticator').iter_files('*.json'):
secrets = secrets_file.read_json()
rows.append([
secrets_file.filename,
secrets['account_name'],
secrets['token_gid'],
fmt_datetime(int(secrets['server_time']), utc=args.utc),
'Created via steamctl' if 'serial_number' in secrets else 'Imported from secret'
])

if rows:
print_table(rows,
['Account', 'Token GID', 'Created'],
['Filename', 'Account', 'Token GID', 'Created', 'Note'],
)
else:
print("No authenticators found")
Expand Down

0 comments on commit 5c9272d

Please sign in to comment.