fix: hide settings edit controls when the server cannot land web edits
commit
2ebfb9afix: 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
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
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("—")))