git-ents.gitmain
⌘K
foforge
commit 2ebfb9a
fix: hide settings edit controls when the server cannot land web edits

A signed-in member was offered the edit form and told "you can edit this repository" even when the server ran without a web signing key or the signed-push gate, so submitting only reached the rejection page. Reflect the server’s actual edit capability in whether the controls are shown.

fix: gate the General edit form on the server’s edit capability fix: tell a member editing is disabled instead of offering a failing form Assisted-by: Claude:claude-opus-4-8

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

crates/git-ents-server/src/web/mod.rs @@ -67,7 +67,7 @@ } if let Some((repo, rel, rest)) = resolve_repo(&state.data_dir, &segments) { - return route(&repo, &rel, rest, host, session).await; + return route(&repo, &rel, rest, host, session, editing_enabled(state)).await; } not_found().into_response() @@ -146,6 +146,14 @@ save_settings(state, &repo, &rel, cookie, body).await } +/// Whether this server can actually land browser edits: it needs the signed-push +/// gate (nonce seed + hooks) and its own signing key, all of which +/// [`write::edit_config`] requires. When any is unset, edit controls are not +/// offered so a member is never told they can edit when a submit would only fail. +fn editing_enabled(state: &AppState) -> bool { + state.cert_nonce_seed.is_some() && state.hooks_dir.is_some() && state.web_signing_key.is_some() +} + /// Whether the request reached us over HTTPS — directly, or through a TLS /// terminator that set `X-Forwarded-Proto`. Gates the cookie `Secure` flag so a /// plain-HTTP development server still works. @@ -259,6 +267,7 @@ rest: &[&str], host: Option<&str>, session: Option<write::SessionSnapshot>, + editing: bool, ) -> Response { let meta = gather_meta(repo, rel).await; match rest.split_first() { @@ -272,7 +281,7 @@ Some((&"issues", &[])) => pages::issues_page(repo, &meta).await.into_response(), Some((&"settings", &[])) => { let auth = resolve_auth(repo, session).await; - pages::settings_page(repo, &meta, auth.as_ref()) + pages::settings_page(repo, &meta, auth.as_ref(), editing) .await .into_response() }
crates/git-ents-server/src/web/pages.rs @@ -814,14 +814,16 @@ .map_err(|err| err.to_string()) } -/// The Settings tab: a read-only projection over the repository's typed meta -/// refs — `refs/meta/config` (General), `refs/meta/members` (Members), and the -/// derived feature and check status. Editing is a members-gated write path that -/// does not exist yet, so the values are presented as the current configuration. +/// The Settings tab: a projection over the repository's typed meta refs — +/// `refs/meta/config` (General), `refs/meta/members` (Members), and the derived +/// feature and check status. The General fields are editable in place by a +/// signed-in member when `editing` is set (the server has a signing key and the +/// gate); everything else is read-only. pub(super) async fn settings_page( repo: &Path, meta: &RepoMeta, auth: Option<&super::Auth>, + editing: bool, ) -> Markup { let members = load_members(repo).await; let checks = load_checks(repo).await; @@ -836,13 +838,13 @@ "The repository's configuration on " code { "refs/meta/config" } " and " code { "refs/meta/members" } "." } - (settings_auth_banner(auth)) + (settings_auth_banner(auth, editing)) div.card { div.card-header { "General" } (setting_row("Repository name", meta.name())) (setting_row("Default branch", meta.branch.as_deref().unwrap_or("—"))) - (general_settings(meta, auth)) + (general_settings(meta, auth, editing)) } div.card { @@ -913,16 +915,22 @@ } /// The settings authorization banner: who is signed in and whether they may -/// edit this repository. -fn settings_auth_banner(auth: Option<&super::Auth>) -> Markup { +/// edit this repository. When `editing` is unset the server cannot land edits at +/// all, so a member is told editing is disabled rather than offered controls +/// that would only fail. +fn settings_auth_banner(auth: Option<&super::Auth>, editing: bool) -> Markup { html! { @match auth { None => p.shell-note { a href="/login" { "Sign in" } " with a member web key to edit these settings." } - Some(auth) if auth.username.is_some() => p.shell-note.can-edit { + Some(auth) if auth.username.is_some() && editing => p.shell-note.can-edit { "Signed in as " strong { (auth.label) } " — you can edit this repository." } + Some(auth) if auth.username.is_some() => p.shell-note { + "Signed in as " strong { (auth.label) } ", but this server has browser editing " + "disabled, so settings are read-only." + } Some(auth) => p.shell-note { "Signed in as " strong { (auth.label) } ", but this key is not a member of this " "repository, so settings are read-only." @@ -932,9 +940,9 @@ } /// The editable General fields (description, homepage, topics): an edit form when -/// the signed-in key is a member of this repo, otherwise read-only rows. -fn general_settings(meta: &RepoMeta, auth: Option<&super::Auth>) -> Markup { - let Some(auth) = auth.filter(|a| a.username.is_some()) else { +/// a signed-in member edits a server that can land edits, otherwise read-only rows. +fn general_settings(meta: &RepoMeta, auth: Option<&super::Auth>, editing: bool) -> Markup { + let Some(auth) = auth.filter(|a| editing && a.username.is_some()) else { return html! { (setting_row("Description", meta.description.as_deref().unwrap_or("—"))) (setting_row("Homepage", meta.homepage.as_deref().unwrap_or("—")))