use super::*;
const BROWSER_UI_BOUNDARY_ID: &str = "bnd:bucketwarden.browser-ui-full-product-scope";
const BROWSER_UI_CLAIM_TIER: &str = "T3";
const SESSION_DURATION_SECONDS: u64 = 3600;
mod catalog;
mod features;
mod foundation;
pub use bucketwarden_browser_ui_shell::{
browser_ui_accessibility_checks, browser_ui_action_filter_controls, browser_ui_api_endpoints,
browser_ui_responsive_breakpoints, browser_ui_routes, browser_ui_secondary_inspector_regions,
browser_ui_security_policy, browser_ui_sidebar_order, browser_ui_topbar_context_order,
BrowserUiRoute, BrowserUiSecurityPolicy,
};
use catalog::{browser_ui_assets, browser_ui_feature_statuses, browser_ui_product_e2e_workflows};
pub use catalog::{
browser_ui_css, browser_ui_html, browser_ui_js, browser_ui_manifest_json, browser_ui_v1_html,
};
pub use foundation::*;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct BrowserUiFeatureStatus {
pub feature_id: String,
pub title: String,
pub category: String,
pub runtime_surface: String,
pub implementation_status: String,
pub claim_tier: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct BrowserUiAsset {
pub path: String,
pub content_type: String,
pub bytes: usize,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct BrowserUiProductReport {
pub boundary_id: String,
pub claim_tier: String,
pub implementation_status: String,
pub generated_at_epoch_seconds: u64,
pub features: Vec<BrowserUiFeatureStatus>,
pub routes: Vec<BrowserUiRoute>,
pub api_endpoints: Vec<String>,
pub assets: Vec<BrowserUiAsset>,
pub security: BrowserUiSecurityPolicy,
pub accessibility_checks: Vec<String>,
pub responsive_breakpoints: Vec<String>,
pub preference_keys: Vec<String>,
pub export_actions: Vec<String>,
pub e2e_workflows: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct BrowserUiLoginRequest {
pub principal_id: String,
pub shared_secret: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct BrowserUiSession {
pub principal_id: String,
pub access_key_id: String,
pub session_token: String,
pub expires_at_epoch_seconds: u64,
pub role_count: usize,
pub scope: String,
}
impl BucketWarden {
pub fn browser_ui_product_report(
&mut self,
principal: &str,
) -> Result<BrowserUiProductReport, RuntimeError> {
self.require_operator_action(
principal,
OperatorAction::ReadDiagnostics,
"*",
"ui:BrowserProductReport",
)?;
let report = BrowserUiProductReport {
boundary_id: BROWSER_UI_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,
features: browser_ui_feature_statuses(),
routes: browser_ui_routes(),
api_endpoints: browser_ui_api_endpoints(),
assets: browser_ui_assets(),
security: browser_ui_security_policy(),
accessibility_checks: browser_ui_accessibility_checks(),
responsive_breakpoints: browser_ui_responsive_breakpoints(),
preference_keys: vec![
"bucketwarden.ui.density".to_string(),
"bucketwarden.ui.visibleColumns".to_string(),
"bucketwarden.ui.reportFilters".to_string(),
"bucketwarden.ui.refreshSeconds".to_string(),
],
export_actions: vec![
"download health report JSON".to_string(),
"download config report JSON".to_string(),
"download evidence export JSON".to_string(),
"download audit log JSONL".to_string(),
],
e2e_workflows: browser_ui_product_e2e_workflows(),
};
self.audit.append(
principal,
"ui:BrowserProductReport",
"*",
AuditOutcome::Allowed,
Some(format!(
"features={},routes={},assets={}",
report.features.len(),
report.routes.len(),
report.assets.len()
)),
);
Ok(report)
}
pub fn browser_ui_login(
&mut self,
request: BrowserUiLoginRequest,
) -> Result<BrowserUiSession, RuntimeError> {
self.auth
.enforce_login_attempt_limit(&request.principal_id, 5)?;
let access_key_id = format!("BWUI-{}", sanitize_token_part(&request.principal_id));
let session_token = format!(
"bwui-session-{}-{}",
sanitize_token_part(&request.principal_id),
self.clock_epoch_seconds
);
let session = self.auth.assume_role_with_custom_identity(
AssumeRoleWithCustomIdentityRequest::new(
request.principal_id.clone(),
request.shared_secret.clone(),
SESSION_DURATION_SECONDS,
CredentialScope::new(
vec!["ops:*".to_string(), "ui:*".to_string()],
vec!["*".to_string()],
),
access_key_id.clone(),
"browser-ui-session-secret",
session_token.clone(),
),
self.clock_epoch_seconds,
);
match session {
Ok(session) => {
self.auth
.record_login_success(&request.principal_id, self.clock_epoch_seconds);
let role_count = self.auth.role_assignments(&request.principal_id).len();
let session_token = session.credentials().session_token.unwrap_or_default();
self.audit.append(
&request.principal_id,
"ui:BrowserLogin",
"*",
AuditOutcome::Allowed,
Some(format!("session={}", session.access_key_id)),
);
Ok(BrowserUiSession {
principal_id: session.principal_id,
access_key_id: session.access_key_id,
session_token,
expires_at_epoch_seconds: session.expires_at_epoch_seconds,
role_count,
scope: "*".to_string(),
})
}
Err(error) => {
self.auth.record_login_failure(
&request.principal_id,
self.clock_epoch_seconds,
error.to_string(),
);
self.audit.append(
&request.principal_id,
"ui:BrowserLogin",
"*",
AuditOutcome::Denied,
Some(error.to_string()),
);
Err(RuntimeError::Auth(error))
}
}
}
pub fn browser_ui_current_user(
&self,
access_key_id: &str,
) -> Result<BrowserUiSession, RuntimeError> {
let credential = self
.auth
.resolve_credential(access_key_id, self.clock_epoch_seconds)?;
Ok(BrowserUiSession {
principal_id: credential.principal_id.clone(),
access_key_id: credential.access_key_id,
session_token: credential.credentials.session_token.unwrap_or_default(),
expires_at_epoch_seconds: credential
.scope
.as_ref()
.and_then(|_| self.auth.credential(access_key_id))
.and_then(|record| match record {
CredentialRecord::Session(session) => Some(session.expires_at_epoch_seconds),
CredentialRecord::AccessKey(_) => None,
})
.unwrap_or(self.clock_epoch_seconds),
role_count: self.auth.role_assignments(&credential.principal_id).len(),
scope: credential
.scope
.as_ref()
.map(|scope| scope.resource_prefixes.join(","))
.unwrap_or_else(|| "*".to_string()),
})
}
pub fn browser_ui_logout(&mut self, access_key_id: &str) -> Result<(), RuntimeError> {
let principal = self
.auth
.resolve_credential(access_key_id, self.clock_epoch_seconds)?
.principal_id;
self.auth
.revoke_credential(access_key_id, self.clock_epoch_seconds)?;
self.audit.append(
&principal,
"ui:BrowserLogout",
"*",
AuditOutcome::Allowed,
Some(format!("session={access_key_id}")),
);
Ok(())
}
}
fn sanitize_token_part(value: &str) -> String {
value
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() {
ch.to_ascii_uppercase()
} else {
'-'
}
})
.collect()
}
fn escape_html(value: &str) -> String {
value
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}