diff --git a/admin/mailu/ui/forms.py b/admin/mailu/ui/forms.py
index b00f4dac..bd371eec 100644
--- a/admin/mailu/ui/forms.py
+++ b/admin/mailu/ui/forms.py
@@ -104,6 +104,17 @@ class UserReplyForm(flask_wtf.FlaskForm):
submit = fields.SubmitField(_('Update'))
+class TokenForm(flask_wtf.FlaskForm):
+ raw_password = fields.StringField(
+ _('Your token (write it down, as it will never be displayed again)')
+ )
+ comment = fields.StringField(_('Comment'))
+ ip = fields.StringField(
+ _('Authorized IP'), [validators.Optional(), validators.IPAddress()]
+ )
+ submit = fields.SubmitField(_('Create'))
+
+
class AliasForm(flask_wtf.FlaskForm):
localpart = fields.StringField(_('Alias'), [validators.DataRequired()])
wildcard = fields.BooleanField(
diff --git a/admin/mailu/ui/templates/sidebar.html b/admin/mailu/ui/templates/sidebar.html
index 14abe241..920074e2 100644
--- a/admin/mailu/ui/templates/sidebar.html
+++ b/admin/mailu/ui/templates/sidebar.html
@@ -28,6 +28,11 @@
{% trans %}Fetched accounts{% endtrans %}
+
{% trans %}Sign out{% endtrans %}
diff --git a/admin/mailu/ui/templates/token/create.html b/admin/mailu/ui/templates/token/create.html
new file mode 100644
index 00000000..a64e662c
--- /dev/null
+++ b/admin/mailu/ui/templates/token/create.html
@@ -0,0 +1,9 @@
+{% extends "form.html" %}
+
+{% block title %}
+{% trans %}Create an authentication token{% endtrans %}
+{% endblock %}
+
+{% block subtitle %}
+{{ user }}
+{% endblock %}
diff --git a/admin/mailu/ui/templates/token/list.html b/admin/mailu/ui/templates/token/list.html
new file mode 100644
index 00000000..b3996da8
--- /dev/null
+++ b/admin/mailu/ui/templates/token/list.html
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+
+{% block title %}
+{% trans %}Authentication tokens{% endtrans %}
+{% endblock %}
+
+{% block subtitle %}
+{{ user }}
+{% endblock %}
+
+{% block main_action %}
+{% trans %}New token{% endtrans %}
+{% endblock %}
+
+{% block box %}
+
+
+
+ {% trans %}Actions{% endtrans %} |
+ {% trans %}Comment{% endtrans %} |
+ {% trans %}Authorized IP{% endtrans %} |
+ {% trans %}Created{% endtrans %} |
+
+ {% for token in user.tokens %}
+
+
+
+ |
+ {{ token.comment }} |
+ {{ token.ip or "any" }} |
+ {{ token.created_at }} |
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/admin/mailu/ui/templates/user/list.html b/admin/mailu/ui/templates/user/list.html
index a3ee52b1..3c6156e9 100644
--- a/admin/mailu/ui/templates/user/list.html
+++ b/admin/mailu/ui/templates/user/list.html
@@ -36,6 +36,7 @@
+
{{ user }} |
diff --git a/admin/mailu/ui/views/__init__.py b/admin/mailu/ui/views/__init__.py
index e4f5d7d2..e7ae8b35 100644
--- a/admin/mailu/ui/views/__init__.py
+++ b/admin/mailu/ui/views/__init__.py
@@ -1 +1,4 @@
-__all__ = ['admins', 'aliases', 'alternatives', 'base', 'domains', 'fetches', 'managers', 'users', 'relays']
+__all__ = [
+ 'admins', 'aliases', 'alternatives', 'base', 'domains', 'fetches',
+ 'managers', 'users', 'relays', 'tokens'
+]
diff --git a/admin/mailu/ui/views/tokens.py b/admin/mailu/ui/views/tokens.py
new file mode 100644
index 00000000..312d8a49
--- /dev/null
+++ b/admin/mailu/ui/views/tokens.py
@@ -0,0 +1,51 @@
+from mailu import db, models
+from mailu.ui import ui, forms, access
+
+from passlib import pwd
+
+import flask
+import flask_login
+import wtforms_components
+
+
+@ui.route('/token/list', methods=['GET', 'POST'], defaults={'user_email': None})
+@ui.route('/token/list/', methods=['GET'])
+@access.owner(models.User, 'user_email')
+def token_list(user_email):
+ user_email = user_email or flask_login.current_user.email
+ user = models.User.query.get(user_email) or flask.abort(404)
+ return flask.render_template('token/list.html', user=user)
+
+
+@ui.route('/token/create', methods=['GET', 'POST'], defaults={'user_email': None})
+@ui.route('/token/create/', methods=['GET', 'POST'])
+@access.owner(models.User, 'user_email')
+def token_create(user_email):
+ user_email = user_email or flask_login.current_user.email
+ user = models.User.query.get(user_email) or flask.abort(404)
+ form = forms.TokenForm()
+ form.raw_password.data = pwd.genword(entropy=128, charset="hex")
+ wtforms_components.read_only(form.raw_password)
+ if form.validate_on_submit():
+ token = models.Token(user=user)
+ form.populate_obj(token)
+ token.set_password(form.raw_password.data)
+ db.session.add(token)
+ db.session.commit()
+ flask.flash('Authentication token created')
+ return flask.redirect(
+ flask.url_for('.token_list', user_email=user.email))
+ return flask.render_template('token/create.html', form=form)
+
+
+@ui.route('/token/delete/', methods=['GET', 'POST'])
+@access.confirmation_required("delete an authentication token")
+@access.owner(models.Token, 'token_id')
+def token_delete(token_id):
+ token = models.Token.query.get(token_id) or flask.abort(404)
+ user = token.user
+ db.session.delete(token)
+ db.session.commit()
+ flask.flash('Authentication token deleted')
+ return flask.redirect(
+ flask.url_for('.token_list', user_email=user.email))
|