2022-01-26 14:53:27 +01:00
|
|
|
import re
|
|
|
|
|
2021-11-05 11:44:39 +01:00
|
|
|
from flask import render_template, request, redirect, url_for, flash
|
|
|
|
from flask_login import login_required, current_user
|
2023-01-12 12:34:47 +01:00
|
|
|
from flask_wtf import FlaskForm
|
|
|
|
from wtforms import StringField, validators
|
2021-11-05 11:44:39 +01:00
|
|
|
|
2023-01-11 22:08:52 +01:00
|
|
|
from app import parallel_limiter
|
2021-11-09 10:17:47 +01:00
|
|
|
from app.config import MAX_NB_SUBDOMAIN
|
2021-11-05 11:44:39 +01:00
|
|
|
from app.dashboard.base import dashboard_bp
|
2021-11-17 17:21:13 +01:00
|
|
|
from app.errors import SubdomainInTrashError
|
2021-11-05 11:44:39 +01:00
|
|
|
from app.log import LOG
|
|
|
|
from app.models import CustomDomain, Mailbox, SLDomain
|
|
|
|
|
2022-01-26 14:53:27 +01:00
|
|
|
# Only lowercase letters, numbers, dashes (-) are currently supported
|
|
|
|
_SUBDOMAIN_PATTERN = r"[0-9a-z-]{1,}"
|
|
|
|
|
2021-11-05 11:44:39 +01:00
|
|
|
|
2023-01-12 12:34:47 +01:00
|
|
|
class NewSubdomainForm(FlaskForm):
|
|
|
|
domain = StringField(
|
|
|
|
"domain", validators=[validators.DataRequired(), validators.Length(max=64)]
|
|
|
|
)
|
|
|
|
subdomain = StringField(
|
|
|
|
"subdomain", validators=[validators.DataRequired(), validators.Length(max=64)]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-11-05 11:44:39 +01:00
|
|
|
@dashboard_bp.route("/subdomain", methods=["GET", "POST"])
|
|
|
|
@login_required
|
2023-01-11 22:08:52 +01:00
|
|
|
@parallel_limiter.lock(only_when=lambda: request.method == "POST")
|
2021-11-05 11:44:39 +01:00
|
|
|
def subdomain_route():
|
2021-11-15 11:16:03 +01:00
|
|
|
if not current_user.subdomain_is_available():
|
2021-11-05 11:44:39 +01:00
|
|
|
flash("Unknown error, redirect to the home page", "error")
|
|
|
|
return redirect(url_for("dashboard.index"))
|
|
|
|
|
|
|
|
sl_domains = SLDomain.filter_by(can_use_subdomain=True).all()
|
|
|
|
subdomains = CustomDomain.filter_by(
|
|
|
|
user_id=current_user.id, is_sl_subdomain=True
|
|
|
|
).all()
|
|
|
|
|
|
|
|
errors = {}
|
2023-01-12 12:34:47 +01:00
|
|
|
new_subdomain_form = NewSubdomainForm()
|
2021-11-05 11:44:39 +01:00
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
if request.form.get("form-name") == "create":
|
2023-01-12 12:34:47 +01:00
|
|
|
if not new_subdomain_form.validate():
|
|
|
|
flash("Invalid new subdomain", "warning")
|
|
|
|
return redirect(url_for("dashboard.subdomain_route"))
|
2021-11-05 11:44:39 +01:00
|
|
|
if not current_user.is_premium():
|
|
|
|
flash("Only premium plan can add subdomain", "warning")
|
|
|
|
return redirect(request.url)
|
|
|
|
|
2021-11-18 10:33:15 +01:00
|
|
|
if current_user.subdomain_quota <= 0:
|
2021-11-09 10:17:47 +01:00
|
|
|
flash(
|
|
|
|
f"You can't create more than {MAX_NB_SUBDOMAIN} subdomains", "error"
|
|
|
|
)
|
|
|
|
return redirect(request.url)
|
|
|
|
|
2023-01-12 12:34:47 +01:00
|
|
|
subdomain = new_subdomain_form.subdomain.data.lower().strip()
|
|
|
|
domain = new_subdomain_form.domain.data.lower().strip()
|
2021-11-05 11:44:39 +01:00
|
|
|
|
2022-01-26 14:53:27 +01:00
|
|
|
if len(subdomain) < 3:
|
|
|
|
flash("Subdomain must have at least 3 characters", "error")
|
|
|
|
return redirect(request.url)
|
|
|
|
|
|
|
|
if re.fullmatch(_SUBDOMAIN_PATTERN, subdomain) is None:
|
|
|
|
flash(
|
|
|
|
"Subdomain can only contain lowercase letters, numbers and dashes (-)",
|
|
|
|
"error",
|
|
|
|
)
|
|
|
|
return redirect(request.url)
|
|
|
|
|
|
|
|
if subdomain.endswith("-"):
|
|
|
|
flash("Subdomain can't end with dash (-)", "error")
|
|
|
|
return redirect(request.url)
|
|
|
|
|
2021-11-05 11:44:39 +01:00
|
|
|
if domain not in [sl_domain.domain for sl_domain in sl_domains]:
|
|
|
|
LOG.e("Domain %s is tampered by %s", domain, current_user)
|
|
|
|
flash("Unknown error, refresh the page", "error")
|
|
|
|
return redirect(request.url)
|
|
|
|
|
|
|
|
full_domain = f"{subdomain}.{domain}"
|
|
|
|
|
|
|
|
if CustomDomain.get_by(domain=full_domain):
|
|
|
|
flash(f"{full_domain} already used", "error")
|
|
|
|
elif Mailbox.filter(
|
|
|
|
Mailbox.verified.is_(True),
|
|
|
|
Mailbox.email.endswith(f"@{full_domain}"),
|
|
|
|
).first():
|
|
|
|
flash(f"{full_domain} already used in a SimpleLogin mailbox", "error")
|
|
|
|
else:
|
2021-11-17 17:21:13 +01:00
|
|
|
try:
|
|
|
|
new_custom_domain = CustomDomain.create(
|
|
|
|
is_sl_subdomain=True,
|
|
|
|
catch_all=True, # by default catch-all is enabled
|
|
|
|
domain=full_domain,
|
|
|
|
user_id=current_user.id,
|
|
|
|
verified=True,
|
|
|
|
dkim_verified=False, # wildcard DNS does not work for DKIM
|
|
|
|
spf_verified=True,
|
|
|
|
dmarc_verified=False, # wildcard DNS does not work for DMARC
|
|
|
|
ownership_verified=True,
|
|
|
|
commit=True,
|
|
|
|
)
|
|
|
|
except SubdomainInTrashError:
|
|
|
|
flash(
|
|
|
|
f"{full_domain} has been used before and cannot be reused",
|
|
|
|
"error",
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
flash(
|
|
|
|
f"New subdomain {new_custom_domain.domain} is created",
|
|
|
|
"success",
|
|
|
|
)
|
2021-11-05 11:44:39 +01:00
|
|
|
|
2021-11-17 17:21:13 +01:00
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"dashboard.domain_detail",
|
|
|
|
custom_domain_id=new_custom_domain.id,
|
|
|
|
)
|
2021-11-05 11:44:39 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
"dashboard/subdomain.html",
|
|
|
|
sl_domains=sl_domains,
|
|
|
|
errors=errors,
|
|
|
|
subdomains=subdomains,
|
2023-01-12 12:34:47 +01:00
|
|
|
new_subdomain_form=new_subdomain_form,
|
2021-11-05 11:44:39 +01:00
|
|
|
)
|