diff --git a/src/Module/Core/Infrastructure/Audit/AuditLogWriter.php b/src/Module/Core/Infrastructure/Audit/AuditLogWriter.php new file mode 100644 index 0000000..e1f1bfa --- /dev/null +++ b/src/Module/Core/Infrastructure/Audit/AuditLogWriter.php @@ -0,0 +1,94 @@ + keys always stripped from the `changes` payload */ + private const array SENSITIVE_KEYS = ['password', 'plainPassword', 'apiToken', 'token', 'secret']; + + public function __construct( + #[Autowire(service: 'doctrine.dbal.audit_connection')] + private readonly Connection $connection, + private readonly Security $security, + private readonly RequestStack $requestStack, + private readonly RequestIdProvider $requestIdProvider, + ) {} + + /** + * @param string $entityType Format "module.Entity" (e.g. "core.User") + * @param string $entityId Entity id (int or serialized UUID) + * @param string $action create|update|delete + * @param array $changes JSON payload (sensitive keys stripped) + */ + public function log( + string $entityType, + string $entityId, + string $action, + array $changes, + ): void { + $filteredChanges = $this->stripSensitive($changes); + + $this->connection->insert('audit_log', [ + 'id' => Uuid::v7()->toRfc4122(), + 'entity_type' => $entityType, + 'entity_id' => $entityId, + 'action' => $action, + 'changes' => $filteredChanges, + 'performed_by' => $this->security->getUser()?->getUserIdentifier() ?? 'system', + 'performed_at' => new DateTimeImmutable('now', new DateTimeZone('UTC')), + 'ip_address' => $this->requestStack->getCurrentRequest()?->getClientIp(), + 'request_id' => $this->requestIdProvider->getRequestId(), + ], [ + 'id' => Types::GUID, + 'changes' => Types::JSON, + 'performed_at' => Types::DATETIMETZ_IMMUTABLE, + ]); + } + + /** + * Recursively removes sensitive keys from the payload. + * + * @param array $data + * + * @return array + */ + private function stripSensitive(array $data): array + { + foreach ($data as $key => $value) { + if (in_array($key, self::SENSITIVE_KEYS, true)) { + unset($data[$key]); + + continue; + } + if (is_array($value)) { + $data[$key] = $this->stripSensitive($value); + } + } + + return $data; + } +} diff --git a/src/Module/Core/Infrastructure/Audit/RequestIdProvider.php b/src/Module/Core/Infrastructure/Audit/RequestIdProvider.php new file mode 100644 index 0000000..9521405 --- /dev/null +++ b/src/Module/Core/Infrastructure/Audit/RequestIdProvider.php @@ -0,0 +1,33 @@ +isMainRequest()) { + return; + } + + $this->requestId = Uuid::v4()->toRfc4122(); + } + + public function getRequestId(): ?string + { + return $this->requestId; + } +}