switch crypto implementation and add spinner
This commit is contained in:
		@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "creddy",
 | 
					  "name": "creddy",
 | 
				
			||||||
  "version": "0.2.0",
 | 
					  "version": "0.2.1",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "dev": "vite",
 | 
					    "dev": "vite",
 | 
				
			||||||
    "build": "vite build",
 | 
					    "build": "vite build",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										123
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										123
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -8,6 +8,16 @@ version = "1.0.2"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 | 
					checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aead"
 | 
				
			||||||
 | 
					version = "0.5.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "crypto-common",
 | 
				
			||||||
 | 
					 "generic-array",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "ahash"
 | 
					name = "ahash"
 | 
				
			||||||
version = "0.7.6"
 | 
					version = "0.7.6"
 | 
				
			||||||
@@ -60,13 +70,15 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "app"
 | 
					name = "app"
 | 
				
			||||||
version = "0.2.0"
 | 
					version = "0.2.1"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "argon2",
 | 
				
			||||||
 "auto-launch",
 | 
					 "auto-launch",
 | 
				
			||||||
 "aws-config",
 | 
					 "aws-config",
 | 
				
			||||||
 "aws-sdk-sts",
 | 
					 "aws-sdk-sts",
 | 
				
			||||||
 "aws-smithy-types",
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 "aws-types",
 | 
					 "aws-types",
 | 
				
			||||||
 | 
					 "chacha20poly1305",
 | 
				
			||||||
 "clap",
 | 
					 "clap",
 | 
				
			||||||
 "dirs 5.0.1",
 | 
					 "dirs 5.0.1",
 | 
				
			||||||
 "is-terminal",
 | 
					 "is-terminal",
 | 
				
			||||||
