all is change; in change is all again made new
This commit is contained in:
parent
cee43342b9
commit
c19b573b26
@ -23,7 +23,7 @@ pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()>
|
||||
});
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Error accepting connection: {e}");
|
||||
eprintln!("Error accepting connection: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,27 +73,33 @@ 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> {
|
||||
app_handle.emit_all("credentials-request", ()).unwrap();
|
||||
|
||||
// let mut out = stdout();
|
||||
// out.write_all(b"Enter passphrase: ").await?;
|
||||
// out.flush().await?;
|
||||
{
|
||||
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("passphrase-entered", |event| {
|
||||
match event.payload() {
|
||||
Some(p) => {tx.send(p.to_string());}
|
||||
None => {tx.send("".to_string());} // will fail decryption, we just need to unblock the outer function
|
||||
}
|
||||
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
|
||||
let passphrase = rx.await.unwrap();
|
||||
// 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();
|
||||
|
||||
// let mut passphrase = String::new();
|
||||
// let mut reader = BufReader::new(stdin());
|
||||
// reader.read_line(&mut passphrase).await?;
|
||||
|
||||
Ok(storage::load(&passphrase.trim()))
|
||||
Ok(state.current_session.clone().unwrap())
|
||||
}
|
||||
|
10
src-tauri/src/ipc.rs
Normal file
10
src-tauri/src/ipc.rs
Normal file
@ -0,0 +1,10 @@
|
||||
pub enum RequestResponse {
|
||||
Approved,
|
||||
Denied,
|
||||
}
|
||||
|
||||
|
||||
pub struct Request {
|
||||
pub id: u64,
|
||||
pub response: RequestResponse,
|
||||
}
|
@ -3,15 +3,25 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
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 storage;
|
||||
mod http;
|
||||
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.manage(CurrentSession)
|
||||
.manage(RequestCount)
|
||||
.manage(OpenRequests)
|
||||
.invoke_handler(tauri::generate_handler![unlock])
|
||||
.setup(|app| {
|
||||
let addr = std::net::SocketAddrV4::from_str("127.0.0.1:12345").unwrap();
|
||||
tauri::async_runtime::spawn(http::serve(addr, app.handle()));
|
||||
@ -28,3 +38,35 @@ fn main() {
|
||||
// let creds = std::fs::read_to_string("credentials.json").unwrap();
|
||||
// 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
|
||||
}
|
||||
|
74
src-tauri/src/state.rs
Normal file
74
src-tauri/src/state.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use tokio::sync::oneshot::Sender;
|
||||
|
||||
use crate::ipc;
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Credentials {
|
||||
LongLived {
|
||||
key_id: String,
|
||||
secret_key: String,
|
||||
},
|
||||
ShortLived {
|
||||
key_id: String,
|
||||
secret_key: String,
|
||||
session_token: String,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum CurrentSession {
|
||||
Unlocked(String),
|
||||
Locked,
|
||||
Empty,
|
||||
}
|
||||
|
||||
|
||||
pub struct AppState {
|
||||
current_session: RwLock<CurrentSession>,
|
||||
request_count: RwLock<u64>,
|
||||
open_requests: RwLock<HashMap<u64, Sender>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(current_session: CurrentSession) -> Self {
|
||||
AppState {
|
||||
current_session,
|
||||
request_count: 0,
|
||||
open_requests: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_request(&mut self, chan: Sender) -> u64 {
|
||||
let count = {
|
||||
let c = self.request_count.write().unwrap();
|
||||
*c += 1;
|
||||
c
|
||||
};
|
||||
|
||||
let open_requests = self.open_requests.write().unwrap();
|
||||
self.open_requests.insert(count, chan);
|
||||
count
|
||||
}
|
||||
|
||||
pub fn send_response(&mut self, req_id: u64, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
|
||||
let mut open_requests = self.open_requests.write().unwrap();
|
||||
let chan = self.open_requests
|
||||
.remove(&req_id)
|
||||
.ok_or(SendResponseError::NotFound)
|
||||
?;
|
||||
|
||||
chan.send(response)
|
||||
.map_err(|_e| SendResponseError::Abandoned)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub enum SendResponseError {
|
||||
NotFound, // no request with the given id
|
||||
Abandoned, // request has already been closed by client
|
||||
}
|
@ -1,33 +1,36 @@
|
||||
<script>
|
||||
import { emit, listen } from '@tauri-apps/api/event';
|
||||
import queue from './lib/queue.js';
|
||||
import Home from './views/Home.svelte';
|
||||
import Approve from './views/Approve.svelte';
|
||||
import ShowApproved from './views/ShowApproved.svelte';
|
||||
import ShowDenied from './views/ShowDenied.svelte';
|
||||
|
||||
const VIEWS = {
|
||||
Home: Home,
|
||||
Approve: Approve,
|
||||
ShowApproved: ShowApproved,
|
||||
ShowDenied: ShowDenied,
|
||||
};
|
||||
const VIEWS = import.meta.glob('./views/*.svelte', {eager: true});
|
||||
|
||||
window.emit = emit;
|
||||
window.queue = queue;
|
||||
|
||||
var appState = {
|
||||
currentRequest: null,
|
||||
pendingRequests: queue(),
|
||||
credentialStatus: 'locked',
|
||||
}
|
||||
window.appState = appState;
|
||||
|
||||
|
||||
let currentView = Home;
|
||||
function navigate(event) {
|
||||
currentView = VIEWS[event.detail.target];
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
window.invoke = invoke;
|
||||
|
||||
|
||||
var currentView = VIEWS['./views/Home.svelte'].default;
|
||||
window.currentView = currentView;
|
||||
window.VIEWS = VIEWS;
|
||||
function navigate(svelteEvent) {
|
||||
const moduleName = `./views/${svelteEvent.detail.target}.svelte`;
|
||||
currentView = VIEWS[moduleName].default;
|
||||
}
|
||||
|
||||
listen('credentials-request', (event) => {
|
||||
queue.put(1)
|
||||
listen('credentials-request', (tauriEvent) => {
|
||||
appState.pendingRequests.put(tauriEvent.payload);
|
||||
console.log('Received request.');
|
||||
});
|
||||
|
||||
let requests = queue();
|
||||
</script>
|
||||
|
||||
{#if currentView === Home}
|
||||
<svelte:component this={currentView} on:navigate={navigate} {requests} />
|
||||
{:else}
|
||||
<svelte:component this={currentView} on:navigate={navigate} />
|
||||
{/if}
|
||||
<svelte:component this={currentView} on:navigate={navigate} bind:appState={appState} />
|
||||
|
@ -1,10 +1,18 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
|
||||
export let appState;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function approve() {
|
||||
dispatch('navigate', {target: 'ShowApproved'});
|
||||
async function approve() {
|
||||
if (appState.credentialStatus === 'unlocked') {
|
||||
dispatch('navigate', {target: 'ShowApproved'});
|
||||
}
|
||||
else {
|
||||
dispatch('navigate', {target: 'Unlock'});
|
||||
}
|
||||
}
|
||||
|
||||
function deny() {
|
||||
|
@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { onMount, createEventDispatcher } from 'svelte';
|
||||
|
||||
export let appState;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let requests;
|
||||
let r = await requests.get();
|
||||
|
||||
dispatch('navigate', {target: 'Approve.svelte'});
|
||||
onMount(async () => {
|
||||
// will block until a request comes in
|
||||
let req = await appState.pendingRequests.get();
|
||||
console.log(req);
|
||||
appState.currentRequest = req;
|
||||
console.log('Got credentials request from queue.');
|
||||
console.log(appState);
|
||||
dispatch('navigate', {target: 'Approve'});
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1 class="text-4xl text-gray-300">Creddy</h1>
|
@ -1,5 +1,14 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
|
||||
export let appState;
|
||||
|
||||
var p = emit('request-response', {response: 'approved', requestId: 1});
|
||||
console.log('event emitted');
|
||||
console.log(p);
|
||||
appState.currentRequest = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 3000);
|
||||
</script>
|
||||
|
@ -1,5 +1,12 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
|
||||
export let appState;
|
||||
|
||||
emit('request-response', {response: 'denied', requestId: 1});
|
||||
appState.currentRequest = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 3000);
|
||||
</script>
|
||||
|
29
src/views/Unlock.svelte
Normal file
29
src/views/Unlock.svelte
Normal file
@ -0,0 +1,29 @@
|
||||
<script>
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let appState;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let passphrase = '';
|
||||
async function unlock() {
|
||||
console.log('invoking unlock command.')
|
||||
let res = await invoke('unlock', {passphrase});
|
||||
if (res) {
|
||||
appState.credentialStatus = 'unlocked';
|
||||
console.log('Unlock successful!');
|
||||
if (appState.currentRequest) {
|
||||
dispatch('navigate', {target: 'ShowApproved'});
|
||||
}
|
||||
}
|
||||
else {
|
||||
// indicate decryption failed
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form action="#" on:submit|preventDefault="{unlock}">
|
||||
<div class="text-gray-200">Enter your passphrase:</div>
|
||||
<input class="text-gray-200 bg-zinc-800" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" />
|
||||
</form>
|
Loading…
x
Reference in New Issue
Block a user