- Extract shared extract_bearer/1 from api/monitor_auth.ex → api/auth.ex - Refactor admin_changeset to pipeline through validate_callsign/validate_name/validate_email - Add limit: 500 to list_beacons/0 and candidate_rover_locations/1 - Combine get_mission_with_paths preloads into single round-trip
65 lines
2.1 KiB
Elixir
65 lines
2.1 KiB
Elixir
defmodule MicrowavepropWeb.Api.MonitorAuth do
|
|
@moduledoc """
|
|
Bearer-token authentication for propmonitor uploads.
|
|
|
|
Resolves `Authorization: Bearer <token>` against
|
|
`Microwaveprop.BeaconMonitors.get_monitor_by_token/1`, separate from
|
|
the user-API-token plug at `MicrowavepropWeb.Api.Auth`. Monitor tokens
|
|
are issued via `POST /api/v1/me/beacon-monitors` and identify a
|
|
monitor station, not a user session.
|
|
|
|
On success, assigns:
|
|
|
|
* `:current_monitor` — the `BeaconMonitor` struct
|
|
* `:current_api_token` — `{:monitor, monitor_id}` so the
|
|
rate-limiter buckets the monitor by id rather than IP.
|
|
|
|
On failure, halts with a 401 `application/problem+json` response.
|
|
"""
|
|
|
|
@behaviour Plug
|
|
|
|
import Plug.Conn
|
|
|
|
alias Microwaveprop.BeaconMonitors
|
|
alias MicrowavepropWeb.Api.Auth
|
|
alias MicrowavepropWeb.Api.ErrorJSON
|
|
|
|
@impl true
|
|
def init(opts), do: opts
|
|
|
|
@impl true
|
|
def call(conn, _opts) do
|
|
case extract_bearer(conn) do
|
|
{:ok, token} ->
|
|
case BeaconMonitors.get_monitor_by_token(token) do
|
|
nil -> halt_unauthorized(conn, "Bearer token is invalid, expired, or revoked.")
|
|
monitor -> assign_monitor(conn, monitor)
|
|
end
|
|
|
|
{:error, :missing_token} ->
|
|
halt_unauthorized(conn, "Missing bearer token in Authorization header.")
|
|
|
|
{:error, :invalid_authorization_header} ->
|
|
halt_unauthorized(conn, "Authorization header must be `Bearer <token>`.")
|
|
end
|
|
end
|
|
|
|
defp extract_bearer(conn), do: Auth.extract_bearer(conn)
|
|
|
|
defp assign_monitor(conn, monitor) do
|
|
# The rate-limiter buckets on `current_api_token.id`. Map the
|
|
# monitor onto that shape so retries from one monitor count
|
|
# against a per-monitor bucket, not the shared anon-IP bucket
|
|
# (which would punish co-located stations behind one NAT).
|
|
conn
|
|
|> assign(:current_monitor, monitor)
|
|
|> assign(:current_api_token, %{id: {:monitor, monitor.id}})
|
|
end
|
|
|
|
defp halt_unauthorized(conn, detail) do
|
|
conn
|
|
|> put_resp_header("www-authenticate", ~s(Bearer realm="api"))
|
|
|> ErrorJSON.send_problem(401, "unauthorized", detail)
|
|
end
|
|
end
|