浏览代码

Add option to resend 2FA SMS

Tulir Asokan 2 年之前
父节点
当前提交
ebd2177a4a
共有 4 个文件被更改,包括 73 次插入7 次删除
  1. 17 1
      mauigpapi/http/login.py
  2. 5 5
      mauigpapi/types/error.py
  3. 19 1
      mautrix_instagram/commands/auth.py
  4. 32 0
      mautrix_instagram/web/provisioning_api.py

+ 17 - 1
mauigpapi/http/login.py

@@ -25,7 +25,7 @@ from Crypto.Cipher import AES, PKCS1_v1_5
 from Crypto.PublicKey import RSA
 from Crypto.Random import get_random_bytes
 
-from ..types import FacebookLoginResponse, LoginResponse, LogoutResponse
+from ..types import FacebookLoginResponse, LoginErrorResponse, LoginResponse, LogoutResponse
 from .base import BaseAndroidAPI
 
 
@@ -71,6 +71,21 @@ class LoginAPI(BaseAndroidAPI):
             "/api/v1/accounts/one_tap_app_login/", data=req, response_type=LoginResponse
         )
 
+    async def send_two_factor_login_sms(
+        self, username: str, identifier: str
+    ) -> LoginErrorResponse:
+        req = {
+            "two_factor_identifier": identifier,
+            "username": username,
+            "guid": self.state.device.uuid,
+            "device_id": self.state.device.id,
+        }
+        return await self.std_http_post(
+            "/api/v1/accounts/send_two_factor_login_sms/",
+            data=req,
+            response_type=LoginErrorResponse,
+        )
+
     async def two_factor_login(
         self,
         username: str,
@@ -86,6 +101,7 @@ class LoginAPI(BaseAndroidAPI):
             "trust_this_device": "1" if trust_device else "0",
             "guid": self.state.device.uuid,
             "device_id": self.state.device.id,
+            # TOTP = 3, Backup code = 2, SMS = 1
             "verification_method": "3" if is_totp else "1",
         }
         return await self.std_http_post(

+ 5 - 5
mauigpapi/types/error.py

@@ -109,9 +109,9 @@ class LoginTwoFactorInfo(SerializableAttrs):
     two_factor_identifier: str
     show_messenger_code_option: bool
     show_new_login_screen: bool
-    show_trusted_device_option: bool
-    should_opt_in_trusted_device_option: bool
-    pending_trusted_notification: bool
+    should_opt_in_trusted_device_option: Optional[bool] = None
+    pending_trusted_notification: Optional[bool] = None
+    show_trusted_device_option: Optional[bool] = None
     # TODO type
     # sms_not_allowed_reason: Any
     pk: Optional[int] = None
@@ -121,9 +121,9 @@ class LoginTwoFactorInfo(SerializableAttrs):
 
 @dataclass
 class LoginErrorResponse(SerializableAttrs):
-    message: str
     status: str
-    error_type: str
+    message: Optional[str] = None
+    error_type: Optional[str] = None
     error_title: Optional[str] = None
     buttons: Optional[List[LoginErrorResponseButton]] = None
     invalid_credentials: Optional[bool] = None

+ 19 - 1
mautrix_instagram/commands/auth.py

@@ -93,6 +93,7 @@ async def login(evt: CommandEvent) -> None:
             "next": enter_login_2fa,
             "username": tfa_info.username,
             "is_totp": tfa_info.totp_two_factor_on,
+            "has_sms": tfa_info.sms_two_factor_on,
             "2fa_identifier": tfa_info.two_factor_identifier,
         }
         await evt.reply(msg)
@@ -123,9 +124,26 @@ async def enter_login_2fa(evt: CommandEvent) -> None:
     identifier = evt.sender.command_status["2fa_identifier"]
     username = evt.sender.command_status["username"]
     is_totp = evt.sender.command_status["is_totp"]
+    has_sms = evt.sender.command_status["has_sms"]
+    code = "".join(evt.args).lower()
+    if has_sms and code == "resend-sms":
+        try:
+            resp = await api.send_two_factor_login_sms(username, identifier=identifier)
+        except Exception as e:
+            evt.log.exception("Failed to re-request SMS code")
+            await evt.reply(f"Failed to re-request SMS code: {e}")
+        else:
+            await evt.reply(
+                f"Re-requested SMS code to {resp.two_factor_info.obfuscated_phone_number}"
+            )
+            evt.sender.command_status[
+                "2fa_identifier"
+            ] = resp.two_factor_info.two_factor_identifier
+            evt.sender.command_status["is_totp"] = False
+        return
     try:
         resp = await api.two_factor_login(
-            username, code="".join(evt.args), identifier=identifier, is_totp=is_totp
+            username, code=code, identifier=identifier, is_totp=is_totp
         )
     except IGBad2FACodeError:
         await evt.reply(

+ 32 - 0
mautrix_instagram/web/provisioning_api.py

@@ -64,11 +64,13 @@ class ProvisioningAPI:
         self.app.router.add_get("/api/whoami", self.status)
         self.app.router.add_options("/api/login", self.login_options)
         self.app.router.add_options("/api/login/2fa", self.login_options)
+        self.app.router.add_options("/api/login/resend_2fa_sms", self.login_options)
         self.app.router.add_options("/api/login/checkpoint", self.login_options)
         self.app.router.add_options("/api/login/fb", self.login_options)
         self.app.router.add_options("/api/logout", self.login_options)
         self.app.router.add_post("/api/login", self.login)
         self.app.router.add_post("/api/login/2fa", self.login_2fa)
+        self.app.router.add_post("/api/login/resend_2fa_sms", self.login_resend_2fa_sms)
         self.app.router.add_post("/api/login/checkpoint", self.login_checkpoint)
         self.app.router.add_get("/api/login/fb", self.get_fb_login_url)
         self.app.router.add_post("/api/login/fb", self.post_fb_login_token)
@@ -339,6 +341,36 @@ class ProvisioningAPI:
             data = None
         return user, data
 
+    async def login_resend_2fa_sms(self, request: web.Request) -> web.Response:
+        user, data = await self._get_user(request, check_state=True)
+
+        try:
+            username = data["username"]
+            identifier = data["2fa_identifier"]
+        except KeyError as e:
+            raise self._missing_key_error(e)
+
+        api: AndroidAPI = user.command_status["api"]
+        state: AndroidState = user.command_status["state"]
+        if state.login_2fa_username and "@" in username or "+" in username:
+            self.log.debug(
+                "Replacing %s with %s in 2FA SMS re-request", username, state.login_2fa_username
+            )
+            username = state.login_2fa_username
+        track(user, "$login_resend_2fa_sms")
+        try:
+            resp = await api.send_two_factor_login_sms(username, identifier=identifier)
+        except Exception as e:
+            return self._unknown_error(user, username, e, after="2fa sms request")
+        return web.json_response(
+            data={
+                "status": "two-factor",
+                "response": resp.serialize(),
+            },
+            status=202,
+            headers=self._acao_headers,
+        )
+
     async def login_2fa(self, request: web.Request) -> web.Response:
         user, data = await self._get_user(request, check_state=True)