pub(in crate::biome) mod models;
mod operations;
pub(in crate::biome) mod schema;
use std::sync::{Arc, RwLock};
use diesel::r2d2::{ConnectionManager, Pool};
use crate::store::pool::ConnectionPool;
use super::{
Credentials, CredentialsStore, CredentialsStoreError, PasswordEncryptionCost, UsernameId,
};
use models::CredentialsModel;
use operations::add_credentials::CredentialsStoreAddCredentialsOperation as _;
use operations::fetch_credential_by_id::CredentialsStoreFetchCredentialByIdOperation as _;
use operations::fetch_credential_by_username::CredentialsStoreFetchCredentialByUsernameOperation as _;
use operations::fetch_username::CredentialsStoreFetchUsernameOperation as _;
use operations::list_usernames::CredentialsStoreListUsernamesOperation as _;
use operations::remove_credentials::CredentialsStoreRemoveCredentialsOperation as _;
use operations::update_credentials::CredentialsStoreUpdateCredentialsOperation as _;
use operations::CredentialsStoreOperations;
pub struct DieselCredentialsStore<C: diesel::Connection + 'static> {
connection_pool: ConnectionPool<C>,
}
impl<C: diesel::Connection> DieselCredentialsStore<C> {
pub fn new(connection_pool: Pool<ConnectionManager<C>>) -> Self {
DieselCredentialsStore {
connection_pool: connection_pool.into(),
}
}
pub fn new_with_write_exclusivity(
connection_pool: Arc<RwLock<Pool<ConnectionManager<C>>>>,
) -> Self {
DieselCredentialsStore {
connection_pool: connection_pool.into(),
}
}
}
#[cfg(feature = "postgres")]
impl CredentialsStore for DieselCredentialsStore<diesel::pg::PgConnection> {
fn add_credentials(&self, credentials: Credentials) -> Result<(), CredentialsStoreError> {
self.connection_pool.execute_write(|conn| {
CredentialsStoreOperations::new(conn).add_credentials(credentials)
})
}
fn update_credentials(
&self,
user_id: &str,
username: &str,
password: &str,
password_encryption_cost: PasswordEncryptionCost,
) -> Result<(), CredentialsStoreError> {
self.connection_pool.execute_write(|conn| {
CredentialsStoreOperations::new(conn).update_credentials(
user_id,
username,
password,
password_encryption_cost,
)
})
}
fn remove_credentials(&self, user_id: &str) -> Result<(), CredentialsStoreError> {
self.connection_pool
.execute_write(|conn| CredentialsStoreOperations::new(conn).remove_credentials(user_id))
}
fn fetch_credential_by_user_id(
&self,
user_id: &str,
) -> Result<Credentials, CredentialsStoreError> {
self.connection_pool.execute_read(|conn| {
CredentialsStoreOperations::new(conn).fetch_credential_by_id(user_id)
})
}
fn fetch_credential_by_username(
&self,
username: &str,
) -> Result<Credentials, CredentialsStoreError> {
self.connection_pool.execute_read(|conn| {
CredentialsStoreOperations::new(conn).fetch_credential_by_username(username)
})
}
fn fetch_username_by_id(&self, user_id: &str) -> Result<UsernameId, CredentialsStoreError> {
self.connection_pool.execute_read(|conn| {
CredentialsStoreOperations::new(conn).fetch_username_by_id(user_id)
})
}
fn list_usernames(&self) -> Result<Vec<UsernameId>, CredentialsStoreError> {
self.connection_pool
.execute_read(|conn| CredentialsStoreOperations::new(conn).list_usernames())
}
}
#[cfg(feature = "sqlite")]
impl CredentialsStore for DieselCredentialsStore<diesel::sqlite::SqliteConnection> {
fn add_credentials(&self, credentials: Credentials) -> Result<(), CredentialsStoreError> {
self.connection_pool.execute_write(|conn| {
CredentialsStoreOperations::new(conn).add_credentials(credentials)
})
}
fn update_credentials(
&self,
user_id: &str,
username: &str,
password: &str,
password_encryption_cost: PasswordEncryptionCost,
) -> Result<(), CredentialsStoreError> {
self.connection_pool.execute_write(|conn| {
CredentialsStoreOperations::new(conn).update_credentials(
user_id,
username,
password,
password_encryption_cost,
)
})
}
fn remove_credentials(&self, user_id: &str) -> Result<(), CredentialsStoreError> {
self.connection_pool
.execute_write(|conn| CredentialsStoreOperations::new(conn).remove_credentials(user_id))
}
fn fetch_credential_by_user_id(
&self,
user_id: &str,
) -> Result<Credentials, CredentialsStoreError> {
self.connection_pool.execute_read(|conn| {
CredentialsStoreOperations::new(conn).fetch_credential_by_id(user_id)
})
}
fn fetch_credential_by_username(
&self,
username: &str,
) -> Result<Credentials, CredentialsStoreError> {
self.connection_pool.execute_read(|conn| {
CredentialsStoreOperations::new(conn).fetch_credential_by_username(username)
})
}
fn fetch_username_by_id(&self, user_id: &str) -> Result<UsernameId, CredentialsStoreError> {
self.connection_pool.execute_read(|conn| {
CredentialsStoreOperations::new(conn).fetch_username_by_id(user_id)
})
}
fn list_usernames(&self) -> Result<Vec<UsernameId>, CredentialsStoreError> {
self.connection_pool
.execute_read(|conn| CredentialsStoreOperations::new(conn).list_usernames())
}
}
impl From<CredentialsModel> for UsernameId {
fn from(user_credentials: CredentialsModel) -> Self {
Self {
user_id: user_credentials.user_id,
username: user_credentials.username,
}
}
}
impl From<CredentialsModel> for Credentials {
fn from(user_credentials: CredentialsModel) -> Self {
Self {
user_id: user_credentials.user_id,
username: user_credentials.username,
password: user_credentials.password,
}
}
}
#[cfg(all(test, feature = "sqlite"))]
pub mod tests {
use super::*;
use crate::biome::credentials::store::CredentialsBuilder;
use crate::migrations::run_sqlite_migrations;
use diesel::{
r2d2::{ConnectionManager, Pool},
sqlite::SqliteConnection,
};
#[test]
fn sqlite_fetch_credential_by_user_id() {
let pool = create_connection_pool_and_migrate();
let store = DieselCredentialsStore::new(pool);
let cred1 = CredentialsBuilder::default()
.with_user_id("id1")
.with_username("user1")
.with_password("pwd1")
.with_password_encryption_cost(PasswordEncryptionCost::Low)
.build()
.expect("Failed to build cred1");
store
.add_credentials(cred1.clone())
.expect("Failed to add cred1");
let cred2 = CredentialsBuilder::default()
.with_user_id("id2")
.with_username("user2")
.with_password("pwd2")
.with_password_encryption_cost(PasswordEncryptionCost::Medium)
.build()
.expect("Failed to build cred2");
store
.add_credentials(cred2.clone())
.expect("Failed to add cred2");
let cred3 = CredentialsBuilder::default()
.with_user_id("id3")
.with_username("user3")
.with_password("pwd3")
.with_password_encryption_cost(PasswordEncryptionCost::High)
.build()
.expect("Failed to build cred3");
store
.add_credentials(cred3.clone())
.expect("Failed to add cred3");
assert_eq!(
store
.fetch_credential_by_user_id("id1")
.expect("Failed to fetch cred1"),
cred1,
);
assert_eq!(
store
.fetch_credential_by_user_id("id2")
.expect("Failed to fetch cred2"),
cred2,
);
assert_eq!(
store
.fetch_credential_by_user_id("id3")
.expect("Failed to fetch cred3"),
cred3,
);
match store.fetch_credential_by_user_id("cred4") {
Err(CredentialsStoreError::NotFoundError(_)) => {}
res => panic!(
"Expected Err(CredentialsStoreError::NotFoundError), got {:?} instead",
res
),
}
}
#[test]
fn sqlite_fetch_credential_by_username() {
let pool = create_connection_pool_and_migrate();
let store = DieselCredentialsStore::new(pool);
let cred1 = CredentialsBuilder::default()
.with_user_id("id1")
.with_username("user1")
.with_password("pwd1")
.with_password_encryption_cost(PasswordEncryptionCost::Low)
.build()
.expect("Failed to build cred1");
store
.add_credentials(cred1.clone())
.expect("Failed to add cred1");
let cred2 = CredentialsBuilder::default()
.with_user_id("id2")
.with_username("user2")
.with_password("pwd2")
.with_password_encryption_cost(PasswordEncryptionCost::Medium)
.build()
.expect("Failed to build cred2");
store
.add_credentials(cred2.clone())
.expect("Failed to add cred2");
let cred3 = CredentialsBuilder::default()
.with_user_id("id3")
.with_username("user3")
.with_password("pwd3")
.with_password_encryption_cost(PasswordEncryptionCost::High)
.build()
.expect("Failed to build cred3");
store
.add_credentials(cred3.clone())
.expect("Failed to add cred3");
assert_eq!(
store
.fetch_credential_by_username("user1")
.expect("Failed to fetch cred1"),
cred1,
);
assert_eq!(
store
.fetch_credential_by_username("user2")
.expect("Failed to fetch cred2"),
cred2,
);
assert_eq!(
store
.fetch_credential_by_username("user3")
.expect("Failed to fetch cred3"),
cred3,
);
match store.fetch_credential_by_username("user4") {
Err(CredentialsStoreError::NotFoundError(_)) => {}
res => panic!(
"Expected Err(CredentialsStoreError::NotFoundError), got {:?} instead",
res
),
}
}
#[test]
fn sqlite_fetch_username_by_id() {
let pool = create_connection_pool_and_migrate();
let store = DieselCredentialsStore::new(pool);
let cred1 = CredentialsBuilder::default()
.with_user_id("id1")
.with_username("user1")
.with_password("pwd1")
.with_password_encryption_cost(PasswordEncryptionCost::Low)
.build()
.expect("Failed to build cred1");
store.add_credentials(cred1).expect("Failed to add cred1");
let cred2 = CredentialsBuilder::default()
.with_user_id("id2")
.with_username("user2")
.with_password("pwd2")
.with_password_encryption_cost(PasswordEncryptionCost::Medium)
.build()
.expect("Failed to build cred2");
store.add_credentials(cred2).expect("Failed to add cred2");
let cred3 = CredentialsBuilder::default()
.with_user_id("id3")
.with_username("user3")
.with_password("pwd3")
.with_password_encryption_cost(PasswordEncryptionCost::High)
.build()
.expect("Failed to build cred3");
store.add_credentials(cred3).expect("Failed to add cred3");
assert_eq!(
store
.fetch_username_by_id("id1")
.expect("Failed to fetch id1"),
UsernameId {
username: "user1".into(),
user_id: "id1".into(),
},
);
assert_eq!(
store
.fetch_username_by_id("id2")
.expect("Failed to fetch id2"),
UsernameId {
username: "user2".into(),
user_id: "id2".into(),
},
);
assert_eq!(
store
.fetch_username_by_id("id3")
.expect("Failed to fetch id3"),
UsernameId {
username: "user3".into(),
user_id: "id3".into(),
},
);
match store.fetch_username_by_id("id4") {
Err(CredentialsStoreError::NotFoundError(_)) => {}
res => panic!(
"Expected Err(CredentialsStoreError::NotFoundError), got {:?} instead",
res
),
}
}
#[test]
fn sqlite_list_usernames() {
let pool = create_connection_pool_and_migrate();
let store = DieselCredentialsStore::new(pool);
let cred1 = CredentialsBuilder::default()
.with_user_id("id1")
.with_username("user1")
.with_password("pwd1")
.with_password_encryption_cost(PasswordEncryptionCost::Low)
.build()
.expect("Failed to build cred1");
store.add_credentials(cred1).expect("Failed to add cred1");
let cred2 = CredentialsBuilder::default()
.with_user_id("id2")
.with_username("user2")
.with_password("pwd2")
.with_password_encryption_cost(PasswordEncryptionCost::Medium)
.build()
.expect("Failed to build cred2");
store.add_credentials(cred2).expect("Failed to add cred2");
let cred3 = CredentialsBuilder::default()
.with_user_id("id3")
.with_username("user3")
.with_password("pwd3")
.with_password_encryption_cost(PasswordEncryptionCost::High)
.build()
.expect("Failed to build cred3");
store.add_credentials(cred3).expect("Failed to add cred3");
let usernames = store.list_usernames().expect("Failed to list usernames");
assert_eq!(usernames.len(), 3);
assert!(usernames.contains(&UsernameId {
username: "user1".into(),
user_id: "id1".into(),
}));
assert!(usernames.contains(&UsernameId {
username: "user2".into(),
user_id: "id2".into(),
}));
assert!(usernames.contains(&UsernameId {
username: "user3".into(),
user_id: "id3".into(),
}));
}
#[test]
fn sqlite_update() {
let pool = create_connection_pool_and_migrate();
let store = DieselCredentialsStore::new(pool);
let cred = CredentialsBuilder::default()
.with_user_id("id")
.with_username("user1")
.with_password("pwd1")
.with_password_encryption_cost(PasswordEncryptionCost::Low)
.build()
.expect("Failed to build cred");
store
.add_credentials(cred.clone())
.expect("Failed to add cred");
assert_eq!(
store
.fetch_credential_by_user_id("id")
.expect("Failed to fetch cred"),
cred,
);
store
.update_credentials("id", "user2", "pwd2", PasswordEncryptionCost::Low)
.expect("Failed to update cred");
let cred = store
.fetch_credential_by_user_id("id")
.expect("Failed to fetch cred");
assert_eq!(cred.username, "user2");
assert!(cred
.verify_password("pwd2")
.expect("Failed to verify password"));
}
#[test]
fn sqlite_remove() {
let pool = create_connection_pool_and_migrate();
let store = DieselCredentialsStore::new(pool);
let cred1 = CredentialsBuilder::default()
.with_user_id("id1")
.with_username("user1")
.with_password("pwd1")
.with_password_encryption_cost(PasswordEncryptionCost::Low)
.build()
.expect("Failed to build cred1");
store.add_credentials(cred1).expect("Failed to add cred1");
let cred2 = CredentialsBuilder::default()
.with_user_id("id2")
.with_username("user2")
.with_password("pwd2")
.with_password_encryption_cost(PasswordEncryptionCost::Medium)
.build()
.expect("Failed to build cred2");
store.add_credentials(cred2).expect("Failed to add cred2");
let cred3 = CredentialsBuilder::default()
.with_user_id("id3")
.with_username("user3")
.with_password("pwd3")
.with_password_encryption_cost(PasswordEncryptionCost::High)
.build()
.expect("Failed to build cred3");
store.add_credentials(cred3).expect("Failed to add cred3");
store
.remove_credentials("id3")
.expect("Failed to remove cred3");
match store.fetch_credential_by_user_id("id3") {
Err(CredentialsStoreError::NotFoundError(_)) => {}
res => panic!(
"Expected Err(KeyStoreError::NotFoundError), got {:?} instead",
res
),
}
match store.fetch_credential_by_username("user3") {
Err(CredentialsStoreError::NotFoundError(_)) => {}
res => panic!(
"Expected Err(KeyStoreError::NotFoundError), got {:?} instead",
res
),
}
match store.fetch_username_by_id("id3") {
Err(CredentialsStoreError::NotFoundError(_)) => {}
res => panic!(
"Expected Err(KeyStoreError::NotFoundError), got {:?} instead",
res
),
}
let usernames = store.list_usernames().expect("Failed to list usernames");
assert_eq!(usernames.len(), 2);
assert!(!usernames.contains(&UsernameId {
username: "user3".into(),
user_id: "id3".into(),
}));
}
fn create_connection_pool_and_migrate() -> Pool<ConnectionManager<SqliteConnection>> {
let connection_manager = ConnectionManager::<SqliteConnection>::new(":memory:");
let pool = Pool::builder()
.max_size(1)
.build(connection_manager)
.expect("Failed to build connection pool");
run_sqlite_migrations(&*pool.get().expect("Failed to get connection for migrations"))
.expect("Failed to run migrations");
pool
}
}