""" This file is part of the SplendidBear Websites' projects. Copyright (c) 2026 @ www.splendidbear.org For the full copyright and license information, please view the LICENSE file that was distributed with this source code. """ from __future__ import annotations from mineseeker.api import client class AuthError(Exception): """Raised on authentication failure.""" class TotpRequired(Exception): """Raised when the server requires a TOTP code after password login.""" def login(username: str, password: str) -> None: """ Authenticate with username + password via the dedicated JSON endpoint POST /api/auth/login, which bypasses the reCAPTCHA gate. Raises: TotpRequired – server confirmed credentials but TOTP is required next. AuthError – credentials wrong, account inactive, or server error. """ session = client.get_session() resp = session.post( client.url("/api/auth/login"), json={"username": username, "password": password}, # The endpoint sets a session cookie; follow any redirects allow_redirects=True, ) # Non-2xx means a hard server error (500 etc.) — let it propagate if resp.status_code >= 500: resp.raise_for_status() data = resp.json() if not data.get("success"): raise AuthError(data.get("error", "Login failed.")) if data.get("requiresTwoFactor"): raise TotpRequired() def submit_totp(code: str) -> None: """ Submit the 6-digit TOTP code after login() raises TotpRequired. The scheb/2fa bundle processes POST /2fa_check directly as a firewall listener — no CSRF token required, no JSON body. The code goes as a form-encoded field named _auth_code, same as the browser form. The LoginCaptchaListener already skips /2fa_check paths. Raises: AuthError – if the code is wrong or the session is no longer in IS_AUTHENTICATED_2FA_IN_PROGRESS state. """ session = client.get_session() resp = session.post( client.url("/2fa_check"), data={"_auth_code": code}, headers={"Content-Type": "application/x-www-form-urlencoded"}, allow_redirects=True, ) resp.raise_for_status() # If we land back on /2fa the code was wrong if "/2fa" in resp.url: raise AuthError("Invalid authentication code.") def login_as_guest() -> None: """ Start an anonymous session. A GET to the homepage is enough for Symfony to create a session and assign the anon_ identity used by ResolveUserNamesService. """ session = client.get_session() resp = session.get(client.url("/"), headers={"Accept": "text/html"}) resp.raise_for_status() def logout() -> None: """Discard the local session (client-side only).""" client.reset_session()