Compare commits
6 Commits
v0.4.0
...
0d9cbc62cc
Author | SHA1 | Date | |
---|---|---|---|
0d9cbc62cc | |||
dd40eb379e | |||
13545ac725 | |||
040a01536a | |||
4e2a90b15b | |||
e0d919ed4a |
1854
package-lock.json
generated
1854
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "creddy",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
|
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@ -1035,7 +1035,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "creddy"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"auto-launch",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "creddy"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
description = "A friendly AWS credentials manager"
|
||||
authors = ["Joseph Montanaro"]
|
||||
license = ""
|
||||
|
@ -108,6 +108,10 @@ async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
|
||||
setup_errors.push("Failed to register hotkeys. Hotkey settings have been disabled.".into());
|
||||
}
|
||||
|
||||
let desktop_is_gnome = std::env::var("XDG_CURRENT_DESKTOP")
|
||||
.map(|names| names.split(':').any(|n| n == "GNOME"))
|
||||
.unwrap_or(false);
|
||||
|
||||
// if session is empty, this is probably the first launch, so don't autohide
|
||||
if !conf.start_minimized || is_first_launch {
|
||||
app.get_window("main")
|
||||
@ -115,7 +119,7 @@ async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
|
||||
.show()?;
|
||||
}
|
||||
|
||||
let state = AppState::new(conf, session, pool, setup_errors);
|
||||
let state = AppState::new(conf, session, pool, setup_errors, desktop_is_gnome);
|
||||
app.manage(state);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -34,59 +34,3 @@ pub fn get_process_parent_info(pid: u32) -> Result<Client, ClientInfoError> {
|
||||
|
||||
Ok(Client { pid: parent_pid_sys.as_u32(), exe })
|
||||
}
|
||||
|
||||
|
||||
// async fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::error::Error> {
|
||||
// let state = APP.get().unwrap().state::<AppState>();
|
||||
// let AppConfig {
|
||||
// listen_addr: app_listen_addr,
|
||||
// listen_port: app_listen_port,
|
||||
// ..
|
||||
// } = *state.config.read().await;
|
||||
|
||||
// let sockets_iter = netstat2::iterate_sockets_info(
|
||||
// AddressFamilyFlags::IPV4,
|
||||
// ProtocolFlags::TCP
|
||||
// )?;
|
||||
// for item in sockets_iter {
|
||||
// let sock_info = item?;
|
||||
// let proto_info = match sock_info.protocol_socket_info {
|
||||
// ProtocolSocketInfo::Tcp(tcp_info) => tcp_info,
|
||||
// ProtocolSocketInfo::Udp(_) => {continue;}
|
||||
// };
|
||||
|
||||
// if proto_info.local_port == local_port
|
||||
// && proto_info.remote_port == app_listen_port
|
||||
// && proto_info.local_addr == app_listen_addr
|
||||
// && proto_info.remote_addr == app_listen_addr
|
||||
// {
|
||||
// return Ok(sock_info.associated_pids)
|
||||
// }
|
||||
// }
|
||||
// Ok(vec![])
|
||||
// }
|
||||
|
||||
|
||||
// Theoretically, on some systems, multiple processes can share a socket
|
||||
// pub async fn get_clients(local_port: u16) -> Result<Vec<Option<Client>>, ClientInfoError> {
|
||||
// let mut clients = Vec::new();
|
||||
// let mut sys = System::new();
|
||||
// for p in get_associated_pids(local_port).await? {
|
||||
// let pid = Pid::from_u32(p);
|
||||
// sys.refresh_process(pid);
|
||||
// let proc = sys.process(pid)
|
||||
// .ok_or(ClientInfoError::ProcessNotFound)?;
|
||||
|
||||
// let client = Client {
|
||||
// pid: p,
|
||||
// exe: proc.exe().to_path_buf(),
|
||||
// };
|
||||
// clients.push(Some(client));
|
||||
// }
|
||||
|
||||
// if clients.is_empty() {
|
||||
// clients.push(None);
|
||||
// }
|
||||
|
||||
// Ok(clients)
|
||||
// }
|
||||
|
@ -18,6 +18,7 @@ use tauri::api::dialog::{
|
||||
MessageDialogBuilder,
|
||||
MessageDialogKind,
|
||||
};
|
||||
use tokio::sync::oneshot::error::RecvError;
|
||||
use serde::{
|
||||
Serialize,
|
||||
Serializer,
|
||||
@ -164,7 +165,7 @@ pub enum HandlerError {
|
||||
#[error("HTTP request too large")]
|
||||
RequestTooLarge,
|
||||
#[error("Internal server error")]
|
||||
Internal,
|
||||
Internal(#[from] RecvError),
|
||||
#[error("Error accessing credentials: {0}")]
|
||||
NoCredentials(#[from] GetCredentialsError),
|
||||
#[error("Error getting client details: {0}")]
|
||||
|
@ -21,6 +21,7 @@ pub struct AwsRequestNotification {
|
||||
pub struct RequestResponse {
|
||||
pub id: u64,
|
||||
pub approval: Approval,
|
||||
pub base: bool,
|
||||
}
|
||||
|
||||
|
||||
|
@ -97,9 +97,10 @@ async fn get_aws_credentials(base: bool, client: Client, app_handle: AppHandle)
|
||||
let notification = AwsRequestNotification {id: request_id, client, base};
|
||||
app_handle.emit_all("credentials-request", ¬ification)?;
|
||||
|
||||
match chan_recv.await {
|
||||
Ok(Approval::Approved) => {
|
||||
if base {
|
||||
let response = chan_recv.await?;
|
||||
match response.approval {
|
||||
Approval::Approved => {
|
||||
if response.base {
|
||||
let creds = state.base_creds_cloned().await?;
|
||||
Ok(Response::Aws(Credentials::Base(creds)))
|
||||
}
|
||||
@ -108,8 +109,7 @@ async fn get_aws_credentials(base: bool, client: Client, app_handle: AppHandle)
|
||||
Ok(Response::Aws(Credentials::Session(creds)))
|
||||
}
|
||||
},
|
||||
Ok(Approval::Denied) => Err(HandlerError::Denied),
|
||||
Err(_e) => Err(HandlerError::Internal),
|
||||
Approval::Denied => Err(HandlerError::Denied),
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
use tokio::{
|
||||
net::windows::named_pipe::{
|
||||
NamedPipeServer,
|
||||
ServerOptions,
|
||||
},
|
||||
sync::oneshot,
|
||||
use tokio::net::windows::named_pipe::{
|
||||
NamedPipeServer,
|
||||
ServerOptions,
|
||||
};
|
||||
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
use windows::Win32:: {
|
||||
Foundation::HANDLE,
|
||||
System::Pipes::GetNamedPipeClientProcessId,
|
||||
@ -52,11 +51,11 @@ impl Server {
|
||||
|
||||
// create a new pipe instance to listen for the next client, and swap it in
|
||||
let new_listener = ServerOptions::new().create(r"\\.\pipe\creddy-requests")?;
|
||||
let mut stream = std::mem::replace(&mut self.listener, new_listener);
|
||||
let stream = std::mem::replace(&mut self.listener, new_listener);
|
||||
let new_handle = self.app_handle.app_handle();
|
||||
let client_pid = get_client_pid(&stream)?;
|
||||
rt::spawn(async move {
|
||||
super::handle(stream, app_handle)
|
||||
super::handle(stream, new_handle, client_pid)
|
||||
.await
|
||||
.error_print_prefix("Error responding to request: ");
|
||||
});
|
||||
@ -71,5 +70,5 @@ fn get_client_pid(pipe: &NamedPipeServer) -> Result<u32, ClientInfoError> {
|
||||
let mut pid = 0u32;
|
||||
let handle = HANDLE(raw_handle as _);
|
||||
unsafe { GetNamedPipeClientProcessId(handle, &mut pid as *mut u32)? };
|
||||
pid
|
||||
Ok(pid)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ use crate::credentials::{
|
||||
SessionCredentials,
|
||||
};
|
||||
use crate::{config, config::AppConfig};
|
||||
use crate::ipc::{self, Approval};
|
||||
use crate::ipc::{self, Approval, RequestResponse};
|
||||
use crate::errors::*;
|
||||
use crate::shortcuts;
|
||||
|
||||
@ -39,13 +39,22 @@ impl Visibility {
|
||||
.ok_or(WindowError::NoMainWindow)?;
|
||||
|
||||
self.leases += 1;
|
||||
// `original` represents the visibility of the window before any leases were acquired
|
||||
// None means we don't know, Some(false) means it was previously hidden,
|
||||
// Some(true) means it was previously visible
|
||||
if self.original.is_none() {
|
||||
let is_visible = window.is_visible()?;
|
||||
self.original = Some(is_visible);
|
||||
if !is_visible {
|
||||
window.show()?;
|
||||
}
|
||||
}
|
||||
|
||||
let state = app.state::<AppState>();
|
||||
if matches!(self.original, Some(true)) && state.desktop_is_gnome {
|
||||
// Gnome has a really annoying "focus-stealing prevention" behavior means we
|
||||
// can't just pop up when the window is already visible, so to work around it
|
||||
// we hide and then immediately unhide the window
|
||||
window.hide()?;
|
||||
}
|
||||
window.show()?;
|
||||
window.set_focus()?;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -93,10 +102,11 @@ pub struct AppState {
|
||||
pub config: RwLock<AppConfig>,
|
||||
pub session: RwLock<Session>,
|
||||
pub request_count: RwLock<u64>,
|
||||
pub waiting_requests: RwLock<HashMap<u64, Sender<Approval>>>,
|
||||
pub waiting_requests: RwLock<HashMap<u64, Sender<RequestResponse>>>,
|
||||
pub pending_terminal_request: RwLock<bool>,
|
||||
// setup_errors is never modified and so doesn't need to be wrapped in RwLock
|
||||
// these are never modified and so don't need to be wrapped in RwLocks
|
||||
pub setup_errors: Vec<String>,
|
||||
pub desktop_is_gnome: bool,
|
||||
pool: sqlx::SqlitePool,
|
||||
visibility: RwLock<Visibility>,
|
||||
}
|
||||
@ -107,6 +117,7 @@ impl AppState {
|
||||
session: Session,
|
||||
pool: SqlitePool,
|
||||
setup_errors: Vec<String>,
|
||||
desktop_is_gnome: bool,
|
||||
) -> AppState {
|
||||
AppState {
|
||||
config: RwLock::new(config),
|
||||
@ -115,6 +126,7 @@ impl AppState {
|
||||
waiting_requests: RwLock::new(HashMap::new()),
|
||||
pending_terminal_request: RwLock::new(false),
|
||||
setup_errors,
|
||||
desktop_is_gnome,
|
||||
pool,
|
||||
visibility: RwLock::new(Visibility::new()),
|
||||
}
|
||||
@ -149,7 +161,7 @@ impl AppState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn register_request(&self, sender: Sender<Approval>) -> u64 {
|
||||
pub async fn register_request(&self, sender: Sender<RequestResponse>) -> u64 {
|
||||
let count = {
|
||||
let mut c = self.request_count.write().await;
|
||||
*c += 1;
|
||||
@ -181,7 +193,7 @@ impl AppState {
|
||||
waiting_requests
|
||||
.remove(&response.id)
|
||||
.ok_or(SendResponseError::NotFound)?
|
||||
.send(response.approval)
|
||||
.send(response)
|
||||
.map_err(|_| SendResponseError::Abandoned)
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "creddy",
|
||||
"version": "0.4.0"
|
||||
"version": "0.4.1"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
@ -5,3 +5,8 @@
|
||||
.btn-alert-error {
|
||||
@apply bg-transparent hover:bg-[#cd5a5a] border border-error-content text-error-content
|
||||
}
|
||||
|
||||
/* I like alert icons to be top-aligned */
|
||||
.alert > :where(*) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
@ -11,10 +11,11 @@
|
||||
|
||||
// Send response to backend, display error if applicable
|
||||
let error, alert;
|
||||
let base = $appState.currentRequest.base;
|
||||
async function respond() {
|
||||
let {id, approval} = $appState.currentRequest;
|
||||
try {
|
||||
await invoke('respond', {response: {id, approval}});
|
||||
await invoke('respond', {response: {id, approval, base}});
|
||||
navigate('ShowResponse');
|
||||
}
|
||||
catch (e) {
|
||||
@ -83,7 +84,7 @@
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
<span>
|
||||
WARNING: This application is requesting your base (long-lived) AWS credentials.
|
||||
WARNING: This application is requesting your long-lived AWS credentials.
|
||||
These credentials are less secure than session credentials, since they don't expire automatically.
|
||||
</span>
|
||||
</div>
|
||||
@ -116,5 +117,12 @@
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<label class="label cursor-pointer justify-end gap-x-2">
|
||||
<span class="label-text">Send long-lived credentials</span>
|
||||
<input type="checkbox" class="checkbox checkbox-success" bind:checked={base}>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -39,8 +39,8 @@
|
||||
Launch Terminal
|
||||
</button>
|
||||
<label class="label cursor-pointer flex items-center space-x-2">
|
||||
<span class="label-text">Launch with long-lived credentials</span>
|
||||
<input type="checkbox" class="checkbox checkbox-sm" bind:checked={launchBase}>
|
||||
<span class="label-text">Launch with base credentials</span>
|
||||
</label>
|
||||
|
||||
{:else if status === 'empty'}
|
||||
|
Reference in New Issue
Block a user