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::OAuthTokenBody, 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() != token_request_body.client_id.as_ref() {
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 Some(client_id) = &token_body.client_id else {
428        return Err(OIDCError::new(
429            OIDCErrorCode::InvalidRequest,
430            Some("client_id is required for client_credentials grant type.".to_string()),
431            token_body.redirect_uri.clone(),
432        ));
433    };
434
435    let client_app =
436        find_client_app(state, tenant.clone(), project.clone(), client_id.clone()).await?;
437
438    verify_client(&client_app, &token_body)?;
439
440    // Allow basic auth if client app allows grant.
441    if method == ClientCredentialsMethod::BasicAuth {
442        validate_client_grant_type(&client_app, &ClientapplicationGrantType::Basic_auth(None))?;
443    }
444
445    let client_app_scopes = client_app
446        .scope
447        .as_ref()
448        .and_then(|s| s.value.as_ref().map(String::as_str))
449        .unwrap_or_default();
450
451    let requested_scopes = Scopes::from(
452        token_body
453            .scope
454            .clone()
455            .unwrap_or_else(|| client_app_scopes.to_string()),
456    );
457
458    verify_requested_scope_is_subset(
459        &requested_scopes,
460        &Scopes::try_from(client_app_scopes).map_err(|_| {
461            OIDCError::new(
462                OIDCErrorCode::InvalidScope,
463                Some("Client application's configured scopes are invalid.".to_string()),
464                None,
465            )
466        })?,
467    )?;
468
469    let response = create_token_response(
470        user_agent,
471        &*state.repo,
472        &client_app,
473        &token_body.grant_type,
474        TokenResponseArguments {
475            user_id: client_app.id.clone().unwrap_or_default(),
476            user_role: UserRole::Member,
477            user_kind: AuthorKind::ClientApplication,
478            client_id: client_app.id.clone().unwrap_or_default(),
479            scopes: requested_scopes,
480            tenant: tenant.clone(),
481            project: project.clone(),
482            membership: None,
483            access_policy_version_ids: find_users_access_policy_version_ids(
484                state.search.as_ref(),
485                &tenant,
486                &project,
487                client_id,
488                &ResourceType::ClientApplication,
489            )
490            .await?,
491        },
492    )
493    .await?;
494
495    Ok(response)
496}
497
498pub async fn token<
499    Repo: Repository + Send + Sync,
500    Search: SearchEngine + Send + Sync,
501    Terminology: FHIRTerminology + Send + Sync,
502>(
503    _: TokenPath,
504    user_agent: Option<TypedHeader<UserAgent>>,
505    Cached(TenantIdentifier { tenant }): Cached<TenantIdentifier>,
506    Cached(ProjectIdentifier { project }): Cached<ProjectIdentifier>,
507    State(state): State<Arc<AppState<Repo, Search, Terminology>>>,
508    OAuthTokenBody(token_body): OAuthTokenBody,
509) -> Result<Response, OIDCError> {
510    match &token_body.grant_type {
511        schemas::token_body::OAuth2TokenBodyGrantType::ClientCredentials => {
512            let response = client_credentials_to_token_response(
513                &*state,
514                &tenant,
515                &project,
516                &user_agent,
517                &token_body,
518                ClientCredentialsMethod::Body,
519            )
520            .await?;
521
522            Ok(Json(response).into_response())
523        }
524        schemas::token_body::OAuth2TokenBodyGrantType::RefreshToken => {
525            let Some(client_id) = &token_body.client_id else {
526                return Err(OIDCError::new(
527                    OIDCErrorCode::InvalidRequest,
528                    Some("client_id is required for refresh_token grant type.".to_string()),
529                    token_body.redirect_uri.clone(),
530                ));
531            };
532            let refresh_token = &token_body.refresh_token.as_ref().ok_or_else(|| {
533                OIDCError::new(
534                    OIDCErrorCode::InvalidRequest,
535                    Some("refresh_token is required for refresh_token grant type.".to_string()),
536                    token_body.redirect_uri.clone(),
537                )
538            })?;
539
540            let client_app =
541                find_client_app(&state, tenant.clone(), project.clone(), client_id.clone()).await?;
542
543            verify_client(&client_app, &token_body)?;
544
545            let code = code_verification::retrieve_and_verify_code(
546                &*state.repo,
547                &tenant,
548                &project,
549                &client_app,
550                AuthorizationCodeKind::RefreshToken,
551                &refresh_token,
552                None,
553                None,
554            )
555            .await
556            .map_err(|_e| {
557                OIDCError::new(
558                    OIDCErrorCode::InvalidGrant,
559                    Some("Invalid refresh token.".to_string()),
560                    token_body.redirect_uri.clone(),
561                )
562            })?;
563
564            if code.kind != AuthorizationCodeKind::RefreshToken {
565                return Err(OIDCError::new(
566                    OIDCErrorCode::InvalidGrant,
567                    Some("Invalid refresh token.".to_string()),
568                    token_body.redirect_uri.clone(),
569                ));
570            }
571
572            if code.is_expired.unwrap_or(true) {
573                return Err(OIDCError::new(
574                    OIDCErrorCode::InvalidGrant,
575                    Some("Refresh token has expired.".to_string()),
576                    token_body.redirect_uri.clone(),
577                ));
578            }
579
580            let approved_scopes = get_approved_scopes(
581                &*state.repo,
582                &tenant,
583                &project,
584                UserId::new(code.user_id.clone()),
585                ClientId::new(client_id.clone()),
586            )
587            .await?;
588
589            ProjectAuthAdmin::<CreateAuthorizationCode, _, _, _, _>::delete(
590                &*state.repo,
591                &tenant,
592                &project,
593                &refresh_token,
594            )
595            .await
596            .map_err(|_e| {
597                OIDCError::new(
598                    OIDCErrorCode::ServerError,
599                    Some("Failed to delete used refresh token.".to_string()),
600                    token_body.redirect_uri.clone(),
601                )
602            })?;
603
604            let user =
605                TenantAuthAdmin::<_, User, _, _, _>::read(&*state.repo, &tenant, &code.user_id)
606                    .await
607                    .map_err(|_e| {
608                        OIDCError::new(
609                            OIDCErrorCode::ServerError,
610                            Some("Failed to retrieve user.".to_string()),
611                            token_body.redirect_uri.clone(),
612                        )
613                    })?;
614
615            let response = create_token_response(
616                &user_agent,
617                &*state.repo,
618                &client_app,
619                &token_body.grant_type,
620                TokenResponseArguments {
621                    user_id: code.user_id,
622                    user_kind: AuthorKind::Membership,
623                    user_role: match user.map(|u| u.role) {
624                        Some(RepoUserRole::Admin) => UserRole::Admin,
625                        Some(RepoUserRole::Member) => UserRole::Member,
626                        Some(RepoUserRole::Owner) => UserRole::Owner,
627                        None => UserRole::Member,
628                    },
629                    client_id: client_id.clone(),
630                    scopes: approved_scopes.clone(),
631                    tenant: tenant.clone(),
632                    project: project.clone(),
633                    access_policy_version_ids: match code.membership.as_ref() {
634                        Some(membership) => {
635                            find_users_access_policy_version_ids(
636                                state.search.as_ref(),
637                                &tenant,
638                                &project,
639                                &membership,
640                                &ResourceType::Membership,
641                            )
642                            .await?
643                        }
644                        None => vec![],
645                    },
646                    membership: code.membership,
647                },
648            )
649            .await?;
650
651            Ok(Json(response).into_response())
652        }
653        schemas::token_body::OAuth2TokenBodyGrantType::AuthorizationCode => {
654            let Some(client_id) = &token_body.client_id else {
655                return Err(OIDCError::new(
656                    OIDCErrorCode::InvalidRequest,
657                    Some("client_id is required for authorization_code grant type.".to_string()),
658                    token_body.redirect_uri.clone(),
659                ));
660            };
661
662            let code = token_body.code.as_ref().ok_or_else(|| {
663                OIDCError::new(
664                    OIDCErrorCode::InvalidRequest,
665                    Some("code is required for authorization_code grant type.".to_string()),
666                    None,
667                )
668            })?;
669            let code_verifier = token_body.code_verifier.as_ref().ok_or_else(|| {
670                OIDCError::new(
671                    OIDCErrorCode::InvalidRequest,
672                    Some(
673                        "code_verifier is required for authorization_code grant type.".to_string(),
674                    ),
675                    None,
676                )
677            })?;
678            let redirect_uri = token_body.redirect_uri.as_ref().ok_or_else(|| {
679                OIDCError::new(
680                    OIDCErrorCode::InvalidRequest,
681                    Some("redirect_uri is required for authorization_code grant type.".to_string()),
682                    None,
683                )
684            })?;
685
686            let client_app =
687                find_client_app(&state, tenant.clone(), project.clone(), client_id.clone()).await?;
688
689            verify_client(&client_app, &token_body)?;
690
691            let code = code_verification::retrieve_and_verify_code(
692                &*state.repo,
693                &tenant,
694                &project,
695                &client_app,
696                AuthorizationCodeKind::OAuth2CodeGrant,
697                &code,
698                Some(&redirect_uri),
699                Some(&code_verifier),
700            )
701            .await
702            .map_err(|_| {
703                OIDCError::new(
704                    OIDCErrorCode::AccessDenied,
705                    Some("Invalid authorization code.".to_string()),
706                    None,
707                )
708            })?;
709
710            if code.kind != AuthorizationCodeKind::OAuth2CodeGrant {
711                return Err(OIDCError::new(
712                    OIDCErrorCode::InvalidGrant,
713                    Some("Invalid authorization code.".to_string()),
714                    None,
715                ));
716            }
717
718            if code.is_expired.unwrap_or(true) {
719                return Err(OIDCError::new(
720                    OIDCErrorCode::AccessDenied,
721                    Some("Authorization code has expired.".to_string()),
722                    None,
723                ));
724            }
725
726            let approved_scopes = get_approved_scopes(
727                &*state.repo,
728                &tenant,
729                &project,
730                UserId::new(code.user_id.clone()),
731                ClientId::new(client_id.clone()),
732            )
733            .await?;
734
735            // Remove the code once valid.
736            ProjectAuthAdmin::<CreateAuthorizationCode, _, _, _, _>::delete(
737                &*state.repo,
738                &tenant,
739                &project,
740                &code.code,
741            )
742            .await
743            .map_err(|_e| {
744                OIDCError::new(
745                    OIDCErrorCode::ServerError,
746                    Some("Failed to delete used authorization code.".to_string()),
747                    None,
748                )
749            })?;
750
751            let user =
752                TenantAuthAdmin::<_, User, _, _, _>::read(&*state.repo, &tenant, &code.user_id)
753                    .await
754                    .map_err(|_e| {
755                        OIDCError::new(
756                            OIDCErrorCode::ServerError,
757                            Some("Failed to retrieve user.".to_string()),
758                            None,
759                        )
760                    })?;
761
762            let response = create_token_response(
763                &user_agent,
764                &*state.repo,
765                &client_app,
766                &token_body.grant_type,
767                TokenResponseArguments {
768                    user_id: code.user_id,
769                    user_kind: AuthorKind::Membership,
770                    user_role: match user.map(|u| u.role) {
771                        Some(RepoUserRole::Admin) => UserRole::Admin,
772                        Some(RepoUserRole::Member) => UserRole::Member,
773                        Some(RepoUserRole::Owner) => UserRole::Owner,
774                        None => UserRole::Member,
775                    },
776                    client_id: client_id.clone(),
777                    scopes: approved_scopes.clone(),
778                    tenant: tenant.clone(),
779                    project: project.clone(),
780                    access_policy_version_ids: match code.membership.as_ref() {
781                        Some(membership) => {
782                            find_users_access_policy_version_ids(
783                                state.search.as_ref(),
784                                &tenant,
785                                &project,
786                                &membership,
787                                &ResourceType::Membership,
788                            )
789                            .await?
790                        }
791                        None => vec![],
792                    },
793                    membership: code.membership,
794                },
795            )
796            .await?;
797
798            Ok(Json(response).into_response())
799        }
800    }
801}