Fix: Add csrf verification to directory updates (#1358)

* Fix: Add csrf verification to directory updates

* Update templates/dashboard/directory.html

* Added csrf for delete account form

* Fix tests

* Added CSRF check for settings page

* Added csrf to batch import

* Added CSRF to alias dashboard and alias transfer

* Added csrf to contact manager

* Added csrf to mailbox

* Added csrf for mailbox detail

* Added csrf to domain detail

* Lint

Co-authored-by: Adrià Casajús <adria.casajus@proton.ch>
This commit is contained in:
Adrià Casajús 2022-10-27 10:04:47 +02:00 committed by GitHub
parent 2f769b38ad
commit d324e2fa79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 235 additions and 63 deletions

View file

@ -25,7 +25,7 @@ from app.errors import (
) )
from app.log import LOG from app.log import LOG
from app.models import Alias, Contact, EmailLog, User from app.models import Alias, Contact, EmailLog, User
from app.utils import sanitize_email from app.utils import sanitize_email, CSRFValidationForm
def email_validator(): def email_validator():
@ -258,8 +258,12 @@ def alias_contact_manager(alias_id):
return redirect(url_for("dashboard.index")) return redirect(url_for("dashboard.index"))
new_contact_form = NewContactForm() new_contact_form = NewContactForm()
csrf_form = CSRFValidationForm()
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(request.url)
if request.form.get("form-name") == "create": if request.form.get("form-name") == "create":
if new_contact_form.validate(): if new_contact_form.validate():
contact_address = new_contact_form.email.data.strip() contact_address = new_contact_form.email.data.strip()
@ -323,4 +327,5 @@ def alias_contact_manager(alias_id):
query=query, query=query,
nb_contact=nb_contact, nb_contact=nb_contact,
can_create_contacts=user_can_create_contacts(current_user), can_create_contacts=user_can_create_contacts(current_user),
csrf_form=csrf_form,
) )

View file

@ -22,6 +22,7 @@ from app.models import (
ClientUser, ClientUser,
) )
from app.models import Mailbox from app.models import Mailbox
from app.utils import CSRFValidationForm
def transfer(alias, new_user, new_mailboxes: [Mailbox]): def transfer(alias, new_user, new_mailboxes: [Mailbox]):
@ -105,8 +106,12 @@ def alias_transfer_send_route(alias_id):
return redirect(url_for("dashboard.index")) return redirect(url_for("dashboard.index"))
alias_transfer_url = None alias_transfer_url = None
csrf_form = CSRFValidationForm()
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(request.url)
# generate a new transfer_token # generate a new transfer_token
if request.form.get("form-name") == "create": if request.form.get("form-name") == "create":
transfer_token = f"{alias.id}.{secrets.token_urlsafe(32)}" transfer_token = f"{alias.id}.{secrets.token_urlsafe(32)}"
@ -133,6 +138,7 @@ def alias_transfer_send_route(alias_id):
alias_transfer_url=alias_transfer_url, alias_transfer_url=alias_transfer_url,
link_active=alias.transfer_token_expiration is not None link_active=alias.transfer_token_expiration is not None
and alias.transfer_token_expiration > arrow.utcnow(), and alias.transfer_token_expiration > arrow.utcnow(),
csrf_form=csrf_form,
) )

View file

@ -8,7 +8,7 @@ from app.dashboard.base import dashboard_bp
from app.db import Session from app.db import Session
from app.log import LOG from app.log import LOG
from app.models import File, BatchImport, Job from app.models import File, BatchImport, Job
from app.utils import random_string from app.utils import random_string, CSRFValidationForm
@dashboard_bp.route("/batch_import", methods=["GET", "POST"]) @dashboard_bp.route("/batch_import", methods=["GET", "POST"])
@ -29,14 +29,21 @@ def batch_import_route():
user_id=current_user.id, processed=False user_id=current_user.id, processed=False
).all() ).all()
csrf_form = CSRFValidationForm()
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
redirect(request.url)
if len(batch_imports) > 10: if len(batch_imports) > 10:
flash( flash(
"You have too many imports already. Wait until some get cleaned up", "You have too many imports already. Wait until some get cleaned up",
"error", "error",
) )
return render_template( return render_template(
"dashboard/batch_import.html", batch_imports=batch_imports "dashboard/batch_import.html",
batch_imports=batch_imports,
csrf_form=csrf_form,
) )
alias_file = request.files["alias-file"] alias_file = request.files["alias-file"]
@ -66,4 +73,6 @@ def batch_import_route():
return redirect(url_for("dashboard.batch_import_route")) return redirect(url_for("dashboard.batch_import_route"))
return render_template("dashboard/batch_import.html", batch_imports=batch_imports) return render_template(
"dashboard/batch_import.html", batch_imports=batch_imports, csrf_form=csrf_form
)

View file

@ -1,6 +1,7 @@
import arrow import arrow
from flask import flash, redirect, url_for, request, render_template from flask import flash, redirect, url_for, request, render_template
from flask_login import login_required, current_user from flask_login import login_required, current_user
from flask_wtf import FlaskForm
from app.config import JOB_DELETE_ACCOUNT from app.config import JOB_DELETE_ACCOUNT
from app.dashboard.base import dashboard_bp from app.dashboard.base import dashboard_bp
@ -9,11 +10,21 @@ from app.log import LOG
from app.models import Subscription, Job from app.models import Subscription, Job
class DeleteDirForm(FlaskForm):
pass
@dashboard_bp.route("/delete_account", methods=["GET", "POST"]) @dashboard_bp.route("/delete_account", methods=["GET", "POST"])
@login_required @login_required
@sudo_required @sudo_required
def delete_account(): def delete_account():
delete_form = DeleteDirForm()
if request.method == "POST" and request.form.get("form-name") == "delete-account": if request.method == "POST" and request.form.get("form-name") == "delete-account":
if not delete_form.validate():
flash("Invalid request", "warning")
return render_template(
"dashboard/delete_account.html", delete_form=delete_form
)
sub: Subscription = current_user.get_paddle_subscription() sub: Subscription = current_user.get_paddle_subscription()
# user who has canceled can also re-subscribe # user who has canceled can also re-subscribe
if sub and not sub.cancelled: if sub and not sub.cancelled:
@ -36,6 +47,4 @@ def delete_account():
) )
return redirect(url_for("dashboard.setting")) return redirect(url_for("dashboard.setting"))
return render_template( return render_template("dashboard/delete_account.html", delete_form=delete_form)
"dashboard/delete_account.html",
)

View file

