haste_server/auth_n/oidc/
utilities.rs1use haste_fhir_model::r4::generated::{resources::ClientApplication, terminology::IssueType};
2use haste_fhir_operation_error::OperationOutcomeError;
3use haste_jwt::TenantId;
4use haste_repository::{
5 Repository,
6 admin::TenantModelAdmin,
7 types::{
8 authorization_code::{
9 AuthorizationCodeKind, AuthorizationCodeSearchClaims, CreateAuthorizationCode,
10 },
11 user::{CreateUser, UpdateUser},
12 },
13};
14use regex::Regex;
15
16pub fn is_valid_redirect_url(redirect_url: &str, client: &ClientApplication) -> bool {
17 let k = client.redirectUri.as_ref().and_then(|redirect_uris| {
18 redirect_uris.iter().find(|redirect_pattern| {
19 if let Some(redirect_pattern) = redirect_pattern.value.as_ref()
20 && let Ok(pattern) = Regex::new(&format!(
21 "^{}$",
22 regex::escape(redirect_pattern).replace("\\*", "(.+)")
23 ))
24 {
25 pattern.is_match(redirect_url)
26 } else {
27 false
28 }
29 })
30 });
31
32 k.is_some() && !redirect_url.is_empty()
33}
34
35pub fn check_password_strength(
38 password: &str,
39 user_email: &str,
40) -> Result<(), OperationOutcomeError> {
41 let password_strength = zxcvbn::zxcvbn(password, &[user_email]);
42
43 if u8::from(password_strength.score()) < 3 {
44 let feedback = password_strength
45 .feedback()
46 .map(|f| format!("{}", f))
47 .unwrap_or_default();
48
49 return Err(OperationOutcomeError::fatal(
50 IssueType::security(),
51 feedback,
52 ));
53 }
54
55 Ok(())
56}
57
58pub async fn set_user_password<Repo: Repository>(
59 repo: &Repo,
60 tenant: &TenantId,
61 user_email: &str,
62 user_id: &str,
63 password: &str,
64) -> Result<(), OperationOutcomeError> {
65 check_password_strength(password, user_email)?;
66
67 TenantModelAdmin::<CreateUser, _, _, _, String>::update(
68 repo,
69 tenant,
70 UpdateUser {
71 id: user_id.to_string(),
72 password: Some(password.to_string()),
73 email: None,
74 role: None,
75 method: None,
76 provider_id: None,
77 email_verified: None,
78 },
79 )
80 .await?;
81
82 Ok(())
83}
84
85pub async fn revoke_refresh_tokens<Repo: Repository>(
92 repo: &Repo,
93 tenant: &TenantId,
94 user_id: &str,
95) -> Result<(), OperationOutcomeError> {
96 let refresh_tokens = TenantModelAdmin::<CreateAuthorizationCode, _, _, _, _>::search(
97 repo,
98 tenant,
99 &AuthorizationCodeSearchClaims {
100 client_id: None,
101 code: None,
102 kind: Some(AuthorizationCodeKind::RefreshToken),
103 user_id: Some(user_id.to_string()),
104 user_agent: None,
105 is_expired: None,
106 },
107 )
108 .await?;
109
110 for refresh_token in refresh_tokens {
111 TenantModelAdmin::<CreateAuthorizationCode, _, _, _, _>::delete(
112 repo,
113 tenant,
114 &refresh_token.code,
115 )
116 .await?;
117 }
118
119 Ok(())
120}