creddy/src-tauri/src/state.rs

298 lines
9.6 KiB
Rust
Raw Normal View History

2022-12-19 16:20:46 -08:00
use core::time::Duration;
use std::collections::{HashMap, HashSet};
use std::sync::RwLock;
use serde::{Serialize, Deserialize};
use tokio::sync::oneshot::Sender;
2022-12-19 16:20:46 -08:00
use tokio::time::sleep;
2023-04-28 14:33:04 -07:00
use sqlx::SqlitePool;
2022-12-03 21:47:09 -08:00
use sodiumoxide::crypto::{
pwhash,
pwhash::Salt,
secretbox,
secretbox::{Nonce, Key}
};
use tauri::async_runtime as runtime;
2022-12-19 16:20:46 -08:00
use tauri::Manager;
use crate::{config, config::AppConfig};
use crate::ipc;
2022-12-20 16:11:49 -08:00
use crate::clientinfo::Client;
2022-11-28 16:16:33 -08:00
use crate::errors::*;
2023-04-28 14:33:04 -07:00
use crate::server::Server;
#[derive(Debug, Serialize, Deserialize)]
2022-11-28 16:16:33 -08:00
#[serde(untagged)]
pub enum Credentials {
2022-12-14 14:52:16 -08:00
#[serde(rename_all = "PascalCase")]
LongLived {
2022-11-28 16:16:33 -08:00
access_key_id: String,
secret_access_key: String,
},
2022-12-14 14:52:16 -08:00
#[serde(rename_all = "PascalCase")]
ShortLived {
2022-11-28 16:16:33 -08:00
access_key_id: String,
secret_access_key: String,
token: String,
expiration: String,
},
}
#[derive(Debug)]
2022-12-03 21:47:09 -08:00
pub struct LockedCredentials {
access_key_id: String,
secret_key_enc: Vec<u8>,
salt: Salt,
nonce: Nonce,
}
#[derive(Debug)]
2022-12-03 21:47:09 -08:00
pub enum Session {
Unlocked(Credentials),
Locked(LockedCredentials),
Empty,
}
#[derive(Debug)]
pub struct AppState {
pub config: RwLock<AppConfig>,
2022-12-03 21:47:09 -08:00
pub session: RwLock<Session>,
pub request_count: RwLock<u64>,
pub open_requests: RwLock<HashMap<u64, Sender<ipc::Approval>>>,
2022-12-20 16:11:49 -08:00
pub bans: RwLock<std::collections::HashSet<Option<Client>>>,
2023-04-28 14:33:04 -07:00
server: RwLock<Server>,
pool: sqlx::SqlitePool,
}
impl AppState {
2023-04-29 10:01:45 -07:00
pub fn new(config: AppConfig, session: Session, server: Server, pool: SqlitePool) -> AppState {
AppState {
config: RwLock::new(config),
session: RwLock::new(session),
request_count: RwLock::new(0),
open_requests: RwLock::new(HashMap::new()),
bans: RwLock::new(HashSet::new()),
server: RwLock::new(server),
pool,
}
2022-12-02 22:59:13 -08:00
}
2023-04-28 14:33:04 -07:00
pub async fn load_creds(pool: &SqlitePool) -> Result<Session, SetupError> {
2023-04-25 22:10:14 -07:00
let res = sqlx::query!("SELECT * FROM credentials ORDER BY created_at desc")
2022-12-03 21:47:09 -08:00
.fetch_optional(pool)
2022-12-02 22:59:13 -08:00
.await?;
2022-12-03 21:47:09 -08:00
let row = match res {
Some(r) => r,
None => {return Ok(Session::Empty);}
};
2022-12-02 22:59:13 -08:00
2022-12-03 21:47:09 -08:00
let salt_buf: [u8; 32] = row.salt
.try_into()
.map_err(|_e| SetupError::InvalidRecord)?;
let nonce_buf: [u8; 24] = row.nonce
.try_into()
.map_err(|_e| SetupError::InvalidRecord)?;
let creds = LockedCredentials {
access_key_id: row.access_key_id,
secret_key_enc: row.secret_key_enc,
salt: Salt(salt_buf),
nonce: Nonce(nonce_buf),
};
Ok(Session::Locked(creds))
}
2022-12-19 15:26:44 -08:00
pub async fn save_creds(&self, creds: Credentials, passphrase: &str) -> Result<(), UnlockError> {
2022-12-03 21:47:09 -08:00
let (key_id, secret_key) = match creds {
Credentials::LongLived {access_key_id, secret_access_key} => {
(access_key_id, secret_access_key)
},
_ => unreachable!(),
};
2023-04-25 22:10:14 -07:00
// do this first so that if it fails we don't save bad credentials
self.new_session(&key_id, &secret_key).await?;
2022-12-03 21:47:09 -08:00
let salt = pwhash::gen_salt();
let mut key_buf = [0; secretbox::KEYBYTES];
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &salt).unwrap();
let key = Key(key_buf);
// not sure we need both salt AND nonce given that we generate a
// fresh salt every time we encrypt, but better safe than sorry
let nonce = secretbox::gen_nonce();
2022-12-19 15:26:44 -08:00
let secret_key_enc = secretbox::seal(secret_key.as_bytes(), &nonce, &key);
2022-12-14 14:52:16 -08:00
2022-12-19 15:26:44 -08:00
sqlx::query(
2023-04-25 22:10:14 -07:00
"INSERT INTO credentials (access_key_id, secret_key_enc, salt, nonce, created_at)
VALUES (?, ?, ?, ?, strftime('%s'))"
2022-12-19 15:26:44 -08:00
)
.bind(&key_id)
.bind(&secret_key_enc)
.bind(&salt.0[0..])
.bind(&nonce.0[0..])
.execute(&self.pool)
.await?;
2022-12-02 22:59:13 -08:00
Ok(())
}
2023-04-27 14:24:08 -07:00
pub async fn update_config(&self, new_config: AppConfig) -> Result<(), SetupError> {
2023-04-28 14:33:04 -07:00
{
let orig_config = self.config.read().unwrap();
if new_config.start_on_login != orig_config.start_on_login {
config::set_auto_launch(new_config.start_on_login)?;
}
if new_config.listen_addr != orig_config.listen_addr
|| new_config.listen_port != orig_config.listen_port
{
let mut sv = self.server.write().unwrap();
sv.rebind(new_config.listen_addr, new_config.listen_port)?;
}
}
2023-04-27 14:24:08 -07:00
2023-04-28 14:33:04 -07:00
new_config.save(&self.pool).await?;
2023-04-27 14:24:08 -07:00
let mut live_config = self.config.write().unwrap();
*live_config = new_config;
2023-04-26 15:49:08 -07:00
Ok(())
}
2022-11-28 16:16:33 -08:00
pub fn register_request(&self, chan: Sender<ipc::Approval>) -> u64 {
let count = {
2022-11-29 16:13:09 -08:00
let mut c = self.request_count.write().unwrap();
*c += 1;
c
};
2022-11-29 16:13:09 -08:00
let mut open_requests = self.open_requests.write().unwrap();
2022-12-03 21:47:09 -08:00
open_requests.insert(*count, chan); // `count` is the request id
2022-11-28 16:16:33 -08:00
*count
}
2022-12-20 16:11:49 -08:00
pub fn unregister_request(&self, id: u64) {
let mut open_requests = self.open_requests.write().unwrap();
open_requests.remove(&id);
}
2022-12-21 16:04:12 -08:00
pub fn req_count(&self) -> usize {
let open_requests = self.open_requests.read().unwrap();
open_requests.len()
}
2022-11-28 16:16:33 -08:00
pub fn send_response(&self, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
let mut open_requests = self.open_requests.write().unwrap();
2022-11-28 16:16:33 -08:00
let chan = open_requests
.remove(&response.id)
.ok_or(SendResponseError::NotFound)
?;
2022-11-28 16:16:33 -08:00
chan.send(response.approval)
.map_err(|_e| SendResponseError::Abandoned)
}
2022-12-20 16:11:49 -08:00
pub fn add_ban(&self, client: Option<Client>, app: tauri::AppHandle) {
2022-12-19 16:20:46 -08:00
let mut bans = self.bans.write().unwrap();
2022-12-20 16:11:49 -08:00
bans.insert(client.clone());
2022-12-19 16:20:46 -08:00
runtime::spawn(async move {
sleep(Duration::from_secs(5)).await;
let state = app.state::<AppState>();
let mut bans = state.bans.write().unwrap();
2022-12-20 16:11:49 -08:00
bans.remove(&client);
2022-12-19 16:20:46 -08:00
});
}
2022-12-20 16:11:49 -08:00
pub fn is_banned(&self, client: &Option<Client>) -> bool {
self.bans.read().unwrap().contains(&client)
2022-12-19 16:20:46 -08:00
}
2022-12-03 21:47:09 -08:00
pub async fn decrypt(&self, passphrase: &str) -> Result<(), UnlockError> {
2022-12-19 15:26:44 -08:00
let (key_id, secret) = {
// do this all in a block so that we aren't holding a lock across an await
2022-12-19 15:26:44 -08:00
let session = self.session.read().unwrap();
let locked = match *session {
Session::Empty => {return Err(UnlockError::NoCredentials);},
Session::Unlocked(_) => {return Err(UnlockError::NotLocked);},
Session::Locked(ref c) => c,
};
let mut key_buf = [0; secretbox::KEYBYTES];
// pretty sure this only fails if we're out of memory
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &locked.salt).unwrap();
let decrypted = secretbox::open(&locked.secret_key_enc, &locked.nonce, &Key(key_buf))
.map_err(|_e| UnlockError::BadPassphrase)?;
let secret_str = String::from_utf8(decrypted).map_err(|_e| UnlockError::InvalidUtf8)?;
(locked.access_key_id.clone(), secret_str)
2022-12-03 21:47:09 -08:00
};
2022-12-19 15:26:44 -08:00
self.new_session(&key_id, &secret).await?;
2022-12-03 21:47:09 -08:00
Ok(())
2022-11-29 16:13:09 -08:00
}
2022-12-03 21:47:09 -08:00
pub fn get_creds_serialized(&self) -> Result<String, GetCredentialsError> {
let session = self.session.read().unwrap();
match *session {
Session::Unlocked(ref creds) => Ok(serde_json::to_string(creds).unwrap()),
Session::Locked(_) => Err(GetCredentialsError::Locked),
Session::Empty => Err(GetCredentialsError::Empty),
}
2022-11-28 16:16:33 -08:00
}
2022-12-19 15:26:44 -08:00
async fn new_session(&self, key_id: &str, secret_key: &str) -> Result<(), GetSessionError> {
let creds = aws_sdk_sts::Credentials::new(
key_id,
secret_key,
None, // token
None, // expiration
"creddy", // "provider name" apparently
);
let config = aws_config::from_env()
.credentials_provider(creds)
.load()
.await;
let client = aws_sdk_sts::Client::new(&config);
let resp = client.get_session_token()
.duration_seconds(43_200)
.send()
.await?;
let aws_session = resp.credentials().ok_or(GetSessionError::NoCredentials)?;
let access_key_id = aws_session.access_key_id()
.ok_or(GetSessionError::NoCredentials)?
.to_string();
let secret_access_key = aws_session.secret_access_key()
.ok_or(GetSessionError::NoCredentials)?
.to_string();
let token = aws_session.session_token()
.ok_or(GetSessionError::NoCredentials)?
.to_string();
let expiration = aws_session.expiration()
.ok_or(GetSessionError::NoCredentials)?
.fmt(aws_smithy_types::date_time::Format::DateTime)
.unwrap(); // only fails if the d/t is out of range, which it can't be for this format
let mut app_session = self.session.write().unwrap();
let session_creds = Credentials::ShortLived {
access_key_id,
secret_access_key,
token,
expiration,
};
2023-04-29 10:01:45 -07:00
#[cfg(debug_assertions)]
println!("Got new session:\n{}", serde_json::to_string(&session_creds).unwrap());
2022-12-19 15:26:44 -08:00
*app_session = Session::Unlocked(session_creds);
Ok(())
}
}