Auth tweaks, Audit implementation, Webhooks

This commit is contained in:
2026-04-13 14:17:45 -07:00
parent b9a04df861
commit 334b6cf3e1
46 changed files with 4654 additions and 61 deletions

View File

@@ -1,26 +1,41 @@
using System.Text.Json;
using MicCheck.Api.Data;
using MicCheck.Api.Webhooks;
namespace MicCheck.Api.Audit;
public class AuditService(MicCheckDbContext db, IHttpContextAccessor httpContextAccessor)
public class AuditService(MicCheckDbContext db, IHttpContextAccessor httpContextAccessor, WebhookQueue webhookQueue)
{
public virtual async Task LogAsync(
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public virtual async Task RecordAsync(
string resourceType,
string resourceId,
string action,
int organizationId,
int? projectId = null,
int? environmentId = null,
string? changes = null,
object? before = null,
object? after = null,
CancellationToken ct = default)
{
int? actorUserId = null;
string? changes = null;
if (before is not null || after is not null)
{
changes = JsonSerializer.Serialize(new
{
before,
after
}, JsonOptions);
}
var userIdClaim = httpContextAccessor.HttpContext?.User.FindFirst("UserId")?.Value;
if (int.TryParse(userIdClaim, out var parsedId))
actorUserId = parsedId;
var actorUserId = ResolveActorUserId();
db.AuditLogs.Add(new AuditLog
var log = new AuditLog
{
ResourceType = resourceType,
ResourceId = resourceId,
@@ -31,8 +46,75 @@ public class AuditService(MicCheckDbContext db, IHttpContextAccessor httpContext
ActorUserId = actorUserId,
Changes = changes,
CreatedAt = DateTimeOffset.UtcNow
});
};
db.AuditLogs.Add(log);
await db.SaveChangesAsync(ct);
await webhookQueue.EnqueueAsync(new WebhookEvent
{
EventType = WebhookEventTypes.AuditLogCreated,
EnvironmentId = environmentId,
OrganizationId = organizationId,
Data = new
{
audit_log_id = log.Id,
resource_type = resourceType,
resource_id = resourceId,
action,
created_at = log.CreatedAt
}
});
}
// Backward-compatible overload used by existing callers
public virtual async Task LogAsync(
string resourceType,
string resourceId,
string action,
int organizationId,
int? projectId = null,
int? environmentId = null,
string? changes = null,
CancellationToken ct = default)
{
var actorUserId = ResolveActorUserId();
var log = new AuditLog
{
ResourceType = resourceType,
ResourceId = resourceId,
Action = action,
OrganizationId = organizationId,
ProjectId = projectId,
EnvironmentId = environmentId,
ActorUserId = actorUserId,
Changes = changes,
CreatedAt = DateTimeOffset.UtcNow
};
db.AuditLogs.Add(log);
await db.SaveChangesAsync(ct);
await webhookQueue.EnqueueAsync(new WebhookEvent
{
EventType = WebhookEventTypes.AuditLogCreated,
EnvironmentId = environmentId,
OrganizationId = organizationId,
Data = new
{
audit_log_id = log.Id,
resource_type = resourceType,
resource_id = resourceId,
action,
created_at = log.CreatedAt
}
});
}
private int? ResolveActorUserId()
{
var claim = httpContextAccessor.HttpContext?.User.FindFirst("UserId")?.Value;
return int.TryParse(claim, out var id) ? id : null;
}
}