lenso-module-auth-password 0.1.19

First-party password auth module for the Lenso backend framework.
Documentation
use crate::config::AuthPasswordConfig;
use crate::module::RESET_PASSWORD_ACTION;
use crate::repositories::PasswordAuthRepository;
use auth::public::AuthUserId;
use platform_core::{AppContext, AppError, AppResult, ErrorCode};
use platform_module::AdminActionSource;
use serde_json::Value;

#[derive(Debug, Clone)]
pub struct AuthPasswordAdminActions {
    ctx: AppContext,
    repository: PasswordAuthRepository,
}

impl AuthPasswordAdminActions {
    #[must_use]
    pub fn new(ctx: AppContext) -> Self {
        Self {
            repository: PasswordAuthRepository::new(ctx.db.clone()),
            ctx,
        }
    }
}

#[async_trait::async_trait]
impl AdminActionSource for AuthPasswordAdminActions {
    async fn invoke(&self, action: &str, input: Value) -> AppResult<Value> {
        match action {
            RESET_PASSWORD_ACTION => {
                let user_id = AuthUserId(required_string(&input, "user_id")?.to_owned());
                let new_password = required_string(&input, "new_password")?;
                let config = AuthPasswordConfig::from_context(&self.ctx)?;
                let updated = self
                    .repository
                    .reset_password(&user_id, new_password, self.ctx.clock.now(), &config)
                    .await?;
                if !updated {
                    return Err(AppError::new(
                        ErrorCode::NotFound,
                        "password credential not found for user",
                    ));
                }
                Ok(serde_json::json!({
                    "reset": true,
                    "user_id": user_id.0,
                }))
            }
            other => Err(AppError::new(
                ErrorCode::NotFound,
                format!("Unknown auth-password admin action `{other}`"),
            )),
        }
    }
}

fn required_string<'a>(input: &'a Value, name: &str) -> AppResult<&'a str> {
    input
        .get(name)
        .and_then(Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .ok_or_else(|| AppError::new(ErrorCode::Validation, format!("{name} is required")))
}