Improve the login and validation system

This commit is contained in:
DevilXD committed 2021-12-03 21:27:17 +01:00
1 parent 4cc6711551
commit e65d992b43
3 files changed
+56 -32

No files matched your search

+1
View File
@@ -6,6 +6,7 @@ from typing import Any, Optional, Union, Dict, Callable
BASE_URL = "https://twitch.tv" BASE_URL = "https://twitch.tv"
AUTH_URL = "https://passport.twitch.tv"
WEBSOCKET_URL = "wss://pubsub-edge.twitch.tv/v1" WEBSOCKET_URL = "wss://pubsub-edge.twitch.tv/v1"
GQL_URL = "https://gql.twitch.tv/gql" GQL_URL = "https://gql.twitch.tv/gql"
CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko" CLIENT_ID = "kimne78kx3ncx6brgo4mv6wki5h1ko"
-5
View File
@@ -25,8 +25,3 @@ class LoginException(RequestException):
class CaptchaRequired(LoginException): class CaptchaRequired(LoginException):
def __init__(self): def __init__(self):
super().__init__("Captcha is required") super().__init__("Captcha is required")
class IncorrectCredentials(LoginException):
def __init__(self):
super().__init__("Incorrect username or password")
+55 -27
View File
@@ -16,11 +16,12 @@ except ImportError:
from channel import Channel from channel import Channel
from websocket import Websocket from websocket import Websocket
from inventory import DropsCampaign from inventory import DropsCampaign
from exceptions import LoginException, CaptchaRequired, IncorrectCredentials from exceptions import LoginException, CaptchaRequired
from constants import ( from constants import (
CLIENT_ID, CLIENT_ID,
USER_AGENT, USER_AGENT,
COOKIES_PATH, COOKIES_PATH,
AUTH_URL,
GQL_URL, GQL_URL,
GQL_OPERATIONS, GQL_OPERATIONS,
GQLOperation, GQLOperation,
@@ -212,15 +213,38 @@ class Twitch:
else: else:
logger.error(f"Channel viewcount update for an offline stream: {channel.name}") logger.error(f"Channel viewcount update for an offline stream: {channel.name}")
async def _validate_password(self, password: str) -> bool:
"""
Use Twitch's password validator to validate the password length, characters required, etc.
Helps avoid running into the CAPTCHA if you mistype your password by mistake.
"""
payload = {"password": password}
async with self._session.post(
f"{AUTH_URL}/api/v1/password_strength", json=payload
) as response:
strength_response = await response.json()
return strength_response["isValid"]
async def get_password(self) -> str:
"""
A simple loop that'll keep asking for password, until it's considered valid.
"""
while True:
password = getpass()
if await self._validate_password(password):
return password
async def _login(self) -> str: async def _login(self) -> str:
logger.debug("Login flow started") logger.debug("Login flow started")
if self.username is None: if self.username is None:
self.username = input("Username: ") self.username = input("Username: ")
if self.password is None: if self.password is None:
self.password = getpass() print(
if not self.password: "\nReminder: Passwords can be pasted in by pressing right click a single time\n"
# catch early empty pass "inside the window. Due to security reasons, no feedback is displayed.\n"
raise IncorrectCredentials() "Make sure not to paste it in twice.\n"
)
self.password = await self.get_password()
payload: Dict[str, Any] = { payload: Dict[str, Any] = {
"username": self.username, "username": self.username,
@@ -231,9 +255,7 @@ class Twitch:
} }
for attempt in range(10): for attempt in range(10):
async with self._session.post( async with self._session.post(f"{AUTH_URL}/login", json=payload) as response:
"https://passport.twitch.tv/login", json=payload
) as response:
login_response = await response.json() login_response = await response.json()
# Feed this back in to avoid running into CAPTCHA if possible # Feed this back in to avoid running into CAPTCHA if possible
@@ -252,9 +274,7 @@ class Twitch:
# wrong password you dummy # wrong password you dummy
logger.debug("Login failed due to incorrect login or pass") logger.debug("Login failed due to incorrect login or pass")
print(f"Incorrect username or password.\nUsername: {self.username}") print(f"Incorrect username or password.\nUsername: {self.username}")
self.password = getpass() self.password = await self.get_password()
if not self.password:
raise IncorrectCredentials()
elif error_code in ( elif error_code in (
3011, # Authy token needed 3011, # Authy token needed
3012, # Invalid authy token 3012, # Invalid authy token
@@ -294,22 +314,30 @@ class Twitch:
# looks like we're missing something # looks like we're missing something
print("Logging in") print("Logging in")
jar = self._session.cookie_jar jar = self._session.cookie_jar
cookie = jar.filter_cookies("https://twitch.tv") # type: ignore while True:
if not cookie: cookie = jar.filter_cookies("https://twitch.tv") # type: ignore
# no cookie - login if not cookie:
await self._login() # no cookie - login
# store our auth token inside the cookie await self._login()
cookie["auth-token"] = cast(str, self._access_token) # store our auth token inside the cookie
elif self._access_token is None: cookie["auth-token"] = cast(str, self._access_token)
# have cookie - get our access token elif self._access_token is None:
self._access_token = cookie["auth-token"].value # have cookie - get our access token
logger.debug("Session restored from cookie") self._access_token = cookie["auth-token"].value
# validate our access token, by obtaining user_id logger.debug("Session restored from cookie")
async with self._session.get( # validate our access token, by obtaining user_id
"https://id.twitch.tv/oauth2/validate", async with self._session.get(
headers={"Authorization": f"OAuth {self._access_token}"} "https://id.twitch.tv/oauth2/validate",
) as response: headers={"Authorization": f"OAuth {self._access_token}"}
validate_response = await response.json() ) as response:
status = response.status
if status == 401:
# the access token we have is invalid - clear the cookie and reauth
jar.clear_domain("twitch.tv")
continue
elif status == 200:
validate_response = await response.json()
break
self._user_id = cookie["persistent"] = validate_response["user_id"] self._user_id = cookie["persistent"] = validate_response["user_id"]
self._is_logged_in.set() self._is_logged_in.set()
print(f"Login successful, User ID: {self._user_id}") print(f"Login successful, User ID: {self._user_id}")