Skip to content

Commit 86f3bdc

Browse files
fix: security hardening, reliability improvements, and notification UI polish
- Add plugin ownership checks to prevent cross-project deletion (index + version) - Remove internal storage_key from plugin version API response - Check channel.enabled at delivery time, skip disabled channels - Add HTTP timeouts (receive: 15s, pool: 5s) to all notification adapters - Extract shared helpers module for 11 notification LiveViews (~370 lines removed) - Add missing channel links in rules list, rule show, and delivery list - Disable rule creation submit when no channels exist with link to create one - Expand resource_badge to cover 13 additional resource types - Add data-confirm to WAF anomaly resolve/false-positive and portal key revoke - Add Analytics sidebar link
1 parent be3a5a0 commit 86f3bdc

24 files changed

Lines changed: 305 additions & 523 deletions

File tree

lib/sentinel_cp/events/adapters/pagerduty.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ defmodule SentinelCp.Events.Adapters.PagerDuty do
2727
def deliver(routing_key, payload) do
2828
body = Jason.encode!(payload)
2929

30-
case Req.post(@events_url, body: body, headers: [{"content-type", "application/json"}]) do
30+
case Req.post(@events_url,
31+
body: body,
32+
headers: [{"content-type", "application/json"}],
33+
receive_timeout: 15_000,
34+
pool_timeout: 5_000
35+
) do
3136
{:ok, %{status: 202}} -> {:ok, 202}
3237
{:ok, %{status: status, body: body}} -> {:error, {:http_error, status, body}}
3338
{:error, reason} -> {:error, reason}

lib/sentinel_cp/events/adapters/slack.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ defmodule SentinelCp.Events.Adapters.Slack do
3535
def deliver(webhook_url, payload) do
3636
body = Jason.encode!(payload)
3737

38-
case Req.post(webhook_url, body: body, headers: [{"content-type", "application/json"}]) do
38+
case Req.post(webhook_url,
39+
body: body,
40+
headers: [{"content-type", "application/json"}],
41+
receive_timeout: 15_000,
42+
pool_timeout: 5_000
43+
) do
3944
{:ok, %{status: status}} when status in 200..299 ->
4045
{:ok, status}
4146

lib/sentinel_cp/events/adapters/teams.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ defmodule SentinelCp.Events.Adapters.Teams do
3838
def deliver(webhook_url, payload) do
3939
body = Jason.encode!(payload)
4040

41-
case Req.post(webhook_url, body: body, headers: [{"content-type", "application/json"}]) do
41+
case Req.post(webhook_url,
42+
body: body,
43+
headers: [{"content-type", "application/json"}],
44+
receive_timeout: 15_000,
45+
pool_timeout: 5_000
46+
) do
4247
{:ok, %{status: status}} when status in 200..299 -> {:ok, status}
4348
{:ok, %{status: status, body: body}} -> {:error, {:http_error, status, body}}
4449
{:error, reason} -> {:error, reason}

lib/sentinel_cp/events/adapters/webhook.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ defmodule SentinelCp.Events.Adapters.Webhook do
1717
body = Jason.encode!(payload)
1818
headers = build_headers(body, signing_secret)
1919

20-
case Req.post(url, body: body, headers: headers) do
20+
case Req.post(url,
21+
body: body,
22+
headers: headers,
23+
receive_timeout: 15_000,
24+
pool_timeout: 5_000
25+
) do
2126
{:ok, %{status: status}} when status in 200..299 -> {:ok, status}
2227
{:ok, %{status: status, body: body}} -> {:error, {:http_error, status, body}}
2328
{:error, reason} -> {:error, reason}

lib/sentinel_cp/events/delivery_worker.ex

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,22 @@ defmodule SentinelCp.Events.DeliveryWorker do
3030
event = Events.get_event(attempt.event_id)
3131
channel = attempt.channel
3232

