CI / CI (push) Successful in 15m14s
All errors were API mismatches — handler code calling wrong method names, wrong argument types, or missing imports/derives. No logic changes. Build now passes with SQLX_OFFLINE=true. Key fixes: - embed_text -> embed_one, Vector -> Vec<f32> conversion - extract_token: extract auth header from HttpRequest first - AuthError variants aligned to actual enum definition - recursive async fns boxed (dfs_paths in inference + path_finder) - missing derives (Default, Serialize), imports (sqlx::Row, Timelike) - borrow-after-move: compute .len() before struct field move - streaming_body -> streaming with Result<Bytes> for SSE - CI: add SQLX_OFFLINE=true for offline builds without DB 25 files changed, 99 insertions(+), 81 deletions(-) Co-authored-by: rock <[email protected]>
122 lines
4.0 KiB
Rust
122 lines
4.0 KiB
Rust
/// 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::TokenExpired => ("Unauthorized", "Token has expired"),
|
|
AuthError::InvalidIssuer => ("Unauthorized", "Invalid token issuer"),
|
|
AuthError::InvalidAudience => ("Unauthorized", "Invalid token audience"),
|
|
AuthError::ProviderUnavailable(_) => ("ServiceUnavailable", "Auth provider unavailable"),
|
|
AuthError::Other(_) => ("Unauthorized", "Authentication error"),
|
|
};
|
|
|
|
HttpResponse::build(match status {
|
|
"Unauthorized" => actix_web::http::StatusCode::UNAUTHORIZED,
|
|
"Forbidden" => actix_web::http::StatusCode::FORBIDDEN,
|
|
"ServiceUnavailable" => actix_web::http::StatusCode::SERVICE_UNAVAILABLE,
|
|
_ => 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);
|
|
}
|
|
}
|