2020-11-12 17:41:47 +01:00
|
|
|
from threading import Thread
|
|
|
|
|
2019-12-27 23:50:09 +01:00
|
|
|
from flask import render_template, request, redirect, url_for, flash
|
|
|
|
from flask_login import login_required, current_user
|
|
|
|
|
2020-05-03 12:01:31 +02:00
|
|
|
from app.config import EMAIL_SERVERS_WITH_PRIORITY, EMAIL_DOMAIN
|
2019-12-27 23:50:09 +01:00
|
|
|
from app.dashboard.base import dashboard_bp
|
|
|
|
from app.dns_utils import (
|
|
|
|
get_mx_domains,
|
|
|
|
get_spf_domain,
|
|
|
|
get_txt_record,
|
2020-05-03 11:19:14 +02:00
|
|
|
get_cname_record,
|
2019-12-27 23:50:09 +01:00
|
|
|
)
|
2020-11-12 17:41:47 +01:00
|
|
|
from app.email_utils import send_email
|
2019-12-27 23:50:09 +01:00
|
|
|
from app.extensions import db
|
2020-11-12 17:41:47 +01:00
|
|
|
from app.log import LOG
|
2020-05-23 11:48:43 +02:00
|
|
|
from app.models import CustomDomain, Alias, DomainDeletedAlias
|
2019-12-27 23:50:09 +01:00
|
|
|
|
|
|
|
|
2019-12-30 17:52:08 +01:00
|
|
|
@dashboard_bp.route("/domains/<int:custom_domain_id>/dns", methods=["GET", "POST"])
|
2019-12-27 23:50:09 +01:00
|
|
|
@login_required
|
2019-12-30 17:52:08 +01:00
|
|
|
def domain_detail_dns(custom_domain_id):
|
2019-12-27 23:50:09 +01:00
|
|
|
custom_domain = CustomDomain.get(custom_domain_id)
|
|
|
|
if not custom_domain or custom_domain.user_id != current_user.id:
|
|
|
|
flash("You cannot see this page", "warning")
|
|
|
|
return redirect(url_for("dashboard.index"))
|
|
|
|
|
2020-05-03 11:19:14 +02:00
|
|
|
spf_record = f"v=spf1 include:{EMAIL_DOMAIN} -all"
|
|
|
|
|
|
|
|
# hardcode the DKIM selector here
|
|
|
|
dkim_cname = f"dkim._domainkey.{EMAIL_DOMAIN}"
|
|
|
|
|
2020-05-03 12:01:31 +02:00
|
|
|
dmarc_record = "v=DMARC1; p=quarantine; pct=100; adkim=s; aspf=s"
|
|
|
|
|
|
|
|
mx_ok = spf_ok = dkim_ok = dmarc_ok = True
|
|
|
|
mx_errors = spf_errors = dkim_errors = dmarc_errors = []
|
2019-12-27 23:50:09 +01:00
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
if request.form.get("form-name") == "check-mx":
|
|
|
|
mx_domains = get_mx_domains(custom_domain.domain)
|
|
|
|
|
2019-12-30 19:34:38 +01:00
|
|
|
if sorted(mx_domains) != sorted(EMAIL_SERVERS_WITH_PRIORITY):
|
2020-01-02 22:22:09 +01:00
|
|
|
flash("The MX record is not correctly set", "warning")
|
2020-08-31 17:11:18 +02:00
|
|
|
|
2019-12-27 23:50:09 +01:00
|
|
|
mx_ok = False
|
2019-12-30 19:34:38 +01:00
|
|
|
# build mx_errors to show to user
|
|
|
|
mx_errors = [
|
|
|
|
f"{priority} {domain}" for (priority, domain) in mx_domains
|
|
|
|
]
|
2019-12-27 23:50:09 +01:00
|
|
|
else:
|
|
|
|
flash(
|
2020-05-03 12:01:31 +02:00
|
|
|
"Your domain can start receiving emails. You can now use it to create alias",
|
2019-12-27 23:50:09 +01:00
|
|
|
"success",
|
|
|
|
)
|
|
|
|
custom_domain.verified = True
|
|
|
|
db.session.commit()
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
2019-12-30 21:11:31 +01:00
|
|
|
"dashboard.domain_detail_dns", custom_domain_id=custom_domain.id
|
2019-12-27 23:50:09 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
elif request.form.get("form-name") == "check-spf":
|
|
|
|
spf_domains = get_spf_domain(custom_domain.domain)
|
2019-12-30 19:34:38 +01:00
|
|
|
if EMAIL_DOMAIN in spf_domains:
|
2019-12-27 23:50:09 +01:00
|
|
|
custom_domain.spf_verified = True
|
|
|
|
db.session.commit()
|
2020-05-03 12:01:31 +02:00
|
|
|
flash("SPF is setup correctly", "success")
|
2019-12-27 23:50:09 +01:00
|
|
|
return redirect(
|
|
|
|
url_for(
|
2019-12-30 21:11:31 +01:00
|
|
|
"dashboard.domain_detail_dns", custom_domain_id=custom_domain.id
|
2019-12-27 23:50:09 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
else:
|
2020-05-14 15:05:04 +02:00
|
|
|
custom_domain.spf_verified = False
|
|
|
|
db.session.commit()
|
2020-01-02 22:22:09 +01:00
|
|
|
flash(
|
|
|
|
f"SPF: {EMAIL_DOMAIN} is not included in your SPF record.",
|
|
|
|
"warning",
|
|
|
|
)
|
2019-12-30 19:34:38 +01:00
|
|
|
spf_ok = False
|
2019-12-27 23:50:09 +01:00
|
|
|
spf_errors = get_txt_record(custom_domain.domain)
|
|
|
|
|
|
|
|
elif request.form.get("form-name") == "check-dkim":
|
2020-05-03 12:48:42 +02:00
|
|
|
dkim_record = get_cname_record("dkim._domainkey." + custom_domain.domain)
|
2020-05-03 11:19:14 +02:00
|
|
|
if dkim_record == dkim_cname:
|
2020-05-03 12:01:31 +02:00
|
|
|
flash("DKIM is setup correctly.", "success")
|
2019-12-27 23:50:09 +01:00
|
|
|
custom_domain.dkim_verified = True
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
2019-12-30 21:11:31 +01:00
|
|
|
"dashboard.domain_detail_dns", custom_domain_id=custom_domain.id
|
2019-12-27 23:50:09 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
else:
|
2020-05-14 15:05:04 +02:00
|
|
|
custom_domain.dkim_verified = False
|
|
|
|
db.session.commit()
|
2020-05-03 11:19:14 +02:00
|
|
|
flash("DKIM: the CNAME record is not correctly set", "warning")
|
2019-12-27 23:50:09 +01:00
|
|
|
dkim_ok = False
|
2020-05-03 11:19:14 +02:00
|
|
|
dkim_errors = [dkim_record or "[Empty]"]
|
2019-12-27 23:50:09 +01:00
|
|
|
|
2020-05-03 12:01:31 +02:00
|
|
|
elif request.form.get("form-name") == "check-dmarc":
|
|
|
|
txt_records = get_txt_record("_dmarc." + custom_domain.domain)
|
|
|
|
if dmarc_record in txt_records:
|
|
|
|
custom_domain.dmarc_verified = True
|
|
|
|
db.session.commit()
|
|
|
|
flash("DMARC is setup correctly", "success")
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"dashboard.domain_detail_dns", custom_domain_id=custom_domain.id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
else:
|
2020-05-14 15:05:04 +02:00
|
|
|
custom_domain.dmarc_verified = False
|
|
|
|
db.session.commit()
|
2020-05-03 12:01:31 +02:00
|
|
|
flash(
|
2020-12-06 22:01:38 +01:00
|
|
|
"DMARC: The TXT record is not correctly set",
|
2020-08-27 10:20:48 +02:00
|
|
|
"warning",
|
2020-05-03 12:01:31 +02:00
|
|
|
)
|
|
|
|
dmarc_ok = False
|
|
|
|
dmarc_errors = txt_records
|
|
|
|
|
2019-12-30 18:58:44 +01:00
|
|
|
return render_template(
|
|
|
|
"dashboard/domain_detail/dns.html",
|
|
|
|
EMAIL_SERVERS_WITH_PRIORITY=EMAIL_SERVERS_WITH_PRIORITY,
|
|
|
|
**locals(),
|
|
|
|
)
|
2019-12-30 17:52:08 +01:00
|
|
|
|
|
|
|
|
|
|
|
@dashboard_bp.route("/domains/<int:custom_domain_id>/info", methods=["GET", "POST"])
|
|
|
|
@login_required
|
|
|
|
def domain_detail(custom_domain_id):
|
|
|
|
custom_domain = CustomDomain.get(custom_domain_id)
|
|
|
|
if not custom_domain or custom_domain.user_id != current_user.id:
|
|
|
|
flash("You cannot see this page", "warning")
|
|
|
|
return redirect(url_for("dashboard.index"))
|
|
|
|
|
2019-12-30 18:20:49 +01:00
|
|
|
if request.method == "POST":
|
|
|
|
if request.form.get("form-name") == "switch-catch-all":
|
|
|
|
custom_domain.catch_all = not custom_domain.catch_all
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
if custom_domain.catch_all:
|
|
|
|
flash(
|
|
|
|
f"The catch-all has been enabled for {custom_domain.domain}",
|
|
|
|
"success",
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
flash(
|
|
|
|
f"The catch-all has been disabled for {custom_domain.domain}",
|
|
|
|
"warning",
|
|
|
|
)
|
|
|
|
return redirect(
|
|
|
|
url_for("dashboard.domain_detail", custom_domain_id=custom_domain.id)
|
|
|
|
)
|
2020-05-03 16:05:34 +02:00
|
|
|
elif request.form.get("form-name") == "set-name":
|
2020-11-24 12:02:47 +01:00
|
|
|
if request.form.get("action") == "save":
|
|
|
|
custom_domain.name = request.form.get("alias-name").replace("\n", "")
|
|
|
|
db.session.commit()
|
|
|
|
flash(
|
|
|
|
f"Default alias name for Domain {custom_domain.domain} has been set",
|
|
|
|
"success",
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
custom_domain.name = None
|
|
|
|
db.session.commit()
|
|
|
|
flash(
|
|
|
|
f"Default alias name for Domain {custom_domain.domain} has been removed",
|
|
|
|
"info",
|
|
|
|
)
|
2020-05-03 16:05:34 +02:00
|
|
|
|
|
|
|
return redirect(
|
|
|
|
url_for("dashboard.domain_detail", custom_domain_id=custom_domain.id)
|
|
|
|
)
|
2020-10-09 22:54:13 +02:00
|
|
|
elif request.form.get("form-name") == "switch-random-prefix-generation":
|
2020-10-09 23:00:10 +02:00
|
|
|
custom_domain.random_prefix_generation = (
|
|
|
|
not custom_domain.random_prefix_generation
|
|
|
|
)
|
2020-10-09 22:54:13 +02:00
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
if custom_domain.random_prefix_generation:
|
|
|
|
flash(
|
|
|
|
f"Random prefix generation has been enabled for {custom_domain.domain}",
|
|
|
|
"success",
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
flash(
|
|
|
|
f"Random prefix generation has been disabled for {custom_domain.domain}",
|
|
|
|
"warning",
|
|
|
|
)
|
|
|
|
return redirect(
|
|
|
|
url_for("dashboard.domain_detail", custom_domain_id=custom_domain.id)
|
|
|
|
)
|
2019-12-30 18:51:55 +01:00
|
|
|
elif request.form.get("form-name") == "delete":
|
|
|
|
name = custom_domain.domain
|
2020-11-12 17:41:47 +01:00
|
|
|
LOG.d("Schedule deleting %s", custom_domain)
|
|
|
|
Thread(target=delete_domain, args=(custom_domain_id,)).start()
|
|
|
|
flash(
|
|
|
|
f"{name} scheduled for deletion."
|
|
|
|
f"You will receive a confirmation email when the deletion is finished",
|
|
|
|
"success",
|
|
|
|
)
|
2019-12-30 18:51:55 +01:00
|
|
|
|
|
|
|
return redirect(url_for("dashboard.custom_domain"))
|
2019-12-30 18:20:49 +01:00
|
|
|
|
2020-03-17 11:51:40 +01:00
|
|
|
nb_alias = Alias.filter_by(custom_domain_id=custom_domain.id).count()
|
2019-12-30 17:52:08 +01:00
|
|
|
|
|
|
|
return render_template("dashboard/domain_detail/info.html", **locals())
|
2020-05-23 11:48:43 +02:00
|
|
|
|
|
|
|
|
2021-08-15 17:50:47 +02:00
|
|
|
def delete_domain(custom_domain_id: int):
|
2020-11-12 17:56:03 +01:00
|
|
|
from server import create_light_app
|
|
|
|
|
2020-11-12 17:41:47 +01:00
|
|
|
with create_light_app().app_context():
|
|
|
|
custom_domain = CustomDomain.get(custom_domain_id)
|
|
|
|
if not custom_domain:
|
|
|
|
return
|
|
|
|
|
|
|
|
domain_name = custom_domain.domain
|
|
|
|
user = custom_domain.user
|
|
|
|
|
|
|
|
CustomDomain.delete(custom_domain.id)
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
LOG.d("Domain %s deleted", domain_name)
|
|
|
|
|
|
|
|
send_email(
|
|
|
|
user.email,
|
|
|
|
f"Your domain {domain_name} has been deleted",
|
|
|
|
f"""Domain {domain_name} along with its aliases are deleted successfully.
|
2020-12-06 22:01:38 +01:00
|
|
|
|
2020-11-12 17:41:47 +01:00
|
|
|
Regards,
|
|
|
|
SimpleLogin team.
|
|
|
|
""",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-05-23 11:48:43 +02:00
|
|
|
@dashboard_bp.route("/domains/<int:custom_domain_id>/trash", methods=["GET", "POST"])
|
|
|
|
@login_required
|
|
|
|
def domain_detail_trash(custom_domain_id):
|
|
|
|
custom_domain = CustomDomain.get(custom_domain_id)
|
|
|
|
if not custom_domain or custom_domain.user_id != current_user.id:
|
|
|
|
flash("You cannot see this page", "warning")
|
|
|
|
return redirect(url_for("dashboard.index"))
|
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
if request.form.get("form-name") == "empty-all":
|
|
|
|
DomainDeletedAlias.filter_by(domain_id=custom_domain.id).delete()
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
flash("All deleted aliases can now be re-created", "success")
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"dashboard.domain_detail_trash", custom_domain_id=custom_domain.id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
elif request.form.get("form-name") == "remove-single":
|
|
|
|
deleted_alias_id = request.form.get("deleted-alias-id")
|
|
|
|
deleted_alias = DomainDeletedAlias.get(deleted_alias_id)
|
|
|
|
if not deleted_alias or deleted_alias.domain_id != custom_domain.id:
|
|
|
|
flash("Unknown error, refresh the page", "warning")
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"dashboard.domain_detail_trash",
|
|
|
|
custom_domain_id=custom_domain.id,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
DomainDeletedAlias.delete(deleted_alias.id)
|
|
|
|
db.session.commit()
|
|
|
|
flash(
|
2020-08-27 10:20:48 +02:00
|
|
|
f"{deleted_alias.email} can now be re-created",
|
|
|
|
"success",
|
2020-05-23 11:48:43 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"dashboard.domain_detail_trash", custom_domain_id=custom_domain.id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
domain_deleted_aliases = DomainDeletedAlias.filter_by(
|
|
|
|
domain_id=custom_domain.id
|
|
|
|
).all()
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
"dashboard/domain_detail/trash.html",
|
|
|
|
domain_deleted_aliases=domain_deleted_aliases,
|
|
|
|
custom_domain=custom_domain,
|
|
|
|
)
|