4.22.0
This commit is contained in:
@ -7,6 +7,7 @@ from app.dashboard.base import dashboard_bp
|
||||
from app.dashboard.views.enter_sudo import sudo_required
|
||||
from app.db import Session
|
||||
from app.models import ApiKey
|
||||
from app.utils import CSRFValidationForm
|
||||
|
||||
|
||||
class NewApiKeyForm(FlaskForm):
|
||||
@ -23,9 +24,13 @@ def api_key():
|
||||
.all()
|
||||
)
|
||||
|
||||
csrf_form = CSRFValidationForm()
|
||||
new_api_key_form = NewApiKeyForm()
|
||||
|
||||
if request.method == "POST":
|
||||
if not csrf_form.validate():
|
||||
flash("Invalid request", "warning")
|
||||
return redirect(request.url)
|
||||
if request.form.get("form-name") == "delete":
|
||||
api_key_id = request.form.get("api-key-id")
|
||||
|
||||
@ -62,5 +67,8 @@ def api_key():
|
||||
return redirect(url_for("dashboard.api_key"))
|
||||
|
||||
return render_template(
|
||||
"dashboard/api_key.html", api_keys=api_keys, new_api_key_form=new_api_key_form
|
||||
"dashboard/api_key.html",
|
||||
api_keys=api_keys,
|
||||
new_api_key_form=new_api_key_form,
|
||||
csrf_form=csrf_form,
|
||||
)
|
||||
|
@ -34,7 +34,7 @@ def batch_import_route():
|
||||
if request.method == "POST":
|
||||
if not csrf_form.validate():
|
||||
flash("Invalid request", "warning")
|
||||
redirect(request.url)
|
||||
return redirect(request.url)
|
||||
if len(batch_imports) > 10:
|
||||
flash(
|
||||
"You have too many imports already. Wait until some get cleaned up",
|
||||
|
@ -3,6 +3,7 @@ from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, validators
|
||||
|
||||
from app import parallel_limiter
|
||||
from app.config import EMAIL_SERVERS_WITH_PRIORITY
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.db import Session
|
||||
@ -19,6 +20,7 @@ class NewCustomDomainForm(FlaskForm):
|
||||
|
||||
@dashboard_bp.route("/custom_domain", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@parallel_limiter.lock(only_when=lambda: request.method == "POST")
|
||||
def custom_domain():
|
||||
custom_domains = CustomDomain.filter_by(
|
||||
user_id=current_user.id, is_sl_subdomain=False
|
||||
|
@ -9,6 +9,7 @@ from wtforms import (
|
||||
IntegerField,
|
||||
)
|
||||
|
||||
from app import parallel_limiter
|
||||
from app.config import (
|
||||
EMAIL_DOMAIN,
|
||||
ALIAS_DOMAINS,
|
||||
@ -45,6 +46,7 @@ class DeleteDirForm(FlaskForm):
|
||||
|
||||
@dashboard_bp.route("/directory", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@parallel_limiter.lock(only_when=lambda: request.method == "POST")
|
||||
def directory():
|
||||
dirs = (
|
||||
Directory.filter_by(user_id=current_user.id)
|
||||
|
@ -2,10 +2,11 @@ import arrow
|
||||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from itsdangerous import Signer
|
||||
from wtforms import validators
|
||||
from itsdangerous import TimestampSigner
|
||||
from wtforms import validators, IntegerField
|
||||
from wtforms.fields.html5 import EmailField
|
||||
|
||||
from app import parallel_limiter
|
||||
from app.config import MAILBOX_SECRET, URL, JOB_DELETE_MAILBOX
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.db import Session
|
||||
@ -27,8 +28,16 @@ class NewMailboxForm(FlaskForm):
|
||||
)
|
||||
|
||||
|
||||
class DeleteMailboxForm(FlaskForm):
|
||||
mailbox_id = IntegerField(
|
||||
validators=[validators.DataRequired()],
|
||||
)
|
||||
transfer_mailbox_id = IntegerField()
|
||||
|
||||
|
||||
@dashboard_bp.route("/mailbox", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@parallel_limiter.lock(only_when=lambda: request.method == "POST")
|
||||
def mailbox_route():
|
||||
mailboxes = (
|
||||
Mailbox.filter_by(user_id=current_user.id)
|
||||
@ -38,28 +47,53 @@ def mailbox_route():
|
||||
|
||||
new_mailbox_form = NewMailboxForm()
|
||||
csrf_form = CSRFValidationForm()
|
||||
delete_mailbox_form = DeleteMailboxForm()
|
||||
|
||||
if request.method == "POST":
|
||||
if not csrf_form.validate():
|
||||
flash("Invalid request", "warning")
|
||||
return redirect(request.url)
|
||||
if request.form.get("form-name") == "delete":
|
||||
mailbox_id = request.form.get("mailbox-id")
|
||||
mailbox = Mailbox.get(mailbox_id)
|
||||
if not delete_mailbox_form.validate():
|
||||
flash("Invalid request", "warning")
|
||||
return redirect(request.url)
|
||||
mailbox = Mailbox.get(delete_mailbox_form.mailbox_id.data)
|
||||
|
||||
if not mailbox or mailbox.user_id != current_user.id:
|
||||
flash("Unknown error. Refresh the page", "warning")
|
||||
flash("Invalid mailbox. Refresh the page", "warning")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
if mailbox.id == current_user.default_mailbox_id:
|
||||
flash("You cannot delete default mailbox", "error")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
transfer_mailbox_id = delete_mailbox_form.transfer_mailbox_id.data
|
||||
if transfer_mailbox_id and transfer_mailbox_id > 0:
|
||||
transfer_mailbox = Mailbox.get(transfer_mailbox_id)
|
||||
|
||||
if not transfer_mailbox or transfer_mailbox.user_id != current_user.id:
|
||||
flash("You must transfer the aliases to a mailbox you own.")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
if transfer_mailbox.id == mailbox.id:
|
||||
flash(
|
||||
"You can not transfer the aliases to the mailbox you want to delete."
|
||||
)
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
if not transfer_mailbox.verified:
|
||||
flash("Your new mailbox is not verified")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
# Schedule delete account job
|
||||
LOG.w("schedule delete mailbox job for %s", mailbox)
|
||||
LOG.w(
|
||||
f"schedule delete mailbox job for {mailbox.id} with transfer to mailbox {transfer_mailbox_id}"
|
||||
)
|
||||
Job.create(
|
||||
name=JOB_DELETE_MAILBOX,
|
||||
payload={"mailbox_id": mailbox.id},
|
||||
payload={
|
||||
"mailbox_id": mailbox.id,
|
||||
"transfer_mailbox_id": transfer_mailbox_id
|
||||
if transfer_mailbox_id > 0
|
||||
else None,
|
||||
},
|
||||
run_at=arrow.now(),
|
||||
commit=True,
|
||||
)
|
||||
@ -72,7 +106,10 @@ def mailbox_route():
|
||||
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
if request.form.get("form-name") == "set-default":
|
||||
mailbox_id = request.form.get("mailbox-id")
|
||||
if not csrf_form.validate():
|
||||
flash("Invalid request", "warning")
|
||||
return redirect(request.url)
|
||||
mailbox_id = request.form.get("mailbox_id")
|
||||
mailbox = Mailbox.get(mailbox_id)
|
||||
|
||||
if not mailbox or mailbox.user_id != current_user.id:
|
||||
@ -110,12 +147,12 @@ def mailbox_route():
|
||||
elif not email_can_be_used_as_mailbox(mailbox_email):
|
||||
flash(f"You cannot use {mailbox_email}.", "error")
|
||||
else:
|
||||
new_mailbox = Mailbox.create(
|
||||
transfer_mailbox = Mailbox.create(
|
||||
email=mailbox_email, user_id=current_user.id
|
||||
)
|
||||
Session.commit()
|
||||
|
||||
send_verification_email(current_user, new_mailbox)
|
||||
send_verification_email(current_user, transfer_mailbox)
|
||||
|
||||
flash(
|
||||
f"You are going to receive an email to confirm {mailbox_email}.",
|
||||
@ -124,7 +161,8 @@ def mailbox_route():
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"dashboard.mailbox_detail_route", mailbox_id=new_mailbox.id
|
||||
"dashboard.mailbox_detail_route",
|
||||
mailbox_id=transfer_mailbox.id,
|
||||
)
|
||||
)
|
||||
|
||||
@ -132,38 +170,13 @@ def mailbox_route():
|
||||
"dashboard/mailbox.html",
|
||||
mailboxes=mailboxes,
|
||||
new_mailbox_form=new_mailbox_form,
|
||||
delete_mailbox_form=delete_mailbox_form,
|
||||
csrf_form=csrf_form,
|
||||
)
|
||||
|
||||
|
||||
def delete_mailbox(mailbox_id: int):
|
||||
from server import create_light_app
|
||||
|
||||
with create_light_app().app_context():
|
||||
mailbox = Mailbox.get(mailbox_id)
|
||||
if not mailbox:
|
||||
return
|
||||
|
||||
mailbox_email = mailbox.email
|
||||
user = mailbox.user
|
||||
|
||||
Mailbox.delete(mailbox_id)
|
||||
Session.commit()
|
||||
LOG.d("Mailbox %s %s deleted", mailbox_id, mailbox_email)
|
||||
|
||||
send_email(
|
||||
user.email,
|
||||
f"Your mailbox {mailbox_email} has been deleted",
|
||||
f"""Mailbox {mailbox_email} along with its aliases are deleted successfully.
|
||||
|
||||
Regards,
|
||||
SimpleLogin team.
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def send_verification_email(user, mailbox):
|
||||
s = Signer(MAILBOX_SECRET)
|
||||
s = TimestampSigner(MAILBOX_SECRET)
|
||||
mailbox_id_signed = s.sign(str(mailbox.id)).decode()
|
||||
verification_url = (
|
||||
URL + "/dashboard/mailbox_verify" + f"?mailbox_id={mailbox_id_signed}"
|
||||
@ -188,11 +201,11 @@ def send_verification_email(user, mailbox):
|
||||
|
||||
@dashboard_bp.route("/mailbox_verify")
|
||||
def mailbox_verify():
|
||||
s = Signer(MAILBOX_SECRET)
|
||||
s = TimestampSigner(MAILBOX_SECRET)
|
||||
mailbox_id = request.args.get("mailbox_id")
|
||||
|
||||
try:
|
||||
r_id = int(s.unsign(mailbox_id))
|
||||
r_id = int(s.unsign(mailbox_id, max_age=900))
|
||||
except Exception:
|
||||
flash("Invalid link. Please delete and re-add your mailbox", "error")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
@ -4,7 +4,7 @@ from email_validator import validate_email, EmailNotValidError
|
||||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from itsdangerous import Signer
|
||||
from itsdangerous import TimestampSigner
|
||||
from wtforms import validators
|
||||
from wtforms.fields.html5 import EmailField
|
||||
|
||||
@ -210,7 +210,7 @@ def mailbox_detail_route(mailbox_id):
|
||||
|
||||
|
||||
def verify_mailbox_change(user, mailbox, new_email):
|
||||
s = Signer(MAILBOX_SECRET)
|
||||
s = TimestampSigner(MAILBOX_SECRET)
|
||||
mailbox_id_signed = s.sign(str(mailbox.id)).decode()
|
||||
verification_url = (
|
||||
f"{URL}/dashboard/mailbox/confirm_change?mailbox_id={mailbox_id_signed}"
|
||||
@ -262,11 +262,11 @@ def cancel_mailbox_change_route(mailbox_id):
|
||||
|
||||
@dashboard_bp.route("/mailbox/confirm_change")
|
||||
def mailbox_confirm_change_route():
|
||||
s = Signer(MAILBOX_SECRET)
|
||||
s = TimestampSigner(MAILBOX_SECRET)
|
||||
signed_mailbox_id = request.args.get("mailbox_id")
|
||||
|
||||
try:
|
||||
mailbox_id = int(s.unsign(signed_mailbox_id))
|
||||
mailbox_id = int(s.unsign(signed_mailbox_id, max_age=900))
|
||||
except Exception:
|
||||
flash("Invalid link", "error")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
@ -5,6 +5,7 @@ from app.dashboard.base import dashboard_bp
|
||||
from app.dashboard.views.enter_sudo import sudo_required
|
||||
from app.db import Session
|
||||
from app.models import RecoveryCode
|
||||
from app.utils import CSRFValidationForm
|
||||
|
||||
|
||||
@dashboard_bp.route("/mfa_cancel", methods=["GET", "POST"])
|
||||
@ -15,8 +16,13 @@ def mfa_cancel():
|
||||
flash("you don't have MFA enabled", "warning")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
csrf_form = CSRFValidationForm()
|
||||
|
||||
# user cancels TOTP
|
||||
if request.method == "POST":
|
||||
if not csrf_form.validate():
|
||||
flash("Invalid request", "warning")
|
||||
return redirect(request.url)
|
||||
current_user.enable_otp = False
|
||||
current_user.otp_secret = None
|
||||
Session.commit()
|
||||
@ -28,4 +34,4 @@ def mfa_cancel():
|
||||
flash("TOTP is now disabled", "warning")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
return render_template("dashboard/mfa_cancel.html")
|
||||
return render_template("dashboard/mfa_cancel.html", csrf_form=csrf_form)
|
||||
|
@ -2,7 +2,10 @@ import re
|
||||
|
||||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, validators
|
||||
|
||||
from app import parallel_limiter
|
||||
from app.config import MAX_NB_SUBDOMAIN
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.errors import SubdomainInTrashError
|
||||
@ -13,8 +16,18 @@ from app.models import CustomDomain, Mailbox, SLDomain
|
||||
_SUBDOMAIN_PATTERN = r"[0-9a-z-]{1,}"
|
||||
|
||||
|
||||
class NewSubdomainForm(FlaskForm):
|
||||
domain = StringField(
|
||||
"domain", validators=[validators.DataRequired(), validators.Length(max=64)]
|
||||
)
|
||||
subdomain = StringField(
|
||||
"subdomain", validators=[validators.DataRequired(), validators.Length(max=64)]
|
||||
)
|
||||
|
||||
|
||||
@dashboard_bp.route("/subdomain", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@parallel_limiter.lock(only_when=lambda: request.method == "POST")
|
||||
def subdomain_route():
|
||||
if not current_user.subdomain_is_available():
|
||||
flash("Unknown error, redirect to the home page", "error")
|
||||
@ -26,9 +39,13 @@ def subdomain_route():
|
||||
).all()
|
||||
|
||||
errors = {}
|
||||
new_subdomain_form = NewSubdomainForm()
|
||||
|
||||
if request.method == "POST":
|
||||
if request.form.get("form-name") == "create":
|
||||
if not new_subdomain_form.validate():
|
||||
flash("Invalid new subdomain", "warning")
|
||||
return redirect(url_for("dashboard.subdomain_route"))
|
||||
if not current_user.is_premium():
|
||||
flash("Only premium plan can add subdomain", "warning")
|
||||
return redirect(request.url)
|
||||
@ -39,8 +56,8 @@ def subdomain_route():
|
||||
)
|
||||
return redirect(request.url)
|
||||
|
||||
subdomain = request.form.get("subdomain").lower().strip()
|
||||
domain = request.form.get("domain").lower().strip()
|
||||
subdomain = new_subdomain_form.subdomain.data.lower().strip()
|
||||
domain = new_subdomain_form.domain.data.lower().strip()
|
||||
|
||||
if len(subdomain) < 3:
|
||||
flash("Subdomain must have at least 3 characters", "error")
|
||||
@ -108,4 +125,5 @@ def subdomain_route():
|
||||
sl_domains=sl_domains,
|
||||
errors=errors,
|
||||
subdomains=subdomains,
|
||||
new_subdomain_form=new_subdomain_form,
|
||||
)
|
||||
|
Reference in New Issue
Block a user