db/models/
password_reset_token.rs

1use sea_orm::entity::prelude::*;
2use sea_orm::ActiveValue::{Set, NotSet};
3use sea_orm::IntoActiveModel;
4use serde::{Deserialize, Serialize};
5use chrono::{DateTime, Utc, Duration};
6use rand::{thread_rng, Rng};
7use rand::distributions::Alphanumeric;
8
9#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
10#[sea_orm(table_name = "password_reset_tokens")]
11pub struct Model {
12    #[sea_orm(primary_key)]
13    pub id: i64,
14    pub user_id: i64,
15    pub token: String,
16    pub expires_at: DateTime<Utc>,
17    pub used: bool,
18    pub created_at: DateTime<Utc>,
19}
20
21#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
22pub enum Relation {
23    #[sea_orm(
24        belongs_to = "super::user::Entity",
25        from = "Column::UserId",
26        to = "super::user::Column::Id",
27        on_update = "NoAction",
28        on_delete = "Cascade"
29    )]
30    User,
31}
32
33impl Related<super::user::Entity> for Entity {
34    fn to() -> RelationDef {
35        Relation::User.def()
36    }
37}
38
39impl ActiveModelBehavior for ActiveModel {}
40
41impl Model {
42    pub fn new(user_id: i64, expiry_minutes: i64) -> Self {
43        let token = thread_rng()
44            .sample_iter(&Alphanumeric)
45            .take(32)
46            .map(char::from)
47            .collect::<String>();
48
49        Self {
50            id: 0,
51            user_id,
52            token,
53            expires_at: Utc::now() + Duration::minutes(expiry_minutes),
54            used: false,
55            created_at: Utc::now(),
56        }
57    }
58
59    pub async fn create(
60        db: &DatabaseConnection,
61        user_id: i64,
62        expiry_minutes: i64,
63    ) -> Result<Self, DbErr> {
64        let model = Self::new(user_id, expiry_minutes);
65        let mut active_model = model.into_active_model();
66        active_model.id = NotSet;
67        active_model.insert(db).await
68    }
69
70    pub async fn find_valid_token(
71        db: &DatabaseConnection,
72        token: &str,
73    ) -> Result<Option<Self>, DbErr> {
74        Entity::find()
75            .filter(Column::Token.eq(token))
76            .filter(Column::Used.eq(false))
77            .filter(Column::ExpiresAt.gt(Utc::now()))
78            .one(db)
79            .await
80    }
81
82    pub async fn mark_as_used(&self, db: &DatabaseConnection) -> Result<(), DbErr> {
83        let mut active_model: ActiveModel = self.clone().into();
84        active_model.used = Set(true);
85        active_model.update(db).await?;
86        Ok(())
87    }
88}