P0 (security-critical): - Gate CSV/ADIF upload tabs behind authentication, add 30s cooldown to all upload handlers - Cap CSV/ADIF imports at 2,000 rows server-side in both parsers - Add submitter_verified boolean to contacts (client-cannot-set, anonymous=false) - Create k8s/secret.example.yaml with placeholders, add LIVE_VIEW_SIGNING_SALT P1 (high-priority): - Add Mox.verify_on_exit!() to valkey_test.exs - Replace DateTime.utc_now() truncation with static ~U literals in map_live_test.exs - Replace Process.sleep with render_async in pskr_spots_live_test.exs (6 occurrences) - Add MonitorLive.Show test coverage (4 tests: owner view, non-owner redirect, config success/error) - Extract duct-detection and mechanism-classification logic from ContactLive.Show into Propagation.PathAnalysis - Split ContactLive.Show render into 12 function components - Update CLAUDE.md: remove stale ML model, mark HRDPS active, add backtest/pskr dirs - Batch CSV import enrichment jobs via new enqueue_for_contacts/1 P2 (medium-priority): - Set secure:true on session and remember-me cookies in production - Change SMTP TLS from verify_none to verify_peer with public_key cacerts - Make /metrics fail-closed in production when PROMETHEUS_AUTH_TOKEN unset - Add RateLimiter (anon_limit:10, auth_limit:60) to /api/contacts/map - Add content-security-policy-report-only header - Add comment noting String.to_atom is compile-time safe in hrdps_client.ex - Delegate duplicated haversine_km to canonical Microwaveprop.Geo.haversine_km/4 - Consolidate score-tier/color/verdict formatting into Microwaveprop.Format - Update CLAUDE.md testing section to match actual raw-string-matching practice - Batch HrrrPointEnqueuer Repo.insert_all calls to single round-trip - Split weather.ex (1696→216 lines) and radio.ex (1285→54 lines) into purpose-based sub-facades P3 (low-priority): - Add LIVE_VIEW_SIGNING_SALT warning comment, extend filter_parameters - Add host/community validation to snmp_client.ex - Add raw/1 safety comment in algo_live.ex - Add hex-audit and cargo-audit Makefile targets - Add privacy_live smoke test - Replace notify_listener busy-poll loop with Process.monitor/1 + assert_receive - Add ContactCommonVolumeRadar changeset validation tests (5 tests)
66 lines
1.7 KiB
Elixir
66 lines
1.7 KiB
Elixir
defmodule MicrowavepropWeb.MetricsPlug do
|
|
@moduledoc """
|
|
Serves PromEx metrics at `/metrics`. Optionally gated by a bearer
|
|
token via `config :microwaveprop, :prometheus_auth_token`, sourced
|
|
from the `PROMETHEUS_AUTH_TOKEN` env var in `runtime.exs`.
|
|
"""
|
|
|
|
@behaviour Plug
|
|
|
|
import Plug.Conn
|
|
|
|
@impl true
|
|
def init(opts), do: opts
|
|
|
|
@impl true
|
|
def call(conn, _opts) do
|
|
case authorized?(conn) do
|
|
:ok ->
|
|
body = PromEx.get_metrics(Microwaveprop.PromEx)
|
|
|
|
# Defer the ETS cron flusher; otherwise it runs every
|
|
# `ets_flush_interval` (7.5s) concurrently with scrapes and its
|
|
# `Task.await` (hardcoded 10s) trips under contention, killing
|
|
# the GenServer. Upstream `PromEx.Plug` does the same.
|
|
PromEx.ETSCronFlusher.defer_ets_flush(Microwaveprop.PromEx.__ets_cron_flusher_name__())
|
|
|
|
conn
|
|
|> put_resp_content_type("text/plain; version=0.0.4")
|
|
|> send_resp(200, body)
|
|
|> halt()
|
|
|
|
:denied ->
|
|
conn
|
|
|> put_resp_header("www-authenticate", ~s(Bearer realm="metrics"))
|
|
|> send_resp(401, "unauthorized")
|
|
|> halt()
|
|
end
|
|
end
|
|
|
|
defp authorized?(conn) do
|
|
case Application.get_env(:microwaveprop, :prometheus_auth_token) do
|
|
token when is_binary(token) and token != "" ->
|
|
check_token(conn, token)
|
|
|
|
_ ->
|
|
if Application.get_env(:microwaveprop, :env) == :prod do
|
|
:denied
|
|
else
|
|
:ok
|
|
end
|
|
end
|
|
end
|
|
|
|
defp check_token(conn, expected) do
|
|
conn
|
|
|> get_req_header("authorization")
|
|
|> List.first()
|
|
|> case do
|
|
"Bearer " <> token ->
|
|
if Plug.Crypto.secure_compare(token, expected), do: :ok, else: :denied
|
|
|
|
_ ->
|
|
:denied
|
|
end
|
|
end
|
|
end
|