store base credentials as well as session credentials

This commit is contained in:
Joseph Montanaro 2023-05-01 23:03:34 -07:00
parent 760987f09b
commit 161148d1f6
4 changed files with 50 additions and 48 deletions

View File

@ -4,7 +4,7 @@ use tauri::State;
use crate::errors::*; use crate::errors::*;
use crate::config::AppConfig; use crate::config::AppConfig;
use crate::clientinfo::Client; use crate::clientinfo::Client;
use crate::state::{AppState, Session, Credentials}; use crate::state::{AppState, Session, BaseCredentials};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@ -37,7 +37,7 @@ pub fn respond(response: RequestResponse, app_state: State<'_, AppState>) -> Res
#[tauri::command] #[tauri::command]
pub async fn unlock(passphrase: String, app_state: State<'_, AppState>) -> Result<(), UnlockError> { pub async fn unlock(passphrase: String, app_state: State<'_, AppState>) -> Result<(), UnlockError> {
app_state.decrypt(&passphrase).await app_state.unlock(&passphrase).await
} }
@ -46,7 +46,7 @@ pub fn get_session_status(app_state: State<'_, AppState>) -> String {
let session = app_state.session.read().unwrap(); let session = app_state.session.read().unwrap();
match *session { match *session {
Session::Locked(_) => "locked".into(), Session::Locked(_) => "locked".into(),
Session::Unlocked(_) => "unlocked".into(), Session::Unlocked{..} => "unlocked".into(),
Session::Empty => "empty".into() Session::Empty => "empty".into()
} }
} }
@ -54,7 +54,7 @@ pub fn get_session_status(app_state: State<'_, AppState>) -> String {
#[tauri::command] #[tauri::command]
pub async fn save_credentials( pub async fn save_credentials(
credentials: Credentials, credentials: BaseCredentials,
passphrase: String, passphrase: String,
app_state: State<'_, AppState> app_state: State<'_, AppState>
) -> Result<(), UnlockError> { ) -> Result<(), UnlockError> {

View File

@ -157,7 +157,7 @@ impl Handler {
async fn send_credentials(&mut self) -> Result<(), RequestError> { async fn send_credentials(&mut self) -> Result<(), RequestError> {
let state = self.app.state::<AppState>(); let state = self.app.state::<AppState>();
let creds = state.get_creds_serialized()?; let creds = state.serialize_session_creds()?;
self.stream.write(b"\r\nContent-Length: ").await?; self.stream.write(b"\r\nContent-Length: ").await?;
self.stream.write(creds.as_bytes().len().to_string().as_bytes()).await?; self.stream.write(creds.as_bytes().len().to_string().as_bytes()).await?;

View File

@ -23,20 +23,19 @@ use crate::server::Server;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)] #[serde(rename_all = "PascalCase")]
pub enum Credentials { pub struct BaseCredentials {
#[serde(rename_all = "PascalCase")] access_key_id: String,
LongLived { secret_access_key: String,
access_key_id: String, }
secret_access_key: String,
}, #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
ShortLived { pub struct SessionCredentials {
access_key_id: String, access_key_id: String,
secret_access_key: String, secret_access_key: String,
token: String, token: String,
expiration: String, expiration: String,
},
} }
@ -51,7 +50,10 @@ pub struct LockedCredentials {
#[derive(Debug)] #[derive(Debug)]
pub enum Session { pub enum Session {
Unlocked(Credentials), Unlocked{
base: BaseCredentials,
session: SessionCredentials,
},
Locked(LockedCredentials), Locked(LockedCredentials),
Empty, Empty,
} }
@ -106,16 +108,11 @@ impl AppState {
Ok(Session::Locked(creds)) Ok(Session::Locked(creds))
} }
pub async fn save_creds(&self, creds: Credentials, passphrase: &str) -> Result<(), UnlockError> { pub async fn save_creds(&self, creds: BaseCredentials, passphrase: &str) -> Result<(), UnlockError> {
let (key_id, secret_key) = match creds { let BaseCredentials {access_key_id, secret_access_key} = creds;
Credentials::LongLived {access_key_id, secret_access_key} => {
(access_key_id, secret_access_key)
},
_ => unreachable!(),
};
// do this first so that if it fails we don't save bad credentials // do this first so that if it fails we don't save bad credentials
self.new_session(&key_id, &secret_key).await?; self.new_session(&access_key_id, &secret_access_key).await?;
let salt = pwhash::gen_salt(); let salt = pwhash::gen_salt();
let mut key_buf = [0; secretbox::KEYBYTES]; let mut key_buf = [0; secretbox::KEYBYTES];
@ -124,14 +121,14 @@ impl AppState {
// not sure we need both salt AND nonce given that we generate a // not sure we need both salt AND nonce given that we generate a
// fresh salt every time we encrypt, but better safe than sorry // fresh salt every time we encrypt, but better safe than sorry
let nonce = secretbox::gen_nonce(); let nonce = secretbox::gen_nonce();
let secret_key_enc = secretbox::seal(secret_key.as_bytes(), &nonce, &key); let secret_key_enc = secretbox::seal(secret_access_key.as_bytes(), &nonce, &key);
sqlx::query( sqlx::query(
"INSERT INTO credentials (access_key_id, secret_key_enc, salt, nonce, created_at) "INSERT INTO credentials (access_key_id, secret_key_enc, salt, nonce, created_at)
VALUES (?, ?, ?, ?, strftime('%s'))" VALUES (?, ?, ?, ?, strftime('%s'))"
) )
.bind(&key_id) .bind(&access_key_id)
.bind(&secret_key_enc) .bind(&secret_key_enc)
.bind(&salt.0[0..]) .bind(&salt.0[0..])
.bind(&nonce.0[0..]) .bind(&nonce.0[0..])
@ -210,13 +207,13 @@ impl AppState {
self.bans.read().unwrap().contains(&client) self.bans.read().unwrap().contains(&client)
} }
pub async fn decrypt(&self, passphrase: &str) -> Result<(), UnlockError> { pub async fn unlock(&self, passphrase: &str) -> Result<(), UnlockError> {
let (key_id, secret) = { let (access_key_id, secret_access_key) = {
// do this all in a block so that we aren't holding a lock across an await // do this all in a block so that we aren't holding a lock across an await
let session = self.session.read().unwrap(); let session = self.session.read().unwrap();
let locked = match *session { let locked = match *session {
Session::Empty => {return Err(UnlockError::NoCredentials);}, Session::Empty => {return Err(UnlockError::NoCredentials);},
Session::Unlocked(_) => {return Err(UnlockError::NotLocked);}, Session::Unlocked{..} => {return Err(UnlockError::NotLocked);},
Session::Locked(ref c) => c, Session::Locked(ref c) => c,
}; };
@ -230,21 +227,35 @@ impl AppState {
(locked.access_key_id.clone(), secret_str) (locked.access_key_id.clone(), secret_str)
}; };
self.new_session(&key_id, &secret).await?; let session_creds = self.new_session(&access_key_id, &secret_access_key).await?;
let mut app_session = self.session.write().unwrap();
*app_session = Session::Unlocked {
base: BaseCredentials {access_key_id, secret_access_key},
session: session_creds
};
Ok(()) Ok(())
} }
pub fn get_creds_serialized(&self) -> Result<String, GetCredentialsError> { // pub fn serialize_base_creds(&self) -> Result<String, GetCredentialsError> {
// let session = self.session.read().unwrap();
// match *session {
// Session::Unlocked{ref base, ..} => Ok(serde_json::to_string(base).unwrap()),
// Session::Locked(_) => Err(GetCredentialsError::Locked),
// Session::Empty => Err(GetCredentialsError::Empty),
// }
// }
pub fn serialize_session_creds(&self) -> Result<String, GetCredentialsError> {
let session = self.session.read().unwrap(); let session = self.session.read().unwrap();
match *session { match *session {
Session::Unlocked(ref creds) => Ok(serde_json::to_string(creds).unwrap()), Session::Unlocked{ref session, ..} => Ok(serde_json::to_string(session).unwrap()),
Session::Locked(_) => Err(GetCredentialsError::Locked), Session::Locked(_) => Err(GetCredentialsError::Locked),
Session::Empty => Err(GetCredentialsError::Empty), Session::Empty => Err(GetCredentialsError::Empty),
} }
} }
async fn new_session(&self, key_id: &str, secret_key: &str) -> Result<(), GetSessionError> { async fn new_session(&self, key_id: &str, secret_key: &str) -> Result<SessionCredentials, GetSessionError> {
let creds = aws_sdk_sts::Credentials::new( let creds = aws_sdk_sts::Credentials::new(
key_id, key_id,
secret_key, secret_key,
@ -279,8 +290,7 @@ impl AppState {
.fmt(aws_smithy_types::date_time::Format::DateTime) .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 .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 = SessionCredentials {
let session_creds = Credentials::ShortLived {
access_key_id, access_key_id,
secret_access_key, secret_access_key,
token, token,
@ -290,8 +300,6 @@ impl AppState {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
println!("Got new session:\n{}", serde_json::to_string(&session_creds).unwrap()); println!("Got new session:\n{}", serde_json::to_string(&session_creds).unwrap());
*app_session = Session::Unlocked(session_creds); Ok(session_creds)
Ok(())
} }
} }

View File

@ -7,7 +7,6 @@ import { appState, acceptRequest } from './lib/state.js';
import { views, currentView, navigate } from './lib/routing.js'; import { views, currentView, navigate } from './lib/routing.js';
$views = import.meta.glob('./views/*.svelte', {eager: true}); $views = import.meta.glob('./views/*.svelte', {eager: true});
navigate('Home'); navigate('Home');
@ -17,11 +16,6 @@ listen('credentials-request', (tauriEvent) => {
$appState.pendingRequests.put(tauriEvent.payload); $appState.pendingRequests.put(tauriEvent.payload);
}); });
// $appState.pendingRequests.get().then(req => {
// $appState.currentRequest = req;
// })
appState.subscribe($s => window.state = $s);
acceptRequest(); acceptRequest();
</script> </script>