@ -1,7 +1,13 @@
from flask import render_template, request, redirect, url_for, flash from flask import render_template, request, redirect, url_for, flash
from flask_login import login_required, current_user from flask_login import login_required, current_user
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, validators from wtforms import (
StringField,
validators,
SelectMultipleField,
BooleanField,
IntegerField,
)
from app.config import ( from app.config import (
EMAIL_DOMAIN, EMAIL_DOMAIN,
@ -21,6 +27,22 @@ class NewDirForm(FlaskForm):
) )
class ToggleDirForm(FlaskForm):
directory_id = IntegerField(validators=[validators.DataRequired()])
directory_enabled = BooleanField(validators=[])
class UpdateDirForm(FlaskForm):
directory_id = IntegerField(validators=[validators.DataRequired()])
mailbox_ids = SelectMultipleField(
validators=[validators.DataRequired()], validate_choice=False, choices=[]
)
class DeleteDirForm(FlaskForm):
directory_id = IntegerField(validators=[validators.DataRequired()])
@dashboard_bp.route("/directory", methods=["GET", "POST"]) @dashboard_bp.route("/directory", methods=["GET", "POST"])
@login_required @login_required
def directory(): def directory():
@ -33,54 +55,68 @@ def directory():
mailboxes = current_user.mailboxes() mailboxes = current_user.mailboxes()
new_dir_form = NewDirForm() new_dir_form = NewDirForm()
toggle_dir_form = ToggleDirForm()
update_dir_form = UpdateDirForm()
update_dir_form.mailbox_ids.choices = [
(str(mailbox.id), str(mailbox.id)) for mailbox in mailboxes
]
delete_dir_form = DeleteDirForm()
if request.method == "POST": if request.method == "POST":
if request.form.get("form-name") == "delete": if request.form.get("form-name") == "delete":
dir_id = request.form.get("dir-id") if not delete_dir_form.validate():
dir = Directory.get(dir_id) flash(f"Invalid request", "warning")
return redirect(url_for("dashboard.directory"))
dir_obj = Directory.get(delete_dir_form.directory_id.data)
if not dir: if not dir_obj:
flash("Unknown error. Refresh the page", "warning") flash("Unknown error. Refresh the page", "warning")
return redirect(url_for("dashboard.directory")) return redirect(url_for("dashboard.directory"))
elif dir.user_id != current_user.id: elif dir_obj.user_id != current_user.id:
flash("You cannot delete this directory", "warning") flash("You cannot delete this directory", "warning")
return redirect(url_for("dashboard.directory")) return redirect(url_for("dashboard.directory"))
name = dir.name name = dir_obj.name
Directory.delete(dir_id) Directory.delete(dir_obj.id)
Session.commit() Session.commit()
flash(f"Directory {name} has been deleted", "success") flash(f"Directory {name} has been deleted", "success")
return redirect(url_for("dashboard.directory")) return redirect(url_for("dashboard.directory"))
if request.form.get("form-name") == "toggle-directory": if request.form.get("form-name") == "toggle-directory":
dir_id = request.form.get("dir-id") if not toggle_dir_form.validate():
dir = Directory.get(dir_id) flash(f"Invalid request", "warning")
return redirect(url_for("dashboard.directory"))
dir_id = toggle_dir_form.directory_id.data
dir_obj = Directory.get(dir_id)
if not dir or dir.user_id != current_user.id: if not dir_obj or dir_obj.user_id != current_user.id:
flash("Unknown error. Refresh the page", "warning") flash("Unknown error. Refresh the page", "warning")
return redirect(url_for("dashboard.directory")) return redirect(url_for("dashboard.directory"))
if request.form.get("dir-status") == "on": if toggle_dir_form.directory_enabled.data:
dir.disabled = False dir_obj.disabled = False
flash(f"On-the-fly is enabled for {dir.name}", "success") flash(f"On-the-fly is enabled for {dir_obj.name}", "success")
else: else:
dir.disabled = True dir_obj.disabled = True
flash(f"On-the-fly is disabled for {dir.name}", "warning") flash(f"On-the-fly is disabled for {dir_obj.name}", "warning")
Session.commit() Session.commit()
return redirect(url_for("dashboard.directory")) return redirect(url_for("dashboard.directory"))
elif request.form.get("form-name") == "update": elif request.form.get("form-name") == "update":
dir_id = request.form.get("dir-id") if not update_dir_form.validate():
dir = Directory.get(dir_id) flash(f"Invalid request", "warning")
return redirect(url_for("dashboard.directory"))
dir_id = update_dir_form.directory_id.data
dir_obj = Directory.get(dir_id)
if not dir or dir.user_id != current_user.id: if not dir_obj or dir_obj.user_id != current_user.id:
flash("Unknown error. Refresh the page", "warning") flash("Unknown error. Refresh the page", "warning")
return redirect(url_for("dashboard.directory")) return redirect(url_for("dashboard.directory"))
mailbox_ids = request.form.getlist("mailbox_ids") mailbox_ids = update_dir_form.mailbox_ids.data
# check if mailbox is not tempered with # check if mailbox is not tempered with
mailboxes = [] mailboxes = []
for mailbox_id in mailbox_ids: for mailbox_id in mailbox_ids:
@ -99,14 +135,14 @@ def directory():
return redirect(url_for("dashboard.directory")) return redirect(url_for("dashboard.directory"))
# first remove all existing directory-mailboxes links # first remove all existing directory-mailboxes links
DirectoryMailbox.filter_by(directory_id=dir.id).delete() DirectoryMailbox.filter_by(directory_id=dir_obj.id).delete()
Session.flush() Session.flush()
for mailbox in mailboxes: for mailbox in mailboxes:
DirectoryMailbox.create(directory_id=dir.id, mailbox_id=mailbox.id) DirectoryMailbox.create(directory_id=dir_obj.id, mailbox_id=mailbox.id)
Session.commit() Session.commit()
flash(f"Directory {dir.name} has been updated", "success") flash(f"Directory {dir_obj.name} has been updated", "success")
return redirect(url_for("dashboard.directory")) return redirect(url_for("dashboard.directory"))
elif request.form.get("form-name") == "create": elif request.form.get("form-name") == "create":
@ -181,6 +217,9 @@ def directory():
return render_template( return render_template(
"dashboard/directory.html", "dashboard/directory.html",
dirs=dirs, dirs=dirs,
toggle_dir_form=toggle_dir_form,
update_dir_form=update_dir_form,
delete_dir_form=delete_dir_form,
new_dir_form=new_dir_form, new_dir_form=new_dir_form,
mailboxes=mailboxes, mailboxes=mailboxes,
EMAIL_DOMAIN=EMAIL_DOMAIN, EMAIL_DOMAIN=EMAIL_DOMAIN,

View file

@ -28,7 +28,7 @@ from app.models import (
Job, Job,
) )
from app.regex_utils import regex_match from app.regex_utils import regex_match
from app.utils import random_string from app.utils import random_string, CSRFValidationForm
@dashboard_bp.route("/domains/<int:custom_domain_id>/dns", methods=["GET", "POST"]) @dashboard_bp.route("/domains/<int:custom_domain_id>/dns", methods=["GET", "POST"])
@ -47,6 +47,7 @@ def domain_detail_dns(custom_domain_id):
spf_record = f"v=spf1 include:{EMAIL_DOMAIN} ~all" spf_record = f"v=spf1 include:{EMAIL_DOMAIN} ~all"
domain_validator = CustomDomainValidation(EMAIL_DOMAIN) domain_validator = CustomDomainValidation(EMAIL_DOMAIN)
csrf_form = CSRFValidationForm()
dmarc_record = "v=DMARC1; p=quarantine; pct=100; adkim=s; aspf=s" dmarc_record = "v=DMARC1; p=quarantine; pct=100; adkim=s; aspf=s"
@ -54,6 +55,9 @@ def domain_detail_dns(custom_domain_id):
mx_errors = spf_errors = dkim_errors = dmarc_errors = ownership_errors = [] mx_errors = spf_errors = dkim_errors = dmarc_errors = ownership_errors = []
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(request.url)
if request.form.get("form-name") == "check-ownership": if request.form.get("form-name") == "check-ownership":
txt_records = get_txt_record(custom_domain.domain) txt_records = get_txt_record(custom_domain.domain)
@ -165,6 +169,7 @@ def domain_detail_dns(custom_domain_id):
@dashboard_bp.route("/domains/<int:custom_domain_id>/info", methods=["GET", "POST"]) @dashboard_bp.route("/domains/<int:custom_domain_id>/info", methods=["GET", "POST"])
@login_required @login_required
def domain_detail(custom_domain_id): def domain_detail(custom_domain_id):
csrf_form = CSRFValidationForm()
custom_domain: CustomDomain = CustomDomain.get(custom_domain_id) custom_domain: CustomDomain = CustomDomain.get(custom_domain_id)
mailboxes = current_user.mailboxes() mailboxes = current_user.mailboxes()
@ -173,6 +178,9 @@ def domain_detail(custom_domain_id):
return redirect(url_for("dashboard.index")) return redirect(url_for("dashboard.index"))
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(request.url)
if request.form.get("form-name") == "switch-catch-all": if request.form.get("form-name") == "switch-catch-all":
custom_domain.catch_all = not custom_domain.catch_all custom_domain.catch_all = not custom_domain.catch_all
Session.commit() Session.commit()
@ -301,12 +309,16 @@ def domain_detail(custom_domain_id):
@dashboard_bp.route("/domains/<int:custom_domain_id>/trash", methods=["GET", "POST"]) @dashboard_bp.route("/domains/<int:custom_domain_id>/trash", methods=["GET", "POST"])
@login_required @login_required
def domain_detail_trash(custom_domain_id): def domain_detail_trash(custom_domain_id):
csrf_form = CSRFValidationForm()
custom_domain = CustomDomain.get(custom_domain_id) custom_domain = CustomDomain.get(custom_domain_id)
if not custom_domain or custom_domain.user_id != current_user.id: if not custom_domain or custom_domain.user_id != current_user.id:
flash("You cannot see this page", "warning") flash("You cannot see this page", "warning")
return redirect(url_for("dashboard.index")) return redirect(url_for("dashboard.index"))
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(request.url)
if request.form.get("form-name") == "empty-all": if request.form.get("form-name") == "empty-all":
DomainDeletedAlias.filter_by(domain_id=custom_domain.id).delete() DomainDeletedAlias.filter_by(domain_id=custom_domain.id).delete()
Session.commit() Session.commit()
@ -350,6 +362,7 @@ def domain_detail_trash(custom_domain_id):
"dashboard/domain_detail/trash.html", "dashboard/domain_detail/trash.html",
domain_deleted_aliases=domain_deleted_aliases, domain_deleted_aliases=domain_deleted_aliases,
custom_domain=custom_domain, custom_domain=custom_domain,
csrf_form=csrf_form,
) )

