sub-aligator/templates/tokens.html
SW 8c69c1731d feat: JSON REST API /api/v1 with DB-managed Bearer tokens
- New endpoints: GET /api/v1/pools, GET /api/v1/pools/{slug}/next,
  GET/POST /api/v1/allocations, GET /api/v1/allocations/{id},
  POST /api/v1/allocations/{id}/release
- Bearer token auth; only SHA-256 hashes stored, plaintext shown once
- Token management GUI at /tokens (create, revoke, delete) linked in footer
- API actions audited with actor api:<token name>
- Fix latent transaction leak: replace manual BEGIN IMMEDIATE/COMMIT with
  begin_with(), which rolls back on drop; previously a failed allocation
  (e.g. disallowed prefix or full pool) returned a connection with an open
  transaction to the pool, causing 'database is locked' on later writes
- README: API documentation with curl examples
2026-10-09 14:25:17 +02:00

120 lines
4.5 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}API tokens | SUB-ALIGATOR{% endblock %}
{% block content %}
{% if has_new_token %}
<section class="box">
<div class="box-title">Token created – copy it now</div>
<div class="message success">
<p>The new API token <strong>{{ new_token_name }}</strong> was created. Copy it now – <strong>it will not be shown again</strong>. Only its hash is stored.</p>
<input type="text" readonly value="{{ new_token_value }}" style="font-family: inherit;">
<p class="small" style="margin-top: 0.5rem;">Use it as: <code>Authorization: Bearer {{ new_token_value }}</code></p>
</div>
</section>
{% endif %}
<section class="box">
<div class="box-title">API tokens</div>
{% if error != "" %}
<div class="message error">
<p>{{ error }}</p>
</div>
{% endif %}
<p class="small">Tokens authenticate the JSON REST API under <code>/api/v1/</code> via the <code>Authorization: Bearer</code> header. See the README for endpoint documentation.</p>
{% if tokens.len() == 0 %}
<p>No API tokens yet.</p>
{% else %}
<table>
<thead>
<tr>
<th>Name</th>
<th>Token</th>
<th>Status</th>
<th>Created</th>
<th>Last used</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for t in tokens %}
<tr>
<td data-label="Name">{{ t.name }}</td>
<td data-label="Token"><code>{{ t.token_prefix }}…</code></td>
<td data-label="Status">
{% if t.enabled %}
<span class="status active">active</span>
{% else %}
<span class="status released">revoked</span>
{% endif %}
</td>
<td data-label="Created">{{ t.created_at_local }} ({{ t.created_by }})</td>
<td data-label="Last used">{{ t.last_used_at_local }}</td>
<td data-label="Actions">
{% if t.enabled %}
<button type="button" class="btn btn-danger" data-modal-open="revoke-token-modal-{{ t.id }}">Revoke</button>
{% else %}
<button type="button" class="btn btn-danger" data-modal-open="delete-token-modal-{{ t.id }}">Delete</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</section>
<section class="box">
<div class="box-title">Create new token</div>
<form method="post" action="/tokens/new">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label for="name">Token name *</label>
<input type="text" id="name" name="name" required minlength="2" maxlength="100" placeholder="e.g. monitoring-integration">
<p class="small">A descriptive name of the integration that will use this token. It appears in the audit log as actor <code>api:&lt;name&gt;</code>.</p>
<div style="margin-top: 1rem;">
<button type="submit" class="btn">Create token</button>
<a href="/" class="btn btn-secondary">Back</a>
</div>
</form>
</section>
{% for t in tokens %}
{% if t.enabled %}
<div id="revoke-token-modal-{{ t.id }}" class="modal-overlay" style="display:none;">
<div class="modal">
<h2 class="modal-title">Revoke API token</h2>
<form method="post" action="/tokens/{{ t.id }}/revoke" class="modal-body">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<p>Token: <strong>{{ t.name }}</strong> (<code>{{ t.token_prefix }}…</code>)</p>
<p>The token will stop working immediately. The record is kept for audit purposes.</p>
<div class="modal-actions">
<button type="button" class="btn btn-secondary cancel-btn" data-modal="revoke-token-modal-{{ t.id }}">Cancel</button>
<button type="submit" class="btn btn-danger">Revoke token</button>
</div>
</form>
</div>
</div>
{% else %}
<div id="delete-token-modal-{{ t.id }}" class="modal-overlay" style="display:none;">
<div class="modal">
<h2 class="modal-title">Delete API token</h2>
<form method="post" action="/tokens/{{ t.id }}/delete" class="modal-body">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<p>Token: <strong>{{ t.name }}</strong> (<code>{{ t.token_prefix }}…</code>)</p>
<p>The revoked token record will be permanently deleted. This cannot be undone.</p>
<div class="modal-actions">
<button type="button" class="btn btn-secondary cancel-btn" data-modal="delete-token-modal-{{ t.id }}">Cancel</button>
<button type="submit" class="btn btn-danger">Delete token</button>
</div>
</form>
</div>
</div>
{% endif %}
{% endfor %}
{% endblock %}