git-ents.gitmain
⌘K
foforge
commit 4936b3c
fix: show sign-in state on every repo page, not just index and login

The account strip (Signed in as X / Sign in) only rendered on / and /login, so a visitor browsing Files, Checks, or Settings had no way to tell whether they were signed in. resolve_auth now runs once per request in route() and repo_shell renders the strip for every tab.

Assisted-by: Claude:claude-sonnet-5

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 @@ -420,15 +420,14 @@ live_runs: &git_effect::engine::LiveRegistry, ) -> Response { let meta = gather_meta(repo, rel).await; + let auth = resolve_auth(repo, session).await; match rest.split_first() { - None => pages::repo_page(repo, &meta, host).await.into_response(), - Some((&"files", sub)) => { - let auth = resolve_auth(repo, session).await; - pages::files_page(repo, &meta, sub, auth.as_ref(), editing).await - } - Some((&"tree", sub)) => pages::tree_page(repo, &meta, sub).await, + None => pages::repo_page(repo, &meta, host, auth.as_ref()) + .await + .into_response(), + Some((&"files", sub)) => pages::files_page(repo, &meta, sub, auth.as_ref(), editing).await, + Some((&"tree", sub)) => pages::tree_page(repo, &meta, sub, auth.as_ref()).await, Some((&"blob", sub)) => { - let auth = resolve_auth(repo, session).await; pages::blob_page( repo, &meta, @@ -440,7 +439,6 @@ .await } Some((&"source", sub)) => { - let auth = resolve_auth(repo, session).await; pages::blob_page( repo, &meta, @@ -451,11 +449,15 @@ ) .await } - Some((&"commit", &[sha])) => pages::commit_page(repo, &meta, sha).await, - Some((&"releases", &[])) => pages::releases_page(repo, &meta).await.into_response(), - Some((&"checks", &[])) => pages::checks_page(repo, &meta).await.into_response(), + Some((&"commit", &[sha])) => pages::commit_page(repo, &meta, sha, auth.as_ref()).await, + Some((&"releases", &[])) => pages::releases_page(repo, &meta, auth.as_ref()) + .await + .into_response(), + Some((&"checks", &[])) => pages::checks_page(repo, &meta, auth.as_ref()) + .await + .into_response(), Some((&"checks", &[commit, name])) => { - pages::check_recording_page(repo, &meta, commit, name, live_runs).await + pages::check_recording_page(repo, &meta, commit, name, live_runs, auth.as_ref()).await } Some((&"checks", &[commit, name, "live"])) => { pages::check_live_fragment(repo, commit, name, live_runs).await @@ -463,13 +465,12 @@ Some((&"checks", &[commit, name, "download"])) => { pages::check_recording_download(repo, commit, name).await } - 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(), editing) - .await - .into_response() - } + Some((&"issues", &[])) => pages::issues_page(repo, &meta, auth.as_ref()) + .await + .into_response(), + Some((&"settings", &[])) => pages::settings_page(repo, &meta, auth.as_ref(), editing) + .await + .into_response(), _ => not_found().into_response(), } } @@ -577,10 +578,16 @@ /// Wrap a repository view in the shared header band and tab bar, then the page /// shell. `active` highlights the current tab. -fn repo_shell(meta: &RepoMeta, active: Tab, title: &str, body: Markup) -> Markup { +fn repo_shell( + meta: &RepoMeta, + active: Tab, + title: &str, + auth: Option<&Auth>, + body: Markup, +) -> Markup { page( title, - html! { (repo_header(meta)) (tab_bar(meta, active)) (body) }, + html! { (account_strip_auth(auth)) (repo_header(meta)) (tab_bar(meta, active)) (body) }, ) } @@ -708,15 +715,16 @@ } /// A small right-aligned strip showing who is signed in, with a sign-in or -/// sign-out control. -fn account_strip(session: Option<&write::SessionSnapshot>) -> Markup { +/// sign-out control. Shared by every page shell so auth state is never +/// ambiguous, whichever tab a visitor lands on. +fn account_strip_view(identity: Option<(&str, &str)>) -> Markup { html! { div.account-strip { - @match session { - Some(s) => { - span.muted { "Signed in · " (s.label) } + @match identity { + Some((label, csrf)) => { + span.muted { "Signed in · " (label) } form method="post" action="/logout" { - input type="hidden" name="csrf" value=(s.csrf); + input type="hidden" name="csrf" value=(csrf); button.btn.btn-quiet type="submit" { "Sign out" } } } @@ -726,6 +734,14 @@ } } +fn account_strip(session: Option<&write::SessionSnapshot>) -> Markup { + account_strip_view(session.map(|s| (s.label.as_str(), s.csrf.as_str()))) +} + +fn account_strip_auth(auth: Option<&Auth>) -> Markup { + account_strip_view(auth.map(|a| (a.label.as_str(), a.csrf.as_str()))) +} + /// The sign-in page: prove control of a member key by signing a one-time /// challenge locally, without ever surrendering the key. `error` shows a failed /// attempt's reason; `challenge` is the nonce to sign.
crates/git-ents-server/src/web/pages.rs @@ -49,7 +49,12 @@ /// A single repository's overview: the rendered README beside an aside of /// clone, about, releases, and language cards. -pub(super) async fn repo_page(repo: &Path, meta: &RepoMeta, host: Option<&str>) -> Markup { +pub(super) async fn repo_page( + repo: &Path, + meta: &RepoMeta, + host: Option<&str>, + auth: Option<&super::Auth>, +) -> Markup { let rel = &meta.rel; let updated = git_output(repo, &["log", "-1", "--format=%ar"]) .await @@ -158,6 +163,7 @@ meta, Tab::Overview, name, + auth, html! { div.overview { div { (main) } (aside) } }, ) } @@ -351,6 +357,7 @@ meta, Tab::Files, name, + auth, html! { div.files { div.tree-pane { @@ -479,7 +486,12 @@ } /// A directory listing at `sub` within the repository. -pub(super) async fn tree_page(repo: &Path, meta: &RepoMeta, sub: &[&str]) -> Response { +pub(super) async fn tree_page( + repo: &Path, + meta: &RepoMeta, + sub: &[&str], + auth: Option<&super::Auth>, +) -> Response { let rel = &meta.rel; let Some(dir) = browse_path(sub) else { return not_found().into_response(); @@ -498,6 +510,7 @@ meta, Tab::Files, name, + auth, html! { (crumbs(rel, &dir, false)) div.card { @@ -581,6 +594,7 @@ meta, Tab::Files, name, + auth, html! { (crumbs(rel, &path, true)) @if displayable && is_doc(name) { @@ -785,7 +799,12 @@ /// ## Requirements /// /// @relation(web.tabs) -pub(super) async fn commit_page(repo: &Path, meta: &RepoMeta, sha: &str) -> Response { +pub(super) async fn commit_page( + repo: &Path, + meta: &RepoMeta, + sha: &str, + auth: Option<&super::Auth>, +) -> Response { if sha.is_empty() || sha.len() > 64 || !sha.bytes().all(|b| b.is_ascii_hexdigit()) { return not_found().into_response(); } @@ -822,6 +841,7 @@ meta, Tab::Files, &subject, + auth, html! { div.card { div.card-header { (icon_commit()) " Commit " span.sha { (short) } } @@ -847,12 +867,17 @@ /// ## Requirements /// /// @relation(web.tabs) -pub(super) async fn releases_page(repo: &Path, meta: &RepoMeta) -> Markup { +pub(super) async fn releases_page( + repo: &Path, + meta: &RepoMeta, + auth: Option<&super::Auth>, +) -> Markup { let releases = releases(repo).await; repo_shell( meta, Tab::Releases, "Releases", + auth, html! { div.page-header { h1.page-title { "Releases" } } @if releases.is_empty() { @@ -900,7 +925,11 @@ /// ## Requirements /// /// @relation(web.tabs) -pub(super) async fn checks_page(repo: &Path, meta: &RepoMeta) -> Markup { +pub(super) async fn checks_page( + repo: &Path, + meta: &RepoMeta, + auth: Option<&super::Auth>, +) -> Markup { let rel = &meta.rel; let checks = component::load::<git_effect::Effect>(repo).await; let runs = load_runs(repo).await; @@ -921,6 +950,7 @@ meta, Tab::Checks, "Checks", + auth, html! { div.page-header { h1.page-title { "Checks" } } p.shell-note { @@ -1023,6 +1053,7 @@ commit: &str, name: &str, live_runs: &git_effect::engine::LiveRegistry, + auth: Option<&super::Auth>, ) -> Response { let Some(commit_oid) = ObjectId::from_hex(commit.as_bytes()).ok() else { return not_found().into_response(); @@ -1053,6 +1084,7 @@ meta, Tab::Checks, &format!("{name} @ {short_commit}"), + auth, html! { div.page-header { h1.page-title { (name) " on " code { (short_commit) } } @@ -1161,7 +1193,11 @@ /// ## Requirements /// /// @relation(web.tabs) -pub(super) async fn issues_page(repo: &Path, meta: &RepoMeta) -> Markup { +pub(super) async fn issues_page( + repo: &Path, + meta: &RepoMeta, + auth: Option<&super::Auth>, +) -> Markup { let tpl = match component::load::<git_ents_core::issues::Issue>(repo).await { Err(err) => IssuesTemplate { icons: Icons, @@ -1194,7 +1230,7 @@ } } }; - repo_shell(meta, Tab::Issues, "Bug reports", render_body(&tpl)) + repo_shell(meta, Tab::Issues, "Bug reports", auth, render_body(&tpl)) } /// The Issues tab body: the open/closed filter and per-issue cards. @@ -1231,6 +1267,7 @@ meta, Tab::Settings, "Repository settings", + auth, html! { div.settings { div.page-header { h1.page-title { "Repository settings" } }