Let session owners reassign their session to another user

Previously a session could only be reassigned to another user via a
manual DB UPDATE. This adds it to the session edit UI for every owner of
a session (not just admins) - the ownership check in HandleUpdate
(existing.UserId == userId) already guarantees the caller owns the
session, so handing over one's own session is safe.

Backend:
- ProfileEndpoint: new GET /api/users returning a minimal user directory
  (userId/username/displayName only) for any logged-in user. The
  admin-gated /api/admin/users stays the source for sensitive fields
  (defaultPath/canUseCli/isAdmin).
- SessionEndpoint.HandleUpdate: validate the target user exists (400 if
  not) and pass allowUserReassign: true to UpdateSession.
- DatabaseService.UpdateSession: new allowUserReassign flag; only then is
  a non-empty userId applied as user_id (empty/null is ignored - a
  session must always have an owner).

Frontend:
- index.html: hidden #sessionOwnerRow with a user picker card in the
  session edit form (existing sessions only).
- cards.js: buildUserCard helper for the picker.
- session-page.js: initSessionUserPicker loads users via apiGetUsers(),
  preselects the current owner, and on an actual owner change asks for
  confirmation (irreversible - the session leaves the user's own list).
- api.js: apiGetUsers() -> /api/users.
- ARCHITECTURE.md: document /api/users.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MelekiandClaude Opus 4.8 committed 2026-06-13 11:52:53 +02:00
1 parent 483aa471a1
commit b23eba32b4
8 files changed
+155 -2

No files matched your search

+19
View File
@@ -1,3 +1,4 @@
using System.Linq;
using System.Text.Json;
using Gott.Extensions;
using Gott.Services;
@@ -14,6 +15,24 @@ public static class ProfileEndpoint
app.MapGet("/api/profile", HandleGet);
app.MapPut("/api/profile", HandleUpdate);
app.MapPut("/api/profile/password", HandlePasswordChange);
app.MapGet("/api/users", HandleListDirectory);
}
/// <summary>
/// Minimales User-Verzeichnis für jeden eingeloggten Nutzer (z. B. um eine Session
/// einem anderen Benutzer zuzuweisen). Gibt bewusst NUR id/username/displayName zurück –
/// keine sensiblen Felder wie defaultPath/canUseCli/isAdmin (dafür gibt es /api/admin/users).
/// </summary>
private static async System.Threading.Tasks.Task HandleListDirectory(HttpContext ctx, DatabaseService db)
{
var users = await db.GetUsers();
var result = users.Select(u => new
{
userId = u.UserId,
username = u.Username,
displayName = u.DisplayName
});
await ctx.Response.WriteAsJsonAsync(result);
}
private static async System.Threading.Tasks.Task HandleGet(HttpContext ctx, DatabaseService db)
+17 -1
View File
@@ -186,7 +186,23 @@ public static class SessionEndpoint
}
}
await db.UpdateSession(sessionId, body, imageService);
// Session einem anderen Benutzer zuweisen. Jeder Besitzer darf seine EIGENE Session
// übergeben – die Ownership-Prüfung oben (existing.UserId == userId) stellt bereits sicher,
// dass der Aufrufer der Besitzer ist. Der Ziel-User muss existieren.
if (body.TryGetProperty("userId", out var newUid)
&& newUid.ValueKind == JsonValueKind.String
&& !string.IsNullOrWhiteSpace(newUid.GetString()))
{
var targetUser = await db.GetUser(newUid.GetString());
if (targetUser == null)
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Zielbenutzer nicht gefunden" });
return;
}
}
await db.UpdateSession(sessionId, body, imageService, allowUserReassign: true);
var session = await db.GetSession(sessionId);
await ctx.Response.WriteAsJsonAsync(session);