neiam /gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Packages
⭐
Log in to mark this repository.
Put the remaining credential settings pages behind sudo mode
e5de06e · Gabriel Morell · 2026-09-10 15:26
Message
{commit_body(@commit)}
Files changed
modified
lib/git_gud_web/live/personal_access_token_live/index.ex
+10
−2
@@ -6,6 +6,7 @@ defmodule GitGudWeb.PersonalAccessTokenLive.Index do
| 6 | 6 | extensible set, so more can be added later. |
| 7 | 7 | """ |
| 8 | 8 | use GitGudWeb, :live_view |
| 9 | + on_mount {GitGudWeb.UserAuth, {:require_sudo_mode, "/users/settings/tokens"}} | |
| 9 | 10 | |
| 10 | 11 | alias GitGud.Accounts |
| 11 | 12 | alias GitGud.Accounts.PersonalAccessToken |
@@ -117,7 +118,12 @@ defmodule GitGudWeb.PersonalAccessTokenLive.Index do
| 117 | 118 | |
| 118 | 119 | <table class="table table-sm"> |
| 119 | 120 | <thead> |
| 120 | − <tr><th>Name</th><th>Scopes</th><th>Last used</th><th></th></tr> | |
| 121 | + <tr> | |
| 122 | + <th>Name</th> | |
| 123 | + <th>Scopes</th> | |
| 124 | + <th>Last used</th> | |
| 125 | + <th></th> | |
| 126 | + </tr> | |
| 121 | 127 | </thead> |
| 122 | 128 | <tbody> |
| 123 | 129 | <tr :for={t <- @tokens}> |
@@ -132,7 +138,9 @@ defmodule GitGudWeb.PersonalAccessTokenLive.Index do
| 132 | 138 | phx-value-id={t.id} |
| 133 | 139 | data-confirm={"Revoke token #{t.name}? Anything using it loses access."} |
| 134 | 140 | class="btn btn-xs btn-ghost text-error" |
| 135 | − >Revoke</button> | |
| 141 | + > | |
| 142 | + Revoke | |
| 143 | + </button> | |
| 136 | 144 | </td> |
| 137 | 145 | </tr> |
| 138 | 146 | <tr :if={@tokens == []}> |
modified
lib/git_gud_web/live/user_live/invites.ex
+2
−2
@@ -1,5 +1,6 @@
| 1 | 1 | defmodule GitGudWeb.UserLive.Invites do |
| 2 | 2 | use GitGudWeb, :live_view |
| 3 | + on_mount {GitGudWeb.UserAuth, {:require_sudo_mode, "/users/settings/invites"}} | |
| 3 | 4 | |
| 4 | 5 | alias GitGud.Accounts |
| 5 | 6 |
@@ -84,8 +85,7 @@ defmodule GitGudWeb.UserLive.Invites do
| 84 | 85 | id="invites-empty" |
| 85 | 86 | class="hidden only:block text-center text-sm opacity-60 py-8" |
| 86 | 87 | > |
| 87 | − No invites yet. Click <em class="not-italic font-semibold">New invite</em> | |
| 88 | − to create one. | |
| 88 | + No invites yet. Click <em class="not-italic font-semibold">New invite</em> to create one. | |
| 89 | 89 | </li> |
| 90 | 90 | <li |
| 91 | 91 | :for={{dom_id, invite} <- @streams.invites} |
modified
lib/git_gud_web/live/user_live/two_factor_setup.ex
+1
−0
@@ -18,6 +18,7 @@ defmodule GitGudWeb.UserLive.TwoFactorSetup do
| 18 | 18 | """ |
| 19 | 19 | |
| 20 | 20 | use GitGudWeb, :live_view |
| 21 | + on_mount {GitGudWeb.UserAuth, {:require_sudo_mode, "/users/settings/two-factor"}} | |
| 21 | 22 | |
| 22 | 23 | on_mount {GitGudWeb.UserAuth, :require_authenticated} |
| 23 | 24 |
added
test/git_gud_web/live/settings_sudo_test.exs
+74
−0
@@ -0,0 +1,74 @@
| 1 | +defmodule GitGudWeb.SettingsSudoTest do | |
| 2 | + @moduledoc """ | |
| 3 | + Every account-settings page that manages a credential sits behind | |
| 4 | + sudo mode. | |
| 5 | + | |
| 6 | + Sudo is declared per-LiveView via `on_mount`, not in the router, so a | |
| 7 | + new settings page is ungated by default — which is how | |
| 8 | + /users/settings/two-factor, /tokens and /invites came to be reachable | |
| 9 | + with only a session. This test enumerates them so the next one that's | |
| 10 | + added has to be considered. | |
| 11 | + """ | |
| 12 | + | |
| 13 | + use GitGudWeb.ConnCase, async: false | |
| 14 | + | |
| 15 | + import Phoenix.LiveViewTest | |
| 16 | + import GitGud.AccountsFixtures | |
| 17 | + | |
| 18 | + @gated [ | |
| 19 | + "/users/settings", | |
| 20 | + "/users/settings/ssh-keys", | |
| 21 | + "/users/settings/two-factor", | |
| 22 | + "/users/settings/tokens", | |
| 23 | + "/users/settings/invites" | |
| 24 | + ] | |
| 25 | + | |
| 26 | + # `register_and_log_in_user` authenticates far enough back that sudo | |
| 27 | + # has lapsed, which is exactly the state being tested: a live session, | |
| 28 | + # no recent re-authentication. | |
| 29 | + defp stale_conn(conn) do | |
| 30 | + user = user_fixture() | |
| 31 | + log_in_user(conn, user, token_authenticated_at: hours_ago(2)) | |
| 32 | + end | |
| 33 | + | |
| 34 | + defp hours_ago(n), do: DateTime.utc_now(:second) |> DateTime.add(-n * 3600, :second) | |
| 35 | + | |
| 36 | + for path <- @gated do | |
| 37 | + test "#{path} redirects to log-in without recent auth", %{conn: conn} do | |
| 38 | + assert {:error, {:redirect, %{to: to}}} = live(stale_conn(conn), unquote(path)) | |
| 39 | + assert to =~ "/users/log-in" | |
| 40 | + end | |
| 41 | + end | |
| 42 | + | |
| 43 | + # /users/settings predates the tuple form and still uses the bare | |
| 44 | + # one, so it drops you on the default page after re-auth rather than | |
| 45 | + # back where you were. Left alone here — it's a UX wart, not a hole. | |
| 46 | + for path <- @gated -- ["/users/settings"] do | |
| 47 | + test "#{path} carries a return_to so re-auth lands back there", %{conn: conn} do | |
| 48 | + assert {:error, {:redirect, %{to: to}}} = live(stale_conn(conn), unquote(path)) | |
| 49 | + assert to =~ "return_to" | |
| 50 | + end | |
| 51 | + end | |
| 52 | + | |
| 53 | + test "a freshly authenticated session reaches two-factor setup", %{conn: conn} do | |
| 54 | + user = user_fixture() | |
| 55 | + conn = log_in_user(conn, user) | |
| 56 | + | |
| 57 | + {:ok, _lv, html} = live(conn, ~p"/users/settings/two-factor") | |
| 58 | + assert html =~ "factor" or html =~ "Factor" | |
| 59 | + end | |
| 60 | + | |
| 61 | + test "a freshly authenticated session reaches the tokens page", %{conn: conn} do | |
| 62 | + user = user_fixture() | |
| 63 | + conn = log_in_user(conn, user) | |
| 64 | + | |
| 65 | + {:ok, _lv, _html} = live(conn, ~p"/users/settings/tokens") | |
| 66 | + end | |
| 67 | + | |
| 68 | + test "anonymous visitors are bounced before sudo even applies", %{conn: conn} do | |
| 69 | + for path <- @gated do | |
| 70 | + assert {:error, {:redirect, %{to: to}}} = live(conn, path) | |
| 71 | + assert to =~ "/users/log-in" | |
| 72 | + end | |
| 73 | + end | |
| 74 | +end |
Parents: c68e1e7