use rand_core::CryptoRng;
use time::OffsetDateTime;
use super::{Client, Effect, LinkKind, Refusal, Transition, UNEXPECTED, audit, issue_link};
use crate::Error;
use crate::account::{Account, Factors, Lockout, Role};
use crate::audit::AuditKind;
use crate::config::Config;
use crate::mail::MailKind;
use crate::session::{Access, Session};
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Reset {
Start,
Done {
account: Account,
},
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ResetEvent {
Reset {
admin: Account,
session: Session,
member: Account,
remove_password: bool,
client: Client,
},
}
impl Reset {
pub fn advance<R: CryptoRng + ?Sized>(
self,
config: &Config,
event: ResetEvent,
now: OffsetDateTime,
rng: &mut R,
) -> Result<Transition<Reset>, Refusal> {
let (
Reset::Start,
ResetEvent::Reset {
admin,
session,
mut member,
remove_password,
client,
},
) = (self, event)
else {
return Err(UNEXPECTED.into());
};
if session.user_id != admin.id {
return Err(Error::Unauthorized.into());
}
session.authorize(admin.active, Access::Recent, config.step_up_window(), now)?;
if admin.role != Role::Owner || member.role == Role::Owner || member.id == admin.id {
return Err(Error::Forbidden.into());
}
let user_id = member.id;
let keep_password = member.factors.password && !remove_password;
member.active = false;
member.factors = Factors::new(keep_password, false, 0);
member.lockout = Lockout::default();
let issued = issue_link(config, &member, LinkKind::Invite, now, rng);
let mut effects = vec![
Effect::RevokeSessions { user_id },
Effect::RemoveFactors {
user_id,
password: remove_password,
},
Effect::DeactivateAccount { user_id },
Effect::ClearFailures { user_id },
Effect::DeletePendingLinks { user_id },
];
let reset = audit(AuditKind::SignInReset, Some(user_id), &client, now)
.with_detail(format!("by:{}", admin.id));
effects.extend(super::mail_link(
&member,
issued,
MailKind::SignInReset,
reset,
));
Ok(Transition {
state: Reset::Done { account: member },
effects,
})
}
}