use rand_core::CryptoRng;
use time::OffsetDateTime;
use super::{
Client, LinkKind, LinkToken, Refusal, Transition, UNEXPECTED, complete_sign_in, send_link,
};
use crate::Error;
use crate::account::Account;
use crate::config::Config;
use crate::session::{Session, SignInMethod};
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum EmailLink {
Start,
Requested,
SignedIn {
account: Account,
session: Session,
},
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum EmailLinkEvent {
Request {
account: Option<Account>,
last_sent_at: Option<OffsetDateTime>,
client: Client,
},
Redeem {
link: Option<LinkToken>,
account: Option<Account>,
client: Client,
},
}
impl EmailLink {
#[allow(clippy::too_many_lines)] pub fn advance<R: CryptoRng + ?Sized>(
self,
config: &Config,
event: EmailLinkEvent,
now: OffsetDateTime,
rng: &mut R,
) -> Result<Transition<EmailLink>, Refusal> {
match (self, event) {
(
EmailLink::Start,
EmailLinkEvent::Request {
account,
last_sent_at,
client,
},
) => {
let effects = account
.filter(|account| account.active)
.map(|account| {
send_link(
config,
&account,
LinkKind::SignIn,
last_sent_at,
&client,
now,
rng,
)
})
.unwrap_or_default();
Ok(Transition {
state: EmailLink::Requested,
effects,
})
}
(
EmailLink::Start,
EmailLinkEvent::Redeem {
link,
account,
client,
},
) => {
let valid = |account: &Account| {
account.active
&& link.is_some_and(|link| {
link.is_redeemable(LinkKind::SignIn, now) && link.user_id == account.id
})
};
let Some(account) = account.filter(valid) else {
return Err(Error::SignInFailed.into());
};
let (session, effects) = complete_sign_in(
config,
&account,
&SignInMethod::EmailLink,
&client,
None,
now,
rng,
);
Ok(Transition {
state: EmailLink::SignedIn { account, session },
effects,
})
}
_ => Err(UNEXPECTED.into()),
}
}
}