better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
use super::{TwoFactorConfig, TwoFactorPlugin, request};
use better_auth_core::{
    AuthContext, AuthError, AuthResponse, AuthResult, AuthRoute, AuthSchema, FieldMap, FieldValue,
    HttpMethod, NativeRequest,
};
use serde_json::Value;

/// Server-only Two Factor endpoints bound to this instance's registered options.
/// These operations have no public HTTP routes.
pub struct TwoFactorApi<'a, S: AuthSchema> {
    plugin: TwoFactorPlugin,
    context: &'a AuthContext<S>,
    source: NativeRequest<'a>,
}
impl<'a, S: AuthSchema> TwoFactorApi<'a, S> {
    /// Bind to the plugin configuration installed on this auth instance.
    pub fn from_context(context: &'a AuthContext<S>) -> AuthResult<Self> {
        let config = context
            .extensions
            .get::<TwoFactorConfig>()
            .ok_or_else(|| AuthError::config("TwoFactorPlugin is not registered"))?;
        Ok(Self {
            plugin: TwoFactorPlugin::with_config(config.clone()),
            context,
            source: NativeRequest::default(),
        })
    }
    /// Preserve an explicit native Request for dynamic runtime resolution.
    pub fn with_request(mut self, source: NativeRequest<'a>) -> Self {
        self.source = source;
        self
    }
    /// Validate a native body and generate a TOTP using the registered digit and period options.
    pub async fn generate_totp(&self, body: Option<Value>) -> AuthResult<String> {
        let route = AuthRoute::server_only(HttpMethod::Post, "generateTOTP")
            .body_validator(|request| request::validate_native(request, "generateTOTP"));
        let response = self
            .context
            .dispatch_native(self.source, route, body, None, |request, _| async move {
                let body = request
                    .validated_body::<request::GenerateTotpRequest>()
                    .ok_or_else(|| AuthError::internal("Missing validated TOTP input"))?;
                AuthResponse::json(
                    None,
                    &serde_json::json!({"code":self.plugin.generate_totp(&body.secret)?}),
                )
            })
            .await?;
        #[derive(serde::Deserialize)]
        struct Result {
            code: String,
        }
        Ok(serde_json::from_slice::<Result>(&response.body.bytes()?)?.code)
    }
    /// Validate and coerce the supplied user ID, then read the stored backup codes.
    pub async fn view_backup_codes(&self, body: Option<Value>) -> AuthResult<FieldValue> {
        let route = AuthRoute::server_only(HttpMethod::Post, "viewBackupCodes")
            .body_validator(|request| request::validate_native(request, "viewBackupCodes"));
        let response = self
            .context
            .dispatch_native(
                self.source,
                route,
                body,
                None,
                |request, context| async move {
                    let body = request
                        .validated_body::<request::ViewBackupCodesRequest>()
                        .ok_or_else(|| {
                            AuthError::internal("Missing validated backup code input")
                        })?;
                    let codes = self
                        .plugin
                        .view_backup_codes(&body.user_id, &context)
                        .await?;
                    Ok(AuthResponse::native(
                        None,
                        FieldMap::from([
                            ("status".into(), true.into()),
                            ("backupCodes".into(), codes),
                        ])
                        .into(),
                    ))
                },
            )
            .await?;
        Ok(response
            .body
            .field_value()?
            .as_object()
            .map(|fields| fields.get("backupCodes"))
            .transpose()?
            .flatten()
            .unwrap_or_default())
    }
}