mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-29 09:52:36 +02:00
Rework project deletion to add CSRF validation
It requires reworking the user interface, but it's probably for the best.
This commit is contained in:
parent
109d7fca17
commit
2bb6f2b6a7
5 changed files with 61 additions and 13 deletions
|
@ -221,6 +221,26 @@ class ProjectForm(EditProjectForm):
|
||||||
raise ValidationError(Markup(message))
|
raise ValidationError(Markup(message))
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteProjectForm(FlaskForm):
|
||||||
|
password = PasswordField(
|
||||||
|
_("Private code"),
|
||||||
|
description=_("Enter private code to confirm deletion"),
|
||||||
|
validators=[DataRequired()],
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# Same trick as EditProjectForm: we need to know the project ID
|
||||||
|
self.id = SimpleNamespace(data=kwargs.pop("id", ""))
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def validate_password(form, field):
|
||||||
|
project = Project.query.get(form.id.data)
|
||||||
|
if project is None:
|
||||||
|
raise ValidationError(_("Unknown error"))
|
||||||
|
if not check_password_hash(project.password, form.password.data):
|
||||||
|
raise ValidationError(_("Invalid private code."))
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationForm(FlaskForm):
|
class AuthenticationForm(FlaskForm):
|
||||||
id = StringField(_("Project identifier"), validators=[DataRequired()])
|
id = StringField(_("Project identifier"), validators=[DataRequired()])
|
||||||
password = PasswordField(_("Private code"), validators=[DataRequired()])
|
password = PasswordField(_("Private code"), validators=[DataRequired()])
|
||||||
|
|
|
@ -303,11 +303,6 @@ footer .footer-left {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm,
|
|
||||||
.confirm:hover {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bill-actions {
|
.bill-actions {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
$('#delete-project').click(function ()
|
|
||||||
{
|
$('#delete-project').click(function(){
|
||||||
$(this).html("<a style='color:red; ' href='{{ url_for('.delete_project') }}' >{{_("you sure?")}}</a>");
|
var link = $(this).find('button');
|
||||||
|
link.click(function(){
|
||||||
|
if ($(this).hasClass("confirm")){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$(this).html("{{_("Are you sure?")}}");
|
||||||
|
$(this).addClass("confirm");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.custom-file-input').on('change', function(event) {
|
$('.custom-file-input').on('change', function(event) {
|
||||||
|
@ -21,6 +29,10 @@
|
||||||
{{ forms.edit_project(edit_form) }}
|
{{ forms.edit_project(edit_form) }}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<h2>{{ _("Delete project") }}</h2>
|
||||||
|
<form id="delete-project" class="form-horizontal" action="{{ url_for(".delete_project") }}" method="post" >
|
||||||
|
{{ forms.delete_project(delete_form) }}
|
||||||
|
</form>
|
||||||
|
|
||||||
<h2>{{ _("Import JSON") }}</h2>
|
<h2>{{ _("Import JSON") }}</h2>
|
||||||
<form class="form-horizontal" method="post" enctype="multipart/form-data">
|
<form class="form-horizontal" method="post" enctype="multipart/form-data">
|
||||||
|
|
|
@ -100,7 +100,18 @@
|
||||||
{{ input(form.default_currency) }}
|
{{ input(form.default_currency) }}
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-primary">{{ _("Edit the project") }}</button>
|
<button class="btn btn-primary">{{ _("Edit the project") }}</button>
|
||||||
<a id="delete-project" style="color:red; margin-left:10px; cursor:pointer; ">{{ _("delete") }}</a>
|
</div>
|
||||||
|
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro delete_project(form) %}
|
||||||
|
|
||||||
|
{% include "display_errors.html" %}
|
||||||
|
<p><strong>{{ _("This will remove all bills and participants in this project!") }}</strong></p>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ input(form.password) }}
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-danger">{{ _("Delete project") }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
|
@ -39,6 +39,7 @@ from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
from ihatemoney.forms import (
|
from ihatemoney.forms import (
|
||||||
AdminAuthenticationForm,
|
AdminAuthenticationForm,
|
||||||
AuthenticationForm,
|
AuthenticationForm,
|
||||||
|
DeleteProjectForm,
|
||||||
EditProjectForm,
|
EditProjectForm,
|
||||||
EmptyForm,
|
EmptyForm,
|
||||||
InviteForm,
|
InviteForm,
|
||||||
|
@ -401,6 +402,7 @@ def reset_password():
|
||||||
@main.route("/<project_id>/edit", methods=["GET", "POST"])
|
@main.route("/<project_id>/edit", methods=["GET", "POST"])
|
||||||
def edit_project():
|
def edit_project():
|
||||||
edit_form = EditProjectForm(id=g.project.id)
|
edit_form = EditProjectForm(id=g.project.id)
|
||||||
|
delete_form = DeleteProjectForm(id=g.project.id)
|
||||||
import_form = UploadForm()
|
import_form = UploadForm()
|
||||||
# Import form
|
# Import form
|
||||||
if import_form.validate_on_submit():
|
if import_form.validate_on_submit():
|
||||||
|
@ -434,6 +436,7 @@ def edit_project():
|
||||||
return render_template(
|
return render_template(
|
||||||
"edit_project.html",
|
"edit_project.html",
|
||||||
edit_form=edit_form,
|
edit_form=edit_form,
|
||||||
|
delete_form=delete_form,
|
||||||
import_form=import_form,
|
import_form=import_form,
|
||||||
current_view="edit_project",
|
current_view="edit_project",
|
||||||
)
|
)
|
||||||
|
@ -512,11 +515,18 @@ def import_project(file, project):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
@main.route("/<project_id>/delete")
|
@main.route("/<project_id>/delete", methods=["POST"])
|
||||||
def delete_project():
|
def delete_project():
|
||||||
|
form = DeleteProjectForm(id=g.project.id)
|
||||||
|
if form.validate():
|
||||||
g.project.remove_project()
|
g.project.remove_project()
|
||||||
flash(_("Project successfully deleted"))
|
flash(_("Project successfully deleted"))
|
||||||
|
return redirect(url_for(".home"))
|
||||||
|
else:
|
||||||
|
flash(
|
||||||
|
_("Error deleting project: wrong private code or wrong CSRF token"),
|
||||||
|
category="danger",
|
||||||
|
)
|
||||||
return redirect(request.headers.get("Referer") or url_for(".home"))
|
return redirect(request.headers.get("Referer") or url_for(".home"))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue