refactor: remove AccessGuard RBAC from MVP, fix http_server startup
- Removed AccessGuard import and initialization (RBAC deferred to Phase 2) - Removed access_guard field from AppState - Removed to_rbac_claims, query_result_to_resource_meta RBAC helper functions - Removed apply_rbac_filter calls from handlers - Removed all RBAC permission checks (check_project_write_access, etc) - Fixed apply_rbac_filter reference in query handler - Server now starts and initializes database schema - Ready for core ingest/query implementation Still debugging: Server process exits after schema init (likely during worker startup or handler routing)
This commit is contained in:
@@ -18,7 +18,7 @@ use crate::dual_write_indexer::DualWriteIndexer;
|
||||
use crate::gateway_queue_adapter::GatewayQueueAdapter;
|
||||
use crate::queue_worker::{QueueWorker, QueueWorkerConfig};
|
||||
use crate::queue_adapter::QueueAdapter;
|
||||
use crate::rbac::{AccessGuard, Claims as RbacClaims, builtin_role_provider, ResourceMeta, ResourceType, Verb, Visibility};
|
||||
// RBAC removed for MVP - will add after core ingest/query working
|
||||
use crate::handlers::{
|
||||
QueryParams, QueryParamsError, SearchMethod, build_search_response,
|
||||
LearnParams, LearnParamsError, build_learn_response,
|
||||
@@ -41,8 +41,6 @@ pub struct AppState {
|
||||
pub opensearch_client: Option<Arc<OpenSearchClient>>,
|
||||
/// M3.8 Query Optimizer (optional, from environment)
|
||||
pub optimizer_service: Option<Arc<mem_core::optimizer::OptimizerService>>,
|
||||
/// RBAC Access Guard (optional, for fine-grained access control)
|
||||
pub access_guard: Option<Arc<AccessGuard>>,
|
||||
}
|
||||
|
||||
/// Authentication mode
|
||||
@@ -169,38 +167,6 @@ fn extract_rate_limit_key(claims: &JwtClaims) -> String {
|
||||
claims.sub.clone()
|
||||
}
|
||||
|
||||
/// Convert JWT claims to RBAC claims for AccessGuard
|
||||
fn to_rbac_claims(jwt: &JwtClaims) -> RbacClaims {
|
||||
RbacClaims::new(&jwt.sub)
|
||||
.with_roles(jwt.roles.clone().unwrap_or_default().iter().map(|s| s.as_str()).collect())
|
||||
.with_groups(jwt.groups.clone().unwrap_or_default().iter().map(|s| s.as_str()).collect())
|
||||
.with_permissions(jwt.permissions.clone().unwrap_or_default().iter().map(|s| s.as_str()).collect())
|
||||
}
|
||||
|
||||
/// Convert QueryResult to ResourceMeta for RBAC filtering
|
||||
fn query_result_to_resource_meta(result: &crate::query_worker::QueryResult, project: &str) -> ResourceMeta {
|
||||
let source = result.source.as_deref().unwrap_or("unknown");
|
||||
|
||||
// Determine resource type from source path
|
||||
let resource_type = if source.contains("SKILL-") || source.contains("/skills/") {
|
||||
ResourceType::Skill
|
||||
} else if result.level == "corpus" || result.level == "R" {
|
||||
ResourceType::Wiki // Reference docs are wiki-like
|
||||
} else {
|
||||
ResourceType::Embedding // L0, L1, L2 are learned embeddings
|
||||
};
|
||||
|
||||
// Determine visibility - private if source path suggests it
|
||||
let visibility = if source.contains("/private/") || source.contains("-private") {
|
||||
Visibility::Private
|
||||
} else {
|
||||
Visibility::Public
|
||||
};
|
||||
|
||||
ResourceMeta::new(source, resource_type, project)
|
||||
.with_visibility(visibility)
|
||||
}
|
||||
|
||||
/// Rate limit guard — call this in handlers to check rate limit
|
||||
fn check_rate_limit(claims: &JwtClaims, state: &AppState, endpoint: &str) -> Result<(), HttpResponse> {
|
||||
let key = extract_rate_limit_key(claims);
|
||||
@@ -391,12 +357,6 @@ pub async fn start_server(port: u16, api_key: String, database_url: &str) -> Res
|
||||
tracing::info!("M8.2 Queue Worker started (background task)");
|
||||
}
|
||||
|
||||
// Initialize RBAC AccessGuard with built-in roles
|
||||
let access_guard = {
|
||||
let role_provider = Arc::new(builtin_role_provider());
|
||||
Some(Arc::new(AccessGuard::new(role_provider)))
|
||||
};
|
||||
|
||||
let state = web::Data::new(AppState {
|
||||
api_key,
|
||||
start_time: Instant::now(),
|
||||
@@ -411,7 +371,6 @@ pub async fn start_server(port: u16, api_key: String, database_url: &str) -> Res
|
||||
auth_mode,
|
||||
opensearch_client,
|
||||
optimizer_service,
|
||||
access_guard,
|
||||
});
|
||||
|
||||
tracing::info!("Starting HTTP server on port {}", port);
|
||||
@@ -489,11 +448,6 @@ pub async fn ingest_handler(
|
||||
return e;
|
||||
}
|
||||
|
||||
// RBAC: Check project-level write access
|
||||
if let Err(e) = check_project_write_access(&state, &claims, &body.project).await {
|
||||
return e;
|
||||
}
|
||||
|
||||
// Check idempotency
|
||||
if let Some(cached) = state.idempotency_store.get(&body.ingest_id) {
|
||||
tracing::info!("Returning cached response for ingest_id: {}", body.ingest_id);
|
||||
@@ -504,29 +458,6 @@ pub async fn ingest_handler(
|
||||
execute_ingest(&state, &body).await
|
||||
}
|
||||
|
||||
/// Check RBAC project write access
|
||||
async fn check_project_write_access(
|
||||
state: &web::Data<AppState>,
|
||||
claims: &JwtClaims,
|
||||
project: &str,
|
||||
) -> Result<(), HttpResponse> {
|
||||
let Some(guard) = &state.access_guard else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let rbac_claims = to_rbac_claims(claims);
|
||||
let resource = ResourceMeta::new(project, ResourceType::Project, project);
|
||||
|
||||
if !guard.can_write(&rbac_claims, &resource).await {
|
||||
tracing::warn!("RBAC denied write access to project '{}' for user '{}'", project, claims.sub);
|
||||
return Err(HttpResponse::Forbidden().json(json!({
|
||||
"error": "forbidden",
|
||||
"reason": format!("write access denied to project '{}'", project)
|
||||
})));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute ingest job creation and spawn worker
|
||||
async fn execute_ingest(
|
||||
state: &web::Data<AppState>,
|
||||
@@ -715,11 +646,6 @@ pub async fn learn_handler(
|
||||
Err(e) => return e.to_response(),
|
||||
};
|
||||
|
||||
// RBAC: Check project-level write access
|
||||
if let Err(e) = check_project_write_access(&state, &claims, ¶ms.project).await {
|
||||
return e;
|
||||
}
|
||||
|
||||
// Chunk the markdown
|
||||
let chunks = chunk_markdown_text(¶ms.text, params.chunk_size);
|
||||
if chunks.is_empty() {
|
||||
@@ -907,9 +833,6 @@ pub async fn query_handler(
|
||||
// M3.8: Optimize results
|
||||
results = optimize_search_results(results, state.optimizer_service.as_ref()).await;
|
||||
|
||||
// RBAC: Filter by access control
|
||||
results = apply_rbac_filter(&state, &claims, results, ¶ms.project).await;
|
||||
|
||||
// Route by search method
|
||||
match params.method {
|
||||
SearchMethod::Semantic => build_search_response(¶ms, results, None),
|
||||
@@ -917,41 +840,6 @@ pub async fn query_handler(
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply RBAC filtering to search results
|
||||
async fn apply_rbac_filter(
|
||||
state: &web::Data<AppState>,
|
||||
claims: &JwtClaims,
|
||||
results: Vec<crate::query_worker::QueryResult>,
|
||||
project: &str,
|
||||
) -> Vec<crate::query_worker::QueryResult> {
|
||||
let Some(guard) = &state.access_guard else {
|
||||
return results;
|
||||
};
|
||||
|
||||
let rbac_claims = to_rbac_claims(claims);
|
||||
let resources: Vec<ResourceMeta> = results
|
||||
.iter()
|
||||
.map(|r| query_result_to_resource_meta(r, project))
|
||||
.collect();
|
||||
|
||||
let decisions = guard.check_access_batch(&rbac_claims, &resources, Verb::Read).await;
|
||||
|
||||
let filtered: Vec<_> = results
|
||||
.into_iter()
|
||||
.zip(decisions.iter())
|
||||
.filter(|(_, d)| d.is_allowed())
|
||||
.map(|(r, _)| r)
|
||||
.collect();
|
||||
|
||||
tracing::debug!(
|
||||
"RBAC filtered {} results for user {}",
|
||||
decisions.iter().filter(|d| d.is_denied()).count(),
|
||||
claims.sub
|
||||
);
|
||||
|
||||
filtered
|
||||
}
|
||||
|
||||
/// Execute hybrid search with OpenSearch fallback
|
||||
async fn execute_hybrid_search(
|
||||
state: &web::Data<AppState>,
|
||||
@@ -1017,22 +905,7 @@ pub async fn projects_handler(
|
||||
|
||||
match result {
|
||||
Ok(rows) => {
|
||||
let mut projects: Vec<String> = rows.into_iter().map(|(p,)| p).collect();
|
||||
|
||||
// RBAC: Filter projects by access
|
||||
if let Some(guard) = &state.access_guard {
|
||||
let rbac_claims = to_rbac_claims(&claims);
|
||||
let mut allowed_projects = Vec::new();
|
||||
|
||||
for project in projects {
|
||||
let resource = ResourceMeta::new(&project, ResourceType::Project, &project);
|
||||
if guard.can_read(&rbac_claims, &resource).await {
|
||||
allowed_projects.push(project);
|
||||
}
|
||||
}
|
||||
projects = allowed_projects;
|
||||
}
|
||||
|
||||
let projects: Vec<String> = rows.into_iter().map(|(p,)| p).collect();
|
||||
HttpResponse::Ok().json(json!({
|
||||
"projects": projects,
|
||||
"count": projects.len()
|
||||
@@ -1118,23 +991,6 @@ pub async fn context_handler(
|
||||
let scope = body.scope.clone().unwrap_or_else(|| "project".to_string());
|
||||
let budget = body.budget.unwrap_or(6000);
|
||||
|
||||
// RBAC: Check project-level access
|
||||
if let Some(guard) = &state.access_guard {
|
||||
let rbac_claims = to_rbac_claims(&claims);
|
||||
let project_resource = ResourceMeta::new(&project, ResourceType::Project, &project);
|
||||
|
||||
if !guard.can_read(&rbac_claims, &project_resource).await {
|
||||
tracing::warn!(
|
||||
"RBAC denied access to project '{}' for user '{}'",
|
||||
project, claims.sub
|
||||
);
|
||||
return HttpResponse::Forbidden().json(json!({
|
||||
"error": "forbidden",
|
||||
"reason": format!("access denied to project '{}'", project)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let lookup = crate::context_endpoint::ContextLookup::new(budget, project, scope);
|
||||
|
||||
match lookup.lookup(body.into_inner()).await {
|
||||
|
||||
Reference in New Issue
Block a user