├── .github └── workflows │ ├── test-auth.yml │ └── test-auth │ └── script.py ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── example_auth.py └── example_auth_with_2fa_enabled.py ├── pyproject.toml ├── requirements.txt ├── riot_auth ├── __init__.py ├── auth.py └── auth_exceptions.py └── setup.cfg /.github/workflows/test-auth.yml: -------------------------------------------------------------------------------- 1 | name: Test Auth 2 | 3 | on: 4 | workflow_dispatch: 5 | # schedule: 6 | # - cron: "0 3 * * *" 7 | 8 | jobs: 9 | auth: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 5 12 | 13 | env: 14 | RIOT_USERNAME: ${{ secrets.RIOT_USERNAME }} 15 | RIOT_PASSWORD: ${{ secrets.RIOT_PASSWORD }} 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: actions/setup-python@v5 21 | with: 22 | python-version: "3.12" 23 | cache: "pip" 24 | 25 | - run: | 26 | python -m pip install --upgrade pip 27 | pip install git+https://github.com/floxay/python-riot-auth.git 28 | 29 | - run: python ./.github/workflows/test-auth/script.py 30 | -------------------------------------------------------------------------------- /.github/workflows/test-auth/script.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | import riot_auth 5 | 6 | 7 | USERNAME = os.getenv("RIOT_USERNAME") 8 | assert USERNAME 9 | PASSWORD = os.getenv("RIOT_PASSWORD") 10 | assert PASSWORD 11 | 12 | auth = riot_auth.RiotAuth() 13 | asyncio.run(auth.authorize(username=USERNAME, password=PASSWORD)) 14 | assert auth.user_id 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Huba Tuba (floxay) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # riot-auth 2 | 3 | ![auth test workflow](https://github.com/floxay/python-riot-auth/actions/workflows/test-auth.yml/badge.svg?branch=main) 4 | 5 | The goal of this project is to bypass Cloudflare's filters (403, error code: 1020). 6 | To do this TLS 1.3 cipher suites and signature algorithms are set via ctypes using Python's bundled libssl in `RiotAuth.create_riot_auth_ssl_ctx()`. 7 | Currently HTTP/2 is not being used as HTTP/1.1 does not appear to be an issue, ...and aiohttp does not support it. 8 | 9 | ## Installation 10 | - `$ pip install git+https://github.com/floxay/python-riot-auth.git` 11 | 12 | ## Examples 13 | - Example(s) can be found in the examples folder. 14 | -------------------------------------------------------------------------------- /examples/example_auth.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | 4 | import riot_auth 5 | 6 | # region asyncio.run() bug workaround for Windows, remove below 3.8 and above 3.10.6 7 | if sys.platform == "win32": 8 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 9 | # endregion 10 | 11 | CREDS = "USERNAME", "PASSWORD" 12 | 13 | auth = riot_auth.RiotAuth() 14 | asyncio.run(auth.authorize(*CREDS)) 15 | 16 | # Note: if you have 2FA enabled, these will be None (see "example_auth_with_2fa_enabled.py"): 17 | print(f"Access Token Type: {auth.token_type}\n") 18 | print(f"Access Token: {auth.access_token}\n") 19 | print(f"Entitlements Token: {auth.entitlements_token}\n") 20 | print(f"User ID: {auth.user_id}") 21 | 22 | # Reauth using cookies. Returns a bool indicating whether the reauth attempt was successful. 23 | asyncio.run(auth.reauthorize()) 24 | -------------------------------------------------------------------------------- /examples/example_auth_with_2fa_enabled.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | 4 | import aioconsole 5 | import riot_auth 6 | 7 | 8 | async def main() -> None: 9 | CREDS = "USERNAME", "PASSWORD" 10 | 11 | auth = riot_auth.RiotAuth() 12 | 13 | multifactor_status = await auth.authorize(*CREDS) 14 | 15 | while multifactor_status is True: 16 | # fetching the code must be asynchronous or blocking 17 | code = await aioconsole.ainput("Input 2fa code: ") 18 | try: 19 | await auth.authorize_mfa(code) 20 | break 21 | except riot_auth.RiotMultifactorError: 22 | print("Invalid 2fa code, please try again") 23 | 24 | print(f"Access Token Type: {auth.token_type}\n") 25 | print(f"Access Token: {auth.access_token}\n") 26 | print(f"Entitlements Token: {auth.entitlements_token}\n") 27 | print(f"User ID: {auth.user_id}") 28 | 29 | # region asyncio.run() bug workaround for Windows, remove below 3.8 and above 3.10.6 30 | if sys.platform == "win32": 31 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 32 | # endregion 33 | 34 | asyncio.run(main()) 35 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.7.0,<3.10.0 2 | -------------------------------------------------------------------------------- /riot_auth/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.9" 2 | 3 | from .auth import ( 4 | RiotAuth, 5 | RiotAuthenticationError, 6 | RiotAuthError, 7 | RiotMultifactorError, 8 | RiotRatelimitError, 9 | RiotUnknownErrorTypeError, 10 | RiotUnknownResponseTypeError, 11 | ) 12 | 13 | __all__ = ( 14 | "RiotAuth", 15 | "RiotAuthenticationError", 16 | "RiotAuthError", 17 | "RiotMultifactorError", 18 | "RiotRatelimitError", 19 | "RiotUnknownErrorTypeError", 20 | "RiotUnknownResponseTypeError", 21 | ) 22 | -------------------------------------------------------------------------------- /riot_auth/auth.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import ctypes 3 | import json 4 | import ssl 5 | import sys 6 | import warnings 7 | from base64 import urlsafe_b64decode 8 | from secrets import token_urlsafe, token_hex 9 | from typing import Dict, List, Optional, Sequence, Tuple, Union 10 | from urllib.parse import parse_qsl, urlsplit 11 | 12 | import aiohttp 13 | 14 | from .auth_exceptions import ( 15 | RiotAuthenticationError, 16 | RiotAuthError, 17 | RiotMultifactorError, 18 | RiotRatelimitError, 19 | RiotUnknownErrorTypeError, 20 | RiotUnknownResponseTypeError, 21 | ) 22 | 23 | __all__ = ( 24 | "RiotAuthenticationError", 25 | "RiotAuthError", 26 | "RiotMultifactorError", 27 | "RiotRatelimitError", 28 | "RiotUnknownErrorTypeError", 29 | "RiotUnknownResponseTypeError", 30 | "RiotAuth", 31 | ) 32 | 33 | 34 | class RiotAuth: 35 | RIOT_CLIENT_USER_AGENT = token_urlsafe(111).replace("_", "W").replace("-", "w") 36 | CIPHERS13 = ":".join( # https://docs.python.org/3/library/ssl.html#tls-1-3 37 | ( 38 | "TLS_CHACHA20_POLY1305_SHA256", 39 | "TLS_AES_128_GCM_SHA256", 40 | "TLS_AES_256_GCM_SHA384", 41 | ) 42 | ) 43 | CIPHERS = ":".join( 44 | ( 45 | "ECDHE-ECDSA-CHACHA20-POLY1305", 46 | "ECDHE-RSA-CHACHA20-POLY1305", 47 | "ECDHE-ECDSA-AES128-GCM-SHA256", 48 | "ECDHE-RSA-AES128-GCM-SHA256", 49 | "ECDHE-ECDSA-AES256-GCM-SHA384", 50 | "ECDHE-RSA-AES256-GCM-SHA384", 51 | "ECDHE-ECDSA-AES128-SHA", 52 | "ECDHE-RSA-AES128-SHA", 53 | "ECDHE-ECDSA-AES256-SHA", 54 | "ECDHE-RSA-AES256-SHA", 55 | "AES128-GCM-SHA256", 56 | "AES256-GCM-SHA384", 57 | "AES128-SHA", 58 | "AES256-SHA", 59 | "DES-CBC3-SHA", # most likely not available 60 | ) 61 | ) 62 | SIGALGS = ":".join( 63 | ( 64 | "ecdsa_secp256r1_sha256", 65 | "rsa_pss_rsae_sha256", 66 | "rsa_pkcs1_sha256", 67 | "ecdsa_secp384r1_sha384", 68 | "rsa_pss_rsae_sha384", 69 | "rsa_pkcs1_sha384", 70 | "rsa_pss_rsae_sha512", 71 | "rsa_pkcs1_sha512", 72 | "rsa_pkcs1_sha1", # will get ignored and won't be negotiated 73 | ) 74 | ) 75 | 76 | def __init__(self) -> None: 77 | self._auth_ssl_ctx = RiotAuth.create_riot_auth_ssl_ctx() 78 | self._cookie_jar = aiohttp.CookieJar() 79 | self.access_token: Optional[str] = None 80 | self.scope: Optional[str] = None 81 | self.id_token: Optional[str] = None 82 | self.token_type: Optional[str] = None 83 | self.expires_at: int = 0 84 | self.user_id: Optional[str] = None 85 | self.entitlements_token: Optional[str] = None 86 | 87 | @staticmethod 88 | def create_riot_auth_ssl_ctx() -> ssl.SSLContext: 89 | ssl_ctx = ssl.create_default_context() 90 | 91 | # https://github.com/python/cpython/issues/88068 92 | addr = id(ssl_ctx) + sys.getsizeof(object()) 93 | ssl_ctx_addr = ctypes.cast(addr, ctypes.POINTER(ctypes.c_void_p)).contents 94 | 95 | libssl: Optional[ctypes.CDLL] = None 96 | if sys.platform.startswith("win32"): 97 | for dll_name in ( 98 | "libssl-3.dll", 99 | "libssl-3-x64.dll", 100 | "libssl-1_1.dll", 101 | "libssl-1_1-x64.dll", 102 | ): 103 | with contextlib.suppress(FileNotFoundError, OSError): 104 | libssl = ctypes.CDLL(dll_name) 105 | break 106 | elif sys.platform.startswith(("linux", "darwin")): 107 | libssl = ctypes.CDLL(ssl._ssl.__file__) # type: ignore 108 | 109 | if libssl is None: 110 | raise NotImplementedError( 111 | "Failed to load libssl. Your platform or distribution might be unsupported, please open an issue." 112 | ) 113 | 114 | with warnings.catch_warnings(): 115 | warnings.filterwarnings("ignore", category=DeprecationWarning) 116 | ssl_ctx.minimum_version = ssl.TLSVersion.TLSv1 # deprecated since 3.10 117 | ssl_ctx.set_alpn_protocols(["http/1.1"]) 118 | ssl_ctx.options |= 1 << 19 # SSL_OP_NO_ENCRYPT_THEN_MAC 119 | libssl.SSL_CTX_set_ciphersuites(ssl_ctx_addr, RiotAuth.CIPHERS13.encode()) 120 | libssl.SSL_CTX_set_cipher_list(ssl_ctx_addr, RiotAuth.CIPHERS.encode()) 121 | # setting SSL_CTRL_SET_SIGALGS_LIST 122 | libssl.SSL_CTX_ctrl(ssl_ctx_addr, 98, 0, RiotAuth.SIGALGS.encode()) 123 | # setting SSL_CTRL_SET_GROUPS_LIST 124 | libssl.SSL_CTX_ctrl(ssl_ctx_addr, 92, 0, ":".join( 125 | ( 126 | "x25519", 127 | "secp256r1", 128 | "secp384r1", 129 | ) 130 | ).encode()) 131 | 132 | # print([cipher["name"] for cipher in ssl_ctx.get_ciphers()]) 133 | return ssl_ctx 134 | 135 | def __update( 136 | self, 137 | extract_jwt: bool = False, 138 | key_attr_pairs: Sequence[Tuple[str, str]] = ( 139 | ("sub", "user_id"), 140 | ("exp", "expires_at"), 141 | ), 142 | **kwargs, 143 | ) -> None: 144 | # ONLY PREDEFINED PUBLIC KEYS ARE SET, rest is silently ignored! 145 | predefined_keys = [key for key in self.__dict__.keys() if key[0] != "_"] 146 | 147 | self.__dict__.update( 148 | (key, val) for key, val in kwargs.items() if key in predefined_keys 149 | ) 150 | 151 | if extract_jwt: # extract additional data from access JWT 152 | additional_data = self.__get_keys_from_access_token(key_attr_pairs) 153 | self.__dict__.update( 154 | (key, val) for key, val in additional_data if key in predefined_keys 155 | ) 156 | 157 | def __get_keys_from_access_token( 158 | self, key_attr_pairs: Sequence[Tuple[str, str]] 159 | ) -> List[ 160 | Tuple[str, Union[str, int, List, Dict, None]] 161 | ]: # List[Tuple[str, JSONType]] 162 | assert self.access_token is not None 163 | payload = self.access_token.split(".")[1] 164 | decoded = urlsafe_b64decode(f"{payload}===") 165 | temp_dict: Dict = json.loads(decoded) 166 | return [(attr, temp_dict.get(key)) for key, attr in key_attr_pairs] 167 | 168 | def __set_tokens_from_uri(self, data: Dict) -> None: 169 | mode = data["response"]["mode"] 170 | uri = data["response"]["parameters"]["uri"] 171 | 172 | result = getattr(urlsplit(uri), mode) 173 | data = dict(parse_qsl(result)) 174 | self.__update(extract_jwt=True, **data) 175 | 176 | async def __fetch_access_token( 177 | self, session: aiohttp.ClientSession, body: Dict, headers: Dict, data: Dict 178 | ) -> bool: 179 | multifactor_status = False 180 | resp_type = data["type"] 181 | 182 | if resp_type != "response": # not reauth 183 | async with session.put( 184 | "https://auth.riotgames.com/api/v1/authorization", 185 | json=body, 186 | # credit for the discovery of the necessity of the Referer header goes to @pradishb 187 | # original commit in league-client repo: https://github.com/sandbox-pokhara/league-client/commit/34d4c4a48b5925c5c6315e955d37654ad8511210 188 | headers=headers | {"referer": f"https://{token_hex(5)}.riotgames.com/"}, 189 | ) as r: 190 | data = await r.json() 191 | resp_type = data["type"] 192 | if resp_type == "response": 193 | ... 194 | elif resp_type == "auth": 195 | err = data.get("error") 196 | if err == "auth_failure": 197 | raise RiotAuthenticationError( 198 | f"Failed to authenticate. Make sure username and password are correct. `{err}`." 199 | ) 200 | elif err == "rate_limited": 201 | raise RiotRatelimitError() 202 | else: 203 | raise RiotUnknownErrorTypeError( 204 | f"Got unknown error `{err}` during authentication." 205 | ) 206 | elif resp_type == "multifactor": 207 | multifactor_status = True 208 | else: 209 | raise RiotUnknownResponseTypeError( 210 | f"Got unknown response type `{resp_type}` during authentication." 211 | ) 212 | 213 | self._cookie_jar = session.cookie_jar 214 | 215 | if not multifactor_status: 216 | self.__set_tokens_from_uri(data) 217 | await self.__fetch_entitlements_token(session) 218 | 219 | return multifactor_status 220 | 221 | async def __fetch_entitlements_token(self, session: aiohttp.ClientSession) -> None: 222 | headers = { 223 | "Accept-Encoding": "deflate, gzip, zstd", 224 | # "user-agent": RiotAuth.RIOT_CLIENT_USER_AGENT % "entitlements", 225 | "user-agent": RiotAuth.RIOT_CLIENT_USER_AGENT, 226 | "Cache-Control": "no-cache", 227 | "Accept": "application/json", 228 | "Authorization": f"{self.token_type} {self.access_token}", 229 | } 230 | 231 | async with session.post( 232 | "https://entitlements.auth.riotgames.com/api/token/v1", 233 | headers=headers, 234 | json={}, 235 | # json={"urn": "urn:entitlement:%"}, 236 | ) as r: 237 | self.entitlements_token = (await r.json())["entitlements_token"] 238 | 239 | async def authorize( 240 | self, username: str, password: str, use_query_response_mode: bool = False 241 | ) -> bool: 242 | """ 243 | Authenticate using username and password. 244 | """ 245 | if username and password: 246 | self._cookie_jar.clear() 247 | 248 | conn = aiohttp.TCPConnector(ssl=self._auth_ssl_ctx) 249 | async with aiohttp.ClientSession( 250 | connector=conn, raise_for_status=True, cookie_jar=self._cookie_jar 251 | ) as session: 252 | headers = { 253 | "Accept-Encoding": "deflate, gzip, zstd", 254 | # "user-agent": RiotAuth.RIOT_CLIENT_USER_AGENT % "rso-auth", 255 | "user-agent": RiotAuth.RIOT_CLIENT_USER_AGENT, 256 | "Cache-Control": "no-cache", 257 | "Accept": "application/json", 258 | } 259 | 260 | # region Begin auth/Reauth 261 | body = { 262 | "acr_values": "", 263 | "claims": "", 264 | "client_id": "riot-client", 265 | "code_challenge": "", 266 | "code_challenge_method": "", 267 | "nonce": token_urlsafe(16), 268 | "redirect_uri": "http://localhost/redirect", 269 | "response_type": "token id_token", 270 | "scope": "openid link ban lol_region account", 271 | } 272 | if use_query_response_mode: 273 | body["response_mode"] = "query" 274 | async with session.post( 275 | "https://auth.riotgames.com/api/v1/authorization", 276 | json=body, 277 | headers=headers, 278 | ) as r: 279 | data: Dict = await r.json() 280 | # endregion 281 | 282 | body = { 283 | "language": "en_US", 284 | "password": password, 285 | "region": None, 286 | "remember": False, 287 | "type": "auth", 288 | "username": username, 289 | } 290 | return await self.__fetch_access_token(session, body, headers, data) 291 | 292 | async def authorize_mfa(self, code: str) -> None: 293 | """ 294 | Send the 2FA and finish authentication. 295 | """ 296 | conn = aiohttp.TCPConnector(ssl=self._auth_ssl_ctx) 297 | async with aiohttp.ClientSession( 298 | connector=conn, raise_for_status=True, cookie_jar=self._cookie_jar 299 | ) as session: 300 | headers = { 301 | "Accept-Encoding": "deflate, gzip, zstd", 302 | # "user-agent": RiotAuth.RIOT_CLIENT_USER_AGENT % "rso-auth", 303 | "user-agent": RiotAuth.RIOT_CLIENT_USER_AGENT, 304 | "Cache-Control": "no-cache", 305 | "Accept": "application/json", 306 | } 307 | body = { 308 | "type": "multifactor", 309 | "rememberDevice": "false", 310 | "code": code, 311 | } 312 | if await self.__fetch_access_token( 313 | session, body, headers, data={"type": "multifactor"} 314 | ): 315 | raise RiotMultifactorError( 316 | "Auth with Multi-factor failed. Make sure 2FA code is correct." 317 | ) 318 | 319 | async def reauthorize(self) -> bool: 320 | """ 321 | Reauthenticate using cookies. 322 | 323 | Returns a ``bool`` indicating success or failure. 324 | """ 325 | try: 326 | await self.authorize("", "") 327 | return True 328 | except RiotAuthenticationError: # because credentials are empty 329 | return False 330 | -------------------------------------------------------------------------------- /riot_auth/auth_exceptions.py: -------------------------------------------------------------------------------- 1 | __all__ = ( 2 | "RiotAuthenticationError", 3 | "RiotAuthError", 4 | "RiotMultifactorError", 5 | "RiotRatelimitError", 6 | "RiotUnknownErrorTypeError", 7 | "RiotUnknownResponseTypeError", 8 | ) 9 | 10 | 11 | class RiotAuthError(Exception): 12 | """Base class for RiotAuth errors.""" 13 | 14 | 15 | class RiotAuthenticationError(RiotAuthError): 16 | """Failed to authenticate.""" 17 | 18 | 19 | class RiotRatelimitError(RiotAuthError): 20 | """Ratelimit error.""" 21 | 22 | 23 | class RiotMultifactorError(RiotAuthError): 24 | """Multi-factor failed.""" 25 | 26 | 27 | class RiotUnknownResponseTypeError(RiotAuthError): 28 | """Unknown response type.""" 29 | 30 | 31 | class RiotUnknownErrorTypeError(RiotAuthError): 32 | """Unknown response error type.""" 33 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = riot-auth 3 | version = attr: riot_auth.__version__ 4 | author = floxay 5 | description = "A library to get various Riot tokens and get around Cloudflare's filter during auth." 6 | long_description = file: README.md 7 | long_description_content_type = text/markdown 8 | url = https://github.com/floxay/python-riot-auth 9 | license = MIT 10 | license_files = LICENSE 11 | classifiers = 12 | Intended Audience :: Developers 13 | 14 | Operating System :: POSIX 15 | Operating System :: MacOS X 16 | Operating System :: Microsoft :: Windows 17 | 18 | License :: OSI Approved :: MIT License 19 | 20 | Programming Language :: Python 21 | Programming Language :: Python :: 3 22 | Programming Language :: Python :: 3.6 23 | Programming Language :: Python :: 3.7 24 | Programming Language :: Python :: 3.8 25 | Programming Language :: Python :: 3.9 26 | Programming Language :: Python :: 3.10 27 | Programming Language :: Python :: 3.11 28 | 29 | [options] 30 | packages = find: 31 | install_requires = 32 | aiohttp >= 3.7.0, < 3.10.0 33 | python_requires = >=3.6 34 | 35 | [options.packages.find] 36 | exclude = 37 | examples 38 | --------------------------------------------------------------------------------