Files
mnemosyne/mnemosyne/mcp_server/admin.py
Robert Helewka 81426327bf feat(mcp): store MCP tokens as SHA-256 hashes instead of plaintext
Replace plaintext token storage with SHA-256 hashes so leaked database
contents cannot be used to authenticate. Plaintext is generated, shown
once at creation time, and never persisted.

- Add `hash_token()` helper and `MCPTokenManager.create_token()` that
  returns `(instance, plaintext)`.
- Replace `token` field with indexed `token_hash`; look up bearers by
  hashing the incoming value.
- Update dashboard, management command, and admin to surface plaintext
  only at creation. Disable admin "add" since it cannot reveal plaintext.
- Migration drops the old `token` column and adds `token_hash`;
  pre-existing tokens are invalidated and must be reissued.
2026-04-27 09:01:36 -04:00

39 lines
1.2 KiB
Python

from django.contrib import admin
from .models import MCPToken
@admin.register(MCPToken)
class MCPTokenAdmin(admin.ModelAdmin):
list_display = [
"name",
"user",
"is_active",
"masked_token",
"expires_at",
"last_used_at",
"created_at",
]
list_filter = ["is_active"]
search_fields = ["name", "user__email", "user__username"]
readonly_fields = ["token_hash", "last_used_at", "created_at", "updated_at"]
fieldsets = (
(None, {"fields": ("user", "name", "is_active")}),
("Restrictions", {"fields": ("allowed_tools", "expires_at")}),
(
"Token (hashed at rest — plaintext is shown only once at creation)",
{"fields": ("token_hash",)},
),
("Audit", {"fields": ("last_used_at", "created_at", "updated_at")}),
)
@admin.display(description="Token")
def masked_token(self, obj):
return obj.get_masked_token()
def has_add_permission(self, request):
# Tokens must be created via the dashboard or management command
# so the plaintext can be surfaced to the user. Adding via admin
# would persist a hash with no plaintext ever shown.
return False