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; pub static REFRESH_TOKEN_EXPIRATION: usize = 43200; #[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 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 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 && *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 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 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 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}