bucketwarden-server 0.1.0

BucketWarden storage server runtime.
Documentation
use super::catalog::{browser_ui_assets, browser_ui_v1_html};
use super::*;
use bucketwarden_browser_ui_shell::{
    browser_ui_accessibility_checks, browser_ui_action_filter_controls,
    browser_ui_api_client_contracts, browser_ui_api_endpoints, browser_ui_mobile_layout_rules,
    browser_ui_page_header_regions, browser_ui_responsive_breakpoints, browser_ui_routes,
    browser_ui_secondary_inspector_regions, browser_ui_security_policy, browser_ui_sidebar_order,
    browser_ui_state_contracts, browser_ui_topbar_context_order,
};

const FOUNDATION_BOUNDARY_ID: &str = "bnd:bucketwarden.browser-ui.foundation-slice";
const BUCKETWARDEN_MONOGRAM_PNG: &[u8] =
    include_bytes!("../../assets/bucketwarden-monogram.png");

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct BrowserUiAssetResponse {
    pub status: u16,
    pub path: String,
    pub content_type: String,
    pub headers: BTreeMap<String, String>,
    pub body: Vec<u8>,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct BrowserUiFoundationReport {
    pub boundary_id: String,
    pub claim_tier: String,
    pub implementation_status: String,
    pub generated_at_epoch_seconds: u64,
    pub routes: Vec<BrowserUiRoute>,
    pub sidebar_order: Vec<String>,
    pub topbar_context_order: Vec<String>,
    pub page_header_regions: Vec<String>,
    pub action_filter_controls: Vec<String>,
    pub secondary_inspector_regions: Vec<String>,
    pub mobile_layout_rules: Vec<String>,
    pub api_endpoints: Vec<String>,
    pub assets: Vec<BrowserUiAsset>,
    pub api_client_contracts: Vec<String>,
    pub state_contracts: Vec<String>,
    pub accessibility_checks: Vec<String>,
    pub responsive_breakpoints: Vec<String>,
    pub security: BrowserUiSecurityPolicy,
}

impl BucketWarden {
    pub fn browser_ui_foundation_report(
        &mut self,
        principal: &str,
    ) -> Result<BrowserUiFoundationReport, RuntimeError> {
        self.require_operator_action(
            principal,
            OperatorAction::ReadDiagnostics,
            "*",
            "ui:GetBrowserFoundationReport",
        )?;
        let report = BrowserUiFoundationReport {
            boundary_id: FOUNDATION_BOUNDARY_ID.to_string(),
            claim_tier: BROWSER_UI_CLAIM_TIER.to_string(),
            implementation_status: "implemented".to_string(),
            generated_at_epoch_seconds: self.clock_epoch_seconds,
            routes: browser_ui_routes(),
            sidebar_order: browser_ui_sidebar_order(),
            topbar_context_order: browser_ui_topbar_context_order(),
            page_header_regions: browser_ui_page_header_regions(),
            action_filter_controls: browser_ui_action_filter_controls(),
            secondary_inspector_regions: browser_ui_secondary_inspector_regions(),
            mobile_layout_rules: browser_ui_mobile_layout_rules(),
            api_endpoints: browser_ui_api_endpoints(),
            assets: browser_ui_assets(),
            api_client_contracts: browser_ui_api_client_contracts(),
            state_contracts: browser_ui_state_contracts(),
            accessibility_checks: browser_ui_accessibility_checks(),
            responsive_breakpoints: browser_ui_responsive_breakpoints(),
            security: browser_ui_security_policy(),
        };
        self.audit.append(
            principal,
            "ui:GetBrowserFoundationReport",
            "*",
            AuditOutcome::Allowed,
            Some(format!(
                "routes={},assets={},api_endpoints={}",
                report.routes.len(),
                report.assets.len(),
                report.api_endpoints.len()
            )),
        );
        Ok(report)
    }

    pub fn browser_ui_asset_response(
        &self,
        path: &str,
    ) -> Result<BrowserUiAssetResponse, RuntimeError> {
        let normalized = normalize_ui_path(path)?;
        let canonical = canonical_ui_path(&normalized);
        let (content_type, body) = match canonical.as_str() {
            "/ui" | "/ui/" => ("text/html; charset=utf-8", browser_ui_html().into_bytes()),
            "/ui/v1" | "/ui/v1/" => (
                "text/html; charset=utf-8",
                browser_ui_v1_html().into_bytes(),
            ),
            "/ui/logout" => ("text/html; charset=utf-8", browser_ui_html().into_bytes()),
            "/ui/v1/logout" => (
                "text/html; charset=utf-8",
                browser_ui_v1_html().into_bytes(),
            ),
            "/ui/assets/app.css" => (
                "text/css; charset=utf-8",
                browser_ui_css().as_bytes().to_vec(),
            ),
            "/ui/assets/app.js" => (
                "application/javascript; charset=utf-8",
                browser_ui_js().as_bytes().to_vec(),
            ),
            "/ui/assets/bucketwarden-monogram.png" => {
                ("image/png", BUCKETWARDEN_MONOGRAM_PNG.to_vec())
            }
            "/ui/assets/favicon.png" => ("image/png", BUCKETWARDEN_MONOGRAM_PNG.to_vec()),
            "/ui/manifest.json" => (
                "application/json; charset=utf-8",
                browser_ui_manifest_json().into_bytes(),
            ),
            route
                if route.starts_with("/ui/v1")
                    && browser_ui_routes()
                        .iter()
                        .any(|entry| entry.route.replacen("/ui", "/ui/v1", 1) == route) =>
            {
                (
                    "text/html; charset=utf-8",
                    browser_ui_v1_html().into_bytes(),
                )
            }
            route if is_versioned_bucket_workspace_route(route) => (
                "text/html; charset=utf-8",
                browser_ui_v1_html().into_bytes(),
            ),
            route if is_legacy_bucket_workspace_route(route) => {
                ("text/html; charset=utf-8", browser_ui_html().into_bytes())
            }
            route if browser_ui_routes().iter().any(|entry| entry.route == route) => {
                ("text/html; charset=utf-8", browser_ui_html().into_bytes())
            }
            _ => return Err(RuntimeError::NoSuchKey(normalized)),
        };
        Ok(BrowserUiAssetResponse {
            status: 200,
            path: normalized,
            content_type: content_type.to_string(),
            headers: browser_ui_security_headers(),
            body,
        })
    }
}

pub fn browser_ui_security_headers() -> BTreeMap<String, String> {
    BTreeMap::from([
        (
            "content-security-policy".to_string(),
            browser_ui_security_policy().content_security_policy,
        ),
        ("x-content-type-options".to_string(), "nosniff".to_string()),
        ("referrer-policy".to_string(), "same-origin".to_string()),
        ("cache-control".to_string(), "no-store".to_string()),
    ])
}

fn normalize_ui_path(path: &str) -> Result<String, RuntimeError> {
    if !path.starts_with("/ui") || path.contains('\\') || path.split('/').any(|part| part == "..") {
        return Err(RuntimeError::InvalidObjectKey(path.to_string()));
    }
    Ok(path.trim_end_matches('/').to_string())
}

fn canonical_ui_path(path: &str) -> String {
    for suffix in [
        "/assets/app.css",
        "/assets/app.js",
        "/assets/bucketwarden-monogram.png",
        "/assets/favicon.png",
        "/manifest.json",
    ] {
        if path == format!("/ui/v1{suffix}") {
            return format!("/ui{suffix}");
        }
    }
    path.to_string()
}

fn is_versioned_bucket_workspace_route(route: &str) -> bool {
    route
        .strip_prefix("/ui/v1/buckets/")
        .is_some_and(is_bucket_workspace_tail)
}

fn is_legacy_bucket_workspace_route(route: &str) -> bool {
    route
        .strip_prefix("/ui/buckets/")
        .is_some_and(is_bucket_workspace_tail)
}

fn is_bucket_workspace_tail(tail: &str) -> bool {
    if tail.is_empty() || tail.contains("..") || tail.contains('\\') {
        return false;
    }
    let parts: Vec<&str> = tail.split('/').collect();
    parts.len() == 1 || (parts.len() >= 3 && parts[1] == "objects")
}