@@ -86,6 +98,17 @@ dependencies = [
 | 
				
			|||||||
 "tokio",
 | 
					 "tokio",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "argon2"
 | 
				
			||||||
 | 
					version = "0.5.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "base64ct",
 | 
				
			||||||
 | 
					 "blake2",
 | 
				
			||||||
 | 
					 "password-hash",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "async-broadcast"
 | 
					name = "async-broadcast"
 | 
				
			||||||
version = "0.5.1"
 | 
					version = "0.5.1"
 | 
				
			||||||
@@ -545,12 +568,27 @@ dependencies = [
 | 
				
			|||||||
 "simd-abstraction",
 | 
					 "simd-abstraction",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "base64ct"
 | 
				
			||||||
 | 
					version = "1.6.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "bitflags"
 | 
					name = "bitflags"
 | 
				
			||||||
version = "1.3.2"
 | 
					version = "1.3.2"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 | 
					checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "blake2"
 | 
				
			||||||
 | 
					version = "0.10.6"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "digest",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "block"
 | 
					name = "block"
 | 
				
			||||||
version = "0.1.6"
 | 
					version = "0.1.6"
 | 
				
			||||||
@@ -727,6 +765,41 @@ version = "1.0.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 | 
					checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "chacha20"
 | 
				
			||||||
 | 
					version = "0.9.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "cfg-if",
 | 
				
			||||||
 | 
					 "cipher",
 | 
				
			||||||
 | 
					 "cpufeatures",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "chacha20poly1305"
 | 
				
			||||||
 | 
					version = "0.10.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aead",
 | 
				
			||||||
 | 
					 "chacha20",
 | 
				
			||||||
 | 
					 "cipher",
 | 
				
			||||||
 | 
					 "poly1305",
 | 
				
			||||||
 | 
					 "zeroize",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "cipher"
 | 
				
			||||||
 | 
					version = "0.4.4"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "crypto-common",
 | 
				
			||||||
 | 
					 "inout",
 | 
				
			||||||
 | 
					 "zeroize",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "clap"
 | 
					name = "clap"
 | 
				
			||||||
version = "3.2.25"
 | 
					version = "3.2.25"
 | 
				
			||||||
@@ -962,6 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 | 
					checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "generic-array",
 | 
					 "generic-array",
 | 
				
			||||||
 | 
					 "rand_core 0.6.4",
 | 
				
			||||||
 "typenum",
 | 
					 "typenum",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1996,6 +2070,15 @@ dependencies = [
 | 
				
			|||||||
 "cfb",
 | 
					 "cfb",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "inout"
 | 
				
			||||||
 | 
					version = "0.1.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "generic-array",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "instant"
 | 
					name = "instant"
 | 
				
			||||||
version = "0.1.12"
 | 
					version = "0.1.12"
 | 
				
			||||||
@@ -2551,6 +2634,12 @@ version = "1.17.1"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
 | 
					checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "opaque-debug"
 | 
				
			||||||
 | 
					version = "0.3.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "openssl-probe"
 | 
					name = "openssl-probe"
 | 
				
			||||||
version = "0.1.5"
 | 
					version = "0.1.5"
 | 
				
			||||||
@@ -2681,6 +2770,17 @@ dependencies = [
 | 
				
			|||||||
 "windows-sys 0.45.0",
 | 
					 "windows-sys 0.45.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "password-hash"
 | 
				
			||||||
 | 
					version = "0.5.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "base64ct",
 | 
				
			||||||
 | 
					 "rand_core 0.6.4",
 | 
				
			||||||
 | 
					 "subtle",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "paste"
 | 
					name = "paste"
 | 
				
			||||||
version = "1.0.12"
 | 
					version = "1.0.12"
 | 
				
			||||||
@@ -2872,6 +2972,17 @@ dependencies = [
 | 
				
			|||||||
 "windows-sys 0.48.0",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "poly1305"
 | 
				
			||||||
 | 
					version = "0.8.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "cpufeatures",
 | 
				
			||||||
 | 
					 "opaque-debug",
 | 
				
			||||||
 | 
					 "universal-hash",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "ppv-lite86"
 | 
					name = "ppv-lite86"
 | 
				
			||||||
version = "0.2.17"
 | 
					version = "0.2.17"
 | 
				
			||||||
@@ -4456,6 +4567,16 @@ version = "0.1.1"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 | 
					checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "universal-hash"
 | 
				
			||||||
 | 
					version = "0.5.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "crypto-common",
 | 
				
			||||||
 | 
					 "subtle",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "untrusted"
 | 
					name = "untrusted"
 | 
				
			||||||
version = "0.7.1"
 | 
					version = "0.7.1"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "app"
 | 
					name = "app"
 | 
				
			||||||
version = "0.2.0"
 | 
					version = "0.2.1"
 | 
				
			||||||
description = "A Tauri App"
 | 
					description = "A Tauri App"
 | 
				
			||||||
authors = ["you"]
 | 
					authors = ["you"]
 | 
				
			||||||
license = ""
 | 
					license = ""
 | 
				
			||||||
@@ -36,6 +36,8 @@ auto-launch = "0.4.0"
 | 
				
			|||||||
dirs = "5.0"
 | 
					dirs = "5.0"
 | 
				
			||||||
clap = { version = "3.2.23", features = ["derive"] }
 | 
					clap = { version = "3.2.23", features = ["derive"] }
 | 
				
			||||||
is-terminal = "0.4.7"
 | 
					is-terminal = "0.4.7"
 | 
				
			||||||
 | 
					argon2 = { version = "0.5.0", features = ["std"] }
 | 
				
			||||||
 | 
					chacha20poly1305 = { version = "0.10.1", features = ["std"] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[features]
 | 
					[features]
 | 
				
			||||||
# by default Tauri runs in production mode
 | 
					# by default Tauri runs in production mode
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,24 @@ use std::fmt::{self, Formatter};
 | 
				
			|||||||
use std::time::{SystemTime, UNIX_EPOCH};
 | 
					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,
 | 
				
			||||||
 | 
					    Version,
 | 
				
			||||||
 | 
					    ParamsBuilder,
 | 
				
			||||||
 | 
					    password_hash::rand_core::{RngCore, OsRng},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use chacha20poly1305::{
 | 
				
			||||||
 | 
					    XChaCha20Poly1305,
 | 
				
			||||||
 | 
					    XNonce,
 | 
				
			||||||
 | 
					    aead::{
 | 
				
			||||||
 | 
					        Aead,
 | 
				
			||||||
 | 
					        AeadCore,
 | 
				
			||||||
 | 
					        KeyInit,
 | 
				
			||||||
 | 
					        Error as AeadError,
 | 
				
			||||||
 | 
					        generic_array::GenericArray,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use serde::{
 | 
					use serde::{
 | 
				
			||||||
    Serialize,
 | 
					    Serialize,
 | 
				
			||||||
    Deserialize,
 | 
					    Deserialize,
 | 
				
			||||||
@@ -10,12 +28,7 @@ use serde::{
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
use serde::de::{self, Visitor};
 | 
					use serde::de::{self, Visitor};
 | 
				
			||||||
use sqlx::SqlitePool;
 | 
					use sqlx::SqlitePool;
 | 
				
			||||||
use sodiumoxide::crypto::{
 | 
					
 | 
				
			||||||
        pwhash,
 | 
					 | 
				
			||||||
        pwhash::Salt, 
 | 
					 | 
				
			||||||
        secretbox, 
 | 
					 | 
				
			||||||
        secretbox::{Nonce, Key}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::errors::*;
 | 
					use crate::errors::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,18 +53,17 @@ impl Session {
 | 
				
			|||||||
            None => {return Ok(Session::Empty);}
 | 
					            None => {return Ok(Session::Empty);}
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let salt_buf: [u8; 32] = row.salt
 | 
					        let salt: [u8; 32] = row.salt
 | 
				
			||||||
            .try_into()
 | 
					 | 
				
			||||||
            .map_err(|_e| SetupError::InvalidRecord)?;
 | 
					 | 
				
			||||||
        let nonce_buf: [u8; 24] = row.nonce
 | 
					 | 
				
			||||||
            .try_into()
 | 
					            .try_into()
 | 
				
			||||||
            .map_err(|_e| SetupError::InvalidRecord)?;
 | 
					            .map_err(|_e| SetupError::InvalidRecord)?;
 | 
				
			||||||
 | 
					        let nonce = XNonce::from_exact_iter(row.nonce.into_iter())
 | 
				
			||||||
 | 
					            .ok_or(SetupError::InvalidRecord)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let creds = LockedCredentials {
 | 
					        let creds = LockedCredentials {
 | 
				
			||||||
            access_key_id: row.access_key_id,
 | 
					            access_key_id: row.access_key_id,
 | 
				
			||||||
            secret_key_enc: row.secret_key_enc,
 | 
					            secret_key_enc: row.secret_key_enc,
 | 
				
			||||||
            salt: Salt(salt_buf),
 | 
					            salt,
 | 
				
			||||||
            nonce: Nonce(nonce_buf),
 | 
					            nonce,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        Ok(Session::Locked(creds))
 | 
					        Ok(Session::Locked(creds))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -76,8 +88,8 @@ impl Session {
 | 
				
			|||||||
pub struct LockedCredentials {
 | 
					pub struct LockedCredentials {
 | 
				
			||||||
    pub access_key_id: String,
 | 
					    pub access_key_id: String,
 | 
				
			||||||
    pub secret_key_enc: Vec<u8>,
 | 
					    pub secret_key_enc: Vec<u8>,
 | 
				
			||||||
    pub salt: Salt,
 | 
					    pub salt: [u8; 32],
 | 
				
			||||||
    pub nonce: Nonce,
 | 
					    pub nonce: XNonce,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl LockedCredentials {
 | 
					impl LockedCredentials {
 | 
				
			||||||
@@ -88,8 +100,8 @@ impl LockedCredentials {
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
            .bind(&self.access_key_id)
 | 
					            .bind(&self.access_key_id)
 | 
				
			||||||
            .bind(&self.secret_key_enc)
 | 
					            .bind(&self.secret_key_enc)
 | 
				
			||||||
            .bind(&self.salt.0[0..])
 | 
					            .bind(&self.salt[..])
 | 
				
			||||||
            .bind(&self.nonce.0[0..])
 | 
					            .bind(&self.nonce[..])
 | 
				
			||||||
            .execute(pool)
 | 
					            .execute(pool)
 | 
				
			||||||
            .await?;
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -97,11 +109,10 @@ impl LockedCredentials {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn decrypt(&self, passphrase: &str) -> Result<BaseCredentials, UnlockError> {
 | 
					    pub fn decrypt(&self, passphrase: &str) -> Result<BaseCredentials, UnlockError> {
 | 
				
			||||||
        let mut key_buf = [0; secretbox::KEYBYTES];
 | 
					        let crypto = Crypto::new(passphrase, &self.salt)
 | 
				
			||||||
        // pretty sure this only fails if we're out of memory
 | 
					            .map_err(|e| CryptoError::Argon2(e))?;
 | 
				
			||||||
        pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &self.salt).unwrap();
 | 
					        let decrypted = crypto.decrypt(&self.nonce, &self.secret_key_enc)
 | 
				
			||||||
        let decrypted = secretbox::open(&self.secret_key_enc, &self.nonce, &Key(key_buf))
 | 
					            .map_err(|e| CryptoError::Aead(e))?;
 | 
				
			||||||
            .map_err(|_| UnlockError::BadPassphrase)?;
 | 
					 | 
				
			||||||
        let secret_access_key = String::from_utf8(decrypted)
 | 
					        let secret_access_key = String::from_utf8(decrypted)
 | 
				
			||||||
            .map_err(|_| UnlockError::InvalidUtf8)?;
 | 
					            .map_err(|_| UnlockError::InvalidUtf8)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,21 +133,18 @@ pub struct BaseCredentials {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl BaseCredentials {
 | 
					impl BaseCredentials {
 | 
				
			||||||
    pub fn encrypt(&self, passphrase: &str) -> LockedCredentials {
 | 
					    pub fn encrypt(&self, passphrase: &str) -> Result<LockedCredentials, CryptoError> {
 | 
				
			||||||
        let salt = pwhash::gen_salt();
 | 
					        let salt = Crypto::salt();
 | 
				
			||||||
        let mut key_buf = [0; secretbox::KEYBYTES];
 | 
					        let crypto = Crypto::new(passphrase, &salt)?;
 | 
				
			||||||
        pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &salt).unwrap();
 | 
					        let (nonce, secret_key_enc) = crypto.encrypt(self.secret_access_key.as_bytes())?;
 | 
				
			||||||
        let key = Key(key_buf);
 | 
					 | 
				
			||||||
        let nonce = secretbox::gen_nonce();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let secret_key_enc = secretbox::seal(self.secret_access_key.as_bytes(), &nonce, &key);
 | 
					        let locked = LockedCredentials {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        LockedCredentials {
 | 
					 | 
				
			||||||
            access_key_id: self.access_key_id.clone(),
 | 
					            access_key_id: self.access_key_id.clone(),
 | 
				
			||||||
            secret_key_enc,
 | 
					            secret_key_enc,
 | 
				
			||||||
            salt,
 | 
					            salt,
 | 
				
			||||||
            nonce,
 | 
					            nonce,
 | 
				
			||||||
        }
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(locked)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -242,3 +250,72 @@ where D: Deserializer<'de>
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    deserializer.deserialize_str(DateTimeVisitor)
 | 
					    deserializer.deserialize_str(DateTimeVisitor)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Crypto {
 | 
				
			||||||
 | 
					    cipher: XChaCha20Poly1305,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Crypto {
 | 
				
			||||||
 | 
					    /// Argon2 params rationale:
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// m_cost is measured in KiB, so 128 * 1024 gives us 128MiB.
 | 
				
			||||||
 | 
					    /// This should roughly double the memory usage of the application
 | 
				
			||||||
 | 
					    /// while deriving the key.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// p_cost is irrelevant since (at present) there isn't any parallelism
 | 
				
			||||||
 | 
					    /// implemented, so we leave it at 1.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// With the above m_cost, t_cost = 8 results in about 800ms to derive
 | 
				
			||||||
 | 
					    /// 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 in an unoptimized build,
 | 
				
			||||||
 | 
					    /// 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)
 | 
				
			||||||
 | 
					            .p_cost(1)
 | 
				
			||||||
 | 
					            .t_cost(Self::TIME_COST)
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					            .unwrap(); // only errors if the given params are invalid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let hasher = Argon2::new(
 | 
				
			||||||
 | 
					            Algorithm::Argon2id,
 | 
				
			||||||
 | 
					            Version::V0x13,
 | 
				
			||||||
 | 
					            params,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut key = [0; 32];
 | 
				
			||||||
 | 
					        hasher.hash_password_into(passphrase.as_bytes(), &salt, &mut key)?;
 | 
				
			||||||
 | 
					        let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(&key));
 | 
				
			||||||
 | 
					        Ok(Crypto { cipher })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn salt() -> [u8; 32] {
 | 
				
			||||||
 | 
					        let mut salt = [0; 32];
 | 
				
			||||||
 | 
					        OsRng.fill_bytes(&mut salt);
 | 
				
			||||||
 | 
					        salt
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec<u8>), AeadError> {
 | 
				
			||||||
 | 
					        let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
 | 
				
			||||||
 | 
					        let ciphertext = self.cipher.encrypt(&nonce, data)?;
 | 
				
			||||||
 | 
					        Ok((nonce, ciphertext))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn decrypt(&self, nonce: &XNonce, data: &[u8]) -> Result<Vec<u8>, AeadError> {
 | 
				
			||||||
 | 
					        self.cipher.decrypt(nonce, data)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -164,8 +164,8 @@ pub enum UnlockError {
 | 
				
			|||||||
    NotLocked,
 | 
					    NotLocked,
 | 
				
			||||||
    #[error("No saved credentials were found")]
 | 
					    #[error("No saved credentials were found")]
 | 
				
			||||||
    NoCredentials,
 | 
					    NoCredentials,
 | 
				
			||||||
    #[error("Invalid passphrase")]
 | 
					    #[error(transparent)]
 | 
				
			||||||
    BadPassphrase,
 | 
					    Crypto(#[from] CryptoError),
 | 
				
			||||||
    #[error("Data was found to be corrupt after decryption")]
 | 
					    #[error("Data was found to be corrupt after decryption")]
 | 
				
			||||||
    InvalidUtf8, // Somehow we got invalid utf-8 even though decryption succeeded
 | 
					    InvalidUtf8, // Somehow we got invalid utf-8 even though decryption succeeded
 | 
				
			||||||
    #[error("Database error: {0}")]
 | 
					    #[error("Database error: {0}")]
 | 
				
			||||||
@@ -175,6 +175,15 @@ 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
 | 
					// Errors encountered while trying to figure out who's on the other end of a request
 | 
				
			||||||
#[derive(Debug, ThisError, AsRefStr)]
 | 
					#[derive(Debug, ThisError, AsRefStr)]
 | 
				
			||||||
pub enum ClientInfoError {
 | 
					pub enum ClientInfoError {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,7 +48,7 @@ impl AppState {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn new_creds(&self, base_creds: BaseCredentials, passphrase: &str) -> Result<(), UnlockError> {
 | 
					    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
 | 
					        // do this first so that if it fails we don't save bad credentials
 | 
				
			||||||
        self.new_session(base_creds).await?;
 | 
					        self.new_session(base_creds).await?;
 | 
				
			||||||
        locked.save(&self.pool).await?;
 | 
					        locked.save(&self.pool).await?;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "package": {
 | 
					  "package": {
 | 
				
			||||||
    "productName": "creddy",
 | 
					    "productName": "creddy",
 | 
				
			||||||
    "version": "0.2.0"
 | 
					    "version": "0.2.1"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "tauri": {
 | 
					  "tauri": {
 | 
				
			||||||
    "allowlist": {
 | 
					    "allowlist": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,6 @@ invoke('get_config').then(config => $appState.config = config);
 | 
				
			|||||||
listen('credentials-request', (tauriEvent) => {
 | 
					listen('credentials-request', (tauriEvent) => {
 | 
				
			||||||
    $appState.pendingRequests.put(tauriEvent.payload);
 | 
					    $appState.pendingRequests.put(tauriEvent.payload);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
window.state = $appState;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
acceptRequest();
 | 
					acceptRequest();
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										113
									
								
								src/ui/Spinner.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/ui/Spinner.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					<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,6 +7,7 @@
 | 
				
			|||||||
    import { navigate } from '../lib/routing.js';
 | 
					    import { navigate } from '../lib/routing.js';
 | 
				
			||||||
    import Link from '../ui/Link.svelte';
 | 
					    import Link from '../ui/Link.svelte';
 | 
				
			||||||
    import ErrorAlert from '../ui/ErrorAlert.svelte';
 | 
					    import ErrorAlert from '../ui/ErrorAlert.svelte';
 | 
				
			||||||
 | 
					    import Spinner from '../ui/Spinner.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let errorMsg = null;
 | 
					    let errorMsg = null;
 | 
				
			||||||
@@ -19,6 +20,7 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let saving = false;
 | 
				
			||||||
    async function save() {
 | 
					    async function save() {
 | 
				
			||||||
        if (passphrase !== confirmPassphrase) {
 | 
					        if (passphrase !== confirmPassphrase) {
 | 
				
			||||||
            alert.shake();
 | 
					            alert.shake();
 | 
				
			||||||
@@ -27,6 +29,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let credentials = {AccessKeyId, SecretAccessKey};
 | 
					        let credentials = {AccessKeyId, SecretAccessKey};
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            saving = true;
 | 
				
			||||||
            await invoke('save_credentials', {credentials, passphrase});
 | 
					            await invoke('save_credentials', {credentials, passphrase});
 | 
				
			||||||
            if ($appState.currentRequest) {
 | 
					            if ($appState.currentRequest) {
 | 
				
			||||||
                navigate('Approve');
 | 
					                navigate('Approve');
 | 
				
			||||||
@@ -47,6 +50,8 @@
 | 
				
			|||||||
            if (alert) {
 | 
					            if (alert) {
 | 
				
			||||||
                alert.shake();
 | 
					                alert.shake();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            saving = false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@@ -65,7 +70,13 @@
 | 
				
			|||||||
    <input type="password" placeholder="Passphrase" bind:value="{passphrase}" class="input input-bordered" />
 | 
					    <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} />
 | 
					    <input type="password" placeholder="Re-enter passphrase" bind:value={confirmPassphrase} class="input input-bordered" on:change={confirm} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <input type="submit" class="btn btn-primary" />
 | 
					    <button type="submit" class="btn btn-primary">
 | 
				
			||||||
 | 
					        {#if saving}
 | 
				
			||||||
 | 
					            <Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
 | 
				
			||||||
 | 
					        {:else}
 | 
				
			||||||
 | 
					            Submit
 | 
				
			||||||
 | 
					        {/if}
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
    <Link target="Home" hotkey="Escape">
 | 
					    <Link target="Home" hotkey="Escape">
 | 
				
			||||||
        <button class="btn btn-sm btn-outline w-full">Cancel</button>
 | 
					        <button class="btn btn-sm btn-outline w-full">Cancel</button>
 | 
				
			||||||
    </Link>
 | 
					    </Link>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,12 +7,14 @@
 | 
				
			|||||||
    import { getRootCause } from '../lib/errors.js';
 | 
					    import { getRootCause } from '../lib/errors.js';
 | 
				
			||||||
    import ErrorAlert from '../ui/ErrorAlert.svelte';
 | 
					    import ErrorAlert from '../ui/ErrorAlert.svelte';
 | 
				
			||||||
    import Link from '../ui/Link.svelte';
 | 
					    import Link from '../ui/Link.svelte';
 | 
				
			||||||
 | 
					    import Spinner from '../ui/Spinner.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let errorMsg = null;
 | 
					    let errorMsg = null;
 | 
				
			||||||
    let alert;
 | 
					    let alert;
 | 
				
			||||||
    let passphrase = '';
 | 
					    let passphrase = '';
 | 
				
			||||||
    let loadTime = 0;
 | 
					    let loadTime = 0;
 | 
				
			||||||
 | 
					    let saving = false;
 | 
				
			||||||
    async function unlock() {
 | 
					    async function unlock() {
 | 
				
			||||||
        // The hotkey for navigating here from homepage is Enter, which also
 | 
					        // The hotkey for navigating here from homepage is Enter, which also
 | 
				
			||||||
        // happens to trigger the form submit event
 | 
					        // happens to trigger the form submit event
 | 
				
			||||||
@@ -21,6 +23,7 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            saving = true;
 | 
				
			||||||
            let r = await invoke('unlock', {passphrase});
 | 
					            let r = await invoke('unlock', {passphrase});
 | 
				
			||||||
            $appState.credentialStatus = 'unlocked';
 | 
					            $appState.credentialStatus = 'unlocked';
 | 
				
			||||||
            if ($appState.currentRequest) {
 | 
					            if ($appState.currentRequest) {
 | 
				
			||||||
@@ -43,6 +46,8 @@
 | 
				
			|||||||
            if (alert) {
 | 
					            if (alert) {
 | 
				
			||||||
                alert.shake();
 | 
					                alert.shake();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            saving = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -62,7 +67,14 @@
 | 
				
			|||||||
    <!-- svelte-ignore a11y-autofocus -->
 | 
					    <!-- svelte-ignore a11y-autofocus -->
 | 
				
			||||||
    <input autofocus name="password" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" class="input input-bordered" />
 | 
					    <input autofocus name="password" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" class="input input-bordered" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <input type="submit" class="btn btn-primary" />
 | 
					    <button type="submit" class="btn btn-primary">
 | 
				
			||||||
 | 
					        {#if saving}
 | 
				
			||||||
 | 
					            <Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
 | 
				
			||||||
 | 
					        {:else}
 | 
				
			||||||
 | 
					            Submit
 | 
				
			||||||
 | 
					        {/if}
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Link target="Home" hotkey="Escape">
 | 
					    <Link target="Home" hotkey="Escape">
 | 
				
			||||||
        <button class="btn btn-outline btn-sm w-full">Cancel</button>
 | 
					        <button class="btn btn-outline btn-sm w-full">Cancel</button>
 | 
				
			||||||
    </Link>
 | 
					    </Link>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user