use async_trait::async_trait; use crank_core::{ AuthenticatedIdentity, IdentityError, IdentityProvider, IdentityProviderKind, LoginOutcome, }; use crank_registry::PostgresRegistry; use tracing::debug; use crate::hashing::verify_password; #[derive(Clone)] pub struct PasswordIdentityProvider { registry: PostgresRegistry, password_pepper: String, } impl PasswordIdentityProvider { pub fn new(registry: PostgresRegistry, password_pepper: impl Into) -> Self { Self { registry, password_pepper: password_pepper.into(), } } } #[async_trait] impl IdentityProvider for PasswordIdentityProvider { fn id(&self) -> &str { "community-password" } fn kind(&self) -> IdentityProviderKind { IdentityProviderKind::Password } async fn login_password( &self, payload: crank_core::LoginPayload, ) -> Result { let user = self .registry .get_auth_user_by_email(&payload.email) .await .map_err(|error| IdentityError::Internal(error.to_string()))? .ok_or(IdentityError::BadCredentials)?; if user.user.status != crank_core::UserStatus::Active { return Err(IdentityError::AccountDisabled); } if !verify_password( &payload.password, &self.password_pepper, &user.password_hash, ) { debug!(email = %payload.email, "password identity provider rejected credentials"); return Err(IdentityError::BadCredentials); } let current_workspace_id = self .registry .list_workspaces_for_user(&user.user.id) .await .map_err(|error| IdentityError::Internal(error.to_string()))? .first() .map(|membership| membership.workspace.id.clone()); Ok(LoginOutcome::Authenticated(AuthenticatedIdentity { user: user.user, memberships: vec![], current_workspace_id, })) } }