Skip to main content

brainos_httpadapter/
auth.rs

1//! Authentication middleware helpers for HTTP routes.
2
3use axum::http::{HeaderMap, StatusCode};
4
5use crate::state::AppState;
6
7/// Extract the raw key from `Authorization: Bearer <key>`.
8pub fn extract_bearer(headers: &HeaderMap) -> Option<&str> {
9    headers
10        .get("authorization")
11        .and_then(|v| v.to_str().ok())
12        .and_then(brain::auth::extract_bearer_from_value)
13}
14
15/// Resolve the requesting `Principal` from the request headers.
16///
17/// Returns `None` when:
18/// - no `Authorization: Bearer` header is present, OR
19/// - the key has no `agent_id` configured (back-compat — pre-Phase-1 keys), OR
20/// - no `IdentityStore` is wired on the `SignalProcessor`, OR
21/// - the agent_id is unknown to the identity store.
22///
23/// `None` propagates as `Signal.principal = None`, which the pipeline's
24/// identity gate treats as "skip enforcement". Adapters that want to
25/// REQUIRE a principal should refuse the request when this returns `None`.
26pub async fn resolve_principal(
27    state: &AppState,
28    headers: &HeaderMap,
29) -> Option<identity::Principal> {
30    let key = extract_bearer(headers)?;
31    let agent_id =
32        brain::auth::find_key_ct(&state.api_keys, key).and_then(|k| k.agent_id.clone())?;
33    let store = state.processor.identity_store()?;
34    store
35        .principal_for(&identity::AgentHint::AgentId(agent_id.into()))
36        .await
37        .ok()
38}
39
40/// Check that the request carries a valid key with the given permission.
41/// Returns `Err((StatusCode::UNAUTHORIZED, message))` on failure.
42pub fn check_auth(
43    state: &AppState,
44    headers: &HeaderMap,
45    permission: &str,
46) -> Result<(), (StatusCode, String)> {
47    let provided_key = extract_bearer(headers);
48    let result = brain::check_auth(&state.api_keys, provided_key, permission);
49    if result.is_allowed() {
50        Ok(())
51    } else {
52        Err((
53            StatusCode::UNAUTHORIZED,
54            result
55                .error_message(permission)
56                .unwrap_or_else(|| "Unauthorized".to_string()),
57        ))
58    }
59}