Keryx: a keryx only carries the owner's own queries

2d73edf · Gabe Morell · 2026-11-04 22:34

10 files +72 -0
Message
{commit_body(@commit)}

Files changed

modified apps/room_sanctum/lib/room_sanctum/configuration/keryx.ex
+5 −0
@@ -2,6 +2,8 @@ defmodule RoomSanctum.Configuration.Keryx do
2 2 use Ecto.Schema
3 3 import Ecto.Changeset
4 4
5 + alias RoomSanctum.Configuration.{Ownership, Query}
6 +
5 7 schema "keryxiae" do
6 8 field :name, :string
7 9 field :ttl, :integer
@@ -18,6 +20,9 @@ defmodule RoomSanctum.Configuration.Keryx do
18 20 |> foreign_key_constraint(:user_id)
19 21 |> validate_required([:name, :query_ids, :ttl])
20 22 |> unique_constraint(:name)
23 + # The worker publishes every one of these queries' results, so another
24 + # user's would be published to this keryx's subscribers.
25 + |> Ownership.validate_owned(:query_ids, Query)
21 26 end
22 27
23 28 @doc """
added apps/room_sanctum/test/room_sanctum_web/live/keryx_owned_ids_test.exs
+67 −0
@@ -0,0 +1,67 @@
1 +defmodule RoomSanctumWeb.KeryxOwnedIdsTest do
2 + @moduledoc """
3 + A keryx publishes its queries' results to whoever subscribes to it, so it
4 + may only carry its owner's queries. The form only offers those, but sends
5 + them as hidden inputs, and a forged one is refused.
6 + """
7 + use RoomSanctumWeb.ConnCase
8 +
9 + import Phoenix.LiveViewTest
10 + import RoomSanctum.DataCase, only: [errors_on: 1]
11 + import RoomSanctum.OwnershipFixtures
12 +
13 + alias RoomSanctum.{Configuration, Repo}
14 + alias RoomSanctum.Configuration.Keryx
15 +
16 + setup %{conn: conn} do
17 + me = owner()
18 + %{conn: log_in_user(conn, me), me: me}
19 + end
20 +
21 + defp submit(conn, query_ids) do
22 + {:ok, live, _html} = live(conn, Routes.keryx_index_path(conn, :new))
23 +
24 + live
25 + |> form("#keryx-form")
26 + |> render_submit(%{
27 + "keryx" => %{
28 + "name" => "herald",
29 + "ttl" => "60",
30 + "query_ids" => Enum.map(query_ids, &to_string/1)
31 + }
32 + })
33 + end
34 +
35 + test "the form saves the owner's own queries", ctx do
36 + mine = query_for(ctx.me)
37 + submit(ctx.conn, [mine.id])
38 +
39 + assert [%Keryx{query_ids: [id]}] = Repo.all(Keryx)
40 + assert id == mine.id
41 + end
42 +
43 + test "the form refuses another user's query", ctx do
44 + theirs = query_for(owner())
45 + submit(ctx.conn, [query_for(ctx.me).id, theirs.id])
46 +
47 + assert Repo.all(Keryx) == []
48 + end
49 +
50 + test "an existing keryx cannot be given another user's query", ctx do
51 + mine = query_for(ctx.me)
52 +
53 + {:ok, keryx} =
54 + Configuration.create_keryx(%{
55 + name: "herald",
56 + ttl: 60,
57 + query_ids: [mine.id],
58 + user_id: ctx.me.id
59 + })
60 +
61 + assert {:error, changeset} =
62 + Configuration.update_keryx(keryx, %{query_ids: [mine.id, query_for(owner()).id]})
63 +
64 + assert %{query_ids: [_]} = errors_on(changeset)
65 + assert {:ok, _} = Configuration.update_keryx(keryx, %{name: "renamed"})
66 + end
67 +end

Parents: 31b25cb