prop/lib/microwaveprop_web/controllers/api/v1/beacon_controller.ex
Graham McIntire 14b90ee9f3
fix(security,perf): address 9 audit findings (access control, DoS, crashes)
- Beacon detail endpoints (LiveView + REST API) now hide unapproved
  beacons from anonymous and unauthorized viewers; only the submitter
  and admins can see pending records before approval. Adds
  Beacons.get_visible_beacon/2 with scope-aware checks.
- API contact pagination now honors per_page end-to-end.
  Radio.list_contacts/1 accepts :per_page and clamps to 200.
- API rate limiter: ETS table is now owned by a long-lived Sweeper
  GenServer (won't die with a request task); Sweeper periodically
  prunes expired-window rows to bound memory; init_table/0 race is
  rescued.
- /scores/cells and /weather/cells: add per-IP rate limiting and a
  shared GridBounds clamp/413 guard so global / oversized viewports
  no longer drive unbounded binary responses.
- NEXRAD PNG unfilter (sub/up/average/paeth): replace acc++[byte]
  + Enum.at(acc, idx-bpp) with O(n) binary recursion. Decode time
  for the 12200x5400 n0q frame goes from quadratic to linear.
- LiveTableFooter.parse_page and ScoresFile.fetch_bound: switch
  String.to_integer/String.to_float to Integer.parse/Float.parse,
  fall back to defaults instead of raising.
- PathLive and MapLive band-event handlers: replace
  String.to_integer(params["band"]) with parse_int / parse_band_param
  so a non-numeric band parameter no longer crashes the LiveView.
2026-05-11 18:53:21 -05:00

46 lines
1.3 KiB
Elixir

defmodule MicrowavepropWeb.Api.V1.BeaconController do
@moduledoc "Read approved beacons; submit new beacons (pending approval)."
use Phoenix.Controller, formats: [:json]
alias Microwaveprop.Beacons
alias MicrowavepropWeb.Api.ErrorJSON
alias MicrowavepropWeb.Api.V1.BeaconJSON
plug :accepts, ["json"]
action_fallback MicrowavepropWeb.Api.FallbackController
def index(conn, _params) do
beacons = Beacons.list_beacons()
json(conn, BeaconJSON.index(%{beacons: beacons}))
end
def show(conn, %{"id" => id}) do
viewer = conn.assigns[:current_api_user]
case Beacons.get_visible_beacon(id, viewer) do
nil -> ErrorJSON.send_problem(conn, 404, "not_found", "Beacon not found.")
beacon -> json(conn, BeaconJSON.show(%{beacon: beacon}))
end
end
def create(conn, params) do
user = conn.assigns.current_api_user
attrs =
Map.take(
params,
~w(frequency_mhz callsign grid lat lon power_mw height_ft on_the_air keying bearing beamwidth_deg notes)
)
case Beacons.create_beacon(user, attrs) do
{:ok, beacon} ->
conn
|> put_status(:created)
|> json(BeaconJSON.show(%{beacon: beacon}))
{:error, changeset} ->
ErrorJSON.send_changeset(conn, changeset)
end
end
end