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 (27275B)


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