33-
if event && channel do
34-
execute_delivery(attempt, event, channel)
35-
else
36-
Logger.warning("Delivery attempt #{attempt_id}: missing event or channel")
37-
:ok
33+
cond do
34+
is_nil(event) || is_nil(channel) ->
35+
Logger.warning("Delivery attempt #{attempt_id}: missing event or channel")
36+
:ok
37+
38+
not channel.enabled ->
39+
Logger.info("Delivery attempt #{attempt_id}: channel disabled, skipping")
40+
41+
attempt
42+
|> Ecto.Changeset.change(%{status: "skipped"})
43+
|> Repo.update!()
44+
45+
:ok
46+
47+
true ->
48+
execute_delivery(attempt, event, channel)
3849
end
3950
else
4051
:ok

lib/sentinel_cp_web/components/core_components.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,19 @@ defmodule SentinelCpWeb.CoreComponents do
463463
"rollout" -> {"RO", "badge-warning"}
464464
"project" -> {"PJ", "badge-primary"}
465465
"org" -> {"OR", "badge-secondary"}
466+
"service" -> {"SV", "badge-info"}
467+
"certificate" -> {"CT", "badge-success"}
468+
"middleware" -> {"MW", "badge-warning"}
469+
"plugin" -> {"PL", "badge-primary"}
470+
"plugin_version" -> {"PV", "badge-primary"}
471+
"environment" -> {"EN", "badge-info"}
472+
"upstream" -> {"UP", "badge-secondary"}
473+
"policy" -> {"PO", "badge-warning"}
474+
"notification_channel" -> {"NC", "badge-info"}
475+
"notification_rule" -> {"NR", "badge-secondary"}
476+
"waf" -> {"WF", "badge-error"}
477+
"api_key" -> {"AK", "badge-warning"}
478+
"user" -> {"US", "badge-info"}
466479
_ -> {"??", "badge-ghost"}
467480
end
468481

lib/sentinel_cp_web/components/layouts.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ defmodule SentinelCpWeb.Layouts do
176176
current={@path}
177177
match="/waf"
178178
/>
179+
<.sidebar_link
180+
path={~p"/orgs/#{@org_slug}/projects/#{@project_slug}/analytics"}
181+
icon="hero-chart-bar"
182+
label="Analytics"
183+
current={@path}
184+
match="/analytics"
185+
/>
179186
180187
<div class="sidebar-section-title mt-4">Settings</div>
181188
<.sidebar_link

lib/sentinel_cp_web/controllers/api/plugin_controller.ex

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,6 @@ defmodule SentinelCpWeb.Api.PluginController do
391391
%{
392392
id: version.id,
393393
version: version.version,
394-
storage_key: version.storage_key,
395394
checksum: version.checksum,
396395
file_size: version.file_size,
397396
changelog: version.changelog,

lib/sentinel_cp_web/live/notifications_live/channel_edit.ex

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
defmodule SentinelCpWeb.NotificationsLive.ChannelEdit do
22
use SentinelCpWeb, :live_view
33

4-
alias SentinelCp.{Audit, Events, Orgs, Projects}
4+
import SentinelCpWeb.NotificationsLive.Helpers
5+
6+
alias SentinelCp.{Audit, Events, Projects}
57

68
@impl true
79
def mount(%{"project_slug" => slug, "id" => id} = params, _session, socket) do
@@ -50,7 +52,7 @@ defmodule SentinelCpWeb.NotificationsLive.ChannelEdit do
5052
{:noreply,
5153
socket
5254
|> put_flash(:info, "Channel updated.")
53-
|> push_navigate(to: show_path(socket.assigns.org, project, updated))}
55+
|> push_navigate(to: channel_show_path(socket.assigns.org, project, updated))}
5456

