vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

two_factor.rs (24585B)


      1 use crate::{
      2     api::EmptyResult,
      3     db::{
      4         schema::{totp, webauthn, webauthn_auth, webauthn_reg},
      5         DbConn, FromDb,
      6     },
      7     error::Error,
      8 };
      9 use serde::ser::{Serialize, SerializeStruct, Serializer};
     10 use serde_json::de;
     11 use tokio::task;
     12 use webauthn_rs::prelude::{
     13     CredentialID, SecurityKey, SecurityKeyAuthentication, SecurityKeyRegistration,
     14 };
     15 db_object! {
     16     #[derive(Insertable, Queryable)]
     17     #[diesel(table_name = webauthn)]
     18     pub struct WebAuthn {
     19         credential_id: String,
     20         pub user_uuid: String,
     21         pub id: i64,
     22         pub name: String,
     23         security_key: String,
     24     }
     25     #[derive(Insertable, Queryable)]
     26     #[diesel(table_name = webauthn_auth)]
     27     pub struct WebAuthnAuth {
     28         pub user_uuid: String,
     29         data: String,
     30     }
     31     #[derive(Insertable, Queryable)]
     32     #[diesel(table_name = webauthn_reg)]
     33     pub struct WebAuthnReg {
     34         pub user_uuid: String,
     35         data: String,
     36     }
     37     #[derive(Insertable, Queryable)]
     38     #[diesel(table_name = totp)]
     39     pub struct Totp {
     40         user_uuid: String,
     41         pub token: String,
     42         last_used: i64,
     43     }
     44 }
     45 impl WebAuthn {
     46     pub fn new(
     47         user_uuid: String,
     48         id: i64,
     49         name: String,
     50         security_key: &SecurityKey,
     51     ) -> Result<Self, Error> {
     52         Ok(Self {
     53             credential_id: security_key.cred_id().to_string(),
     54             user_uuid,
     55             id,
     56             name,
     57             security_key: serde_json::to_string(security_key)?,
     58         })
     59     }
     60     pub fn credential_id(&self) -> Result<CredentialID, Error> {
     61         CredentialID::try_from(self.credential_id.as_str())
     62             .map_err(|()| Error::from(String::from("invalid credential ID")))
     63     }
     64     pub fn security_key(&self) -> Result<SecurityKey, Error> {
     65         serde_json::from_str(self.security_key.as_str()).map_err(Error::from)
     66     }
     67     pub fn set_security_key(&mut self, security_key: &SecurityKey) -> Result<(), Error> {
     68         self.security_key = serde_json::to_string(security_key)?;
     69         Ok(())
     70     }
     71 }
     72 impl WebAuthnAuth {
     73     pub fn new(
     74         user_uuid: String,
     75         security_key_auth: &SecurityKeyAuthentication,
     76     ) -> Result<Self, Error> {
     77         Ok(Self {
     78             user_uuid,
     79             data: serde_json::to_string(security_key_auth)?,
     80         })
     81     }
     82     pub fn security_key_auth(&self) -> Result<SecurityKeyAuthentication, Error> {
     83         serde_json::from_str(&self.data).map_err(Error::from)
     84     }
     85 }
     86 impl WebAuthnReg {
     87     pub fn new(
     88         user_uuid: String,
     89         security_key_reg: &SecurityKeyRegistration,
     90     ) -> Result<Self, Error> {
     91         Ok(Self {
     92             user_uuid,
     93             data: serde_json::to_string(security_key_reg)?,
     94         })
     95     }
     96     pub fn security_key_reg(&self) -> Result<SecurityKeyRegistration, Error> {
     97         serde_json::from_str(&self.data).map_err(Error::from)
     98     }
     99 }
    100 #[derive(Queryable)]
    101 pub struct WebAuthnInfo {
    102     id: i64,
    103     name: String,
    104 }
    105 impl Serialize for WebAuthnInfo {
    106     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    107     where
    108         S: Serializer,
    109     {
    110         let mut s = serializer.serialize_struct("WebAuthnInfo", 3)?;
    111         s.serialize_field("id", &self.id)?;
    112         s.serialize_field("name", self.name.as_str())?;
    113         s.serialize_field("migrated", &false)?;
    114         s.end()
    115     }
    116 }
    117 /// Represents a WebAuthn challenge.
    118 pub enum WebAuthnChallenge {
    119     Auth(WebAuthnAuth),
    120     Reg(WebAuthnReg),
    121 }
    122 
    123 #[derive(Clone, Copy)]
    124 pub enum TwoFactorType {
    125     Totp = 0,
    126     WebAuthn = 7,
    127 }
    128 impl From<TwoFactorType> for i32 {
    129     fn from(value: TwoFactorType) -> Self {
    130         match value {
    131             TwoFactorType::Totp => 0i32,
    132             TwoFactorType::WebAuthn => 7i32,
    133         }
    134     }
    135 }
    136 impl TryFrom<i32> for TwoFactorType {
    137     type Error = Error;
    138     fn try_from(value: i32) -> Result<Self, Self::Error> {
    139         match value {
    140             0i32 => Ok(Self::Totp),
    141             7i32 => Ok(Self::WebAuthn),
    142             _ => Err(Error::from(String::from(
    143                 "i32 is not a valid TwoFactorType",
    144             ))),
    145         }
    146     }
    147 }
    148 impl Serialize for TwoFactorType {
    149     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    150     where
    151         S: Serializer,
    152     {
    153         let mut s = serializer.serialize_struct("TwoFactorType", 3)?;
    154         s.serialize_field("enabled", &true)?;
    155         s.serialize_field("type", &i32::from(*self))?;
    156         s.serialize_field("object", "twoFactorProvider")?;
    157         s.end()
    158     }
    159 }
    160 impl Totp {
    161     pub const fn new(user_uuid: String, token: String) -> Self {
    162         Self {
    163             user_uuid,
    164             token,
    165             last_used: 0,
    166         }
    167     }
    168     pub fn get_last_used(&self) -> u64 {
    169         u64::try_from(self.last_used).expect("underflow")
    170     }
    171     pub fn set_last_used(&mut self, last_used: u64) {
    172         self.last_used = i64::try_from(last_used).expect("overflow");
    173     }
    174 }
    175 impl TwoFactorType {
    176     #[allow(clippy::clone_on_ref_ptr, clippy::shadow_unrelated)]
    177     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
    178         use diesel::prelude::{Connection, ExpressionMethods, RunQueryDsl};
    179         use diesel::result;
    180         let mut con_res = conn.conn.clone().lock_owned().await;
    181         let con = con_res.as_mut().expect("unable to get a pooled connection");
    182         task::block_in_place(move || {
    183             con.transaction(|con| {
    184                 diesel::delete(webauthn::table)
    185                     .filter(webauthn::user_uuid.eq(user_uuid))
    186                     .execute(con)
    187                     .and_then(|_| {
    188                         diesel::delete(totp::table)
    189                             .filter(totp::user_uuid.eq(user_uuid))
    190                             .execute(con)
    191                             .map(|_| ())
    192                     })
    193                     .map_err(result::Error::into)
    194             })
    195         })
    196     }
    197     #[allow(clippy::clone_on_ref_ptr)]
    198     pub async fn delete_by_user(self, user_uuid: &str, conn: &DbConn) -> EmptyResult {
    199         use diesel::prelude::{ExpressionMethods, RunQueryDsl};
    200         use diesel::result;
    201         let mut con_res = conn.conn.clone().lock_owned().await;
    202         let con = con_res.as_mut().expect("unable to get a pooled connection");
    203         task::block_in_place(move || {
    204             match self {
    205                 Self::Totp => diesel::delete(totp::table)
    206                     .filter(totp::user_uuid.eq(user_uuid))
    207                     .execute(con),
    208                 Self::WebAuthn => diesel::delete(webauthn::table)
    209                     .filter(webauthn::user_uuid.eq(user_uuid))
    210                     .execute(con),
    211             }
    212             .map(|_| ())
    213             .map_err(result::Error::into)
    214         })
    215     }
    216     #[allow(clippy::clone_on_ref_ptr, clippy::shadow_unrelated)]
    217     pub async fn has_twofactor(user_uuid: &str, conn: &DbConn) -> Result<bool, Error> {
    218         use diesel::prelude::{Connection, ExpressionMethods, QueryDsl, RunQueryDsl};
    219         use diesel::result;
    220         let mut con_res = conn.conn.clone().lock_owned().await;
    221         let con = con_res.as_mut().expect("unable to get a pooled connection");
    222         task::block_in_place(move || {
    223             con.transaction(|con| {
    224                 webauthn::table
    225                     .count()
    226                     .filter(webauthn::user_uuid.eq(user_uuid))
    227                     .get_result::<i64>(con)
    228                     .map_err(result::Error::into)
    229                     .and_then(|count| {
    230                         if count == 0 {
    231                             totp::table
    232                                 .count()
    233                                 .filter(totp::user_uuid.eq(user_uuid))
    234                                 .get_result::<i64>(con)
    235                                 .map_err(result::Error::into)
    236                                 .map(|count| count > 0)
    237                         } else {
    238                             Ok(true)
    239                         }
    240                     })
    241             })
    242         })
    243     }
    244     /// The `bool` represents if WebAuthn is enabled.
    245     /// The `Option` represents if TOTP is enabled; and if so, contains the secret token.
    246     #[allow(clippy::clone_on_ref_ptr, clippy::shadow_unrelated)]
    247     pub async fn get_factors(
    248         user_uuid: &str,
    249         conn: &DbConn,
    250     ) -> Result<(bool, Option<String>), Error> {
    251         use diesel::prelude::{Connection, ExpressionMethods, QueryDsl, RunQueryDsl};
    252         use diesel::result;
    253         use diesel::OptionalExtension;
    254         let mut con_res = conn.conn.clone().lock_owned().await;
    255         let con = con_res.as_mut().expect("unable to get a pooled connection");
    256         task::block_in_place(move || {
    257             con.transaction(|con| {
    258                 webauthn::table
    259                     .count()
    260                     .filter(webauthn::user_uuid.eq(user_uuid))
    261                     .get_result::<i64>(con)
    262                     .and_then(|count| {
    263                         let authn = count > 0;
    264                         totp::table
    265                             .select(totp::token)
    266                             .filter(totp::user_uuid.eq(user_uuid))
    267                             .first(con)
    268                             .optional()
    269                             .map(|token| (authn, token))
    270                     })
    271                     .map_err(result::Error::into)
    272             })
    273         })
    274     }
    275 }
    276 impl WebAuthnInfo {
    277     #[allow(clippy::clone_on_ref_ptr)]
    278     pub async fn get_all_by_user(user_uuid: &str, conn: &DbConn) -> Result<Vec<Self>, Error> {
    279         use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
    280         use diesel::result;
    281         let mut con_res = conn.conn.clone().lock_owned().await;
    282         let con = con_res.as_mut().expect("unable to get a pooled connection");
    283         task::block_in_place(move || {
    284             webauthn::table
    285                 .select((webauthn::id, webauthn::name))
    286                 .filter(webauthn::user_uuid.eq(user_uuid))
    287                 .load::<Self>(con)
    288                 .map_err(result::Error::into)
    289         })
    290     }
    291 }
    292 
    293 impl WebAuthn {
    294     #[allow(clippy::clone_on_ref_ptr)]
    295     pub async fn get_all_security_keys(
    296         user_uuid: &str,
    297         conn: &DbConn,
    298     ) -> Result<Vec<SecurityKey>, Error> {
    299         use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
    300         use diesel::result;
    301         let mut con_res = conn.conn.clone().lock_owned().await;
    302         let con = con_res.as_mut().expect("unable to get a pooled connection");
    303         task::block_in_place(move || {
    304             webauthn::table
    305                 .select(webauthn::security_key)
    306                 .filter(webauthn::user_uuid.eq(user_uuid))
    307                 .load::<String>(con)
    308                 .map_err(result::Error::into)
    309                 .and_then(|keys| {
    310                     let len = keys.len();
    311                     keys.into_iter()
    312                         .try_fold(Vec::with_capacity(len), |mut sec_keys, key| {
    313                             de::from_str::<SecurityKey>(key.as_str()).map(|sec| {
    314                                 sec_keys.push(sec);
    315                                 sec_keys
    316                             })
    317                         })
    318                         .map_err(Error::from)
    319                 })
    320         })
    321     }
    322     #[allow(clippy::clone_on_ref_ptr)]
    323     pub async fn insert(self, conn: &DbConn) -> EmptyResult {
    324         use __sqlite_model::WebAuthnDb;
    325         use diesel::prelude::RunQueryDsl;
    326         use diesel::result;
    327         let mut con_res = conn.conn.clone().lock_owned().await;
    328         let con = con_res.as_mut().expect("unable to get a pooled connection");
    329         task::block_in_place(move || {
    330             diesel::insert_into(webauthn::table)
    331                 .values(WebAuthnDb::to_db(&self))
    332                 .execute(con)
    333                 .map_err(result::Error::into)
    334                 .and_then(|count| {
    335                     if count == 1 {
    336                         Ok(())
    337                     } else {
    338                         Err(Error::from(String::from(
    339                             "exactly one row would not have been inserted into webauthn",
    340                         )))
    341                     }
    342                 })
    343         })
    344     }
    345     #[allow(clippy::clone_on_ref_ptr)]
    346     pub async fn update(self, conn: &DbConn) -> EmptyResult {
    347         use diesel::prelude::{ExpressionMethods, RunQueryDsl};
    348         use diesel::result;
    349         let mut con_res = conn.conn.clone().lock_owned().await;
    350         let con = con_res.as_mut().expect("unable to get a pooled connection");
    351         task::block_in_place(move || {
    352             diesel::update(webauthn::table)
    353                 .set(webauthn::security_key.eq(self.security_key))
    354                 .filter(webauthn::credential_id.eq(self.credential_id))
    355                 .execute(con)
    356                 .map_err(result::Error::into)
    357                 .and_then(|count| {
    358                     if count == 1 {
    359                         Ok(())
    360                     } else {
    361                         Err(Error::from(String::from(
    362                             "exactly one webauthn row would not have been updated",
    363                         )))
    364                     }
    365                 })
    366         })
    367     }
    368     #[allow(clippy::clone_on_ref_ptr)]
    369     pub async fn get_all_credentials_by_user(
    370         user_uuid: &str,
    371         conn: &DbConn,
    372     ) -> Result<Vec<CredentialID>, Error> {
    373         use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
    374         use diesel::result;
    375         let mut con_res = conn.conn.clone().lock_owned().await;
    376         let con = con_res.as_mut().expect("unable to get a pooled connection");
    377         task::block_in_place(move || {
    378             webauthn::table
    379                 .select(webauthn::credential_id)
    380                 .filter(webauthn::user_uuid.eq(user_uuid))
    381                 .load::<String>(con)
    382                 .map_err(result::Error::into)
    383                 .and_then(|ids| {
    384                     let len = ids.len();
    385                     ids.into_iter()
    386                         .try_fold(Vec::with_capacity(len), |mut cred_ids, id| {
    387                             CredentialID::try_from(id.as_str())
    388                                 .map_err(|()| Error::from(String::from("invalid credential ID")))
    389                                 .map(|cred_id| {
    390                                     cred_ids.push(cred_id);
    391                                     cred_ids
    392                                 })
    393                         })
    394                 })
    395         })
    396     }
    397     #[allow(clippy::clone_on_ref_ptr)]
    398     pub async fn get_by_cred_id(credential_id: &str, conn: &DbConn) -> Result<Option<Self>, Error> {
    399         use __sqlite_model::WebAuthnDb;
    400         use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
    401         use diesel::result;
    402         use diesel::OptionalExtension;
    403         let mut con_res = conn.conn.clone().lock_owned().await;
    404         let con = con_res.as_mut().expect("unable to get a pooled connection");
    405         task::block_in_place(move || {
    406             webauthn::table
    407                 .filter(webauthn::credential_id.eq(credential_id))
    408                 .first::<WebAuthnDb>(con)
    409                 .optional()
    410                 .map_err(result::Error::into)
    411                 .map(FromDb::from_db)
    412         })
    413     }
    414     #[allow(clippy::clone_on_ref_ptr)]
    415     pub async fn delete_by_user_uuid_and_id(
    416         user_uuid: &str,
    417         id: i64,
    418         conn: &DbConn,
    419     ) -> EmptyResult {
    420         use diesel::prelude::{ExpressionMethods, RunQueryDsl};
    421         use diesel::result;
    422         let mut con_res = conn.conn.clone().lock_owned().await;
    423         let con = con_res.as_mut().expect("unable to get a pooled connection");
    424         task::block_in_place(move || {
    425             diesel::delete(webauthn::table)
    426                 .filter(webauthn::user_uuid.eq(user_uuid))
    427                 .filter(webauthn::id.eq(id))
    428                 .execute(con)
    429                 .map_err(result::Error::into)
    430                 .and_then(|count| {
    431                     if count == 1 {
    432                         Ok(())
    433                     } else {
    434                         Err(Error::from(String::from(
    435                             "exactly one webauthn row would not have been removed for the user",
    436                         )))
    437                     }
    438                 })
    439         })
    440     }
    441 }
    442 
    443 impl WebAuthnAuth {
    444     #[allow(clippy::clone_on_ref_ptr)]
    445     pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Result<Option<Self>, Error> {
    446         use __sqlite_model::WebAuthnAuthDb;
    447         use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
    448         use diesel::result;
    449         use diesel::OptionalExtension;
    450         let mut con_res = conn.conn.clone().lock_owned().await;
    451         let con = con_res.as_mut().expect("unable to get a pooled connection");
    452         task::block_in_place(move || {
    453             webauthn_auth::table
    454                 .filter(webauthn_auth::user_uuid.eq(user_uuid))
    455                 .first::<WebAuthnAuthDb>(con)
    456                 .optional()
    457                 .map_err(result::Error::into)
    458                 .map(FromDb::from_db)
    459         })
    460     }
    461 }
    462 
    463 impl WebAuthnReg {
    464     #[allow(clippy::clone_on_ref_ptr)]
    465     pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Result<Option<Self>, Error> {
    466         use __sqlite_model::WebAuthnRegDb;
    467         use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
    468         use diesel::result;
    469         use diesel::OptionalExtension;
    470         let mut con_res = conn.conn.clone().lock_owned().await;
    471         let con = con_res.as_mut().expect("unable to get a pooled connection");
    472         task::block_in_place(move || {
    473             webauthn_reg::table
    474                 .filter(webauthn_reg::user_uuid.eq(user_uuid))
    475                 .first::<WebAuthnRegDb>(con)
    476                 .optional()
    477                 .map_err(result::Error::into)
    478                 .map(FromDb::from_db)
    479         })
    480     }
    481 }
    482 
    483 impl WebAuthnChallenge {
    484     #[allow(clippy::clone_on_ref_ptr, clippy::shadow_unrelated)]
    485     pub async fn delete_all(user_uuid: &str, conn: &DbConn) -> EmptyResult {
    486         use diesel::prelude::{Connection, ExpressionMethods, RunQueryDsl};
    487         use diesel::result;
    488         let mut con_res = conn.conn.clone().lock_owned().await;
    489         let con = con_res.as_mut().expect("unable to get a pooled connection");
    490         task::block_in_place(move || {
    491             con.transaction(|con| {
    492                 diesel::delete(webauthn_auth::table)
    493                     .filter(webauthn_auth::user_uuid.eq(user_uuid))
    494                     .execute(con)
    495                     .and_then(|_| {
    496                         diesel::delete(webauthn_reg::table)
    497                             .filter(webauthn_reg::user_uuid.eq(user_uuid))
    498                             .execute(con)
    499                             .map(|_| ())
    500                     })
    501                     .map_err(result::Error::into)
    502             })
    503         })
    504     }
    505     #[allow(clippy::clone_on_ref_ptr)]
    506     pub async fn delete(self, conn: &DbConn) -> EmptyResult {
    507         use diesel::prelude::{ExpressionMethods, RunQueryDsl};
    508         use diesel::result;
    509         let mut con_res = conn.conn.clone().lock_owned().await;
    510         let con = con_res.as_mut().expect("unable to get a pooled connection");
    511         task::block_in_place(move || {
    512             match self {
    513                 Self::Auth(chal) => diesel::delete(webauthn_auth::table)
    514                     .filter(webauthn_auth::user_uuid.eq(chal.user_uuid.as_str()))
    515                     .execute(con),
    516                 Self::Reg(chal) => diesel::delete(webauthn_reg::table)
    517                     .filter(webauthn_reg::user_uuid.eq(chal.user_uuid.as_str()))
    518                     .execute(con),
    519             }
    520             .map_err(result::Error::into)
    521             .and_then(|count| {
    522                 if count == 1 {
    523                     Ok(())
    524                 } else {
    525                     Err(Error::from(String::from(
    526                         "exactly one webauthn challenge would not have been removed",
    527                     )))
    528                 }
    529             })
    530         })
    531     }
    532     #[allow(clippy::clone_on_ref_ptr, clippy::shadow_unrelated)]
    533     pub async fn replace(&self, conn: &DbConn) -> EmptyResult {
    534         use __sqlite_model::{WebAuthnAuthDb, WebAuthnRegDb};
    535         use diesel::prelude::{Connection, ExpressionMethods, RunQueryDsl};
    536         use diesel::result;
    537         let mut con_res = conn.conn.clone().lock_owned().await;
    538         let con = con_res.as_mut().expect("unable to get a pooled connection");
    539         task::block_in_place(move || {
    540             con.transaction(|con| {
    541                 match *self {
    542                     Self::Auth(ref chal) => diesel::update(webauthn_auth::table)
    543                         .set(webauthn_auth::data.eq(&chal.data))
    544                         .filter(webauthn_auth::user_uuid.eq(chal.user_uuid.as_str()))
    545                         .execute(con),
    546                     Self::Reg(ref chal) => diesel::update(webauthn_reg::table)
    547                         .set(webauthn_reg::data.eq(&chal.data))
    548                         .filter(webauthn_reg::user_uuid.eq(chal.user_uuid.as_str()))
    549                         .execute(con),
    550                 }
    551                 .map_err(result::Error::into)
    552                 .and_then(|count| {
    553                     if count == 0 {
    554                         match *self {
    555                             Self::Auth(ref chal) => diesel::insert_into(webauthn_auth::table)
    556                                 .values(WebAuthnAuthDb::to_db(chal))
    557                                 .execute(con),
    558                             Self::Reg(ref chal) => diesel::insert_into(webauthn_reg::table)
    559                                 .values(WebAuthnRegDb::to_db(chal))
    560                                 .execute(con),
    561                         }
    562                         .map_err(result::Error::into)
    563                         .and_then(|count| {
    564                             if count == 1 {
    565                                 Ok(())
    566                             } else {
    567                                 Err(Error::from(String::from(
    568                                     "exactly one webauthn challenge would not have been inserted/updated",
    569                                 )))
    570                             }
    571                         })
    572                     } else {
    573                         Ok(())
    574                     }
    575                 })
    576             })
    577         })
    578     }
    579 }
    580 
    581 impl Totp {
    582     #[allow(clippy::clone_on_ref_ptr, clippy::shadow_unrelated)]
    583     pub async fn replace(self, conn: &DbConn) -> EmptyResult {
    584         use __sqlite_model::TotpDb;
    585         use diesel::prelude::{ExpressionMethods, RunQueryDsl};
    586         use diesel::result;
    587         let mut con_res = conn.conn.clone().lock_owned().await;
    588         let con = con_res.as_mut().expect("unable to get a pooled connection");
    589         task::block_in_place(move || {
    590             diesel::update(totp::table)
    591                 .set(totp::last_used.eq(self.last_used))
    592                 .filter(totp::user_uuid.eq(&self.user_uuid))
    593                 .execute(con)
    594                 .map_err(result::Error::into)
    595                 .and_then(|count| {
    596                     if count == 1 {
    597                         Ok(())
    598                     } else {
    599                         diesel::insert_into(totp::table)
    600                             .values(TotpDb::to_db(&self))
    601                             .execute(con)
    602                             .map_err(result::Error::into)
    603                             .and_then(|count| {
    604                                 if count == 1 {
    605                                     Ok(())
    606                                 } else {
    607                                     Err(Error::from(String::from(
    608                                         "exactly one totp row was not inserted/updated",
    609                                     )))
    610                                 }
    611                             })
    612                     }
    613                 })
    614         })
    615     }
    616     #[allow(clippy::clone_on_ref_ptr)]
    617     pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Result<Option<Self>, Error> {
    618         use __sqlite_model::TotpDb;
    619         use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
    620         use diesel::result;
    621         use diesel::OptionalExtension;
    622         let mut con_res = conn.conn.clone().lock_owned().await;
    623         let con = con_res.as_mut().expect("unable to get a pooled connection");
    624         task::block_in_place(move || {
    625             totp::table
    626                 .filter(totp::user_uuid.eq(user_uuid))
    627                 .first::<TotpDb>(con)
    628                 .optional()
    629                 .map_err(result::Error::into)
    630                 .map(FromDb::from_db)
    631         })
    632     }
    633 }