diff --git a/core/admin/mailu/__init__.py b/core/admin/mailu/__init__.py index e32d2e7d..1183a174 100644 --- a/core/admin/mailu/__init__.py +++ b/core/admin/mailu/__init__.py @@ -27,6 +27,7 @@ default_config = { 'BOOTSTRAP_SERVE_LOCAL': True, 'RATELIMIT_STORAGE_URL': 'redis://redis', 'DEBUG': False, + 'DOMAIN_REGISTRATION': False, # Statistics management 'INSTANCE_ID_PATH': '/data/instance', 'STATS_ENDPOINT': '0.{}.stats.mailu.io', diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py index c17da724..c5ce5798 100644 --- a/core/admin/mailu/ui/forms.py +++ b/core/admin/mailu/ui/forms.py @@ -52,6 +52,15 @@ class DomainForm(flask_wtf.FlaskForm): 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): name = fields.StringField(_('Alternative name'), [validators.DataRequired()]) submit = fields.SubmitField(_('Create')) diff --git a/core/admin/mailu/ui/templates/domain/signup.html b/core/admin/mailu/ui/templates/domain/signup.html new file mode 100644 index 00000000..c8a52f6c --- /dev/null +++ b/core/admin/mailu/ui/templates/domain/signup.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block title %} +{% trans %}Register a domain{% endtrans %} +{% endblock %} + +{% block content %} + +
+ {{ form.hidden_tag() }} + + {% call macros.box(title="Requirements") %} +

{% trans %}In order to register a new domain, you must first setup the + domain zone so that the domain MX points to this server{% endtrans %} + ({{ config["HOSTNAMES"].split(",")[0] }}). +

+

+ {% trans %}If you do not know how to setup an MX record for your DNS zone, + please contact your DNS provider or administrator. Also, please wait a + couple minutes after the MX is set so the local server cache + expires.{% endtrans %} +

+ {% endcall %} + + {% call macros.box() %} + {% if form.localpart %} + {{ macros.form_fields((form.localpart, form.name), append='@') }} + {{ 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 %} +
+{% endblock %} diff --git a/core/admin/mailu/ui/templates/sidebar.html b/core/admin/mailu/ui/templates/sidebar.html index e700064c..2520fa33 100644 --- a/core/admin/mailu/ui/templates/sidebar.html +++ b/core/admin/mailu/ui/templates/sidebar.html @@ -92,6 +92,13 @@ {% trans %}Help{% endtrans %} + {% if config['DOMAIN_REGISTRATION'] %} +
  • + + {% trans %}Register a domain{% endtrans %} + +
  • + {% endif %} {% if current_user.is_authenticated %}
  • diff --git a/core/admin/mailu/ui/views/domains.py b/core/admin/mailu/ui/views/domains.py index 048e1829..459902e2 100644 --- a/core/admin/mailu/ui/views/domains.py +++ b/core/admin/mailu/ui/views/domains.py @@ -2,7 +2,9 @@ from mailu import app, db, models from mailu.ui import ui, forms, access import flask +import flask_login import wtforms_components +import dns.resolver @ui.route('/domain', methods=['GET']) @@ -73,3 +75,52 @@ def domain_genkeys(domain_name): domain.generate_dkim_key() return flask.redirect( 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) diff --git a/core/admin/requirements.txt b/core/admin/requirements.txt index 3d06b34b..a40e6eb5 100644 --- a/core/admin/requirements.txt +++ b/core/admin/requirements.txt @@ -16,3 +16,4 @@ docker-py tabulate PyYAML PyOpenSSL +dnspython