diff --git a/crates/common/src/repo/project_members.rs b/crates/common/src/repo/project_members.rs index d77c9f4..2f3b9f1 100644 --- a/crates/common/src/repo/project_members.rs +++ b/crates/common/src/repo/project_members.rs @@ -6,7 +6,7 @@ use uuid::Uuid; use crate::{ error::{CiError, Result}, models::{CreateProjectMember, ProjectMember, UpdateProjectMember}, - roles::{VALID_PROJECT_ROLES, has_project_permission}, + roles::VALID_PROJECT_ROLES, validation::validate_role, }; diff --git a/crates/common/src/repo/users.rs b/crates/common/src/repo/users.rs index ca91cd6..e899043 100644 --- a/crates/common/src/repo/users.rs +++ b/crates/common/src/repo/users.rs @@ -6,7 +6,7 @@ use uuid::Uuid; use crate::{ error::{CiError, Result}, models::{CreateUser, LoginCredentials, UpdateUser, User}, - roles::{ROLE_READ_ONLY, VALID_ROLES, is_valid_role}, + roles::{ROLE_READ_ONLY, VALID_ROLES}, validation::{ validate_email, validate_full_name, diff --git a/crates/migrate-cli/Cargo.toml b/crates/migrate-cli/Cargo.toml index f3fdd0b..b98f9f7 100644 --- a/crates/migrate-cli/Cargo.toml +++ b/crates/migrate-cli/Cargo.toml @@ -11,8 +11,9 @@ name = "fc-migrate" path = "src/main.rs" [dependencies] +fc-common.workspace = true + anyhow.workspace = true clap.workspace = true -fc-common = { path = "../common" } tokio.workspace = true tracing-subscriber.workspace = true diff --git a/crates/server/src/auth_middleware.rs b/crates/server/src/auth_middleware.rs index 7c9a5dd..ede3b05 100644 --- a/crates/server/src/auth_middleware.rs +++ b/crates/server/src/auth_middleware.rs @@ -72,8 +72,8 @@ pub async fn require_api_key( .and_then(|v| v.to_str().ok()) { // Try user session first (new fc_user_session cookie) - if let Some(session_id) = parse_cookie(cookie_header, "fc_user_session") { - if let Some(session) = state.sessions.get(&session_id) { + if let Some(session_id) = parse_cookie(cookie_header, "fc_user_session") + && let Some(session) = state.sessions.get(&session_id) { // Check session expiry (24 hours) if session.created_at.elapsed() < std::time::Duration::from_secs(24 * 60 * 60) @@ -92,11 +92,10 @@ pub async fn require_api_key( state.sessions.remove(&session_id); } } - } // Try legacy API key session (fc_session cookie) - if let Some(session_id) = parse_cookie(cookie_header, "fc_session") { - if let Some(session) = state.sessions.get(&session_id) { + if let Some(session_id) = parse_cookie(cookie_header, "fc_session") + && let Some(session) = state.sessions.get(&session_id) { // Check session expiry (24 hours) if session.created_at.elapsed() < std::time::Duration::from_secs(24 * 60 * 60) @@ -111,7 +110,6 @@ pub async fn require_api_key( state.sessions.remove(&session_id); } } - } } // No valid auth found @@ -134,8 +132,8 @@ impl FromRequestParts for RequireAdmin { _state: &AppState, ) -> Result { // Check for user first (new auth) - if let Some(user) = parts.extensions.get::() { - if user.role == "admin" { + if let Some(user) = parts.extensions.get::() + && user.role == "admin" { // Create a synthetic API key for compatibility return Ok(RequireAdmin(ApiKey { id: user.id, @@ -147,7 +145,6 @@ impl FromRequestParts for RequireAdmin { user_id: Some(user.id), })); } - } // Fall back to API key let key = parts @@ -175,8 +172,8 @@ impl RequireRoles { allowed: &[&str], ) -> Result { // Check for user first - if let Some(user) = extensions.get::() { - if user.role == "admin" || allowed.contains(&user.role.as_str()) { + if let Some(user) = extensions.get::() + && (user.role == "admin" || allowed.contains(&user.role.as_str())) { return Ok(ApiKey { id: user.id, name: user.username.clone(), @@ -187,7 +184,6 @@ impl RequireRoles { user_id: Some(user.id), }); } - } // Fall back to API key let key = extensions @@ -220,8 +216,8 @@ pub async fn extract_session( if let Some(cookie_header) = cookie_header { // Try user session first - if let Some(session_id) = parse_cookie(&cookie_header, "fc_user_session") { - if let Some(session) = state.sessions.get(&session_id) { + if let Some(session_id) = parse_cookie(&cookie_header, "fc_user_session") + && let Some(session) = state.sessions.get(&session_id) { // Check session expiry if session.created_at.elapsed() < std::time::Duration::from_secs(24 * 60 * 60) @@ -237,11 +233,10 @@ pub async fn extract_session( state.sessions.remove(&session_id); } } - } // Try legacy API key session - if let Some(session_id) = parse_cookie(&cookie_header, "fc_session") { - if let Some(session) = state.sessions.get(&session_id) { + if let Some(session_id) = parse_cookie(&cookie_header, "fc_session") + && let Some(session) = state.sessions.get(&session_id) { // Check session expiry if session.created_at.elapsed() < std::time::Duration::from_secs(24 * 60 * 60) @@ -254,7 +249,6 @@ pub async fn extract_session( state.sessions.remove(&session_id); } } - } } next.run(request).await diff --git a/crates/server/src/routes/users.rs b/crates/server/src/routes/users.rs index 46f71a4..db00365 100644 --- a/crates/server/src/routes/users.rs +++ b/crates/server/src/routes/users.rs @@ -11,12 +11,11 @@ use fc_common::{ models::{ CreateStarredJob, CreateUser, - LoginCredentials, PaginationParams, UpdateUser, User, }, - repo::{self, api_keys}, + repo::{self}, }; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -175,7 +174,6 @@ async fn delete_user( // --- Current User Handlers --- async fn get_current_user( - State(state): State, extensions: axum::http::Extensions, ) -> Result, ApiError> { // Try to get user from extensions first diff --git a/crates/server/tests/api_tests.rs b/crates/server/tests/api_tests.rs index d39247b..ca28b63 100644 --- a/crates/server/tests/api_tests.rs +++ b/crates/server/tests/api_tests.rs @@ -41,6 +41,24 @@ fn build_app(pool: sqlx::PgPool) -> axum::Router { fc_server::routes::router(state, &server_config) } +#[tokio::test] +async fn test_router_no_duplicate_routes() { + let pool = match get_pool().await { + Some(p) => p, + None => return, + }; + + let config = fc_common::config::Config::default(); + let server_config = config.server.clone(); + let state = fc_server::state::AppState { + pool, + config, + sessions: std::sync::Arc::new(dashmap::DashMap::new()), + }; + + let _app = fc_server::routes::router(state, &server_config); +} + fn build_app_with_config( pool: sqlx::PgPool, config: fc_common::config::Config,