aboutsummaryrefslogtreecommitdiff
path: root/src/db/models/auth_request.rs
blob: 9388c71a3df560e0c88ea12d13bb65694a017ab3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use crate::crypto::ct_eq;
use chrono::{NaiveDateTime, Utc};

db_object! {
    #[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
    #[diesel(table_name = auth_requests)]
    #[diesel(treat_none_as_null = true)]
    #[diesel(primary_key(uuid))]
    pub struct AuthRequest {
        pub uuid: String,
        pub user_uuid: String,
        pub organization_uuid: Option<String>,

        pub request_device_identifier: String,
        pub device_type: i32,  // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs

        pub request_ip: String,
        pub response_device_id: Option<String>,

        pub access_code: String,
        pub public_key: String,

        pub enc_key: Option<String>,

        pub master_password_hash: Option<String>,
        pub approved: Option<bool>,
        pub creation_date: NaiveDateTime,
        pub response_date: Option<NaiveDateTime>,

        pub authentication_date: Option<NaiveDateTime>,
    }
}

impl AuthRequest {
    pub fn new(
        user_uuid: String,
        request_device_identifier: String,
        device_type: i32,
        request_ip: String,
        access_code: String,
        public_key: String,
    ) -> Self {
        let now = Utc::now().naive_utc();

        Self {
            uuid: crate::util::get_uuid(),
            user_uuid,
            organization_uuid: None,

            request_device_identifier,
            device_type,
            request_ip,
            response_device_id: None,
            access_code,
            public_key,
            enc_key: None,
            master_password_hash: None,
            approved: None,
            creation_date: now,
            response_date: None,
            authentication_date: None,
        }
    }
}

use crate::db::DbConn;

use crate::api::EmptyResult;
use crate::error::MapResult;

impl AuthRequest {
    pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
        db_run! { conn:
            sqlite, mysql {
                match diesel::replace_into(auth_requests::table)
                    .values(AuthRequestDb::to_db(self))
                    .execute(conn)
                {
                    Ok(_) => Ok(()),
                    // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
                    Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
                        diesel::update(auth_requests::table)
                            .filter(auth_requests::uuid.eq(&self.uuid))
                            .set(AuthRequestDb::to_db(self))
                            .execute(conn)
                            .map_res("Error auth_request")
                    }
                    Err(e) => Err(e.into()),
                }.map_res("Error auth_request")
            }
            postgresql {
                let value = AuthRequestDb::to_db(self);
                diesel::insert_into(auth_requests::table)
                    .values(&value)
                    .on_conflict(auth_requests::uuid)
                    .do_update()
                    .set(&value)
                    .execute(conn)
                    .map_res("Error saving auth_request")
            }
        }
    }

    pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> {
        db_run! {conn: {
            auth_requests::table
                .filter(auth_requests::uuid.eq(uuid))
                .first::<AuthRequestDb>(conn)
                .ok()
                .from_db()
        }}
    }

    pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
        db_run! {conn: {
            auth_requests::table
                .filter(auth_requests::user_uuid.eq(user_uuid))
                .load::<AuthRequestDb>(conn).expect("Error loading auth_requests").from_db()
        }}
    }

    pub async fn find_created_before(dt: &NaiveDateTime, conn: &mut DbConn) -> Vec<Self> {
        db_run! {conn: {
            auth_requests::table
                .filter(auth_requests::creation_date.lt(dt))
                .load::<AuthRequestDb>(conn).expect("Error loading auth_requests").from_db()
        }}
    }

    pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
        db_run! { conn: {
            diesel::delete(auth_requests::table.filter(auth_requests::uuid.eq(&self.uuid)))
                .execute(conn)
                .map_res("Error deleting auth request")
        }}
    }

    pub fn check_access_code(&self, access_code: &str) -> bool {
        ct_eq(&self.access_code, access_code)
    }

    pub async fn purge_expired_auth_requests(conn: &mut DbConn) {
        let expiry_time = Utc::now().naive_utc() - chrono::TimeDelta::try_minutes(5).unwrap(); //after 5 minutes, clients reject the request
        for auth_request in Self::find_created_before(&expiry_time, conn).await {
            auth_request.delete(conn).await.ok();
        }
    }
}