Private
Public Access
1
0
Files
MineSeeker/gtk-client/mineseeker/api/auth.py

94 lines
2.8 KiB
Python
Raw Normal View History

"""
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_<session_id> 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()