From 3063f02db3bd86590259ad666eb5b458c32e9e6a Mon Sep 17 00:00:00 2001 From: derrod Date: Sun, 31 May 2020 02:01:39 +0200 Subject: [PATCH] [cli/core] Implement newer Epic authentication scheme Fixes #52 --- legendary/cli.py | 27 ++++++++++++++++++--------- legendary/core.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/legendary/cli.py b/legendary/cli.py index e689ea2..67b1b1e 100644 --- a/legendary/cli.py +++ b/legendary/cli.py @@ -56,6 +56,7 @@ class LegendaryCLI: if self.core.login(): logger.info('Stored credentials are still valid, if you wish to switch to a different ' 'account, run "legendary auth --delete" and try again.') + return except ValueError: pass except InvalidCredentialsError: @@ -76,21 +77,27 @@ class LegendaryCLI: logger.error('No EGS login session found, please login normally.') exit(1) - if not args.auth_code: + exchange_token = '' + if not args.auth_code and not args.session_id: # unfortunately the captcha stuff makes a complete CLI login flow kinda impossible right now... print('Please login via the epic web login!') webbrowser.open( - 'https://www.epicgames.com/id/login?redirectUrl=https%3A%2F%2Fwww.epicgames.com%2Fid%2Fapi%2Fexchange' + 'https://www.epicgames.com/id/login?redirectUrl=https%3A%2F%2Fwww.epicgames.com%2Fid%2Fapi%2Fredirect' ) - print('If web page did not open automatically, please navigate ' - 'to https://www.epicgames.com/id/login in your web browser') - print('- In case you opened the link manually; please open https://www.epicgames.com/id/api/exchange ' - 'in your web browser after you have finished logging in.') - exchange_code = input('Please enter code from JSON response: ') - exchange_token = exchange_code.strip().strip('"') - else: + print('If web page did not open automatically, please manually open the following URL: ' + 'https://www.epicgames.com/id/login?redirectUrl=https://www.epicgames.com/id/api/redirect') + sid = input('Please enter the "sid" value from the JSON response: ') + sid = sid.strip().strip('"') + exchange_token = self.core.auth_sid(sid) + elif args.session_id: + exchange_token = self.core.auth_sid(args.session_id) + elif args.auth_code: exchange_token = args.auth_code + if not exchange_token: + logger.fatal('No exchange token, cannot login.') + return + if self.core.auth_code(exchange_token): logger.info(f'Successfully logged in as "{self.core.lgd.userdata["displayName"]}"') else: @@ -904,6 +911,8 @@ def main(): help='Import Epic Games Launcher authentication data (logs out of EGL)') auth_parser.add_argument('--code', dest='auth_code', action='store', metavar='', help='Use specified exchange code instead of interactive authentication') + auth_parser.add_argument('--sid', dest='session_id', action='store', metavar='', + help='Use specified session id instead of interactive authentication') auth_parser.add_argument('--delete', dest='auth_delete', action='store_true', help='Remove existing authentication (log out)') diff --git a/legendary/core.py b/legendary/core.py index 42bbcc8..e8c2d41 100644 --- a/legendary/core.py +++ b/legendary/core.py @@ -12,7 +12,7 @@ from datetime import datetime, timezone from locale import getdefaultlocale from multiprocessing import Queue from random import choice as randchoice -from requests import Request +from requests import Request, session from requests.exceptions import HTTPError from typing import List, Dict from uuid import uuid4 @@ -88,6 +88,34 @@ class LegendaryCore: """ raise NotImplementedError + def auth_sid(self, sid) -> str: + """ + Handles getting an exchange code from a session id + :param sid: session id + :return: exchange code + """ + s = session() + s.headers.update({ + 'X-Epic-Event-Action': 'login', + 'X-Epic-Event-Category': 'login', + 'X-Epic-Strategy-Flags': '', + 'X-Requested-With': 'XMLHttpRequest' + }) + + # get first set of cookies (EPIC_BEARER_TOKEN etc.) + _ = s.get('https://www.epicgames.com/id/api/set-sid', params=dict(sid=sid)) + # get XSRF-TOKEN and EPIC_SESSION_AP cookie + _ = s.get('https://www.epicgames.com/id/api/csrf') + # finally, get the exchange code + r = s.post('https://www.epicgames.com/id/api/exchange/generate', + headers={'X-XSRF-TOKEN': s.cookies['XSRF-TOKEN']}) + + if r.status_code == 200: + return r.json()['code'] + else: + self.log.error(f'Getting exchange code failed: {r.json()}') + return '' + def auth_code(self, code) -> bool: """ Handles authentication via exchange code (either retrieved manually or automatically)