- Neue Felder CanCreateAgents und CanUseCli im UserRecord (Default: true) - DB-Migration mit Abwärtskompatibilität (bestehende User behalten alle Rechte) - Admin-UI: Checkboxen für beide Berechtigungen pro User - AgentEndpoint: Prüfung auf CanCreateAgents beim Erstellen - ChatEndpoint: CLI-Fallback nur wenn CanUseCli erlaubt - Frontend: Neuer-Agent-Button und Agent-Page bei fehlender Berechtigung gesperrt - Chat: Proaktive Warnung wenn kein API-Key und keine CLI-Berechtigung - Bug Fix: is_public fehlte im Agent-INSERT (Public Agents erst nach erneutem Speichern sichtbar) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
133 lines
4.2 KiB
C#
133 lines
4.2 KiB
C#
using System.Text.Json;
|
|
using Gott.Services;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Routing;
|
|
|
|
namespace Gott.Endpoints;
|
|
|
|
public static class AuthEndpoint
|
|
{
|
|
public static void MapAuthEndpoints(this WebApplication app)
|
|
{
|
|
app.MapPost("/api/auth/login", HandleLogin);
|
|
app.MapPost("/api/auth/logout", HandleLogout);
|
|
app.MapGet("/api/auth/me", HandleMe);
|
|
app.MapGet("/api/auth/autologin", HandleAutologin);
|
|
}
|
|
|
|
private static async System.Threading.Tasks.Task HandleLogin(HttpContext ctx, DatabaseService db, AuthService auth)
|
|
{
|
|
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
|
|
var username = body.TryGetProperty("username", out var u) ? u.GetString() : null;
|
|
var password = body.TryGetProperty("password", out var p) ? p.GetString() : null;
|
|
|
|
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
|
{
|
|
ctx.Response.StatusCode = 400;
|
|
await ctx.Response.WriteAsJsonAsync(new { error = "Username and password required" });
|
|
return;
|
|
}
|
|
|
|
var user = await db.GetUserByUsername(username);
|
|
if (user == null || !auth.VerifyPassword(password, user.PasswordHash))
|
|
{
|
|
ctx.Response.StatusCode = 401;
|
|
await ctx.Response.WriteAsJsonAsync(new { error = "Invalid credentials" });
|
|
return;
|
|
}
|
|
|
|
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
|
|
});
|
|
}
|
|
|
|
private static async System.Threading.Tasks.Task HandleLogout(HttpContext ctx, AuthService auth)
|
|
{
|
|
var token = ctx.Request.Cookies["gott_token"];
|
|
auth.RevokeToken(token);
|
|
|
|
ctx.Response.Cookies.Delete("gott_token", new CookieOptions
|
|
{
|
|
Path = "/"
|
|
});
|
|
|
|
await ctx.Response.WriteAsJsonAsync(new { ok = true });
|
|
}
|
|
|
|
private static async System.Threading.Tasks.Task HandleMe(HttpContext ctx, DatabaseService db, AuthService auth)
|
|
{
|
|
var token = ctx.Request.Cookies["gott_token"];
|
|
var userId = auth.ValidateToken(token);
|
|
|
|
if (userId == null)
|
|
{
|
|
ctx.Response.StatusCode = 401;
|
|
await ctx.Response.WriteAsJsonAsync(new { error = "Not authenticated" });
|
|
return;
|
|
}
|
|
|
|
var user = await db.GetUser(userId);
|
|
if (user == null)
|
|
{
|
|
ctx.Response.StatusCode = 401;
|
|
await ctx.Response.WriteAsJsonAsync(new { error = "User not found" });
|
|
return;
|
|
}
|
|
|
|
await ctx.Response.WriteAsJsonAsync(new
|
|
{
|
|
userId = user.UserId,
|
|
username = user.Username,
|
|
displayName = user.DisplayName,
|
|
isAdmin = user.IsAdmin,
|
|
defaultPath = user.DefaultPath,
|
|
canCreateAgents = user.CanCreateAgents,
|
|
canUseCli = user.CanUseCli
|
|
});
|
|
}
|
|
|
|
private static System.Threading.Tasks.Task HandleAutologin(HttpContext ctx, AuthService auth)
|
|
{
|
|
var token = ctx.Request.Query["token"].ToString();
|
|
|
|
if (string.IsNullOrEmpty(token))
|
|
{
|
|
ctx.Response.StatusCode = 400;
|
|
return ctx.Response.WriteAsync("Token fehlt");
|
|
}
|
|
|
|
var userId = auth.ValidateToken(token);
|
|
if (userId == null)
|
|
{
|
|
ctx.Response.StatusCode = 401;
|
|
return ctx.Response.WriteAsync("Ungültiger Token");
|
|
}
|
|
|
|
ctx.Response.Cookies.Append("gott_token", token, new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Path = "/",
|
|
MaxAge = System.TimeSpan.FromDays(365)
|
|
});
|
|
|
|
ctx.Response.Redirect("/");
|
|
return System.Threading.Tasks.Task.CompletedTask;
|
|
}
|
|
}
|