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