leankg 0.19.10

Lightweight Knowledge Graph for AI-Assisted Development
#![allow(dead_code)]
use axum::{
    extract::{Request, State},
    http::StatusCode,
    middleware::Next,
    response::{IntoResponse, Response},
};
use std::sync::Arc;

use crate::api::ApiState;
use crate::db::keys::ApiKeyStore;

pub async fn auth_middleware(
    State(_state): State<ApiState>,
    mut request: Request,
    next: Next,
) -> Response {
    let auth_header = request
        .headers()
        .get("authorization")
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());

    if auth_header.is_none() {
        return (StatusCode::UNAUTHORIZED, "Missing Authorization header").into_response();
    }

    let auth_header = auth_header.unwrap();
    let token = if let Some(token) = auth_header.strip_prefix("Bearer ") {
        token.to_string()
    } else {
        return (StatusCode::UNAUTHORIZED, "Invalid Authorization format").into_response();
    };

    let store = match ApiKeyStore::new() {
        Ok(store) => store,
        Err(e) => {
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Auth error: {}", e),
            )
                .into_response();
        }
    };
    match store.validate_key(&token) {
        Ok(Some(_key_id)) => {
            request
                .extensions_mut()
                .insert(AuthContext { key_id: _key_id });
            next.run(request).await
        }
        Ok(None) => (StatusCode::UNAUTHORIZED, "Invalid API key").into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Auth error: {}", e),
        )
            .into_response(),
    }
}

pub async fn require_auth_middleware(
    State(_state): State<Arc<ApiState>>,
    request: Request,
    next: Next,
) -> Response {
    if request.extensions().get::<AuthContext>().is_none() {
        return (StatusCode::UNAUTHORIZED, "Authentication required").into_response();
    }
    next.run(request).await
}

#[derive(Clone)]
pub struct AuthContext {
    pub key_id: String,
}

#[derive(Clone, Debug)]
pub struct TeamAuthContext {
    pub token: String,
    pub engineer: String,
    pub env: String,
}

pub async fn team_token_middleware(
    State(_state): State<ApiState>,
    mut request: Request,
    next: Next,
) -> Response {
    let get_header = |name: &str| -> Option<String> {
        request
            .headers()
            .get(name)
            .and_then(|v| v.to_str().ok())
            .map(|s| s.to_string())
    };

    let token = get_header("X-LeanKG-Token").unwrap_or_default();
    let engineer = get_header("X-LeanKG-Engineer").unwrap_or_else(|| "unknown".to_string());
    let env = get_header("X-LeanKG-Env").unwrap_or_else(|| "production".to_string());

    if token.is_empty() {
        return (StatusCode::UNAUTHORIZED, "Missing X-LeanKG-Token header").into_response();
    }

    let store = match ApiKeyStore::new() {
        Ok(store) => store,
        Err(e) => {
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Auth error: {}", e),
            )
                .into_response();
        }
    };
    match store.validate_key(&token) {
        Ok(Some(key_id)) => {
            request.extensions_mut().insert(TeamAuthContext {
                token: key_id,
                engineer,
                env,
            });
            next.run(request).await
        }
        Ok(None) => (StatusCode::UNAUTHORIZED, "Invalid team token").into_response(),
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Auth error: {}", e),
        )
            .into_response(),
    }
}

pub fn get_team_ctx(request: &Request) -> Option<TeamAuthContext> {
    request.extensions().get::<TeamAuthContext>().cloned()
}