Pythiae: the pythiae pages only reach the user's own

099a0e1 · Gabe Morell · 2026-11-04 22:08

12 files +105 -16
Message
{commit_body(@commit)}

Files changed

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_pythiae_live/index.ex
+5 −2
@@ -20,7 +20,7 @@ defmodule RoomSanctumWeb.PythiaeLive.Index do
20 20 defp apply_action(socket, :edit, %{"id" => id}) do
21 21 socket
22 22 |> assign(:page_title, "Edit Pythiae")
23 − |> assign(:pythiae, Configuration.get_pythiae!(id))
23 + |> assign(:pythiae, Configuration.get_pythiae!(owner(socket), id))
24 24 end
25 25
26 26 defp apply_action(socket, :new, _params) do
@@ -42,7 +42,7 @@ defmodule RoomSanctumWeb.PythiaeLive.Index do
42 42
43 43 @impl true
44 44 def handle_event("delete", %{"id" => id}, socket) do
45 − pythiae = Configuration.get_pythiae!(id)
45 + pythiae = Configuration.get_pythiae!(owner(socket), id)
46 46 {:ok, _} = Configuration.delete_pythiae(pythiae)
47 47
48 48 {:noreply, stream_delete(socket, :cfg_pythiae, pythiae)}
@@ -56,4 +56,7 @@ defmodule RoomSanctumWeb.PythiaeLive.Index do
56 56 defp list_cfg_pythiae(uid) do
57 57 Configuration.list_cfg_pythiae({:user, uid})
58 58 end
59 +
60 + # Ids from the URL or the client are looked up among the user's own.
61 + defp owner(socket), do: {:user, socket.assigns.current_user.id}
59 62 end
modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_pythiae_live/show.ex
+24 −14
@@ -22,7 +22,7 @@ defmodule RoomSanctumWeb.PythiaeLive.Show do
22 22 {:noreply,
23 23 socket
24 24 |> assign(:page_title, page_title(socket.assigns.live_action))
25 − |> assign(:pythiae, Configuration.get_pythiae!(id))
25 + |> assign(:pythiae, Configuration.get_pythiae!(owner(socket), id))
26 26 |> assign(:visions, Configuration.list_visions({:user, socket.assigns.current_user.id}))
27 27 |> assign(:foci, Configuration.list_focis({:user, socket.assigns.current_user.id}))
28 28 |> assign(:ankyra, Accounts.list_users_rabbit({:user, socket.assigns.current_user.id}))
@@ -153,35 +153,31 @@ defmodule RoomSanctumWeb.PythiaeLive.Show do
153 153 end
154 154
155 155 @impl true
156 − def handle_event("do-publish", %{"id" => id}, socket) do
157 − RoomSanctum.Worker.Pythiae.query_current_now(id)
156 + # The buttons send the page's own pythiae id; the page's is what is used, so
157 + # an event naming another pythiae does not reach it.
158 + def handle_event("do-publish", _params, socket) do
159 + RoomSanctum.Worker.Pythiae.query_current_now(socket.assigns.pythiae.id)
158 160 {:noreply, socket}
159 161 end
160 162
161 − def handle_event("do-publish-img", %{"id" => id}, socket) do
162 − RoomSanctum.Worker.Pythiae.publish_img(id)
163 + def handle_event("do-publish-img", _params, socket) do
164 + RoomSanctum.Worker.Pythiae.publish_img(socket.assigns.pythiae.id)
163 165 {:noreply, socket}
164 166 end
165 167
166 168 def handle_event("set-current-vision", %{"id" => vision_id}, socket) do
167 − pythiae = socket.assigns.pythiae
168 − {:ok, updated} = Configuration.update_pythiae(pythiae, %{curr_vision: vision_id})
169 − {:noreply, socket |> assign(:pythiae, updated)}
169 + set_current(socket, :curr_vision, vision_id, socket.assigns.visions)
170 170 end
171 171
172 172 def handle_event("set-current-foci", %{"id" => foci_id}, socket) do
173 − pythiae = socket.assigns.pythiae
174 − {:ok, updated} = Configuration.update_pythiae(pythiae, %{curr_foci: foci_id})
175 − {:noreply, socket |> assign(:pythiae, updated)}
173 + set_current(socket, :curr_foci, foci_id, socket.assigns.foci)
176 174 end
177 175
178 176 # Setting a foci was one-way: every foci had a button to become the current
179 177 # one and none of them had a way back, so a Pythiae that had ever been
180 178 # pointed at a place stayed pointed at it.
181 179 def handle_event("set-current-plani", %{"id" => plani_id}, socket) do
182 − pythiae = socket.assigns.pythiae
183 − {:ok, updated} = Configuration.update_pythiae(pythiae, %{curr_plani: plani_id})
184 − {:noreply, socket |> assign(:pythiae, updated)}
180 + set_current(socket, :curr_plani, plani_id, socket.assigns.plani)
185 181 end
186 182
187 183 # Back to the vision. Exclusive rather than additive, so unsetting is the
@@ -197,4 +193,18 @@ defmodule RoomSanctumWeb.PythiaeLive.Show do
197 193 {:ok, updated} = Configuration.update_pythiae(pythiae, %{curr_foci: nil})
198 194 {:noreply, socket |> assign(:pythiae, updated)}
199 195 end
196 +
197 + # The id is the client's, so it has to be one of the user's own that the
198 + # page listed -- not another user's vision, foci or plani.
199 + defp set_current(socket, field, id, owned) do
200 + if Enum.any?(owned, &(to_string(&1.id) == to_string(id))) do
201 + {:ok, updated} = Configuration.update_pythiae(socket.assigns.pythiae, %{field => id})
202 + {:noreply, socket |> assign(:pythiae, updated)}
203 + else
204 + {:noreply, put_flash(socket, :error, "That is not yours to show")}
205 + end
206 + end
207 +
208 + # The pythiae comes from the URL, so it is looked up among the user's own.
209 + defp owner(socket), do: {:user, socket.assigns.current_user.id}
200 210 end
modified apps/room_sanctum/test/room_sanctum_web/live/ownership_test.exs
+76 −0
@@ -311,4 +311,80 @@ defmodule RoomSanctumWeb.OwnershipTest do
311 311 assert Configuration.get_queries(:source, their_source.id) == []
312 312 end
313 313 end
314 +
315 + # users_rabbit comes from the hermes repo's migrations and is not in the
316 + # sanctum test database, and the pages that list Ankyra need it to mount. It
317 + # is made inside the test's sandbox transaction, so it goes when the test does.
318 + defp users_rabbit!(_ctx) do
319 + Repo.query!("""
320 + CREATE TABLE IF NOT EXISTS users_rabbit (
321 + id bigserial PRIMARY KEY,
322 + password varchar,
323 + username uuid,
324 + topic varchar,
325 + client_ids varchar[] DEFAULT '{}',
326 + auto_register_clients boolean DEFAULT true,
327 + user_id bigint,
328 + inserted_at timestamp,
329 + updated_at timestamp
330 + )
331 + """)
332 +
333 + :ok
334 + end
335 +
336 + describe "the pythiae pages" do
337 + setup :users_rabbit!
338 +
339 + test "will not show another user's pythiae", ctx do
340 + theirs = pythiae_for(ctx.them, [])
341 +
342 + assert_raise Ecto.NoResultsError, fn ->
343 + live(ctx.conn, Routes.pythiae_show_path(ctx.conn, :show, theirs))
344 + end
345 + end
346 +
347 + test "will not open another user's pythiae for editing", ctx do
348 + theirs = pythiae_for(ctx.them, [])
349 +
350 + assert_raise Ecto.NoResultsError, fn ->
351 + live(ctx.conn, Routes.pythiae_index_path(ctx.conn, :edit, theirs))
352 + end
353 + end
354 +
355 + test "will not delete another user's pythiae", ctx do
356 + theirs = pythiae_for(ctx.them, [])
357 + {:ok, live, _html} = live(ctx.conn, Routes.pythiae_index_path(ctx.conn, :index))
358 +
359 + forge(live, "delete", %{"id" => to_string(theirs.id)})
360 +
361 + assert Repo.reload(theirs)
362 + end
363 +
364 + test "will not point the pythiae at another user's vision or foci", ctx do
365 + mine = vision_for(ctx.me, "My dash")
366 + screen = pythiae_for(ctx.me, [mine.id])
367 + their_vision = vision_for(ctx.them, "Their dash")
368 + their_foci = foci_for(ctx.them)
369 + {:ok, live, _html} = live(ctx.conn, Routes.pythiae_show_path(ctx.conn, :show, screen))
370 +
371 + render_click(live, "set-current-vision", %{"id" => to_string(their_vision.id)})
372 + render_click(live, "set-current-foci", %{"id" => to_string(their_foci.id)})
373 +
374 + screen = Repo.reload!(screen)
375 + assert screen.curr_vision == mine.id
376 + assert screen.curr_foci == nil
377 + end
378 +
379 + test "still points the pythiae at the user's own vision", ctx do
380 + first = vision_for(ctx.me, "First")
381 + second = vision_for(ctx.me, "Second")
382 + screen = pythiae_for(ctx.me, [first.id, second.id])
383 + {:ok, live, _html} = live(ctx.conn, Routes.pythiae_show_path(ctx.conn, :show, screen))
384 +
385 + render_click(live, "set-current-vision", %{"id" => to_string(second.id)})
386 +
387 + assert Repo.reload!(screen).curr_vision == second.id
388 + end
389 + end
314 390 end

Parents: 3d51658