feat: add Daedalus API token management to profile settings
All checks were successful
CVE Scan & Docker Build / security-scan (push) Successful in 49s
CVE Scan & Docker Build / build-and-push (push) Successful in 2m32s

- Display the user's DRF auth token on the profile settings page
- Add copy-to-clipboard button for easy token retrieval
- Add token regeneration endpoint with confirmation prompt
- Auto-create token on first visit via get_or_create
- Instruct users to set DAEDALUS_MNEMOSYNE_API_KEY in Daedalus env
This commit is contained in:
2026-05-12 06:29:20 -04:00
parent 4f77ed39b9
commit 955761b748
3 changed files with 42 additions and 1 deletions

View File

@@ -140,5 +140,32 @@
<button type="submit" class="btn btn-primary">Save Settings</button>
</div>
</form>
<!-- Daedalus API Token — separate form, outside the settings form -->
<div class="card bg-base-200 mb-6">
<div class="card-body">
<h2 class="card-title text-lg">Daedalus API Token</h2>
<p class="text-sm opacity-70 mb-4">
Used by Daedalus to authenticate with Mnemosyne. Set
<code class="font-mono text-xs">DAEDALUS_MNEMOSYNE_API_KEY</code>
in your Daedalus environment to this value.
</p>
<div class="flex items-center gap-3">
<code class="font-mono bg-base-300 px-3 py-2 rounded flex-1 break-all select-all text-sm">{{ api_token.key }}</code>
<button type="button"
class="btn btn-ghost btn-sm"
onclick="navigator.clipboard.writeText('{{ api_token.key }}').then(() => { this.textContent = 'Copied!'; setTimeout(() => this.textContent = 'Copy', 2000); }).catch(() => {})">
Copy
</button>
</div>
<div class="mt-3">
<form method="post" action="{% url 'themis:api-token-regenerate' %}"
onsubmit="return confirm('Regenerate token? Daedalus will stop working until you update DAEDALUS_MNEMOSYNE_API_KEY.')">
{% csrf_token %}
<button type="submit" class="btn btn-warning btn-sm">Regenerate</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -13,6 +13,7 @@ urlpatterns = [
path("live/", views.live, name="live"),
# Profile settings
path("profile/settings/", views.profile_settings, name="profile-settings"),
path("profile/api-token/regenerate/", views.api_token_regenerate, name="api-token-regenerate"),
# API key management
path("profile/keys/", views.key_list, name="key-list"),
path("profile/keys/add/", views.key_create, name="key-create"),

View File

@@ -12,6 +12,8 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views.decorators.http import require_GET, require_http_methods, require_POST
from rest_framework.authtoken.models import Token
from themis.encryption import encrypt_value
from themis.forms import APIKeyCreateForm, APIKeyEditForm, ProfileSettingsForm
from themis.models import UserAPIKey, UserNotification
@@ -63,6 +65,7 @@ def live(request):
def profile_settings(request):
"""Display and update user profile preferences."""
profile = request.user.profile
api_token, _ = Token.objects.get_or_create(user=request.user)
if request.method == "POST":
form = ProfileSettingsForm(request.POST, instance=profile)
@@ -73,7 +76,17 @@ def profile_settings(request):
else:
form = ProfileSettingsForm(instance=profile)
return render(request, "themis/profile/settings.html", {"form": form})
return render(request, "themis/profile/settings.html", {"form": form, "api_token": api_token})
@login_required
@require_POST
def api_token_regenerate(request):
"""Delete and recreate the user's DRF API token."""
Token.objects.filter(user=request.user).delete()
Token.objects.create(user=request.user)
messages.success(request, "API token regenerated.")
return redirect("themis:profile-settings")
# ---------------------------------------------------------------------------