diff --git a/poetry.lock b/poetry.lock index ef45fa2..a99b8f8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "attrs" @@ -286,6 +286,17 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mintotp" +version = "0.3.0" +description = "MinTOTP - Minimal TOTP Generator" +optional = false +python-versions = "*" +files = [ + {file = "mintotp-0.3.0-py3-none-any.whl", hash = "sha256:eadee8531d9ee95eda92fd17949137454acd1d2a001dcf68f99bb8de56f06468"}, + {file = "mintotp-0.3.0.tar.gz", hash = "sha256:d0f4db5edb38a7481120176a526e8c29539b9e80581dd2dcc1811557d77cfad5"}, +] + [[package]] name = "pillow" version = "10.1.0" @@ -518,4 +529,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "db7957ff5e3633c6716e6035d0cdab043789c46371afdc99f301af0e150ce6cc" +content-hash = "62055e6bfbff10e93d4cfa5aa54dba2e45bfa7344c07dd84118ad8abed1def68" diff --git a/pyproject.toml b/pyproject.toml index f211a14..b04eace 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ html5lib = "^1.1" requests = "^2.31.0" requests-cache = "^1.1.0" Pillow = "^10.1.0" +mintotp = "^0.3.0" [tool.poetry.group.dev.dependencies] flake8 = "^6.1.0" diff --git a/sites/xenforo.py b/sites/xenforo.py index 3b7370d..551b69c 100644 --- a/sites/xenforo.py +++ b/sites/xenforo.py @@ -7,6 +7,7 @@ import requests_cache from bs4 import BeautifulSoup from . import register, Site, SiteException, SiteSpecificOption, Section, Chapter +import mintotp logger = logging.getLogger(__name__) @@ -73,10 +74,21 @@ class XenForo(Site): self._join_url(login.url, action), data=post, cookies=login.cookies ) - if result.ok: - logger.info("Logged in as %s", login_details[0]) - else: - logger.error("Failed to log in as %s", login_details[0]) + if not result.ok: + return logger.error("Failed to log in as %s", login_details[0]) + soup = BeautifulSoup(result.text, 'html5lib') + if twofactor := soup.find('form', action="/login/two-step"): + if len(login_details) < 3: + return logger.error("Failed to log in as %s; login requires 2FA secret", login_details[0]) + post, action, method = self._form_data(twofactor) + post['code'] = mintotp.totp(login_details[2]) + result = self.session.post( + self._join_url(login.url, action), + data=post, cookies=login.cookies + ) + if not result.ok: + return logger.error("Failed to log in as %s; 2FA failed", login_details[0]) + logger.info("Logged in as %s", login_details[0]) def extract(self, url): soup = self._soup(url)