View file

@ -17,6 +17,7 @@ from app.models import (
EmailLog, EmailLog,
Contact, Contact,
) )
from app.utils import CSRFValidationForm
@dataclass @dataclass
@ -75,8 +76,12 @@ def index():
"highlight_alias_id must be a number, received %s", "highlight_alias_id must be a number, received %s",
request.args.get("highlight_alias_id"), request.args.get("highlight_alias_id"),
) )
csrf_form = CSRFValidationForm()
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(request.url)
if request.form.get("form-name") == "create-custom-email": if request.form.get("form-name") == "create-custom-email":
if current_user.can_create_new_alias(): if current_user.can_create_new_alias():
return redirect(url_for("dashboard.custom_alias")) return redirect(url_for("dashboard.custom_alias"))
@ -204,6 +209,7 @@ def index():
sort=sort, sort=sort,
filter=alias_filter, filter=alias_filter,
stats=stats, stats=stats,
csrf_form=csrf_form,
) )

View file

@ -18,6 +18,7 @@ from app.email_utils import (
) )
from app.log import LOG from app.log import LOG
from app.models import Mailbox, Job from app.models import Mailbox, Job
from app.utils import CSRFValidationForm
class NewMailboxForm(FlaskForm): class NewMailboxForm(FlaskForm):
@ -36,8 +37,12 @@ def mailbox_route():
) )
new_mailbox_form = NewMailboxForm() new_mailbox_form = NewMailboxForm()
csrf_form = CSRFValidationForm()
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(request.url)
if request.form.get("form-name") == "delete": if request.form.get("form-name") == "delete":
mailbox_id = request.form.get("mailbox-id") mailbox_id = request.form.get("mailbox-id")
mailbox = Mailbox.get(mailbox_id) mailbox = Mailbox.get(mailbox_id)
@ -127,6 +132,7 @@ def mailbox_route():
"dashboard/mailbox.html", "dashboard/mailbox.html",
mailboxes=mailboxes, mailboxes=mailboxes,
new_mailbox_form=new_mailbox_form, new_mailbox_form=new_mailbox_form,
csrf_form=csrf_form,
) )

View file

