#[cfg(feature = "diesel")]
pub(in crate::biome) mod diesel;
pub(in crate::biome) mod memory;
use std::str::FromStr;
mod error;
pub use error::CredentialsStoreError;
use bcrypt::{hash, verify, DEFAULT_COST};
#[cfg(feature = "diesel")]
use self::diesel::models::{CredentialsModel, NewCredentialsModel};
use error::{CredentialsBuilderError, CredentialsError};
const MEDIUM_COST: u32 = 8;
const LOW_COST: u32 = 4;
#[derive(Clone, Debug, PartialEq)]
pub struct Credentials {
pub user_id: String,
pub username: String,
pub password: String,
}
impl Credentials {
pub fn verify_password(&self, password: &str) -> Result<bool, CredentialsError> {
Ok(verify(password, &self.password)?)
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct UsernameId {
pub username: String,
pub user_id: String,
}
#[derive(Default)]
pub struct CredentialsBuilder {
user_id: Option<String>,
username: Option<String>,
password: Option<String>,
password_encryption_cost: Option<PasswordEncryptionCost>,
}
impl CredentialsBuilder {
pub fn with_user_id(mut self, user_id: &str) -> CredentialsBuilder {
self.user_id = Some(user_id.to_owned());
self
}
pub fn with_username(mut self, username: &str) -> CredentialsBuilder {
self.username = Some(username.to_owned());
self
}
pub fn with_password(mut self, password: &str) -> CredentialsBuilder {
self.password = Some(password.to_owned());
self
}
pub fn with_password_encryption_cost(
mut self,
cost: PasswordEncryptionCost,
) -> CredentialsBuilder {
self.password_encryption_cost = Some(cost);
self
}
pub fn build(self) -> Result<Credentials, CredentialsBuilderError> {
let user_id = self.user_id.ok_or_else(|| {
CredentialsBuilderError::MissingRequiredField("Missing user_id".to_string())
})?;
let username = self.username.ok_or_else(|| {
CredentialsBuilderError::MissingRequiredField("Missing username".to_string())
})?;
let cost = self
.password_encryption_cost
.unwrap_or(PasswordEncryptionCost::High);
let hashed_password = hash(
self.password.ok_or_else(|| {
CredentialsBuilderError::MissingRequiredField("Missing password".to_string())
})?,
cost.to_value(),
)?;
Ok(Credentials {
user_id,
username,
password: hashed_password,
})
}
}
pub trait CredentialsStore: Send + Sync {
fn add_credentials(&self, credentials: Credentials) -> Result<(), CredentialsStoreError>;
fn update_credentials(
&self,
user_id: &str,
updated_username: &str,
updated_password: &str,
password_encryption_cost: PasswordEncryptionCost,
) -> Result<(), CredentialsStoreError>;
fn remove_credentials(&self, user_id: &str) -> Result<(), CredentialsStoreError>;
fn fetch_credential_by_user_id(
&self,
user_id: &str,
) -> Result<Credentials, CredentialsStoreError>;
fn fetch_credential_by_username(
&self,
username: &str,
) -> Result<Credentials, CredentialsStoreError>;
fn fetch_username_by_id(&self, user_id: &str) -> Result<UsernameId, CredentialsStoreError>;
fn list_usernames(&self) -> Result<Vec<UsernameId>, CredentialsStoreError>;
}
impl<CS> CredentialsStore for Box<CS>
where
CS: CredentialsStore + ?Sized,
{
fn add_credentials(&self, credentials: Credentials) -> Result<(), CredentialsStoreError> {
(**self).add_credentials(credentials)
}
fn update_credentials(
&self,
user_id: &str,
updated_username: &str,
updated_password: &str,
password_encryption_cost: PasswordEncryptionCost,
) -> Result<(), CredentialsStoreError> {
(**self).update_credentials(
user_id,
updated_username,
updated_password,
password_encryption_cost,
)
}
fn remove_credentials(&self, user_id: &str) -> Result<(), CredentialsStoreError> {
(**self).remove_credentials(user_id)
}
fn fetch_credential_by_user_id(
&self,
user_id: &str,
) -> Result<Credentials, CredentialsStoreError> {
(**self).fetch_credential_by_user_id(user_id)
}
fn fetch_credential_by_username(
&self,
username: &str,
) -> Result<Credentials, CredentialsStoreError> {
(**self).fetch_credential_by_username(username)
}
fn fetch_username_by_id(&self, user_id: &str) -> Result<UsernameId, CredentialsStoreError> {
(**self).fetch_username_by_id(user_id)
}
fn list_usernames(&self) -> Result<Vec<UsernameId>, CredentialsStoreError> {
(**self).list_usernames()
}
}
#[cfg(feature = "diesel")]
impl From<Credentials> for NewCredentialsModel {
fn from(creds: Credentials) -> Self {
Self {
user_id: creds.user_id,
username: creds.username,
password: creds.password,
}
}
}
#[derive(Debug, Deserialize, Copy, 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 {
pub(in crate::biome) fn to_value(self) -> u32 {
match self {
PasswordEncryptionCost::High => DEFAULT_COST,
PasswordEncryptionCost::Medium => MEDIUM_COST,
PasswordEncryptionCost::Low => LOW_COST,
}
}
}