Добавить контракт изменений деталей аудита

Детали событий описываются списком изменений «параметр: было → стало» (AuditChangeDto, AuditDetails), а не произвольным JSON. Запись везде идёт через конструктор AuditDetails; разбор старого плоского формата сохранён. Операторские ручки аудита отдают AuditRecordViewDto с changes и именем пользователя (батч-резолв через ITenantRepository.FindNamesByIdsAsync). Детектор подозрительной активности читает логин через новый разбор.
This commit is contained in:
2026-09-13 19:17:57 +03:00
parent 585397b9a3
commit 02a4f9c749
26 changed files with 407 additions and 94 deletions
@@ -64,7 +64,7 @@ public static class OperatorAuthEndpoints
ActorId: null,
TenantId: null,
Ip: ClientIp(context),
DetailJson: AuditService.ToDetailJson(new { login = attemptedLogin })), ct);
DetailJson: AuditService.ToDetailJson([AuditDetails.Set("login", attemptedLogin)])), ct);
}
return EndpointResults.Unauthorized(InvalidCredentialsDetail);
@@ -78,7 +78,7 @@ public static class OperatorAuthEndpoints
ActorId: result.OperatorId,
TenantId: null,
Ip: ClientIp(context),
DetailJson: AuditService.ToDetailJson(new { login = result.Login })), ct);
DetailJson: AuditService.ToDetailJson([AuditDetails.Set("login", result.Login)])), ct);
SetOperatorSessionCookie(context, cookieOptions.Value, result.Token);
return Results.Ok(new { ok = true, login = result.Login });
@@ -100,7 +100,7 @@ public static class OperatorAuthEndpoints
if (operatorIdentity is not null)
{
await AuditAppender.AppendOperatorAsync(context, AuditEvents.OperatorLogout, new { login = operatorIdentity.Login }, ct);
await AuditAppender.AppendOperatorAsync(context, AuditEvents.OperatorLogout, [AuditDetails.Set("login", operatorIdentity.Login)], ct);
}
return Results.Ok(new { ok = true });