use async_trait::async_trait;
use reqwest::{Response, StatusCode};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::user_management::{PasswordResetToken, User, UserManagement};
use crate::{ResponseExt, WorkOsError, WorkOsResult};
#[derive(Debug, Serialize)]
pub struct ResetPasswordParams<'a> {
pub token: &'a PasswordResetToken,
pub new_password: &'a str,
}
#[derive(Debug, Deserialize)]
pub struct ResetPasswordResponse {
pub user: User,
}
#[derive(Debug, Error, Deserialize)]
#[serde(tag = "code", rename_all = "snake_case")]
pub enum ResetPasswordError {
#[error("password_reset_token_not_found: {message}")]
PasswordResetTokenNotFound {
message: String,
},
#[error("password_reset_error: {message}")]
PasswordResetError {
message: String,
errors: Vec<PasswordResetError>,
},
}
impl From<ResetPasswordError> for WorkOsError<ResetPasswordError> {
fn from(err: ResetPasswordError) -> Self {
Self::Operation(err)
}
}
#[derive(Debug, Error, Deserialize, Serialize)]
#[serde(tag = "code", rename_all = "snake_case")]
pub enum PasswordResetError {
#[error("password_reset_token_expired: {message}")]
PasswordResetTokenExpired {
message: String,
},
#[error("password_too_weak: {message}")]
PasswordTooWeak {
message: String,
suggestions: Vec<String>,
warning: String,
},
}
#[async_trait]
pub(crate) trait HandleResetPasswordError
where
Self: Sized,
{
async fn handle_reset_password_error(self) -> WorkOsResult<Self, ResetPasswordError>;
}
#[async_trait]
impl HandleResetPasswordError for Response {
async fn handle_reset_password_error(self) -> WorkOsResult<Self, ResetPasswordError> {
match self.error_for_status_ref() {
Ok(_) => Ok(self),
Err(err) => match err.status() {
Some(StatusCode::BAD_REQUEST) | Some(StatusCode::NOT_FOUND) => {
let error = self.json::<ResetPasswordError>().await?;
Err(WorkOsError::Operation(error))
}
_ => Err(WorkOsError::RequestError(err)),
},
}
}
}
#[async_trait]
pub trait ResetPassword {
async fn reset_password(
&self,
params: &ResetPasswordParams<'_>,
) -> WorkOsResult<ResetPasswordResponse, ResetPasswordError>;
}
#[async_trait]
impl ResetPassword for UserManagement<'_> {
async fn reset_password(
&self,
params: &ResetPasswordParams<'_>,
) -> WorkOsResult<ResetPasswordResponse, ResetPasswordError> {
let url = self
.workos
.base_url()
.join("/user_management/password_reset/confirm")?;
let response = self
.workos
.client()
.post(url)
.bearer_auth(self.workos.key())
.json(¶ms)
.send()
.await?
.handle_unauthorized_error()?
.handle_reset_password_error()
.await?
.json::<ResetPasswordResponse>()
.await?;
Ok(response)
}
}
#[cfg(test)]
mod test {
use serde_json::json;
use tokio;
use crate::user_management::UserId;
use crate::{ApiKey, WorkOs};
use super::*;
#[tokio::test]
async fn it_calls_the_reset_password_endpoint() {
let mut server = mockito::Server::new_async().await;
let workos = WorkOs::builder(&ApiKey::from("sk_example_123456789"))
.base_url(&server.url())
.unwrap()
.build();
server
.mock("POST", "/user_management/password_reset/confirm")
.match_header("Authorization", "Bearer sk_example_123456789")
.with_status(201)
.with_body(
json!({
"user": {
"object": "user",
"id": "user_01E4ZCR3C56J083X43JQXF3JK5",
"email": "marcelina.davis@example.com",
"first_name": "Marcelina",
"last_name": "Davis",
"email_verified": true,
"profile_picture_url": "https://workoscdn.com/images/v1/123abc",
"metadata": {},
"created_at": "2021-06-25T19:07:33.155Z",
"updated_at": "2021-06-25T19:07:33.155Z"
}
})
.to_string(),
)
.create_async()
.await;
let response = workos
.user_management()
.reset_password(&ResetPasswordParams {
token: &PasswordResetToken::from("stpIJ48IFJt0HhSIqjf8eppe0"),
new_password: "i8uv6g34kd490s",
})
.await
.unwrap();
assert_eq!(
response.user.id,
UserId::from("user_01E4ZCR3C56J083X43JQXF3JK5")
)
}
}