@ -17,7 +17,7 @@ from app.log import LOG
from app.models import Alias, AuthorizedAddress from app.models import Alias, AuthorizedAddress
from app.models import Mailbox from app.models import Mailbox
from app.pgp_utils import PGPException, load_public_key_and_check from app.pgp_utils import PGPException, load_public_key_and_check
from app.utils import sanitize_email from app.utils import sanitize_email, CSRFValidationForm
class ChangeEmailForm(FlaskForm): class ChangeEmailForm(FlaskForm):
@ -35,6 +35,7 @@ def mailbox_detail_route(mailbox_id):
return redirect(url_for("dashboard.index")) return redirect(url_for("dashboard.index"))
change_email_form = ChangeEmailForm() change_email_form = ChangeEmailForm()
csrf_form = CSRFValidationForm()
if mailbox.new_email: if mailbox.new_email:
pending_email = mailbox.new_email pending_email = mailbox.new_email
@ -42,6 +43,9 @@ def mailbox_detail_route(mailbox_id):
pending_email = None pending_email = None
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(request.url)
if ( if (
request.form.get("form-name") == "update-email" request.form.get("form-name") == "update-email"
and change_email_form.validate_on_submit() and change_email_form.validate_on_submit()

View file

@ -53,7 +53,7 @@ from app.models import (
UnsubscribeBehaviourEnum, UnsubscribeBehaviourEnum,
) )
from app.proton.utils import get_proton_partner, perform_proton_account_unlink from app.proton.utils import get_proton_partner, perform_proton_account_unlink
from app.utils import random_string, sanitize_email from app.utils import random_string, sanitize_email, CSRFValidationForm
class SettingForm(FlaskForm): class SettingForm(FlaskForm):
@ -104,6 +104,7 @@ def setting():
form = SettingForm() form = SettingForm()
promo_form = PromoCodeForm() promo_form = PromoCodeForm()
change_email_form = ChangeEmailForm() change_email_form = ChangeEmailForm()
csrf_form = CSRFValidationForm()
email_change = EmailChange.get_by(user_id=current_user.id) email_change = EmailChange.get_by(user_id=current_user.id)
if email_change: if email_change:
@ -112,6 +113,9 @@ def setting():
pending_email = None pending_email = None
if request.method == "POST": if request.method == "POST":
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(url_for("dashboard.setting"))
if request.form.get("form-name") == "update-email": if request.form.get("form-name") == "update-email":
if change_email_form.validate(): if change_email_form.validate():
# whether user can proceed with the email update # whether user can proceed with the email update
@ -395,6 +399,7 @@ def setting():
return render_template( return render_template(
"dashboard/setting.html", "dashboard/setting.html",
csrf_form=csrf_form,
form=form, form=form,
PlanEnum=PlanEnum, PlanEnum=PlanEnum,
SenderFormatEnum=SenderFormatEnum, SenderFormatEnum=SenderFormatEnum,
@ -477,9 +482,14 @@ def cancel_email_change():
return redirect(url_for("dashboard.setting")) return redirect(url_for("dashboard.setting"))
@dashboard_bp.route("/unlink_proton_account", methods=["GET", "POST"]) @dashboard_bp.route("/unlink_proton_account", methods=["POST"])
@login_required @login_required
def unlink_proton_account(): def unlink_proton_account():
csrf_form = CSRFValidationForm()
if not csrf_form.validate():
flash("Invalid request", "warning")
return redirect(url_for("dashboard.setting"))
perform_proton_account_unlink(current_user) perform_proton_account_unlink(current_user)
flash("Your Proton account has been unlinked", "success") flash("Your Proton account has been unlinked", "success")
return redirect(url_for("dashboard.setting")) return redirect(url_for("dashboard.setting"))

View file

@ -6,6 +6,7 @@ import urllib.parse
from functools import wraps from functools import wraps
from typing import List, Optional from typing import List, Optional
from flask_wtf import FlaskForm
from unidecode import unidecode from unidecode import unidecode
from .config import WORDS_FILE_PATH, ALLOWED_REDIRECT_DOMAINS from .config import WORDS_FILE_PATH, ALLOWED_REDIRECT_DOMAINS
@ -126,3 +127,7 @@ def debug_info(func):
return ret return ret
return wrap return wrap
class CSRFValidationForm(FlaskForm):
pass

View file

@ -80,6 +80,7 @@
<div class="col-12 col-lg-6 pt-1"> <div class="col-12 col-lg-6 pt-1">
<div class="float-right d-flex"> <div class="float-right d-flex">
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="search"> <input type="hidden" name="form-name" value="search">
<input type="search" <input type="search"
name="query" name="query"
@ -184,6 +185,7 @@
</div> </div>
<a href="{{ url_for('dashboard.contact_detail_route', contact_id=contact.id) }}">Edit ➡</a> <a href="{{ url_for('dashboard.contact_detail_route', contact_id=contact.id) }}">Edit ➡</a>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="delete"> <input type="hidden" name="form-name" value="delete">
<input type="hidden" name="contact-id" value="{{ contact.id }}"> <input type="hidden" name="contact-id" value="{{ contact.id }}">
<span class="card-link btn btn-link float-right delete-forward-email text-danger">Delete</span> <span class="card-link btn btn-link float-right delete-forward-email text-danger">Delete</span>

View file

@ -26,6 +26,7 @@
This transfer URL is <strong>valid for 24 hours</strong>. If it hasn't been used by then it will be automatically disabled. This transfer URL is <strong>valid for 24 hours</strong>. If it hasn't been used by then it will be automatically disabled.
</p> </p>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="remove"> <input type="hidden" name="form-name" value="remove">
<button class="btn btn-warning mt-2">Remove alias transfer URL</button> <button class="btn btn-warning mt-2">Remove alias transfer URL</button>
<div class="small-text">If you don't want to share this alias anymore, you can remove the share URL.</div> <div class="small-text">If you don't want to share this alias anymore, you can remove the share URL.</div>
@ -34,9 +35,10 @@
{% if link_active %} {% if link_active %}
<p class="alert alert-info"> <p class="alert alert-info">
You have an active transfer link. If you don't want to share this alias any more, please delete the link. You have an active transfer link. If you don't want to share this alias anymore, please delete the link.
</p> </p>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="remove"> <input type="hidden" name="form-name" value="remove">
<button class="btn btn-warning mt-2">Remove alias transfer URL</button> <button class="btn btn-warning mt-2">Remove alias transfer URL</button>
</form> </form>
@ -46,6 +48,7 @@
please create the <b>Share URL</b> 👇 and send it to the other person. please create the <b>Share URL</b> 👇 and send it to the other person.
</p> </p>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="create"> <input type="hidden" name="form-name" value="create">
<button class="btn btn-primary mt-2">Generate a new alias transfer URL</button> <button class="btn btn-primary mt-2">Generate a new alias transfer URL</button>
</form> </form>

View file

@ -24,6 +24,7 @@
download>Download CSV Template</a> download>Download CSV Template</a>
<hr /> <hr />
<form method="post" enctype="multipart/form-data" class="mt-4"> <form method="post" enctype="multipart/form-data" class="mt-4">
{{ csrf_form.csrf_token }}
<input required <input required
type="file" type="file"
name="alias-file" name="alias-file"

View file

@ -13,6 +13,7 @@
</div> </div>
<form method="post"> <form method="post">
<input type="hidden" name="form-name" value="delete-account"> <input type="hidden" name="form-name" value="delete-account">
{{ delete_form.csrf_token }}
<span class="delete-account btn btn-outline-danger">Delete account</span> <span class="delete-account btn btn-outline-danger">Delete account</span>
</form> </form>
</div> </div>

View file

@ -66,11 +66,12 @@
<div class="d-flex"> <div class="d-flex">
{{ dir.name }} {{ dir.name }}
<form method="post"> <form method="post">
{{ toggle_dir_form.csrf_token }}
<input type="hidden" name="form-name" value="toggle-directory"> <input type="hidden" name="form-name" value="toggle-directory">
<input type="hidden" name="dir-id" value="{{ dir.id }}"> {{ toggle_dir_form.directory_id( type="hidden", value=dir.id) }}
<label class="custom-switch cursor" style="padding-left: 1rem" data-toggle="tooltip" {% if dir.disabled %} <label class="custom-switch cursor" style="padding-left: 1rem" data-toggle="tooltip" {% if dir.disabled %}
title="Enable directory on-the-fly alias creation" {% else %} title="Disable directory on-the-fly alias creation" {% endif %}> title="Enable directory on-the-fly alias creation" {% else %} title="Disable directory on-the-fly alias creation" {% endif %}>
<input type="checkbox" class="custom-switch-input" name="dir-status" {{ "" if dir.disabled else "checked" }}> {{ toggle_dir_form.directory_enabled( class="custom-switch-input", checked=(not dir.disabled) ) }}
<span class="custom-switch-indicator"></span> <span class="custom-switch-indicator"></span>
</label> </label>
</form> </form>
@ -92,8 +93,9 @@
<br /> <br />
{% set dir_mailboxes=dir.mailboxes %} {% set dir_mailboxes=dir.mailboxes %}
<form method="post" class="mt-2"> <form method="post" class="mt-2">
{{ update_dir_form.csrf_token }}
<input type="hidden" name="form-name" value="update"> <input type="hidden" name="form-name" value="update">
<input type="hidden" name="dir-id" value="{{ dir.id }}"> {{ update_dir_form.directory_id( type="hidden", value=dir.id) }}
<select data-width="100%" <select data-width="100%"
required required
class="mailbox-select" class="mailbox-select"
@ -115,9 +117,9 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<form method="post"> <form method="post">
{{ delete_dir_form.csrf_token }}
<input type="hidden" name="form-name" value="delete"> <input type="hidden" name="form-name" value="delete">
<input type="hidden" class="dir-name" value="{{ dir.name }}"> {{ delete_dir_form.directory_id( type="hidden", value=dir.id) }}
<input type="hidden" name="dir-id" value="{{ dir.id }}">
<span class="card-link btn btn-link float-right text-danger delete-dir">Delete</span> <span class="card-link btn btn-link float-right text-danger delete-dir">Delete</span>
</form> </form>
</div> </div>
@ -173,18 +175,20 @@
<script> <script>
$(".delete-dir").on("click", function (e) { $(".delete-dir").on("click", function (e) {
let directory = $(this).parent().find(".dir-name").val(); let directory_name = $(this).parent().find("#directory_name").val();
const unsanitizedMessage = `All aliases associated with <b>${directory}</b> directory will also be deleted. ` + const element = document.createElement('div');
element.innerText = directory_name;
const sanitized_name = element.innerHTML;
const message = `All aliases associated with <b>${sanitized_name}</b> directory will also be deleted. ` +
`As a deleted directory can't be used by someone else, deleting a directory doesn't reset your directory quota. ` + `As a deleted directory can't be used by someone else, deleting a directory doesn't reset your directory quota. ` +
`Your directory quota will be {{ current_user.directory_quota }} after the deletion, ` + `Your directory quota will be {{ current_user.directory_quota }} after the deletion, ` +
" please confirm."; " please confirm.";
const element = document.createElement('div');
element.innerText = unsanitizedMessage;
const sanitizedMessage = element.innerHTML;
bootbox.confirm({ bootbox.confirm({
message: sanitizedMessage, message: message,
buttons: { buttons: {
confirm: { confirm: {
label: 'Yes, delete it', label: 'Yes, delete it',

View file

@ -41,6 +41,7 @@
data-clipboard-text="{{ custom_domain.get_ownership_dns_txt_value() }}">{{ custom_domain.get_ownership_dns_txt_value() }}</em> data-clipboard-text="{{ custom_domain.get_ownership_dns_txt_value() }}">{{ custom_domain.get_ownership_dns_txt_value() }}</em>
</div> </div>
<form method="post" action="#ownership-form"> <form method="post" action="#ownership-form">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="check-ownership"> <input type="hidden" name="form-name" value="check-ownership">
<button type="submit" class="btn btn-primary">Verify</button> <button type="submit" class="btn btn-primary">Verify</button>
</form> </form>
@ -107,6 +108,7 @@
</div> </div>
{% endfor %} {% endfor %}
<form method="post" action="#mx-form"> <form method="post" action="#mx-form">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="check-mx"> <input type="hidden" name="form-name" value="check-mx">
{% if custom_domain.verified %} {% if custom_domain.verified %}
@ -180,6 +182,7 @@
</em> </em>
</div> </div>
<form method="post" action="#spf-form"> <form method="post" action="#spf-form">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="check-spf"> <input type="hidden" name="form-name" value="check-spf">
{% if custom_domain.spf_verified %} {% if custom_domain.spf_verified %}
@ -273,6 +276,7 @@
<img src="/static/images/cloudflare-proxy.png" class="w-100"> <img src="/static/images/cloudflare-proxy.png" class="w-100">
</div> </div>
<form method="post" action="#dkim-form"> <form method="post" action="#dkim-form">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="check-dkim"> <input type="hidden" name="form-name" value="check-dkim">
{% if custom_domain.dkim_verified %} {% if custom_domain.dkim_verified %}
@ -367,6 +371,7 @@
<br /> <br />
</div> </div>
<form method="post" action="#dmarc-form"> <form method="post" action="#dmarc-form">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="check-dmarc"> <input type="hidden" name="form-name" value="check-dmarc">
{% if custom_domain.dmarc_verified %} {% if custom_domain.dmarc_verified %}

View file

@ -10,6 +10,7 @@
<h3 class="mb-1">Auto create/on the fly alias</h3> <h3 class="mb-1">Auto create/on the fly alias</h3>
<div> <div>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="switch-catch-all"> <input type="hidden" name="form-name" value="switch-catch-all">
<label class="custom-switch cursor mt-2 pl-0" data-toggle="tooltip" {% if custom_domain.catch_all %} <label class="custom-switch cursor mt-2 pl-0" data-toggle="tooltip" {% if custom_domain.catch_all %}
title="Disable catch-all" {% else %} title="Enable catch-all" {% endif %}> title="Disable catch-all" {% else %} title="Enable catch-all" {% endif %}>
@ -41,6 +42,7 @@
</div> </div>
{% set domain_mailboxes=custom_domain.mailboxes %} {% set domain_mailboxes=custom_domain.mailboxes %}
<form method="post" class="mt-2"> <form method="post" class="mt-2">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="update"> <input type="hidden" name="form-name" value="update">
<input type="hidden" name="domain-id" value="{{ custom_domain.id }}"> <input type="hidden" name="domain-id" value="{{ custom_domain.id }}">
<div class="d-flex"> <div class="d-flex">
@ -73,6 +75,7 @@
</div> </div>
<div> <div>
<form method="post" class="form-inline"> <form method="post" class="form-inline">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="set-name"> <input type="hidden" name="form-name" value="set-name">
<div class="form-group"> <div class="form-group">
<input class="form-control mr-2" <input class="form-control mr-2"
@ -94,6 +97,7 @@
<div>Add a random prefix for this domain when creating a new alias.</div> <div>Add a random prefix for this domain when creating a new alias.</div>
<div> <div>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" <input type="hidden"
name="form-name" name="form-name"
value="switch-random-prefix-generation"> value="switch-random-prefix-generation">
@ -134,6 +138,7 @@
{% endif %} {% endif %}
</div> </div>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="delete"> <input type="hidden" name="form-name" value="delete">
<span class="delete-custom-domain btn btn-danger">Delete {{ custom_domain.domain }}</span> <span class="delete-custom-domain btn btn-danger">Delete {{ custom_domain.domain }}</span>
</form> </form>

View file

@ -26,6 +26,7 @@
{% if domain_deleted_aliases | length > 0 %} {% if domain_deleted_aliases | length > 0 %}
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="empty-all"> <input type="hidden" name="form-name" value="empty-all">
<button class="btn btn-outline-danger">Empty Trash</button> <button class="btn btn-outline-danger">Empty Trash</button>
<div class="small-text"> <div class="small-text">
@ -42,6 +43,7 @@
<b>{{ deleted_alias.email }}</b> - <b>{{ deleted_alias.email }}</b> -
deleted {{ deleted_alias.created_at | dt }} deleted {{ deleted_alias.created_at | dt }}
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="remove-single"> <input type="hidden" name="form-name" value="remove-single">
<input type="hidden" name="deleted-alias-id" value="{{ deleted_alias.id }}"> <input type="hidden" name="deleted-alias-id" value="{{ deleted_alias.id }}">
<button class="btn btn-sm btn-outline-warning">Remove from trash</button> <button class="btn btn-sm btn-outline-warning">Remove from trash</button>

View file

@ -90,6 +90,7 @@
<div> <div>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="create-custom-email"> <input type="hidden" name="form-name" value="create-custom-email">
<button data-toggle="tooltip" <button data-toggle="tooltip"
title="Create a custom alias" title="Create a custom alias"
@ -99,6 +100,7 @@
</form> </form>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="create-random-email"> <input type="hidden" name="form-name" value="create-random-email">
<button data-toggle="tooltip" <button data-toggle="tooltip"
title="Create a totally random alias" title="Create a totally random alias"
@ -117,6 +119,7 @@
aria-labelledby="btnGroupDrop1"> aria-labelledby="btnGroupDrop1">
<div> <div>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="create-random-email"> <input type="hidden" name="form-name" value="create-random-email">
<input type="hidden" <input type="hidden"
name="generator_scheme" name="generator_scheme"
@ -126,6 +129,7 @@
</div> </div>
<div> <div>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="create-random-email"> <input type="hidden" name="form-name" value="create-random-email">
<input type="hidden" <input type="hidden"
name="generator_scheme" name="generator_scheme"
@ -152,6 +156,7 @@
<!-- Filter Control --> <!-- Filter Control -->
<div class="col d-flex"> <div class="col d-flex">
<form method="get" class="form-inline"> <form method="get" class="form-inline">
{{ csrf_form.csrf_token }}
<select name="sort" <select name="sort"
onchange="this.form.submit()" onchange="this.form.submit()"
class="form-control mr-3 shadow"> class="form-control mr-3 shadow">
@ -493,6 +498,7 @@
<i class="ml-0 dropdown-icon fe fe-share-2 text-primary"></i> <i class="ml-0 dropdown-icon fe fe-share-2 text-primary"></i>
</a> </a>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="delete-alias"> <input type="hidden" name="form-name" value="delete-alias">
<input type="hidden" name="alias-id" value="{{ alias.id }}"> <input type="hidden" name="alias-id" value="{{ alias.id }}">
<input type="hidden" name="alias" class="alias" value="{{ alias.email }}"> <input type="hidden" name="alias" class="alias" value="{{ alias.email }}">

View file

@ -86,6 +86,7 @@
<div class="col"> <div class="col">
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="set-default"> <input type="hidden" name="form-name" value="set-default">
<input type="hidden" class="mailbox" value="{{ mailbox.email }}"> <input type="hidden" class="mailbox" value="{{ mailbox.email }}">
<input type="hidden" name="mailbox-id" value="{{ mailbox.id }}"> <input type="hidden" name="mailbox-id" value="{{ mailbox.id }}">
@ -97,6 +98,7 @@
{% endif %} {% endif %}
<div class="col"> <div class="col">
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="delete"> <input type="hidden" name="form-name" value="delete">
<input type="hidden" class="mailbox" value="{{ mailbox.email }}"> <input type="hidden" class="mailbox" value="{{ mailbox.email }}">
<input type="hidden" name="mailbox-id" value="{{ mailbox.id }}"> <input type="hidden" name="mailbox-id" value="{{ mailbox.id }}">

View file

@ -87,6 +87,7 @@
{% if mailbox.pgp_finger_print %} {% if mailbox.pgp_finger_print %}
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="toggle-pgp"> <input type="hidden" name="form-name" value="toggle-pgp">
<label class="custom-switch cursor" style="padding-left: 1rem" data-toggle="tooltip" {% if mailbox.disable_pgp %} <label class="custom-switch cursor" style="padding-left: 1rem" data-toggle="tooltip" {% if mailbox.disable_pgp %}
title="Enable PGP" {% else %} title="Disable PGP" {% endif %}> title="Enable PGP" {% else %} title="Disable PGP" {% endif %}>
@ -108,6 +109,7 @@
<div class="alert alert-danger" role="alert">This feature is only available in premium plan.</div> <div class="alert alert-danger" role="alert">This feature is only available in premium plan.</div>
{% endif %} {% endif %}
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<div class="form-group"> <div class="form-group">
<label class="form-label">PGP Public Key</label> <label class="form-label">PGP Public Key</label>
<textarea name="pgp" {% if not current_user.is_premium() %} disabled {% endif %} class="form-control" rows=10 id="pgp-public-key" placeholder="-----BEGIN PGP PUBLIC KEY BLOCK-----">{{ mailbox.pgp_public_key or "" }}</textarea> <textarea name="pgp" {% if not current_user.is_premium() %} disabled {% endif %} class="form-control" rows=10 id="pgp-public-key" placeholder="-----BEGIN PGP PUBLIC KEY BLOCK-----">{{ mailbox.pgp_public_key or "" }}</textarea>
@ -127,6 +129,7 @@
<div class="card" {% if not mailbox.pgp_enabled() %} <div class="card" {% if not mailbox.pgp_enabled() %}
disabled {% endif %}> disabled {% endif %}>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="generic-subject"> <input type="hidden" name="form-name" value="generic-subject">
<div class="card-body"> <div class="card-body">
<div class="card-title"> <div class="card-title">
@ -167,6 +170,7 @@
<div class="card" id="spf"> <div class="card" id="spf">
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="force-spf"> <input type="hidden" name="form-name" value="force-spf">
<div class="card-body"> <div class="card-body">
<div class="card-title"> <div class="card-title">
@ -210,6 +214,7 @@
<li> <li>
{{ authorized_address.email }} {{ authorized_address.email }}
<form method="post" action="#authorized-address" style="display: inline"> <form method="post" action="#authorized-address" style="display: inline">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="delete-authorized-address"> <input type="hidden" name="form-name" value="delete-authorized-address">
<input type="hidden" <input type="hidden"
name="authorized-address-id" name="authorized-address-id"
@ -221,6 +226,7 @@
</ul> </ul>
{% endif %} {% endif %}
<form method="post" action="#authorized-address" class="form-inline"> <form method="post" action="#authorized-address" class="form-inline">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="add-authorized-address"> <input type="hidden" name="form-name" value="add-authorized-address">
<input type="email" name="email" size="50" class="form-control"> <input type="email" name="email" size="50" class="form-control">
<input type="submit" class="btn btn-primary" value="Add"> <input type="submit" class="btn btn-primary" value="Add">

View file

@ -132,6 +132,7 @@
<div class="card-title">Newsletters</div> <div class="card-title">Newsletters</div>
<div class="mb-3">We will occasionally send you emails with new feature announcements.</div> <div class="mb-3">We will occasionally send you emails with new feature announcements.</div>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="notification-preference"> <input type="hidden" name="form-name" value="notification-preference">
<div class="form-check"> <div class="form-check">
<input type="checkbox" <input type="checkbox"
@ -231,11 +232,14 @@
Your account is currently linked to the Proton account <b>{{ proton_linked_account }}</b> Your account is currently linked to the Proton account <b>{{ proton_linked_account }}</b>
<br /> <br />
</div> </div>
<a class="btn btn-primary mt-2 proton-button" <form method="post"
href="{{ url_for('dashboard.unlink_proton_account') }}"> action="{{ url_for("dashboard.unlink_proton_account") }}">
<img class="mr-2" src="/static/images/proton.svg"> {{ csrf_form.csrf_token }}
Unlink account <button class="btn btn-primary mt-2 proton-button">
</a> <img class="mr-2" src="/static/images/proton.svg">
Unlink account
</button>
</form>
{% else %} {% else %}
<div class="mb-3"> <div class="mb-3">
You can connect your Proton and SimpleLogin accounts. You can connect your Proton and SimpleLogin accounts.
@ -262,6 +266,7 @@
<div class="card-title">Password</div> <div class="card-title">Password</div>
<div class="mb-3">You will receive an email containing instructions on how to change your password.</div> <div class="mb-3">You will receive an email containing instructions on how to change your password.</div>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="change-password"> <input type="hidden" name="form-name" value="change-password">
<button class="btn btn-outline-primary">Change password</button> <button class="btn btn-outline-primary">Change password</button>
</form> </form>
@ -278,6 +283,7 @@
Change the way random aliases are generated by default. Change the way random aliases are generated by default.
</div> </div>
<form method="post" action="#random-alias" class="form-inline"> <form method="post" action="#random-alias" class="form-inline">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="change-alias-generator"> <input type="hidden" name="form-name" value="change-alias-generator">
<select class="form-control mr-sm-2" name="alias-generator-scheme"> <select class="form-control mr-sm-2" name="alias-generator-scheme">
<option value="{{ AliasGeneratorEnum.word.value }}" <option value="{{ AliasGeneratorEnum.word.value }}"
@ -299,6 +305,7 @@
Select the default domain for aliases. Select the default domain for aliases.
</div> </div>
<form method="post" action="#random-alias" class="form-inline"> <form method="post" action="#random-alias" class="form-inline">
{{ csrf_form.csrf_token }}
<input type="hidden" <input type="hidden"
name="form-name" name="form-name"
value="change-random-alias-default-domain"> value="change-random-alias-default-domain">
@ -330,6 +337,7 @@
Select the default suffix generator for aliases. Select the default suffix generator for aliases.
</div> </div>
<form method="post" action="#random-alias-suffix" class="form-inline"> <form method="post" action="#random-alias-suffix" class="form-inline">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="random-alias-suffix"> <input type="hidden" name="form-name" value="random-alias-suffix">
<select class="form-control mr-sm-2" name="random-alias-suffix-generator"> <select class="form-control mr-sm-2" name="random-alias-suffix-generator">
<option value="0" <option value="0"
@ -362,6 +370,7 @@
in the original form and needs to <b>transform</b> it to one of the formats below. in the original form and needs to <b>transform</b> it to one of the formats below.
</div> </div>
<form method="post" action="#sender-format"> <form method="post" action="#sender-format">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="change-sender-format"> <input type="hidden" name="form-name" value="change-sender-format">
<select class="form-control mr-sm-2" name="sender-format"> <select class="form-control mr-sm-2" name="sender-format">
<option value="{{ SenderFormatEnum.AT.value }}" <option value="{{ SenderFormatEnum.AT.value }}"
@ -408,6 +417,7 @@
<br /> <br />
</div> </div>
<form method="post" action="#reverse-alias-replacement-section"> <form method="post" action="#reverse-alias-replacement-section">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="replace-ra"> <input type="hidden" name="form-name" value="replace-ra">
<div class="form-check"> <div class="form-check">
<input type="checkbox" <input type="checkbox"
@ -440,6 +450,7 @@
Please note that existing reverse-aliases won't change. Please note that existing reverse-aliases won't change.
</div> </div>
<form method="post" action="#sender-in-ra"> <form method="post" action="#sender-in-ra">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="sender-in-ra"> <input type="hidden" name="form-name" value="sender-in-ra">
<div class="form-check"> <div class="form-check">
<input type="checkbox" id="include-sender-ra" name="enable" <input type="checkbox" id="include-sender-ra" name="enable"
@ -471,6 +482,7 @@
<br /> <br />
</div> </div>
<form method="post" action="#expand-alias-info-section"> <form method="post" action="#expand-alias-info-section">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="expand-alias-info"> <input type="hidden" name="form-name" value="expand-alias-info">
<div class="form-check"> <div class="form-check">
<input type="checkbox" <input type="checkbox"
@ -504,6 +516,7 @@
style="max-width: 40%"> style="max-width: 40%">
</div> </div>
<form method="post" action="#include_website_in_one_click_alias"> <form method="post" action="#include_website_in_one_click_alias">
{{ csrf_form.csrf_token }}
<input type="hidden" <input type="hidden"
name="form-name" name="form-name"
value="include_website_in_one_click_alias"> value="include_website_in_one_click_alias">
@ -535,6 +548,7 @@
{# You can disable these "loop" emails by enabling this option.#} {# You can disable these "loop" emails by enabling this option.#}
{# </div>#} {# </div>#}
{# <form method="post" action="#ignore-loop-email-section">#} {# <form method="post" action="#ignore-loop-email-section">#}
{# {{ csrf_form.csrf_token }} #}
{# <input type="hidden" name="form-name" value="ignore-loop-email">#} {# <input type="hidden" name="form-name" value="ignore-loop-email">#}
{# <div class="form-check">#} {# <div class="form-check">#}
{# <input type="checkbox" id="ignore-loop-email" name="enable"#} {# <input type="checkbox" id="ignore-loop-email" name="enable"#}
@ -574,6 +588,7 @@
<form method="post" <form method="post"
action="#one-click-unsubscribe-section" action="#one-click-unsubscribe-section"
class="form-inline"> class="form-inline">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="one-click-unsubscribe"> <input type="hidden" name="form-name" value="one-click-unsubscribe">
<select class="form-control mr-sm-2" name="unsubscribe-behaviour"> <select class="form-control mr-sm-2" name="unsubscribe-behaviour">
<option value="{{ UnsubscribeBehaviourEnum.PreserveOriginal.name }}" {% if current_user.unsub_behaviour.value == UnsubscribeBehaviourEnum.PreserveOriginal.value %} <option value="{{ UnsubscribeBehaviourEnum.PreserveOriginal.name }}" {% if current_user.unsub_behaviour.value == UnsubscribeBehaviourEnum.PreserveOriginal.value %}
@ -633,6 +648,7 @@
<br /> <br />
</div> </div>
<form method="post" action="#blocked-behaviour" class="form-inline"> <form method="post" action="#blocked-behaviour" class="form-inline">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="change-blocked-behaviour"> <input type="hidden" name="form-name" value="change-blocked-behaviour">
<select class="form-control mr-sm-2" name="blocked-behaviour"> <select class="form-control mr-sm-2" name="blocked-behaviour">
<option value="{{ BlockBehaviourEnum.return_2xx.value }}" <option value="{{ BlockBehaviourEnum.return_2xx.value }}"
@ -665,6 +681,7 @@
As email headers aren't encrypted, your mailbox service can know the sender address via this header. As email headers aren't encrypted, your mailbox service can know the sender address via this header.
</div> </div>
<form method="post" action="#sender-header"> <form method="post" action="#sender-header">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="sender-header"> <input type="hidden" name="form-name" value="sender-header">
<div class="form-check"> <div class="form-check">
<input type="checkbox" <input type="checkbox"
@ -709,6 +726,7 @@
<div class="d-flex"> <div class="d-flex">
<div> <div>
<form method="post"> <form method="post">
{{ csrf_form.csrf_token }}
<input type="hidden" name="form-name" value="send-full-user-report"> <input type="hidden" name="form-name" value="send-full-user-report">
<button class="btn btn-outline-info"> <button class="btn btn-outline-info">
Request your data Request your data

View file

@ -2,61 +2,66 @@ from flask import url_for
from app.config import MAX_NB_DIRECTORY from app.config import MAX_NB_DIRECTORY
from app.models import Directory from app.models import Directory
from tests.utils import login from tests.utils import login, random_token
def test_create_directory(flask_client): def test_create_directory(flask_client):
login(flask_client) login(flask_client)
directory_name = random_token()
r = flask_client.post( r = flask_client.post(
url_for("dashboard.directory"), url_for("dashboard.directory"),
data={"form-name": "create", "name": "test"}, data={"form-name": "create", "name": directory_name},
follow_redirects=True, follow_redirects=True,
) )
assert r.status_code == 200 assert r.status_code == 200
assert f"Directory test is created" in r.data.decode() assert f"Directory {directory_name} is created" in r.data.decode()
assert Directory.get_by(name="test") is not None assert Directory.get_by(name=directory_name) is not None
def test_delete_directory(flask_client): def test_delete_directory(flask_client):
"""cannot add domain if user personal email uses this domain""" """cannot add domain if user personal email uses this domain"""
user = login(flask_client) user = login(flask_client)
directory = Directory.create(name="test", user_id=user.id, commit=True) directory_name = random_token()
directory = Directory.create(name=directory_name, user_id=user.id, commit=True)
r = flask_client.post( r = flask_client.post(
url_for("dashboard.directory"), url_for("dashboard.directory"),
data={"form-name": "delete", "dir-id": directory.id}, data={"form-name": "delete", "directory_id": directory.id},
follow_redirects=True, follow_redirects=True,
) )
assert r.status_code == 200 assert r.status_code == 200
assert f"Directory test has been deleted" in r.data.decode() assert f"Directory {directory_name} has been deleted" in r.data.decode()
assert Directory.get_by(name="test") is None assert Directory.get_by(name=directory_name) is None
def test_create_directory_in_trash(flask_client): def test_create_directory_in_trash(flask_client):
user = login(flask_client) user = login(flask_client)
directory_name = random_token()
directory = Directory.create(name="test", user_id=user.id, commit=True) directory = Directory.create(name=directory_name, user_id=user.id, commit=True)
# delete the directory # delete the directory
r = flask_client.post( r = flask_client.post(
url_for("dashboard.directory"), url_for("dashboard.directory"),
data={"form-name": "delete", "dir-id": directory.id}, data={"form-name": "delete", "directory_id": directory.id},
follow_redirects=True, follow_redirects=True,
) )
assert Directory.get_by(name="test") is None assert Directory.get_by(name=directory_name) is None
# try to recreate the directory # try to recreate the directory
r = flask_client.post( r = flask_client.post(
url_for("dashboard.directory"), url_for("dashboard.directory"),
data={"form-name": "create", "name": "test"}, data={"form-name": "create", "name": directory_name},
follow_redirects=True, follow_redirects=True,
) )
assert r.status_code == 200 assert r.status_code == 200
assert "test has been used before and cannot be reused" in r.data.decode() assert (
f"{directory_name} has been used before and cannot be reused" in r.data.decode()
)
def test_create_directory_out_of_quota(flask_client): def test_create_directory_out_of_quota(flask_client):