use serde::{Serialize, Deserialize}; use sqlx::{ FromRow, Sqlite, SqlitePool, sqlite::SqliteRow, Transaction, types::Uuid, }; use tokio_stream::StreamExt; use crate::errors::*; mod aws; pub use aws::{AwsBaseCredential, AwsSessionCredential}; mod crypto; pub use crypto::Crypto; mod record; pub use record::CredentialRecord; mod session; pub use session::AppSession; mod ssh; pub use ssh::SshKey; #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(tag = "type")] pub enum Credential { AwsBase(AwsBaseCredential), AwsSession(AwsSessionCredential), Ssh(SshKey), } pub trait PersistentCredential: for<'a> Deserialize<'a> + Sized { type Row: Send + Unpin + for<'r> FromRow<'r, SqliteRow>; fn type_name() -> &'static str; fn into_credential(self) -> Credential; fn row_id(row: &Self::Row) -> Uuid; fn from_row(row: Self::Row, crypto: &Crypto) -> Result; // save_details needs to be implemented per-type because we don't know the number of parameters in advance async fn save_details(&self, id: &Uuid, crypto: &Crypto, txn: &mut Transaction<'_, Sqlite>) -> Result<(), SaveCredentialsError>; fn table_name() -> String { format!("{}_credentials", Self::type_name()) } async fn load(id: &Uuid, crypto: &Crypto, pool: &SqlitePool) -> Result { let q = format!("SELECT * FROM {} WHERE id = ?", Self::table_name()); let row: Self::Row = sqlx::query_as(&q) .bind(id) .fetch_optional(pool) .await? .ok_or(LoadCredentialsError::NoCredentials)?; Self::from_row(row, crypto) } async fn load_by_name(name: &str, crypto: &Crypto, pool: &SqlitePool) -> Result { let q = format!( "SELECT * FROM {} WHERE id = (SELECT id FROM credentials WHERE name = ?)", Self::table_name(), ); let row: Self::Row = sqlx::query_as(&q) .bind(name) .fetch_optional(pool) .await? .ok_or(LoadCredentialsError::NoCredentials)?; Self::from_row(row, crypto) } async fn load_default(crypto: &Crypto, pool: &SqlitePool) -> Result { let q = format!( "SELECT details.* FROM {} details JOIN credentials c ON c.id = details.id AND c.is_default = 1", Self::table_name(), ); let row: Self::Row = sqlx::query_as(&q) .fetch_optional(pool) .await? .ok_or(LoadCredentialsError::NoCredentials)?; Self::from_row(row, crypto) } async fn list(crypto: &Crypto, pool: &SqlitePool) -> Result, LoadCredentialsError> { let q = format!( "SELECT details.* FROM {} details JOIN credentials c ON c.id = details.id ORDER BY c.created_at", Self::table_name(), ); let mut rows = sqlx::query_as::<_, Self::Row>(&q).fetch(pool); let mut creds = Vec::new(); while let Some(row) = rows.try_next().await? { let id = Self::row_id(&row); let cred = Self::from_row(row, crypto)?.into_credential(); creds.push((id, cred)); } Ok(creds) } }