diff --git a/aurweb/models/package_vote.py b/aurweb/models/package_vote.py index fa769bb6..b9e233d9 100644 --- a/aurweb/models/package_vote.py +++ b/aurweb/models/package_vote.py @@ -14,7 +14,7 @@ class PackageVote(Base): User = relationship( _User, - backref=backref("package_votes", lazy="dynamic"), + backref=backref("package_votes", lazy="dynamic", cascade="all, delete"), foreign_keys=[__table__.c.UsersID], ) diff --git a/aurweb/models/session.py b/aurweb/models/session.py index d3d69f8c..ff97f017 100644 --- a/aurweb/models/session.py +++ b/aurweb/models/session.py @@ -13,7 +13,7 @@ class Session(Base): User = relationship( _User, - backref=backref("session", uselist=False), + backref=backref("session", cascade="all, delete", uselist=False), foreign_keys=[__table__.c.UsersID], ) diff --git a/aurweb/routers/accounts.py b/aurweb/routers/accounts.py index 524ef814..12e59b30 100644 --- a/aurweb/routers/accounts.py +++ b/aurweb/routers/accounts.py @@ -3,13 +3,13 @@ import typing from http import HTTPStatus from typing import Any -from fastapi import APIRouter, Form, Request +from fastapi import APIRouter, Form, HTTPException, Request from fastapi.responses import HTMLResponse, RedirectResponse from sqlalchemy import and_, or_ import aurweb.config from aurweb import cookies, db, l10n, logging, models, util -from aurweb.auth import account_type_required, requires_auth, requires_guest +from aurweb.auth import account_type_required, creds, requires_auth, requires_guest from aurweb.captcha import get_captcha_salts from aurweb.exceptions import ValidationError, handle_form_exceptions from aurweb.l10n import get_translator_for_request @@ -598,6 +598,78 @@ async def accounts_post( return render_template(request, "account/index.html", context) +@router.get("/account/{name}/delete") +@requires_auth +async def account_delete(request: Request, name: str): + user = db.query(models.User).filter(models.User.Username == name).first() + if not user: + raise HTTPException(status_code=HTTPStatus.NOT_FOUND) + + has_cred = request.user.has_credential(creds.ACCOUNT_EDIT, approved=[user]) + if not has_cred: + _ = l10n.get_translator_for_request(request) + raise HTTPException( + detail=_("You do not have permission to edit this account."), + status_code=HTTPStatus.UNAUTHORIZED, + ) + + context = make_context(request, "Accounts") + context["name"] = name + return render_template(request, "account/delete.html", context) + + +@db.async_retry_deadlock +@router.post("/account/{name}/delete") +@handle_form_exceptions +@requires_auth +async def account_delete_post( + request: Request, + name: str, + passwd: str = Form(default=str()), + confirm: bool = Form(default=False), +): + user = db.query(models.User).filter(models.User.Username == name).first() + if not user: + raise HTTPException(status_code=HTTPStatus.NOT_FOUND) + + has_cred = request.user.has_credential(creds.ACCOUNT_EDIT, approved=[user]) + if not has_cred: + _ = l10n.get_translator_for_request(request) + raise HTTPException( + detail=_("You do not have permission to edit this account."), + status_code=HTTPStatus.UNAUTHORIZED, + ) + + context = make_context(request, "Accounts") + context["name"] = name + + confirm = util.strtobool(confirm) + if not confirm: + context["errors"] = [ + "The account has not been deleted, check the confirmation checkbox." + ] + return render_template( + request, + "account/delete.html", + context, + status_code=HTTPStatus.BAD_REQUEST, + ) + + if not request.user.valid_password(passwd): + context["errors"] = ["Invalid password."] + return render_template( + request, + "account/delete.html", + context, + status_code=HTTPStatus.BAD_REQUEST, + ) + + with db.begin(): + db.delete(user) + + return RedirectResponse("/", status_code=HTTPStatus.SEE_OTHER) + + def render_terms_of_service(request: Request, context: dict, terms: typing.Iterable): if not terms: return RedirectResponse("/", status_code=HTTPStatus.SEE_OTHER) diff --git a/po/aurweb.pot b/po/aurweb.pot index bc4bab84..1838fae5 100644 --- a/po/aurweb.pot +++ b/po/aurweb.pot @@ -2346,3 +2346,7 @@ msgstr "" #: templates/partials/packages/package_metadata.html msgid "dependencies" msgstr "" + +#: aurweb/routers/accounts.py +msgid "The account has not been deleted, check the confirmation checkbox." +msgstr "" diff --git a/templates/account/delete.html b/templates/account/delete.html new file mode 100644 index 00000000..625d3c2d --- /dev/null +++ b/templates/account/delete.html @@ -0,0 +1,43 @@ +{% extends "partials/layout.html" %} + +{% block pageContent %} +
+ {{ + "You can use this form to permanently delete the AUR account %s%s%s." + | tr | format("", name, "") | safe + }} +
+ ++ {{ + "%sWARNING%s: This action cannot be undone." + | tr | format("", "") | safe + }} +
+ + + + +