Skip to main content

haste_server/auth_n/oidc/routes/
token.rs

1use crate::{
2    auth_n::{
3        certificates::get_certification_provider,
4        oidc::{
5            code_verification,
6            error::{OIDCError, OIDCErrorCode},
7            extract::{body::ParsedBody, client_app::find_client_app},
8            routes::scope::verify_requested_scope_is_subset,
9            schemas,
10        },
11    },
12    extract::path_tenant::{ProjectIdentifier, TenantIdentifier},
13    services::AppState,
14};
15use axum::{
16    Json,
17    extract::State,
18    response::{IntoResponse, Response},
19};
20use axum_extra::{TypedHeader, extract::Cached, headers::UserAgent, routing::TypedPath};
21use haste_fhir_client::{
22    request::{FHIRSearchTypeRequest, SearchRequest},
23    url::{Parameter, ParsedParameter, ParsedParameters},
24};
25use haste_fhir_model::r4::generated::{
26    resources::{ClientApplication, ResourceType},
27    terminology::ClientapplicationGrantType,
28};
29use haste_fhir_search::SearchEngine;
30use haste_fhir_terminology::FHIRTerminology;
31use haste_jwt::{
32    AuthorId, AuthorKind, ProjectId, TenantId, UserRole, VersionId,
33    claims::{SubscriptionTier, UserTokenClaims},
34    scopes::{OIDCScope, Scope, Scopes},
35};
36use haste_repository::{
37    Repository,
38    admin::{ProjectAuthAdmin, TenantAuthAdmin},
39    types::{
40        SupportedFHIRVersions,
41        authorization_code::{
42            AuthorizationCodeKind, AuthorizationCodeSearchClaims, CreateAuthorizationCode,
43        },
44        scope::{ClientId, CreateScope, ScopeSearchClaims, UserId},
45        tenant::CreateTenant,
46        user::{User, UserRole as RepoUserRole},
47    },
48};
49use jsonwebtoken::{Algorithm, Header};
50use serde::{Deserialize, Serialize};
51use serde_json::json;
52use std::{sync::Arc, time::Duration};
53
54#[derive(TypedPath)]
55#[typed_path("/token")]
56pub struct TokenPath;
57
58#[derive(Serialize, Deserialize, Debug)]
59pub enum TokenType {
60    Bearer,
61}
62
63pub static TOKEN_EXPIRATION: usize = 7200; // 2 hours
64pub static REFRESH_TOKEN_EXPIRATION: usize = 43200; // 12 hours
65
66#[derive(Serialize, Deserialize, Debug)]
67pub struct TokenResponse {
68    pub access_token: String,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    refresh_token: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub id_token: Option<String>,
73    token_type: TokenType,
74    expires_in: usize,
75}
76
77struct TokenResponseArguments {
78    user_id: String,
79    user_role: UserRole,
80    user_kind: AuthorKind,
81    client_id: String,
82    scopes: Scopes,
83    tenant: TenantId,
84    project: ProjectId,
85    membership: Option<String>,
86    access_policy_version_ids: Vec<VersionId>,
87}
88
89async fn create_token_response<Repo: Repository>(
90    user_agent: &Option<TypedHeader<UserAgent>>,
91    repo: &Repo,
92    client_app: &ClientApplication,
93    grant_type_used: &schemas::token_body::OAuth2TokenBodyGrantType,
94    args: TokenResponseArguments,
95) -> Result<TokenResponse, OIDCError> {
96    let cert_provider = get_certification_provider();
97    let encoding_key = cert_provider.encoding_key().map_err(|_e| {
98        OIDCError::new(
99            OIDCErrorCode::ServerError,
100            Some("Failed to create access token. No encoding key available.".to_string()),
101            None,
102        )
103    })?;
104
105    let tenant = TenantAuthAdmin::<CreateTenant, _, _, _, _>::read(
106        repo,
107        &TenantId::System,
108        &args.tenant.as_ref().to_string(),
109    )
110    .await
111    .map_err(|_e| {
112        OIDCError::new(
113            OIDCErrorCode::ServerError,
114            Some("Failed to retrieve tenant information.".to_string()),
115            None,
116        )
117    })?
118    .ok_or_else(|| {
119        OIDCError::new(
120            OIDCErrorCode::ServerError,
121            Some("Tenant not found.".to_string()),
122            None,
123        )
124    })?;
125
126    let mut header = Header::new(Algorithm::RS256);
127    header.kid = Some(encoding_key.kid.clone());
128
129    let token = jsonwebtoken::encode(
130        &header,
131        &UserTokenClaims {
132            sub: AuthorId::new(args.user_id.clone()),
133            exp: (chrono::Utc::now() + chrono::Duration::seconds(TOKEN_EXPIRATION as i64))
134                .timestamp() as usize,
135            aud: args.client_id.clone(),
136            scope: args.scopes.clone(),
137            tenant: args.tenant.clone(),
138            subscription_tier: SubscriptionTier::try_from(tenant.subscription_tier).map_err(
139                |e| OIDCError::new(OIDCErrorCode::ServerError, Some(e.to_string()), None),
140            )?,
141            project: Some(args.project.clone()),
142            user_role: args.user_role,
143            user_id: AuthorId::new(args.user_id.clone()),
144            membership: args.membership.clone(),
145            resource_type: args.user_kind,
146            access_policy_version_ids: args.access_policy_version_ids,
147        },
148        &encoding_key.encoding_key,
149    )
150    .map_err(|_| {
151        OIDCError::new(
152            OIDCErrorCode::ServerError,
153            Some("Failed to create access token.".to_string()),
154            None,
155        )
156    })?;
157
158    let mut response = TokenResponse {
159        access_token: token.clone(),
160        id_token: None,
161        expires_in: TOKEN_EXPIRATION,
162        refresh_token: None,
163        token_type: TokenType::Bearer,
164    };
165
166    if args.scopes.contains_scope(&Scope::OIDC(OIDCScope::OpenId)) {
167        response.id_token = Some(token);
168    }
169
170    // If offline means refresh token should be generated.
171    if (&args.scopes.0)
172        .iter()
173        .find(|s| **s == Scope::OIDC(OIDCScope::OfflineAccess))
174        .is_some()
175        && client_app
176            .grantType
177            .iter()
178            .find(|gt| {
179                let discriminator = std::mem::discriminant(gt.as_ref());
180                let offline_discriminator =
181                    std::mem::discriminant(&ClientapplicationGrantType::Refresh_token(None));
182                discriminator == offline_discriminator
183            })
184            .is_some()
185            // Client credentials grant does not get refresh tokens. Serves no purpose and requires knowing user kind to 
186            // rebuild the token.
187        && *grant_type_used != schemas::token_body::OAuth2TokenBodyGrantType::ClientCredentials
188    {
189        let existing_refresh_tokens_for_agent =
190            ProjectAuthAdmin::<CreateAuthorizationCode, _, _, _, _>::search(
191                repo,
192                &args.tenant,
193                &args.project,
194                &AuthorizationCodeSearchClaims {
195                    client_id: Some(args.client_id.clone()),
196                    user_id: Some(args.user_id.clone()),
197                    kind: Some(AuthorizationCodeKind::RefreshToken),
198                    code: None,
199                    user_agent: user_agent.as_ref().map(|ua| ua.as_str().to_string()),
200                    is_expired: None,
201                },
202            )
203            .await
204            .map_err(|_| {
205                OIDCError::new(
206                    OIDCErrorCode::ServerError,
207                    Some("Failed to retrieve existing refresh tokens.".to_string()),
208                    None,
209                )
210            })?;
211
212        for existing_token in existing_refresh_tokens_for_agent {
213            ProjectAuthAdmin::<CreateAuthorizationCode, _, _, _, _>::delete(
214                repo,
215                &args.tenant,
216                &args.project,
217                &existing_token.code,
218            )
219            .await
220            .map_err(|_e| {
221                OIDCError::new(
222                    OIDCErrorCode::ServerError,
223                    Some("Failed to delete existing refresh token.".to_string()),
224                    None,
225                )
226            })?;
227        }
228
229        let refresh_token = ProjectAuthAdmin::create(
230            repo,
231            &args.tenant,
232            &args.project,
233            CreateAuthorizationCode {
234                membership: args.membership,
235                user_id: args.user_id,
236                expires_in: Duration::from_secs(REFRESH_TOKEN_EXPIRATION as u64),
237                kind: AuthorizationCodeKind::RefreshToken,
238                client_id: Some(args.client_id),
239                pkce_code_challenge: None,
240                pkce_code_challenge_method: None,
241                redirect_uri: None,
242                meta: Some(sqlx::types::Json(json!({
243                    "user_agent": user_agent.as_ref().map(|ua| ua.to_string()),
244                }))),
245            },
246        )
247        .await
248        .map_err(|_e| {
249            OIDCError::new(
250                OIDCErrorCode::ServerError,
251                Some("Failed to create refresh token.".to_string()),
252                None,
253            )
254        })?;
255
256        response.refresh_token = Some(refresh_token.code);
257    }
258
259    Ok(response)
260}
261
262async fn get_approved_scopes<Repo: Repository>(
263    repo: &Repo,
264    tenant: &TenantId,
265    project: &ProjectId,
266    user_id: UserId,
267    client_id: ClientId,
268) -> Result<Scopes, OIDCError> {
269    let approved_scopes = ProjectAuthAdmin::<CreateScope, _, _, _, _>::search(
270        repo,
271        &tenant,
272        &project,
273        &ScopeSearchClaims {
274            user_: Some(user_id),
275            client: Some(client_id),
276        },
277    )
278    .await
279    .map_err(|_e| {
280        OIDCError::new(
281            OIDCErrorCode::ServerError,
282            Some("Failed to retrieve user's approved scopes.".to_string()),
283            None,
284        )
285    })?
286    .get(0)
287    .map(|s| s.scope.clone())
288    .unwrap_or_else(|| Default::default());
289
290    Ok(approved_scopes)
291}
292
293fn validate_client_grant_type(
294    client_app: &ClientApplication,
295    grant_type: &ClientapplicationGrantType,
296) -> Result<(), OIDCError> {
297    if client_app
298        .grantType
299        .iter()
300        .find(|gt| {
301            let discriminator = std::mem::discriminant(gt.as_ref());
302            let requested_discriminator = std::mem::discriminant(grant_type);
303            discriminator == requested_discriminator
304        })
305        .is_none()
306    {
307        return Err(OIDCError::new(
308            OIDCErrorCode::AccessDenied,
309            Some("Client application is not authorized for the requested grant type.".to_string()),
310            None,
311        ));
312    }
313
314    Ok(())
315}
316
317fn verify_client(
318    client_app: &ClientApplication,
319    token_request_body: &schemas::token_body::OAuth2TokenBody,
320) -> Result<(), OIDCError> {
321    // Verify the grant types align
322    match token_request_body.grant_type {
323        schemas::token_body::OAuth2TokenBodyGrantType::ClientCredentials => {
324            validate_client_grant_type(
325                client_app,
326                &ClientapplicationGrantType::Client_credentials(None),
327            )?;
328        }
329        schemas::token_body::OAuth2TokenBodyGrantType::RefreshToken => {
330            validate_client_grant_type(
331                client_app,
332                &ClientapplicationGrantType::Refresh_token(None),
333            )?;
334        }
335        schemas::token_body::OAuth2TokenBodyGrantType::AuthorizationCode => {
336            validate_client_grant_type(
337                client_app,
338                &ClientapplicationGrantType::Authorization_code(None),
339            )?;
340        }
341    }
342
343    if client_app.id.as_ref() != Some(&token_request_body.client_id) {
344        return Err(OIDCError::new(
345            OIDCErrorCode::AccessDenied,
346            Some("Invalid credentials".to_string()),
347            None,
348        ));
349    }
350
351    if client_app
352        .secret
353        .as_ref()
354        .and_then(|s| s.value.as_ref().map(String::as_str))
355        != token_request_body
356            .client_secret
357            .as_ref()
358            .map(String::as_str)
359    {
360        return Err(OIDCError::new(
361            OIDCErrorCode::AccessDenied,
362            Some("Invalid credentials".to_string()),
363            None,
364        ));
365    }
366
367    Ok(())
368}
369
370async fn find_users_access_policy_version_ids<Search: SearchEngine>(
371    search: &Search,
372    tenant: &TenantId,
373    project: &ProjectId,
374    user_id: &str,
375    user_type: &ResourceType,
376) -> Result<Vec<VersionId>, OIDCError> {
377    let access_policies = search
378        .search(
379            &SupportedFHIRVersions::R4,
380            &tenant,
381            &project,
382            &SearchRequest::Type(FHIRSearchTypeRequest {
383                resource_type: ResourceType::AccessPolicyV2,
384                parameters: ParsedParameters::new(vec![ParsedParameter::Resource(Parameter {
385                    name: "link".to_string(),
386                    value: vec![format!("{}/{}", user_type.as_ref(), user_id)],
387                    modifier: None,
388                    chains: None,
389                })]),
390            }),
391            None,
392        )
393        .await
394        .map_err(|_e| {
395            OIDCError::new(
396                OIDCErrorCode::ServerError,
397                Some("Failed to search for user's access policies.".to_string()),
398                None,
399            )
400        })?;
401
402    Ok(access_policies
403        .entries
404        .into_iter()
405        .map(|ap| ap.version_id)
406        .collect())
407}
408
409#[derive(PartialEq, Eq)]
410pub enum ClientCredentialsMethod {
411    BasicAuth,
412    Body,
413}
414
415pub async fn client_credentials_to_token_response<
416    Repo: Repository + Send + Sync,
417    Search: SearchEngine + Send + Sync,
418    Terminology: FHIRTerminology + Send + Sync,
419>(
420    state: &AppState<Repo, Search, Terminology>,
421    tenant: &TenantId,
422    project: &ProjectId,
423    user_agent: &Option<TypedHeader<UserAgent>>,
424    token_body: &schemas::token_body::OAuth2TokenBody,
425    method: ClientCredentialsMethod,
426) -> Result<TokenResponse, OIDCError> {
427    let client_id = &token_body.client_id;
428    let client_app =
429        find_client_app(state, tenant.clone(), project.clone(), client_id.clone()).await?;
430
431    verify_client(&client_app, &token_body)?;
432
433    // Allow basic auth if client app allows grant.
434    if method == ClientCredentialsMethod::BasicAuth {
435        validate_client_grant_type(&client_app, &ClientapplicationGrantType::Basic_auth(None))?;
436    }
437
438    let client_app_scopes = client_app
439        .scope
440        .as_ref()
441        .and_then(|s| s.value.as_ref().map(String::as_str))
442        .unwrap_or_default();
443
444    let requested_scopes = Scopes::from(
445        token_body
446            .scope
447            .clone()
448            .unwrap_or_else(|| client_app_scopes.to_string()),
449    );
450
451    verify_requested_scope_is_subset(
452        &requested_scopes,
453        &Scopes::try_from(client_app_scopes).map_err(|_| {
454            OIDCError::new(
455                OIDCErrorCode::InvalidScope,
456                Some("Client application's configured scopes are invalid.".to_string()),
457                None,
458            )
459        })?,
460    )?;
461
462    let response = create_token_response(
463        user_agent,
464        &*state.repo,
465        &client_app,
466        &token_body.grant_type,
467        TokenResponseArguments {
468            user_id: client_app.id.clone().unwrap_or_default(),
469            user_role: UserRole::Member,
470            user_kind: AuthorKind::ClientApplication,
471            client_id: client_app.id.clone().unwrap_or_default(),
472            scopes: requested_scopes,
473            tenant: tenant.clone(),
474            project: project.clone(),
475            membership: None,
476            access_policy_version_ids: find_users_access_policy_version_ids(
477                state.search.as_ref(),
478                &tenant,
479                &project,
480                client_id,
481                &ResourceType::ClientApplication,
482            )
483            .await?,
484        },
485    )
486    .await?;
487
488    Ok(response)
489}
490
491pub async fn token<
492    Repo: Repository + Send + Sync,
493    Search: SearchEngine + Send + Sync,
494    Terminology: FHIRTerminology + Send + Sync,
495>(
496    _: TokenPath,
497    user_agent: Option<TypedHeader<UserAgent>>,
498    Cached(TenantIdentifier { tenant }): Cached<TenantIdentifier>,
499    Cached(ProjectIdentifier { project }): Cached<ProjectIdentifier>,
500    State(state): State<Arc<AppState<Repo, Search, Terminology>>>,
501    ParsedBody(token_body): ParsedBody<schemas::token_body::OAuth2TokenBody>,
502) -> Result<Response, OIDCError> {
503    match &token_body.grant_type {
504        schemas::token_body::OAuth2TokenBodyGrantType::ClientCredentials => {
505            let response = client_credentials_to_token_response(
506                &*state,
507                &tenant,
508                &project,
509                &user_agent,
510                &token_body,
511                ClientCredentialsMethod::Body,
512            )
513            .await?;
514
515            Ok(Json(response).into_response())
516        }
517        schemas::token_body::OAuth2TokenBodyGrantType::RefreshToken => {
518            let client_id = &token_body.client_id;
519            let refresh_token = &token_body.refresh_token.as_ref().ok_or_else(|| {
520                OIDCError::new(
521                    OIDCErrorCode::InvalidRequest,
522                    Some("refresh_token is required for refresh_token grant type.".to_string()),
523                    token_body.redirect_uri.clone(),
524                )
525            })?;
526
527            let client_app =
528                find_client_app(&state, tenant.clone(), project.clone(), client_id.clone()).await?;
529
530            verify_client(&client_app, &token_body)?;
531
532            let code = code_verification::retrieve_and_verify_code(
533                &*state.repo,
534                &tenant,
535                &project,
536                &client_app,
537                AuthorizationCodeKind::RefreshToken,
538                &refresh_token,
539                None,
540                None,
541            )
542            .await
543            .map_err(|_e| {
544                OIDCError::new(
545                    OIDCErrorCode::InvalidGrant,
546                    Some("Invalid refresh token.".to_string()),
547                    token_body.redirect_uri.clone(),
548                )
549            })?;
550
551            if code.kind != AuthorizationCodeKind::RefreshToken {
552                return Err(OIDCError::new(
553                    OIDCErrorCode::InvalidGrant,
554                    Some("Invalid refresh token.".to_string()),
555                    token_body.redirect_uri.clone(),
556                ));
557            }
558
559            if code.is_expired.unwrap_or(true) {
560                return Err(OIDCError::new(
561                    OIDCErrorCode::InvalidGrant,
562                    Some("Refresh token has expired.".to_string()),
563                    token_body.redirect_uri.clone(),
564                ));
565            }
566
567            let approved_scopes = get_approved_scopes(
568                &*state.repo,
569                &tenant,
570                &project,
571                UserId::new(code.user_id.clone()),
572                ClientId::new(client_id.clone()),
573            )
574            .await?;
575
576            ProjectAuthAdmin::<CreateAuthorizationCode, _, _, _, _>::delete(
577                &*state.repo,
578                &tenant,
579                &project,
580                &refresh_token,
581            )
582            .await
583            .map_err(|_e| {
584                OIDCError::new(
585                    OIDCErrorCode::ServerError,
586                    Some("Failed to delete used refresh token.".to_string()),
587                    token_body.redirect_uri.clone(),
588                )
589            })?;
590
591            let user =
592                TenantAuthAdmin::<_, User, _, _, _>::read(&*state.repo, &tenant, &code.user_id)
593                    .await
594                    .map_err(|_e| {
595                        OIDCError::new(
596                            OIDCErrorCode::ServerError,
597                            Some("Failed to retrieve user.".to_string()),
598                            token_body.redirect_uri.clone(),
599                        )
600                    })?;
601
602            let response = create_token_response(
603                &user_agent,
604                &*state.repo,
605                &client_app,
606                &token_body.grant_type,
607                TokenResponseArguments {
608                    user_id: code.user_id,
609                    user_kind: AuthorKind::Membership,
610                    user_role: match user.map(|u| u.role) {
611                        Some(RepoUserRole::Admin) => UserRole::Admin,
612                        Some(RepoUserRole::Member) => UserRole::Member,
613                        Some(RepoUserRole::Owner) => UserRole::Owner,
614                        None => UserRole::Member,
615                    },
616                    client_id: client_id.clone(),
617                    scopes: approved_scopes.clone(),
618                    tenant: tenant.clone(),
619                    project: project.clone(),
620                    access_policy_version_ids: match code.membership.as_ref() {
621                        Some(membership) => {
622                            find_users_access_policy_version_ids(
623                                state.search.as_ref(),
624                                &tenant,
625                                &project,
626                                &membership,
627                                &ResourceType::Membership,
628                            )
629                            .await?
630                        }
631                        None => vec![],
632                    },
633                    membership: code.membership,
634                },
635            )
636            .await?;
637
638            Ok(Json(response).into_response())
639        }
640        schemas::token_body::OAuth2TokenBodyGrantType::AuthorizationCode => {
641            let client_id = &token_body.client_id;
642            let code = token_body.code.as_ref().ok_or_else(|| {
643                OIDCError::new(
644                    OIDCErrorCode::InvalidRequest,
645                    Some("code is required for authorization_code grant type.".to_string()),
646                    None,
647                )
648            })?;
649            let code_verifier = token_body.code_verifier.as_ref().ok_or_else(|| {
650                OIDCError::new(
651                    OIDCErrorCode::InvalidRequest,
652                    Some(
653                        "code_verifier is required for authorization_code grant type.".to_string(),
654                    ),
655                    None,
656                )
657            })?;
658            let redirect_uri = token_body.redirect_uri.as_ref().ok_or_else(|| {
659                OIDCError::new(
660                    OIDCErrorCode::InvalidRequest,
661                    Some("redirect_uri is required for authorization_code grant type.".to_string()),
662                    None,
663                )
664            })?;
665
666            let client_app =
667                find_client_app(&state, tenant.clone(), project.clone(), client_id.clone()).await?;
668
669            verify_client(&client_app, &token_body)?;
670
671            let code = code_verification::retrieve_and_verify_code(
672                &*state.repo,
673                &tenant,
674                &project,
675                &client_app,
676                AuthorizationCodeKind::OAuth2CodeGrant,
677                &code,
678                Some(&redirect_uri),
679                Some(&code_verifier),
680            )
681            .await
682            .map_err(|_| {
683                OIDCError::new(
684                    OIDCErrorCode::AccessDenied,
685                    Some("Invalid authorization code.".to_string()),
686                    None,
687                )
688            })?;
689
690            if code.kind != AuthorizationCodeKind::OAuth2CodeGrant {
691                return Err(OIDCError::new(
692                    OIDCErrorCode::InvalidGrant,
693                    Some("Invalid authorization code.".to_string()),
694                    None,
695                ));
696            }
697
698            if code.is_expired.unwrap_or(true) {
699                return Err(OIDCError::new(
700                    OIDCErrorCode::AccessDenied,
701                    Some("Authorization code has expired.".to_string()),
702                    None,
703                ));
704            }
705
706            let approved_scopes = get_approved_scopes(
707                &*state.repo,
708                &tenant,
709                &project,
710                UserId::new(code.user_id.clone()),
711                ClientId::new(client_id.clone()),
712            )
713            .await?;
714
715            // Remove the code once valid.
716            ProjectAuthAdmin::<CreateAuthorizationCode, _, _, _, _>::delete(
717                &*state.repo,
718                &tenant,
719                &project,
720                &code.code,
721            )
722            .await
723            .map_err(|_e| {
724                OIDCError::new(
725                    OIDCErrorCode::ServerError,
726                    Some("Failed to delete used authorization code.".to_string()),
727                    None,
728                )
729            })?;
730
731            let user =
732                TenantAuthAdmin::<_, User, _, _, _>::read(&*state.repo, &tenant, &code.user_id)
733                    .await
734                    .map_err(|_e| {
735                        OIDCError::new(
736                            OIDCErrorCode::ServerError,
737                            Some("Failed to retrieve user.".to_string()),
738                            None,
739                        )
740                    })?;
741
742            let response = create_token_response(
743                &user_agent,
744                &*state.repo,
745                &client_app,
746                &token_body.grant_type,
747                TokenResponseArguments {
748                    user_id: code.user_id,
749                    user_kind: AuthorKind::Membership,
750                    user_role: match user.map(|u| u.role) {
751                        Some(RepoUserRole::Admin) => UserRole::Admin,
752                        Some(RepoUserRole::Member) => UserRole::Member,
753                        Some(RepoUserRole::Owner) => UserRole::Owner,
754                        None => UserRole::Member,
755                    },
756                    client_id: client_id.clone(),
757                    scopes: approved_scopes.clone(),
758                    tenant: tenant.clone(),
759                    project: project.clone(),
760                    access_policy_version_ids: match code.membership.as_ref() {
761                        Some(membership) => {
762                            find_users_access_policy_version_ids(
763                                state.search.as_ref(),
764                                &tenant,
765                                &project,
766                                &membership,
767                                &ResourceType::Membership,
768                            )
769                            .await?
770                        }
771                        None => vec![],
772                    },
773                    membership: code.membership,
774                },
775            )
776            .await?;
777
778            Ok(Json(response).into_response())
779        }
780    }
781}