Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
crates/web/src/admin.rs +26 −7
| @@ -24,8 +24,10 @@ use crate::session::{RequireAdmin, verify_csrf}; | |||
| 24 | 24 | use crate::{AppState, setting_keys}; | |
| 25 | 25 | ||
| 26 | 26 | /// The admin dashboard shell: a left tab rail and the active tab's content. | |
| 27 | + | /// `show_invites` hides the Invites tab when self-registration is on (invites | |
| 28 | + | /// are only useful when registration is otherwise closed). | |
| 27 | 29 | #[allow(clippy::needless_pass_by_value)] // `content` is embedded once. | |
| 28 | - | fn admin_shell(active: &str, content: Markup) -> Markup { | |
| 30 | + | fn admin_shell(active: &str, content: Markup, show_invites: bool) -> Markup { | |
| 29 | 31 | let tab = |href: &str, key: &str, label: &str| { | |
| 30 | 32 | html! { a href=(href) aria-current=[(active == key).then_some("page")] { (label) } } | |
| 31 | 33 | }; | |
| @@ -38,7 +40,7 @@ fn admin_shell(active: &str, content: Markup) -> Markup { | |||
| 38 | 40 | (tab("/admin/users", "users", "Users")) | |
| 39 | 41 | (tab("/admin/repos", "repos", "Repositories")) | |
| 40 | 42 | (tab("/admin/groups", "groups", "Groups")) | |
| 41 | - | (tab("/admin/invites", "invites", "Invites")) | |
| 43 | + | @if show_invites { (tab("/admin/invites", "invites", "Invites")) } | |
| 42 | 44 | (tab("/admin/settings", "settings", "Settings")) | |
| 43 | 45 | } | |
| 44 | 46 | } | |
| @@ -56,8 +58,13 @@ fn render( | |||
| 56 | 58 | active: &str, | |
| 57 | 59 | content: Markup, | |
| 58 | 60 | ) -> Response { | |
| 61 | + | let show_invites = !state.allow_registration(); | |
| 59 | 62 | let (jar, chrome) = build_chrome(state, jar, Some(user), uri.path().to_string()); | |
| 60 | - | (jar, page(&chrome, "Admin", admin_shell(active, content))).into_response() | |
| 63 | + | ( | |
| 64 | + | jar, | |
| 65 | + | page(&chrome, "Admin", admin_shell(active, content, show_invites)), | |
| 66 | + | ) | |
| 67 | + | .into_response() | |
| 61 | 68 | } | |
| 62 | 69 | ||
| 63 | 70 | /// Rows shown per page in the admin list views. | |
| @@ -557,6 +564,11 @@ pub async fn invites( | |||
| 557 | 564 | jar: CookieJar, | |
| 558 | 565 | uri: Uri, | |
| 559 | 566 | ) -> AppResult<Response> { | |
| 567 | + | // Invites only matter when self-registration is closed; otherwise the page | |
| 568 | + | // is hidden and its route redirects to the overview. | |
| 569 | + | if state.allow_registration() { | |
| 570 | + | return Ok(Redirect::to("/admin").into_response()); | |
| 571 | + | } | |
| 560 | 572 | let invites = state.store.list_signup_invites().await?; | |
| 561 | 573 | let base = state.config.instance.url.trim_end_matches('/').to_string(); | |
| 562 | 574 | let (_, chrome) = build_chrome( | |
| @@ -606,10 +618,14 @@ pub async fn invites( | |||
| 606 | 618 | } | |
| 607 | 619 | } | |
| 608 | 620 | }; | |
| 609 | - | // Clear the flash cookie after showing it. | |
| 610 | - | let jar = jar.remove(axum_extra::extract::cookie::Cookie::from( | |
| 611 | - | "fabrica_flash_invite", | |
| 612 | - | )); | |
| 621 | + | // Clear the flash cookie after showing it once. The removal must carry the | |
| 622 | + | // same path the cookie was set with ("/admin/invites"), or the browser keeps | |
| 623 | + | // it and the link reappears on reload. | |
| 624 | + | let jar = jar.remove( | |
| 625 | + | axum_extra::extract::cookie::Cookie::build("fabrica_flash_invite") | |
| 626 | + | .path("/admin/invites") | |
| 627 | + | .build(), | |
| 628 | + | ); | |
| 613 | 629 | Ok(render(&state, jar, user, &uri, "invites", content)) | |
| 614 | 630 | } | |
| 615 | 631 | ||
| @@ -633,6 +649,9 @@ pub async fn invite_create( | |||
| 633 | 649 | if verify_csrf(&jar, &headers, Some(&form.csrf)).is_err() { | |
| 634 | 650 | return AppError::Forbidden.into_response(); | |
| 635 | 651 | } | |
| 652 | + | if state.allow_registration() { | |
| 653 | + | return Redirect::to("/admin").into_response(); | |
| 654 | + | } | |
| 636 | 655 | let token = auth::new_session_token(); | |
| 637 | 656 | let note = form.note.trim(); | |
| 638 | 657 | let created = state | |