Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • uffd/uffd
  • rixx/uffd
  • thies/uffd
  • leona/uffd
  • enbewe/uffd
  • strifel/uffd
  • thies/uffd-2
7 results
Select Git revision
Show changes
Showing
with 553 additions and 57 deletions
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
{% block body %} {% block body %}
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<p class="text-right">
<a class="btn btn-primary" href="{{ url_for("group.show") }}">
<i class="fa fa-plus" aria-hidden="true"></i> {{_("New")}}
</a>
</p>
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
<tr> <tr>
...@@ -12,13 +17,13 @@ ...@@ -12,13 +17,13 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for group in groups|sort(attribute="gid") %} {% for group in groups|sort(attribute="unix_gid") %}
<tr id="group-{{ group.gid }}"> <tr id="group-{{ group.id }}">
<th scope="row"> <th scope="row">
{{ group.gid }} {{ group.unix_gid }}
</th> </th>
<td> <td>
<a href="{{ url_for("group.show", gid=group.gid) }}"> <a href="{{ url_for("group.show", id=group.id) }}">
{{ group.name }} {{ group.name }}
</a> </a>
</td> </td>
......
{% extends 'base.html' %}
{% block body %}
<form action="{{ url_for("group.update", id=group.id) }}" method="POST" autocomplete="off">
<div class="align-self-center">
<div class="clearfix pb-2 col">
<div class="float-sm-right">
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_("Save")}}</button>
<a href="{{ url_for("group.index") }}" class="btn btn-secondary">{{_("Cancel")}}</a>
{% if group.id %}
<a href="{{ url_for("group.delete", id=group.id) }}" onClick='return confirm({{_("Are you sure?")|tojson}});' class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a>
{% else %}
<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a>
{% endif %}
</div>
</div>
<div class="form-group col">
<label for="group-gid">{{_("Group ID")}}</label>
{% if not group.id %}
<input type="number" class="form-control" id="group-gid" name="unix_gid" value="" placeholder="Automatically chosen if empty">
{% else %}
<input type="number" class="form-control" id="group-gid" name="unix_gid" value="{{ group.unix_gid }}" readonly>
{% endif %}
</div>
<div class="form-group col">
<label for="group-loginname">{{_("Name")}}</label>
<input type="text" class="form-control" id="group-loginname" name="name" minlength=1 maxlength=32 pattern="[a-z0-9_-]*" value="{{ group.name or '' }}" {{ 'readonly' if group.id }}>
<small class="form-text text-muted">
{{_('At least one and at most 32 lower-case characters, digits, dashes ("-") or underscores ("_"). <b>Cannot be changed later!</b>')|safe}}
</small>
</div>
<div class="form-group col">
<label for="group-description">{{_("Description")}}</label>
<textarea class="form-control" id="group-description" name="description" rows="5">{{ group.description or '' }}</textarea>
<small class="form-text text-muted">
</small>
</div>
{% if group.id %}
<div class="col">
<span>{{_("Members")}}:</span>
<ul class="row">
{% for member in group.members|sort(attribute='loginname') %}
<li class="col-12 col-xs-6 col-sm-4 col-md-3 col-lg-2"><a href="{{ url_for("user.show", id=member.id) }}">{{ member.loginname }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
</form>
{% endblock %}
...@@ -22,17 +22,15 @@ ...@@ -22,17 +22,15 @@
<tr> <tr>
<td> <td>
{% if invite.creator == request.user and invite.active %} {% if invite.creator == request.user and invite.active %}
<a href="{{ url_for('invite.use', token=invite.token) }}"><code>{{ invite.short_token }}</code></a> <a href="{{ url_for('invite.use', invite_id=invite.id, token=invite.token) }}"><code>{{ invite.short_token }}</code></a>
<button type="button" class="btn btn-link btn-sm p-0 copy-clipboard" data-copy="{{ url_for('invite.use', token=invite.token, _external=True) }}" title="{{_('Copy link to clipboard')}}"><i class="fas fa-clipboard"></i></button> <button type="button" class="btn btn-link btn-sm p-0 copy-clipboard" data-copy="{{ url_for('invite.use', invite_id=invite.id, token=invite.token, _external=True) }}" title="{{_('Copy link to clipboard')}}"><i class="fas fa-clipboard"></i></button>
<button type="button" class="btn btn-link btn-sm p-0" data-toggle="modal" data-target="#modal-{{ invite.id }}-qrcode" title="{{_('Show link as QR code')}}"><i class="fas fa-qrcode"></i></button> <button type="button" class="btn btn-link btn-sm p-0" data-toggle="modal" data-target="#modal-{{ invite.id }}-qrcode" title="{{_('Show link as QR code')}}"><i class="fas fa-qrcode"></i></button>
{% else %} {% else %}
<code>{{ invite.short_token }}</code> <code>{{ invite.short_token }}</code>
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if not invite.creator_dn %} {% if not invite.creator %}
{{ '<admin>' }}
{% elif not invite.creator %}
{{ '<deleted user>' }} {{ '<deleted user>' }}
{% else %} {% else %}
{{ invite.creator.loginname }} {{ invite.creator.loginname }}
...@@ -43,7 +41,7 @@ ...@@ -43,7 +41,7 @@
{% for role in invite.roles %}{{ ', ' if loop.index != 1 }}<i class="fas fa-key"></i>&thinsp;{{ role.name }}{% endfor %} {% for role in invite.roles %}{{ ', ' if loop.index != 1 }}<i class="fas fa-key"></i>&thinsp;{{ role.name }}{% endfor %}
</td> </td>
<td> <td>
<span style="white-space: nowrap;">{{ invite.signups|selectattr('completed')|list|length }} <i class="fas fa-users" title="user registrations"></i></span>, <span style="white-space: nowrap;">{{ invite.signups|selectattr('completed')|list|length }} <i class="fas fa-users" title="{{ _('user signups') }}"></i></span>,
<span style="white-space: nowrap;">{{ invite.grants|length }} <i class="fas fa-key" title="role grants"></i></span> <span style="white-space: nowrap;">{{ invite.grants|length }} <i class="fas fa-key" title="role grants"></i></span>
</td> </td>
<td> <td>
...@@ -58,9 +56,9 @@ ...@@ -58,9 +56,9 @@
{% elif not invite.active %} {% elif not invite.active %}
{{_('Invalid')}} {{_('Invalid')}}
{% elif invite.single_use %} {% elif invite.single_use %}
{{ _('Valid once, expires %(expiry_date)s', expiry_date=invite.valid_until.strftime('%Y-%m-%d')) }} {{ _('Valid once, expires %(expiry_date)s', expiry_date=invite.valid_until|dateformat) }}
{% else %} {% else %}
{{ _('Valid, expires %(expiry_date)s',expiry_date=invite.valid_until.strftime('%Y-%m-%d')) }} {{ _('Valid, expires %(expiry_date)s', expiry_date=invite.valid_until|dateformat) }}
{% endif %} {% endif %}
</td> </td>
<td class="text-right"> <td class="text-right">
...@@ -85,8 +83,8 @@ ...@@ -85,8 +83,8 @@
<div class="modal-body"> <div class="modal-body">
<ul class="list-unstyled"> <ul class="list-unstyled">
<li><b>{{_('Type:')}}</b> {% if invite.single_use %}{{_('Single-use')}}{% else %}{{_('Multi-use')}}{% endif %}</li> <li><b>{{_('Type:')}}</b> {% if invite.single_use %}{{_('Single-use')}}{% else %}{{_('Multi-use')}}{% endif %}</li>
<li><b>{{_('Created:')}}</b> {{ invite.created.strftime('%Y-%m-%d %H:%M:%S') }}</li> <li><b>{{_('Created:')}}</b> {{ invite.created|datetimeformat }}</li>
<li><b>{{_('Expires:')}}</b> {{ invite.valid_until.strftime('%Y-%m-%d %H:%M:%S') }}</li> <li><b>{{_('Expires:')}}</b> {{ invite.valid_until|datetimeformat }}</li>
<li><b>{{_('Permissions:')}}</b> <li><b>{{_('Permissions:')}}</b>
<ul> <ul>
{% if invite.allow_signup %} {% if invite.allow_signup %}
...@@ -105,10 +103,10 @@ ...@@ -105,10 +103,10 @@
{% else %} {% else %}
<ul> <ul>
{% for signup in invite.signups if signup.completed %} {% for signup in invite.signups if signup.completed %}
<li>{{_('Registration of user <a href="%(user_url)s">%(user_name)s</a>', user_url=url_for('user.show', uid=signup.user.uid)|e, user_name=signup.user.loginname|e)|safe}}</li> <li>{{_('Registration of user <a href="%(user_url)s">%(user_name)s</a>', user_url=url_for('user.show', id=signup.user.id)|e, user_name=signup.user.loginname|e)|safe}}</li>
{% endfor %} {% endfor %}
{% for grant in invite.grants if grant.user %} {% for grant in invite.grants if grant.user %}
<li>{{_('Roles granted to <a href="%(user_url)s">%(user_name)s</a>', user_url=url_for('user.show', uid=grant.user.uid)|e, user_name=grant.user.loginname|e)|safe}}</li> <li>{{_('Roles granted to <a href="%(user_url)s">%(user_name)s</a>', user_url=url_for('user.show', id=grant.user.id)|e, user_name=grant.user.loginname|e)|safe}}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
...@@ -143,7 +141,7 @@ ...@@ -143,7 +141,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
{{ url_for('invite.use', token=invite.token, _external=True)|qrcode_svg(width='100%', height='100%') }} {{ url_for('invite.use', invite_id=invite.id, token=invite.token, _external=True)|qrcode_svg(width='100%', height='100%') }}
</div> </div>
</div> </div>
</div> </div>
......
{% extends 'base.html' %} {% extends 'base.html' %}
{% block body %} {% block body %}
<form action="{{ url_for("invite.new_submit") }}" method="POST" class="form"> <form action="{{ url_for("invite.new_submit") }}" method="POST" autocomplete="off" class="form">
<div class="form-group"> <div class="form-group">
<label for="single-use">{{_('Link Type')}}</label> <label for="single-use">{{_('Link Type')}}</label>
<select class="form-control" id="single-use" name="single-use"> <select class="form-control" id="single-use" name="single-use">
<option value="1">{{_('Valid for a single successful use')}}</option> <option value="1" {{ 'selected' if request.values.get('single-use', '1') == '1' }}>{{_('Valid for a single successful use')}}</option>
<option value="0">{{_('Multi-use')}}</option> <option value="0" {{ 'selected' if request.values.get('single-use', '1') == '0' }}>{{_('Multi-use')}}</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="valid-until">{{_('Valid Until')}}</label> <label for="valid-until">{{_('Valid Until')}}</label>
<input class="form-control" type="datetime-local" id="valid-until" name="valid-until" value="{{ (datetime.now() + timedelta(hours=36)).replace(hour=23, minute=59, second=59, microsecond=0).isoformat(timespec='minutes') }}"> <input class="form-control" type="datetime-local" id="valid-until" name="valid-until" value="{{ request.values.get('valid-until') or (datetime.now() + timedelta(hours=36)).replace(hour=23, minute=59).isoformat(timespec='minutes') }}" min="{{ datetime.now().isoformat(timespec='minutes') }}" max="{{ (datetime.now() + timedelta(days=config['INVITE_MAX_VALID_DAYS'])).isoformat(timespec='minutes') }}">
<small class="text-muted">{{_('Must be within the next %(max_valid_days)d days', max_valid_days=config['INVITE_MAX_VALID_DAYS'])}}</small> <small class="text-muted">{{_('Must be within the next %(max_valid_days)d days', max_valid_days=config['INVITE_MAX_VALID_DAYS'])}}</small>
</div> </div>
{% if allow_signup %} {% if allow_signup %}
<div class="form-group"> <div class="form-group">
<label for="allow-signup">{{_('Account Registration')}}</label> <label for="allow-signup">{{_('Account Registration')}}</label>
<select class="form-control" id="allow-signup" name="allow-signup"> <select class="form-control" id="allow-signup" name="allow-signup">
<option value="1">{{_('Link allows account registration')}}</option> <option value="1" {{ 'selected' if request.values.get('allow-signup', '1') == '1' }}>{{_('Link allows account registration')}}</option>
<option value="0">{{_('No account registration allowed')}}</option> <option value="0" {{ 'selected' if request.values.get('allow-signup', '1') == '0' }}>{{_('No account registration allowed')}}</option>
</select> </select>
</div> </div>
{% else %} {% else %}
......
{% extends 'base_narrow.html' %}
{% block body %}
<div class="col-12 mb-3">
<h2 class="text-center">{{_('Invite Link')}}</h2>
</div>
{% if not request.user %}
<p>{{_('Welcome to the %(org_name)s Single-Sign-On!', org_name=config.ORGANISATION_NAME)}}</p>
{% endif %}
{% if invite.roles and invite.allow_signup %}
<p>{{_('With this link you can register a new user account with the following roles or add the roles to an existing account:')}}</p>
{% elif invite.roles %}
<p>{{_('With this link you can add the following roles to an existing account:')}}</p>
{% elif invite.allow_signup %}
<p>{{_('With this link you can register a new user account.')}}</p>
{% endif %}
{% if invite.roles %}
<ul>
{% for role in invite.roles %}
<li>{{ role.name }}{% if role.description %}: {{ role.description }}{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
{% if request.user %}
{% if invite.roles %}
<form method="POST" action="{{ url_for("invite.grant", invite_id=invite.id, token=invite.token) }}" class="mb-2">
<button type="submit" class="btn btn-primary btn-block">{{_('Add the roles to your account now')}}</button>
</form>
<a href="{{ url_for("session.logout", ref=url_for("session.login", ref=request.full_path)) }}" class="btn btn-secondary btn-block">{{_('Logout and switch to a different account')}}</a>
{% endif %}
{% if invite.allow_signup %}
<a href="{{ url_for("session.logout", ref=url_for("invite.signup_start", invite_id=invite.id, token=invite.token)) }}" class="btn btn-secondary btn-block">{{_('Logout to register a new account')}}</a>
{% endif %}
{% else %}
{% if invite.allow_signup %}
<a href="{{ url_for("invite.signup_start", invite_id=invite.id, token=invite.token) }}" class="btn btn-primary btn-block">{{_('Register a new account')}}</a>
{% endif %}
{% if invite.roles %}
<a href="{{ url_for("session.login", ref=request.full_path) }}" class="btn btn-primary btn-block">{{_('Login and add the roles to your account')}}</a>
{% endif %}
{% endif %}
{% endblock %}
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
</thead> </thead>
<tbody> <tbody>
{% for mail in mails|sort(attribute="uid") %} {% for mail in mails|sort(attribute="uid") %}
<tr id="mail-{{ mail.uid }}"> <tr id="mail-{{ mail.id }}">
<th scope="row"> <th scope="row">
<a href="{{ url_for("mail.show", uid=mail.uid) }}"> <a href="{{ url_for("mail.show", mail_id=mail.id) }}">
{{ mail.uid }} {{ mail.uid }}
</a> </a>
</th> </th>
......
{% extends 'base.html' %} {% extends 'base.html' %}
{% block body %} {% block body %}
<form action="{{ url_for("mail.update", uid=mail.uid) }}" method="POST"> <form action="{{ url_for("mail.update", mail_id=mail.id) }}" method="POST" autocomplete="off">
<div class="align-self-center"> <div class="align-self-center">
<div class="form-group col"> <div class="form-group col">
<label for="mail-name">{{_('Name')}}</label> <label for="mail-name">{{_('Name')}}</label>
<input type="text" class="form-control" id="mail-name" name="mail-uid" {% if mail.uid %} value="{{ mail.uid }}" readonly {% else %} value=""{% endif %}> <input type="text" class="form-control" id="mail-name" name="mail-uid" {% if mail.id %} value="{{ mail.uid }}" readonly {% else %} value=""{% endif %}>
<small class="form-text text-muted"> <small class="form-text text-muted">
</small> </small>
</div> </div>
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<label for="mail-receivers">{{_('Receiving addresses')}}</label> <label for="mail-receivers">{{_('Receiving addresses')}}</label>
<textarea rows="10" class="form-control" id="mail-receivers" name="mail-receivers">{{ mail.receivers|join('\n') }}</textarea> <textarea rows="10" class="form-control" id="mail-receivers" name="mail-receivers">{{ mail.receivers|join('\n') }}</textarea>
<small class="form-text text-muted"> <small class="form-text text-muted">
{{_('One address per line')}} {{_('One address pattern (local+ext@domain, local@domain, local, @domain) per line. Only lower-case ASCII letters, digits and symbols.')}}
</small> </small>
</div> </div>
<div class="form-group col"> <div class="form-group col">
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
<div class="form-group col"> <div class="form-group col">
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_('Save')}}</button> <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_('Save')}}</button>
<a href="{{ url_for("mail.index") }}" class="btn btn-secondary">{{_('Cancel')}}</a> <a href="{{ url_for("mail.index") }}" class="btn btn-secondary">{{_('Cancel')}}</a>
{% if mail.uid %} {% if mail.id %}
<a href="{{ url_for("mail.delete", uid=mail.uid) }}" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> {{_('Delete')}}</a> <a href="{{ url_for("mail.delete", mail_id=mail.id) }}" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> {{_('Delete')}}</a>
{% else %} {% else %}
<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> {{_('Delete')}}</a> <a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> {{_('Delete')}}</a>
{% endif %} {% endif %}
......
...@@ -3,14 +3,6 @@ ...@@ -3,14 +3,6 @@
{% block body %} {% block body %}
<h1>OAuth2.0 Authorization Error</h1> <h1>OAuth2.0 Authorization Error</h1>
<p><b>Error: {{ error }}</b> {{ '(' + error_description + ')' if error_description else '' }}</p> <p><b>Error: {{ error }}</b> {{ '(' + error_description + ')' if error_description else '' }}</p>
{% if args %}
<p>Parameters:</p>
<ul>
{% for key, value in args.items() %}
<li>{{ key }}={{ value }}</li>
{% endfor %}
</ul>
{% endif %}
<hr> <hr>
......
{% extends 'base.html' %} {% extends 'base_narrow.html' %}
{% block body %} {% block body %}
<div class="row mt-2 justify-content-center">
<div class="col-lg-6 col-md-10" style="background: #f7f7f7; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); padding: 30px;">
<div class="text-center">
<img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" >
</div>
<div class="col-12"> <div class="col-12">
<h2 class="text-center">{{_('Logout')}}</h2> <h2 class="text-center">{{_('Logout')}}</h2>
</div> </div>
...@@ -16,9 +11,9 @@ ...@@ -16,9 +11,9 @@
</noscript> </noscript>
<p>{{_('While you successfully logged out of the Single-Sign-On service, you may still be logged in on these services:')}}</p> <p>{{_('While you successfully logged out of the Single-Sign-On service, you may still be logged in on these services:')}}</p>
<ul> <ul>
{% for client in clients if client.logout_urls %} {% for client in clients if client.logout_uris %}
<li class="client" data-urls='{{ client.logout_urls|tojson }}'> <li class="client" data-urls='{{ client.logout_uris_json }}'>
{{ client.client_id }} {{ client.service.name }}
<span class="status-active spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span> <span class="status-active spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
<i class="status-success fas fa-check d-none"></i> <i class="status-success fas fa-check d-none"></i>
<i class="status-failed fas fa-exclamation d-none"></i> <i class="status-failed fas fa-exclamation d-none"></i>
...@@ -39,8 +34,6 @@ ...@@ -39,8 +34,6 @@
</a> </a>
</div> </div>
</div>
</div>
<script> <script>
function logout_services() { function logout_services() {
...@@ -60,7 +53,6 @@ function logout_services() { ...@@ -60,7 +53,6 @@ function logout_services() {
}); });
}); });
p = p.then(function () { p = p.then(function () {
console.log('done', elem);
elem.find('.status-active').addClass('d-none'); elem.find('.status-active').addClass('d-none');
elem.find('.status-success').removeClass('d-none'); elem.find('.status-success').removeClass('d-none');
elem.removeClass('client'); elem.removeClass('client');
...@@ -68,13 +60,11 @@ function logout_services() { ...@@ -68,13 +60,11 @@ function logout_services() {
.catch(function (err) { .catch(function (err) {
elem.find('.status-active').addClass('d-none'); elem.find('.status-active').addClass('d-none');
elem.find('.status-failed').removeClass('d-none'); elem.find('.status-failed').removeClass('d-none');
console.log(err);
throw err; throw err;
}); });
all_promises.push(p); all_promises.push(p);
}); });
Promise.allSettled(all_promises).then(function (results) { Promise.allSettled(all_promises).then(function (results) {
console.log(results);
for (result of results) { for (result of results) {
if (result.status == 'rejected') if (result.status == 'rejected')
throw result.reason; throw result.reason;
......
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
</div> </div>
{% endif %} {% endif %}
<form action="{{ url_for("role.update", roleid=role.id) }}" method="POST"> <form action="{{ url_for("role.update", roleid=role.id) }}" method="POST" autocomplete="off">
<div class="align-self-center"> <div class="align-self-center">
<div class="float-sm-right pb-2"> <div class="clearfix pb-2"><div class="float-sm-right">
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_("Save")}}</button> <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_("Save")}}</button>
<a href="{{ url_for("role.index") }}" class="btn btn-secondary">{{_("Cancel")}}</a> <a href="{{ url_for("role.index") }}" class="btn btn-secondary">{{_("Cancel")}}</a>
{% if role.id %} {% if role.id %}
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
<a href="#" class="btn btn-secondary disabled">{{_("Set as default")}}</a> <a href="#" class="btn btn-secondary disabled">{{_("Set as default")}}</a>
<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a> <a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a>
{% endif %} {% endif %}
</div> </div></div>
<ul class="nav nav-tabs pt-2 border-0" id="tablist" role="tablist"> <ul class="nav nav-tabs pt-2 border-0" id="tablist" role="tablist">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" id="settings-tab" data-toggle="tab" href="#settings" role="tab" aria-controls="settings" aria-selected="true">{{_("Settings")}}</a> <a class="nav-link active" id="settings-tab" data-toggle="tab" href="#settings" role="tab" aria-controls="settings" aria-selected="true">{{_("Settings")}}</a>
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
<select class="form-control" id="moderator-group" name="moderator-group" {{ 'disabled' if role.locked }}> <select class="form-control" id="moderator-group" name="moderator-group" {{ 'disabled' if role.locked }}>
<option value="" class="text-muted">{{_("No Moderator Group")}}</option> <option value="" class="text-muted">{{_("No Moderator Group")}}</option>
{% for group in groups %} {% for group in groups %}
<option value="{{ group.dn }}" {{ 'selected' if group == role.moderator_group }}>{{ group.name }}</option> <option value="{{ group.id }}" {{ 'selected' if group == role.moderator_group }}>{{ group.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
<span>{{_("Moderators")}}:</span> <span>{{_("Moderators")}}:</span>
<ul class="row"> <ul class="row">
{% for moderator in role.moderator_group.members %} {% for moderator in role.moderator_group.members %}
<li class="col-12 col-xs-6 col-sm-4 col-md-3 col-lg-2"><a href="{{ url_for("user.show", uid=moderator.uid) }}">{{ moderator.loginname }}</a></li> <li class="col-12 col-xs-6 col-sm-4 col-md-3 col-lg-2"><a href="{{ url_for("user.show", id=moderator.id) }}">{{ moderator.loginname }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
<span>{{_("Members")}}:</span> <span>{{_("Members")}}:</span>
<ul class="row"> <ul class="row">
{% for member in role.members|sort(attribute='loginname') %} {% for member in role.members|sort(attribute='loginname') %}
<li class="col-12 col-xs-6 col-sm-4 col-md-3 col-lg-2"><a href="{{ url_for("user.show", uid=member.uid) }}">{{ member.loginname }}</a></li> <li class="col-12 col-xs-6 col-sm-4 col-md-3 col-lg-2"><a href="{{ url_for("user.show", id=member.id) }}">{{ member.loginname }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
</thead> </thead>
<tbody> <tbody>
{% for r in roles|sort(attribute="name")|sort(attribute='name') %} {% for r in roles|sort(attribute="name")|sort(attribute='name') %}
<tr id="include-role-{{ role.id }}"> <tr id="include-role-{{ r.id }}">
<td> <td>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="include-role-{{ r.id }}-checkbox" name="include-role-{{ r.id }}" value="1" aria-label="enabled" <input class="form-check-input" type="checkbox" id="include-role-{{ r.id }}-checkbox" name="include-role-{{ r.id }}" value="1" aria-label="enabled"
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
</td> </td>
<td> <td>
{% for group in r.groups_effective|sort(attribute='name') %} {% for group in r.groups_effective|sort(attribute='name') %}
<a href="{{ url_for("group.show", gid=group.gid) }}">{{ group.name }}</a>{{ ', ' if not loop.last }} <a href="{{ url_for("group.show", id=group.id) }}">{{ group.name }}</a>{{ ', ' if not loop.last }}
{% endfor %} {% endfor %}
</td> </td>
</tr> </tr>
...@@ -131,14 +131,14 @@ ...@@ -131,14 +131,14 @@
</thead> </thead>
<tbody> <tbody>
{% for group in groups|sort(attribute="name") %} {% for group in groups|sort(attribute="name") %}
<tr id="group-{{ group.gid }}"> <tr id="group-{{ group.id }}">
<td> <td>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="group-{{ group.gid }}-checkbox" name="group-{{ group.gid }}" value="1" aria-label="enabled" {% if group in role.groups %}checked{% endif %} {{ 'disabled' if role.locked }}> <input class="form-check-input" type="checkbox" id="group-{{ group.id }}-checkbox" name="group-{{ group.id }}" value="1" aria-label="enabled" {% if group in role.groups %}checked{% endif %} {{ 'disabled' if role.locked }}>
</div> </div>
</td> </td>
<td> <td>
<a href="{{ url_for("group.show", gid=group.gid) }}"> <a href="{{ url_for("group.show", id=group.id) }}">
{{ group.name }} {{ group.name }}
</a> </a>
</td> </td>
...@@ -147,7 +147,7 @@ ...@@ -147,7 +147,7 @@
</td> </td>
<td> <td>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="group-mfa-{{ group.gid }}-checkbox" name="group-mfa-{{ group.gid }}" value="1" aria-label="enabled" {% if group in role.groups and role.groups[group].requires_mfa %}checked{% endif %} {{ 'disabled' if role.locked }}> <input class="form-check-input" type="checkbox" id="group-mfa-{{ group.id }}-checkbox" name="group-mfa-{{ group.id }}" value="1" aria-label="enabled" {% if group in role.groups and role.groups[group].requires_mfa %}checked{% endif %} {{ 'disabled' if role.locked }}>
</div> </div>
</td> </td>
</tr> </tr>
......
...@@ -4,9 +4,7 @@ ...@@ -4,9 +4,7 @@
<form method="POST" action="{{ url_for("rolemod.update", role_id=role.id) }}"> <form method="POST" action="{{ url_for("rolemod.update", role_id=role.id) }}">
<div class="float-sm-right pb-2"> <div class="float-sm-right pb-2">
{% if config['ENABLE_INVITE'] %}
<a href="{{ url_for("invite.new", **{"role-%d"%role.id: 1}) }}" class="btn btn-primary mr-2"><i class="fa fa-link" aria-hidden="true"></i> {{_('Invite Members')}}</a> <a href="{{ url_for("invite.new", **{"role-%d"%role.id: 1}) }}" class="btn btn-primary mr-2"><i class="fa fa-link" aria-hidden="true"></i> {{_('Invite Members')}}</a>
{% endif %}
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_('Save')}}</button> <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_('Save')}}</button>
<a href="{{ url_for("rolemod.index") }}" class="btn btn-secondary">{{_('Cancel')}}</a> <a href="{{ url_for("rolemod.index") }}" class="btn btn-secondary">{{_('Cancel')}}</a>
</div> </div>
...@@ -52,7 +50,7 @@ ...@@ -52,7 +50,7 @@
<tr> <tr>
<td>{{ member.displayname }} ({{ member.loginname }})</td> <td>{{ member.displayname }} ({{ member.loginname }})</td>
<td class="text-right"> <td class="text-right">
<a class="btn btn-danger py-0" href="{{ url_for('rolemod.delete_member', role_id=role.id, member_dn=member.dn) }}">{{_('Remove')}}</a> <a class="btn btn-danger py-0" href="{{ url_for('rolemod.delete_member', role_id=role.id, member_id=member.id) }}">{{_('Remove')}}</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
You can later generate new recovery codes and setup your applications and devices again.")}} You can later generate new recovery codes and setup your applications and devices again.")}}
</p> </p>
<form class="form" action="{{ url_for('mfa.disable_confirm') }}" method="POST"> <form class="form" action="{{ url_for('selfservice.disable_mfa_confirm') }}" method="POST">
<button type="submit" class="btn btn-danger btn-block">{{_("Disable two-factor authentication")}}</button> <button type="submit" class="btn btn-danger btn-block">{{_("Disable two-factor authentication")}}</button>
</form> </form>
......
{% extends 'base_narrow.html' %}
{% block body %}
<form action="{{ url_for("selfservice.forgot_password") }}" method="POST">
<div class="col-12">
<h2 class="text-center">{{_("Forgot password")}}</h2>
</div>
<div class="form-group col-12">
<label for="user-loginname">{{_("Login Name")}}</label>
<input type="text" autocomplete="username" class="form-control" id="user-loginname" name="loginname" required="required" tabindex="1">
</div>
<div class="form-group col-12">
<label for="user-mail">{{_("Mail Address")}}</label>
<input type="email" autocomplete="email" class="form-control" id="user-mail" name="mail" required="required" tabindex="2">
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block" tabindex="3">{{_("Send password reset mail")}}</button>
</div>
</form>
{% endblock %}
Hi {{ user.displayname }}, Hi {{ user.displayname }},
you have requested to change your mail address. To confirm the change, please visit the following url: you have requested to change your mail address. To confirm the change, please visit the following url:
{{ url_for('selfservice.token_mail', token=token, _external=True) }} {{ url_for('selfservice.verify_email', email_id=email.id, secret=secret, _external=True) }}
**The link is valid for 48h** **The link is valid for 48h**
...@@ -4,7 +4,7 @@ welcome to the {{ config.ORGANISATION_NAME }} infrastructure! An account was cre ...@@ -4,7 +4,7 @@ welcome to the {{ config.ORGANISATION_NAME }} infrastructure! An account was cre
Please visit the following url to set your password: Please visit the following url to set your password:
{{ url_for('selfservice.token_password', token=token, _external=True) }} {{ url_for('selfservice.token_password', token_id=token.id, token=token.token, _external=True) }}
**The link is valid for 48h** **The link is valid for 48h**
......
Hi {{ user.displayname }}, Hi {{ user.displayname }},
you have requested a password reset. To reset your password, visit the following url: you have requested a password reset. To reset your password, visit the following url:
{{ url_for('selfservice.token_password', token=token, _external=True) }} {{ url_for('selfservice.token_password', token_id=token.id, token=token.token, _external=True) }}
**The link is valid for 48h** **The link is valid for 48h**
......
...@@ -9,32 +9,122 @@ ...@@ -9,32 +9,122 @@
</div> </div>
{% endif %} {% endif %}
<div class="row mt-3"> <div class="row">
<div class="col-12 col-md-5"> <div class="col-12 col-md-5">
<h5>{{_("Profile")}}</h5> <h5>{{_("Profile")}}</h5>
<p>{{_("Your profile information is used by all services that are integrated into the Single-Sign-On. Your e-mail address is also used for password recovery.")}}</p> <p>{{_("Your profile information is used by all services that are integrated into the Single-Sign-On.")}}</p>
<p>{{_("Changes may take serveral minutes to be visible in all services.")}}</p> <p>{{_("Changes may take several minutes to be visible in all services.")}}</p>
</div> </div>
<div class="col-12 col-md-7"> <div class="col-12 col-md-7">
<form class="form" action="{{ url_for("selfservice.update_profile") }}" method="POST"> <form class="form" action="{{ url_for("selfservice.update_profile") }}" method="POST">
<div class="form-row"> <div class="form-group">
<div class="form-group col-12 col-md-9">
<label>{{_("Login Name")}}</label> <label>{{_("Login Name")}}</label>
<input type="text" class="form-control" value="{{ user.loginname }}" readonly> <input type="text" class="form-control" value="{{ user.loginname }}" readonly>
</div> </div>
</div>
<div class="form-group"> <div class="form-group">
<label>{{_("Display Name")}}</label> <label>{{_("Display Name")}}</label>
<input type="text" class="form-control" id="user-displayname" name="displayname" value="{{ user.displayname }}"> <input type="text" class="form-control" id="user-displayname" name="displayname" value="{{ user.displayname }}">
</div> </div>
<button type="submit" class="btn btn-primary btn-block">{{_("Update Profile")}}</button>
</form>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-md-5">
<h5>{{_("E-Mail Addresses")}}</h5>
<p>{{_("Add and delete addresses associated with your account. You will need to verify new addresses by opening a link set to them.")}}</p>
</div>
<div class="col-12 col-md-7">
<form method="POST" action="{{ url_for('selfservice.add_email') }}" class="form mb-2">
<div class="row m-0">
<label class="sr-only" for="new-email-address">{{_("Email")}}</label>
<input type="email" autocomplete="email" class="form-control mb-2 col-12 col-lg-auto mr-2" style="width: 20em;" id="new-email-address" name="address" placeholder="{{_("New E-Mail Address")}}" required>
<button type="submit" class="btn btn-primary mb-2 col">{{_("Add address")}}</button>
</div>
</form>
<table class="table mb-0">
<tbody>
{% for email in user.all_emails|sort(attribute='id') %}
<tr>
<td class="pl-0">
{{ email.address }}
{% if email == user.primary_email %}
<span class="badge badge-primary">{{ _('primary') }}</span>
{% elif not email.verified %}
<span class="badge badge-danger">{{ _('unverified') }}</span>
{% endif %}
</td>
<td class="pt-2 pb-1 pr-0">
<form method="POST" action="{{ url_for('selfservice.delete_email', email_id=email.id) }}" onsubmit='return confirm({{_("Are you sure?")|tojson|e}});'>
<button type="submit" class="btn btn-sm btn-danger float-right ml-1 mb-1"{% if email == user.primary_email %} disabled title="{{ _('Cannot delete primary e-mail address') }}"{% endif %}>{{_("Delete")}}</button>
</form>
{% if not email.verified %}
<a href="{{ url_for('selfservice.retry_email_verification', email_id=email.id) }}" class="btn btn-sm btn-primary float-right mb-1">{{_("Retry verification")}}</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<hr>
{% set service_users_with_email_prefs = user.service_users|selectattr('has_email_preferences')|list %}
{% set collapse_email_prefs = service_users_with_email_prefs|length > 2 %}
<div class="row">
<div class="col-12 col-md-5">
<h5>{{_("E-Mail Preferences")}}</h5>
<p>
{{ _("Choose your primary e-mail address and the address password recovery e-mails will be sent to.") }}
{% if service_users_with_email_prefs %}
{{ _("You can also select different addresses for different services.") }}
{% endif %}
</p>
<p>{{ _("Adresses must be verified before you can select them here.") }}</p>
</div>
<div class="col-12 col-md-7">
<form class="form" action="{{ url_for("selfservice.update_email_preferences") }}" method="POST">
<div class="form-group"> <div class="form-group">
<label>{{_("E-Mail Address")}}</label> <label>{{_("Primary Address")}}</label>
<input type="email" class="form-control" id="user-mail" name="mail" value="{{ user.mail }}" required> <select name="primary_email" class="form-control">
<small class="form-text text-muted"> {% for email in user.all_emails if email.verified %}
{{_("We will send you a confirmation mail to this address if you change it")}} <option value="{{ email.id }}" {{ 'selected' if email == user.primary_email }}>{{ email.address }}</option>
</small> {% endfor %}
</select>
</div> </div>
<button type="submit" class="btn btn-primary btn-block">{{_("Update Profile")}}</button> <div class="form-group">
<label>{{_("Address for Password Reset E-Mails")}}</label>
<select name="recovery_email" class="form-control">
<option value="primary" {{ 'selected' if not user.recovery_email }}>{{ _('Use primary address') }}</option>
{% for email in user.all_emails if email.verified %}
<option value="{{ email.id }}" {{ 'selected' if email == user.recovery_email }}>{{ email.address }}</option>
{% endfor %}
</select>
</div>
{% for service_user in service_users_with_email_prefs %}
{% if collapse_email_prefs and loop.index == 2 %}
<div id="collapsed-email-prefs">
{% endif %}
<div class="form-group">
<label>{{ _('Address for Service "%(name)s"', name=service_user.service.name) }}</label>
<select name="service_{{ service_user.service.id }}_email" class="form-control">
<option value="primary" {{ 'selected' if not service_user.service_email }}>{{ _('Use primary address') }}</option>
{% for email in user.all_emails if email.verified %}
<option value="{{ email.id }}" {{ 'selected' if email == service_user.service_email }}>{{ email.address }}</option>
{% endfor %}
</select>
</div>
{% endfor %}
{% if collapse_email_prefs %}
</div>
<button type="button" class="btn btn-sm btn-link pl-0 mb-1 showmore" data-target="#collapsed-email-prefs" style="display: none;" aria-expanded="false" aria-controls="collapsed-email-prefs">{{ _("Show more settings ...") }}</button>
{% endif %}
<button type="submit" class="btn btn-primary btn-block">{{_("Update E-Mail Preferences")}}</button>
</form> </form>
</div> </div>
</div> </div>
...@@ -49,13 +139,13 @@ ...@@ -49,13 +139,13 @@
<div class="col-12 col-md-7"> <div class="col-12 col-md-7">
<form class="form" action="{{ url_for("selfservice.change_password") }}" method="POST"> <form class="form" action="{{ url_for("selfservice.change_password") }}" method="POST">
<div class="form-group"> <div class="form-group">
<input type="password" class="form-control" id="user-password1" name="password1" placeholder="{{_("New Password")}}" required> <input type="password" autocomplete="new-password" class="form-control" id="user-password1" name="password1" placeholder="{{_("New Password")}}" minlength={{ User.PASSWORD_MINLEN }} maxlength={{ User.PASSWORD_MAXLEN }} pattern="{{ User.PASSWORD_REGEX }}" required>
<small class="form-text text-muted"> <small class="form-text text-muted">
{{_('At least 8 and at most 256 characters, no other special requirements.')}} {{ User.PASSWORD_DESCRIPTION|safe }}
</small> </small>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="password" class="form-control" id="user-password2" name="password2" placeholder="{{_("Repeat Password")}}" required> <input type="password" autocomplete="new-password" class="form-control" id="user-password2" name="password2" placeholder="{{_("Repeat Password")}}" required>
</div> </div>
<button type="submit" class="btn btn-primary btn-block">{{_("Change Password")}}</button> <button type="submit" class="btn btn-primary btn-block">{{_("Change Password")}}</button>
</form> </form>
...@@ -77,7 +167,56 @@ ...@@ -77,7 +167,56 @@
{{ _("Two-factor authentication is currently <strong>disabled</strong>.")|safe }} {{ _("Two-factor authentication is currently <strong>disabled</strong>.")|safe }}
{% endif %} {% endif %}
</p> </p>
<a class="btn btn-primary btn-block" href="{{ url_for('mfa.setup') }}">{{_("Manage two-factor authentication")}}</a> <a class="btn btn-primary btn-block" href="{{ url_for('selfservice.setup_mfa') }}">{{_("Manage two-factor authentication")}}</a>
</div>
</div>
<hr>
<div class="row mt-3">
<div class="col-12 col-md-5">
<h5>{{_("Active Sessions")}}</h5>
<p>{{_("Your active login sessions on this device and other devices.")}}</p>
<p>{{_("Revoke a session to log yourself out on another device. Note that this is limited to the Single-Sign-On session and <b>does not affect login sessions on services.</b>")}}</p>
</div>
<div class="col-12 col-md-7">
<table class="table">
<thead>
<tr>
<th scope="col">{{_("Last used")}}</th>
<th scope="col">{{_("Device")}}</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr>
<td>{{_("Just now")}}</td>
<td>{{ request.session.user_agent_browser }} on {{ request.session.user_agent_platform }} ({{ request.session.ip_address }})</td>
<td></td>
</tr>
{% for session in user.sessions|sort(attribute='last_used', reverse=True) if not session.expired and session != request.session %}
<tr>
<td>
{% set last_used_rel = session.last_used - datetime.utcnow() %}
{% if -last_used_rel.total_seconds() <= 60 %}
{{_("Just now")}}
{% else %}
{{ last_used_rel|timedeltaformat(add_direction=True, granularity='minute') }}
{% endif %}
</td>
<td>{{ session.user_agent_browser }} on {{ session.user_agent_platform }} ({{ session.ip_address }})</td>
<td>
{% if session != request.session %}
<form action="{{ url_for("selfservice.revoke_session", session_id=session.id) }}" method="POST" onsubmit='return confirm({{_("Are you sure?")|tojson|e}});'>
<button type="submit" class="btn btn-sm btn-danger float-right">{{_("Revoke")}}</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
</div> </div>
...@@ -88,15 +227,12 @@ ...@@ -88,15 +227,12 @@
<h5>{{_("Roles")}}</h5> <h5>{{_("Roles")}}</h5>
<p>{{_("Aside from a set of base permissions, your roles determine the permissions of your account.")}}</p> <p>{{_("Aside from a set of base permissions, your roles determine the permissions of your account.")}}</p>
{% if config['SERVICES'] %} {% if config['SERVICES'] %}
<p>{{_("See <a href=\"%(services_url)s\">Services</a> for an overview of your current permissions.", services_url=url_for('services.index'))}}</p> <p>{{_("See <a href=\"%(services_url)s\">Services</a> for an overview of your current permissions.", services_url=url_for('service.overview'))}}</p>
{% endif %} {% endif %}
</div> </div>
<div class="col-12 col-md-7"> <div class="col-12 col-md-7">
{% if config['ENABLE_INVITE'] %}
<p>{{_("Administrators and role moderators can invite you to new roles.")}}</p> <p>{{_("Administrators and role moderators can invite you to new roles.")}}</p>
{% else %}
<p>{{_("Administrators can add new roles to your account.")}}</p>
{% endif %}
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
...@@ -115,11 +251,9 @@ ...@@ -115,11 +251,9 @@
</td> </td>
<td>{{ role.description }}</td> <td>{{ role.description }}</td>
<td> <td>
{% if config['ENABLE_ROLESELFSERVICE'] %}
<form action="{{ url_for("selfservice.leave_role", roleid=role.id) }}" method="POST" onsubmit='return confirm({{_("Are you sure?")|tojson|e}});'> <form action="{{ url_for("selfservice.leave_role", roleid=role.id) }}" method="POST" onsubmit='return confirm({{_("Are you sure?")|tojson|e}});'>
<button type="submit" class="btn btn-sm btn-danger float-right">{{_("Leave")}}</button> <button type="submit" class="btn btn-sm btn-danger float-right">{{_("Leave")}}</button>
</form> </form>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -130,7 +264,20 @@ ...@@ -130,7 +264,20 @@
{% endif %} {% endif %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<script>
$(".showmore").each(function () {
$(this).show()
$($(this).data("target")).hide()
})
$(".showmore").on("click", function () {
$(this).slideUp(200)
$(this).prop("ariaExpanded", true)
$($(this).data("target")).slideDown()
})
</script>
{% endblock %} {% endblock %}
{% extends 'base_narrow.html' %}
{% block body %}
<form action="{{ url_for("selfservice.token_password", token_id=token.id, token=token.token) }}" method="POST" onInput="password2.setCustomValidity(password1.value != password2.value ? 'Passwords do not match.' : '') ">
<div class="col-12">
<h2 class="text-center">{{_("Reset password")}}</h2>
</div>
<div class="form-group col-12">
<label for="user-password1">{{_("New Password")}}</label>
<input type="password" autocomplete="new-password" class="form-control" id="user-password1" name="password1" tabindex="2" minlength={{ User.PASSWORD_MINLEN }} maxlength={{ User.PASSWORD_MAXLEN }} pattern="{{ User.PASSWORD_REGEX }}" required>
<small class="form-text text-muted">
{{ User.PASSWORD_DESCRIPTION|safe }}
</small>
</div>
<div class="form-group col-12">
<label for="user-password2">{{_("Repeat Password")}}</label>
<input type="password" autocomplete="new-password" class="form-control" id="user-password2" name="password2" tabindex="3" required>
</div>
<div class="form-group col-12">
<button type="submit" class="btn btn-primary btn-block" tabindex="3">{{_("Set password")}}</button>
</div>
</form>
{% endblock %}