fix: show sign-in state on every repo page, not just index and login
commit
4936b3cfix: 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
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
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" } }