navbar unified
This commit is contained in:
@@ -25,10 +25,68 @@ pub(crate) struct Nav {
|
||||
}
|
||||
|
||||
impl Nav {
|
||||
/// The navbar for a page, resolved in full: this fetches the caller's
|
||||
/// authorization itself, so a handler cannot forget to and end up
|
||||
/// rendering a navbar with every capability link missing.
|
||||
///
|
||||
/// `active` is the nav link to highlight: `"admin"`, `"permissions"`,
|
||||
/// `"analytics"`, `"login"`, or `""` for pages that are not themselves nav
|
||||
/// entries.
|
||||
pub(crate) fn new(headers: &HeaderMap, active: &'static str) -> Self {
|
||||
pub(crate) async fn for_request(
|
||||
state: crate::AppState,
|
||||
headers: &HeaderMap,
|
||||
active: &'static str,
|
||||
) -> Self {
|
||||
let Ok(request) =
|
||||
crate::services::authenticated_request(headers, crate::auth::GetAuthorizationRequest {})
|
||||
else {
|
||||
return Self::without_authorization(headers, active);
|
||||
};
|
||||
let mut auth = state.auth;
|
||||
match auth.get_authorization(request).await {
|
||||
Ok(response) => Self::from_authorization(headers, active, response.get_ref()),
|
||||
Err(_) => Self::without_authorization(headers, active),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Self::for_request`] for a handler that already holds the snapshot,
|
||||
/// because gating the page needed it — the same navbar without a second
|
||||
/// round-trip.
|
||||
pub(crate) fn from_authorization(
|
||||
headers: &HeaderMap,
|
||||
active: &'static str,
|
||||
authorization: &crate::auth::AuthorizationSnapshot,
|
||||
) -> Self {
|
||||
let mut nav = Self::without_authorization(headers, active);
|
||||
// The backend answered for this caller, so they are signed in — a
|
||||
// stronger fact than the cookie's mere presence, which is all
|
||||
// `without_authorization` has to go on.
|
||||
nav.authenticated = true;
|
||||
nav.role = authorization.role.clone();
|
||||
nav.can_admin = crate::authz::can_open_admin(authorization);
|
||||
// Permissions is its own nav section, so it is not gated on the admin
|
||||
// panel: managing either roles or users is enough to open it.
|
||||
nav.can_permissions = crate::authz::can_manage(authorization, crate::authz::STRUCT_ROLE)
|
||||
|| crate::authz::can_manage(authorization, crate::authz::STRUCT_USER);
|
||||
nav.can_import = authorization.permissions.iter().any(|permission| {
|
||||
permission.action == "insert" && permission.object.starts_with("data:")
|
||||
});
|
||||
nav.can_export = authorization.permissions.iter().any(|permission| {
|
||||
permission.action == "read" && permission.object.starts_with("data:")
|
||||
});
|
||||
nav.can_ecb = crate::authz::can_read_ecb(authorization);
|
||||
nav
|
||||
}
|
||||
|
||||
/// A navbar with no capabilities at all — every `can_*` link hidden.
|
||||
///
|
||||
/// This is only correct for markup that has no navbar to get wrong: the
|
||||
/// login and register pages, the HTMX fragments that carry a `Nav` purely
|
||||
/// to translate their labels, and the error pages rendered when the
|
||||
/// authorization could not be loaded in the first place. A page rendering
|
||||
/// `ui/navbar.html` for a signed-in user wants [`Self::for_request`];
|
||||
/// reaching for this one there is what leaves the navbar half empty.
|
||||
pub(crate) fn without_authorization(headers: &HeaderMap, active: &'static str) -> Self {
|
||||
Self {
|
||||
locale: crate::i18n::Locale::from_headers(headers),
|
||||
authenticated: crate::cookie_value(headers, SESSION_COOKIE).is_some(),
|
||||
@@ -91,25 +149,6 @@ impl Nav {
|
||||
self.locale.lookup_args(key, &map)
|
||||
}
|
||||
|
||||
pub(crate) fn with_authorization(
|
||||
mut self,
|
||||
authorization: &crate::auth::AuthorizationSnapshot,
|
||||
) -> Self {
|
||||
self.role = authorization.role.clone();
|
||||
self.can_admin = crate::authz::can_open_admin(authorization);
|
||||
// Permissions is its own nav section, so it is not gated on the admin
|
||||
// panel: managing either roles or users is enough to open it.
|
||||
self.can_permissions = crate::authz::can_manage(authorization, crate::authz::STRUCT_ROLE)
|
||||
|| crate::authz::can_manage(authorization, crate::authz::STRUCT_USER);
|
||||
self.can_import = authorization.permissions.iter().any(|permission| {
|
||||
permission.action == "insert" && permission.object.starts_with("data:")
|
||||
});
|
||||
self.can_export = authorization.permissions.iter().any(|permission| {
|
||||
permission.action == "read" && permission.object.starts_with("data:")
|
||||
});
|
||||
self.can_ecb = crate::authz::can_read_ecb(authorization);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The escaping Askama's `escape` filter would have applied, for the values
|
||||
|
||||
Reference in New Issue
Block a user