mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
In my opinion, this kind of handling of transactions is pretty ugly. The being said, we have issues with running into deadlocks on aur.al, so this commit works against that immediate bug. An ideal solution would be to deal with retrying transactions through the `db.begin()` scope, so we wouldn't have to explicitly annotate functions as "retry functions," which is what this commit does. Closes #376 Signed-off-by: Kevin Morris <kevr@0cost.org>
129 lines
3.9 KiB
Python
129 lines
3.9 KiB
Python
from http import HTTPStatus
|
|
|
|
from fastapi import APIRouter, Form, HTTPException, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from sqlalchemy import or_
|
|
|
|
import aurweb.config
|
|
from aurweb import cookies, db
|
|
from aurweb.auth import requires_auth, requires_guest
|
|
from aurweb.exceptions import handle_form_exceptions
|
|
from aurweb.l10n import get_translator_for_request
|
|
from aurweb.models import User
|
|
from aurweb.templates import make_variable_context, render_template
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
async def login_template(request: Request, next: str, errors: list = None):
|
|
"""Provide login-specific template context to render_template."""
|
|
context = await make_variable_context(request, "Login", next)
|
|
context["errors"] = errors
|
|
context["url_base"] = f"{request.url.scheme}://{request.url.netloc}"
|
|
return render_template(request, "login.html", context)
|
|
|
|
|
|
@router.get("/login", response_class=HTMLResponse)
|
|
async def login_get(request: Request, next: str = "/"):
|
|
return await login_template(request, next)
|
|
|
|
|
|
@db.retry_deadlock
|
|
def _retry_login(request: Request, user: User, passwd: str, cookie_timeout: int) -> str:
|
|
return user.login(request, passwd, cookie_timeout)
|
|
|
|
|
|
@router.post("/login", response_class=HTMLResponse)
|
|
@handle_form_exceptions
|
|
@requires_guest
|
|
async def login_post(
|
|
request: Request,
|
|
next: str = Form(...),
|
|
user: str = Form(default=str()),
|
|
passwd: str = Form(default=str()),
|
|
remember_me: bool = Form(default=False),
|
|
):
|
|
# TODO: Once the Origin header gets broader adoption, this code can be
|
|
# slightly simplified to use it.
|
|
login_path = aurweb.config.get("options", "aur_location") + "/login"
|
|
referer = request.headers.get("Referer")
|
|
if not referer or not referer.startswith(login_path):
|
|
_ = get_translator_for_request(request)
|
|
raise HTTPException(
|
|
status_code=HTTPStatus.BAD_REQUEST, detail=_("Bad Referer header.")
|
|
)
|
|
|
|
user = (
|
|
db.query(User)
|
|
.filter(
|
|
or_(
|
|
User.Username == user,
|
|
User.Email == user,
|
|
)
|
|
)
|
|
.first()
|
|
)
|
|
if not user:
|
|
return await login_template(request, next, errors=["Bad username or password."])
|
|
|
|
if user.Suspended:
|
|
return await login_template(request, next, errors=["Account Suspended"])
|
|
|
|
cookie_timeout = cookies.timeout(remember_me)
|
|
sid = _retry_login(request, user, passwd, cookie_timeout)
|
|
if not sid:
|
|
return await login_template(request, next, errors=["Bad username or password."])
|
|
|
|
response = RedirectResponse(url=next, status_code=HTTPStatus.SEE_OTHER)
|
|
|
|
secure = aurweb.config.getboolean("options", "disable_http_login")
|
|
response.set_cookie(
|
|
"AURSID",
|
|
sid,
|
|
max_age=cookie_timeout,
|
|
secure=secure,
|
|
httponly=secure,
|
|
samesite=cookies.samesite(),
|
|
)
|
|
response.set_cookie(
|
|
"AURTZ",
|
|
user.Timezone,
|
|
secure=secure,
|
|
httponly=secure,
|
|
samesite=cookies.samesite(),
|
|
)
|
|
response.set_cookie(
|
|
"AURLANG",
|
|
user.LangPreference,
|
|
secure=secure,
|
|
httponly=secure,
|
|
samesite=cookies.samesite(),
|
|
)
|
|
response.set_cookie(
|
|
"AURREMEMBER",
|
|
remember_me,
|
|
secure=secure,
|
|
httponly=secure,
|
|
samesite=cookies.samesite(),
|
|
)
|
|
return response
|
|
|
|
|
|
@db.retry_deadlock
|
|
def _retry_logout(request: Request) -> None:
|
|
request.user.logout(request)
|
|
|
|
|
|
@router.post("/logout")
|
|
@handle_form_exceptions
|
|
@requires_auth
|
|
async def logout(request: Request, next: str = Form(default="/")):
|
|
if request.user.is_authenticated():
|
|
_retry_logout(request)
|
|
|
|
# Use 303 since we may be handling a post request, that'll get it
|
|
# to redirect to a get request.
|
|
response = RedirectResponse(url=next, status_code=HTTPStatus.SEE_OTHER)
|
|
response.delete_cookie("AURSID")
|
|
response.delete_cookie("AURTZ")
|
|
return response
|