auth.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # mautrix-twitter - A Matrix-Twitter DM puppeting bridge
  2. # Copyright (C) 2020 Tulir Asokan
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. from mauigpapi.state import AndroidState
  17. from mauigpapi.http import AndroidAPI
  18. from mauigpapi.errors import (IGLoginTwoFactorRequiredError, IGLoginBadPasswordError,
  19. IGLoginInvalidUserError, IGLoginError)
  20. from mautrix.bridge.commands import HelpSection, command_handler
  21. from .typehint import CommandEvent
  22. SECTION_AUTH = HelpSection("Authentication", 10, "")
  23. @command_handler(needs_auth=False, management_only=True, help_section=SECTION_AUTH,
  24. help_text="Log in to Instagram", help_args="<_username_> <_password_>")
  25. async def login(evt: CommandEvent) -> None:
  26. if await evt.sender.is_logged_in():
  27. await evt.reply("You're already logged in")
  28. return
  29. elif len(evt.args) < 2:
  30. await evt.reply("**Usage:** `$cmdprefix+sp login <username> <password>`")
  31. return
  32. username = evt.args[0]
  33. password = " ".join(evt.args[1:])
  34. state = AndroidState()
  35. state.device.generate(username)
  36. api = AndroidAPI(state)
  37. await api.simulate_pre_login_flow()
  38. try:
  39. resp = await api.login(username, password)
  40. except IGLoginTwoFactorRequiredError as e:
  41. tfa_info = e.body.two_factor_info
  42. msg = "Username and password accepted, but you have two-factor authentication enabled.\n"
  43. if tfa_info.sms_two_factor_on:
  44. if tfa_info.totp_two_factor_on:
  45. msg += (f"Send the code from your authenticator app "
  46. f"or one sent to {tfa_info.obfuscated_phone_number} here.")
  47. else:
  48. msg += f"Send the code sent to {tfa_info.obfuscated_phone_number} here."
  49. elif tfa_info.totp_two_factor_on:
  50. msg += "Send the code from your authenticator app here."
  51. else:
  52. msg += ("Unfortunately, none of your two-factor authentication methods are currently "
  53. "supported by the bridge.")
  54. return
  55. evt.sender.command_status = {
  56. "action": "Login",
  57. "room_id": evt.room_id,
  58. "next": enter_login_2fa,
  59. "username": tfa_info.username,
  60. "2fa_identifier": tfa_info.two_factor_identifier,
  61. "state": state,
  62. "api": api,
  63. }
  64. except IGLoginInvalidUserError:
  65. await evt.reply("Invalid username")
  66. except IGLoginBadPasswordError:
  67. await evt.reply("Incorrect password")
  68. else:
  69. evt.sender.state = state
  70. user = resp.logged_in_user
  71. await evt.reply(f"Successfully logged in as {user.full_name} ([@{user.username}]"
  72. f"(https://instagram.com/{user.username}), user ID: {user.pk})")
  73. await evt.sender.try_connect()
  74. async def enter_login_2fa(evt: CommandEvent) -> None:
  75. api: AndroidAPI = evt.sender.command_status["api"]
  76. state: AndroidState = evt.sender.command_status["state"]
  77. identifier = evt.sender.command_status["2fa_identifier"]
  78. username = evt.sender.command_status["username"]
  79. evt.sender.command_status = None
  80. try:
  81. user = await api.two_factor_login(username, code="".join(evt.args), identifier=identifier)
  82. except IGLoginError as e:
  83. await evt.reply(f"Failed to log in: {e.body.message}")
  84. except Exception as e:
  85. await evt.reply(f"Failed to log in: {e}")
  86. evt.log.exception("Failed to log in")
  87. else:
  88. evt.sender.state = state
  89. await evt.reply(f"Successfully logged in as {user.full_name} ([@{user.username}]"
  90. f"(https://instagram.com/{user.username}), user ID: {user.pk})")
  91. await evt.sender.try_connect()
  92. @command_handler(needs_auth=True, help_section=SECTION_AUTH, help_text="Disconnect the bridge from"
  93. "your Instagram account")
  94. async def logout(evt: CommandEvent) -> None:
  95. await evt.sender.logout()
  96. await evt.reply("Successfully logged out")