- Rename MCPToken to UserToken across models, views, and tests - Update URL names from mcp-token-* to token-* - Add Daedalus/Pallas integration design doc (v2) - Switch docker-compose to build local mnemosyne:local image via shared build config instead of pulling from git.helu.ca
100 lines
3.6 KiB
Python
100 lines
3.6 KiB
Python
"""Self-service dashboard for per-user API tokens.
|
|
|
|
Mirrors the Themis API-keys flow visually but stores hashed tokens.
|
|
Plaintext is shown to the user exactly once (on the create-success
|
|
page) and never persisted.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from django.contrib import messages
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import HttpRequest, HttpResponse
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.views.decorators.http import require_GET, require_http_methods, require_POST
|
|
|
|
from .forms import UserTokenCreateForm, UserTokenEditForm
|
|
from .models import UserToken
|
|
|
|
|
|
@login_required
|
|
@require_GET
|
|
def token_list(request: HttpRequest) -> HttpResponse:
|
|
tokens = UserToken.objects.filter(user=request.user).order_by("-created_at")
|
|
return render(request, "mcp_server/tokens/list.html", {"tokens": tokens})
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["GET", "POST"])
|
|
def token_create(request: HttpRequest) -> HttpResponse:
|
|
if request.method == "POST":
|
|
form = UserTokenCreateForm(request.POST, user=request.user)
|
|
if form.is_valid():
|
|
token, plaintext = UserToken.objects.create_token(
|
|
user=request.user,
|
|
name=form.cleaned_data["name"],
|
|
allowed_tools=form.cleaned_data.get("allowed_tools") or [],
|
|
allowed_libraries=form.cleaned_data.get("allowed_libraries") or [],
|
|
expires_at=form.cleaned_data.get("expires_at") or None,
|
|
)
|
|
return render(
|
|
request,
|
|
"mcp_server/tokens/created.html",
|
|
{"token": token, "plaintext": plaintext},
|
|
)
|
|
else:
|
|
form = UserTokenCreateForm(user=request.user)
|
|
|
|
return render(request, "mcp_server/tokens/create.html", {"form": form})
|
|
|
|
|
|
@login_required
|
|
@require_GET
|
|
def token_detail(request: HttpRequest, pk: int) -> HttpResponse:
|
|
token = get_object_or_404(UserToken, pk=pk, user=request.user)
|
|
return render(request, "mcp_server/tokens/detail.html", {"token": token})
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["GET", "POST"])
|
|
def token_edit(request: HttpRequest, pk: int) -> HttpResponse:
|
|
token = get_object_or_404(UserToken, pk=pk, user=request.user)
|
|
|
|
if request.method == "POST":
|
|
form = UserTokenEditForm(request.POST, instance=token, user=request.user)
|
|
if form.is_valid():
|
|
instance = form.save(commit=False)
|
|
instance.allowed_tools = form.cleaned_data.get("allowed_tools") or []
|
|
instance.allowed_libraries = (
|
|
form.cleaned_data.get("allowed_libraries") or []
|
|
)
|
|
instance.save()
|
|
messages.success(request, "Token updated.")
|
|
return redirect("mcp_server:token-detail", pk=token.pk)
|
|
else:
|
|
form = UserTokenEditForm(instance=token, user=request.user)
|
|
|
|
return render(
|
|
request, "mcp_server/tokens/edit.html", {"form": form, "token": token}
|
|
)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def token_revoke(request: HttpRequest, pk: int) -> HttpResponse:
|
|
token = get_object_or_404(UserToken, pk=pk, user=request.user)
|
|
token.is_active = False
|
|
token.save(update_fields=["is_active", "updated_at"])
|
|
messages.success(request, f"Revoked “{token.name}”. The token can no longer be used.")
|
|
return redirect("mcp_server:token-detail", pk=token.pk)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def token_delete(request: HttpRequest, pk: int) -> HttpResponse:
|
|
token = get_object_or_404(UserToken, pk=pk, user=request.user)
|
|
name = token.name
|
|
token.delete()
|
|
messages.success(request, f"Deleted “{name}”.")
|
|
return redirect("mcp_server:token-list")
|