revolt_database/models/mfa_tickets/
model.rs1use iso8601_timestamp::{Duration, Timestamp};
2use std::ops::Deref;
3
4use nanoid::nanoid;
5use revolt_result::Result;
6
7use crate::{Database, MultiFactorAuthentication};
8
9auto_derived_partial!(
10 pub struct MFATicket {
12 #[serde(rename = "_id")]
14 pub id: String,
15
16 pub account_id: String,
18
19 pub token: String,
21
22 pub validated: bool,
25
26 pub authorised: bool,
29
30 pub last_totp_code: Option<String>,
32 },
33 "PartialMFATicket"
34);
35
36#[derive(Debug, Serialize, Deserialize)]
40pub struct ValidatedTicket(pub MFATicket);
41
42#[derive(Debug, Serialize, Deserialize)]
44pub struct UnvalidatedTicket(pub MFATicket);
45
46impl MFATicket {
47 pub fn new(account_id: String, validated: bool) -> MFATicket {
49 MFATicket {
50 id: ulid::Ulid::new().to_string(),
51 account_id,
52 token: nanoid!(64),
53 validated,
54 authorised: false,
55 last_totp_code: None,
56 }
57 }
58
59 pub async fn populate(&mut self, mfa: &MultiFactorAuthentication) {
61 self.last_totp_code = mfa.totp_token.generate_code().ok();
62 }
63
64 pub async fn save(&self, db: &Database) -> Result<()> {
66 db.save_ticket(self).await
67 }
68
69 pub fn is_expired(&self) -> bool {
71 let now = Timestamp::now_utc();
72
73 let datetime: Timestamp = ulid::Ulid::from_string(&self.id)
74 .expect("Valid `ulid`")
75 .datetime()
76 .into();
77
78 now > (datetime.checked_add(Duration::minutes(5)).unwrap())
79 }
80
81 pub async fn claim(&self, db: &Database) -> Result<()> {
83 if self.is_expired() {
84 return Err(create_error!(InvalidToken));
85 }
86
87 db.delete_ticket(&self.id).await
88 }
89}
90
91impl Deref for ValidatedTicket {
92 type Target = MFATicket;
93
94 fn deref(&self) -> &Self::Target {
95 &self.0
96 }
97}
98
99impl Deref for UnvalidatedTicket {
100 type Target = MFATicket;
101
102 fn deref(&self) -> &Self::Target {
103 &self.0
104 }
105}