From 58e20f41b3a20980e76d376c013956bff40391b4 Mon Sep 17 00:00:00 2001 From: Tobias Frischmann Date: Thu, 24 Sep 2026 13:20:16 +0200 Subject: [PATCH] validate if in org --- api/services/src/organization.rs | 14 ++++++ api/src/auth.rs | 34 +++++++++++---- api/tests/auth.rs | 52 +++++++++++++++++++++++ api/tests/common.rs | 73 +++++++++++++++++--------------- 4 files changed, 130 insertions(+), 43 deletions(-) diff --git a/api/services/src/organization.rs b/api/services/src/organization.rs index dab05f7..bbfcd72 100644 --- a/api/services/src/organization.rs +++ b/api/services/src/organization.rs @@ -47,6 +47,20 @@ impl OrganizationService { Ok(exists) } + pub async fn has_access( + db: &OrmConnection, + user: &str, + organization: Uuid, + ) -> Result { + let exists = organization_user::Entity::find() + .filter(organization_user::COLUMN.user_id.eq(user)) + .filter(organization_user::COLUMN.organization_id.eq(organization)) + .exists(db) + .await?; + + Ok(exists) + } + pub async fn joined( user: &User, db: &OrmConnection, diff --git a/api/src/auth.rs b/api/src/auth.rs index 808cce2..05e50cb 100644 --- a/api/src/auth.rs +++ b/api/src/auth.rs @@ -4,7 +4,7 @@ use axum::extract::FromRequestParts; use http::{HeaderMap, HeaderValue, StatusCode, request::Parts}; use jsonwebtoken::{DecodingKey, jwk::JwkSet}; use serde::Deserialize; -use services::User; +use services::{User, organization::OrganizationService}; use tokio::sync::RwLock; use tracing::{info, warn}; @@ -38,18 +38,34 @@ impl FromRequestParts for User { None }; - if let Some(user_id) = user_id { - if let Some(org_id) = headers.get("x-org-id") { - return Ok(User::new( - user_id, - Some(org_id.to_str().unwrap_or_default().to_string()), - )); + let Some(user_id) = user_id else { + return Err((StatusCode::UNAUTHORIZED, "Authorization header missing")); + }; + + if let Some(org_id) = headers.get("x-org-id") { + let Ok(org_id) = org_id.to_str().unwrap_or_default().to_string().parse() else { + return Err((StatusCode::UNAUTHORIZED, "Authorization Invalid Org")); + }; + + let has_access = + OrganizationService::has_access(&state.db_connection, &user_id, org_id) + .await + .map_err(|e| { + warn!("{:?}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to check Org access", + ) + })?; + + if !has_access { + return Err((StatusCode::UNAUTHORIZED, "No Access")); } - return Ok(User::new(user_id, None)); + return Ok(User::new(user_id, Some(org_id.to_string()))); } - Err((StatusCode::UNAUTHORIZED, "Authorization header missing")) + Ok(User::new(user_id, None)) } } diff --git a/api/tests/auth.rs b/api/tests/auth.rs index c563dbe..36f3132 100644 --- a/api/tests/auth.rs +++ b/api/tests/auth.rs @@ -1,3 +1,6 @@ +use serde_json::json; +use uuid::Uuid; + use crate::common::{Client, get_server}; mod common; @@ -10,3 +13,52 @@ async fn failes_without_login() { let response = client.get_todos().await; response.assert_status_unauthorized(); } + +#[tokio::test] +async fn works_with_org() { + let server = get_server().await; + let mut client = Client::new(&server); + + let org_uuid = Uuid::new_v4(); + client + .add_organization_json(json!({ + "uuid": org_uuid, + "name": "Test Organization", + })) + .await; + + client.set_org(org_uuid); + + let response = client.get_todos().await; + response.assert_status_success(); +} + +#[tokio::test] +async fn failes_for_unknown_org() { + let server = get_server().await; + let client = Client::new_with_org(&server, Uuid::new_v4()); + + let response = client.get_todos().await; + response.assert_status_unauthorized(); +} + +#[tokio::test] +async fn failes_for_not_joined_org() { + let server = get_server().await; + + let user1_client = Client::new(&server); + let org_uuid = Uuid::new_v4(); + let add_org_response = user1_client + .add_organization(json!({ + "uuid": org_uuid, + "name": "Test Organization", + })) + .await; + add_org_response.assert_status_success(); + + // User 2 tries to access the organization without joining + let user2_client = Client::new_with_org(&server, org_uuid); + + let response = user2_client.get_todos().await; + response.assert_status_unauthorized(); +} diff --git a/api/tests/common.rs b/api/tests/common.rs index 153561c..6ef38f1 100644 --- a/api/tests/common.rs +++ b/api/tests/common.rs @@ -3,7 +3,7 @@ use std::sync::OnceLock; use api::app; -use axum_test::{TestResponse, TestServer}; +use axum_test::{TestRequest, TestResponse, TestServer}; use chrono::NaiveDate; use serde::de::DeserializeOwned; use serde_json::json; @@ -18,6 +18,7 @@ use uuid::Uuid; pub struct Client<'a> { uuid: Option, + org: Option, server: &'a TestServer, } @@ -27,14 +28,31 @@ impl<'a> Client<'a> { Client { uuid: Some(uuid), + org: None, server, } } pub fn new_no_user(server: &'a TestServer) -> Client<'a> { + Client { + uuid: None, + org: None, + server, + } + } + + pub fn new_with_org(server: &'a TestServer, org: Uuid) -> Client<'a> { let uuid = Uuid::new_v4(); - Client { uuid: None, server } + Client { + uuid: Some(uuid), + org: Some(org), + server, + } + } + + pub fn set_org(&mut self, org: Uuid) { + self.org = Some(org) } pub fn uuid(&self) -> Option { @@ -43,54 +61,41 @@ impl<'a> Client<'a> { } impl<'a> Client<'a> { - async fn get(&self, path: &str) -> TestResponse { - let mut request = self.server.get(&format!("/api/{path}")); - + fn add_header(&self, mut request: TestRequest) -> TestRequest { if let Some(uuid) = self.uuid { request = request.add_header("x-user-id", uuid.to_string()); }; - request.await - } - - async fn post(&self, path: &str) -> TestResponse { - let mut request = self.server.post(&format!("/api/{path}")); - - if let Some(uuid) = self.uuid { - request = request.add_header("x-user-id", uuid.to_string()); + if let Some(uuid) = self.org { + request = request.add_header("x-org-id", uuid.to_string()); }; - request.await + request } - async fn post_json(&self, path: &str, body: serde_json::Value) -> TestResponse { - let mut request = self.server.post(&format!("/api/{path}")).json(&body); + async fn get(&self, path: &str) -> TestResponse { + self.add_header(self.server.get(&format!("/api/{path}"))) + .await + } - if let Some(uuid) = self.uuid { - request = request.add_header("x-user-id", uuid.to_string()); - }; + async fn post(&self, path: &str) -> TestResponse { + self.add_header(self.server.post(&format!("/api/{path}"))) + .await + } - request.await + async fn post_json(&self, path: &str, body: serde_json::Value) -> TestResponse { + self.add_header(self.server.post(&format!("/api/{path}")).json(&body)) + .await } async fn put_json(&self, path: &str, body: serde_json::Value) -> TestResponse { - let mut request = self.server.put(&format!("/api/{path}")).json(&body); - - if let Some(uuid) = self.uuid { - request = request.add_header("x-user-id", uuid.to_string()); - }; - - request.await + self.add_header(self.server.put(&format!("/api/{path}")).json(&body)) + .await } async fn delete(&self, path: &str) -> TestResponse { - let mut request = self.server.delete(&format!("/api/{path}")); - - if let Some(uuid) = self.uuid { - request = request.add_header("x-user-id", uuid.to_string()); - }; - - request.await + self.add_header(self.server.delete(&format!("/api/{path}"))) + .await } fn json(response: &TestResponse) -> T { -- 2.51.2