Skip to main content

haste_server/auth_n/session/
user.rs

1use haste_fhir_model::r4::generated::terminology::IssueType;
2use haste_fhir_operation_error::OperationOutcomeError;
3use haste_repository::{
4    Repository,
5    admin::TenantModelAdmin,
6    types::{
7        mfa::{UserMFACredentialCreate, UserMFASearchClaims},
8        scope::UserId,
9        user::User,
10    },
11};
12use serde::{Deserialize, Serialize};
13use tower_sessions::Session;
14
15static AUTHORIZATION_STATE_KEY: &str = "user_authorization_state";
16
17#[derive(Deserialize, Serialize)]
18pub struct AuthorizationStateCompleted {
19    pub user: User,
20}
21
22#[derive(Deserialize, Serialize)]
23pub enum SessionAuthorizationState {
24    Complete(AuthorizationStateCompleted),
25    MFARequired { user: User },
26    // [TODO] Enforce automatic MFA enrollment for users who have not yet set it up.
27    // This will likely be a per tenant setting.
28    // MFAEnrollmentRequired { user: User },
29}
30
31pub async fn get_completed_authorization_state(
32    session: &Session,
33) -> Result<AuthorizationStateCompleted, OperationOutcomeError> {
34    let authorization_state = get_authorization_state(session).await?;
35
36    match authorization_state {
37        Some(SessionAuthorizationState::Complete(completed_state)) => Ok(completed_state),
38        _ => Err(OperationOutcomeError::error(
39            IssueType::invalid(),
40            "Authorization state is not complete.".to_string(),
41        )),
42    }
43}
44
45pub async fn get_authorization_state(
46    session: &Session,
47) -> Result<Option<SessionAuthorizationState>, OperationOutcomeError> {
48    let authorization_state = session
49        .get::<SessionAuthorizationState>(AUTHORIZATION_STATE_KEY)
50        .await
51        .map_err(|_e| {
52            OperationOutcomeError::fatal(
53                IssueType::exception(),
54                "Session returned an error when retrieving current user.".to_string(),
55            )
56        })?;
57
58    Ok(authorization_state)
59}
60
61pub async fn set_initial_authorization_state<Repo: Repository>(
62    repo: &Repo,
63    session: &Session,
64    user: User,
65) -> Result<(), OperationOutcomeError> {
66    let active_mfa_credentials = TenantModelAdmin::<UserMFACredentialCreate, _, _, _, _>::search(
67        repo,
68        &user.tenant,
69        &UserMFASearchClaims {
70            tenant: user.tenant.clone(),
71            user_id: UserId::new(user.id.clone()),
72            is_active: Some(true),
73        },
74    )
75    .await?;
76
77    let initial_state = if active_mfa_credentials.is_empty() {
78        // No active MFA credentials so can state that state is completed.
79        SessionAuthorizationState::Complete(AuthorizationStateCompleted { user })
80    } else {
81        SessionAuthorizationState::MFARequired { user }
82    };
83
84    session
85        .insert(AUTHORIZATION_STATE_KEY, initial_state)
86        .await
87        .map_err(|_e| {
88            OperationOutcomeError::fatal(
89                IssueType::exception(),
90                "Failed to set user in session.".to_string(),
91            )
92        })
93}
94
95pub async fn set_completed_authorization_state(
96    session: &Session,
97    user: User,
98) -> Result<(), OperationOutcomeError> {
99    session
100        .insert(
101            AUTHORIZATION_STATE_KEY,
102            SessionAuthorizationState::Complete(AuthorizationStateCompleted { user }),
103        )
104        .await
105        .map_err(|_e| {
106            OperationOutcomeError::fatal(
107                IssueType::exception(),
108                "Failed to set user in session.".to_string(),
109            )
110        })
111}
112
113pub async fn clear_authorization_state(session: &Session) -> Result<(), OperationOutcomeError> {
114    session
115        .remove::<SessionAuthorizationState>(AUTHORIZATION_STATE_KEY)
116        .await
117        .map_err(|_e| {
118            OperationOutcomeError::fatal(
119                IssueType::exception(),
120                "Failed to clear user from session.".to_string(),
121            )
122        })?;
123
124    Ok(())
125}