use std::collections::HashSet;
use crate::api;
use crate::ApiKey;
use crate::Client;
use crate::DeleteAttribute;
use crate::DisplayName;
use crate::Email;
use crate::Error;
use crate::ExpiresIn;
use crate::IdToken;
use crate::IdpPostBody;
use crate::LanguageCode;
use crate::OAuthRequestUri;
use crate::Password;
use crate::PhotoUrl;
use crate::ProviderId;
use crate::RefreshToken;
use crate::Result;
use crate::UserData;
#[derive(Clone, Debug)]
pub struct Session {
pub(crate) client: Client,
pub(crate) api_key: ApiKey,
pub id_token: IdToken,
pub expires_in: ExpiresIn,
pub refresh_token: RefreshToken,
}
macro_rules! call_refreshing_tokens_return_session_and_value {
($session:expr, $api_call:expr, $retry_count:expr, $($api_call_args:expr), *) => {{
async move {
let mut session = $session;
let mut attempts = 0;
loop {
match $api_call(&session, $($api_call_args), *).await {
Ok(value) => return Ok((session, value)),
Err(error) => match error {
Error::InvalidIdToken if attempts < $retry_count => {
match session.refresh_token().await {
Ok(new_session) => {
session = new_session;
attempts += 1;
},
Err(e) => return Err(e),
}
},
_ => return Err(error),
},
}
}
}
}};
($session:expr, $api_call:expr, $retry_count:expr,) => {{
call_refreshing_tokens_return_session_and_value!($session, $api_call, $retry_count, ())
}};
}
macro_rules! call_refreshing_tokens_without_value_return_session {
($session:expr, $api_call_unit:expr, $retry_count:expr, $($api_call_args:expr), *) => {{
async move {
let mut session = $session;
let mut attempts = 0;
loop {
match $api_call_unit(&session, $($api_call_args), *).await {
Ok(_) => return Ok(session),
Err(error) => match error {
Error::InvalidIdToken if attempts < $retry_count => {
match session.refresh_token().await {
Ok(new_session) => {
session = new_session;
attempts += 1;
},
Err(e) => return Err(e),
}
},
_ => return Err(error),
},
}
}
}
}};
($session:expr, $api_call_unit:expr, $retry_count:expr,) => {{
call_refreshing_tokens_without_value_return_session!($session, $api_call_unit, $retry_count, ())
}};
}
#[allow(unused_macros)]
macro_rules! call_refreshing_tokens_return_session {
($session:expr, $api_call:expr, $retry_count:expr, $($api_call_args:expr),*) => {{
async move {
let mut session = $session;
let mut attempts = 0;
loop {
match $api_call(&session, $($api_call_args),*).await {
Ok(new_session) => return Ok(new_session),
Err(error) => match error {
Error::InvalidIdToken if attempts < $retry_count => {
match session.refresh_token().await {
Ok(new_session) => {
session = new_session;
attempts += 1;
},
Err(e) => return Err(e),
}
},
_ => return Err(error),
},
}
}
}
}};
($session:expr, $api_call:expr, $retry_count:expr) => {{
call_refreshing_tokens_return_session!($session, $api_call, $retry_count, )
}};
}
macro_rules! call_refreshing_tokens_return_nothing {
($session:expr, $api_call:expr, $retry_count:expr, $($api_call_args:expr),*) => {{
async move {
let mut session = $session;
let mut attempts = 0;
loop {
match $api_call(&session, $($api_call_args),*).await {
Ok(_) => return Ok(()),
Err(error) => match error {
Error::InvalidIdToken if attempts < $retry_count => {
match session.refresh_token().await {
Ok(new_session) => {
session = new_session;
attempts += 1;
},
Err(e) => return Err(e),
}
},
_ => return Err(error),
},
}
}
}
}};
($session:expr, $api_call:expr, $retry_count:expr) => {{
call_refreshing_tokens_return_nothing!($session, $api_call, $retry_count, )
}};
}
impl Session {
pub async fn change_email(
self,
new_email: Email,
locale: Option<LanguageCode>,
) -> Result<Session> {
call_refreshing_tokens_without_value_return_session!(
self,
Session::change_email_internal,
1,
new_email.clone(),
locale
)
.await
}
pub async fn change_password(
self,
new_password: Password,
) -> Result<Session> {
call_refreshing_tokens_without_value_return_session!(
self,
Session::change_password_internal,
1,
new_password.clone()
)
.await
}
pub async fn update_profile(
self,
display_name: Option<DisplayName>,
photo_url: Option<PhotoUrl>,
) -> Result<Session> {
call_refreshing_tokens_without_value_return_session!(
self,
Session::update_profile_internal,
1,
display_name.clone(),
photo_url.clone()
)
.await
}
pub async fn delete_profile(
self,
delete_attribute: HashSet<DeleteAttribute>,
) -> Result<Session> {
call_refreshing_tokens_without_value_return_session!(
self,
Session::delete_profile_internal,
1,
delete_attribute.clone()
)
.await
}
pub async fn get_user_data(self) -> Result<(Session, UserData)> {
call_refreshing_tokens_return_session_and_value!(
self,
Session::get_user_data_internal,
1,
)
.await
}
pub async fn link_with_email_password(
self,
email: Email,
password: Password,
) -> Result<Session> {
call_refreshing_tokens_without_value_return_session!(
self,
Session::link_with_email_password_internal,
1,
email.clone(),
password.clone()
)
.await
}
pub async fn link_with_oauth_credential(
self,
request_uri: OAuthRequestUri,
post_body: IdpPostBody,
) -> Result<Session> {
call_refreshing_tokens_without_value_return_session!(
self,
Session::link_with_oauth_credential_internal,
1,
request_uri.clone(),
post_body.clone()
)
.await
}
pub async fn unlink_provider(
self,
delete_provider: HashSet<ProviderId>,
) -> Result<Session> {
call_refreshing_tokens_without_value_return_session!(
self,
Session::unlink_provider_internal,
1,
delete_provider.clone()
)
.await
}
pub async fn send_email_verification(
self,
locale: Option<LanguageCode>,
) -> Result<Session> {
call_refreshing_tokens_without_value_return_session!(
self,
Session::send_email_verification_internal,
1,
locale
)
.await
}
pub async fn delete_account(self) -> Result<()> {
call_refreshing_tokens_return_nothing!(
self,
Session::delete_account_internal,
1,
)
.await
}
pub async fn refresh_token(self) -> Result<Self> {
let request_payload = api::ExchangeRefreshTokenRequestBodyPayload::new(
self.refresh_token
.inner()
.to_string(),
);
let response_payload = api::exchange_refresh_token(
&self.client,
&self.api_key,
request_payload,
)
.await?;
Ok(Self {
client: self.client.clone(),
api_key: self.api_key.clone(),
id_token: IdToken::new(response_payload.id_token),
expires_in: ExpiresIn::parse(response_payload.expires_in)?,
refresh_token: RefreshToken::new(response_payload.refresh_token),
})
}
}
impl Session {
async fn change_email_internal(
&self,
new_email: Email,
locale: Option<LanguageCode>,
) -> Result<()> {
let request_payload = api::ChangeEmailRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
new_email.inner().to_string(),
false,
);
api::change_email(
&self.client,
&self.api_key,
request_payload,
locale,
)
.await?;
Ok(())
}
async fn change_password_internal(
&self,
new_password: Password,
) -> Result<()> {
let request_payload = api::ChangePasswordRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
new_password
.inner()
.to_string(),
false,
);
api::change_password(
&self.client,
&self.api_key,
request_payload,
)
.await?;
Ok(())
}
async fn update_profile_internal(
&self,
display_name: Option<DisplayName>,
photo_url: Option<PhotoUrl>,
) -> Result<()> {
let request_payload = api::UpdateProfileRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
display_name.map(|display_name| {
display_name
.inner()
.to_string()
}),
photo_url.map(|photo_url| photo_url.inner().to_string()),
None,
false,
);
api::update_profile(
&self.client,
&self.api_key,
request_payload,
)
.await?;
Ok(())
}
async fn delete_profile_internal(
&self,
delete_attribute: HashSet<DeleteAttribute>,
) -> Result<()> {
let delete_attribute = delete_attribute
.iter()
.copied()
.collect();
let request_payload = api::UpdateProfileRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
None,
None,
Some(delete_attribute),
false,
);
api::update_profile(
&self.client,
&self.api_key,
request_payload,
)
.await?;
Ok(())
}
async fn get_user_data_internal(&self) -> Result<UserData> {
let request_payload = api::GetUserDataRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
);
let response_payload = api::get_user_data(
&self.client,
&self.api_key,
request_payload,
)
.await?;
let user = response_payload
.users
.first()
.ok_or(Error::NotFoundAnyUserData)?;
Ok(UserData {
local_id: user.local_id.clone(),
email: user.email.clone(),
email_verified: user.email_verified,
display_name: user.display_name.clone(),
photo_url: user.photo_url.clone(),
provider_user_info: user
.provider_user_info
.clone(),
password_hash: user.password_hash.clone(),
password_updated_at: user.password_updated_at,
valid_since: user.valid_since.clone(),
disabled: user.disabled,
last_login_at: user.last_login_at.clone(),
created_at: user.created_at.clone(),
last_refresh_at: user.last_refresh_at.clone(),
custom_auth: user.custom_auth,
})
}
async fn link_with_email_password_internal(
&self,
email: Email,
password: Password,
) -> Result<Self> {
let request_payload = api::LinkWithEmailPasswordRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
email.inner().to_string(),
password.inner().to_string(),
);
let response_payload = api::link_with_email_password(
&self.client,
&self.api_key,
request_payload,
)
.await?;
Ok(Self {
client: self.client.clone(),
api_key: self.api_key.clone(),
id_token: IdToken::new(response_payload.id_token),
expires_in: ExpiresIn::parse(response_payload.expires_in)?,
refresh_token: RefreshToken::new(response_payload.refresh_token),
})
}
async fn link_with_oauth_credential_internal(
&self,
request_uri: OAuthRequestUri,
post_body: IdpPostBody,
) -> Result<Self> {
let request_payload =
api::LinkWithOAuthCredentialRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
request_uri
.inner()
.to_string(),
post_body,
false,
);
let response_payload = api::link_with_oauth_credential(
&self.client,
&self.api_key,
request_payload,
)
.await?;
Ok(Self {
client: self.client.clone(),
api_key: self.api_key.clone(),
id_token: IdToken::new(response_payload.id_token),
expires_in: ExpiresIn::parse(response_payload.expires_in)?,
refresh_token: RefreshToken::new(response_payload.refresh_token),
})
}
async fn unlink_provider_internal(
&self,
delete_provider: HashSet<ProviderId>,
) -> Result<()> {
let request_payload = api::UnlinkProviderRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
delete_provider,
);
api::unlink_provider(
&self.client,
&self.api_key,
request_payload,
)
.await?;
Ok(())
}
async fn send_email_verification_internal(
&self,
locale: Option<LanguageCode>,
) -> Result<()> {
let request_payload = api::SendEmailVerificationRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
);
api::send_email_verification(
&self.client,
&self.api_key,
request_payload,
locale,
)
.await?;
Ok(())
}
async fn delete_account_internal(&self) -> Result<()> {
let request_payload = api::DeleteAccountRequestBodyPayload::new(
self.id_token
.inner()
.to_string(),
);
api::delete_account(
&self.client,
&self.api_key,
request_payload,
)
.await?;
Ok(())
}
}