feat: Login-as-User (Impersonate) + Default-Path pro User
Admin:
- POST /api/admin/users/{userId}/impersonate – erstellt Token für Zieluser
- Login-as-Button (fa-sign-in) in User-Liste (nicht für sich selbst)
- confirm-Dialog, danach location.reload() mit neuem User-Kontext
- Default-Path Feld + Browse-Button im Admin-Formular
Default-Path:
- Neues Feld default_path in users-Tabelle (TryAddColumn Migration)
- UserRecord.DefaultPath Property
- In allen Endpoints: Admin CRUD, Auth /me, Profile Get/Update
- Profil-Seite: Default-Path Feld + Browse-Button
- Session-Seite: Workspace-Browse nutzt defaultPath als Fallback
This commit is contained in:
1 parent
df68843e82
commit
d1355dcb0d
10 files changed
+144
-17
No files matched your search
@@ -17,6 +17,7 @@ public static class AdminEndpoint
|
||||
app.MapPut("/api/admin/users/{userId}", HandleUpdateUser);
|
||||
app.MapPut("/api/admin/users/{userId}/password", HandleResetPassword);
|
||||
app.MapDelete("/api/admin/users/{userId}", HandleDeleteUser);
|
||||
app.MapPost("/api/admin/users/{userId}/impersonate", HandleImpersonate);
|
||||
}
|
||||
|
||||
private static async System.Threading.Tasks.Task HandleListUsers(HttpContext ctx, DatabaseService db)
|
||||
@@ -35,6 +36,7 @@ public static class AdminEndpoint
|
||||
username = u.Username,
|
||||
displayName = u.DisplayName,
|
||||
isAdmin = u.IsAdmin,
|
||||
defaultPath = u.DefaultPath,
|
||||
createdAt = u.CreatedAt,
|
||||
updatedAt = u.UpdatedAt
|
||||
});
|
||||
@@ -56,6 +58,7 @@ public static class AdminEndpoint
|
||||
var password = body.TryGetProperty("password", out var p) ? p.GetString() : null;
|
||||
var displayName = body.TryGetProperty("displayName", out var dn) ? dn.GetString() : null;
|
||||
var isAdmin = body.TryGetProperty("isAdmin", out var ia) && ia.GetBoolean();
|
||||
var defaultPath = body.TryGetProperty("defaultPath", out var dp) ? dp.GetString() : null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
@@ -80,7 +83,7 @@ public static class AdminEndpoint
|
||||
}
|
||||
|
||||
var hash = auth.HashPassword(password);
|
||||
var user = await db.CreateUser(username, hash, displayName, isAdmin);
|
||||
var user = await db.CreateUser(username, hash, displayName, isAdmin, defaultPath);
|
||||
|
||||
ctx.Response.StatusCode = 201;
|
||||
await ctx.Response.WriteAsJsonAsync(new
|
||||
@@ -89,6 +92,7 @@ public static class AdminEndpoint
|
||||
username = user.Username,
|
||||
displayName = user.DisplayName,
|
||||
isAdmin = user.IsAdmin,
|
||||
defaultPath = user.DefaultPath,
|
||||
createdAt = user.CreatedAt
|
||||
});
|
||||
}
|
||||
@@ -114,6 +118,7 @@ public static class AdminEndpoint
|
||||
var username = body.TryGetProperty("username", out var u) ? u.GetString() : user.Username;
|
||||
var displayName = body.TryGetProperty("displayName", out var dn) ? dn.GetString() : user.DisplayName;
|
||||
var isAdmin = body.TryGetProperty("isAdmin", out var ia) ? ia.GetBoolean() : user.IsAdmin;
|
||||
var defaultPath = body.TryGetProperty("defaultPath", out var dp) ? dp.GetString() : user.DefaultPath;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
@@ -131,7 +136,7 @@ public static class AdminEndpoint
|
||||
return;
|
||||
}
|
||||
|
||||
await db.UpdateUserAdmin(userId, username, displayName, isAdmin);
|
||||
await db.UpdateUserAdmin(userId, username, displayName, isAdmin, defaultPath);
|
||||
var updated = await db.GetUser(userId);
|
||||
|
||||
await ctx.Response.WriteAsJsonAsync(new
|
||||
@@ -140,6 +145,7 @@ public static class AdminEndpoint
|
||||
username = updated.Username,
|
||||
displayName = updated.DisplayName,
|
||||
isAdmin = updated.IsAdmin,
|
||||
defaultPath = updated.DefaultPath,
|
||||
createdAt = updated.CreatedAt,
|
||||
updatedAt = updated.UpdatedAt
|
||||
});
|
||||
@@ -207,4 +213,41 @@ public static class AdminEndpoint
|
||||
await db.DeleteUser(userId);
|
||||
await ctx.Response.WriteAsJsonAsync(new { ok = true });
|
||||
}
|
||||
|
||||
private static async System.Threading.Tasks.Task HandleImpersonate(HttpContext ctx, DatabaseService db, AuthService auth, string userId)
|
||||
{
|
||||
if (!ctx.IsAdmin())
|
||||
{
|
||||
ctx.Response.StatusCode = 403;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" });
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await db.GetUser(userId);
|
||||
if (user == null)
|
||||
{
|
||||
ctx.Response.StatusCode = 404;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = "Benutzer nicht gefunden" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Token für den Zieluser erstellen und als Cookie setzen
|
||||
var token = auth.CreateToken(user.UserId);
|
||||
|
||||
ctx.Response.Cookies.Append("gott_token", token, new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Strict,
|
||||
Path = "/",
|
||||
MaxAge = System.TimeSpan.FromDays(365)
|
||||
});
|
||||
|
||||
await ctx.Response.WriteAsJsonAsync(new
|
||||
{
|
||||
userId = user.UserId,
|
||||
username = user.Username,
|
||||
displayName = user.DisplayName,
|
||||
isAdmin = user.IsAdmin
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,8 @@ public static class AuthEndpoint
|
||||
userId = user.UserId,
|
||||
username = user.Username,
|
||||
displayName = user.DisplayName,
|
||||
isAdmin = user.IsAdmin
|
||||
isAdmin = user.IsAdmin,
|
||||
defaultPath = user.DefaultPath
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ public static class ProfileEndpoint
|
||||
username = user.Username,
|
||||
displayName = user.DisplayName,
|
||||
persona = user.Persona,
|
||||
defaultPath = user.DefaultPath,
|
||||
isAdmin = user.IsAdmin
|
||||
});
|
||||
}
|
||||
@@ -44,8 +45,9 @@ public static class ProfileEndpoint
|
||||
|
||||
var displayName = body.TryGetProperty("displayName", out var dn) ? dn.GetString() : null;
|
||||
var persona = body.TryGetProperty("persona", out var p) ? p.GetString() : null;
|
||||
var defaultPath = body.TryGetProperty("defaultPath", out var dp) ? dp.GetString() : null;
|
||||
|
||||
await db.UpdateUserProfile(userId, displayName, persona);
|
||||
await db.UpdateUserProfile(userId, displayName, persona, defaultPath);
|
||||
|
||||
var user = await db.GetUser(userId);
|
||||
await ctx.Response.WriteAsJsonAsync(new
|
||||
@@ -54,6 +56,7 @@ public static class ProfileEndpoint
|
||||
username = user.Username,
|
||||
displayName = user.DisplayName,
|
||||
persona = user.Persona,
|
||||
defaultPath = user.DefaultPath,
|
||||
isAdmin = user.IsAdmin
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user