coil-runtime 0.1.1

HTTP runtime and request handling for the Coil framework.
Documentation
use super::*;
use std::time::{Instant, SystemTime, UNIX_EPOCH};

pub(crate) async fn authorize_live_request(
    state: &RuntimeServerState,
    request: &mut RequestInput,
) -> Result<(), RuntimeServerError> {
    let matched = state
        .plan
        .http
        .resolve_match(
            &state.plan.config,
            request.method,
            &request.host,
            &request.path,
        )
        .ok_or_else(|| {
            RuntimeServerError::Execution(RequestExecutionError::RouteNotFound {
                method: request.method,
                host: request.host.clone(),
                path: request.path.clone(),
            })
        })?;

    let RouteAuthGate::Capability(capability) = matched.resolved.auth else {
        return Ok(());
    };
    if request.session_id.is_none() {
        return Ok(());
    }

    let Some(principal_id) = request.principal_id.as_deref() else {
        return Ok(());
    };
    if state.is_development() && principal_id == "dev-admin" {
        request.granted_capabilities.insert(capability);
        return Ok(());
    }
    let package = state.plan.auth_package.package();
    let module_manifest = matched.route.module.as_deref().and_then(|module_name| {
        state
            .plan
            .modules
            .iter()
            .find(|manifest| manifest.name == module_name)
    });
    let Some(object) = matched
        .resolved
        .capability_auth_resource(&matched.route, module_manifest, package)
        .map_err(|error| RuntimeServerError::Authorization {
            reason: error.to_string(),
        })?
    else {
        return Ok(());
    };
    let subject = match request.principal_kind {
        RequestPrincipalKind::ServiceAccount => coil_auth::DefaultSubject::entity(
            coil_auth::Entity::service_account(principal_id.to_string()),
        ),
        _ => coil_auth::DefaultSubject::entity(coil_auth::Entity::user(
            principal_id.to_string(),
        )),
    };
    let started_at = Instant::now();
    let allowed = state
        .route_authorizer
        .check_capability(&subject, capability, &object)
        .await?;
    let elapsed_ms = started_at.elapsed().as_millis().min(u128::from(u64::MAX)) as u64;
    let _ = state
        .plan
        .observability
        .telemetry
        .record_histogram("coil.auth.check.latency_ms", elapsed_ms);
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs();
    let _ = state.plan.observability.telemetry.record_trace(
        coil_observability::TraceRecord::new(
            format!("auth:{}:{}", matched.resolved.route_name, capability.as_str()),
            "auth.check",
            if allowed { "allowed" } else { "denied" },
            now,
        )
        .with_field("route", matched.resolved.route_name.clone())
        .with_field("capability", capability.as_str())
        .with_field("duration_ms", elapsed_ms.to_string()),
    );

    if allowed {
        request.granted_capabilities.insert(capability);
        Ok(())
    } else {
        Err(RuntimeServerError::Execution(
            RequestExecutionError::CapabilityRequired {
                route: matched.resolved.route_name.clone(),
                capability,
            },
        ))
    }
}