feat(2-Schritt2): Login-Link + persistentes is_admin-Flag pro Nutzer

- Nutzerverwaltung zeigt beim Anlegen/„Link erneuern" jetzt den vollen
  Ein-Klick-Link (location.origin/?k=<token>) statt nur des Tokens.
- Neuer Admin-Schalter pro Nutzer (DB-Spalte users.is_admin + Migration).
  is_admin_user() honoriert das Flag ODER ADMIN_USERS (SSO). Dadurch zeigt
  /me den Admin-Button anhand des Flags — Grundlage für den nginx-Cutover.
- admin.py: PUT /admin/users/{id}/admin; list_users liefert is_admin.

Migration live verifiziert (Spalte vorhanden, bestehende Admins via
ADMIN_USERS weiterhin korrekt). app.js v=45. 219 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-25 04:33:33 +02:00
commit bd55807a1c
8 changed files with 94 additions and 9 deletions

View file

@ -17,7 +17,7 @@ from app.auth import is_admin_user, require_admin, require_admin_or_user
from app.config import settings
from app.dependencies import get_store
from app.runtime_config import RUNTIME_SETTABLE, invalidate_cache, runtime_settings
from app.schemas import AdminUserPrefsUpdate, MemoryCreate, MemoryOut, UserCreate, UserCreated, UserUpdate
from app.schemas import AdminUserPrefsUpdate, MemoryCreate, MemoryOut, UserCreate, UserCreated, UserUpdate, UserAdminUpdate
router = APIRouter()
@ -94,6 +94,15 @@ async def reset_token(user_id: str):
return UserCreated(user_id=user.id, display_name=user.display_name, token=token)
@router.put("/admin/users/{user_id}/admin", dependencies=[Depends(require_admin)])
async def set_user_admin(user_id: str, payload: UserAdminUpdate):
"""Schaltet Admin-Rechte (persistentes DB-Flag) für einen Nutzer ein/aus."""
user = get_store().set_user_admin(user_id, payload.is_admin)
if user is None:
raise HTTPException(status_code=404, detail=f"Nutzer {user_id!r} nicht gefunden.")
return {"user_id": user.id, "is_admin": user.is_admin}
@router.put("/admin/users/{user_id}/prefs", dependencies=[Depends(require_admin)])
async def update_user_prefs(user_id: str, payload: AdminUserPrefsUpdate):
"""Setzt Nutzer-Einstellungen (z. B. erlaubte Sprachen). Merge mit bestehenden Prefs."""
@ -135,6 +144,7 @@ async def list_users():
"external_id": u.external_id,
"created_at": u.created_at,
"allowed_languages": (u.prefs or {}).get("allowed_languages", ""),
"is_admin": is_admin_user(u),
}
for u in get_store().list_users()
]