feat: Bao/Iris-Statusrechte + Bao→Iris-Notifications + Agent-Workflow-Übersicht
CI - Build & Test / Backend (.NET) (push) Successful in 29s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 19s
CI - Build & Test / Security Check (push) Successful in 4s

- Bao darf jetzt Status ändern (neben Iris), Sub-Agents weiterhin nicht
- CanEditContent für Inhaltsbearbeitung durch alle bekannten Caller
- Bao-Content-Änderungen triggern task_content_changed-Notification an Iris
- Bao-Status-Änderungen triggern task_status_changed-Notification an Iris
- Iris-Status-Änderungen triggern task_status_changed-Notification an Bao
- Neue WorkTask-Felder: IsAgentTask (bool), ExpectedFrom (string)
- Agent-Workflow-API: CreateAgentTask, WaitingTasks, AgentOverview
- Frontend: Agent-Task-Badge, Iris-Overview-Panel, isBao-Getter
- Login-Rate-Limiter mit strukturiertem JSON-Fehlermeldungs-Body
- Volume-Name: nexus-postgres → postgres-data (Standardisierung)
This commit is contained in:
2026-06-20 18:42:51 +02:00
parent a516353ae8
commit 83e072bc27
21 changed files with 1690 additions and 80 deletions
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.IdentityModel.Tokens;
using Nexus.Api.Data;
using Nexus.Api.Integrations;
using Nexus.Api.RateLimiting;
using Nexus.Api.Repositories;
using Nexus.Api.Routing;
using Nexus.Api.Services;
@@ -71,6 +72,37 @@ public static class ServiceCollectionExtensions
services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.OnRejected = async (context, ct) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
context.HttpContext.Response.Headers.ContentType = "application/json";
var retryAfterSeconds = 60;
// Try to read retry-after info from the metadata
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
retryAfterSeconds = (int)retryAfter.TotalSeconds;
}
// Set standard headers
context.HttpContext.Response.Headers.RetryAfter = retryAfterSeconds.ToString();
context.HttpContext.Response.Headers["X-RateLimit-Remaining"] = "0";
context.HttpContext.Response.Headers["X-RateLimit-Reset"] =
DateTimeOffset.UtcNow.AddSeconds(retryAfterSeconds).ToUnixTimeSeconds().ToString();
var body = new
{
error = "rate_limit_exceeded",
message = $"Too many attempts. Try again in {retryAfterSeconds} second(s).",
remaining = 0,
retryAfterSeconds
};
await context.HttpContext.Response.WriteAsJsonAsync(body, ct);
};
options.AddPolicy("auth", context => RateLimitPartition.GetFixedWindowLimiter(
context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
_ => new FixedWindowRateLimiterOptions
@@ -171,6 +203,7 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddNexusApplicationServices(this IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSingleton<LoginAttemptTracker>();
services.AddTransient<ModelRoutingService>();
services.AddScoped<IAuthService, AuthService>();
services.AddScoped<IAgentService, AgentService>();