Compare commits
1 Commits
wincon
...
663bbfa2f3
Author | SHA1 | Date | |
---|---|---|---|
663bbfa2f3 |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "creddy",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
|
95
src-tauri/Cargo.lock
generated
95
src-tauri/Cargo.lock
generated
@ -68,6 +68,36 @@ version = "1.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
|
||||
[[package]]
|
||||
name = "app"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"auto-launch",
|
||||
"aws-config",
|
||||
"aws-sdk-sts",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"chacha20poly1305",
|
||||
"clap",
|
||||
"dirs 5.0.1",
|
||||
"is-terminal",
|
||||
"netstat2",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sodiumoxide",
|
||||
"sqlx",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"sysinfo",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-single-instance",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.0"
|
||||
@ -945,37 +975,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "creddy"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"auto-launch",
|
||||
"aws-config",
|
||||
"aws-sdk-sts",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"chacha20poly1305",
|
||||
"clap",
|
||||
"dirs 5.0.1",
|
||||
"gag",
|
||||
"netstat2",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sodiumoxide",
|
||||
"sqlx",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"sysinfo",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-single-instance",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"windows 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
@ -1348,17 +1347,6 @@ dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filedescriptor"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"thiserror",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.21"
|
||||
@ -1541,16 +1529,6 @@ dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gag"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a713bee13966e9fbffdf7193af71d54a6b35a0bb34997cd6c9519ebeb5005972"
|
||||
dependencies = [
|
||||
"filedescriptor",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdk"
|
||||
version = "0.15.4"
|
||||
@ -2121,6 +2099,18 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
@ -4019,7 +4009,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe7e0f1d535e7cbbbab43c82be4fc992b84f9156c16c160955617e0260ebc449"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"cocoa",
|
||||
"dirs-next",
|
||||
"embed_plist",
|
||||
|
@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "creddy"
|
||||
version = "0.2.2"
|
||||
description = "A friendly AWS credentials manager"
|
||||
authors = ["Joseph Montanaro"]
|
||||
name = "app"
|
||||
version = "0.2.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "creddy"
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
rust-version = "1.57"
|
||||
|
||||
@ -17,7 +17,7 @@ tauri-build = { version = "1.0.4", features = [] }
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.2", features = ["cli", "dialog", "os-all", "system-tray"] }
|
||||
tauri = { version = "1.2", features = ["dialog", "os-all", "system-tray"] }
|
||||
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
||||
sodiumoxide = "0.2.7"
|
||||
tokio = { version = ">=1.19", features = ["full"] }
|
||||
@ -35,11 +35,10 @@ strum_macros = "0.24"
|
||||
auto-launch = "0.4.0"
|
||||
dirs = "5.0"
|
||||
clap = { version = "3.2.23", features = ["derive"] }
|
||||
# is-terminal = "0.4.7"
|
||||
argon2 = { version = "0.5.0", features = ["std"] }
|
||||
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||
windows = { version = "0.48", features = ["Win32_Foundation", "Win32_System_Console"] }
|
||||
gag = "1.0"
|
||||
is-terminal = "0.4.7"
|
||||
argon2 = "0.5.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
generic_array = "0.14.6"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
|
@ -13,6 +13,7 @@ use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
};
|
||||
|
||||
|
||||
use crate::app;
|
||||
use crate::config::AppConfig;
|
||||
use crate::credentials::{BaseCredentials, SessionCredentials};
|
||||
@ -22,16 +23,6 @@ use crate::errors::*;
|
||||
pub fn parser() -> Command<'static> {
|
||||
Command::new("creddy")
|
||||
.about("A friendly AWS credentials manager")
|
||||
// we don't want the default help handling because it early-exits,
|
||||
// and on Windows we need to setup the console before we can output
|
||||
.disable_help_flag(true)
|
||||
.arg(
|
||||
Arg::new("help")
|
||||
.short('h')
|
||||
.long("help")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Print this message or the help of the given subcommand(s)")
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("run")
|
||||
.about("Launch Creddy")
|
||||
@ -98,11 +89,7 @@ pub fn exec(args: &ArgMatches) -> Result<(), CliError> {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let e = cmd.exec(); // never returns if successful
|
||||
Err(ExecError::ExecutionFailed(e))?;
|
||||
Ok(())
|
||||
}
|
||||
cmd.exec().map_err(|e| ExecError::ExecutionFailed(e))?;
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
|
@ -2,6 +2,7 @@ use std::net::Ipv4Addr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use auto_launch::AutoLaunchBuilder;
|
||||
use is_terminal::IsTerminal;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
@ -95,7 +96,7 @@ pub fn get_or_create_db_path() -> Result<PathBuf, DataDirError> {
|
||||
path.push("Creddy");
|
||||
|
||||
std::fs::create_dir_all(&path)?;
|
||||
if cfg!(debug_assertions) {
|
||||
if cfg!(debug_assertions) && std::io::stdout().is_terminal() {
|
||||
path.push("creddy.dev.db");
|
||||
}
|
||||
else {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::fmt::{self, Formatter};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use aws_smithy_types::date_time::{DateTime, Format};
|
||||
use aws_smithy_types::date_time::{DateTime, Format};
|
||||
use argon2::{
|
||||
Argon2,
|
||||
Algorithm,
|
||||
@ -15,6 +15,7 @@ use chacha20poly1305::{
|
||||
aead::{
|
||||
Aead,
|
||||
AeadCore,
|
||||
Key,
|
||||
KeyInit,
|
||||
Error as AeadError,
|
||||
generic_array::GenericArray,
|
||||
@ -53,17 +54,18 @@ impl Session {
|
||||
None => {return Ok(Session::Empty);}
|
||||
};
|
||||
|
||||
let salt: [u8; 32] = row.salt
|
||||
let salt_buf: [u8; 32] = row.salt
|
||||
.try_into()
|
||||
.map_err(|_e| SetupError::InvalidRecord)?;
|
||||
let nonce_buf: [u8; 24] = row.nonce
|
||||
.try_into()
|
||||
.map_err(|_e| SetupError::InvalidRecord)?;
|
||||
let nonce = XNonce::from_exact_iter(row.nonce.into_iter())
|
||||
.ok_or(SetupError::InvalidRecord)?;
|
||||
|
||||
let creds = LockedCredentials {
|
||||
access_key_id: row.access_key_id,
|
||||
secret_key_enc: row.secret_key_enc,
|
||||
salt,
|
||||
nonce,
|
||||
salt: Salt(salt_buf),
|
||||
nonce: Nonce(nonce_buf),
|
||||
};
|
||||
Ok(Session::Locked(creds))
|
||||
}
|
||||
@ -100,8 +102,8 @@ impl LockedCredentials {
|
||||
)
|
||||
.bind(&self.access_key_id)
|
||||
.bind(&self.secret_key_enc)
|
||||
.bind(&self.salt[..])
|
||||
.bind(&self.nonce[..])
|
||||
.bind(&self.salt.0[0..])
|
||||
.bind(&self.nonce.0[0..])
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
@ -109,10 +111,9 @@ impl LockedCredentials {
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, passphrase: &str) -> Result<BaseCredentials, UnlockError> {
|
||||
let crypto = Crypto::new(passphrase, &self.salt)
|
||||
.map_err(|e| CryptoError::Argon2(e))?;
|
||||
let crypto = Crypto::new(passphrase, &self.salt);
|
||||
let decrypted = crypto.decrypt(&self.nonce, &self.secret_key_enc)
|
||||
.map_err(|e| CryptoError::Aead(e))?;
|
||||
.map_err(|_| UnlockError::BadPassphrase)?;
|
||||
let secret_access_key = String::from_utf8(decrypted)
|
||||
.map_err(|_| UnlockError::InvalidUtf8)?;
|
||||
|
||||
@ -133,10 +134,10 @@ pub struct BaseCredentials {
|
||||
}
|
||||
|
||||
impl BaseCredentials {
|
||||
pub fn encrypt(&self, passphrase: &str) -> Result<LockedCredentials, CryptoError> {
|
||||
pub fn encrypt(&self, passphrase: &str) -> Result<CryptoError, LockedCredentials> {
|
||||
let salt = Crypto::salt();
|
||||
let crypto = Crypto::new(passphrase, &salt)?;
|
||||
let (nonce, secret_key_enc) = crypto.encrypt(self.secret_access_key.as_bytes())?;
|
||||
let (nonce, secret_key_enc) = crypto.encrypt(self.secret_access_key.as_bytes());
|
||||
|
||||
let locked = LockedCredentials {
|
||||
access_key_id: self.access_key_id.clone(),
|
||||
@ -270,24 +271,11 @@ impl Crypto {
|
||||
/// a key on my (somewhat older) CPU. This is probably overkill, but
|
||||
/// given that it should only have to happen ~once a day for most
|
||||
/// usage, it should be acceptable.
|
||||
#[cfg(not(debug_assertions))]
|
||||
const MEM_COST: u32 = 128 * 1024;
|
||||
#[cfg(not(debug_assertions))]
|
||||
const TIME_COST: u32 = 8;
|
||||
|
||||
/// But since this takes a million years without optimizations,
|
||||
/// we turn it way down in debug builds.
|
||||
#[cfg(debug_assertions)]
|
||||
const MEM_COST: u32 = 48 * 1024;
|
||||
#[cfg(debug_assertions)]
|
||||
const TIME_COST: u32 = 1;
|
||||
|
||||
|
||||
fn new(passphrase: &str, salt: &[u8]) -> argon2::Result<Crypto> {
|
||||
let params = ParamsBuilder::new()
|
||||
.m_cost(Self::MEM_COST)
|
||||
.m_cost(128 * 1024)
|
||||
.p_cost(1)
|
||||
.t_cost(Self::TIME_COST)
|
||||
.t_cost(8)
|
||||
.build()
|
||||
.unwrap(); // only errors if the given params are invalid
|
||||
|
||||
|
@ -164,8 +164,8 @@ pub enum UnlockError {
|
||||
NotLocked,
|
||||
#[error("No saved credentials were found")]
|
||||
NoCredentials,
|
||||
#[error(transparent)]
|
||||
Crypto(#[from] CryptoError),
|
||||
#[error("Invalid passphrase")]
|
||||
BadPassphrase,
|
||||
#[error("Data was found to be corrupt after decryption")]
|
||||
InvalidUtf8, // Somehow we got invalid utf-8 even though decryption succeeded
|
||||
#[error("Database error: {0}")]
|
||||
@ -175,15 +175,6 @@ pub enum UnlockError {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum CryptoError {
|
||||
#[error(transparent)]
|
||||
Argon2(#[from] argon2::Error),
|
||||
#[error("Invalid passphrase")] // I think this is the only way decryption fails
|
||||
Aead(#[from] chacha20poly1305::aead::Error),
|
||||
}
|
||||
|
||||
|
||||
// Errors encountered while trying to figure out who's on the other end of a request
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum ClientInfoError {
|
||||
|
@ -3,20 +3,6 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
#[cfg(windows)]
|
||||
use {
|
||||
std::fs::File,
|
||||
std::os::windows::io::FromRawHandle,
|
||||
std::os::raw::c_void,
|
||||
gag::Redirect,
|
||||
windows::Win32::System::Console::{
|
||||
AllocConsole,
|
||||
AttachConsole,
|
||||
GetStdHandle,
|
||||
STD_OUTPUT_HANDLE,
|
||||
STD_ERROR_HANDLE,
|
||||
}
|
||||
};
|
||||
|
||||
mod app;
|
||||
mod cli;
|
||||
@ -29,40 +15,18 @@ mod state;
|
||||
mod server;
|
||||
mod tray;
|
||||
|
||||
|
||||
use crate::errors::ErrorPopup;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
fn main() {
|
||||
let args = cli::parser().get_matches();
|
||||
let help = matches!(args.get_one::<bool>("help"), Some(true));
|
||||
|
||||
// This is the only case that doesn't need a console
|
||||
if let None | Some(("run", _)) = args.subcommand() {
|
||||
if !help {
|
||||
let res = match cli::parser().get_matches().subcommand() {
|
||||
None | Some(("run", _)) => {
|
||||
app::run().error_popup("Creddy failed to start");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// on Windows, need to do the whole allocate-a-console thing
|
||||
#[cfg(windows)]
|
||||
attach_console();
|
||||
// let (out, err) = setup_console();
|
||||
println!("Testing stdout");
|
||||
// writeln!(&mut out, "Testing allocated file");
|
||||
|
||||
if help {
|
||||
// if we can't print help, we can't print an error, so just panic
|
||||
dbg!(args.get_one::<bool>("help"));
|
||||
cli::parser().print_help().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||
return;
|
||||
}
|
||||
|
||||
let res = match args.subcommand() {
|
||||
Ok(())
|
||||
},
|
||||
Some(("show", m)) => cli::show(m),
|
||||
Some(("exec", m)) => cli::exec(m),
|
||||
// clap ensures that subcommand is either None or run/show/exec
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
@ -70,43 +34,3 @@ fn main() {
|
||||
eprintln!("Error: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn attach_console() {
|
||||
unsafe { AttachConsole(u32::MAX); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn setup_console() -> (Redirect<File>, Redirect<File>) {
|
||||
let (mut stdout, stderr) = unsafe {
|
||||
AllocConsole();
|
||||
// if we can't get handles to stdout/err, we can't display these errors,
|
||||
// so there's no point in doing anything other than panicking
|
||||
let stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE).unwrap();
|
||||
let stderr_handle = GetStdHandle(STD_ERROR_HANDLE).unwrap();
|
||||
|
||||
let mut f = File::create("C:\\Users\\Joe\\Downloads\\debug.txt").unwrap();
|
||||
writeln!(&mut f, "{stdout_handle:?}\n{:?}", stdout_handle.0 as *mut c_void).unwrap();
|
||||
|
||||
(
|
||||
File::from_raw_handle(stdout_handle.0 as *mut c_void),
|
||||
File::from_raw_handle(stderr_handle.0 as *mut c_void),
|
||||
)
|
||||
};
|
||||
|
||||
writeln!(&mut stdout, "Testing stdout before redirect")
|
||||
.map_err(|e| log_err(e))
|
||||
.unwrap();
|
||||
|
||||
(
|
||||
Redirect::stdout(stdout).unwrap(),
|
||||
Redirect::stderr(stderr).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn log_err(e: impl std::error::Error) {
|
||||
let mut f = File::create("C:\\Users\\Joe\\Downloads\\log.txt").unwrap();
|
||||
writeln!(&mut f, "{e}").unwrap();
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
pub async fn new_creds(&self, base_creds: BaseCredentials, passphrase: &str) -> Result<(), UnlockError> {
|
||||
let locked = base_creds.encrypt(passphrase)?;
|
||||
let locked = base_creds.encrypt(passphrase);
|
||||
// do this first so that if it fails we don't save bad credentials
|
||||
self.new_session(base_creds).await?;
|
||||
locked.save(&self.pool).await?;
|
||||
|
@ -7,8 +7,8 @@
|
||||
"distDir": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "Creddy",
|
||||
"version": "0.2.2"
|
||||
"productName": "creddy",
|
||||
"version": "0.2.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
@ -47,14 +47,6 @@
|
||||
"timestampUrl": ""
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"description": "A friendly AWS credentials manager",
|
||||
"subcommands": {
|
||||
"run": {
|
||||
"description": "Launch Creddy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"csp": {
|
||||
"default-src": ["'self'"],
|
||||
|
@ -15,6 +15,7 @@ invoke('get_config').then(config => $appState.config = config);
|
||||
listen('credentials-request', (tauriEvent) => {
|
||||
$appState.pendingRequests.put(tauriEvent.payload);
|
||||
});
|
||||
window.state = $appState;
|
||||
|
||||
acceptRequest();
|
||||
</script>
|
||||
|
@ -1,113 +0,0 @@
|
||||
<script>
|
||||
export let color = 'base-content';
|
||||
export let thickness = '2px';
|
||||
let classes = '';
|
||||
export { classes as class };
|
||||
|
||||
const colorVars = {
|
||||
'primary': 'p',
|
||||
'primary-focus': 'pf',
|
||||
'primary-content': 'pc',
|
||||
'secondary': 's',
|
||||
'secondary-focus': 'sf',
|
||||
'secondary-content': 'sc',
|
||||
'accent': 'a',
|
||||
'accent-focus': 'af',
|
||||
'accent-content': 'ac',
|
||||
'neutral': 'n',
|
||||
'neutral-focus': 'nf',
|
||||
'neutral-content': 'nc',
|
||||
'base-100': 'b1',
|
||||
'base-200': 'b2',
|
||||
'base-300': 'b3',
|
||||
'base-content': 'bc',
|
||||
'info': 'in',
|
||||
'info-content': 'inc',
|
||||
'success': 'su',
|
||||
'success-content': 'suc',
|
||||
'warning': 'wa',
|
||||
'warning-content': 'wac',
|
||||
'error': 'er',
|
||||
'error-content': 'erc',
|
||||
}
|
||||
|
||||
let arcStyle = `border-width: ${thickness};`;
|
||||
arcStyle += `border-color: hsl(var(--${colorVars[color]})) transparent transparent transparent;`;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#spinner {
|
||||
position: relative;
|
||||
|
||||
animation: spin;
|
||||
animation-duration: 1.5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
50% { transform: rotate(225deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.arc {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.arc-top {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.arc-right {
|
||||
animation: spin-right;
|
||||
animation-duration: 3s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.arc-bottom {
|
||||
animation: spin-bottom;
|
||||
animation-duration: 3s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.arc-left {
|
||||
animation: spin-left;
|
||||
animation-duration: 3s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@keyframes spin-top {
|
||||
0% { transform: rotate(-45deg); }
|
||||
50% { transform: rotate(315deg); }
|
||||
100% { transform: rotate(-45deg); }
|
||||
}
|
||||
|
||||
@keyframes spin-right {
|
||||
0% { transform: rotate(45deg); }
|
||||
50% { transform: rotate(315deg); }
|
||||
100% { transform: rotate(405deg); }
|
||||
}
|
||||
|
||||
@keyframes spin-bottom {
|
||||
0% { transform: rotate(135deg); }
|
||||
50% { transform: rotate(315deg); }
|
||||
100% { transform: rotate(495deg); }
|
||||
}
|
||||
|
||||
@keyframes spin-left {
|
||||
0% { transform: rotate(225deg); }
|
||||
50% { transform: rotate(315deg); }
|
||||
100% { transform: rotate(585deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div id="spinner" class="w-6 h-6 {classes}">
|
||||
<div class="arc arc-top w-full h-full" style={arcStyle}></div>
|
||||
<div class="arc arc-right w-full h-full" style={arcStyle}></div>
|
||||
<div class="arc arc-bottom w-full h-full" style={arcStyle}></div>
|
||||
<div class="arc arc-left w-full h-full" style={arcStyle}></div>
|
||||
</div>
|
@ -7,7 +7,6 @@
|
||||
import { navigate } from '../lib/routing.js';
|
||||
import Link from '../ui/Link.svelte';
|
||||
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
||||
import Spinner from '../ui/Spinner.svelte';
|
||||
|
||||
|
||||
let errorMsg = null;
|
||||
@ -20,7 +19,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
let saving = false;
|
||||
async function save() {
|
||||
if (passphrase !== confirmPassphrase) {
|
||||
alert.shake();
|
||||
@ -29,7 +27,6 @@
|
||||
|
||||
let credentials = {AccessKeyId, SecretAccessKey};
|
||||
try {
|
||||
saving = true;
|
||||
await invoke('save_credentials', {credentials, passphrase});
|
||||
if ($appState.currentRequest) {
|
||||
navigate('Approve');
|
||||
@ -50,8 +47,6 @@
|
||||
if (alert) {
|
||||
alert.shake();
|
||||
}
|
||||
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -70,13 +65,7 @@
|
||||
<input type="password" placeholder="Passphrase" bind:value="{passphrase}" class="input input-bordered" />
|
||||
<input type="password" placeholder="Re-enter passphrase" bind:value={confirmPassphrase} class="input input-bordered" on:change={confirm} />
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{#if saving}
|
||||
<Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
|
||||
{:else}
|
||||
Submit
|
||||
{/if}
|
||||
</button>
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
<Link target="Home" hotkey="Escape">
|
||||
<button class="btn btn-sm btn-outline w-full">Cancel</button>
|
||||
</Link>
|
||||
|
@ -7,14 +7,12 @@
|
||||
import { getRootCause } from '../lib/errors.js';
|
||||
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
||||
import Link from '../ui/Link.svelte';
|
||||
import Spinner from '../ui/Spinner.svelte';
|
||||
|
||||
|
||||
let errorMsg = null;
|
||||
let alert;
|
||||
let passphrase = '';
|
||||
let loadTime = 0;
|
||||
let saving = false;
|
||||
async function unlock() {
|
||||
// The hotkey for navigating here from homepage is Enter, which also
|
||||
// happens to trigger the form submit event
|
||||
@ -23,7 +21,6 @@
|
||||
}
|
||||
|
||||
try {
|
||||
saving = true;
|
||||
let r = await invoke('unlock', {passphrase});
|
||||
$appState.credentialStatus = 'unlocked';
|
||||
if ($appState.currentRequest) {
|
||||
@ -46,8 +43,6 @@
|
||||
if (alert) {
|
||||
alert.shake();
|
||||
}
|
||||
|
||||
saving = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,14 +62,7 @@
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input autofocus name="password" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" class="input input-bordered" />
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{#if saving}
|
||||
<Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
|
||||
{:else}
|
||||
Submit
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
<Link target="Home" hotkey="Escape">
|
||||
<button class="btn btn-outline btn-sm w-full">Cancel</button>
|
||||
</Link>
|
||||
|
Reference in New Issue
Block a user