Skip to content
Snippets Groups Projects
Commit d0ff3930 authored by Julian's avatar Julian
Browse files

Refactored invite page and increased invite/signup token entropy

parent b7454ffa
No related branches found
No related tags found
No related merge requests found
...@@ -19,7 +19,7 @@ invite_roles = db.Table('invite_roles', ...@@ -19,7 +19,7 @@ invite_roles = db.Table('invite_roles',
class Invite(db.Model): class Invite(db.Model):
__tablename__ = 'invite' __tablename__ = 'invite'
id = Column(Integer(), primary_key=True, autoincrement=True) id = Column(Integer(), primary_key=True, autoincrement=True)
token = Column(String(128), unique=True, nullable=False, default=lambda: secrets.token_hex(20)) token = Column(String(128), unique=True, nullable=False, default=lambda: secrets.token_urlsafe(32))
created = Column(DateTime, default=datetime.datetime.now, nullable=False) created = Column(DateTime, default=datetime.datetime.now, nullable=False)
creator_dn = Column(String(128), nullable=True) creator_dn = Column(String(128), nullable=True)
creator = DBRelationship('creator_dn', User) creator = DBRelationship('creator_dn', User)
......
...@@ -10,11 +10,10 @@ ...@@ -10,11 +10,10 @@
<thead> <thead>
<tr> <tr>
<th scope="col">Link</th> <th scope="col">Link</th>
<th scope="col">Status</th> <th scope="col">Created by</th>
<th scope="col">Created on</th>
<th scope="col">Expires after</th>
<th scope="col">Permissions</th> <th scope="col">Permissions</th>
<th scope="col">Usages</th> <th scope="col">Usages</th>
<th scope="col">Status</th>
<th scope="col"></th> <th scope="col"></th>
</tr> </tr>
</thead> </thead>
...@@ -22,12 +21,31 @@ ...@@ -22,12 +21,31 @@
{% for invite in invites|sort(attribute='created', reverse=True)|sort(attribute='active', reverse=True) %} {% for invite in invites|sort(attribute='created', reverse=True)|sort(attribute='active', reverse=True) %}
<tr> <tr>
<td> <td>
{% if invite.creator == get_current_user() %} {% if invite.creator == get_current_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', 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" 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>
{% if not invite.creator_dn %}
{{ '<admin>' }}
{% elif not invite.creator %}
{{ '<deleted user>' }}
{% else %}
{{ invite.creator.loginname }}
{% endif %}
</td>
<td>
{{ 'Signup' if invite.allow_signup }}{{ ', ' if invite.allow_signup and invite.roles }}
{% for role in invite.roles %}{{ ', ' if loop.index != 1 }}<i class="fas fa-key"></i>&thinsp;{{ role.name }}{% endfor %}
</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.grants|length }} <i class="fas fa-key" title="role grants"></i></span>
</td>
<td> <td>
{% if invite.disabled %} {% if invite.disabled %}
Disabled Disabled
...@@ -35,36 +53,18 @@ ...@@ -35,36 +53,18 @@
Voided Voided
{% elif invite.expired %} {% elif invite.expired %}
Expired Expired
{% elif not invite.permitted %}
Invalid, unpermitted creator
{% elif not invite.active %} {% elif not invite.active %}
Invalid Invalid
{% elif invite.single_use %} {% elif invite.single_use %}
Valid once Valid once, expires {{ invite.valid_until.strftime('%Y-%m-%d') }}
{% else %} {% else %}
Valid Valid, expires {{ invite.valid_until.strftime('%Y-%m-%d') }}
{% endif %} {% endif %}
</td> </td>
<td>{{ invite.created.strftime('%Y-%m-%d %H:%M') }}</td>
<td>{{ invite.valid_until.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
{{ 'Signup' if invite.allow_signup }}{{ ', ' if invite.allow_signup and invite.roles }}
{% for role in invite.roles %}{{ ', ' if loop.index != 1 }}<a href="{{ url_for('role.show', roleid=role.id) }}" style="white-space: nowrap;"><i class="fas fa-key"></i>&thinsp;{{ role.name }}</a>{% endfor %}
</td>
<td>
<a href="#" data-toggle="modal" data-target="#modal-{{ invite.id }}">
<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.grants|length }} <i class="fas fa-key" title="role grants"></i></span>
</a>
</td>
<td class="text-right"> <td class="text-right">
{% if invite.active %} <button type="button" class="btn btn-link btn-sm p-0" data-toggle="modal" data-target="#modal-{{ invite.id }}"><i class="fas fa-ellipsis-h"></i></button>
<form action="{{ url_for('invite.disable', invite_id=invite.id) }}" method="POST">
<button type="submit" class="btn btn-link btn-sm py-0" title="Disable"><i class="fas fa-ban" style="width: 1.5em;"></i></button>
</form>
{% elif invite.creator == get_current_user() and not invite.expired and invite.permitted %}
<form action="{{ url_for('invite.reset', invite_id=invite.id) }}" method="POST">
<button type="submit" class="btn btn-link btn-sm py-0" title="Reenable"><i class="fas fa-redo" style="width: 1.5em;"></i></button>
</form>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -77,12 +77,32 @@ ...@@ -77,12 +77,32 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Invite Usages</h5> <h5 class="modal-title">Invite Link Details</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<ul class="list-unstyled">
<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>Expires:</b> {{ invite.valid_until.strftime('%Y-%m-%d %H:%M:%S') }}</li>
<li><b>Permissions:</b>
<ul>
{% if invite.signup %}
<li>Link allows account registration</li>
{% else %}
<li>No account registration allowed</li>
{% endif %}
{% for role in invite.roles %}
<li>Link grants users the role "{{ role.name }}"</li>
{% endfor %}
</ul>
</li>
<li><b>Usages:</b>
{% if not invite.signups and not invite.grants %}
Never used
{% 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="{{ url_for('user.show', uid=signup.user.uid) }}">{{ signup.user.loginname }}</a></li> <li>Registration of user <a href="{{ url_for('user.show', uid=signup.user.uid) }}">{{ signup.user.loginname }}</a></li>
...@@ -91,13 +111,48 @@ ...@@ -91,13 +111,48 @@
<li>Roles granted to <a href="{{ url_for('user.show', uid=grant.user.uid) }}">{{ grant.user.loginname }}</a></li> <li>Roles granted to <a href="{{ url_for('user.show', uid=grant.user.uid) }}">{{ grant.user.loginname }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %}
</li>
</ul>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
{% if invite.active %}
<form action="{{ url_for('invite.disable', invite_id=invite.id) }}" method="POST">
<button type="submit" class="btn btn-primary">Disable Link</button>
</form>
{% elif invite.creator == get_current_user() and not invite.expired and invite.permitted %}
<form action="{{ url_for('invite.reset', invite_id=invite.id) }}" method="POST">
<button type="submit" class="btn btn-primary">Reenable Link</button>
</form>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{% for invite in invites if invite.creator == get_current_user() %}
<div class="modal" tabindex="-1" id="modal-{{ invite.id }}-qrcode">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Invite</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{{ url_for('invite.use', token=invite.token, _external=True)|qrcode_svg(width='100%', height='100%') }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<script>
$(".copy-clipboard").on("click", function() {
navigator.clipboard.writeText($(this).data("copy"));
});
</script>
{% endblock %} {% endblock %}
...@@ -28,7 +28,7 @@ class Signup(db.Model): ...@@ -28,7 +28,7 @@ class Signup(db.Model):
As long as they are not completed, signup requests have no effect each other As long as they are not completed, signup requests have no effect each other
or different parts of the application.''' or different parts of the application.'''
__tablename__ = 'signup' __tablename__ = 'signup'
token = Column(String(128), primary_key=True, default=lambda: secrets.token_hex(20)) token = Column(String(128), primary_key=True, default=lambda: secrets.token_urlsafe(32))
created = Column(DateTime, default=datetime.datetime.now, nullable=False) created = Column(DateTime, default=datetime.datetime.now, nullable=False)
loginname = Column(Text) loginname = Column(Text)
displayname = Column(Text) displayname = Column(Text)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment