- M2: get_user_by_email/1 uses lower(email) = lower(?) so User@Example.com and user@example.com resolve to the same account; closes a lookalike- registration / password-reset confusion risk. - M8: webhook auth plug no longer echoes "Webhook authentication not configured" — the misconfiguration is logged server-side and the caller gets a generic Internal server error so endpoints can't be probed. - M9: coverages controller logs the underlying KMZ build error and returns a generic "Failed to build KMZ" string — filesystem paths and zip internals no longer leak. - M10: account-data controller no longer crashes with MatchError when an org has zero or multiple memberships; defaults to "member" and treats any owner membership as owner. - M11: ActivityController switches to a hard whitelist (Atom.to_string/1 comparison) instead of String.to_existing_atom/1; unknown filter types are dropped silently and the atom table can't be grown from API input. - M16: title-tracking MutationObserver is stored on `this` so destroyed() actually disconnects it — fixes a memory leak per LV navigation.
57 lines
1.6 KiB
Elixir
57 lines
1.6 KiB
Elixir
defmodule ToweropsWeb.Api.V1.ActivityController do
|
|
@moduledoc "API controller for the organization activity feed."
|
|
use ToweropsWeb, :controller
|
|
|
|
alias Towerops.ActivityFeed
|
|
|
|
def index(conn, params) do
|
|
organization_id = conn.assigns.current_organization_id
|
|
|
|
opts =
|
|
[]
|
|
|> maybe_add(:limit, parse_int(params["limit"], 50))
|
|
|> maybe_add_types(params["types"])
|
|
|
|
items = ActivityFeed.list_org_activity(organization_id, opts)
|
|
|
|
json(conn, %{data: items})
|
|
end
|
|
|
|
defp maybe_add(opts, key, value), do: Keyword.put(opts, key, value)
|
|
|
|
defp maybe_add_types(opts, nil), do: opts
|
|
|
|
# Allowlist of activity types the API accepts. Anything else is dropped
|
|
# silently. Using a hard list avoids `String.to_existing_atom/1` which
|
|
# would convert any string that happens to match an internal atom
|
|
# somewhere in the BEAM — that produces unpredictable filter behavior
|
|
# and grows the atom table from user input.
|
|
@allowed_types ~w(config_change alert_fired alert_resolved device_event sync device_added)a
|
|
|
|
defp maybe_add_types(opts, types) when is_binary(types) do
|
|
type_atoms =
|
|
types
|
|
|> String.split(",")
|
|
|> Enum.map(&String.trim/1)
|
|
|> Enum.flat_map(&allowed_type/1)
|
|
|
|
Keyword.put(opts, :types, type_atoms)
|
|
end
|
|
|
|
defp maybe_add_types(opts, _), do: opts
|
|
|
|
defp allowed_type(str) do
|
|
Enum.filter(@allowed_types, fn t -> Atom.to_string(t) == str end)
|
|
end
|
|
|
|
defp parse_int(nil, default), do: default
|
|
|
|
defp parse_int(val, default) when is_binary(val) do
|
|
case Integer.parse(val) do
|
|
{n, _} -> n
|
|
:error -> default
|
|
end
|
|
end
|
|
|
|
defp parse_int(val, _default) when is_integer(val), do: val
|
|
end
|