diff --git a/alembic/versions/a9119be92164_add_phone_number_field_to_users.py b/alembic/versions/a9119be92164_add_phone_number_field_to_users.py new file mode 100644 index 00000000..a8892515 --- /dev/null +++ b/alembic/versions/a9119be92164_add_phone_number_field_to_users.py @@ -0,0 +1,25 @@ +"""Add phone number field to users + +Revision ID: a9119be92164 +Revises: b54929c22c86 +Create Date: 2018-09-28 02:38:40.626282 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "a9119be92164" +down_revision = "b54929c22c86" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column("user", sa.Column("tg_phone", sa.String(), nullable=True)) + + +def downgrade(): + with op.batch_alter_table("user") as batch_op: + batch_op.drop_column("tg_phone") diff --git a/mautrix_telegram/commands/auth.py b/mautrix_telegram/commands/auth.py index 929e19f9..fb4bc0ea 100644 --- a/mautrix_telegram/commands/auth.py +++ b/mautrix_telegram/commands/auth.py @@ -173,7 +173,7 @@ async def enter_code_register(evt: CommandEvent) -> Dict: help_text="Get instructions on how to log in.") async def login(evt: CommandEvent) -> Optional[Dict]: if await evt.sender.is_logged_in(): - return await evt.reply("You are already logged in.") + return await evt.reply(f"You are already logged in as {evt.sender.human_tg_id}.") allow_matrix_login = evt.config.get("bridge.allow_matrix_login", True) if allow_matrix_login: @@ -300,7 +300,8 @@ async def sign_in(evt: CommandEvent, **sign_in_info) -> Dict: user = await evt.sender.client.sign_in(**sign_in_info) asyncio.ensure_future(evt.sender.post_login(user), loop=evt.loop) evt.sender.command_status = None - return await evt.reply(f"Successfully logged in as @{user.username}") + name = f"@{user.username}" if user.username else f"+{user.phone}" + return await evt.reply(f"Successfully logged in as {name}") except PhoneCodeExpiredError: return await evt.reply("Phone code expired. Try again with `$cmdprefix+sp login`.") except PhoneCodeInvalidError: diff --git a/mautrix_telegram/db.py b/mautrix_telegram/db.py index b17ef5e8..c78da4a5 100644 --- a/mautrix_telegram/db.py +++ b/mautrix_telegram/db.py @@ -81,6 +81,7 @@ class User(Base): mxid = Column(String, primary_key=True) # type: MatrixUserID tgid = Column(Integer, nullable=True, unique=True) # type: Optional[TelegramID] tg_username = Column(String, nullable=True) + tg_phone = Column(String, nullable=True) saved_contacts = Column(Integer, default=0, nullable=False) contacts = relationship("Contact", uselist=True, cascade="save-update, merge, delete, delete-orphan" diff --git a/mautrix_telegram/user.py b/mautrix_telegram/user.py index e4655425..92efc9d3 100644 --- a/mautrix_telegram/user.py +++ b/mautrix_telegram/user.py @@ -48,7 +48,8 @@ class User(AbstractUser): by_tgid = {} # type: Dict[int, User] def __init__(self, mxid: MatrixUserID, tgid: Optional[TelegramID] = None, - username: Optional[str] = None, db_contacts: Optional[List[DBContact]] = None, + username: Optional[str] = None, phone: Optional[str] = None, + db_contacts: Optional[List[DBContact]] = None, saved_contacts: int = 0, is_bot: bool = False, db_portals: Optional[List[DBPortal]] = None, db_instance: Optional[DBUser] = None) -> None: @@ -57,6 +58,7 @@ class User(AbstractUser): self.tgid = tgid # type: TelegramID self.is_bot = is_bot # type: bool self.username = username # type: str + self.phone = phone # type: str self.contacts = [] # type: List[pu.Puppet] self.saved_contacts = saved_contacts # type: int self.db_contacts = db_contacts # type: List[DBContact] @@ -86,6 +88,10 @@ class User(AbstractUser): match = re.compile("@(.+):(.+)").match(self.mxid) # type: Match return match.group(1) + @property + def human_tg_id(self) -> str: + return f"@{self.username}" if self.username else f"+{self.phone}" or None + # TODO replace with proper displayname getting everywhere @property def displayname(self) -> str: @@ -127,7 +133,8 @@ class User(AbstractUser): def save(self) -> None: self.db_instance.tgid = self.tgid - self.db_instance.username = self.username + self.db_instance.tg_username = self.username + self.db_instance.tg_phone = self.phone self.db_instance.contacts = self.db_contacts self.db_instance.saved_contacts = self.saved_contacts self.db_instance.portals = self.db_portals @@ -145,8 +152,9 @@ class User(AbstractUser): @classmethod def from_db(cls, db_user: DBUser) -> 'User': - return User(db_user.mxid, db_user.tgid, db_user.tg_username, db_user.contacts, - db_user.saved_contacts, False, db_user.portals, db_instance=db_user) + return User(db_user.mxid, db_user.tgid, db_user.tg_username, db_user.tg_phone, + db_user.contacts, db_user.saved_contacts, False, db_user.portals, + db_instance=db_user) # endregion # region Telegram connection management @@ -215,6 +223,9 @@ class User(AbstractUser): if self.username != info.username: self.username = info.username changed = True + if self.phone != info.phone: + self.phone = info.phone + changed = True if self.tgid != info.id: self.tgid = info.id self.by_tgid[self.tgid] = self diff --git a/mautrix_telegram/web/common/auth_api.py b/mautrix_telegram/web/common/auth_api.py index 5bca5c3e..f4b2da93 100644 --- a/mautrix_telegram/web/common/auth_api.py +++ b/mautrix_telegram/web/common/auth_api.py @@ -38,14 +38,15 @@ class AuthAPI(abc.ABC): @abstractmethod def get_login_response(self, status: int = 200, state: str = "", username: str = "", - mxid: str = "", message: str = "", error: str = "", - errcode: str = "") -> web.Response: + phone: str = "", human_tg_id: str = "", mxid: str = "", + message: str = "", error: str = "", errcode: str = "") -> web.Response: raise NotImplementedError() @abstractmethod def get_mx_login_response(self, status: int = 200, state: str = "", username: str = "", - mxid: str = "", message: str = "", error: str = "", - errcode: str = "") -> web.Response: + phone: str = "", human_tg_id: str = "", mxid: str = "", + message: str = "", error: str = "", errcode: str = "" + ) -> web.Response: raise NotImplementedError() async def post_matrix_token(self, user: User, token: str) -> web.Response: @@ -114,7 +115,8 @@ class AuthAPI(abc.ABC): if user.command_status and user.command_status["action"] == "Login": user.command_status = None return self.get_login_response(mxid=user.mxid, state="logged-in", status=200, - username=user_info.username) + username=user_info.username, phone=None, + human_tg_id=f"@{user_info.username}") except AccessTokenInvalidError: return self.get_login_response(mxid=user.mxid, state="token", status=401, errcode="bot_token_invalid", @@ -135,8 +137,10 @@ class AuthAPI(abc.ABC): asyncio.ensure_future(user.post_login(user_info), loop=self.loop) if user.command_status and user.command_status["action"] == "Login": user.command_status = None + human_tg_id = f"@{user_info.username}" if user_info.username else f"+{user_info.phone}" return self.get_login_response(mxid=user.mxid, state="logged-in", status=200, - username=user_info.username) + username=user_info.username, phone=user_info.phone, + human_tg_id=human_tg_id) except PhoneCodeInvalidError: return self.get_login_response(mxid=user.mxid, state="code", status=401, errcode="phone_code_invalid", @@ -168,8 +172,10 @@ class AuthAPI(abc.ABC): asyncio.ensure_future(user.post_login(user_info), loop=self.loop) if user.command_status and user.command_status["action"] == "Login (password entry)": user.command_status = None + human_tg_id = f"@{user_info.username}" if user_info.username else f"+{user_info.phone}" return self.get_login_response(mxid=user.mxid, state="logged-in", status=200, - username=user_info.username) + username=user_info.username, phone=user_info.phone, + human_tg_id=human_tg_id) except PasswordEmptyError: return self.get_login_response(mxid=user.mxid, state="password", status=400, errcode="password_empty", diff --git a/mautrix_telegram/web/provisioning/__init__.py b/mautrix_telegram/web/provisioning/__init__.py index 1ff81995..4124054c 100644 --- a/mautrix_telegram/web/provisioning/__init__.py +++ b/mautrix_telegram/web/provisioning/__init__.py @@ -75,7 +75,7 @@ class ProvisioningAPI(AuthAPI): return self.get_error_response(404, "portal_not_found", "Portal with given Matrix ID not found.") user, _ = await self.get_user(request.query.get("user_id", None), expect_logged_in=None, - require_puppeting=False) + require_puppeting=False) return web.json_response({ "mxid": portal.mxid, "chat_id": get_peer_id(portal.peer), @@ -102,7 +102,7 @@ class ProvisioningAPI(AuthAPI): return self.get_error_response(404, "portal_not_found", "Portal to given Telegram chat not found.") user, _ = await self.get_user(request.query.get("user_id", None), expect_logged_in=None, - require_puppeting=False) + require_puppeting=False) return web.json_response({ "mxid": portal.mxid, "chat_id": get_peer_id(portal.peer), @@ -380,16 +380,18 @@ class ProvisioningAPI(AuthAPI): "errcode": errcode, }, status=status) - def get_mx_login_response(self, status=200, state="", username="", mxid="", message="", - error="", errcode=""): + def get_mx_login_response(self, status=200, state="", username="", phone="", human_tg_id="", + mxid="", message="", error="", errcode=""): raise NotImplementedError() - def get_login_response(self, status=200, state="", username="", mxid="", message="", error="", - errcode="") -> web.Response: - if username: + def get_login_response(self, status=200, state="", username="", phone: str = "", + human_tg_id: str = "", mxid="", message="", error="", errcode="" + ) -> web.Response: + if username or phone: resp = { "state": "logged-in", "username": username, + "phone": phone, } elif message: resp = { @@ -436,7 +438,8 @@ class ProvisioningAPI(AuthAPI): if expect_logged_in is not None: logged_in = await user.is_logged_in() if not expect_logged_in and logged_in: - return user, self.get_login_response(username=user.username, status=409, + return user, self.get_login_response(username=user.username, phone=user.phone, + status=409, error="You are already logged in.", errcode="already_logged_in") elif expect_logged_in and not logged_in: diff --git a/mautrix_telegram/web/provisioning/spec.yaml b/mautrix_telegram/web/provisioning/spec.yaml index 6fe3bcc9..155bbe9b 100644 --- a/mautrix_telegram/web/provisioning/spec.yaml +++ b/mautrix_telegram/web/provisioning/spec.yaml @@ -716,6 +716,9 @@ responses: username: type: string description: The Telegram username the user is logged in as. + phone: + type: string + description: The phone number of the account the user is logged into. BadRequest: description: Invalid JSON. schema: @@ -800,7 +803,7 @@ definitions: example: A. phone: type: string - example: +123456789 + example: 123456789 is_bot: type: boolean example: false @@ -858,6 +861,9 @@ definitions: username: type: string description: The Telegram username the user is logged in as. Only applicable if state=logged-in + phone: + type: string + description: The phone number of the account the user logged into. Only applicable if state=logged-in HumanReadableError: type: string diff --git a/mautrix_telegram/web/public/__init__.py b/mautrix_telegram/web/public/__init__.py index ec6a643e..36717872 100644 --- a/mautrix_telegram/web/public/__init__.py +++ b/mautrix_telegram/web/public/__init__.py @@ -84,7 +84,7 @@ class PublicBridgeWebsite(AuthAPI): if not await user.is_logged_in(): return self.get_login_response(mxid=user.mxid, state=state) - return self.get_login_response(mxid=user.mxid, username=user.username) + return self.get_login_response(mxid=user.mxid, human_tg_id=user.human_tg_id) async def get_matrix_login(self, request: web.Request) -> web.Response: mxid = self.verify_token(request.rel_url.query.get("token", None), endpoint="/matrix-login") @@ -109,18 +109,19 @@ class PublicBridgeWebsite(AuthAPI): return self.get_mx_login_response(mxid=user.mxid) def get_login_response(self, status: int = 200, state: str = "", username: str = "", - mxid: str = "", message: str = "", error: str = "", - errcode: str = "") -> web.Response: + phone: str = "", human_tg_id: str = "", mxid: str = "", + message: str = "", error: str = "", errcode: str = "") -> web.Response: return web.Response(status=status, content_type="text/html", - text=self.login.render(username=username, state=state, error=error, - message=message, mxid=mxid)) + text=self.login.render(human_tg_id=human_tg_id, state=state, + error=error, message=message, mxid=mxid)) def get_mx_login_response(self, status: int = 200, state: str = "", username: str = "", - mxid: str = "", message: str = "", error: str = "", - errcode: str = "") -> web.Response: + phone: str = "", human_tg_id: str = "", mxid: str = "", + message: str = "", error: str = "", errcode: str = "" + ) -> web.Response: return web.Response(status=status, content_type="text/html", - text=self.mx_login.render(username=username, state=state, error=error, - message=message, mxid=mxid)) + text=self.mx_login.render(human_tg_id=human_tg_id, state=state, + error=error, message=message, mxid=mxid)) async def post_matrix_login(self, request: web.Request) -> web.Response: mxid = self.verify_token(request.rel_url.query.get("token", None), endpoint="/matrix-login") @@ -157,7 +158,7 @@ class PublicBridgeWebsite(AuthAPI): return self.get_login_response(mxid=user.mxid, error="You are not whitelisted.", status=403) elif await user.is_logged_in(): - return self.get_login_response(mxid=user.mxid, username=user.username) + return self.get_login_response(mxid=user.mxid, human_tg_id=user.human_tg_id) await user.ensure_started(even_if_no_session=True) diff --git a/mautrix_telegram/web/public/login.html.mako b/mautrix_telegram/web/public/login.html.mako index 96db7bac..b92718fc 100644 --- a/mautrix_telegram/web/public/login.html.mako +++ b/mautrix_telegram/web/public/login.html.mako @@ -51,25 +51,25 @@ along with this program. If not, see .
- % if username: + % if human_tg_id: % if state == "logged-in":

Logged in successfully!

- Logged in as @${username}. + Logged in as ${human_tg_id}. You can now close this page. You should be invited to Telegram portals on Matrix momentarily.

% elif state == "bot-logged-in":

Logged in successfully!

- Logged in as @${username}. + Logged in as ${human_tg_id}. You can now close this page. You should be invited to Telegram portals on Matrix momentarily.

% else:

You're already logged in!

- You're logged in as @${username}. + You're logged in as ${human_tg_id}.

If you want to log in with another account, log out using the logout