5557
{:error, %Ecto.Changeset{} = changeset} ->
5658
errors =
@@ -171,7 +173,7 @@ defmodule SentinelCpWeb.NotificationsLive.ChannelEdit do
171173
172174
<div class="flex gap-2 pt-4">
173175
<button type="submit" class="btn btn-primary btn-sm">Save Changes</button>
174-
<.link navigate={show_path(@org, @project, @channel)} class="btn btn-ghost btn-sm">
176+
<.link navigate={channel_show_path(@org, @project, @channel)} class="btn btn-ghost btn-sm">
175177
Cancel
176178
</.link>
177179
</div>
@@ -180,28 +182,4 @@ defmodule SentinelCpWeb.NotificationsLive.ChannelEdit do
180182
</div>
181183
"""
182184
end
183-
184-
defp resolve_org(%{"org_slug" => slug}), do: Orgs.get_org_by_slug(slug)
185-
defp resolve_org(_), do: nil
186-
187-
defp build_config("slack", params), do: %{"webhook_url" => params["webhook_url"] || ""}
188-
defp build_config("pagerduty", params), do: %{"routing_key" => params["routing_key"] || ""}
189-
190-
defp build_config("email", params) do
191-
config = %{"to" => params["to"] || ""}
192-
193-
if params["from"] && params["from"] != "",
194-
do: Map.put(config, "from", params["from"]),
195-
else: config
196-
end
197-
198-
defp build_config("teams", params), do: %{"webhook_url" => params["webhook_url"] || ""}
199-
defp build_config("webhook", params), do: %{"url" => params["url"] || ""}
200-
defp build_config(_, _), do: %{}
201-
202-
defp show_path(%{slug: org_slug}, project, channel),
203-
do: ~p"/orgs/#{org_slug}/projects/#{project.slug}/notifications/channels/#{channel.id}"
204-
205-
defp show_path(nil, project, channel),
206-
do: ~p"/projects/#{project.slug}/notifications/channels/#{channel.id}"
207185
end

lib/sentinel_cp_web/live/notifications_live/channel_new.ex

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
defmodule SentinelCpWeb.NotificationsLive.ChannelNew do
22
use SentinelCpWeb, :live_view
33

4-
alias SentinelCp.{Audit, Events, Orgs, Projects}
4+
import SentinelCpWeb.NotificationsLive.Helpers
5+
6+
alias SentinelCp.{Audit, Events, Projects}
57
alias SentinelCp.Events.Channel
68

79
@impl true
@@ -56,7 +58,7 @@ defmodule SentinelCpWeb.NotificationsLive.ChannelNew do
5658
{:noreply,
5759
socket
5860
|> put_flash(:info, "Channel created.")
59-
|> push_navigate(to: show_path(socket.assigns.org, project, channel))}
61+
|> push_navigate(to: channel_show_path(socket.assigns.org, project, channel))}
6062

6163
{:error, %Ecto.Changeset{} = changeset} ->
6264
errors =
@@ -194,34 +196,4 @@ defmodule SentinelCpWeb.NotificationsLive.ChannelNew do
194196
</div>
195197
"""
196198
end
197-
198-
defp resolve_org(%{"org_slug" => slug}), do: Orgs.get_org_by_slug(slug)
199-
defp resolve_org(_), do: nil
200-
201-
defp build_config("slack", params), do: %{"webhook_url" => params["webhook_url"] || ""}
202-
defp build_config("pagerduty", params), do: %{"routing_key" => params["routing_key"] || ""}
203-
204-
defp build_config("email", params) do
205-
config = %{"to" => params["to"] || ""}
206-
207-
if params["from"] && params["from"] != "",
208-
do: Map.put(config, "from", params["from"]),
209-
else: config
210-
end
211-
212-
defp build_config("teams", params), do: %{"webhook_url" => params["webhook_url"] || ""}
213-
defp build_config("webhook", params), do: %{"url" => params["url"] || ""}
214-
defp build_config(_, _), do: %{}
215-
216-
defp channels_path(%{slug: org_slug}, project),
217-
do: ~p"/orgs/#{org_slug}/projects/#{project.slug}/notifications/channels"
218-
219-
defp channels_path(nil, project),
220-
do: ~p"/projects/#{project.slug}/notifications/channels"
221-
222-
defp show_path(%{slug: org_slug}, project, channel),
223-
do: ~p"/orgs/#{org_slug}/projects/#{project.slug}/notifications/channels/#{channel.id}"
224-
225-
defp show_path(nil, project, channel),
226-
do: ~p"/projects/#{project.slug}/notifications/channels/#{channel.id}"
227199
end

0 commit comments

Comments
 (0)