neiam
/apollos_crib
It's Apollo's Crib
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions failure · Badges · Packages
⭐
Log in to mark this repository.
Focis: the foci pages only reach the user's own
3d51658 · Gabe Morell · 2026-11-04 22:05
Message
{commit_body(@commit)}
Files changed
modified
apps/room_sanctum/lib/room_sanctum/configuration.ex
+3
−0
@@ -552,6 +552,9 @@ defmodule RoomSanctum.Configuration do
| 552 | 552 | """ |
| 553 | 553 | def get_foci!(id), do: Repo.get!(Foci, id) |
| 554 | 554 | |
| 555 | + # Someone else's foci raises the not-found a missing one does. | |
| 556 | + def get_foci!({:user, uid}, id), do: Repo.get_by!(Foci, id: id, user_id: uid) | |
| 557 | + | |
| 555 | 558 | @doc """ |
| 556 | 559 | Where a query is being asked from. |
| 557 | 560 |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_foci_live/index.ex
+6
−3
@@ -20,13 +20,13 @@ defmodule RoomSanctumWeb.FociLive.Index do
| 20 | 20 | defp apply_action(socket, :edit, %{"id" => id}) do |
| 21 | 21 | socket |
| 22 | 22 | |> assign(:page_title, "Modify Foci") |
| 23 | − |> assign(:foci, Configuration.get_foci!(id)) | |
| 23 | + |> assign(:foci, Configuration.get_foci!(owner(socket), id)) | |
| 24 | 24 | end |
| 25 | 25 | |
| 26 | 26 | defp apply_action(socket, :edit_coords, %{"id" => id}) do |
| 27 | 27 | socket |
| 28 | 28 | |> assign(:page_title, "Modify Foci (Coordinates)") |
| 29 | − |> assign(:foci, Configuration.get_foci!(id)) | |
| 29 | + |> assign(:foci, Configuration.get_foci!(owner(socket), id)) | |
| 30 | 30 | end |
| 31 | 31 | |
| 32 | 32 | defp apply_action(socket, :new, _params) do |
@@ -59,7 +59,7 @@ defmodule RoomSanctumWeb.FociLive.Index do
| 59 | 59 | |
| 60 | 60 | @impl true |
| 61 | 61 | def handle_event("delete", %{"id" => id}, socket) do |
| 62 | − foci = Configuration.get_foci!(id) | |
| 62 | + foci = Configuration.get_foci!(owner(socket), id) | |
| 63 | 63 | {:ok, _} = Configuration.delete_foci(foci) |
| 64 | 64 | |
| 65 | 65 | {:noreply, stream_delete(socket, :focis, foci)} |
@@ -72,4 +72,7 @@ defmodule RoomSanctumWeb.FociLive.Index do
| 72 | 72 | defp list_focis(uid) do |
| 73 | 73 | Configuration.list_focis({:user, uid}) |
| 74 | 74 | end |
| 75 | + | |
| 76 | + # Ids from the URL or the client are looked up among the user's own. | |
| 77 | + defp owner(socket), do: {:user, socket.assigns.current_user.id} | |
| 75 | 78 | end |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_foci_live/show.ex
+11
−4
@@ -12,7 +12,7 @@ defmodule RoomSanctumWeb.FociLive.Show do
| 12 | 12 | |
| 13 | 13 | @impl true |
| 14 | 14 | def handle_params(%{"id" => id}, _, socket) do |
| 15 | − foci = Configuration.get_foci!(id) | |
| 15 | + foci = Configuration.get_foci!(owner(socket), id) | |
| 16 | 16 | |
| 17 | 17 | {:noreply, |
| 18 | 18 | socket |
@@ -31,11 +31,15 @@ defmodule RoomSanctumWeb.FociLive.Show do
| 31 | 31 | foci = socket.assigns.foci |
| 32 | 32 | existing = socket.assigns.existing_queries_by_source |
| 33 | 33 | |
| 34 | + # Only sources the page offered -- the user's own, of a foci type. The ids | |
| 35 | + # are the client's, and any other is skipped rather than looked up. | |
| 36 | + offered = Map.new(socket.assigns.foci_sources, &{&1.id, &1}) | |
| 37 | + | |
| 34 | 38 | results = |
| 35 | 39 | source_ids |
| 36 | − |> Enum.map(&Configuration.get_source!/1) | |
| 37 | − |> Enum.filter(&(&1.type in @foci_source_types)) | |
| 38 | − |> Enum.reject(&Map.has_key?(existing, &1.id)) | |
| 40 | + |> Enum.reject(&Map.has_key?(existing, &1)) | |
| 41 | + |> Enum.map(&Map.get(offered, &1)) | |
| 42 | + |> Enum.reject(&is_nil/1) | |
| 39 | 43 | |> Enum.map(&create_foci_query(&1, foci)) |
| 40 | 44 | |
| 41 | 45 | count = Enum.count(results, fn {status, _} -> status == :ok end) |
@@ -106,4 +110,7 @@ defmodule RoomSanctumWeb.FociLive.Show do
| 106 | 110 | defp getlatlng(%{:place => place}) do |
| 107 | 111 | place |> Map.get(:coordinates, {}) |> Tuple.to_list() |> RoomSanctum.JSON.encode!() |
| 108 | 112 | end |
| 113 | + | |
| 114 | + # The foci comes from the URL, so it is looked up among the user's own. | |
| 115 | + defp owner(socket), do: {:user, socket.assigns.current_user.id} | |
| 109 | 116 | end |
modified
apps/room_sanctum/test/room_sanctum_web/live/ownership_test.exs
+62
−0
@@ -249,4 +249,66 @@ defmodule RoomSanctumWeb.OwnershipTest do
| 249 | 249 | assert import_jobs(theirs.id) == ["executing"] |
| 250 | 250 | end |
| 251 | 251 | end |
| 252 | + | |
| 253 | + defp foci_for(user) do | |
| 254 | + {:ok, foci} = | |
| 255 | + Configuration.create_foci(%{ | |
| 256 | + name: "Home", | |
| 257 | + user_id: user.id, | |
| 258 | + place: %Geo.Point{coordinates: {-71.0589, 42.3601}, srid: 4326} | |
| 259 | + }) | |
| 260 | + | |
| 261 | + foci | |
| 262 | + end | |
| 263 | + | |
| 264 | + describe "the foci pages" do | |
| 265 | + test "will not show another user's foci", ctx do | |
| 266 | + theirs = foci_for(ctx.them) | |
| 267 | + | |
| 268 | + assert_raise Ecto.NoResultsError, fn -> | |
| 269 | + live(ctx.conn, Routes.foci_show_path(ctx.conn, :show, theirs)) | |
| 270 | + end | |
| 271 | + end | |
| 272 | + | |
| 273 | + test "will not open another user's foci for editing", ctx do | |
| 274 | + theirs = foci_for(ctx.them) | |
| 275 | + | |
| 276 | + for action <- [:edit, :edit_coords] do | |
| 277 | + assert_raise Ecto.NoResultsError, fn -> | |
| 278 | + live(ctx.conn, Routes.foci_index_path(ctx.conn, action, theirs)) | |
| 279 | + end | |
| 280 | + end | |
| 281 | + end | |
| 282 | + | |
| 283 | + test "will not delete another user's foci", ctx do | |
| 284 | + theirs = foci_for(ctx.them) | |
| 285 | + {:ok, live, _html} = live(ctx.conn, Routes.foci_index_path(ctx.conn, :index)) | |
| 286 | + | |
| 287 | + forge(live, "delete", %{"id" => to_string(theirs.id)}) | |
| 288 | + | |
| 289 | + assert Repo.reload(theirs) | |
| 290 | + end | |
| 291 | + | |
| 292 | + test "will not make queries on another user's source", ctx do | |
| 293 | + mine = foci_for(ctx.me) | |
| 294 | + | |
| 295 | + {:ok, their_source} = | |
| 296 | + Configuration.create_source(%{ | |
| 297 | + name: "Their sky", | |
| 298 | + notes: "", | |
| 299 | + type: :ephem, | |
| 300 | + enabled: true, | |
| 301 | + user_id: ctx.them.id, | |
| 302 | + config: %{"__type__" => "ephem"} | |
| 303 | + }) | |
| 304 | + | |
| 305 | + {:ok, live, _html} = live(ctx.conn, Routes.foci_show_path(ctx.conn, :show, mine)) | |
| 306 | + | |
| 307 | + render_click(live, "create-queries-for-sources", %{ | |
| 308 | + "source_ids" => [to_string(their_source.id)] | |
| 309 | + }) | |
| 310 | + | |
| 311 | + assert Configuration.get_queries(:source, their_source.id) == [] | |
| 312 | + end | |
| 313 | + end | |
| 252 | 314 | end |
Parents: f92997e