Add self-service domain registration
This commit is contained in:
@@ -27,6 +27,7 @@ default_config = {
|
|||||||
'BOOTSTRAP_SERVE_LOCAL': True,
|
'BOOTSTRAP_SERVE_LOCAL': True,
|
||||||
'RATELIMIT_STORAGE_URL': 'redis://redis',
|
'RATELIMIT_STORAGE_URL': 'redis://redis',
|
||||||
'DEBUG': False,
|
'DEBUG': False,
|
||||||
|
'DOMAIN_REGISTRATION': False,
|
||||||
# Statistics management
|
# Statistics management
|
||||||
'INSTANCE_ID_PATH': '/data/instance',
|
'INSTANCE_ID_PATH': '/data/instance',
|
||||||
'STATS_ENDPOINT': '0.{}.stats.mailu.io',
|
'STATS_ENDPOINT': '0.{}.stats.mailu.io',
|
||||||
|
|||||||
@@ -52,6 +52,15 @@ class DomainForm(flask_wtf.FlaskForm):
|
|||||||
submit = fields.SubmitField(_('Create'))
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|
||||||
|
|
||||||
|
class DomainSignupForm(flask_wtf.FlaskForm):
|
||||||
|
name = fields.StringField(_('Domain name'), [validators.DataRequired()])
|
||||||
|
localpart = fields.StringField(_('Initial admin'), [validators.DataRequired()])
|
||||||
|
pw = fields.PasswordField(_('Admin password'), [validators.DataRequired()])
|
||||||
|
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
||||||
|
captcha = flask_wtf.RecaptchaField()
|
||||||
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|
||||||
|
|
||||||
class AlternativeForm(flask_wtf.FlaskForm):
|
class AlternativeForm(flask_wtf.FlaskForm):
|
||||||
name = fields.StringField(_('Alternative name'), [validators.DataRequired()])
|
name = fields.StringField(_('Alternative name'), [validators.DataRequired()])
|
||||||
submit = fields.SubmitField(_('Create'))
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|||||||
36
core/admin/mailu/ui/templates/domain/signup.html
Normal file
36
core/admin/mailu/ui/templates/domain/signup.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Register a domain{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<form class="form" method="post" role="form">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
|
{% call macros.box(title="Requirements") %}
|
||||||
|
<p>{% trans %}In order to register a new domain, you must first setup the
|
||||||
|
domain zone so that the domain <code>MX</code> points to this server{% endtrans %}
|
||||||
|
(<code>{{ config["HOSTNAMES"].split(",")[0] }}</code>).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% trans %}If you do not know how to setup an <code>MX</code> record for your DNS zone,
|
||||||
|
please contact your DNS provider or administrator. Also, please wait a
|
||||||
|
couple minutes after the <code>MX</code> is set so the local server cache
|
||||||
|
expires.{% endtrans %}
|
||||||
|
</p>
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% call macros.box() %}
|
||||||
|
{% if form.localpart %}
|
||||||
|
{{ macros.form_fields((form.localpart, form.name), append='<span class="input-group-addon">@</span>') }}
|
||||||
|
{{ macros.form_fields((form.pw, form.pw2)) }}
|
||||||
|
{% else %}
|
||||||
|
{{ macros.form_field(form.name) }}
|
||||||
|
{% endif %}
|
||||||
|
{{ macros.form_field(form.captcha) }}
|
||||||
|
{{ macros.form_field(form.submit) }}
|
||||||
|
{% endcall %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -92,6 +92,13 @@
|
|||||||
<i class="fa fa-life-ring"></i> <span>{% trans %}Help{% endtrans %}</span>
|
<i class="fa fa-life-ring"></i> <span>{% trans %}Help{% endtrans %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if config['DOMAIN_REGISTRATION'] %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('.domain_signup') }}">
|
||||||
|
<i class="fa fa-plus-square"></i> <span>{% trans %}Register a domain{% endtrans %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('.logout') }}">
|
<a href="{{ url_for('.logout') }}">
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ from mailu import app, db, models
|
|||||||
from mailu.ui import ui, forms, access
|
from mailu.ui import ui, forms, access
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
import flask_login
|
||||||
import wtforms_components
|
import wtforms_components
|
||||||
|
import dns.resolver
|
||||||
|
|
||||||
|
|
||||||
@ui.route('/domain', methods=['GET'])
|
@ui.route('/domain', methods=['GET'])
|
||||||
@@ -73,3 +75,52 @@ def domain_genkeys(domain_name):
|
|||||||
domain.generate_dkim_key()
|
domain.generate_dkim_key()
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for(".domain_details", domain_name=domain_name))
|
flask.url_for(".domain_details", domain_name=domain_name))
|
||||||
|
|
||||||
|
|
||||||
|
@ui.route('/domain/signup', methods=['GET', 'POST'])
|
||||||
|
def domain_signup(domain_name=None):
|
||||||
|
if not app.config['DOMAIN_REGISTRATION']:
|
||||||
|
flask.abort(403)
|
||||||
|
form = forms.DomainSignupForm()
|
||||||
|
if flask_login.current_user.is_authenticated:
|
||||||
|
del form.localpart
|
||||||
|
del form.pw
|
||||||
|
del form.pw2
|
||||||
|
if form.validate_on_submit():
|
||||||
|
conflicting_domain = models.Domain.query.get(form.name.data)
|
||||||
|
conflicting_alternative = models.Alternative.query.get(form.name.data)
|
||||||
|
conflicting_relay = models.Relay.query.get(form.name.data)
|
||||||
|
hostnames = app.config['HOSTNAMES'].split(',')
|
||||||
|
if conflicting_domain or conflicting_alternative or conflicting_relay:
|
||||||
|
flask.flash('Domain %s is already used' % form.name.data, 'error')
|
||||||
|
else:
|
||||||
|
# Check if the domain MX actually points to this server
|
||||||
|
try:
|
||||||
|
mxok = any(str(rset).split()[-1][:-1] in hostnames
|
||||||
|
for rset in dns.resolver.query(form.name.data, 'MX'))
|
||||||
|
except Exception as e:
|
||||||
|
mxok = False
|
||||||
|
if mxok:
|
||||||
|
# Actually create the domain
|
||||||
|
domain = models.Domain()
|
||||||
|
form.populate_obj(domain)
|
||||||
|
domain.max_quota_bytes = app.config['DEFAULT_QUOTA']
|
||||||
|
domain.max_users = 10
|
||||||
|
domain.max_aliases = 10
|
||||||
|
db.session.add(domain)
|
||||||
|
if flask_login.current_user.is_authenticated:
|
||||||
|
user = models.User.query.get(flask_login.current_user.email)
|
||||||
|
else:
|
||||||
|
user = models.User()
|
||||||
|
user.domain = domain
|
||||||
|
form.populate_obj(user)
|
||||||
|
user.set_password(form.pw.data)
|
||||||
|
user.quota_bytes = domain.max_quota_bytes
|
||||||
|
db.session.add(user)
|
||||||
|
domain.managers.append(user)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('Domain %s created' % domain)
|
||||||
|
return flask.redirect(flask.url_for('.domain_list'))
|
||||||
|
else:
|
||||||
|
flask.flash('The MX record was not properly set', 'error')
|
||||||
|
return flask.render_template('domain/signup.html', form=form)
|
||||||
|
|||||||
@@ -16,3 +16,4 @@ docker-py
|
|||||||
tabulate
|
tabulate
|
||||||
PyYAML
|
PyYAML
|
||||||
PyOpenSSL
|
PyOpenSSL
|
||||||
|
dnspython
|
||||||
|
|||||||
Reference in New Issue
Block a user