haste_server/auth_n/middleware/
project_access.rs

1use crate::extract::path_tenant::{ProjectIdentifier, TenantIdentifier};
2use axum::{Extension, extract::Request, middleware::Next, response::Response};
3use axum_extra::extract::Cached;
4use haste_fhir_model::r4::generated::terminology::IssueType;
5use haste_fhir_operation_error::OperationOutcomeError;
6use haste_jwt::claims::UserTokenClaims;
7use std::sync::Arc;
8
9pub async fn project_access(
10    Cached(TenantIdentifier { tenant }): Cached<TenantIdentifier>,
11    Cached(ProjectIdentifier { project }): Cached<ProjectIdentifier>,
12    // run the `HeaderMap` extractor
13    Extension(claims): Extension<Arc<UserTokenClaims>>,
14    // you can also add more extractors here but the last
15    // extractor must implement `FromRequest` which
16    // `Request` does
17    request: Request,
18    next: Next,
19) -> Result<Response, OperationOutcomeError> {
20    if claims.tenant != tenant {
21        return Err(OperationOutcomeError::error(
22            IssueType::Forbidden(None),
23            format!("User does not have access to tenant '{}'.", tenant),
24        ));
25    }
26
27    let Some(user_project) = &claims.project else {
28        return Err(OperationOutcomeError::error(
29            IssueType::Forbidden(None),
30            format!("User does not have access to project '{}'.", project),
31        ));
32    };
33
34    if user_project != &project {
35        return Err(OperationOutcomeError::error(
36            IssueType::Forbidden(None),
37            format!("User does not have access to project '{}'.", project),
38        ));
39    }
40
41    Ok(next.run(request).await)
42}