mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-07-04 20:11:45 +02:00
4273c541c5
- Personal API tokens (model, user-settings UI, admin, management command, DRF auth class) for non-interactive API access from automations like n8n. Raw token shown once; only a SHA-256 hash is stored; last_used_at writes are throttled. - OAuth2 authorization server via django-oauth-toolkit with authorization server metadata and optional, off-by-default Dynamic Client Registration (RFC 7591), so remote OAuth/MCP clients can authenticate and self-register. - Tests for token auth, DCR gating and the management commands, plus .env.example and README documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
4.0 KiB
HTML
102 lines
4.0 KiB
HTML
{% load crispy_forms_tags %}
|
|
{% load i18n %}
|
|
|
|
<div class="mb-3">
|
|
<div class="text-lg font-bold font-mono">{% translate "API Tokens" %}</div>
|
|
<p class="text-sm opacity-70">
|
|
{% translate "Use these tokens for automations such as n8n. The raw token is shown only once after creation." %}
|
|
</p>
|
|
</div>
|
|
|
|
{% if raw_token %}
|
|
<div role="alert" class="alert alert-warning mb-4">
|
|
<div class="w-full">
|
|
<div class="font-semibold mb-1">{% translate "Copy this token now" %}</div>
|
|
<p class="text-sm opacity-80 mb-3">
|
|
{% translate "It will not be shown again after this response." %}
|
|
</p>
|
|
<div class="join w-full">
|
|
<input id="raw-token-value"
|
|
type="text"
|
|
readonly
|
|
value="{{ raw_token }}"
|
|
class="input input-sm join-item w-full font-mono text-xs"
|
|
_="on focus call me.select()" />
|
|
<button type="button"
|
|
class="btn btn-sm btn-primary join-item"
|
|
_="on click call navigator.clipboard.writeText('{{ raw_token|escapejs }}')
|
|
then put 'Copied!' into me
|
|
then wait 1.5s
|
|
then put 'Copy' into me">
|
|
{% translate "Copy" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<form hx-post="{% url 'user_api_token_add' %}" hx-target="#api-token-settings" hx-swap="innerHTML" novalidate>
|
|
{% crispy api_token_form %}
|
|
</form>
|
|
|
|
{% if api_tokens %}
|
|
<div class="overflow-x-auto mt-4">
|
|
<table class="table table-zebra">
|
|
<tbody>
|
|
{% for token in api_tokens %}
|
|
<tr>
|
|
<td>
|
|
<div class="flex items-center gap-2 flex-wrap">
|
|
<span class="font-medium font-mono">{{ token.name }}</span>
|
|
{% if token.revoked_at %}
|
|
<span class="badge badge-sm badge-ghost">{% translate "Revoked" %}</span>
|
|
{% else %}
|
|
<span class="badge badge-sm badge-success">{% translate "Active" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="text-xs opacity-60 font-mono break-all mt-1">{{ token.token_key }}</div>
|
|
<div class="text-xs opacity-60">
|
|
{% blocktranslate with created=token.created_at|date:"Y-m-d" %}Created {{ created }}{% endblocktranslate %}
|
|
·
|
|
{% if token.last_used_at %}
|
|
{% blocktranslate with used=token.last_used_at|date:"Y-m-d H:i" %}last used {{ used }}{% endblocktranslate %}
|
|
{% else %}
|
|
{% translate "never used" %}
|
|
{% endif %}
|
|
·
|
|
{% if token.expires_at %}
|
|
{% blocktranslate with expires=token.expires_at|date:"Y-m-d" %}expires {{ expires }}{% endblocktranslate %}
|
|
{% else %}
|
|
{% translate "no expiry" %}
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td class="table-col-auto text-right align-top">
|
|
{% if not token.revoked_at %}
|
|
<a class="btn btn-error btn-sm"
|
|
role="button"
|
|
data-tippy-content="{% translate 'Revoke' %}"
|
|
hx-delete="{% url 'user_api_token_revoke' token_id=token.id %}"
|
|
hx-target="#api-token-settings"
|
|
hx-swap="innerHTML"
|
|
hx-trigger="confirmed"
|
|
data-bypass-on-ctrl="true"
|
|
data-title="{% translate 'Revoke token?' %}"
|
|
data-text="{% translate 'This token will stop working immediately.' %}"
|
|
data-confirm-text="{% translate 'Yes, revoke it!' %}"
|
|
_="install prompt_swal">
|
|
<i class="fa-solid fa-ban fa-fw"></i>
|
|
</a>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="mt-4">
|
|
<c-msg.empty title="{% translate "No API tokens" %}" remove-padding></c-msg.empty>
|
|
</div>
|
|
{% endif %}
|