Skip to content

Commit 309896a

Browse files
committed
Add option for users to delete their accounts
And one for admins
1 parent d94e729 commit 309896a

File tree

5 files changed

+173
-8
lines changed

5 files changed

+173
-8
lines changed

KerbalStuff/blueprints/api.py

+36-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
import zipfile
66
from datetime import datetime
77
from functools import wraps
8+
from shutil import rmtree
89
from typing import Dict, Any, Callable, Optional, Tuple, Iterable, List, Union
910

1011
from flask import Blueprint, url_for, current_app, request, abort
11-
from flask_login import login_user, current_user
12+
from flask_login import login_user, current_user, logout_user
1213
from sqlalchemy import desc, asc
1314
from sqlalchemy.orm import Query
1415
from werkzeug.utils import secure_filename
@@ -551,6 +552,40 @@ def change_password(username: str) -> Union[Dict[str, Any], Tuple[Union[str, Any
551552
return {'error': True, 'reason': pw_message}
552553

553554

555+
@api.route("/api/user/<username>/delete", methods=['POST'])
556+
@with_session
557+
@user_required
558+
@json_output
559+
def delete(username: str) -> Tuple[Dict[str, Any], int]:
560+
deletable = False
561+
if current_user:
562+
if current_user.admin:
563+
deletable = True
564+
if current_user.username == username:
565+
deletable = True
566+
if not deletable:
567+
return {'error': True, 'reason': 'Unauthorized'}, 401
568+
569+
form_username = request.form.get('username')
570+
if form_username != username:
571+
return {'error': True, 'reason': 'Wrong username'}, 403
572+
573+
user = User.query.filter(User.username == username).one_or_none()
574+
if not user:
575+
return {'error': True, 'reason': 'User does not exist'}, 404
576+
577+
storage = _cfg('storage')
578+
if storage:
579+
full_path = os.path.join(storage, user.base_path())
580+
rmtree(full_path, ignore_errors=True)
581+
582+
db.delete(user)
583+
if user == current_user:
584+
logout_user()
585+
586+
return {"error": False}, 400
587+
588+
554589
@api.route('/api/mod/<int:mod_id>/update-bg', methods=['POST'])
555590
@with_session
556591
@json_output

KerbalStuff/blueprints/mods.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,7 @@ def delete(mod_id: int) -> werkzeug.wrappers.Response:
405405
storage = _cfg('storage')
406406
if storage:
407407
full_path = os.path.join(storage, mod.base_path())
408-
rmtree(full_path)
409-
408+
rmtree(full_path, ignore_errors=True)
410409
db.delete(mod)
411410
db.commit()
412411
notify_ckan(mod, 'delete', True)
@@ -638,6 +637,11 @@ def delete_version(mod_id: int, version_id: str) -> werkzeug.wrappers.Response:
638637

639638
purge_download(version[0].download_path)
640639

640+
storage = _cfg('storage')
641+
if storage:
642+
full_path = os.path.join(storage, version.download_path)
643+
os.remove(full_path)
644+
641645
db.delete(version)
642646
db.commit()
643647
return redirect(url_for("mods.mod", _anchor='changelog', mod_id=mod.id, mod_name=mod.name))

frontend/coffee/profile.coffee

+56-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ window.upload_bg = (files, box) ->
1717
xhr.upload.onprogress = (e) ->
1818
if e.lengthComputable
1919
progress.style.width = (e.loaded / e.total) * 100 + '%'
20-
xhr.onload = (e) ->
20+
xhr.onload = () ->
2121
if xhr.status != 200
2222
p.textContent = 'Please upload JPG or PNG only.'
2323
setTimeout(() ->
@@ -98,6 +98,60 @@ resetPasswordModalDialog = () ->
9898

9999
$('#change-password').on('hidden.bs.modal', resetPasswordModalDialog)
100100

101+
# Handling of the delete-account dialog
102+
$('#delete-account-form').submit((e) ->
103+
e.preventDefault()
104+
105+
# Disable the buttons until we get an response
106+
buttons = document.getElementsByClassName('btn-account-del')
107+
for button in buttons
108+
button.setAttribute('disabled', '')
109+
110+
form_username = $('#username').val()
111+
112+
xhr = new XMLHttpRequest()
113+
xhr.open('POST', "/api/user/#{window.username}/delete")
114+
# Triggered after we get an response from the server.
115+
# It's in the form {'error': bool, 'reason': string)
116+
xhr.onload = () ->
117+
result = JSON.parse(this.responseText)
118+
error_message_display = $('#delete-account-error-message')
119+
120+
if result.error == true
121+
error_message_display.html(result.reason)
122+
error_message_display.addClass('text-danger')
123+
error_message_display.removeClass('hidden')
124+
# Re-enable the buttons.
125+
for button in buttons
126+
button.removeAttribute('disabled')
127+
else
128+
error_message_display.html('Account deleted successfully.')
129+
error_message_display.removeClass('text-danger')
130+
error_message_display.addClass('text-success')
131+
error_message_display.removeClass('hidden')
132+
# .modal('hide') doesn't work. Let's reload the page instead.
133+
# Delay it a bit to give the user a chance to read the response message.
134+
setTimeout((() -> window.location = '/'), 1000)
135+
136+
form = new FormData()
137+
form.append('username', form_username)
138+
xhr.send(form)
139+
)
140+
141+
deleteAccountModalDialog = () ->
142+
$('#delete-account-form').trigger('reset')
143+
error_message_display = $('#delete-account-error-message')
144+
error_message_display.html('')
145+
error_message_display.removeClass('text-danger')
146+
error_message_display.removeClass('text-success')
147+
error_message_display.addClass('hidden')
148+
149+
buttons = document.getElementsByClassName('btn-account-del')
150+
for button in buttons
151+
button.removeAttribute('disabled')
152+
153+
$('#delete-account').on('hidden.bs.modal', deleteAccountModalDialog)
154+
101155
$('#check-all-updates' ).on('click', () -> $('[id^=updates-]' ).prop('checked', true))
102156
$('#uncheck-all-updates' ).on('click', () -> $('[id^=updates-]' ).prop('checked', false))
103157
$('#check-all-autoupdates' ).on('click', () -> $('[id^=autoupdates-]').prop('checked', true))
@@ -117,4 +171,4 @@ $('#save-changes').on 'click', () ->
117171
$('#overall-error').hide()
118172
else
119173
$('#overall-error').show()
120-
return allValid
174+
return allValid

templates/profile.html

+39-3
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,21 @@ <h2>Change Password</h2>
211211
<a class="btn btn-default" href="#" data-toggle="modal" data-target="#change-password" role="button">Click to change password</a>
212212
</div>
213213
</div>
214+
<div class="row">
215+
<div class="col-md-12">
216+
<h2>Delete User Account</h2>
217+
</div>
218+
</div>
219+
<div class="row">
220+
<div class="col-md-12">
221+
<a class="btn btn-default" href="#" data-toggle="modal" data-target="#confirm-delete-account" role="button">Click to delete your account</a>
222+
</div>
223+
</div>
214224
</div>
215225

216226
<div class="container lead">
217227
<div class="row">
218-
<div class="col-md-8">
228+
<div class="col-md-12">
219229
<h2>Connected Accounts</h2>
220230
</div>
221231

@@ -280,7 +290,7 @@ <h2 style="margin-top: -10px; margin-bottom: 0;">
280290
<div class="modal-content">
281291
<div class="modal-header">
282292
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
283-
<h4 class="modal-title" id="myModalLabel">Disconnect Account: {{provider_full_name}}</h4>
293+
<h4 class="modal-title">Disconnect Account: {{provider_full_name}}</h4>
284294
</div>
285295
<div class="modal-body">
286296
<p>This action will disconnect your {{provider_full_name}} account from your {{ site_name }} account.</p>
@@ -303,7 +313,7 @@ <h4 class="modal-title" id="myModalLabel">Disconnect Account: {{provider_full_na
303313
<div class="modal-content">
304314
<div class="modal-header">
305315
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
306-
<h4 class="modal-title" id="myModalLabel">Change Password</h4>
316+
<h4 class="modal-title">Change Password</h4>
307317
</div>
308318
<form action="#" id="password-form">
309319
<div class="modal-body">
@@ -334,6 +344,32 @@ <h4 class="modal-title" id="myModalLabel">Change Password</h4>
334344
</div>
335345
</div>
336346
</div>
347+
<div class="modal fade" id="confirm-delete-account" tabindex="-1" role="dialog">
348+
<div class="modal-dialog">
349+
<div class="modal-content">
350+
<div class="modal-header">
351+
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
352+
<h4 class="modal-title">Delete User Account</h4>
353+
</div>
354+
<form action="#" id="delete-account-form">
355+
<div class="modal-body">
356+
<p>Are you really, really, really sure you want to delete your account? You can't undo this. It'll be gone.
357+
This will delete any data associated with your account, including uploaded mods.</p>
358+
<div class="form-group">
359+
<label for="username">Your username</label>
360+
<input type="text" id="username" placeholder="Enter your username to confirm" class="form-control"
361+
name="username">
362+
</div>
363+
</div>
364+
<div class="modal-footer">
365+
<p id="delete-account-error-message" class="hidden"></p>
366+
<a href="#" class="btn btn-default btn-account-del" data-dismiss="modal">Cancel</a>
367+
<input type="submit" id="delete-user-submit" class="btn btn-danger btn-account-del" value="Delete account">
368+
</div>
369+
</form>
370+
</div>
371+
</div>
372+
</div>
337373
{% endblock %}
338374
{% block scripts %}
339375
<script>

templates/view_profile.html

+36
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ <h2>
205205
<span class="glyphicon glyphicon-fire"></span>
206206
Impersonate user
207207
</a>
208+
<a href="#" class="btn btn-danger" data-toggle="modal" data-target="#confirm-delete-account" role="button" style="margin-bottom: 10px; margin-top: 5px;">
209+
<span class="glyphicon glyphicon-fire"></span>
210+
Delete user
211+
</a>
208212
{% endif %}
209213
</div>
210214
</div>
@@ -234,6 +238,32 @@ <h4 class="modal-title" id="myModalLabel">Grant Administrator Privileges</h4>
234238
</div>
235239
</div>
236240
</div>
241+
<div class="modal fade" id="confirm-delete-account" tabindex="-1" role="dialog">
242+
<div class="modal-dialog">
243+
<div class="modal-content">
244+
<div class="modal-header">
245+
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
246+
<h4 class="modal-title">Delete User Account</h4>
247+
</div>
248+
<form action="#" id="delete-account-form">
249+
<div class="modal-body">
250+
<p>Are you really, really, really sure you want to delete that account? It can't be undone. It'll be gone.
251+
This will delete any data associated with this account, including uploaded mods.</p>
252+
<div class="form-group">
253+
<label for="username">Username</label>
254+
<input type="text" id="username" placeholder="Enter the user's username to confirm" class="form-control"
255+
name="username">
256+
</div>
257+
</div>
258+
<div class="modal-footer">
259+
<p id="delete-account-error-message" class="hidden"></p>
260+
<a href="#" class="btn btn-default btn-account-del" data-dismiss="modal">Cancel</a>
261+
<input type="submit" id="delete-user-submit" class="btn btn-danger btn-account-del" value="Delete account">
262+
</div>
263+
</form>
264+
</div>
265+
</div>
266+
</div>
237267
{%- endif -%}
238268

239269
{% if len(profile.packs) != 0 %}
@@ -304,3 +334,9 @@ <h4 class="modal-title" id="myModalLabel">Publicize Profile</h4>
304334
</div>
305335
{% endif %}
306336
{% endblock %}
337+
{% block scripts %}
338+
<script>
339+
window.username = "{{profile.username}}";
340+
</script>
341+
<script src="/static/profile.js"></script>
342+
{% endblock %}

0 commit comments

Comments
 (0)