dipper 0.5.3

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

use crate::{
    fnd::authn::{Authn, CaptchaPair, TotpData, UserHandle, password::Password},
    prelude::*,
};

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(tag = "signup_type", content = "signup_data")]
#[allow(clippy::exhaustive_enums)]
pub enum SignupArgs {
    username {
        username: String,
        password: String,
        captcha: CaptchaPair,
    },
    phone {
        username: String,
        password: String,
        phone: String,
        totp_token: String,
    },
    email {
        username: String,
        password: String,
        email: String,
        totp_token: String,
    },
}

#[derive(Debug, Default, Serialize, Deserialize)]
#[allow(clippy::exhaustive_structs)]
pub struct SignupUserInfo {
    pub username: String,
    pub email: Option<String>,
    pub phone: Option<String>,
}

impl SignupArgs {
    pub fn into_user_info(self) -> SignupUserInfo {
        let mut info = SignupUserInfo::default();
        match self {
            SignupArgs::username { username, .. } => info.username = username,
            SignupArgs::phone { username, phone, .. } => {
                info.username = username;
                info.phone = Some(phone)
            }
            SignupArgs::email { username, email, .. } => {
                info.username = username;
                info.email = Some(email)
            }
        }
        info
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(clippy::exhaustive_structs)]
pub struct SignupReply {
    pub password_hash: String,
    pub otp_secret: String,
    pub otp_qr: String,
}

impl<U> Authn<U>
where
    U: UserHandle,
{
    /// Generate a verification code for verification during the user sign up
    /// process.
    #[inline(always)]
    pub async fn signup_totp_token(&self, args: TotpData<()>) -> anyhow::Result<String> {
        self.obtain_pub_totp_token(args).await
    }

    /// Check the verification code and generate some key information during
    /// registration.
    pub async fn check_sign_up(&self, args: SignupArgs) -> Result<SignupReply, ApiError> {
        match args {
            SignupArgs::username {
                username,
                password,
                captcha,
            } => {
                self.verify_captcha(&captcha).await?;
                self.new_password_and_secret(username, password)
            }
            SignupArgs::phone {
                username,
                password,
                phone: _phone,
                totp_token,
            } => {
                let matched = self.verify_pub_totp_token(TotpData::sms(totp_token.as_str())).await?;
                if !matched {
                    return Err(api_err!(ET_VERIF_CODE, "OTP token is not matched.", &ERR_PATH_AUTHN));
                }
                self.new_password_and_secret(username, password)
            }
            SignupArgs::email {
                username,
                password,
                email: _email,
                totp_token,
            } => {
                let matched = self.verify_pub_totp_token(TotpData::email(totp_token.as_str())).await?;
                if !matched {
                    return Err(api_err!(ET_VERIF_CODE, "OTP token is not matched.", &ERR_PATH_AUTHN));
                }
                self.new_password_and_secret(username, password)
            }
        }
    }

    fn new_password_and_secret(&self, username: String, password: String) -> Result<SignupReply, ApiError> {
        let password_hash = Password(password)
            .password_hash()
            .map_err(|err| api_err!(ET_PWD_VERIF_FAIL, &ERR_PATH_AUTHN).with_source(err.into_error(), true))?;
        let otp_secret = TOTP::gen_secret_base32();
        let totp = TOTP::default_otp();
        let otp_qr = totp
            .qr_base64(otp_secret.clone(), self.app_name().clone(), username)
            .map_err(|_| api_err!(ET_SYS_ERR, "Failed to generate OTP QR code.", &ERR_PATH_AUTHN))?;
        Ok(SignupReply {
            password_hash,
            otp_secret,
            otp_qr,
        })
    }
}