initial commit
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					dist
 | 
				
			||||||
 | 
					**/node_modules
 | 
				
			||||||
 | 
					src-tauri/target/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# just in case
 | 
				
			||||||
 | 
					credentials*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										25
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8" />
 | 
				
			||||||
 | 
					    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
				
			||||||
 | 
					    <title>Vite + Svelte</title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					      body {
 | 
				
			||||||
 | 
					        margin:  0;
 | 
				
			||||||
 | 
					        display:  grid;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					        justify-items: center;
 | 
				
			||||||
 | 
					        min-width:  100vw;
 | 
				
			||||||
 | 
					        min-height:  100vh;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body class="bg-zinc-800">
 | 
				
			||||||
 | 
					    <div id="app"></div>
 | 
				
			||||||
 | 
					    <script type="module" src="/src/main.js"></script>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										2660
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										22
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "creddy",
 | 
				
			||||||
 | 
					  "version": "0.1.0",
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "dev": "vite",
 | 
				
			||||||
 | 
					    "build": "vite build",
 | 
				
			||||||
 | 
					    "preview": "vite preview",
 | 
				
			||||||
 | 
					    "tauri": "tauri"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@sveltejs/vite-plugin-svelte": "^1.0.1",
 | 
				
			||||||
 | 
					    "@tauri-apps/cli": "^1.0.5",
 | 
				
			||||||
 | 
					    "autoprefixer": "^10.4.8",
 | 
				
			||||||
 | 
					    "postcss": "^8.4.16",
 | 
				
			||||||
 | 
					    "svelte": "^3.49.0",
 | 
				
			||||||
 | 
					    "tailwindcss": "^3.1.8",
 | 
				
			||||||
 | 
					    "vite": "^3.0.7"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@tauri-apps/api": "^1.0.2"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								postcss.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  plugins: {
 | 
				
			||||||
 | 
					    tailwindcss: {},
 | 
				
			||||||
 | 
					    autoprefixer: {},
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3805
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										31
									
								
								src-tauri/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "app"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					description = "A Tauri App"
 | 
				
			||||||
 | 
					authors = ["you"]
 | 
				
			||||||
 | 
					license = ""
 | 
				
			||||||
 | 
					repository = ""
 | 
				
			||||||
 | 
					default-run = "app"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					rust-version = "1.57"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[build-dependencies]
 | 
				
			||||||
 | 
					tauri-build = { version = "1.0.4", features = [] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					serde_json = "1.0"
 | 
				
			||||||
 | 
					serde = { version = "1.0", features = ["derive"] }
 | 
				
			||||||
 | 
					tauri = { version = "1.0.5", features = ["api-all"] }
 | 
				
			||||||
 | 
					sodiumoxide = "0.2.7"
 | 
				
			||||||
 | 
					tokio = { version = ">=1.19", features = ["full"] }
 | 
				
			||||||
 | 
					# futures = ">=0.3.21"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[features]
 | 
				
			||||||
 | 
					# by default Tauri runs in production mode
 | 
				
			||||||
 | 
					# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
 | 
				
			||||||
 | 
					default = [ "custom-protocol" ]
 | 
				
			||||||
 | 
					# this feature is used used for production builds where `devPath` points to the filesystem
 | 
				
			||||||
 | 
					# DO NOT remove this
 | 
				
			||||||
 | 
					custom-protocol = [ "tauri/custom-protocol" ]
 | 
				
			||||||
							
								
								
									
										3
									
								
								src-tauri/build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					  tauri_build::build()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/128x128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/128x128@2x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 974 B  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square107x107Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square142x142Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square150x150Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.9 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square284x284Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 7.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square30x30Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 903 B  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square310x310Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 8.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square44x44Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.3 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square71x71Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.0 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square89x89Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/StoreLogo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/icon.icns
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/icon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 85 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 14 KiB  | 
							
								
								
									
										42
									
								
								src-tauri/src/http/errors.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					use std::fmt::{Display, Formatter};
 | 
				
			||||||
 | 
					use std::convert::From;
 | 
				
			||||||
 | 
					use std::str::Utf8Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// use tokio::sync::oneshot::error::RecvError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Represents errors encountered while handling an HTTP request
 | 
				
			||||||
 | 
					pub enum RequestError {
 | 
				
			||||||
 | 
					    StreamIOError(std::io::Error),
 | 
				
			||||||
 | 
					    InvalidUtf8,
 | 
				
			||||||
 | 
					    MalformedHttpRequest,
 | 
				
			||||||
 | 
					    RequestTooLarge,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<tokio::io::Error> for RequestError {
 | 
				
			||||||
 | 
					    fn from(e: std::io::Error) -> RequestError {
 | 
				
			||||||
 | 
					        RequestError::StreamIOError(e)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					impl From<Utf8Error> for RequestError {
 | 
				
			||||||
 | 
					    fn from(_e: Utf8Error) -> RequestError {
 | 
				
			||||||
 | 
					        RequestError::InvalidUtf8
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// impl From<RecvError> for RequestError {
 | 
				
			||||||
 | 
					//     fn from (_e: RecvError) -> RequestError {
 | 
				
			||||||
 | 
					//         RequestError::
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Display for RequestError {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
 | 
				
			||||||
 | 
					        use RequestError::*;
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            StreamIOError(e) => write!(f, "Stream IO error: {e}"),
 | 
				
			||||||
 | 
					            InvalidUtf8 => write!(f, "Could not decode UTF-8 from bytestream"),
 | 
				
			||||||
 | 
					            MalformedHttpRequest => write!(f, "Maformed HTTP request"),
 | 
				
			||||||
 | 
					            RequestTooLarge => write!(f, "HTTP request too large"),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										99
									
								
								src-tauri/src/http/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					use std::io;
 | 
				
			||||||
 | 
					use std::net::SocketAddrV4;
 | 
				
			||||||
 | 
					use tokio::net::{TcpListener, TcpStream};
 | 
				
			||||||
 | 
					use tokio::io::{AsyncReadExt, AsyncWriteExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use tauri::{AppHandle, Manager};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod errors;
 | 
				
			||||||
 | 
					use errors::RequestError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()> {
 | 
				
			||||||
 | 
					    let listener = TcpListener::bind(&addr).await?;
 | 
				
			||||||
 | 
					    println!("Listening on {addr}");
 | 
				
			||||||
 | 
					    loop {
 | 
				
			||||||
 | 
					        let new_handle = app_handle.app_handle();
 | 
				
			||||||
 | 
					        match listener.accept().await {
 | 
				
			||||||
 | 
					            Ok((stream, _)) => {
 | 
				
			||||||
 | 
					                tokio::spawn(async {
 | 
				
			||||||
 | 
					                    if let Err(e) = handle(stream, new_handle).await {
 | 
				
			||||||
 | 
					                        eprintln!("{e}");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Err(e) => {
 | 
				
			||||||
 | 
					                println!("Error accepting connection: {e}");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// it doesn't really return a String, we just need to placate the compiler
 | 
				
			||||||
 | 
					async fn stall(stream: &mut TcpStream) -> Result<String, tokio::io::Error> {
 | 
				
			||||||
 | 
					    let delay = std::time::Duration::from_secs(1);
 | 
				
			||||||
 | 
					    loop {
 | 
				
			||||||
 | 
					        tokio::time::sleep(delay).await;
 | 
				
			||||||
 | 
					        stream.write(b"x").await?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn handle(mut stream: TcpStream, app_handle: AppHandle) -> Result<(), RequestError> {
 | 
				
			||||||
 | 
					    let mut buf = [0; 8192]; // it's what tokio's BufReader uses
 | 
				
			||||||
 | 
					    let mut n = 0;
 | 
				
			||||||
 | 
					    loop {
 | 
				
			||||||
 | 
					        n += stream.read(&mut buf[n..]).await?;
 | 
				
			||||||
 | 
					        if &buf[(n - 4)..n] == b"\r\n\r\n" {break;}
 | 
				
			||||||
 | 
					        if n == buf.len() {return Err(RequestError::RequestTooLarge);}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    println!("{}", std::str::from_utf8(&buf).unwrap());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stream.write(b"HTTP/1.0 200 OK\r\n").await?;
 | 
				
			||||||
 | 
					    stream.write(b"Content-Type: application/json\r\n").await?;
 | 
				
			||||||
 | 
					    stream.write(b"X-Creddy-delaying-tactic: ").await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let creds = tokio::select!{
 | 
				
			||||||
 | 
					        r = stall(&mut stream) => r?, // this will never return Ok, just Err if it can't write to the stream
 | 
				
			||||||
 | 
					        c = get_creds(&app_handle) => c?,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stream.write(b"\r\nContent-Length: ").await?;
 | 
				
			||||||
 | 
					    stream.write(creds.as_bytes().len().to_string().as_bytes()).await?;
 | 
				
			||||||
 | 
					    stream.write(b"\r\n\r\n").await?;
 | 
				
			||||||
 | 
					    stream.write(creds.as_bytes()).await?;
 | 
				
			||||||
 | 
					    stream.write(b"\r\n\r\n").await?;
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use tokio::io::{stdin, stdout, BufReader, AsyncBufReadExt};
 | 
				
			||||||
 | 
					use crate::storage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use tokio::sync::oneshot;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 (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
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    // Error is only returned if the rx is closed/dropped before receiving, which should never happen
 | 
				
			||||||
 | 
					    let passphrase = rx.await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // let mut passphrase = String::new();
 | 
				
			||||||
 | 
					    // let mut reader = BufReader::new(stdin());
 | 
				
			||||||
 | 
					    // reader.read_line(&mut passphrase).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(storage::load(&passphrase.trim()))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										30
									
								
								src-tauri/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					#![cfg_attr(
 | 
				
			||||||
 | 
					  all(not(debug_assertions), target_os = "windows"),
 | 
				
			||||||
 | 
					  windows_subsystem = "windows"
 | 
				
			||||||
 | 
					)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					// use tokio::runtime::Runtime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod storage;
 | 
				
			||||||
 | 
					mod http;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					  tauri::Builder::default()
 | 
				
			||||||
 | 
					    .setup(|app| {
 | 
				
			||||||
 | 
					      let addr = std::net::SocketAddrV4::from_str("127.0.0.1:12345").unwrap();
 | 
				
			||||||
 | 
					      tauri::async_runtime::spawn(http::serve(addr, app.handle()));
 | 
				
			||||||
 | 
					      Ok(())
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .run(tauri::generate_context!())
 | 
				
			||||||
 | 
					    .expect("error while running tauri application");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // let addr = std::net::SocketAddrV4::from_str("127.0.0.1:12345").unwrap();
 | 
				
			||||||
 | 
					  // let rt = Runtime::new().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // rt.block_on(http::serve(addr)).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // let creds = std::fs::read_to_string("credentials.json").unwrap();
 | 
				
			||||||
 | 
					  // storage::save(&creds, "correct horse battery staple");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								src-tauri/src/storage.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					use sodiumoxide::crypto::{pwhash, secretbox};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn save(data: &str, passphrase: &str) {
 | 
				
			||||||
 | 
					    let salt = pwhash::Salt([0; 32]); // yes yes, just for now
 | 
				
			||||||
 | 
					    let mut kbuf = [0; secretbox::KEYBYTES];
 | 
				
			||||||
 | 
					    pwhash::derive_key_interactive(&mut kbuf, passphrase.as_bytes(), &salt)
 | 
				
			||||||
 | 
					        .expect("Couldn't compute password hash. Are you out of memory?");
 | 
				
			||||||
 | 
					    let key = secretbox::Key(kbuf);
 | 
				
			||||||
 | 
					    let nonce = secretbox::Nonce([0; 24]); // we don't care about e.g. replay attacks so this might be safe?
 | 
				
			||||||
 | 
					    let encrypted = secretbox::seal(data.as_bytes(), &nonce, &key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //todo: store in a database, along with salt, nonce, and hash parameters
 | 
				
			||||||
 | 
					    std::fs::write("credentials.enc", &encrypted).expect("Failed to write file.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //todo: key is automatically zeroed, but we should use 'zeroize' or something to zero out passphrase and data
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn load(passphrase: &str) -> String {
 | 
				
			||||||
 | 
					    let salt = pwhash::Salt([0; 32]);
 | 
				
			||||||
 | 
					    let mut kbuf = [0; secretbox::KEYBYTES];
 | 
				
			||||||
 | 
					    pwhash::derive_key_interactive(&mut kbuf, passphrase.as_bytes(), &salt)
 | 
				
			||||||
 | 
					        .expect("Couldn't compute password hash. Are you out of memory?");
 | 
				
			||||||
 | 
					    let key = secretbox::Key(kbuf);
 | 
				
			||||||
 | 
					    let nonce = secretbox::Nonce([0; 24]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let encrypted = std::fs::read("credentials.enc").expect("Failed to read file.");
 | 
				
			||||||
 | 
					    let decrypted = secretbox::open(&encrypted, &nonce, &key).expect("Failed to decrypt.");
 | 
				
			||||||
 | 
					    String::from_utf8(decrypted).expect("Invalid utf-8")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										66
									
								
								src-tauri/tauri.conf.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "../node_modules/@tauri-apps/cli/schema.json",
 | 
				
			||||||
 | 
					  "build": {
 | 
				
			||||||
 | 
					    "beforeBuildCommand": "npm run build",
 | 
				
			||||||
 | 
					    "beforeDevCommand": "npm run dev",
 | 
				
			||||||
 | 
					    "devPath": "http://localhost:5173",
 | 
				
			||||||
 | 
					    "distDir": "../dist"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "package": {
 | 
				
			||||||
 | 
					    "productName": "creddy",
 | 
				
			||||||
 | 
					    "version": "0.1.0"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "tauri": {
 | 
				
			||||||
 | 
					    "allowlist": {
 | 
				
			||||||
 | 
					      "all": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "bundle": {
 | 
				
			||||||
 | 
					      "active": true,
 | 
				
			||||||
 | 
					      "category": "DeveloperTool",
 | 
				
			||||||
 | 
					      "copyright": "",
 | 
				
			||||||
 | 
					      "deb": {
 | 
				
			||||||
 | 
					        "depends": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "externalBin": [],
 | 
				
			||||||
 | 
					      "icon": [
 | 
				
			||||||
 | 
					        "icons/32x32.png",
 | 
				
			||||||
 | 
					        "icons/128x128.png",
 | 
				
			||||||
 | 
					        "icons/128x128@2x.png",
 | 
				
			||||||
 | 
					        "icons/icon.icns",
 | 
				
			||||||
 | 
					        "icons/icon.ico"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "identifier": "com.tauri.dev",
 | 
				
			||||||
 | 
					      "longDescription": "",
 | 
				
			||||||
 | 
					      "macOS": {
 | 
				
			||||||
 | 
					        "entitlements": null,
 | 
				
			||||||
 | 
					        "exceptionDomain": "",
 | 
				
			||||||
 | 
					        "frameworks": [],
 | 
				
			||||||
 | 
					        "providerShortName": null,
 | 
				
			||||||
 | 
					        "signingIdentity": null
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "resources": [],
 | 
				
			||||||
 | 
					      "shortDescription": "",
 | 
				
			||||||
 | 
					      "targets": "all",
 | 
				
			||||||
 | 
					      "windows": {
 | 
				
			||||||
 | 
					        "certificateThumbprint": null,
 | 
				
			||||||
 | 
					        "digestAlgorithm": "sha256",
 | 
				
			||||||
 | 
					        "timestampUrl": ""
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "security": {
 | 
				
			||||||
 | 
					      "csp": null
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "updater": {
 | 
				
			||||||
 | 
					      "active": false
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "windows": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "fullscreen": false,
 | 
				
			||||||
 | 
					        "height": 600,
 | 
				
			||||||
 | 
					        "resizable": true,
 | 
				
			||||||
 | 
					        "title": "Creddy",
 | 
				
			||||||
 | 
					        "width": 800
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/App.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import { emit, listen } from '@tauri-apps/api/event';
 | 
				
			||||||
 | 
					import Home from './views/Home.svelte';
 | 
				
			||||||
 | 
					import Approve from './views/Approve.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// listen('credentials-request', (event) => {
 | 
				
			||||||
 | 
					//     const passphrase = prompt('Please enter your passphrase:');
 | 
				
			||||||
 | 
					//     emit('passphrase-entered', passphrase);
 | 
				
			||||||
 | 
					// });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let activeComponent = Approve;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svelte:component this={activeComponent} />
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/lib/queue.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					export default function() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        items: [],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        resolvers: []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        put(item) {
 | 
				
			||||||
 | 
					            this.items.push(item);
 | 
				
			||||||
 | 
					            if (resolvers.length > 0) {
 | 
				
			||||||
 | 
					                resolvers.shift().resolve();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        async get() {
 | 
				
			||||||
 | 
					            if (this.items.length === 0) {
 | 
				
			||||||
 | 
					                await new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					                    this.resolvers.push(resolve);
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return this.items.shift();
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					import './style.css';
 | 
				
			||||||
 | 
					import App from './App.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const app = new App({
 | 
				
			||||||
 | 
					  target: document.getElementById('app')
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default app;
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					@tailwind base;
 | 
				
			||||||
 | 
					@tailwind components;
 | 
				
			||||||
 | 
					@tailwind utilities;
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/views/Approve.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import check_circle from '../assets/check-circle.svg?raw';
 | 
				
			||||||
 | 
					    import x_circle from '../assets/x-circle.svg?raw';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dispatch = createEventDispatcher();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h2 class="text-3xl text-gray-200">An application would like to access your AWS credentials.</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<button on:click={() => dispatch('response', 'approved')}>
 | 
				
			||||||
 | 
					    <svg class="w-32 stroke-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1">
 | 
				
			||||||
 | 
					      <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
 | 
				
			||||||
 | 
					    </svg>
 | 
				
			||||||
 | 
					</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<button on:click={() => dispatch('response', 'denied')}>
 | 
				
			||||||
 | 
					    <svg class="w-32 stroke-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1">
 | 
				
			||||||
 | 
					        <path stroke-linecap="round" stroke-linejoin="round" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
 | 
				
			||||||
 | 
					    </svg>
 | 
				
			||||||
 | 
					</button>
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/views/Home.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<h1 class="text-4xl text-gray-300">Creddy</h1>
 | 
				
			||||||
							
								
								
									
										11
									
								
								tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					/** @type {import('tailwindcss').Config} */
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  content: [
 | 
				
			||||||
 | 
					    "./index.html",
 | 
				
			||||||
 | 
					    "./src/**/*.{svelte,html}",
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  theme: {
 | 
				
			||||||
 | 
					    extend: {},
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  plugins: [],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								vite.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					import { defineConfig } from 'vite'
 | 
				
			||||||
 | 
					import { svelte } from '@sveltejs/vite-plugin-svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://vitejs.dev/config/
 | 
				
			||||||
 | 
					export default defineConfig({
 | 
				
			||||||
 | 
					  plugins: [svelte()]
 | 
				
			||||||
 | 
					})
 | 
				
			||||||