Files
poimen-memory/crates/mem-cli/src/auth_middleware.rs
T

120 lines
3.8 KiB
Rust
Raw Normal View History

/// Auth middleware helpers for HTTP handlers
///
/// Provides utilities to:
/// 1. Validate JWT tokens from requests
/// 2. Extract claims
/// 3. Check permissions
/// 4. Return standardized auth errors
use actix_web::{HttpRequest, HttpResponse};
use serde_json::json;
use crate::auth::provider::{AuthProvider, AuthError};
use crate::auth::guard::{AuthGuard, PermissionGuard, Role};
/// Result type for auth operations
pub type AuthResult<T> = Result<T, AuthError>;
/// Extract and validate bearer token from request
pub async fn validate_request_token(
req: &HttpRequest,
auth_provider: &dyn AuthProvider,
) -> AuthResult<crate::auth::provider::Claims> {
// Extract Authorization header
let auth_header = req
.headers()
.get("Authorization")
.and_then(|h| h.to_str().ok())
.ok_or(AuthError::MissingToken)?;
// Extract token from "Bearer <token>"
let token = AuthGuard::extract_token(auth_header)?;
// Validate token with provider
auth_provider.validate_token(&token).await
}
/// Check if user has required role for resource
pub fn check_resource_role(
claims: &crate::auth::provider::Claims,
resource_type: &str,
resource_id: &str,
required_role: Role,
) -> bool {
let user_role = PermissionGuard::get_resource_role(claims, resource_type, resource_id)
.unwrap_or(Role::User);
user_role.satisfies(required_role)
}
/// Check if user belongs to required group
pub fn check_group_membership(
claims: &crate::auth::provider::Claims,
required_group: &str,
) -> bool {
PermissionGuard::check_group(claims, required_group)
}
/// Convert auth error to HTTP response
pub fn auth_error_response(error: &AuthError) -> HttpResponse {
let (status, message) = match error {
AuthError::MissingToken => ("Unauthorized", "Missing or invalid Authorization header"),
AuthError::InvalidSignature => ("Unauthorized", "Invalid token signature"),
AuthError::ExpiredToken => ("Unauthorized", "Token has expired"),
AuthError::InvalidIssuer => ("Unauthorized", "Invalid token issuer"),
AuthError::AccessDenied => ("Forbidden", "Access denied for this resource"),
AuthError::InvalidClaims => ("Unauthorized", "Invalid or missing required claims"),
};
HttpResponse::build(match status {
"Unauthorized" => actix_web::http::StatusCode::UNAUTHORIZED,
"Forbidden" => actix_web::http::StatusCode::FORBIDDEN,
_ => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
})
.json(json!({
"error": status,
"message": message
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_resource_role() {
let claims = crate::auth::provider::Claims {
sub: "user-1".to_string(),
groups: vec![],
attributes: serde_json::Map::new(),
exp: 999999999,
iat: 0,
};
// User with no resource role defaults to Role::User
assert!(check_resource_role(&claims, "memory", "proj-1", Role::User));
assert!(!check_resource_role(&claims, "memory", "proj-1", Role::Viewer));
}
#[test]
fn test_check_group_membership() {
let claims = crate::auth::provider::Claims {
sub: "user-1".to_string(),
groups: vec!["admins".to_string(), "developers".to_string()],
attributes: serde_json::Map::new(),
exp: 999999999,
iat: 0,
};
assert!(check_group_membership(&claims, "admins"));
assert!(check_group_membership(&claims, "developers"));
assert!(!check_group_membership(&claims, "managers"));
}
#[test]
fn test_auth_error_response() {
let err = AuthError::MissingToken;
let response = auth_error_response(&err);
assert_eq!(response.status(), 401);
}
}