From 1c37871bf85e8f1754345b9ea84ee76191e8478f Mon Sep 17 00:00:00 2001
From: Tulir Asokan <tulir@maunium.net>
Date: Mon, 11 May 2020 23:48:03 +0300
Subject: [PATCH] Fix logging in with cookies and remove user agent stuff

---
 .../97e9f7a3c070_remove_user_agent_from_db.py | 26 +++++++++++
 mautrix_facebook/commands/auth.py             | 43 ++-----------------
 mautrix_facebook/db/user.py                   |  6 +--
 mautrix_facebook/user.py                      | 18 +++-----
 mautrix_facebook/web/public.py                | 13 +-----
 5 files changed, 39 insertions(+), 67 deletions(-)
 create mode 100644 alembic/versions/97e9f7a3c070_remove_user_agent_from_db.py

diff --git a/alembic/versions/97e9f7a3c070_remove_user_agent_from_db.py b/alembic/versions/97e9f7a3c070_remove_user_agent_from_db.py
new file mode 100644
index 0000000..d6d1600
--- /dev/null
+++ b/alembic/versions/97e9f7a3c070_remove_user_agent_from_db.py
@@ -0,0 +1,26 @@
+"""Remove user agent from db
+
+Revision ID: 97e9f7a3c070
+Revises: c56c9a30b228
+Create Date: 2020-05-11 23:40:17.319060
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '97e9f7a3c070'
+down_revision = 'c56c9a30b228'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    with op.batch_alter_table("user") as batch_op:
+        batch_op.drop_column('user_agent')
+
+
+def downgrade():
+    with op.batch_alter_table("user") as batch_op:
+        batch_op.add_column(sa.Column('user_agent', sa.String(length=255), autoincrement=False, nullable=True))
diff --git a/mautrix_facebook/commands/auth.py b/mautrix_facebook/commands/auth.py
index 2353e3c..624ce7b 100644
--- a/mautrix_facebook/commands/auth.py
+++ b/mautrix_facebook/commands/auth.py
@@ -14,10 +14,8 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 import asyncio
-import random
 import time
 
-from http.cookies import SimpleCookie
 from yarl import URL
 
 import fbchat
@@ -28,22 +26,6 @@ from .. import puppet as pu
 from mautrix.bridge import custom_puppet as cpu
 from . import command_handler, CommandEvent, SECTION_AUTH
 
-USER_AGENTS = [
-    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
-    " Chrome/74.0.3729.169 Safari/537.36",
-
-    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0",
-
-    # "Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like"
-    # " Gecko) Version/12.1.1 Mobile/15E148 Safari/604.1",
-
-    "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0",
-
-    # "Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36
-    # (KHTML,"
-    # " like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36",
-]
-
 
 @command_handler(needs_auth=False, management_only=True,
                  help_section=SECTION_AUTH, help_text="Log in to Facebook",
@@ -59,8 +41,6 @@ async def login(evt: CommandEvent) -> None:
         "action": "Login",
         "room_id": evt.room_id,
     }
-    if not evt.sender.user_agent:
-        evt.sender.user_agent = random.choice(USER_AGENTS)
     await evt.reply("Logging in...")
     try:
         session = await fbchat.Session.login(evt.args[0], " ".join(evt.args[1:]),
@@ -74,18 +54,6 @@ async def login(evt: CommandEvent) -> None:
         evt.log.exception("Failed to log in")
 
 
-@command_handler(needs_auth=False, management_only=False, help_section=SECTION_AUTH,
-                 help_text="Change the user agent sent to Facebook", help_args="<_user agent_>")
-async def set_ua(evt: CommandEvent) -> None:
-    if len(evt.args) < 0:
-        await evt.reply("Usage: `$cmdprefix+sp login <user agent>`")
-        return
-    evt.sender.user_agent = " ".join(evt.args)
-    evt.sender.save()
-    await evt.reply(f"Set user agent to `{evt.sender.user_agent}`. The change will be applied when"
-                    " you log in again or on the next bridge restart.")
-
-
 async def enter_2fa_code(evt: CommandEvent) -> None:
     code = " ".join(evt.args)
     future: asyncio.Future = evt.sender.command_status["future"]
@@ -150,14 +118,11 @@ async def enter_login_cookies(evt: CommandEvent) -> None:
                         "the `cancel` command to cancel.")
         return
 
-    if not evt.sender.user_agent:
-        evt.sender.user_agent = random.choice(USER_AGENTS)
-
-    cookie = SimpleCookie()
-    cookie["c_user"] = evt.sender.command_status["c_user"]
-    cookie["xs"] = evt.args[0]
     try:
-        session = await fbchat.Session.from_cookies(cookie)
+        session = await fbchat.Session.from_cookies({
+            "c_user": evt.sender.command_status["c_user"],
+            "xs": evt.args[0],
+        })
     except fbchat.FacebookError as e:
         evt.sender.command_status = None
         await evt.reply(f"Failed to log in: {e}")
diff --git a/mautrix_facebook/db/user.py b/mautrix_facebook/db/user.py
index 7fb66bb..f1ebe8e 100644
--- a/mautrix_facebook/db/user.py
+++ b/mautrix_facebook/db/user.py
@@ -13,8 +13,7 @@
 #
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
-from typing import Optional, Iterable
-from http.cookies import SimpleCookie
+from typing import Optional, Iterable, Dict
 
 from sqlalchemy import Column, String, PickleType
 
@@ -26,9 +25,8 @@ class User(Base):
     __tablename__ = "user"
 
     mxid: UserID = Column(String(255), primary_key=True)
-    session: SimpleCookie = Column(PickleType, nullable=True)
+    session: Dict[str, str] = Column(PickleType, nullable=True)
     fbid: str = Column(String(255), nullable=True)
-    user_agent: str = Column(String(255), nullable=True)
 
     @classmethod
     def all(cls) -> Iterable['User']:
diff --git a/mautrix_facebook/user.py b/mautrix_facebook/user.py
index ef0db5a..c692856 100644
--- a/mautrix_facebook/user.py
+++ b/mautrix_facebook/user.py
@@ -15,7 +15,6 @@
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 from typing import (Any, Dict, Iterator, Optional, Iterable, Type, Callable, Awaitable,
                     Union, TYPE_CHECKING)
-from http.cookies import SimpleCookie
 import asyncio
 import logging
 
@@ -47,24 +46,22 @@ class User:
     client: Optional[fbchat.Client]
     listener: Optional[fbchat.Listener]
     listen_task: Optional[asyncio.Task]
-    user_agent: str
 
     command_status: Optional[Dict[str, Any]]
     is_whitelisted: bool
     is_admin: bool
     permission_level: str
     _is_logged_in: Optional[bool]
-    _session_data: Optional[SimpleCookie]
+    _session_data: Optional[Dict[str, str]]
     _db_instance: Optional[DBUser]
 
     _community_helper: CommunityHelper
     _community_id: Optional[CommunityID]
 
-    def __init__(self, mxid: UserID, session: Optional[SimpleCookie] = None,
-                 user_agent: Optional[str] = None, db_instance: Optional[DBUser] = None) -> None:
+    def __init__(self, mxid: UserID, session: Optional[Dict[str, str]] = None,
+                 db_instance: Optional[DBUser] = None) -> None:
         self.mxid = mxid
         self.by_mxid[mxid] = self
-        self.user_agent = user_agent
         self.command_status = None
         self.is_whitelisted, self.is_admin, self.permission_level = config.get_permissions(mxid)
         self._is_logged_in = None
@@ -90,21 +87,18 @@ class User:
     @property
     def db_instance(self) -> DBUser:
         if not self._db_instance:
-            self._db_instance = DBUser(mxid=self.mxid, session=self._session_data,
-                                       fbid=self.fbid, user_agent=self.user_agent)
+            self._db_instance = DBUser(mxid=self.mxid, session=self._session_data, fbid=self.fbid)
         return self._db_instance
 
     def save(self, _update_session_data: bool = True) -> None:
         self.log.debug("Saving session")
         if _update_session_data and self.session:
             self._session_data = self.session.get_cookies()
-        self.db_instance.edit(session=self._session_data, fbid=self.fbid,
-                              user_agent=self.user_agent)
+        self.db_instance.edit(session=self._session_data, fbid=self.fbid)
 
     @classmethod
     def from_db(cls, db_user: DBUser) -> 'User':
-        return User(mxid=db_user.mxid, session=db_user.session, user_agent=db_user.user_agent,
-                    db_instance=db_user)
+        return User(mxid=db_user.mxid, session=db_user.session, db_instance=db_user)
 
     @classmethod
     def get_all(cls) -> Iterator['User']:
diff --git a/mautrix_facebook/web/public.py b/mautrix_facebook/web/public.py
index fa2ce81..2d74173 100644
--- a/mautrix_facebook/web/public.py
+++ b/mautrix_facebook/web/public.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 from typing import Optional, Dict
-from http.cookies import SimpleCookie
 import logging
 import random
 import string
@@ -113,23 +112,13 @@ class PublicBridgeWebsite:
     async def login(self, request: web.Request) -> web.Response:
         user = self.check_token(request)
 
-        try:
-            user_agent = request.headers["User-Agent"]
-        except KeyError:
-            raise web.HTTPBadRequest(body='{"error": "Missing User-Agent header"}',
-                                     headers=self._headers)
         try:
             data = await request.json()
         except json.JSONDecodeError:
             raise web.HTTPBadRequest(body='{"error": "Malformed JSON"}', headers=self._headers)
 
-        cookie = SimpleCookie()
-        cookie["c_user"] = data["c_user"]
-        cookie["xs"] = data["xs"]
-        user.user_agent = user_agent
-        user.save()
         try:
-            session = await fbchat.Session.from_cookies(cookie)
+            session = await fbchat.Session.from_cookies(data)
         except fbchat.FacebookError:
             self.log.debug("Failed to log in", exc_info=True)
             raise web.HTTPUnauthorized(body='{"error": "Facebook authorization failed"}',
-- 
GitLab