neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
Fix 500 on the run page for anyone who isn't an admin
570765b · Gabriel Morell · 2026-09-12 05:21
Message
{commit_body(@commit)}
Files changed
modified
lib/git_gud_web/live/org_live/actions.ex
+1
−1
@@ -30,7 +30,7 @@ defmodule GitGudWeb.OrgLive.Actions do
| 30 | 30 | socket |
| 31 | 31 | |> assign(:org, org) |
| 32 | 32 | |> assign(:viewer, user) |
| 33 | − |> assign(:is_admin?, user && Organizations.org_admin?(org, user)) | |
| 33 | + |> assign(:is_admin?, !!(user && Organizations.org_admin?(org, user))) | |
| 34 | 34 | |> assign(:repo_ids, repo_ids) |
| 35 | 35 | |> assign(:filter, :active) |
| 36 | 36 | |> assign(:page_title, "CI — @#{org.handle}") |
modified
lib/git_gud_web/live/org_live/show.ex
+2
−2
@@ -31,8 +31,8 @@ defmodule GitGudWeb.OrgLive.Show do
| 31 | 31 | socket |
| 32 | 32 | |> assign(:org, org) |
| 33 | 33 | |> assign(:viewer, user) |
| 34 | − |> assign(:is_admin?, user && Organizations.org_admin?(org, user)) | |
| 35 | − |> assign(:is_member?, user && Organizations.member?(org, user)) | |
| 34 | + |> assign(:is_admin?, !!(user && Organizations.org_admin?(org, user))) | |
| 35 | + |> assign(:is_member?, !!(user && Organizations.member?(org, user))) | |
| 36 | 36 | |> assign(:teams, Organizations.list_teams(org)) |
| 37 | 37 | |> assign(:repos, repos) |
| 38 | 38 | |> assign(:ci_status, Workflows.latest_run_status_by_repo(Enum.map(repos, & &1.id))) |
modified
lib/git_gud_web/live/repo_live/header.ex
+5
−1
@@ -179,7 +179,11 @@ defmodule GitGudWeb.RepoLive.Header do
| 179 | 179 | |
| 180 | 180 | socket |
| 181 | 181 | |> Phoenix.Component.assign(:has_packages?, Packages.list_packages(repo) != []) |
| 182 | − |> Phoenix.Component.assign(:can_admin?, user && admin?(user, repo)) | |
| 182 | + # `!!` is load-bearing: `user && admin?(...)` is nil for a signed-out | |
| 183 | + # visitor, and a page that writes `@can_admin? and ...` then raises | |
| 184 | + # BadBooleanError for exactly the people who are allowed to look but | |
| 185 | + # not to touch. The attr is declared boolean; assign one. | |
| 186 | + |> Phoenix.Component.assign(:can_admin?, !!(user && admin?(user, repo))) | |
| 183 | 187 | |> Phoenix.Component.assign(:latest_run, Workflows.latest_run(repo)) |
| 184 | 188 | |> Phoenix.Component.assign(:open_pulls, PullRequests.count_open(repo)) |
| 185 | 189 | |> Phoenix.Component.assign(:repo_tags, GitGud.Tags.tag_names(repo)) |
modified
lib/git_gud_web/live/workflow_live/show.ex
+6
−3
@@ -27,7 +27,10 @@ defmodule GitGudWeb.WorkflowLive.Show do
| 27 | 27 | |> assign(:run, run) |
| 28 | 28 | |> assign(:artifacts, Workflows.list_artifacts(run)) |
| 29 | 29 | |> assign(:expanded_step, nil) |
| 30 | − |> assign(:can_admin?, user && admin?(user, repo)) | |
| 30 | + # Boolean, not `nil` — this page writes `@can_admin? and ...`, | |
| 31 | + # which raises BadBooleanError on a nil. See | |
| 32 | + # `RepoLive.Header.assign_chrome/2`. | |
| 33 | + |> assign(:can_admin?, !!(user && admin?(user, repo))) | |
| 31 | 34 | |> assign(:page_title, "Run ##{run.number}") |
| 32 | 35 | |> assign(:now, DateTime.utc_now()) |
| 33 | 36 | |> assign(:ticking?, false) |
@@ -391,8 +394,8 @@ defmodule GitGudWeb.WorkflowLive.Show do
| 391 | 394 | <span class="font-mono">{a.name}</span> |
| 392 | 395 | <span class="text-xs opacity-60"> |
| 393 | 396 | {format_size(a.size)} |
| 394 | − <span :if={a.content_type}> · {a.content_type}</span> | |
| 395 | − <span :if={a.inserted_at}> · {format_dt(a.inserted_at)}</span> | |
| 397 | + <span :if={a.content_type}> · {a.content_type}</span> | |
| 398 | + <span :if={a.inserted_at}> · {format_dt(a.inserted_at)}</span> | |
| 396 | 399 | </span> |
| 397 | 400 | </div> |
| 398 | 401 | <a |
added
test/git_gud_web/live/workflow_run_anonymous_test.exs
+84
−0
@@ -0,0 +1,84 @@
| 1 | +defmodule GitGudWeb.WorkflowRunAnonymousTest do | |
| 2 | + @moduledoc """ | |
| 3 | + A signed-out visitor loading a public repository's run page. | |
| 4 | + | |
| 5 | + This 500'd in production: `assign(:can_admin?, user && admin?(...))` | |
| 6 | + is `nil` — not `false` — with no user, and the page writes | |
| 7 | + `@can_admin? and job.status in [...]`, which raises BadBooleanError on | |
| 8 | + a nil left-hand side. The people who could see it were exactly the | |
| 9 | + people who couldn't log in. | |
| 10 | + """ | |
| 11 | + use GitGudWeb.ConnCase, async: false | |
| 12 | + | |
| 13 | + import Phoenix.LiveViewTest | |
| 14 | + import GitGud.ForgeFixtures | |
| 15 | + | |
| 16 | + alias GitGud.Repo | |
| 17 | + alias GitGud.Repositories.Storage | |
| 18 | + alias GitGud.Workflows.WorkflowJob | |
| 19 | + alias GitGud.Workflows.WorkflowRun | |
| 20 | + | |
| 21 | + setup do | |
| 22 | + {user, repo} = repository_fixture(%{visibility: "public"}) | |
| 23 | + repo = Repo.preload(repo, [:owner, :organization]) | |
| 24 | + | |
| 25 | + run = | |
| 26 | + %WorkflowRun{repository_id: repo.id} | |
| 27 | + |> WorkflowRun.create_changeset(%{ | |
| 28 | + "number" => 1, | |
| 29 | + "workflow_path" => ".forgejo/workflows/build.yml", | |
| 30 | + "workflow_name" => "build", | |
| 31 | + "trigger" => "push", | |
| 32 | + "head_ref" => "refs/heads/main", | |
| 33 | + "head_sha" => :crypto.strong_rand_bytes(20), | |
| 34 | + "workflow_doc" => %{"name" => "build", "jobs" => %{"build" => %{"steps" => []}}} | |
| 35 | + }) | |
| 36 | + |> Repo.insert!() | |
| 37 | + | |
| 38 | + # A finished job: the re-run button is only rendered for terminal | |
| 39 | + # statuses, which is where the nil got used as a boolean. | |
| 40 | + %WorkflowJob{workflow_run_id: run.id} | |
| 41 | + |> WorkflowJob.create_changeset(%{ | |
| 42 | + job_id: "build", | |
| 43 | + name: "build", | |
| 44 | + runs_on: %{"labels" => ["self-hosted"]}, | |
| 45 | + needs: [], | |
| 46 | + definition: %{"steps" => []} | |
| 47 | + }) | |
| 48 | + |> Ecto.Changeset.change(status: "failure") | |
| 49 | + |> Repo.insert!() | |
| 50 | + | |
| 51 | + %{ | |
| 52 | + user: user, | |
| 53 | + repo: repo, | |
| 54 | + path: "/r/#{Storage.repo_handle(repo)}/#{repo.name}/actions/#{run.number}" | |
| 55 | + } | |
| 56 | + end | |
| 57 | + | |
| 58 | + test "renders for a signed-out visitor", %{conn: conn, path: path} do | |
| 59 | + {:ok, _lv, html} = live(conn, path) | |
| 60 | + | |
| 61 | + assert html =~ "build" | |
| 62 | + # Visible, but not offered the admin-only action. | |
| 63 | + refute html =~ "Re-run job" | |
| 64 | + end | |
| 65 | + | |
| 66 | + test "renders for a signed-in non-admin", %{conn: conn, path: path} do | |
| 67 | + other = GitGud.AccountsFixtures.user_fixture() | |
| 68 | + | |
| 69 | + {:ok, _lv, html} = conn |> log_in_user(other) |> live(path) | |
| 70 | + | |
| 71 | + assert html =~ "build" | |
| 72 | + refute html =~ "Re-run job" | |
| 73 | + end | |
| 74 | + | |
| 75 | + test "offers the re-run to someone who can administer the repo", %{ | |
| 76 | + conn: conn, | |
| 77 | + user: user, | |
| 78 | + path: path | |
| 79 | + } do | |
| 80 | + {:ok, _lv, html} = conn |> log_in_user(user) |> live(path) | |
| 81 | + | |
| 82 | + assert html =~ "Re-run job" | |
| 83 | + end | |
| 84 | +end |
Parents: af8d19b