Add transfer tracking and secure admin access
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
using MiaoJiZhang.Domain.Entities;
|
||||
using MiaoJiZhang.Infrastructure.Persistence;
|
||||
|
||||
namespace MiaoJiZhang.Api.Services;
|
||||
|
||||
public sealed class AdminAuditMiddleware(RequestDelegate next)
|
||||
{
|
||||
public async Task InvokeAsync(HttpContext context, AppDbContext db)
|
||||
{
|
||||
var isAdmin = context.Request.Path.StartsWithSegments("/api/admin");
|
||||
var isAuth = context.Request.Path.StartsWithSegments("/api/admin/auth");
|
||||
var shouldAudit = isAdmin && (isAuth ||
|
||||
!AdminSessionService.IsSafeMethod(context.Request.Method));
|
||||
if (!shouldAudit)
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
Exception? failure = null;
|
||||
try
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
failure = exception;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
var principal = AdminRequestContext.Principal(context);
|
||||
var attemptedUsername = context.Items.TryGetValue(
|
||||
AdminRequestContext.AuditUsernameKey,
|
||||
out var attemptedValue)
|
||||
? attemptedValue?.ToString()
|
||||
: null;
|
||||
var status = failure is null
|
||||
? context.Response.StatusCode
|
||||
: StatusCodes.Status500InternalServerError;
|
||||
db.AdminAuditLogs.Add(new AdminAuditLog
|
||||
{
|
||||
AdminUserId = principal?.UserId,
|
||||
Username = principal?.Username ?? attemptedUsername,
|
||||
Action = ActionName(context),
|
||||
Resource = context.Request.Path.Value ?? "/api/admin",
|
||||
HttpMethod = context.Request.Method,
|
||||
Path = (context.Request.Path + context.Request.QueryString).ToString(),
|
||||
StatusCode = status,
|
||||
Success = failure is null && status < 400,
|
||||
Detail = failure?.GetType().Name,
|
||||
IpAddress = context.Connection.RemoteIpAddress?.ToString(),
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
});
|
||||
await db.SaveChangesAsync(CancellationToken.None);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Audit persistence must not replace the original API result.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ActionName(HttpContext context)
|
||||
{
|
||||
var path = context.Request.Path.Value?.Trim('/').Replace('/', '.') ?? "api.admin";
|
||||
return $"{context.Request.Method.ToLowerInvariant()}.{path}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user