1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-10 20:26:48 +00:00
webvirtcloud/accounts/templates/profile.html
catborise 6b51c30dd6 feat(accounts): API tokens show their last use, and a user has at most ten
The API Tokens tab gets a "Last used" column, so an unused token can be
told from one a script still needs. The token's last use is written when
it authenticates, at most once a minute (accounts.auth.TokenAuthentication,
knox's class with that one addition; the API schema describes it as
knox's).

A user has at most ten tokens; a new one is refused until one is
deleted. Expired tokens do not count, and are deleted when a new token is
made.
2026-10-25 10:16:52 +03:00

149 lines
No EOL
6.8 KiB
HTML

{% extends "base.html" %}
{% load i18n %}
{% load django_bootstrap5 %}
{% load bootstrap_icons %}
{% load tags_fingerprint %}
{% block title %}{% trans "Profile" %}: {{ request.user.first_name }} {{ request.user.last_name}}{% endblock %}
{% block page_heading %}{% trans "Profile" %}: {{ request.user.first_name }} {{ request.user.last_name}}{% endblock page_heading %}
{% block content %}
<ul class="nav nav-tabs">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#editprofile" type="button" role="tab" aria-controls="editprofile" aria-selected="true">
{% trans "Edit Profile" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#sshkeys" type="button" role="tab" aria-controls="sshkeys" aria-selected="false">
{% trans "SSH Keys" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#apitokens" type="button" role="tab" aria-controls="apitokens" aria-selected="false">
{% trans "API Tokens" %}
</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane tab-pane-bordered active" id="editprofile">
<div class="card">
<div class="card-body">
<form method="post" action="" role="form" aria-label="Edit user info form">
{% csrf_token %}
{% bootstrap_form profile_form layout='horizontal' %}
{% if perms.accounts.change_password %}
<a href="{% url 'accounts:change_password' %}" class="btn btn-primary">
{% bs_icon 'lock' %} {% trans "Change Password" %}
</a>
{% endif %}
<div class="mb-0 float-end">
<button type="submit" class="btn btn-primary">
{% bs_icon 'pencil' %} {% trans "Update" %}
</button>
</div>
</form>
</div>
</div>
</div>
<div class="tab-pane tab-pane-bordered fade" id="sshkeys">
{% if publickeys %}
<div class="col-lg-12">
<div class="table-responsive">
<table class="table table-hover">
<tbody class="text-center">
{% for key in publickeys %}
<tr>
<td>{{ key.keyname }} ({% ssh_to_fingerprint key.keypublic %})</td>
<td>
<a href="{% url 'accounts:ssh_key_delete' key.id %}" title="{% trans "Delete" %}" class="btn btn-sm btn-secondary">
{% bs_icon 'trash' %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<div class="card">
<div class="card-header">
{%trans "Add SSH Key" %}
</div>
<div class="card-body">
<form method="post" action="{% url 'accounts:ssh_key_create' %}" role="form" aria-label="Add key to user form">
{% csrf_token %}
{% bootstrap_form ssh_key_form layout='horizontal' %}
<div class="mb-0 float-end">
<button type="submit" class="btn btn-primary">
{% bs_icon 'plus-circle-fill' %} {% trans "Add" %}
</button>
</div>
</form>
</div>
</div>
</div>
<div class="tab-pane tab-pane-bordered fade" id="apitokens">
<p class="text-body-secondary">{% blocktrans %}A token lets a script or a tool use the REST API as you, without your password or OTP: send it as <code>Authorization: Token &lt;token&gt;</code>. A password change does not delete tokens: delete a token you no longer use, or one that may have leaked.{% endblocktrans %}</p>
{% if api_tokens %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Starts with" %}</th>
<th scope="col">{% trans "Created" %}</th>
<th scope="col">{% trans "Expires" %}</th>
<th scope="col">{% trans "Last used" %}</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for token in api_tokens %}
<tr>
<td>{{ token.name }}</td>
<td><code>{{ token.token_key }}</code></td>
<td>{{ token.created|date:"Y-m-d H:i" }}</td>
<td>{% if token.expiry %}{{ token.expiry|date:"Y-m-d H:i" }}{% else %}{% trans "never" %}{% endif %}</td>
<td>{% if token.last_used %}{{ token.last_used|date:"Y-m-d H:i" }}{% else %}{% trans "never" %}{% endif %}</td>
<td class="text-end">
<a href="{% url 'accounts:api_token_delete' token.token_key %}" title="{% trans "Delete" %}" class="btn btn-sm btn-secondary">
{% bs_icon 'trash' %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<div class="card">
<div class="card-header">
{% trans "Create an API token" %}
</div>
<div class="card-body">
<form method="post" action="{% url 'accounts:api_token_create' %}" role="form" aria-label="{% trans "Create an API token" %}">
{% csrf_token %}
{% bootstrap_form api_token_form layout='horizontal' %}
<div class="mb-0 float-end">
<button type="submit" class="btn btn-primary">
{% bs_icon 'plus-circle-fill' %} {% trans "Create" %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script>
// a link to a tab (#apitokens) opens it
var linked = location.hash.length > 1 && document.querySelector('[data-bs-target="#' + CSS.escape(location.hash.slice(1)) + '"]');
if (linked) {
bootstrap.Tab.getOrCreateInstance(linked).show();
}
</script>
{% endblock %}