#[cfg(feature = "diesel")]
pub(in crate::biome) mod diesel;
use std::str::FromStr;
mod error;
pub use error::CredentialsStoreError;
use bcrypt::{hash, verify, DEFAULT_COST};
use self::diesel::models::{NewUserCredentialsModel, UserCredentialsModel};
use error::{UserCredentialsBuilderError, UserCredentialsError};
const MEDIUM_COST: u32 = 8;
const LOW_COST: u32 = 4;
pub struct UserCredentials {
pub user_id: String,
pub username: String,
pub password: String,
}
impl UserCredentials {
pub fn verify_password(&self, password: &str) -> Result<bool, UserCredentialsError> {
Ok(verify(password, &self.password)?)
}
}
#[derive(Deserialize, Serialize)]
pub struct UsernameId {
username: String,
user_id: String,
}
#[derive(Default)]
pub struct UserCredentialsBuilder {
user_id: Option<String>,
username: Option<String>,
password: Option<String>,
password_encryption_cost: Option<PasswordEncryptionCost>,
}
impl UserCredentialsBuilder {
pub fn with_user_id(mut self, user_id: &str) -> UserCredentialsBuilder {
self.user_id = Some(user_id.to_owned());
self
}
pub fn with_username(mut self, username: &str) -> UserCredentialsBuilder {
self.username = Some(username.to_owned());
self
}
pub fn with_password(mut self, password: &str) -> UserCredentialsBuilder {
self.password = Some(password.to_owned());
self
}
pub fn with_password_encryption_cost(
mut self,
cost: PasswordEncryptionCost,
) -> UserCredentialsBuilder {
self.password_encryption_cost = Some(cost);
self
}
pub fn build(self) -> Result<UserCredentials, UserCredentialsBuilderError> {
let user_id = self.user_id.ok_or_else(|| {
UserCredentialsBuilderError::MissingRequiredField("Missing user_id".to_string())
})?;
let username = self.username.ok_or_else(|| {
UserCredentialsBuilderError::MissingRequiredField("Missing user_id".to_string())
})?;
let cost = self
.password_encryption_cost
.unwrap_or(PasswordEncryptionCost::High);
let hashed_password = hash(
self.password.ok_or_else(|| {
UserCredentialsBuilderError::MissingRequiredField("Missing password".to_string())
})?,
cost.to_value(),
)?;
Ok(UserCredentials {
user_id,
username,
password: hashed_password,
})
}
}
pub trait CredentialsStore<T> {
fn add_credentials(&self, credentials: T) -> Result<(), CredentialsStoreError>;
fn update_credentials(
&self,
user_id: &str,
updated_username: &str,
updated_password: &str,
) -> Result<(), CredentialsStoreError>;
fn remove_credentials(&self, user_id: &str) -> Result<(), CredentialsStoreError>;
fn fetch_credential_by_user_id(&self, user_id: &str) -> Result<T, CredentialsStoreError>;
fn fetch_credential_by_username(&self, username: &str) -> Result<T, CredentialsStoreError>;
fn fetch_username_by_id(&self, user_id: &str) -> Result<UsernameId, CredentialsStoreError>;
fn get_usernames(&self) -> Result<Vec<UsernameId>, CredentialsStoreError>;
}
impl Into<NewUserCredentialsModel> for UserCredentials {
fn into(self) -> NewUserCredentialsModel {
NewUserCredentialsModel {
user_id: self.user_id,
username: self.username,
password: self.password,
}
}
}
#[derive(Debug, Deserialize, Clone)]
pub enum PasswordEncryptionCost {
High,
Medium,
Low,
}
impl FromStr for PasswordEncryptionCost {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_ref() {
"high" => Ok(PasswordEncryptionCost::High),
"medium" => Ok(PasswordEncryptionCost::Medium),
"low" => Ok(PasswordEncryptionCost::Low),
_ => Err(format!(
"Invalid cost value {}, must be high, medium or low",
s
)),
}
}
}
impl PasswordEncryptionCost {
fn to_value(&self) -> u32 {
match self {
PasswordEncryptionCost::High => DEFAULT_COST,
PasswordEncryptionCost::Medium => MEDIUM_COST,
PasswordEncryptionCost::Low => LOW_COST,
}
}
}