use iso8601_timestamp::{Duration, Timestamp};
use std::ops::Deref;
use nanoid::nanoid;
use revolt_result::Result;
use crate::{Database, MultiFactorAuthentication};
auto_derived_partial!(
pub struct MFATicket {
#[serde(rename = "_id")]
pub id: String,
pub account_id: String,
pub token: String,
pub validated: bool,
pub authorised: bool,
pub last_totp_code: Option<String>,
},
"PartialMFATicket"
);
#[derive(Debug, Serialize, Deserialize)]
pub struct ValidatedTicket(pub MFATicket);
#[derive(Debug, Serialize, Deserialize)]
pub struct UnvalidatedTicket(pub MFATicket);
impl MFATicket {
pub fn new(account_id: String, validated: bool) -> MFATicket {
MFATicket {
id: ulid::Ulid::new().to_string(),
account_id,
token: nanoid!(64),
validated,
authorised: false,
last_totp_code: None,
}
}
pub async fn populate(&mut self, mfa: &MultiFactorAuthentication) {
self.last_totp_code = mfa.totp_token.generate_code().ok();
}
pub async fn save(&self, db: &Database) -> Result<()> {
db.save_ticket(self).await
}
pub fn is_expired(&self) -> bool {
let now = Timestamp::now_utc();
let datetime: Timestamp = ulid::Ulid::from_string(&self.id)
.expect("Valid `ulid`")
.datetime()
.into();
now > (datetime.checked_add(Duration::minutes(5)).unwrap())
}
pub async fn claim(&self, db: &Database) -> Result<()> {
if self.is_expired() {
return Err(create_error!(InvalidToken));
}
db.delete_ticket(&self.id).await
}
}
impl Deref for ValidatedTicket {
type Target = MFATicket;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Deref for UnvalidatedTicket {
type Target = MFATicket;
fn deref(&self) -> &Self::Target {
&self.0
}
}