towerops/lib/towerops/admin/audit_logger.ex

177 lines
4.9 KiB
Elixir

defmodule Towerops.Admin.AuditLogger do
@moduledoc """
Helper module for creating audit log entries with automatic extraction of
request metadata (IP address, user agent, request path).
This module provides convenience functions for logging various types of
audit events throughout the application.
"""
alias Towerops.Admin
@doc """
Logs an audit event with automatic extraction of conn metadata.
## Parameters
* `action` - String representing the action being logged
* `conn` - The Plug.Conn struct (can be nil for background jobs)
* `opts` - Keyword list of options:
* `:actor_id` - ID of the user performing the action (optional)
* `:target_user_id` - ID of the user being acted upon (optional)
* `:metadata` - Additional metadata map (optional)
* `:data_accessed` - Map of data fields that were accessed (optional)
## Examples
iex> log_event("user_data_viewed", conn, actor_id: admin_id, target_user_id: user_id)
{:ok, %AuditLog{}}
"""
def log_event(action, conn \\ nil, opts \\ []) do
attrs = build_attrs(action, conn, opts)
Admin.create_audit_log(attrs)
end
@doc """
Logs when a user's data is viewed by an admin or another user.
"""
def log_user_data_viewed(conn, viewer_id, target_user_id, data_accessed) do
log_event("user_data_viewed", conn,
actor_id: viewer_id,
target_user_id: target_user_id,
data_accessed: data_accessed
)
end
@doc """
Logs when a user exports their data.
"""
def log_user_data_exported(conn, user_id) do
log_event("user_data_exported", conn,
actor_id: user_id,
target_user_id: user_id,
metadata: %{export_format: "json"}
)
end
@doc """
Logs when a user's profile is updated.
"""
def log_user_profile_updated(conn, user_id, fields_changed) do
log_event("user_profile_updated", conn,
actor_id: user_id,
target_user_id: user_id,
data_accessed: %{fields_changed: fields_changed}
)
end
@doc """
Logs when organization data is accessed.
"""
def log_org_data_accessed(conn, user_id, org_id, action_detail) do
log_event("org_data_accessed", conn,
actor_id: user_id,
metadata: %{organization_id: org_id, action: action_detail}
)
end
@doc """
Logs device creation.
"""
def log_device_created(conn, user_id, device_id, device_name) do
log_event("device_created", conn,
actor_id: user_id,
metadata: %{device_id: device_id, device_name: device_name}
)
end
@doc """
Logs device update.
"""
def log_device_updated(conn, user_id, device_id, fields_changed) do
log_event("device_updated", conn,
actor_id: user_id,
metadata: %{device_id: device_id},
data_accessed: %{fields_changed: fields_changed}
)
end
@doc """
Logs device deletion.
"""
def log_device_deleted(conn, user_id, device_id, device_name) do
log_event("device_deleted", conn,
actor_id: user_id,
metadata: %{device_id: device_id, device_name: device_name}
)
end
@doc """
Logs when monitoring data is queried.
"""
def log_monitoring_data_queried(conn, user_id, device_id, time_range) do
log_event("monitoring_data_queried", conn,
actor_id: user_id,
metadata: %{device_id: device_id, time_range: time_range}
)
end
@doc """
Logs failed access attempts.
"""
def log_failed_access_attempt(conn, user_id, resource, reason) do
log_event("failed_access_attempt", conn,
actor_id: user_id,
metadata: %{resource: resource, reason: reason}
)
end
@doc """
Logs privilege escalations (e.g., becoming org admin).
"""
def log_privilege_escalation(conn, user_id, from_role, to_role, context) do
log_event("privilege_escalation", conn,
actor_id: user_id,
metadata: %{from_role: from_role, to_role: to_role, context: context}
)
end
# Private helper functions
defp build_attrs(action, conn, opts) do
base_attrs = %{
action: action,
superuser_id: Keyword.get(opts, :actor_id),
target_user_id: Keyword.get(opts, :target_user_id),
metadata: Keyword.get(opts, :metadata, %{}),
data_accessed: Keyword.get(opts, :data_accessed)
}
if conn do
base_attrs
|> Map.put(:ip_address, get_ip_address(conn))
|> Map.put(:user_agent, get_user_agent(conn))
|> Map.put(:request_path, get_request_path(conn))
else
base_attrs
end
end
defp get_ip_address(%Plug.Conn{} = conn) do
case Plug.Conn.get_req_header(conn, "x-forwarded-for") do
[ip | _] -> ip |> String.split(",") |> List.first() |> String.trim()
[] -> to_string(:inet_parse.ntoa(conn.remote_ip))
end
end
defp get_user_agent(%Plug.Conn{} = conn) do
case Plug.Conn.get_req_header(conn, "user-agent") do
[ua | _] -> ua
[] -> nil
end
end
defp get_request_path(%Plug.Conn{} = conn) do
conn.request_path
end
end