add custom_domain view
This commit is contained in:
parent
5d9420a763
commit
a827b27215
|
@ -7,4 +7,5 @@ from .views import (
|
||||||
alias_log,
|
alias_log,
|
||||||
unsubscribe,
|
unsubscribe,
|
||||||
api_key,
|
api_key,
|
||||||
|
custom_domain,
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
{% extends 'default.html' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Custom Domains
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block default_content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 offset-md-2">
|
||||||
|
<h1 class="h3"> Custom Domains </h1>
|
||||||
|
|
||||||
|
{% for custom_domain in custom_domains %}
|
||||||
|
<div class="card" style="max-width: 50rem">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
{{ custom_domain.domain }}
|
||||||
|
{% if custom_domain.verified %}
|
||||||
|
<i class="fe fe-check" style="color: green"></i>
|
||||||
|
{% endif %}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
{% if not custom_domain.verified %}
|
||||||
|
<hr>
|
||||||
|
Please follow the following steps to set up your domain: <br>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="badge badge-primary badge-pill mr-2">1</span>
|
||||||
|
Add the following MX DNS record to your domain
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-6 mt-3">
|
||||||
|
{% for priority, email_server in EMAIL_SERVERS_WITH_PRIORITY %}
|
||||||
|
<div class="ml-3 text-info">
|
||||||
|
Domain: <em>{{ custom_domain.domain }}</em> <br>
|
||||||
|
Priority: 10 <br>
|
||||||
|
Target: <em>{{ email_server }}</em> <br>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
Or if you edit your DNS record in text format, use the following code: <br>
|
||||||
|
|
||||||
|
<pre class="ml-3">
|
||||||
|
{% for priority, email_server in EMAIL_SERVERS_WITH_PRIORITY %}
|
||||||
|
{{ custom_domain.domain }} IN MX {{ priority }} {{ email_server }}
|
||||||
|
{% endfor %}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="badge badge-primary badge-pill mr-2">2</span>
|
||||||
|
Verify 👇🏽
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-6 mt-3">
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="form-name" value="check-domain">
|
||||||
|
<input type="hidden" name="custom-domain-id" value="{{ custom_domain.id }}">
|
||||||
|
<button type="submit" class="btn btn-primary">Verify</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if custom_domain.id in errors %}
|
||||||
|
<div class="text-danger">
|
||||||
|
{{ errors.get(custom_domain.id) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
As the change could take up to 24 hours, do not hesitate to come back to this page and verify again.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="form-name" value="delete">
|
||||||
|
<input type="hidden" name="custom-domain-id" value="{{ custom_domain.id }}">
|
||||||
|
<span class="card-link btn btn-link float-right delete-custom-domain">
|
||||||
|
Delete
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{{ new_custom_domain_form.csrf_token }}
|
||||||
|
<input type="hidden" name="form-name" value="create">
|
||||||
|
|
||||||
|
<label class="form-label">Domain</label>
|
||||||
|
<small>Please use full path domain, for ex <em>my-subdomain.my-domain.com</em></small>
|
||||||
|
|
||||||
|
{{ new_custom_domain_form.domain(class="form-control", placeholder="my-domain.com") }}
|
||||||
|
{{ render_field_errors(new_custom_domain_form.domain) }}
|
||||||
|
<button class="btn btn-lg btn-success mt-2">Create</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
$(".delete-custom-domain").on("click", function (e) {
|
||||||
|
notie.confirm({
|
||||||
|
text: "All aliases associated with this domain will be also deleted, " +
|
||||||
|
" please confirm",
|
||||||
|
cancelCallback: () => {
|
||||||
|
// nothing to do
|
||||||
|
},
|
||||||
|
submitCallback: () => {
|
||||||
|
$(this).closest("form").submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,88 @@
|
||||||
|
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.config import EMAIL_SERVERS_WITH_PRIORITY, EMAIL_SERVERS
|
||||||
|
from app.dashboard.base import dashboard_bp
|
||||||
|
from app.dns_utils import get_mx_domains
|
||||||
|
from app.extensions import db
|
||||||
|
from app.models import CustomDomain
|
||||||
|
|
||||||
|
|
||||||
|
# todo: add more validation
|
||||||
|
class NewCustomDomainForm(FlaskForm):
|
||||||
|
domain = StringField("domain", validators=[validators.DataRequired()])
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_bp.route("/custom_domain", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
|
def custom_domain():
|
||||||
|
custom_domains = CustomDomain.query.filter_by(user_id=current_user.id).all()
|
||||||
|
|
||||||
|
new_custom_domain_form = NewCustomDomainForm()
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
if request.form.get("form-name") == "delete":
|
||||||
|
custom_domain_id = request.form.get("custom-domain-id")
|
||||||
|
custom_domain = CustomDomain.get(custom_domain_id)
|
||||||
|
|
||||||
|
if not custom_domain:
|
||||||
|
flash("Unknown error. Refresh the page", "warning")
|
||||||
|
return redirect(url_for("dashboard.custom_domain"))
|
||||||
|
elif custom_domain.user_id != current_user.id:
|
||||||
|
flash("You cannot delete this domain", "warning")
|
||||||
|
return redirect(url_for("dashboard.custom_domain"))
|
||||||
|
|
||||||
|
name = custom_domain.domain
|
||||||
|
CustomDomain.delete(custom_domain_id)
|
||||||
|
db.session.commit()
|
||||||
|
flash(f"Domain {name} has been deleted successfully", "success")
|
||||||
|
|
||||||
|
return redirect(url_for("dashboard.custom_domain"))
|
||||||
|
|
||||||
|
elif request.form.get("form-name") == "create":
|
||||||
|
if new_custom_domain_form.validate():
|
||||||
|
new_custom_domain = CustomDomain.create(
|
||||||
|
domain=new_custom_domain_form.domain.data, user_id=current_user.id
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
flash(
|
||||||
|
f"New domain {new_custom_domain.domain} has been created successfully",
|
||||||
|
"success",
|
||||||
|
)
|
||||||
|
return redirect(url_for("dashboard.custom_domain"))
|
||||||
|
elif request.form.get("form-name") == "check-domain":
|
||||||
|
custom_domain_id = request.form.get("custom-domain-id")
|
||||||
|
custom_domain = CustomDomain.get(custom_domain_id)
|
||||||
|
|
||||||
|
if not custom_domain:
|
||||||
|
flash("Unknown error. Refresh the page", "warning")
|
||||||
|
return redirect(url_for("dashboard.custom_domain"))
|
||||||
|
elif custom_domain.user_id != current_user.id:
|
||||||
|
flash("You cannot delete this domain", "warning")
|
||||||
|
return redirect(url_for("dashboard.custom_domain"))
|
||||||
|
else:
|
||||||
|
mx_domains = get_mx_domains(custom_domain.domain)
|
||||||
|
if mx_domains != EMAIL_SERVERS:
|
||||||
|
errors[
|
||||||
|
custom_domain.id
|
||||||
|
] = f"Your DNS is not correctly set. The MX record we obtain is {mx_domains}"
|
||||||
|
else:
|
||||||
|
flash(
|
||||||
|
"Your domain is verified. Now it can be used to create custom alias",
|
||||||
|
"success",
|
||||||
|
)
|
||||||
|
custom_domain.verified = True
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"dashboard/custom_domain.html",
|
||||||
|
custom_domains=custom_domains,
|
||||||
|
new_custom_domain_form=new_custom_domain_form,
|
||||||
|
EMAIL_SERVERS_WITH_PRIORITY=EMAIL_SERVERS_WITH_PRIORITY,
|
||||||
|
errors=errors,
|
||||||
|
)
|
|
@ -40,7 +40,7 @@
|
||||||
<hr>
|
<hr>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="form-label col">Authorized URIs</label>
|
<label class="form-label col">Authorized Redirect URIs</label>
|
||||||
<p class="col text-right">
|
<p class="col text-right">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<em>redirect_uri</em> must be <b>HTTPS</b> for security reason.
|
<em>redirect_uri</em> must be <b>HTTPS</b> for security reason.
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import dns.resolver
|
||||||
|
|
||||||
|
|
||||||
|
def get_mx_domains(hostname) -> [str]:
|
||||||
|
answers = dns.resolver.query(hostname, "MX")
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
for a in answers:
|
||||||
|
record = a.to_text() # for ex '20 alt2.aspmx.l.google.com.'
|
||||||
|
r = record.split(" ")[1] # alt2.aspmx.l.google.com.
|
||||||
|
ret.append(r)
|
||||||
|
|
||||||
|
return ret
|
|
@ -209,6 +209,9 @@ class User(db.Model, ModelMixin, UserMixin):
|
||||||
sub = Subscription.get_by(user_id=self.id)
|
sub = Subscription.get_by(user_id=self.id)
|
||||||
return sub
|
return sub
|
||||||
|
|
||||||
|
def verified_custom_domains(self):
|
||||||
|
return CustomDomain.query.filter_by(user_id=self.id, verified=True).all()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<User {self.id} {self.name} {self.email}>"
|
return f"<User {self.id} {self.name} {self.email}>"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue