add passphrase reset

This commit is contained in:
2024-06-28 11:19:52 -04:00
parent bf0a2ca72d
commit 504c0b4156
11 changed files with 226 additions and 98 deletions

View File

@ -46,6 +46,7 @@ pub fn run() -> tauri::Result<()> {
.invoke_handler(tauri::generate_handler![
ipc::unlock,
ipc::lock,
ipc::reset_session,
ipc::set_passphrase,
ipc::respond,
ipc::get_session_status,

View File

@ -151,9 +151,11 @@ impl CredentialRecord {
Ok(records)
}
#[allow(unused_variables)]
pub async fn rekey(old: &Crypto, new: &Crypto, pool: &SqlitePool) -> Result<(), SaveCredentialsError> {
todo!()
for record in Self::list(old, pool).await? {
record.save(new, pool).await?;
}
Ok(())
}
}
@ -340,6 +342,22 @@ mod tests {
assert_eq!(aws_record(), records[0]);
assert_eq!(aws_record_2(), records[1]);
}
#[sqlx::test(fixtures("aws_credentials"))]
async fn test_rekey(pool: SqlitePool) {
let old = Crypto::fixed();
let new = Crypto::random();
CredentialRecord::rekey(&old, &new, &pool).await
.expect("Failed to rekey credentials");
let records = CredentialRecord::list(&new, &pool).await
.expect("Failed to re-list credentials");
assert_eq!(aws_record(), records[0]);
assert_eq!(aws_record_2(), records[1]);
}
}

View File

@ -79,6 +79,17 @@ impl AppSession {
Ok(())
}
pub async fn reset(&mut self, pool: &SqlitePool) -> Result<(), SaveCredentialsError> {
match self {
Self::Unlocked {..} | Self::Locked {..} => {
kv::delete_multi(pool, &["salt", "verify_nonce", "verify_blob"]).await?;
*self = Self::Empty;
},
Self::Empty => (),
}
Ok(())
}
pub fn try_get_crypto(&self) -> Result<&Crypto, GetCredentialsError> {
match self {
Self::Empty => Err(GetCredentialsError::Empty),

View File

@ -0,0 +1,13 @@
INSERT INTO kv (name, value)
VALUES
-- b"hello world" (raw bytes)
('test_bytes', X'68656C6C6F20776F726C64'),
-- b"\"hello world\"" (JSON string)
('test_string', X'2268656C6C6F20776F726C6422'),
-- b"123" (JSON integer)
('test_int', X'313233'),
-- b"true" (JSON bool)
('test_bool', X'74727565')

View File

@ -80,6 +80,12 @@ pub async fn lock(app_state: State<'_, AppState>) -> Result<(), LockError> {
}
#[tauri::command]
pub async fn reset_session(app_state: State<'_, AppState>) -> Result<(), SaveCredentialsError> {
app_state.reset_session().await
}
#[tauri::command]
pub async fn set_passphrase(passphrase: &str, app_state: State<'_, AppState>) -> Result<(), SaveCredentialsError> {
app_state.set_passphrase(passphrase).await

View File

@ -6,7 +6,7 @@ use crate::errors::*;
pub async fn save<T>(pool: &SqlitePool, name: &str, value: &T) -> Result<(), sqlx::Error>
where T: Serialize
where T: Serialize + ?Sized
{
let bytes = serde_json::to_vec(value).unwrap();
save_bytes(pool, name, &bytes).await
@ -44,9 +44,33 @@ pub async fn load_bytes(pool: &SqlitePool, name: &str) -> Result<Option<Vec<u8>>
}
pub async fn delete(pool: &SqlitePool, name: &str) -> Result<(), sqlx::Error> {
sqlx::query!("DELETE FROM kv WHERE name = ?", name)
.execute(pool)
.await?;
Ok(())
}
pub async fn delete_multi(pool: &SqlitePool, names: &[&str]) -> Result<(), sqlx::Error> {
let placeholder = names.iter()
.map(|_| "?")
.collect::<Vec<&str>>()
.join(",");
let query = format!("DELETE FROM kv WHERE name IN ({})", placeholder);
let mut q = sqlx::query(&query);
for name in names {
q = q.bind(name);
}
q.execute(pool).await?;
Ok(())
}
macro_rules! load_bytes_multi {
(
$pool:ident,
$pool:expr,
$($name:literal),*
) => {
// wrap everything up in an async block for easy short-circuiting...
@ -78,7 +102,7 @@ pub(crate) use load_bytes_multi;
// macro_rules! load_multi {
// (
// $pool:ident,
// $pool:expr,
// $($name:literal),*
// ) => {
// (|| {
@ -93,3 +117,94 @@ pub(crate) use load_bytes_multi;
// })()
// }
// }
#[cfg(test)]
mod tests {
use super::*;
#[sqlx::test]
async fn test_save_bytes(pool: SqlitePool) {
save_bytes(&pool, "test_bytes", b"hello world").await
.expect("Failed to save bytes");
}
#[sqlx::test]
async fn test_save(pool: SqlitePool) {
save(&pool, "test_string", "hello world").await
.expect("Failed to save string");
save(&pool, "test_int", &123).await
.expect("Failed to save integer");
save(&pool, "test_bool", &true).await
.expect("Failed to save bool");
}
#[sqlx::test(fixtures("kv"))]
async fn test_load_bytes(pool: SqlitePool) {
let bytes = load_bytes(&pool, "test_bytes").await
.expect("Failed to load bytes")
.expect("Test data not found in database");
assert_eq!(bytes, Vec::from(b"hello world"));
}
#[sqlx::test(fixtures("kv"))]
async fn test_load(pool: SqlitePool) {
let string: String = load(&pool, "test_string").await
.expect("Failed to load string")
.expect("Test data not found in database");
assert_eq!(string, "hello world".to_string());
let integer: usize = load(&pool, "test_int").await
.expect("Failed to load integer")
.expect("Test data not found in database");
assert_eq!(integer, 123);
let boolean: bool = load(&pool, "test_bool").await
.expect("Failed to load boolean")
.expect("Test data not found in database");
assert_eq!(boolean, true);
}
#[sqlx::test(fixtures("kv"))]
async fn test_load_multi(pool: SqlitePool) {
let (bytes, boolean) = load_bytes_multi!(&pool, "test_bytes", "test_bool")
.await
.expect("Failed to load items")
.expect("Test data not found in database");
assert_eq!(bytes, Vec::from(b"hello world"));
assert_eq!(boolean, Vec::from(b"true"));
}
#[sqlx::test(fixtures("kv"))]
async fn test_delete(pool: SqlitePool) {
delete(&pool, "test_bytes").await
.expect("Failed to delete data");
let loaded = load_bytes(&pool, "test_bytes").await
.expect("Failed to load data");
assert_eq!(loaded, None);
}
#[sqlx::test(fixtures("kv"))]
async fn test_delete_multi(pool: SqlitePool) {
delete_multi(&pool, &["test_bytes", "test_string"]).await
.expect("Failed to delete keys");
let bytes_opt = load_bytes(&pool, "test_bytes").await
.expect("Failed to load bytes");
assert_eq!(bytes_opt, None);
let string_opt = load_bytes(&pool, "test_string").await
.expect("Failed to load string");
assert_eq!(string_opt, None);
}
}

View File

@ -254,6 +254,13 @@ impl AppState {
}
}
pub async fn reset_session(&self) -> Result<(), SaveCredentialsError> {
let mut session = self.app_session.write().await;
session.reset(&self.pool).await?;
sqlx::query!("DELETE FROM credentials").execute(&self.pool).await?;
Ok(())
}
pub async fn get_aws_base(&self, name: &str) -> Result<AwsBaseCredential, GetCredentialsError> {
let app_session = self.app_session.read().await;
let crypto = app_session.try_get_crypto()?;