use crate::email_password::EmailPasswordConfig;
use alibi_core::{
AuthAccount, AuthContext, AuthError, AuthRequest, AuthResult, AuthSchema, AuthUser,
CreateAccount, UpdateAccount,
};
pub async fn set_password<S: AuthSchema>(
request: &AuthRequest,
new_password: &str,
context: &AuthContext<S>,
) -> AuthResult<()> {
let (user, _) = context
.require_authoritative_session(request)
.await
.map_err(crate::helpers::unauthorized_if_unauthenticated)?;
set_password_for_user(&user, new_password, context).await
}
pub(super) async fn set_password_for_user<S: AuthSchema>(
user: &impl AuthUser,
new_password: &str,
context: &AuthContext<S>,
) -> AuthResult<()> {
let policy = context.extensions.get::<EmailPasswordConfig>();
let (minimum, maximum) = crate::email_password::password_length_limits(context);
let length = new_password.encode_utf16().count();
if length < minimum {
return Err(AuthError::Upstream {
status: 400,
code: "PASSWORD_TOO_SHORT",
message: "Password too short",
});
}
if length > maximum {
return Err(AuthError::Upstream {
status: 400,
code: "PASSWORD_TOO_LONG",
message: "Password too long",
});
}
let user_id = user.id();
let credential = context
.database
.get_user_accounts(user_id.as_ref())
.await?
.into_iter()
.find(|account| account.provider_id() == "credential" && account.account_id() == user_id);
let hasher = policy
.as_ref()
.and_then(|policy| policy.password_hasher.as_ref());
let hash_context = alibi_core::PasswordHashContext {
path: Some("virtual:".into()),
request: alibi_core::hooks::current_request_hook_context(),
};
let password = context
.hash_password_with_context(hasher, new_password, Some(&hash_context))
.await?;
if let Some(account) = credential {
if account
.password()
.is_some_and(|password| !password.is_empty())
{
return Err(AuthError::Upstream {
status: 400,
code: "PASSWORD_ALREADY_SET",
message: "User already has a password set",
});
}
_ = context
.database
.update_account_record(
account.id().as_ref(),
UpdateAccount {
password: Some(password),
..Default::default()
},
)
.await?;
} else {
_ = context
.database
.create_account_record(CreateAccount {
additional_fields: alibi_core::field_policy::FieldValues::default(),
user_id: user.id().into_owned(),
account_id: user.id().into_owned(),
provider_id: "credential".to_owned(),
access_token: None,
refresh_token: None,
id_token: None,
access_token_expires_at: None,
refresh_token_expires_at: None,
scope: None,
password: Some(password),
})
.await?;
}
Ok(())
}