use std::sync::Arc;
use std::time::Duration;
use systemprompt_models::net::validate_outbound_url;
use systemprompt_models::profile::{AuthzMode, GovernanceConfig, UNRESTRICTED_ACKNOWLEDGEMENT};
use super::audit::{AuthzAuditSink, DbAuditSink, GovernanceDecisionRepository, NullAuditSink};
use super::error::{AuthzBootstrapError, AuthzResult};
use super::hook::{AllowAllHook, AuthzDecisionHook, DenyAllHook, WebhookHook};
pub type SharedAuthzHook = Arc<dyn AuthzDecisionHook>;
pub fn build_authz_hook(
governance: Option<&GovernanceConfig>,
pool: Option<Arc<sqlx::PgPool>>,
) -> AuthzResult<SharedAuthzHook> {
let sink = build_sink(pool);
let Some(authz) = governance.and_then(|g| g.authz.as_ref()) else {
tracing::error!(
"governance.authz block missing — using DenyAllHook (all requests will be denied)"
);
return Ok(Arc::new(DenyAllHook::new(sink)));
};
match authz.hook.mode {
AuthzMode::Disabled => {
tracing::warn!("governance.authz.hook.mode = disabled — all requests will be denied");
Ok(Arc::new(DenyAllHook::new(sink)))
},
AuthzMode::Unrestricted => {
let ack = authz.hook.acknowledgement.as_deref().map_or("", str::trim);
if ack != UNRESTRICTED_ACKNOWLEDGEMENT {
return Err(AuthzBootstrapError::MissingUnrestrictedAcknowledgement {
expected: UNRESTRICTED_ACKNOWLEDGEMENT,
}
.into());
}
tracing::error!(
"governance.authz.hook.mode = unrestricted — ALL REQUESTS WILL BE ALLOWED. This \
MUST NOT run in production."
);
Ok(Arc::new(AllowAllHook::new(sink)))
},
AuthzMode::Webhook => {
let url = authz
.hook
.url
.as_deref()
.map(str::trim)
.filter(|s| !s.is_empty())
.ok_or(AuthzBootstrapError::MissingWebhookUrl)?
.to_owned();
validate_outbound_url(&url)
.map_err(|e| AuthzBootstrapError::InvalidWebhookUrl(e.to_string()))?;
let hook = WebhookHook::new(url, Duration::from_millis(authz.hook.timeout_ms), sink)?;
Ok(Arc::new(hook))
},
}
}
fn build_sink(pool: Option<Arc<sqlx::PgPool>>) -> Arc<dyn AuthzAuditSink> {
pool.map_or_else(
|| -> Arc<dyn AuthzAuditSink> { Arc::new(NullAuditSink) },
|p| -> Arc<dyn AuthzAuditSink> {
Arc::new(DbAuditSink::new(GovernanceDecisionRepository::from_pool(p)))
},
)
}