add account deletion

This commit is contained in:
2026-05-22 03:52:40 +03:00
parent 9ca40e1814
commit e729b43a28
4 changed files with 101 additions and 2 deletions

View File

@@ -11,9 +11,9 @@ from ..auth import (
digest, verify, create_session, digest, verify, create_session,
is_logged_in, parse_username, is_password_valid, is_logged_in, parse_username, is_password_valid,
login_required, revoke_session, get_active_user, login_required, revoke_session, get_active_user,
parse_display_name, revoke_all_sessions parse_display_name, revoke_all_sessions, csrf_verified
) )
from ..models import Users, Posts, Reactions, Threads, Avatars from ..models import Users, Posts, Reactions, Threads, Avatars, PostHistory, Mentions
from ..constants import PermissionLevel, InfoboxKind from ..constants import PermissionLevel, InfoboxKind
from ..util import get_form_checkbox from ..util import get_form_checkbox
import math import math
@@ -55,6 +55,51 @@ def validate_and_create_avatar(input_image, filename):
except WandException: except WandException:
return False return False
def anonymize_user(user_id):
deleted_user = Users.find({'username': 'deleteduser'})
from ..lib.babycode import sanitize, babycode_to_html
from ..db import db
threads = Threads.findall({'user_id': user_id})
posts = Posts.findall({'user_id': user_id})
revs_q = """SELECT DISTINCT m.revision_id FROM mentions m
WHERE m.mentioned_user_id = ?"""
mentioned_revs = db.query(revs_q, int(user_id))
with db.transaction():
for thread in threads:
thread.update({'user_id': int(deleted_user.id)})
for post in posts:
post.update({'user_id': int(deleted_user.id)})
revs = {}
for rev in mentioned_revs:
ph = PostHistory.find({'id': int(rev['revision_id'])})
ms = Mentions.findall({
'mentioned_user_id': int(user_id),
'revision_id': int(rev['revision_id']),
})
data = {
'text': sanitize(ph.original_markup),
'mentions': ms,
}
data['mentions'] = sorted(data['mentions'], key=lambda x: int(x.end_index), reverse=True)
revs[rev['revision_id']] = data
for rev_id, data in revs.items():
text = data['text']
for mention in data['mentions']:
text = text[:mention.start_index] + '@deleteduser' + text[mention.end_index:]
mention.delete()
res = babycode_to_html(text)
ph = PostHistory.find({'id': int(rev_id)})
ph.update({
'original_markup': text.unescape(),
'content': res.result,
})
def redirect_if_logged_in(destination='topics.all_topics'): def redirect_if_logged_in(destination='topics.all_topics'):
def decorator(view_func): def decorator(view_func):
@wraps(view_func) @wraps(view_func)
@@ -378,3 +423,31 @@ def inbox(username):
def bookmarks(username): def bookmarks(username):
username = username.lower() username = username.lower()
return 'stub' return 'stub'
@bp.get('/<username>/delete-confirm/')
@login_required
@redirect_to_own
def delete_confirm(username):
return render_template('users/delete_confirm.html')
@bp.post('/<username>/delete-confirm/')
@login_required
@redirect_to_own
@csrf_verified
def delete_confirm_post(username):
user = get_active_user()
password = request.form.get('password', '')
if not verify(user.password_hash, password):
flash('Incorrect password.', InfoboxKind.ERROR)
return redirect(url_for('.delete_confirm', username=username))
if user.is_admin():
flash('You can not delete the admin account.', InfoboxKind.ERROR)
return redirect(url_for('.delete_confirm', username=username))
anonymize_user(user.id)
revoke_all_sessions(user.id)
user.delete()
return redirect(url_for('topics.all_topics'))

View File

@@ -0,0 +1,20 @@
{%- from 'common/macros.html' import subheader -%}
{%- extends 'base.html' -%}
{%- block title -%}account deletion confirmation{%- endblock -%}
{%- block content -%}
{%- set sub -%}
<a href="{{url_for('users.settings', username=get_active_user().username)}}">&larr; Back to settings</a>
{%- endset -%}
{{- subheader('Confirm account deletion', sub) -}}
<div class="plank">
<form class="full-width" method="POST">
<p>Are you sure you want to delete your account on {{ config.SITE_NAME }}? <strong>This action is irreversible.</strong> Your posts and threads will remain accessible to preserve history but will be de-personalized, showing up as authored by a system user. Posts that @mention you will also mention the system user instead.</p>
<p>If you wish for any and all content relating to you to be removed, you will have to <a href="{{url_for('guides.contact')}}" target="_blank">contact {{ config.SITE_NAME }}'s administrators separately.</a></p>
<p>If you are sure, please confirm your current password below.</p>
<label for="password">Confirm password</label>
{{csrf_input() | safe}}
<input type="password" id="password" name="password" required autocomplete="current-password">
<input type="submit" class="critical" value="Permanently delete account">
</form>
</div>
{%- endblock -%}

View File

@@ -77,4 +77,8 @@
<div>If badges fail to load, make sure JS is enabled.</div> <div>If badges fail to load, make sure JS is enabled.</div>
</fieldset> </fieldset>
{%- endif -%} {%- endif -%}
<fieldset class="plank">
<legend>Disown & Delete account</legend>
<a class="linkbutton critical" href="{{url_for('users.delete_confirm', username=user.username)}}">Delete account</a>
</fieldset>
{%- endblock -%} {%- endblock -%}

View File

@@ -70,10 +70,12 @@
{%- endfor -%} {%- endfor -%}
</div> </div>
{%- endif -%} {%- endif -%}
{#
<fieldset class="plank secondary-bg minimal even no-shadow"> <fieldset class="plank secondary-bg minimal even no-shadow">
<legend>About me</legend> <legend>About me</legend>
<p>stub</p> <p>stub</p>
</fieldset> </fieldset>
#}
{%- if target_user.signature_rendered -%} {%- if target_user.signature_rendered -%}
<fieldset class="plank secondary-bg minimal even no-shadow"> <fieldset class="plank secondary-bg minimal even no-shadow">
<legend>Signature</legend> <legend>Signature</legend>