reorganize backend
This commit is contained in:
parent
c19b573b26
commit
397928b8f1
@ -2,10 +2,25 @@ use std::fmt::{Display, Formatter};
|
|||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
|
|
||||||
// use tokio::sync::oneshot::error::RecvError;
|
|
||||||
|
// error when attempting to tell a request handler whether to release or deny crednetials
|
||||||
|
pub enum SendResponseError {
|
||||||
|
NotFound, // no request with the given id
|
||||||
|
Abandoned, // request has already been closed by client
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SendResponseError {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
use SendResponseError::*;
|
||||||
|
match self {
|
||||||
|
NotFound => write!(f, "The specified command was not found."),
|
||||||
|
Abandoned => write!(f, "The specified request was closed by the client."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Represents errors encountered while handling an HTTP request
|
// errors encountered while handling an HTTP request
|
||||||
pub enum RequestError {
|
pub enum RequestError {
|
||||||
StreamIOError(std::io::Error),
|
StreamIOError(std::io::Error),
|
||||||
InvalidUtf8,
|
InvalidUtf8,
|
||||||
@ -23,11 +38,6 @@ impl From<Utf8Error> for RequestError {
|
|||||||
RequestError::InvalidUtf8
|
RequestError::InvalidUtf8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// impl From<RecvError> for RequestError {
|
|
||||||
// fn from (_e: RecvError) -> RequestError {
|
|
||||||
// RequestError::
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl Display for RequestError {
|
impl Display for RequestError {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
|
@ -1,10 +1,35 @@
|
|||||||
pub enum RequestResponse {
|
use serde::{Serialize, Deserialize};
|
||||||
|
use tauri::State;
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
use crate::storage;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RequestResponse {
|
||||||
|
pub id: u64,
|
||||||
|
pub approval: Approval,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum Approval {
|
||||||
Approved,
|
Approved,
|
||||||
Denied,
|
Denied,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct Request {
|
#[tauri::command]
|
||||||
pub id: u64,
|
pub fn respond(response: RequestResponse, app_state: State<'_, AppState>) -> Result<(), String> {
|
||||||
pub response: RequestResponse,
|
app_state.send_response(response)
|
||||||
|
.map_err(|e| format!("Error responding to request: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn unlock(passphrase: String, app_state: State<'_, AppState>) -> bool {
|
||||||
|
let root_credentials = storage::load(&passphrase);
|
||||||
|
// get new session from AWS and store in app state
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -3,28 +3,24 @@
|
|||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Mutex;
|
|
||||||
// use tokio::runtime::Runtime;
|
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use tauri::{Manager, State};
|
|
||||||
use tokio::sync::oneshot;
|
|
||||||
|
|
||||||
|
mod errors;
|
||||||
|
mod ipc;
|
||||||
|
mod state;
|
||||||
|
mod server;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod http;
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let initial_state = state::AppState::new(state::SessionStatus::Locked, None);
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.manage(CurrentSession)
|
.manage(initial_state)
|
||||||
.manage(RequestCount)
|
.invoke_handler(tauri::generate_handler![ipc::unlock, ipc::respond])
|
||||||
.manage(OpenRequests)
|
|
||||||
.invoke_handler(tauri::generate_handler![unlock])
|
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let addr = std::net::SocketAddrV4::from_str("127.0.0.1:12345").unwrap();
|
let addr = std::net::SocketAddrV4::from_str("127.0.0.1:12345").unwrap();
|
||||||
tauri::async_runtime::spawn(http::serve(addr, app.handle()));
|
tauri::async_runtime::spawn(server::serve(addr, app.handle()));
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
@ -38,35 +34,3 @@ fn main() {
|
|||||||
// let creds = std::fs::read_to_string("credentials.json").unwrap();
|
// let creds = std::fs::read_to_string("credentials.json").unwrap();
|
||||||
// storage::save(&creds, "correct horse battery staple");
|
// storage::save(&creds, "correct horse battery staple");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub enum Session {
|
|
||||||
Unlocked(String),
|
|
||||||
Locked,
|
|
||||||
Empty,
|
|
||||||
}
|
|
||||||
|
|
||||||
type CurrentSession = Mutex<Session>;
|
|
||||||
type RequestCount = Mutex<u64>;
|
|
||||||
type OpenRequests = Mutex<HashMap<u64, oneshot::Sender>>;
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CredentialsRequest {
|
|
||||||
pub request_id: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// struct Session {
|
|
||||||
// key_id: String,
|
|
||||||
// secret_key: String,
|
|
||||||
// token: String,
|
|
||||||
// expires: u64,
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn unlock(passphrase: String, current_session: State<'_, CurrentSession>) -> bool {
|
|
||||||
let credentials = storage::load(&passphrase);
|
|
||||||
*current_session.lock().unwrap() = CredentialStatus::Unlocked(credentials);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
@ -2,11 +2,12 @@ use std::io;
|
|||||||
use std::net::SocketAddrV4;
|
use std::net::SocketAddrV4;
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Manager};
|
||||||
|
|
||||||
mod errors;
|
use crate::errors::RequestError;
|
||||||
use errors::RequestError;
|
use crate::ipc::Approval;
|
||||||
|
|
||||||
|
|
||||||
pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()> {
|
pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()> {
|
||||||
@ -30,8 +31,8 @@ pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()>
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// it doesn't really return a String, we just need to placate the compiler
|
// it doesn't really return Approval, we just need to placate the compiler
|
||||||
async fn stall(stream: &mut TcpStream) -> Result<String, tokio::io::Error> {
|
async fn stall(stream: &mut TcpStream) -> Result<Approval, tokio::io::Error> {
|
||||||
let delay = std::time::Duration::from_secs(1);
|
let delay = std::time::Duration::from_secs(1);
|
||||||
loop {
|
loop {
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
@ -41,6 +42,10 @@ async fn stall(stream: &mut TcpStream) -> Result<String, tokio::io::Error> {
|
|||||||
|
|
||||||
|
|
||||||
async fn handle(mut stream: TcpStream, app_handle: AppHandle) -> Result<(), RequestError> {
|
async fn handle(mut stream: TcpStream, app_handle: AppHandle) -> Result<(), RequestError> {
|
||||||
|
let (chan_send, chan_recv) = oneshot::channel();
|
||||||
|
let app_state = app_handle.state::<crate::state::AppState>();
|
||||||
|
let request_id = app_state.register_request(chan_send);
|
||||||
|
|
||||||
let mut buf = [0; 8192]; // it's what tokio's BufReader uses
|
let mut buf = [0; 8192]; // it's what tokio's BufReader uses
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
loop {
|
loop {
|
||||||
@ -55,11 +60,23 @@ async fn handle(mut stream: TcpStream, app_handle: AppHandle) -> Result<(), Requ
|
|||||||
stream.write(b"Content-Type: application/json\r\n").await?;
|
stream.write(b"Content-Type: application/json\r\n").await?;
|
||||||
stream.write(b"X-Creddy-delaying-tactic: ").await?;
|
stream.write(b"X-Creddy-delaying-tactic: ").await?;
|
||||||
|
|
||||||
let creds = tokio::select!{
|
let approval = tokio::select!{
|
||||||
r = stall(&mut stream) => r?, // this will never return Ok, just Err if it can't write to the stream
|
e = stall(&mut stream) => e?, // this will never return Ok, just Err if it can't write to the stream
|
||||||
c = get_creds(&app_handle) => c?,
|
r = chan_recv => r.unwrap(), // only panics if the sender is dropped without sending, which shouldn't happen
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if matches!(approval, Approval::Denied) {
|
||||||
|
// because we own the stream, it gets closed when we return.
|
||||||
|
// Unfortunately we've already signaled 200 OK, there's no way around this -
|
||||||
|
// we have to write the status code first thing, and we have to assume that the user
|
||||||
|
// might need more time than that gives us (especially if entering the passphrase).
|
||||||
|
// Fortunately most AWS libs automatically retry if the request dies uncompleted, allowing
|
||||||
|
// us to respond with a proper error status.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let creds = app_state.get_creds_serialized();
|
||||||
|
|
||||||
stream.write(b"\r\nContent-Length: ").await?;
|
stream.write(b"\r\nContent-Length: ").await?;
|
||||||
stream.write(creds.as_bytes().len().to_string().as_bytes()).await?;
|
stream.write(creds.as_bytes().len().to_string().as_bytes()).await?;
|
||||||
stream.write(b"\r\n\r\n").await?;
|
stream.write(b"\r\n\r\n").await?;
|
||||||
@ -67,39 +84,3 @@ async fn handle(mut stream: TcpStream, app_handle: AppHandle) -> Result<(), Requ
|
|||||||
stream.write(b"\r\n\r\n").await?;
|
stream.write(b"\r\n\r\n").await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
use tokio::io::{stdin, stdout, BufReader, AsyncBufReadExt};
|
|
||||||
use crate::storage;
|
|
||||||
|
|
||||||
use tokio::sync::oneshot;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
async fn get_creds(app_handle: &AppHandle) -> io::Result<String> {
|
|
||||||
{
|
|
||||||
let state_guard = app_handle.state::<Mutex<crate::AppState>>();
|
|
||||||
let mut state = state_guard.lock().unwrap();
|
|
||||||
state.num_requests += 1;
|
|
||||||
let req = crate::CredentialsRequest {
|
|
||||||
request_id: state.num_requests
|
|
||||||
};
|
|
||||||
app_handle.emit_all("credentials-request", req).unwrap();
|
|
||||||
// lock gets released here in case somebody else needs app state while we're waiting
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
app_handle.once_global("request-response", |event| {
|
|
||||||
let response = event.payload().unwrap_or("").to_string();
|
|
||||||
tx.send(response).unwrap();
|
|
||||||
});
|
|
||||||
// Error is only returned if the rx is closed/dropped before receiving, which should never happen
|
|
||||||
// LOL who am I kidding this happens all the time
|
|
||||||
// fix it later
|
|
||||||
|
|
||||||
// todo: handle "denied" response
|
|
||||||
let _response = rx.await.unwrap();
|
|
||||||
let state_guard = app_handle.state::<Mutex<crate::AppState>>();
|
|
||||||
let state = state_guard.lock().unwrap();
|
|
||||||
|
|
||||||
Ok(state.current_session.clone().unwrap())
|
|
||||||
}
|
|
@ -1,49 +1,56 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use tokio::sync::oneshot::Sender;
|
use tokio::sync::oneshot::Sender;
|
||||||
|
|
||||||
use crate::ipc;
|
use crate::ipc;
|
||||||
|
use crate::errors::*;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
#[serde(untagged)]
|
||||||
pub enum Credentials {
|
pub enum Credentials {
|
||||||
LongLived {
|
LongLived {
|
||||||
key_id: String,
|
access_key_id: String,
|
||||||
secret_key: String,
|
secret_access_key: String,
|
||||||
},
|
},
|
||||||
ShortLived {
|
ShortLived {
|
||||||
key_id: String,
|
access_key_id: String,
|
||||||
secret_key: String,
|
secret_access_key: String,
|
||||||
session_token: String,
|
token: String,
|
||||||
|
expiration: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub enum CurrentSession {
|
pub enum SessionStatus {
|
||||||
Unlocked(String),
|
Unlocked,
|
||||||
Locked,
|
Locked,
|
||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
current_session: RwLock<CurrentSession>,
|
status: RwLock<SessionStatus>,
|
||||||
|
credentials: RwLock<Option<Credentials>>,
|
||||||
request_count: RwLock<u64>,
|
request_count: RwLock<u64>,
|
||||||
open_requests: RwLock<HashMap<u64, Sender>>,
|
open_requests: RwLock<HashMap<u64, Sender<ipc::Approval>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new(current_session: CurrentSession) -> Self {
|
pub fn new(status: SessionStatus, creds: Option<Credentials>) -> Self {
|
||||||
AppState {
|
AppState {
|
||||||
current_session,
|
status: RwLock::new(status),
|
||||||
request_count: 0,
|
credentials: RwLock::new(creds),
|
||||||
open_requests: HashMap::new(),
|
request_count: RwLock::new(0),
|
||||||
|
open_requests: RwLock::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_request(&mut self, chan: Sender) -> u64 {
|
pub fn register_request(&self, chan: Sender<ipc::Approval>) -> u64 {
|
||||||
let count = {
|
let count = {
|
||||||
let c = self.request_count.write().unwrap();
|
let c = self.request_count.write().unwrap();
|
||||||
*c += 1;
|
*c += 1;
|
||||||
@ -51,24 +58,24 @@ impl AppState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let open_requests = self.open_requests.write().unwrap();
|
let open_requests = self.open_requests.write().unwrap();
|
||||||
self.open_requests.insert(count, chan);
|
open_requests.insert(*count, chan);
|
||||||
count
|
*count
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_response(&mut self, req_id: u64, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
|
pub fn send_response(&self, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
|
||||||
let mut open_requests = self.open_requests.write().unwrap();
|
let mut open_requests = self.open_requests.write().unwrap();
|
||||||
let chan = self.open_requests
|
let chan = open_requests
|
||||||
.remove(&req_id)
|
.remove(&response.id)
|
||||||
.ok_or(SendResponseError::NotFound)
|
.ok_or(SendResponseError::NotFound)
|
||||||
?;
|
?;
|
||||||
|
|
||||||
chan.send(response)
|
chan.send(response.approval)
|
||||||
.map_err(|_e| SendResponseError::Abandoned)
|
.map_err(|_e| SendResponseError::Abandoned)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn get_creds_serialized(&self) -> String {
|
||||||
pub enum SendResponseError {
|
let creds = self.credentials.read().unwrap();
|
||||||
NotFound, // no request with the given id
|
// fix this at some point
|
||||||
Abandoned, // request has already been closed by client
|
serde_json::to_string(&creds.unwrap()).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use sodiumoxide::crypto::{pwhash, secretbox};
|
use sodiumoxide::crypto::{pwhash, secretbox};
|
||||||
|
|
||||||
|
use crate::state;
|
||||||
|
|
||||||
|
|
||||||
pub fn save(data: &str, passphrase: &str) {
|
pub fn save(data: &str, passphrase: &str) {
|
||||||
let salt = pwhash::Salt([0; 32]); // yes yes, just for now
|
let salt = pwhash::Salt([0; 32]); // yes yes, just for now
|
||||||
@ -17,15 +19,24 @@ pub fn save(data: &str, passphrase: &str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn load(passphrase: &str) -> String {
|
// pub fn load(passphrase: &str) -> String {
|
||||||
let salt = pwhash::Salt([0; 32]);
|
// let salt = pwhash::Salt([0; 32]);
|
||||||
let mut kbuf = [0; secretbox::KEYBYTES];
|
// let mut kbuf = [0; secretbox::KEYBYTES];
|
||||||
pwhash::derive_key_interactive(&mut kbuf, passphrase.as_bytes(), &salt)
|
// pwhash::derive_key_interactive(&mut kbuf, passphrase.as_bytes(), &salt)
|
||||||
.expect("Couldn't compute password hash. Are you out of memory?");
|
// .expect("Couldn't compute password hash. Are you out of memory?");
|
||||||
let key = secretbox::Key(kbuf);
|
// let key = secretbox::Key(kbuf);
|
||||||
let nonce = secretbox::Nonce([0; 24]);
|
// let nonce = secretbox::Nonce([0; 24]);
|
||||||
|
|
||||||
let encrypted = std::fs::read("credentials.enc").expect("Failed to read file.");
|
// let encrypted = std::fs::read("credentials.enc").expect("Failed to read file.");
|
||||||
let decrypted = secretbox::open(&encrypted, &nonce, &key).expect("Failed to decrypt.");
|
// let decrypted = secretbox::open(&encrypted, &nonce, &key).expect("Failed to decrypt.");
|
||||||
String::from_utf8(decrypted).expect("Invalid utf-8")
|
// String::from_utf8(decrypted).expect("Invalid utf-8")
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn load(passphrase: &str) -> state::Credentials {
|
||||||
|
state::Credentials::ShortLived {
|
||||||
|
access_key_id: "ASIAZ7WSVLORKQI27QGB".to_string(),
|
||||||
|
secret_access_key: "blah".to_string(),
|
||||||
|
token: "gah".to_string(),
|
||||||
|
expiration: "2022-11-29T10:45:12Z".to_string(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user