dipper 0.5.3

An out-of-the-box modular dependency injection web application framework.
Documentation
use serde::{Deserialize, Serialize};

use crate::{
    fnd::authn::{Authn, CaptchaPair, Factor, JwtTokenPair, TotpData, UserHandle, UserIdentifier},
    prelude::*,
};

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "sign_type", content = "sign_data")]
#[allow(clippy::exhaustive_enums)]
pub enum SigninArgs {
    username {
        username: String,
        password: String,
        totp_token: Option<String>,
        captcha: Option<CaptchaPair>,
    },
    email {
        email: String,
        #[serde(flatten)]
        factor: Factor,
    },
    phone {
        phone: String,
        #[serde(flatten)]
        factor: Factor,
    },
}

impl<U> Authn<U>
where
    U: UserHandle,
{
    /// Verify the user's identity and generate the jwt-token pair.
    pub async fn check_sign_in(&self, depot: &mut Depot, args: SigninArgs) -> Result<JwtTokenPair, ApiError> {
        let user_id = match args {
            SigninArgs::username {
                username,
                password,
                totp_token,
                captcha,
            } => {
                if let Some(captcha) = captcha {
                    self.verify_captcha(&captcha).await?;
                }
                let user_ident = UserIdentifier::username(username);
                let user_totp_info = self.user_handle.get_totp_info(depot, &user_ident).await?;
                if let Some(totp_token) = totp_token {
                    let matched = self.verify_totp_token(user_totp_info.totp_secret, TotpData::otp(&totp_token))?;
                    if !matched {
                        return Err(api_err!(
                            ET_USER_VERIF_CODE,
                            "OTP token is not matched.",
                            &ERR_PATH_AUTHN
                        ));
                    }
                } else if user_totp_info.enable_2fa {
                    return Err(api_err!(
                        ET_USER_VERIF_CODE,
                        "Require two-factor verification.",
                        &ERR_PATH_AUTHN
                    ));
                }
                self.verify_user_password(depot, &user_ident, password).await?
            }
            SigninArgs::email { email, factor } => match factor {
                Factor::password { password, captcha } => {
                    if let Some(captcha) = captcha {
                        self.verify_captcha(&captcha).await?;
                    }
                    self.verify_user_password(depot, &UserIdentifier::email(email), password)
                        .await?
                }
                Factor::totp_token { totp_token, captcha } => {
                    if let Some(captcha) = captcha {
                        self.verify_captcha(&captcha).await?;
                    }
                    self.verify_user_totp_token(depot, &UserIdentifier::email(email), TotpData::email(&totp_token))
                        .await?
                }
            },
            SigninArgs::phone { phone, factor } => match factor {
                Factor::password { password, captcha } => {
                    if let Some(captcha) = captcha {
                        self.verify_captcha(&captcha).await?;
                    }
                    self.verify_user_password(depot, &UserIdentifier::phone(phone), password)
                        .await?
                }
                Factor::totp_token { totp_token, captcha } => {
                    if let Some(captcha) = captcha {
                        self.verify_captcha(&captcha).await?;
                    }
                    self.verify_user_totp_token(depot, &UserIdentifier::phone(phone), TotpData::sms(&totp_token))
                        .await?
                }
            },
        };
        self.new_jwt_token_pair(user_id, None).await
    }
}