Add self-service domain registration

master
kaiyou 7 years ago
parent 1c26c9e376
commit 381e76511d

@ -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'))

@ -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

Loading…
Cancel
Save