From 90bd5de74a3461c5945317bae6cb72b7002dbbfb Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 16 Feb 2022 12:54:41 +0100 Subject: [PATCH] Remove phone and hash from sign in --- readthedocs/misc/v2-migration-guide.rst | 4 ++ telethon/_client/auth.py | 68 +++++++++---------------- telethon/_client/telegrambaseclient.py | 2 +- telethon/_client/telegramclient.py | 32 +++++------- telethon/errors/__init__.py | 1 + telethon/errors/_custom.py | 6 +++ 6 files changed, 47 insertions(+), 66 deletions(-) diff --git a/readthedocs/misc/v2-migration-guide.rst b/readthedocs/misc/v2-migration-guide.rst index 12ae66ba..85eb60a4 100644 --- a/readthedocs/misc/v2-migration-guide.rst +++ b/readthedocs/misc/v2-migration-guide.rst @@ -978,3 +978,7 @@ todo update send_message and send_file docs (well review all functions) album overhaul. use a list of Message instead. is_connected is now a property (consistent with the rest of ``is_`` properties) + +send_code_request now returns a custom type (reducing raw api). +sign_in no longer has phone or phone_hash (these are impl details, and now it's less error prone). also mandatory code=. also no longer is a no-op if already logged in. different error for sign up required. +send code / sign in now only expect a single phone. resend code with new phone is send code, not resend. diff --git a/telethon/_client/auth.py b/telethon/_client/auth.py index a82aec03..6301b9b1 100644 --- a/telethon/_client/auth.py +++ b/telethon/_client/auth.py @@ -196,43 +196,22 @@ async def _start( return self -def _parse_phone_and_hash(self, phone, phone_hash): - """ - Helper method to both parse and validate phone and its hash. - """ - phone = utils.parse_phone(phone) or self._phone - if not phone: - raise ValueError( - 'Please make sure to call send_code_request first.' - ) - - phone_hash = phone_hash or self._phone_code_hash.get(phone, None) - if not phone_hash: - raise ValueError('You also need to provide a phone_code_hash.') - - return phone, phone_hash async def sign_in( self: 'TelegramClient', - phone: str = None, - code: typing.Union[str, int] = None, *, + code: typing.Union[str, int] = None, password: str = None, - bot_token: str = None, - phone_code_hash: str = None) -> 'typing.Union[_tl.User, _tl.auth.SentCode]': - me = await self.get_me() - if me: - return me + bot_token: str = None,) -> 'typing.Union[_tl.User, _tl.auth.SentCode]': + if code: + if not self._phone_code_hash: + raise ValueError('Must call client.send_code_request before sign in') - if phone and code: - phone, phone_code_hash = \ - _parse_phone_and_hash(self, phone, phone_code_hash) + phone, phone_code_hash = self._phone_code_hash # May raise PhoneCodeEmptyError, PhoneCodeExpiredError, # PhoneCodeHashEmptyError or PhoneCodeInvalidError. - request = _tl.fn.auth.SignIn( - phone, phone_code_hash, str(code) - ) + request = _tl.fn.auth.SignIn(*self._phone_code_hash, str(code)) elif password: pwd = await self(_tl.fn.account.GetPassword()) request = _tl.fn.auth.CheckPassword( @@ -244,13 +223,13 @@ async def sign_in( api_id=self._api_id, api_hash=self._api_hash ) else: - raise ValueError('You must provide either phone and code, password, or bot_token.') + raise ValueError('You must provide code, password, or bot_token.') result = await self(request) if isinstance(result, _tl.auth.AuthorizationSignUpRequired): - # Emulate pre-layer 104 behaviour + # The method must return the User but we don't have it, so raise instead (matches pre-layer 104 behaviour) self._tos = result.terms_of_service - raise errors.PhoneNumberUnoccupiedError(request=request) + raise errors.SignUpRequired() return await _update_session_state(self, result.user) @@ -287,8 +266,10 @@ async def sign_up( sys.stderr.write("{}\n".format(self._tos.text)) sys.stderr.flush() - phone, phone_code_hash = \ - _parse_phone_and_hash(self, phone, phone_code_hash) + if not self._phone_code_hash: + raise ValueError('Must call client.send_code_request before sign up') + + phone, phone_code_hash = self._phone_code_hash result = await self(_tl.fn.auth.SignUp( phone_number=phone, @@ -321,6 +302,7 @@ async def _update_session_state(self, user, save=True): seq=state.seq, ) + self._phone_code_hash = None return user @@ -336,15 +318,10 @@ async def _replace_session_state(self, *, save=True, **changes): async def send_code_request( self: 'TelegramClient', phone: str) -> 'SentCode': - result = None - phone = utils.parse_phone(phone) or self._phone - phone_hash = self._phone_code_hash.get(phone) + phone = utils.parse_phone(phone) - if phone_hash: - result = await self( - _tl.fn.auth.ResendCode(phone, phone_hash)) - - self._phone_code_hash[phone] = result.phone_code_hash + if self._phone_code_hash and phone == self._phone_code_hash[0]: + result = await self(_tl.fn.auth.ResendCode(*self._phone_code_hash)) else: try: result = await self(_tl.fn.auth.SendCode( @@ -353,13 +330,14 @@ async def send_code_request( return await self.send_code_request(phone) # phone_code_hash may be empty, if it is, do not save it (#1283) - if result.phone_code_hash: - self._phone_code_hash[phone] = phone_hash = result.phone_code_hash - - self._phone = phone + if not result.phone_code_hash: + # The hash is required to login, so this pretty much means send code failed + raise ValueError('Failed to send code') + self._phone_code_hash = (phone, result.phone_code_hash) return _custom.SentCode._new(result) + async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> _custom.QRLogin: qr_login = _custom.QRLogin(self, ignored_ids or []) await qr_login.recreate() diff --git a/telethon/_client/telegrambaseclient.py b/telethon/_client/telegrambaseclient.py index aaaa9a7f..ca11d177 100644 --- a/telethon/_client/telegrambaseclient.py +++ b/telethon/_client/telegrambaseclient.py @@ -141,7 +141,7 @@ def init( self._connect_timeout = connect_timeout self.flood_sleep_threshold = flood_sleep_threshold self._flood_waited_requests = {} # prevent calls that would floodwait entirely - self._phone_code_hash = {} # used during login to prevent exposing the hash to end users + self._phone_code_hash = None # used during login to prevent exposing the hash to end users # Update handling. self._catch_up = catch_up diff --git a/telethon/_client/telegramclient.py b/telethon/_client/telegramclient.py index 5ac3fa4d..e801b26d 100644 --- a/telethon/_client/telegramclient.py +++ b/telethon/_client/telegramclient.py @@ -395,12 +395,10 @@ class TelegramClient: @forward_call(auth.sign_in) async def sign_in( self: 'TelegramClient', - phone: str = None, - code: typing.Union[str, int] = None, *, + code: typing.Union[str, int] = None, password: str = None, - bot_token: str = None, - phone_code_hash: str = None) -> 'typing.Union[_tl.User, _tl.auth.SentCode]': + bot_token: str = None) -> 'typing.Union[_tl.User, _tl.auth.SentCode]': """ Logs in to Telegram to an existing user or bot account. @@ -411,16 +409,13 @@ class TelegramClient: In most cases, you should simply use `start()` and not this method. Arguments - phone (`str` | `int`): - The phone to send the code to if no code was provided, - or to override the phone that was previously used with - these requests. - code (`str` | `int`): - The code that Telegram sent. Note that if you have sent this - code through the application itself it will immediately - expire. If you want to send the code, obfuscate it somehow. - If you're not doing any of this you can ignore this note. + The code that Telegram sent. + + To login to a user account, you must use `client.send_code_request` first. + + The code will expire immediately if you send it through the application itself + as a safety measure. password (`str`): 2FA password, should be used if a previous call raised @@ -431,22 +426,19 @@ class TelegramClient: This should be the hash the `@BotFather `_ gave you. - phone_code_hash (`str`, optional): - The hash returned by `send_code_request`. This can be left as - `None` to use the last hash known for the phone to be used. + You do not need to call `client.send_code_request` to login to a bot account. Returns - The signed in user, or the information about - :meth:`send_code_request`. + The signed in `User`, if the method did not fail. Example .. code-block:: python phone = '+34 123 123 123' - await client.sign_in(phone) # send code + await client.send_code_request(phone) # send code code = input('enter code: ') - await client.sign_in(phone, code) + await client.sign_in(code=code) """ @forward_call(auth.sign_up) diff --git a/telethon/errors/__init__.py b/telethon/errors/__init__.py index 0d4ea0cc..cc83f2fe 100644 --- a/telethon/errors/__init__.py +++ b/telethon/errors/__init__.py @@ -7,6 +7,7 @@ from ._custom import ( CdnFileTamperedError, BadMessageError, MultiError, + SignUpRequired, ) from ._rpcbase import ( RpcError, diff --git a/telethon/errors/_custom.py b/telethon/errors/_custom.py index f48cdc94..b5088bbf 100644 --- a/telethon/errors/_custom.py +++ b/telethon/errors/_custom.py @@ -150,3 +150,9 @@ class MultiError(Exception): self.results = list(result) self.requests = list(requests) return self + + +class SignUpRequired(Exception): + """ + Occurs when trying to sign in with a phone number that doesn't have an account registered yet. + """