use std::sync::Mutex;
use serde::Serialize;
use crate::auth::NoAuth;
use crate::client::{Client, ClientBuilder};
use crate::config::Config;
use crate::error::Error;
use crate::generated::routes;
use crate::generated::types::{
CreateSessionRequestContent, PendingAuthentication, SessionAuthorization,
};
use crate::http::header::COOKIE;
use crate::types::SensitiveString;
pub const PENDING_COOKIE: &str = "pending_authentication_token";
pub struct MagicLinkFlow {
client: Client,
pending: Mutex<Option<SensitiveString>>,
}
#[derive(Serialize)]
struct Redeem<'a> {
code: &'a str,
token: &'a str,
}
impl MagicLinkFlow {
pub fn new(config: Config) -> Result<MagicLinkFlow, Error> {
MagicLinkFlow::from_builder(Client::builder(config))
}
pub fn from_builder(builder: ClientBuilder) -> Result<MagicLinkFlow, Error> {
Ok(MagicLinkFlow {
client: builder.auth_strategy(NoAuth).build()?,
pending: Mutex::new(None),
})
}
pub fn resume(
config: Config,
pending_token: impl Into<SensitiveString>,
) -> Result<MagicLinkFlow, Error> {
let flow = MagicLinkFlow::new(config)?;
*flow
.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(pending_token.into());
Ok(flow)
}
pub fn client(&self) -> &Client {
&self.client
}
pub async fn create_session(
&self,
email_address: &str,
) -> Result<PendingAuthentication, Error> {
let mut operation = self.client.operation(&routes::CREATE_SESSION, &[])?;
operation.operation_name("MagicLinkCreateSession");
operation.json(&CreateSessionRequestContent {
email_address: SensitiveString::new(email_address),
})?;
let pending: PendingAuthentication = self.client.send(operation).await?;
*self
.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) =
Some(pending.pending_authentication_token.clone());
Ok(pending)
}
pub fn pending_token(&self) -> Option<SensitiveString> {
self.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
pub async fn redeem(&self, code: &str) -> Result<SessionAuthorization, Error> {
let pending = self.pending_token().ok_or_else(|| {
Error::usage_with_hint(
"no sign-in is pending",
"call create_session first, or resume with the pending token",
)
})?;
let cookie = crate::auth::cookie_header(PENDING_COOKIE, pending.expose())?;
let mut operation = self.client.operation(&routes::REDEEM_MAGIC_LINK, &[])?;
operation.operation_name("MagicLinkRedeem");
operation.header(COOKIE, cookie);
operation.json(&Redeem { code, token: code })?;
let session: SessionAuthorization = self.client.send(operation).await?;
*self
.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
Ok(session)
}
}