feat: User-Zuordnung für Sessions und Agents

- UserId-Feld zu SessionRecord und AgentRecord hinzugefügt
- DB-Migration: user_id Spalte + bestehende Daten dem Admin zuweisen
- Alle Endpoints filtern/prüfen nach UserId des eingeloggten Users
- HttpContextExtensions.GetUserId() Helper für konsistenten Zugriff
- Betrifft: Agent, Session, Chat, History, Workspace, Skill Endpoints
- Sessions/Agents anderer User sind nicht sichtbar/zugreifbar
This commit is contained in:
Meleki committed 2026-03-02 11:39:55 +01:00
1 parent 9a59ae686d
commit 7614141760
10 files changed
+123 -45

No files matched your search

+10 -4
View File
@@ -1,5 +1,6 @@
using System.Text.Json;
using System.Threading.Tasks;
using Gott.Extensions;
using Gott.Models;
using Gott.Services;
using Microsoft.AspNetCore.Builder;
@@ -21,7 +22,8 @@ public static class AgentEndpoint
private static async Task HandleList(HttpContext ctx, DatabaseService db)
{
var agents = await db.GetAgents();
var userId = ctx.GetUserId();
var agents = await db.GetAgents(userId);
await ctx.Response.WriteAsJsonAsync(agents);
}
@@ -31,13 +33,14 @@ public static class AgentEndpoint
if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; }
var agent = await db.GetAgent(agentId);
if (agent == null) { ctx.Response.StatusCode = 404; return; }
if (agent == null || agent.UserId != ctx.GetUserId()) { ctx.Response.StatusCode = 404; return; }
await ctx.Response.WriteAsJsonAsync(agent);
}
private static async Task HandleCreate(HttpContext ctx, DatabaseService db, ImageService imageService, MemoryService memoryService)
{
var userId = ctx.GetUserId();
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
var avatarRaw = body.TryGetProperty("avatar", out var av) ? av.GetString() : null;
@@ -48,6 +51,7 @@ public static class AgentEndpoint
var agent = new AgentRecord
{
UserId = userId,
Name = body.TryGetProperty("name", out var n) ? n.GetString() ?? "Neuer Agent" : "Neuer Agent",
Avatar = imageService.ResizeDataUrl(avatarRaw),
Description = body.TryGetProperty("description", out var d) ? d.GetString() : null,
@@ -68,11 +72,12 @@ public static class AgentEndpoint
private static async Task HandleUpdate(HttpContext ctx, DatabaseService db, ImageService imageService, MemoryService memoryService)
{
var userId = ctx.GetUserId();
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; }
var agent = await db.GetAgent(agentId);
if (agent == null) { ctx.Response.StatusCode = 404; return; }
if (agent == null || agent.UserId != userId) { ctx.Response.StatusCode = 404; return; }
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
@@ -97,10 +102,11 @@ public static class AgentEndpoint
private static async Task HandleDelete(HttpContext ctx, DatabaseService db)
{
var userId = ctx.GetUserId();
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; }
await db.DeleteAgent(agentId);
await db.DeleteAgent(agentId, userId);
ctx.Response.StatusCode = 204;
}
}
+3 -1
View File
@@ -6,6 +6,7 @@ using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Gott.Extensions;
using Gott.Models;
using Gott.Services;
using Microsoft.AspNetCore.Builder;
@@ -50,8 +51,9 @@ public static class ChatEndpoint
}
// Agent aus Session auflösen
var userId = req.HttpContext.GetUserId();
var session = await db.GetSession(sessionId);
if (session == null || string.IsNullOrEmpty(session.AgentId))
if (session == null || session.UserId != userId || string.IsNullOrEmpty(session.AgentId))
{
res.StatusCode = 400;
await res.WriteAsJsonAsync(new { error = "Kein Agent ausgewählt" });
+12 -4
View File
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Gott.Extensions;
using Gott.Models;
using Gott.Services;
using Microsoft.AspNetCore.Builder;
@@ -23,10 +24,17 @@ public static class HistoryEndpoint
app.MapGet("/api/chains/{chainId}", HandleGetChain);
}
private static async Task<bool> VerifySessionOwner(HttpContext ctx, DatabaseService db, string sessionId)
{
if (string.IsNullOrEmpty(sessionId)) return false;
var session = await db.GetSession(sessionId);
return session != null && session.UserId == ctx.GetUserId();
}
private static async Task HandleHistory(HttpContext ctx, DatabaseService db)
{
var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault();
if (string.IsNullOrEmpty(sessionId))
if (string.IsNullOrEmpty(sessionId) || !await VerifySessionOwner(ctx, db, sessionId))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "sessionId required" });
@@ -84,7 +92,7 @@ public static class HistoryEndpoint
var afterStr = ctx.Request.Query["afterId"].FirstOrDefault();
var afterId = long.TryParse(afterStr, out var a) ? a : 0;
if (string.IsNullOrEmpty(sessionId))
if (string.IsNullOrEmpty(sessionId) || !await VerifySessionOwner(ctx, db, sessionId))
{
ctx.Response.StatusCode = 400;
return;
@@ -97,7 +105,7 @@ public static class HistoryEndpoint
private static async Task HandleArchive(HttpContext ctx, DatabaseService db)
{
var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault();
if (string.IsNullOrEmpty(sessionId))
if (string.IsNullOrEmpty(sessionId) || !await VerifySessionOwner(ctx, db, sessionId))
{
ctx.Response.StatusCode = 400;
return;
@@ -115,7 +123,7 @@ public static class HistoryEndpoint
private static async Task HandleListArchives(HttpContext ctx, DatabaseService db)
{
var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault();
if (string.IsNullOrEmpty(sessionId))
if (string.IsNullOrEmpty(sessionId) || !await VerifySessionOwner(ctx, db, sessionId))
{
ctx.Response.StatusCode = 400;
return;
+34 -9
View File
@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Gott.Extensions;
using Gott.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
@@ -29,12 +30,14 @@ public static class SessionEndpoint
private static async Task HandleList(HttpContext ctx, DatabaseService db)
{
var sessions = await db.GetSessionsWithStats();
var userId = ctx.GetUserId();
var sessions = await db.GetSessionsWithStats(userId);
await ctx.Response.WriteAsJsonAsync(sessions);
}
private static async Task HandleGet(HttpContext ctx, DatabaseService db)
{
var userId = ctx.GetUserId();
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
if (string.IsNullOrEmpty(sessionId))
{
@@ -42,7 +45,7 @@ public static class SessionEndpoint
return;
}
var session = await db.GetSession(sessionId);
if (session == null)
if (session == null || session.UserId != userId)
{
ctx.Response.StatusCode = 404;
return;
@@ -52,6 +55,7 @@ public static class SessionEndpoint
private static async Task HandleCreate(HttpContext ctx, DatabaseService db, ImageService imageService)
{
var userId = ctx.GetUserId();
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
var name = body.TryGetProperty("name", out var n) ? n.GetString() : "Neue Session";
@@ -63,12 +67,13 @@ public static class SessionEndpoint
var context = body.TryGetProperty("context", out var ctxEl) ? ctxEl.GetString() : null;
var customCommands = body.TryGetProperty("customCommands", out var cc) && cc.ValueKind != JsonValueKind.Null ? cc.GetRawText() : null;
var session = await db.CreateSession(name, description, image, agentId, workspace, context, customCommands);
var session = await db.CreateSession(userId, name, description, image, agentId, workspace, context, customCommands);
await ctx.Response.WriteAsJsonAsync(session);
}
private static async Task HandleUpdate(HttpContext ctx, DatabaseService db, ImageService imageService)
{
var userId = ctx.GetUserId();
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
if (string.IsNullOrEmpty(sessionId))
{
@@ -76,6 +81,13 @@ public static class SessionEndpoint
return;
}
var existing = await db.GetSession(sessionId);
if (existing == null || existing.UserId != userId)
{
ctx.Response.StatusCode = 404;
return;
}
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
await db.UpdateSession(sessionId, body, imageService);
@@ -85,15 +97,17 @@ public static class SessionEndpoint
private static async Task HandleDelete(HttpContext ctx, DatabaseService db)
{
var userId = ctx.GetUserId();
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
if (string.IsNullOrEmpty(sessionId)) { ctx.Response.StatusCode = 400; return; }
await db.DeleteSession(sessionId);
await db.DeleteSession(sessionId, userId);
ctx.Response.StatusCode = 204;
}
private static async Task HandleSystemPrompt(HttpContext ctx, DatabaseService db, MemoryService memoryService, SkillService skillService)
{
var userId = ctx.GetUserId();
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
if (string.IsNullOrEmpty(sessionId))
{
@@ -102,7 +116,7 @@ public static class SessionEndpoint
}
var session = await db.GetSession(sessionId);
if (session == null)
if (session == null || session.UserId != userId)
{
ctx.Response.StatusCode = 404;
return;
@@ -135,11 +149,12 @@ public static class SessionEndpoint
private static async Task HandleSystemPromptBlocks(HttpContext ctx, DatabaseService db, MemoryService memoryService, SkillService skillService)
{
var userId = ctx.GetUserId();
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
if (string.IsNullOrEmpty(sessionId)) { ctx.Response.StatusCode = 400; return; }
var session = await db.GetSession(sessionId);
if (session == null) { ctx.Response.StatusCode = 404; return; }
if (session == null || session.UserId != userId) { ctx.Response.StatusCode = 404; return; }
if (string.IsNullOrEmpty(session.AgentId))
{
@@ -165,8 +180,9 @@ public static class SessionEndpoint
await ctx.Response.WriteAsJsonAsync(blocks.Select(b => new { b.Icon, b.Label, b.Content }));
}
private static async Task HandleStreaming(HttpContext ctx, StreamingTracker streamingTracker)
private static async Task HandleStreaming(HttpContext ctx, DatabaseService db, StreamingTracker streamingTracker)
{
var userId = ctx.GetUserId();
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
if (string.IsNullOrEmpty(sessionId))
{
@@ -174,6 +190,13 @@ public static class SessionEndpoint
return;
}
var session = await db.GetSession(sessionId);
if (session == null || session.UserId != userId)
{
ctx.Response.StatusCode = 404;
return;
}
var isStreaming = streamingTracker.IsStreaming(sessionId);
var chainId = streamingTracker.GetChainId(sessionId);
await ctx.Response.WriteAsJsonAsync(new { streaming = isStreaming, chainId });
@@ -181,6 +204,7 @@ public static class SessionEndpoint
private static async Task HandleExec(HttpContext ctx, DatabaseService db)
{
var userId = ctx.GetUserId();
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
if (string.IsNullOrEmpty(sessionId))
{
@@ -189,7 +213,7 @@ public static class SessionEndpoint
}
var session = await db.GetSession(sessionId);
if (session == null)
if (session == null || session.UserId != userId)
{
ctx.Response.StatusCode = 404;
return;
@@ -304,6 +328,7 @@ public static class SessionEndpoint
private static async Task HandleGitStatus(HttpContext ctx, DatabaseService db)
{
var userId = ctx.GetUserId();
var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString();
if (string.IsNullOrEmpty(sessionId))
{
@@ -312,7 +337,7 @@ public static class SessionEndpoint
}
var session = await db.GetSession(sessionId);
if (session == null)
if (session == null || session.UserId != userId)
{
ctx.Response.StatusCode = 404;
return;
+12 -4
View File
@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Gott.Extensions;
using Gott.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
@@ -50,10 +51,17 @@ public static class SkillEndpoint
await ctx.Response.WriteAsJsonAsync(skills);
}
private static async Task<bool> VerifyAgentOwner(HttpContext ctx, DatabaseService db, string agentId)
{
if (string.IsNullOrEmpty(agentId)) return false;
var agent = await db.GetAgent(agentId);
return agent != null && agent.UserId == ctx.GetUserId();
}
private static async Task HandleGetAgentSkills(HttpContext ctx, DatabaseService db, SkillService skillService)
{
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; }
if (string.IsNullOrEmpty(agentId) || !await VerifyAgentOwner(ctx, db, agentId)) { ctx.Response.StatusCode = 400; return; }
var slugs = await db.GetAgentSkills(agentId);
var allSkills = skillService.GetAllSkills();
@@ -69,7 +77,7 @@ public static class SkillEndpoint
private static async Task HandleSetAgentSkills(HttpContext ctx, DatabaseService db)
{
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; }
if (string.IsNullOrEmpty(agentId) || !await VerifyAgentOwner(ctx, db, agentId)) { ctx.Response.StatusCode = 400; return; }
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
var slugs = body.TryGetProperty("skills", out var arr)
@@ -86,7 +94,7 @@ public static class SkillEndpoint
{
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
var slug = ctx.Request.RouteValues["slug"]?.ToString();
if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug)) { ctx.Response.StatusCode = 400; return; }
if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug) || !await VerifyAgentOwner(ctx, db, agentId)) { ctx.Response.StatusCode = 400; return; }
await db.AddAgentSkill(agentId, slug);
ctx.Response.StatusCode = 204;
@@ -96,7 +104,7 @@ public static class SkillEndpoint
{
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
var slug = ctx.Request.RouteValues["slug"]?.ToString();
if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug)) { ctx.Response.StatusCode = 400; return; }
if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug) || !await VerifyAgentOwner(ctx, db, agentId)) { ctx.Response.StatusCode = 400; return; }
await db.RemoveAgentSkill(agentId, slug);
ctx.Response.StatusCode = 204;
+5 -4
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Gott.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
@@ -54,7 +55,7 @@ public static class WorkspaceEndpoint
// Workspace aus Session holen
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || string.IsNullOrEmpty(session.Workspace))
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
@@ -118,7 +119,7 @@ public static class WorkspaceEndpoint
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || string.IsNullOrEmpty(session.Workspace))
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
@@ -176,7 +177,7 @@ public static class WorkspaceEndpoint
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || string.IsNullOrEmpty(session.Workspace))
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
@@ -446,7 +447,7 @@ public static class WorkspaceEndpoint
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
var session = await db.GetSession(sessionId ?? "");
if (session == null || string.IsNullOrEmpty(session.Workspace))
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
{
ctx.Response.StatusCode = 400;
return;