Compare commits
	
		
			28 Commits
		
	
	
		
			dev2
			...
			0d37814cf4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0d37814cf4 | |||
| 31532cd76e | |||
| 
						 | 
					a8aafa1519 | ||
| 6f9cd6b471 | |||
| 865b7fd5c4 | |||
| f35352eedd | |||
| 53580d7919 | |||
| 049b81610d | |||
| 
						 | 
					fd60899f16 | ||
| 
						 | 
					e0c4c849dc | ||
| 
						 | 
					cb26201506 | ||
| 
						 | 
					992e3c8db2 | ||
| 
						 | 
					4956b64371 | ||
| df6b362a31 | |||
| 2943634248 | |||
| 06f5a1af42 | |||
| 
						 | 
					61d674199f | ||
| 
						 | 
					398916fe10 | ||
| 
						 | 
					bf4c46238e | ||
| 
						 | 
					5ffa55c03c | ||
| 
						 | 
					50f0985f4f | ||
| 
						 | 
					69475604c0 | ||
| 
						 | 
					856b6f1e1b | ||
| 
						 | 
					414379b74e | ||
| 
						 | 
					80b92ebe69 | ||
| 983d0e8639 | |||
| 
						 | 
					d77437cda8 | ||
| 
						 | 
					3d5cbedae1 | 
							
								
								
									
										16
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								index.html
									
									
									
									
									
								
							@@ -1,25 +1,13 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					<!DOCTYPE html>
 | 
				
			||||||
<html lang="en">
 | 
					<html lang="en" data-theme="dark">
 | 
				
			||||||
  <head>
 | 
					  <head>
 | 
				
			||||||
    <meta charset="UTF-8" />
 | 
					    <meta charset="UTF-8" />
 | 
				
			||||||
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
 | 
					    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
				
			||||||
    <title>Vite + Svelte</title>
 | 
					    <title>Vite + Svelte</title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <style>
 | 
					 | 
				
			||||||
      body {
 | 
					 | 
				
			||||||
        margin:  0;
 | 
					 | 
				
			||||||
        display:  grid;
 | 
					 | 
				
			||||||
        align-items: center;
 | 
					 | 
				
			||||||
        justify-items: center;
 | 
					 | 
				
			||||||
        min-width:  100vw;
 | 
					 | 
				
			||||||
        min-height:  100vh;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
  <body class="bg-zinc-800">
 | 
					  <body id="app" class="m-0">
 | 
				
			||||||
    <div id="app"></div>
 | 
					 | 
				
			||||||
    <script type="module" src="/src/main.js"></script>
 | 
					    <script type="module" src="/src/main.js"></script>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1727
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1727
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -17,6 +17,7 @@
 | 
				
			|||||||
    "vite": "^3.0.7"
 | 
					    "vite": "^3.0.7"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@tauri-apps/api": "^1.0.2"
 | 
					    "@tauri-apps/api": "^1.0.2",
 | 
				
			||||||
 | 
					    "daisyui": "^2.51.5"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								src-tauri/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src-tauri/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					[target.x86_64-unknown-linux-gnu]
 | 
				
			||||||
 | 
					linker = "clang"
 | 
				
			||||||
 | 
					rustflags = ["-C", "link-arg=--ld-path=/usr/bin/mold"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[target.x86_64-pc-windows-msvc]
 | 
				
			||||||
 | 
					rustflags = ["-C", "link-arg=-fuse-ld=lld"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								src-tauri/.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src-tauri/.env
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					DATABASE_URL=sqlite://creddy.db?mode=rwc
 | 
				
			||||||
							
								
								
									
										641
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										641
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -68,14 +68,22 @@ checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
 | 
				
			|||||||
name = "app"
 | 
					name = "app"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-config",
 | 
				
			||||||
 | 
					 "aws-sdk-sts",
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "aws-types",
 | 
				
			||||||
 "netstat2",
 | 
					 "netstat2",
 | 
				
			||||||
 | 
					 "once_cell",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
 "sodiumoxide",
 | 
					 "sodiumoxide",
 | 
				
			||||||
 "sqlx",
 | 
					 "sqlx",
 | 
				
			||||||
 | 
					 "strum 0.24.1",
 | 
				
			||||||
 | 
					 "strum_macros 0.24.3",
 | 
				
			||||||
 "sysinfo",
 | 
					 "sysinfo",
 | 
				
			||||||
 "tauri",
 | 
					 "tauri",
 | 
				
			||||||
 "tauri-build",
 | 
					 "tauri-build",
 | 
				
			||||||
 | 
					 "thiserror",
 | 
				
			||||||
 "tokio",
 | 
					 "tokio",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -136,12 +144,288 @@ version = "1.1.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 | 
					checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-config"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "e7688e1dfbb9f7804fab0a830820d7e827b8d973906763cf1a855ce4719292f5"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-http",
 | 
				
			||||||
 | 
					 "aws-sdk-sso",
 | 
				
			||||||
 | 
					 "aws-sdk-sts",
 | 
				
			||||||
 | 
					 "aws-smithy-async",
 | 
				
			||||||
 | 
					 "aws-smithy-client",
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "aws-smithy-http-tower",
 | 
				
			||||||
 | 
					 "aws-smithy-json",
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "aws-types",
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "hex",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "hyper",
 | 
				
			||||||
 | 
					 "ring",
 | 
				
			||||||
 | 
					 "time",
 | 
				
			||||||
 | 
					 "tokio",
 | 
				
			||||||
 | 
					 "tower",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					 "zeroize",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-endpoint"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "253d7cd480bfa59a5323390e9e91885a8f06a275e0517d81eeb1070b6aa7d271"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "aws-types",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "regex",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-http"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "4cd1b83859383e46ea8fda633378f9f3f02e6e3a446fd89f0240b5c3662716c9"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "aws-types",
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "http-body",
 | 
				
			||||||
 | 
					 "lazy_static",
 | 
				
			||||||
 | 
					 "percent-encoding",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-sdk-sso"
 | 
				
			||||||
 | 
					version = "0.22.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "bf03342c2b3f52b180f484e60586500765474f2bfc7dcd4ffe893a7a1929db1d"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-endpoint",
 | 
				
			||||||
 | 
					 "aws-http",
 | 
				
			||||||
 | 
					 "aws-sig-auth",
 | 
				
			||||||
 | 
					 "aws-smithy-async",
 | 
				
			||||||
 | 
					 "aws-smithy-client",
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "aws-smithy-http-tower",
 | 
				
			||||||
 | 
					 "aws-smithy-json",
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "aws-types",
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "tokio-stream",
 | 
				
			||||||
 | 
					 "tower",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-sdk-sts"
 | 
				
			||||||
 | 
					version = "0.22.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "aa1de4e07ea87a30a317c7b563b3a40fd18a843ad794216dda81672b6e174bce"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-endpoint",
 | 
				
			||||||
 | 
					 "aws-http",
 | 
				
			||||||
 | 
					 "aws-sig-auth",
 | 
				
			||||||
 | 
					 "aws-smithy-async",
 | 
				
			||||||
 | 
					 "aws-smithy-client",
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "aws-smithy-http-tower",
 | 
				
			||||||
 | 
					 "aws-smithy-query",
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "aws-smithy-xml",
 | 
				
			||||||
 | 
					 "aws-types",
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "tower",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-sig-auth"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "6126c4ff918e35fb9ae1bf2de71157fad36f0cc6a2b1d0f7197ee711713700fc"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-sigv4",
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "aws-types",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-sigv4"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "84c7f88d7395f5411c6eef5889b6cd577ce6b677af461356cbfc20176c26c160"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "form_urlencoded",
 | 
				
			||||||
 | 
					 "hex",
 | 
				
			||||||
 | 
					 "hmac",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "once_cell",
 | 
				
			||||||
 | 
					 "percent-encoding",
 | 
				
			||||||
 | 
					 "regex",
 | 
				
			||||||
 | 
					 "sha2",
 | 
				
			||||||
 | 
					 "time",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-smithy-async"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "3e6a895d68852dd1564328e63ef1583e5eb307dd2a5ebf35d862a5c402957d5e"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "futures-util",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					 "tokio",
 | 
				
			||||||
 | 
					 "tokio-stream",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-smithy-client"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "f505bf793eb3e6d7c166ef1275c27b4b2cd5361173fe950ac8e2cfc08c29a7ef"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-smithy-async",
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "aws-smithy-http-tower",
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "fastrand",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "http-body",
 | 
				
			||||||
 | 
					 "hyper",
 | 
				
			||||||
 | 
					 "hyper-rustls",
 | 
				
			||||||
 | 
					 "lazy_static",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					 "tokio",
 | 
				
			||||||
 | 
					 "tower",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-smithy-http"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "37e4b4304b7ea4af1af3e08535100eb7b6459d5a6264b92078bf85176d04ab85"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "bytes-utils",
 | 
				
			||||||
 | 
					 "futures-core",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "http-body",
 | 
				
			||||||
 | 
					 "hyper",
 | 
				
			||||||
 | 
					 "once_cell",
 | 
				
			||||||
 | 
					 "percent-encoding",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					 "pin-utils",
 | 
				
			||||||
 | 
					 "tokio",
 | 
				
			||||||
 | 
					 "tokio-util",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-smithy-http-tower"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "e86072ecc4dc4faf3e2071144285cfd539263fe7102b701d54fb991eafb04af8"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "http-body",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					 "tower",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-smithy-json"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "9e3ddd9275b167bc59e9446469eca56177ec0b51225632f90aaa2cd5f41c940e"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-smithy-query"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "13b19d2e0b3ce20e460bad0d0d974238673100edebba6978c2c1aadd925602f7"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "urlencoding",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-smithy-types"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "987b1e37febb9bd409ca0846e82d35299e572ad8279bc404778caeb5fc05ad56"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "base64-simd",
 | 
				
			||||||
 | 
					 "itoa 1.0.2",
 | 
				
			||||||
 | 
					 "num-integer",
 | 
				
			||||||
 | 
					 "ryu",
 | 
				
			||||||
 | 
					 "time",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-smithy-xml"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "37ce3791e14eec75ffac851a5a559f1ce6b31843297f42cc8bfba82714a6a5d8"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "xmlparser",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "aws-types"
 | 
				
			||||||
 | 
					version = "0.52.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "6c05adca3e2bcf686dd2c47836f216ab52ed7845c177d180c84b08522c1166a3"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aws-smithy-async",
 | 
				
			||||||
 | 
					 "aws-smithy-client",
 | 
				
			||||||
 | 
					 "aws-smithy-http",
 | 
				
			||||||
 | 
					 "aws-smithy-types",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "rustc_version 0.4.0",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					 "zeroize",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "base64"
 | 
					name = "base64"
 | 
				
			||||||
version = "0.13.0"
 | 
					version = "0.13.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
 | 
					checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "base64-simd"
 | 
				
			||||||
 | 
					version = "0.7.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "simd-abstraction",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "bitflags"
 | 
					name = "bitflags"
 | 
				
			||||||
version = "1.3.2"
 | 
					version = "1.3.2"
 | 
				
			||||||
@@ -217,6 +501,16 @@ version = "1.2.1"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
 | 
					checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "bytes-utils"
 | 
				
			||||||
 | 
					version = "0.1.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "either",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "cairo-rs"
 | 
					name = "cairo-rs"
 | 
				
			||||||
version = "0.15.12"
 | 
					version = "0.15.12"
 | 
				
			||||||
@@ -618,6 +912,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
 | 
				
			|||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "block-buffer",
 | 
					 "block-buffer",
 | 
				
			||||||
 "crypto-common",
 | 
					 "crypto-common",
 | 
				
			||||||
 | 
					 "subtle",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -736,7 +1031,7 @@ dependencies = [
 | 
				
			|||||||
 "cfg-if",
 | 
					 "cfg-if",
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "redox_syscall",
 | 
					 "redox_syscall",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.36.1",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -1203,6 +1498,25 @@ dependencies = [
 | 
				
			|||||||
 "syn",
 | 
					 "syn",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "h2"
 | 
				
			||||||
 | 
					version = "0.3.15"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "fnv",
 | 
				
			||||||
 | 
					 "futures-core",
 | 
				
			||||||
 | 
					 "futures-sink",
 | 
				
			||||||
 | 
					 "futures-util",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "indexmap",
 | 
				
			||||||
 | 
					 "slab",
 | 
				
			||||||
 | 
					 "tokio",
 | 
				
			||||||
 | 
					 "tokio-util",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "hashbrown"
 | 
					name = "hashbrown"
 | 
				
			||||||
version = "0.12.3"
 | 
					version = "0.12.3"
 | 
				
			||||||
@@ -1254,6 +1568,15 @@ version = "0.4.3"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 | 
					checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "hmac"
 | 
				
			||||||
 | 
					version = "0.12.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "digest",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "html5ever"
 | 
					name = "html5ever"
 | 
				
			||||||
version = "0.25.2"
 | 
					version = "0.25.2"
 | 
				
			||||||
@@ -1279,12 +1602,75 @@ dependencies = [
 | 
				
			|||||||
 "itoa 1.0.2",
 | 
					 "itoa 1.0.2",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "http-body"
 | 
				
			||||||
 | 
					version = "0.4.5"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "http-range"
 | 
					name = "http-range"
 | 
				
			||||||
version = "0.1.5"
 | 
					version = "0.1.5"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
 | 
					checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "httparse"
 | 
				
			||||||
 | 
					version = "1.8.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "httpdate"
 | 
				
			||||||
 | 
					version = "1.0.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "hyper"
 | 
				
			||||||
 | 
					version = "0.14.22"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "futures-channel",
 | 
				
			||||||
 | 
					 "futures-core",
 | 
				
			||||||
 | 
					 "futures-util",
 | 
				
			||||||
 | 
					 "h2",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "http-body",
 | 
				
			||||||
 | 
					 "httparse",
 | 
				
			||||||
 | 
					 "httpdate",
 | 
				
			||||||
 | 
					 "itoa 1.0.2",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					 "socket2",
 | 
				
			||||||
 | 
					 "tokio",
 | 
				
			||||||
 | 
					 "tower-service",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					 "want",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "hyper-rustls"
 | 
				
			||||||
 | 
					version = "0.23.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "hyper",
 | 
				
			||||||
 | 
					 "log",
 | 
				
			||||||
 | 
					 "rustls",
 | 
				
			||||||
 | 
					 "rustls-native-certs",
 | 
				
			||||||
 | 
					 "tokio",
 | 
				
			||||||
 | 
					 "tokio-rustls",
 | 
				
			||||||
 | 
					 "webpki-roots",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "ico"
 | 
					name = "ico"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
@@ -1496,6 +1882,30 @@ version = "1.4.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 | 
					checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "libappindicator"
 | 
				
			||||||
 | 
					version = "0.7.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "glib",
 | 
				
			||||||
 | 
					 "gtk",
 | 
				
			||||||
 | 
					 "gtk-sys",
 | 
				
			||||||
 | 
					 "libappindicator-sys",
 | 
				
			||||||
 | 
					 "log",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "libappindicator-sys"
 | 
				
			||||||
 | 
					version = "0.7.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "gtk-sys",
 | 
				
			||||||
 | 
					 "libloading",
 | 
				
			||||||
 | 
					 "once_cell",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "libc"
 | 
					name = "libc"
 | 
				
			||||||
version = "0.2.126"
 | 
					version = "0.2.126"
 | 
				
			||||||
@@ -1511,6 +1921,16 @@ dependencies = [
 | 
				
			|||||||
 "pkg-config",
 | 
					 "pkg-config",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "libloading"
 | 
				
			||||||
 | 
					version = "0.7.4"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "cfg-if",
 | 
				
			||||||
 | 
					 "winapi",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "libsodium-sys"
 | 
					name = "libsodium-sys"
 | 
				
			||||||
version = "0.2.7"
 | 
					version = "0.2.7"
 | 
				
			||||||
@@ -1682,7 +2102,7 @@ dependencies = [
 | 
				
			|||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 "wasi 0.11.0+wasi-snapshot-preview1",
 | 
					 "wasi 0.11.0+wasi-snapshot-preview1",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.36.1",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -1920,9 +2340,9 @@ dependencies = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "once_cell"
 | 
					name = "once_cell"
 | 
				
			||||||
version = "1.13.0"
 | 
					version = "1.16.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
 | 
					checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "open"
 | 
					name = "open"
 | 
				
			||||||
@@ -1931,7 +2351,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "f23a407004a1033f53e93f9b45580d14de23928faad187384f891507c9b0c045"
 | 
					checksum = "f23a407004a1033f53e93f9b45580d14de23928faad187384f891507c9b0c045"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "pathdiff",
 | 
					 "pathdiff",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.36.1",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -2000,6 +2420,12 @@ dependencies = [
 | 
				
			|||||||
 "winapi",
 | 
					 "winapi",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "outref"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "pango"
 | 
					name = "pango"
 | 
				
			||||||
version = "0.15.10"
 | 
					version = "0.15.10"
 | 
				
			||||||
@@ -2076,7 +2502,7 @@ dependencies = [
 | 
				
			|||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "redox_syscall",
 | 
					 "redox_syscall",
 | 
				
			||||||
 "smallvec",
 | 
					 "smallvec",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.36.1",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -2589,6 +3015,18 @@ dependencies = [
 | 
				
			|||||||
 "webpki",
 | 
					 "webpki",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "rustls-native-certs"
 | 
				
			||||||
 | 
					version = "0.6.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "openssl-probe",
 | 
				
			||||||
 | 
					 "rustls-pemfile",
 | 
				
			||||||
 | 
					 "schannel",
 | 
				
			||||||
 | 
					 "security-framework",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "rustls-pemfile"
 | 
					name = "rustls-pemfile"
 | 
				
			||||||
version = "1.0.1"
 | 
					version = "1.0.1"
 | 
				
			||||||
@@ -2632,7 +3070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
 | 
					checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "lazy_static",
 | 
					 "lazy_static",
 | 
				
			||||||
 "windows-sys",
 | 
					 "windows-sys 0.36.1",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -2880,6 +3318,15 @@ version = "1.5.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4"
 | 
					checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "simd-abstraction"
 | 
				
			||||||
 | 
					version = "0.7.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "outref",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "siphasher"
 | 
					name = "siphasher"
 | 
				
			||||||
version = "0.3.10"
 | 
					version = "0.3.10"
 | 
				
			||||||
@@ -3126,9 +3573,15 @@ version = "0.22.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
 | 
					checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "strum_macros",
 | 
					 "strum_macros 0.22.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "strum"
 | 
				
			||||||
 | 
					version = "0.24.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "strum_macros"
 | 
					name = "strum_macros"
 | 
				
			||||||
version = "0.22.0"
 | 
					version = "0.22.0"
 | 
				
			||||||
@@ -3141,6 +3594,25 @@ dependencies = [
 | 
				
			|||||||
 "syn",
 | 
					 "syn",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "strum_macros"
 | 
				
			||||||
 | 
					version = "0.24.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "heck 0.4.0",
 | 
				
			||||||
 | 
					 "proc-macro2",
 | 
				
			||||||
 | 
					 "quote",
 | 
				
			||||||
 | 
					 "rustversion",
 | 
				
			||||||
 | 
					 "syn",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "subtle"
 | 
				
			||||||
 | 
					version = "2.4.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "syn"
 | 
					name = "syn"
 | 
				
			||||||
version = "1.0.98"
 | 
					version = "1.0.98"
 | 
				
			||||||
@@ -3206,6 +3678,7 @@ dependencies = [
 | 
				
			|||||||
 "core-foundation",
 | 
					 "core-foundation",
 | 
				
			||||||
 "core-graphics",
 | 
					 "core-graphics",
 | 
				
			||||||
 "crossbeam-channel",
 | 
					 "crossbeam-channel",
 | 
				
			||||||
 | 
					 "dirs-next",
 | 
				
			||||||
 "dispatch",
 | 
					 "dispatch",
 | 
				
			||||||
 "gdk",
 | 
					 "gdk",
 | 
				
			||||||
 "gdk-pixbuf",
 | 
					 "gdk-pixbuf",
 | 
				
			||||||
@@ -3219,6 +3692,7 @@ dependencies = [
 | 
				
			|||||||
 "instant",
 | 
					 "instant",
 | 
				
			||||||
 "jni 0.19.0",
 | 
					 "jni 0.19.0",
 | 
				
			||||||
 "lazy_static",
 | 
					 "lazy_static",
 | 
				
			||||||
 | 
					 "libappindicator",
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 "ndk",
 | 
					 "ndk",
 | 
				
			||||||
@@ -3459,18 +3933,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "thiserror"
 | 
					name = "thiserror"
 | 
				
			||||||
version = "1.0.31"
 | 
					version = "1.0.38"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
 | 
					checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "thiserror-impl",
 | 
					 "thiserror-impl",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "thiserror-impl"
 | 
					name = "thiserror-impl"
 | 
				
			||||||
version = "1.0.31"
 | 
					version = "1.0.38"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
 | 
					checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "proc-macro2",
 | 
					 "proc-macro2",
 | 
				
			||||||
 "quote",
 | 
					 "quote",
 | 
				
			||||||
@@ -3514,9 +3988,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "tokio"
 | 
					name = "tokio"
 | 
				
			||||||
version = "1.20.1"
 | 
					version = "1.23.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581"
 | 
					checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "autocfg",
 | 
					 "autocfg",
 | 
				
			||||||
 "bytes",
 | 
					 "bytes",
 | 
				
			||||||
@@ -3524,13 +3998,12 @@ dependencies = [
 | 
				
			|||||||
 "memchr",
 | 
					 "memchr",
 | 
				
			||||||
 "mio",
 | 
					 "mio",
 | 
				
			||||||
 "num_cpus",
 | 
					 "num_cpus",
 | 
				
			||||||
 "once_cell",
 | 
					 | 
				
			||||||
 "parking_lot 0.12.1",
 | 
					 "parking_lot 0.12.1",
 | 
				
			||||||
 "pin-project-lite",
 | 
					 "pin-project-lite",
 | 
				
			||||||
 "signal-hook-registry",
 | 
					 "signal-hook-registry",
 | 
				
			||||||
 "socket2",
 | 
					 "socket2",
 | 
				
			||||||
 "tokio-macros",
 | 
					 "tokio-macros",
 | 
				
			||||||
 "winapi",
 | 
					 "windows-sys 0.42.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@@ -3566,6 +4039,20 @@ dependencies = [
 | 
				
			|||||||
 "tokio",
 | 
					 "tokio",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "tokio-util"
 | 
				
			||||||
 | 
					version = "0.7.4"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "futures-core",
 | 
				
			||||||
 | 
					 "futures-sink",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					 "tokio",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "toml"
 | 
					name = "toml"
 | 
				
			||||||
version = "0.5.9"
 | 
					version = "0.5.9"
 | 
				
			||||||
@@ -3575,6 +4062,34 @@ dependencies = [
 | 
				
			|||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "tower"
 | 
				
			||||||
 | 
					version = "0.4.13"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "futures-core",
 | 
				
			||||||
 | 
					 "futures-util",
 | 
				
			||||||
 | 
					 "pin-project",
 | 
				
			||||||
 | 
					 "pin-project-lite",
 | 
				
			||||||
 | 
					 "tokio",
 | 
				
			||||||
 | 
					 "tower-layer",
 | 
				
			||||||
 | 
					 "tower-service",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "tower-layer"
 | 
				
			||||||
 | 
					version = "0.3.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "tower-service"
 | 
				
			||||||
 | 
					version = "0.3.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "tracing"
 | 
					name = "tracing"
 | 
				
			||||||
version = "0.1.36"
 | 
					version = "0.1.36"
 | 
				
			||||||
@@ -3582,6 +4097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			|||||||
checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307"
 | 
					checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "cfg-if",
 | 
					 "cfg-if",
 | 
				
			||||||
 | 
					 "log",
 | 
				
			||||||
 "pin-project-lite",
 | 
					 "pin-project-lite",
 | 
				
			||||||
 "tracing-attributes",
 | 
					 "tracing-attributes",
 | 
				
			||||||
 "tracing-core",
 | 
					 "tracing-core",
 | 
				
			||||||
@@ -3646,6 +4162,12 @@ dependencies = [
 | 
				
			|||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "try-lock"
 | 
				
			||||||
 | 
					version = "0.2.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "typenum"
 | 
					name = "typenum"
 | 
				
			||||||
version = "1.15.0"
 | 
					version = "1.15.0"
 | 
				
			||||||
@@ -3710,6 +4232,12 @@ dependencies = [
 | 
				
			|||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "urlencoding"
 | 
				
			||||||
 | 
					version = "2.1.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "utf-8"
 | 
					name = "utf-8"
 | 
				
			||||||
version = "0.7.6"
 | 
					version = "0.7.6"
 | 
				
			||||||
@@ -3798,6 +4326,16 @@ dependencies = [
 | 
				
			|||||||
 "winapi-util",
 | 
					 "winapi-util",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "want"
 | 
				
			||||||
 | 
					version = "0.3.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "log",
 | 
				
			||||||
 | 
					 "try-lock",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "wasi"
 | 
					name = "wasi"
 | 
				
			||||||
version = "0.9.0+wasi-snapshot-preview1"
 | 
					version = "0.9.0+wasi-snapshot-preview1"
 | 
				
			||||||
@@ -4104,12 +4642,33 @@ dependencies = [
 | 
				
			|||||||
 "windows_x86_64_msvc 0.36.1",
 | 
					 "windows_x86_64_msvc 0.36.1",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows-sys"
 | 
				
			||||||
 | 
					version = "0.42.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "windows_aarch64_gnullvm",
 | 
				
			||||||
 | 
					 "windows_aarch64_msvc 0.42.0",
 | 
				
			||||||
 | 
					 "windows_i686_gnu 0.42.0",
 | 
				
			||||||
 | 
					 "windows_i686_msvc 0.42.0",
 | 
				
			||||||
 | 
					 "windows_x86_64_gnu 0.42.0",
 | 
				
			||||||
 | 
					 "windows_x86_64_gnullvm",
 | 
				
			||||||
 | 
					 "windows_x86_64_msvc 0.42.0",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows-tokens"
 | 
					name = "windows-tokens"
 | 
				
			||||||
version = "0.37.0"
 | 
					version = "0.37.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "3263d25f1170419995b78ff10c06b949e8a986c35c208dc24333c64753a87169"
 | 
					checksum = "3263d25f1170419995b78ff10c06b949e8a986c35c208dc24333c64753a87169"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_aarch64_gnullvm"
 | 
				
			||||||
 | 
					version = "0.42.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_aarch64_msvc"
 | 
					name = "windows_aarch64_msvc"
 | 
				
			||||||
version = "0.32.0"
 | 
					version = "0.32.0"
 | 
				
			||||||
@@ -4128,6 +4687,12 @@ version = "0.37.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
 | 
					checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_aarch64_msvc"
 | 
				
			||||||
 | 
					version = "0.42.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_i686_gnu"
 | 
					name = "windows_i686_gnu"
 | 
				
			||||||
version = "0.24.0"
 | 
					version = "0.24.0"
 | 
				
			||||||
@@ -4152,6 +4717,12 @@ version = "0.37.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
 | 
					checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_i686_gnu"
 | 
				
			||||||
 | 
					version = "0.42.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_i686_msvc"
 | 
					name = "windows_i686_msvc"
 | 
				
			||||||
version = "0.24.0"
 | 
					version = "0.24.0"
 | 
				
			||||||
@@ -4176,6 +4747,12 @@ version = "0.37.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
 | 
					checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_i686_msvc"
 | 
				
			||||||
 | 
					version = "0.42.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_x86_64_gnu"
 | 
					name = "windows_x86_64_gnu"
 | 
				
			||||||
version = "0.24.0"
 | 
					version = "0.24.0"
 | 
				
			||||||
@@ -4200,6 +4777,18 @@ version = "0.37.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
 | 
					checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_x86_64_gnu"
 | 
				
			||||||
 | 
					version = "0.42.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_x86_64_gnullvm"
 | 
				
			||||||
 | 
					version = "0.42.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows_x86_64_msvc"
 | 
					name = "windows_x86_64_msvc"
 | 
				
			||||||
version = "0.24.0"
 | 
					version = "0.24.0"
 | 
				
			||||||
@@ -4224,6 +4813,12 @@ version = "0.37.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
 | 
					checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "windows_x86_64_msvc"
 | 
				
			||||||
 | 
					version = "0.42.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "winreg"
 | 
					name = "winreg"
 | 
				
			||||||
version = "0.10.1"
 | 
					version = "0.10.1"
 | 
				
			||||||
@@ -4248,7 +4843,7 @@ version = "0.5.1"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "007a0353840b23e0c6dc73e5b962ff58ed7f6bc9ceff3ce7fe6fbad8d496edf4"
 | 
					checksum = "007a0353840b23e0c6dc73e5b962ff58ed7f6bc9ceff3ce7fe6fbad8d496edf4"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "strum",
 | 
					 "strum 0.22.0",
 | 
				
			||||||
 "windows 0.24.0",
 | 
					 "windows 0.24.0",
 | 
				
			||||||
 "xml-rs",
 | 
					 "xml-rs",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
@@ -4320,3 +4915,15 @@ name = "xml-rs"
 | 
				
			|||||||
version = "0.8.4"
 | 
					version = "0.8.4"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
 | 
					checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "xmlparser"
 | 
				
			||||||
 | 
					version = "0.13.5"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "zeroize"
 | 
				
			||||||
 | 
					version = "1.5.7"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,12 +17,20 @@ tauri-build = { version = "1.0.4", features = [] }
 | 
				
			|||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
serde_json = "1.0"
 | 
					serde_json = "1.0"
 | 
				
			||||||
serde = { version = "1.0", features = ["derive"] }
 | 
					serde = { version = "1.0", features = ["derive"] }
 | 
				
			||||||
tauri = { version = "1.0.5", features = ["api-all"] }
 | 
					tauri = { version = "1.0.5", features = ["api-all", "system-tray"] }
 | 
				
			||||||
sodiumoxide = "0.2.7"
 | 
					sodiumoxide = "0.2.7"
 | 
				
			||||||
tokio = { version = ">=1.19", features = ["full"] }
 | 
					tokio = { version = ">=1.19", features = ["full"] }
 | 
				
			||||||
sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls"] }
 | 
					sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls"] }
 | 
				
			||||||
netstat2 = "0.9.1"
 | 
					netstat2 = "0.9.1"
 | 
				
			||||||
sysinfo = "0.26.8"
 | 
					sysinfo = "0.26.8"
 | 
				
			||||||
 | 
					aws-types = "0.52.0"
 | 
				
			||||||
 | 
					aws-sdk-sts = "0.22.0"
 | 
				
			||||||
 | 
					aws-smithy-types = "0.52.0"
 | 
				
			||||||
 | 
					aws-config = "0.52.0"
 | 
				
			||||||
 | 
					thiserror = "1.0.38"
 | 
				
			||||||
 | 
					once_cell = "1.16.0"
 | 
				
			||||||
 | 
					strum = "0.24"
 | 
				
			||||||
 | 
					strum_macros = "0.24"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[features]
 | 
					[features]
 | 
				
			||||||
# by default Tauri runs in production mode
 | 
					# by default Tauri runs in production mode
 | 
				
			||||||
@@ -31,3 +39,6 @@ default = [ "custom-protocol" ]
 | 
				
			|||||||
# this feature is used used for production builds where `devPath` points to the filesystem
 | 
					# this feature is used used for production builds where `devPath` points to the filesystem
 | 
				
			||||||
# DO NOT remove this
 | 
					# DO NOT remove this
 | 
				
			||||||
custom-protocol = [ "tauri/custom-protocol" ]
 | 
					custom-protocol = [ "tauri/custom-protocol" ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# [profile.dev.build-override]
 | 
				
			||||||
 | 
					# opt-level = 3
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,12 +3,13 @@ CREATE TABLE credentials (
 | 
				
			|||||||
    access_key_id TEXT NOT NULL,
 | 
					    access_key_id TEXT NOT NULL,
 | 
				
			||||||
    secret_key_enc BLOB NOT NULL,
 | 
					    secret_key_enc BLOB NOT NULL,
 | 
				
			||||||
    salt BLOB NOT NULL,
 | 
					    salt BLOB NOT NULL,
 | 
				
			||||||
    nonce BLOB NOT NULL
 | 
					    nonce BLOB NOT NULL,
 | 
				
			||||||
 | 
					    created_at INTEGER NOT NULL
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CREATE TABLE config (
 | 
					CREATE TABLE config (
 | 
				
			||||||
    name TEXT,
 | 
					    name TEXT NOT NULL,
 | 
				
			||||||
    data TEXT
 | 
					    data TEXT NOT NULL
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CREATE TABLE clients (
 | 
					CREATE TABLE clients (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,26 @@
 | 
				
			|||||||
use netstat2::{AddressFamilyFlags, ProtocolFlags, ProtocolSocketInfo};
 | 
					use netstat2::{AddressFamilyFlags, ProtocolFlags, ProtocolSocketInfo};
 | 
				
			||||||
use sysinfo::{System, SystemExt, Pid, ProcessExt};
 | 
					use sysinfo::{System, SystemExt, Pid, PidExt, ProcessExt};
 | 
				
			||||||
 | 
					use serde::{Serialize, Deserialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::errors::*;
 | 
					use crate::errors::*;
 | 
				
			||||||
use crate::ipc::Client;
 | 
					use crate::get_state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
 | 
				
			||||||
 | 
					pub struct Client {
 | 
				
			||||||
 | 
					    pub pid: u32,
 | 
				
			||||||
 | 
					    pub exe: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::error::Error> {
 | 
					fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::error::Error> {
 | 
				
			||||||
    let mut it = netstat2::iterate_sockets_info(
 | 
					    let sockets_iter = netstat2::iterate_sockets_info(
 | 
				
			||||||
        AddressFamilyFlags::IPV4,
 | 
					        AddressFamilyFlags::IPV4,
 | 
				
			||||||
        ProtocolFlags::TCP
 | 
					        ProtocolFlags::TCP
 | 
				
			||||||
    )?;
 | 
					    )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (i, item) in it.enumerate() {
 | 
					    get_state!(config as app_config);
 | 
				
			||||||
 | 
					    for item in sockets_iter {
 | 
				
			||||||
        let sock_info = item?;
 | 
					        let sock_info = item?;
 | 
				
			||||||
        let proto_info = match sock_info.protocol_socket_info {
 | 
					        let proto_info = match sock_info.protocol_socket_info {
 | 
				
			||||||
            ProtocolSocketInfo::Tcp(tcp_info) => tcp_info,
 | 
					            ProtocolSocketInfo::Tcp(tcp_info) => tcp_info,
 | 
				
			||||||
@@ -19,9 +28,9 @@ fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::error::Err
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if proto_info.local_port == local_port
 | 
					        if proto_info.local_port == local_port
 | 
				
			||||||
            && proto_info.remote_port == 12345
 | 
					            && proto_info.remote_port == app_config.listen_port
 | 
				
			||||||
            && proto_info.local_addr == std::net::Ipv4Addr::LOCALHOST
 | 
					            && proto_info.local_addr == app_config.listen_addr
 | 
				
			||||||
            && proto_info.remote_addr == std::net::Ipv4Addr::LOCALHOST
 | 
					            && proto_info.remote_addr == app_config.listen_addr
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return Ok(sock_info.associated_pids)
 | 
					            return Ok(sock_info.associated_pids)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -30,22 +39,26 @@ fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::error::Err
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Theoretically, on some systems, multiple processes can share a socket. We have to 
 | 
					// Theoretically, on some systems, multiple processes can share a socket
 | 
				
			||||||
// account for this even though 99% of the time there will be only one.
 | 
					pub fn get_clients(local_port: u16) -> Result<Vec<Option<Client>>, ClientInfoError> {
 | 
				
			||||||
pub fn get_clients(local_port: u16) -> Result<Vec<Client>, ClientInfoError> {
 | 
					 | 
				
			||||||
    let mut clients = Vec::new();    
 | 
					    let mut clients = Vec::new();    
 | 
				
			||||||
    let mut sys = System::new();
 | 
					    let mut sys = System::new();
 | 
				
			||||||
    for p in get_associated_pids(local_port)? {
 | 
					    for p in get_associated_pids(local_port)? {
 | 
				
			||||||
        let pid = Pid::from(p as usize);
 | 
					        let pid = Pid::from_u32(p);
 | 
				
			||||||
        sys.refresh_process(pid);
 | 
					        sys.refresh_process(pid);
 | 
				
			||||||
        let proc = sys.process(pid)
 | 
					        let proc = sys.process(pid)
 | 
				
			||||||
            .ok_or(ClientInfoError::PidNotFound)?;
 | 
					            .ok_or(ClientInfoError::ProcessNotFound)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let client = Client {
 | 
					        let client = Client {
 | 
				
			||||||
            pid: p,
 | 
					            pid: p,
 | 
				
			||||||
            exe: proc.exe().to_string_lossy().into_owned(),
 | 
					            exe: proc.exe().to_string_lossy().into_owned(),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        clients.push(client);
 | 
					        clients.push(Some(client));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if clients.is_empty() {
 | 
				
			||||||
 | 
					        clients.push(None); 
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(clients)
 | 
					    Ok(clients)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										80
									
								
								src-tauri/src/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src-tauri/src/config.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					use std::net::Ipv4Addr;
 | 
				
			||||||
 | 
					use std::path::PathBuf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use serde::{Serialize, Deserialize};
 | 
				
			||||||
 | 
					use sqlx::SqlitePool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::errors::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, Serialize, Deserialize)]
 | 
				
			||||||
 | 
					pub struct AppConfig {
 | 
				
			||||||
 | 
					    #[serde(default = "default_listen_addr")]
 | 
				
			||||||
 | 
					    pub listen_addr: Ipv4Addr,
 | 
				
			||||||
 | 
					    #[serde(default = "default_listen_port")]
 | 
				
			||||||
 | 
					    pub listen_port: u16,
 | 
				
			||||||
 | 
					    #[serde(default = "default_rehide_ms")]
 | 
				
			||||||
 | 
					    pub rehide_ms: u64,
 | 
				
			||||||
 | 
					    #[serde(default = "default_start_minimized")]
 | 
				
			||||||
 | 
					    pub start_minimized: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for AppConfig {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        AppConfig {
 | 
				
			||||||
 | 
					            listen_addr: default_listen_addr(),
 | 
				
			||||||
 | 
					            listen_port: default_listen_port(),
 | 
				
			||||||
 | 
					            rehide_ms: default_rehide_ms(),
 | 
				
			||||||
 | 
					            start_minimized: default_start_minimized(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn load(pool: &SqlitePool) -> Result<AppConfig, SetupError> {
 | 
				
			||||||
 | 
					    let res = sqlx::query!("SELECT * from config where name = 'main'")
 | 
				
			||||||
 | 
					        .fetch_optional(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let row = match res {
 | 
				
			||||||
 | 
					        Some(row) => row,
 | 
				
			||||||
 | 
					        None => return Ok(AppConfig::default()),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(serde_json::from_str(&row.data)?)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get_or_create_db_path() -> PathBuf {
 | 
				
			||||||
 | 
					    if cfg!(debug_assertions) {
 | 
				
			||||||
 | 
					        return PathBuf::from("./creddy.db");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut parent = std::env::var("HOME")
 | 
				
			||||||
 | 
					        .map(|h| {
 | 
				
			||||||
 | 
					            let mut p = PathBuf::from(h);
 | 
				
			||||||
 | 
					            p.push(".config");
 | 
				
			||||||
 | 
					            p
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .unwrap_or(PathBuf::from("."));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parent.push("creddy.db");
 | 
				
			||||||
 | 
					    parent
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn default_listen_port() -> u16 {
 | 
				
			||||||
 | 
					    if cfg!(debug_assertions) {
 | 
				
			||||||
 | 
					        12_345
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        19_923
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn default_listen_addr() -> Ipv4Addr { Ipv4Addr::LOCALHOST }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn default_rehide_ms() -> u64 { 1000 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn default_start_minimized() -> bool { !cfg!(debug_assertions) } // default to start-minimized in production only
 | 
				
			||||||
@@ -1,144 +1,243 @@
 | 
				
			|||||||
use std::fmt::{Display, Formatter};
 | 
					use std::error::Error;
 | 
				
			||||||
use std::convert::From;
 | 
					use std::convert::AsRef;
 | 
				
			||||||
use std::str::Utf8Error;
 | 
					use strum_macros::AsRefStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use thiserror::Error as ThisError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use aws_sdk_sts::{
 | 
				
			||||||
 | 
					    types::SdkError as AwsSdkError, 
 | 
				
			||||||
 | 
					    error::GetSessionTokenError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use sqlx::{
 | 
					use sqlx::{
 | 
				
			||||||
    error::Error as SqlxError,
 | 
					    error::Error as SqlxError,
 | 
				
			||||||
    migrate::MigrateError,
 | 
					    migrate::MigrateError,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use serde::{Serialize, Serializer, ser::SerializeMap};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// pub struct SerializeError<E> {
 | 
				
			||||||
 | 
					//     pub err: E,
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// impl<E: std::error::Error> Serialize for SerializeError<E>
 | 
				
			||||||
 | 
					// {
 | 
				
			||||||
 | 
					//     fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
 | 
				
			||||||
 | 
					//         let mut map = serializer.serialize_map(None)?;
 | 
				
			||||||
 | 
					//         map.serialize_entry("msg", &format!("{}", self.err))?;
 | 
				
			||||||
 | 
					//         if let Some(src) = self.err.source() {
 | 
				
			||||||
 | 
					//             let ser_src = SerializeError { err: src };
 | 
				
			||||||
 | 
					//             map.serialize_entry("source", &ser_src)?;
 | 
				
			||||||
 | 
					//         }
 | 
				
			||||||
 | 
					//         map.end()
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// impl<E: std::error::Error> From<E> for SerializeError<E> {
 | 
				
			||||||
 | 
					//     fn from(err: E) -> Self {
 | 
				
			||||||
 | 
					//         SerializeError { err }
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn serialize_basic_err<E, S>(err: &E, serializer: S) -> Result<S::Ok, S::Error>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    E: std::error::Error + AsRef<str>,
 | 
				
			||||||
 | 
					    S: Serializer,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let mut map = serializer.serialize_map(None)?;
 | 
				
			||||||
 | 
					    map.serialize_entry("code", err.as_ref())?;
 | 
				
			||||||
 | 
					    map.serialize_entry("msg", &format!("{err}"))?;
 | 
				
			||||||
 | 
					    if let Some(src) = err.source() {
 | 
				
			||||||
 | 
					        map.serialize_entry("source", &format!("{src}"))?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    map.end()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn serialize_upstream_err<E, M>(err: &E, map: &mut M) -> Result<(), M::Error> 
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    E: Error,
 | 
				
			||||||
 | 
					    M: serde::ser::SerializeMap,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let src = err.source().map(|s| format!("{s}"));
 | 
				
			||||||
 | 
					    map.serialize_entry("source", &src)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					macro_rules! impl_serialize_basic {
 | 
				
			||||||
 | 
					    ($err_type:ident) => {
 | 
				
			||||||
 | 
					        impl Serialize for $err_type {
 | 
				
			||||||
 | 
					            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
 | 
				
			||||||
 | 
					                serialize_basic_err(self, serializer)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// error during initial setup (primarily loading state from db)
 | 
					// error during initial setup (primarily loading state from db)
 | 
				
			||||||
 | 
					#[derive(Debug, ThisError, AsRefStr)]
 | 
				
			||||||
pub enum SetupError {
 | 
					pub enum SetupError {
 | 
				
			||||||
 | 
					    #[error("Invalid database record")]
 | 
				
			||||||
    InvalidRecord, // e.g. wrong size blob for nonce or salt
 | 
					    InvalidRecord, // e.g. wrong size blob for nonce or salt
 | 
				
			||||||
    DbError(SqlxError),
 | 
					    #[error("Error from database: {0}")]
 | 
				
			||||||
}
 | 
					    DbError(#[from] SqlxError),
 | 
				
			||||||
impl From<SqlxError> for SetupError {
 | 
					    #[error("Error running migrations: {0}")]
 | 
				
			||||||
    fn from(e: SqlxError) -> SetupError {
 | 
					    MigrationError(#[from] MigrateError),
 | 
				
			||||||
        SetupError::DbError(e)
 | 
					    #[error("Error parsing configuration from database")]
 | 
				
			||||||
    }
 | 
					    ConfigParseError(#[from] serde_json::Error),
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
impl From<MigrateError> for SetupError {
 | 
					 | 
				
			||||||
    fn from (e: MigrateError) -> SetupError {
 | 
					 | 
				
			||||||
        SetupError::DbError(SqlxError::from(e))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
impl Display for SetupError {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            SetupError::InvalidRecord => write!(f, "Malformed database record"),
 | 
					 | 
				
			||||||
            SetupError::DbError(e) => write!(f, "Error from database: {e}"),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// error when attempting to tell a request handler whether to release or deny crednetials
 | 
					// error when attempting to tell a request handler whether to release or deny credentials
 | 
				
			||||||
 | 
					#[derive(Debug, ThisError, AsRefStr)]
 | 
				
			||||||
pub enum SendResponseError {
 | 
					pub enum SendResponseError {
 | 
				
			||||||
 | 
					    #[error("The specified credentials request was not found")]
 | 
				
			||||||
    NotFound, // no request with the given id
 | 
					    NotFound, // no request with the given id
 | 
				
			||||||
 | 
					    #[error("The specified request was already closed by the client")]
 | 
				
			||||||
    Abandoned, // request has already been closed by client
 | 
					    Abandoned, // request has already been closed by client
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Display for SendResponseError {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
 | 
					 | 
				
			||||||
        use SendResponseError::*;
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            NotFound => write!(f, "The specified command was not found."),
 | 
					 | 
				
			||||||
            Abandoned => write!(f, "The specified request was closed by the client."),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// errors encountered while handling an HTTP request
 | 
					// errors encountered while handling an HTTP request
 | 
				
			||||||
 | 
					#[derive(Debug, ThisError, AsRefStr)]
 | 
				
			||||||
pub enum RequestError {
 | 
					pub enum RequestError {
 | 
				
			||||||
    StreamIOError(std::io::Error),
 | 
					    #[error("Error writing to stream: {0}")]
 | 
				
			||||||
    InvalidUtf8,
 | 
					    StreamIOError(#[from] std::io::Error),
 | 
				
			||||||
    MalformedHttpRequest,
 | 
					    // #[error("Received invalid UTF-8 in request")]
 | 
				
			||||||
 | 
					    // InvalidUtf8,
 | 
				
			||||||
 | 
					    // MalformedHttpRequest,
 | 
				
			||||||
 | 
					    #[error("HTTP request too large")]
 | 
				
			||||||
    RequestTooLarge,
 | 
					    RequestTooLarge,
 | 
				
			||||||
    NoCredentials(GetCredentialsError),
 | 
					    #[error("Error accessing credentials: {0}")]
 | 
				
			||||||
    ClientInfo(ClientInfoError),
 | 
					    NoCredentials(#[from] GetCredentialsError),
 | 
				
			||||||
}
 | 
					    #[error("Error getting client details: {0}")]
 | 
				
			||||||
impl From<tokio::io::Error> for RequestError {
 | 
					    ClientInfo(#[from] ClientInfoError),
 | 
				
			||||||
    fn from(e: std::io::Error) -> RequestError {
 | 
					    #[error("Error from Tauri: {0}")]
 | 
				
			||||||
        RequestError::StreamIOError(e)
 | 
					    Tauri(#[from] tauri::Error),
 | 
				
			||||||
    }
 | 
					    #[error("No main application window found")]
 | 
				
			||||||
}
 | 
					    NoMainWindow,
 | 
				
			||||||
impl From<Utf8Error> for RequestError {
 | 
					 | 
				
			||||||
    fn from(_e: Utf8Error) -> RequestError {
 | 
					 | 
				
			||||||
        RequestError::InvalidUtf8
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
impl From<GetCredentialsError> for RequestError {
 | 
					 | 
				
			||||||
    fn from (e: GetCredentialsError) -> RequestError {
 | 
					 | 
				
			||||||
        RequestError::NoCredentials(e)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
impl From<ClientInfoError> for RequestError {
 | 
					 | 
				
			||||||
    fn from(e: ClientInfoError) -> RequestError {
 | 
					 | 
				
			||||||
        RequestError::ClientInfo(e)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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"),
 | 
					 | 
				
			||||||
            NoCredentials(GetCredentialsError::Locked) => write!(f, "Recieved go-ahead but app is locked"),
 | 
					 | 
				
			||||||
            NoCredentials(GetCredentialsError::Empty) => write!(f, "Received go-ahead but no credentials are known"),
 | 
					 | 
				
			||||||
            ClientInfo(ClientInfoError::PidNotFound) => write!(f, "Could not resolve PID of client process."),
 | 
					 | 
				
			||||||
            ClientInfo(ClientInfoError::NetstatError(e)) => write!(f, "Error getting client socket details: {e}"),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, ThisError, AsRefStr)]
 | 
				
			||||||
pub enum GetCredentialsError {
 | 
					pub enum GetCredentialsError {
 | 
				
			||||||
 | 
					    #[error("Credentials are currently locked")]
 | 
				
			||||||
    Locked,
 | 
					    Locked,
 | 
				
			||||||
 | 
					    #[error("No credentials are known")]
 | 
				
			||||||
    Empty,
 | 
					    Empty,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, ThisError, AsRefStr)]
 | 
				
			||||||
 | 
					pub enum GetSessionError {
 | 
				
			||||||
 | 
					    #[error("Request completed successfully but no credentials were returned")]
 | 
				
			||||||
 | 
					    NoCredentials, // SDK returned successfully but credentials are None
 | 
				
			||||||
 | 
					    #[error("Error response from AWS SDK: {0}")]
 | 
				
			||||||
 | 
					    SdkError(#[from] AwsSdkError<GetSessionTokenError>),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, ThisError, AsRefStr)]
 | 
				
			||||||
pub enum UnlockError {
 | 
					pub enum UnlockError {
 | 
				
			||||||
 | 
					    #[error("App is not locked")]
 | 
				
			||||||
    NotLocked,
 | 
					    NotLocked,
 | 
				
			||||||
 | 
					    #[error("No saved credentials were found")]
 | 
				
			||||||
    NoCredentials,
 | 
					    NoCredentials,
 | 
				
			||||||
 | 
					    #[error("Invalid passphrase")]
 | 
				
			||||||
    BadPassphrase,
 | 
					    BadPassphrase,
 | 
				
			||||||
 | 
					    #[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
 | 
				
			||||||
    DbError(SqlxError),
 | 
					    #[error("Database error: {0}")]
 | 
				
			||||||
}
 | 
					    DbError(#[from] SqlxError),
 | 
				
			||||||
impl From<SqlxError> for UnlockError {
 | 
					    #[error("Failed to create AWS session: {0}")]
 | 
				
			||||||
    fn from (e: SqlxError) -> UnlockError {
 | 
					    GetSession(#[from] GetSessionError),
 | 
				
			||||||
        match e {
 | 
					 | 
				
			||||||
            SqlxError::RowNotFound => UnlockError::NoCredentials,
 | 
					 | 
				
			||||||
            _ => UnlockError::DbError(e),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
impl Display for UnlockError {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
 | 
					 | 
				
			||||||
        use UnlockError::*;
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            NotLocked => write!(f, "App is not locked"),
 | 
					 | 
				
			||||||
            NoCredentials => write!(f, "No saved credentials were found"),
 | 
					 | 
				
			||||||
            BadPassphrase => write!(f, "Invalid passphrase"),
 | 
					 | 
				
			||||||
            InvalidUtf8 => write!(f, "Decrypted data was corrupted"),
 | 
					 | 
				
			||||||
            DbError(e) => write!(f, "Database error: {e}"),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 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)]
 | 
				
			||||||
pub enum ClientInfoError {
 | 
					pub enum ClientInfoError {
 | 
				
			||||||
    PidNotFound,
 | 
					    #[error("Found PID for client socket, but no corresponding process")]
 | 
				
			||||||
    NetstatError(netstat2::error::Error),
 | 
					    ProcessNotFound,
 | 
				
			||||||
 | 
					    #[error("Couldn't get client socket details: {0}")]
 | 
				
			||||||
 | 
					    NetstatError(#[from] netstat2::error::Error),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
impl From<netstat2::error::Error> for ClientInfoError {
 | 
					
 | 
				
			||||||
    fn from(e: netstat2::error::Error) -> ClientInfoError {
 | 
					
 | 
				
			||||||
        ClientInfoError::NetstatError(e)
 | 
					// =========================
 | 
				
			||||||
 | 
					// Serialize implementations
 | 
				
			||||||
 | 
					// =========================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct SerializeWrapper<E>(pub E);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Serialize for SerializeWrapper<&GetSessionTokenError> {
 | 
				
			||||||
 | 
					    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
 | 
				
			||||||
 | 
					        let err = self.0;
 | 
				
			||||||
 | 
					        let mut map = serializer.serialize_map(None)?;
 | 
				
			||||||
 | 
					        map.serialize_entry("code", &err.code())?;
 | 
				
			||||||
 | 
					        map.serialize_entry("msg", &err.message())?;
 | 
				
			||||||
 | 
					        map.serialize_entry("source", &None::<&str>)?;
 | 
				
			||||||
 | 
					        map.end()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl_serialize_basic!(SetupError);
 | 
				
			||||||
 | 
					impl_serialize_basic!(SendResponseError);
 | 
				
			||||||
 | 
					impl_serialize_basic!(GetCredentialsError);
 | 
				
			||||||
 | 
					impl_serialize_basic!(ClientInfoError);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Serialize for RequestError {
 | 
				
			||||||
 | 
					    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
 | 
				
			||||||
 | 
					        let mut map = serializer.serialize_map(None)?;
 | 
				
			||||||
 | 
					        map.serialize_entry("code", self.as_ref())?;
 | 
				
			||||||
 | 
					        map.serialize_entry("msg", &format!("{self}"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            RequestError::NoCredentials(src) => map.serialize_entry("source", &src)?,
 | 
				
			||||||
 | 
					            RequestError::ClientInfo(src) => map.serialize_entry("source", &src)?,
 | 
				
			||||||
 | 
					            _ => serialize_upstream_err(self, &mut map)?,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        map.end()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Serialize for GetSessionError {
 | 
				
			||||||
 | 
					    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
 | 
				
			||||||
 | 
					        let mut map = serializer.serialize_map(None)?;
 | 
				
			||||||
 | 
					        map.serialize_entry("code", self.as_ref())?;
 | 
				
			||||||
 | 
					        map.serialize_entry("msg", &format!("{self}"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            GetSessionError::SdkError(AwsSdkError::ServiceError(se_wrapper)) => {
 | 
				
			||||||
 | 
					                let err = se_wrapper.err();
 | 
				
			||||||
 | 
					                map.serialize_entry("source", &SerializeWrapper(err))?
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => serialize_upstream_err(self, &mut map)?,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        map.end()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Serialize for UnlockError {
 | 
				
			||||||
 | 
					    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
 | 
				
			||||||
 | 
					        let mut map = serializer.serialize_map(None)?;
 | 
				
			||||||
 | 
					        map.serialize_entry("code", self.as_ref())?;
 | 
				
			||||||
 | 
					        map.serialize_entry("msg", &format!("{self}"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            UnlockError::GetSession(src) => map.serialize_entry("source", &src)?,
 | 
				
			||||||
 | 
					            _ => serialize_upstream_err(self, &mut map)?,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        map.end()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,31 +1,27 @@
 | 
				
			|||||||
use serde::{Serialize, Deserialize};
 | 
					use serde::{Serialize, Deserialize};
 | 
				
			||||||
use tauri::State;
 | 
					use tauri::State;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::errors::*;
 | 
				
			||||||
 | 
					use crate::config::AppConfig;
 | 
				
			||||||
 | 
					use crate::clientinfo::Client;
 | 
				
			||||||
use crate::state::{AppState, Session, Credentials};
 | 
					use crate::state::{AppState, Session, Credentials};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Serialize, Deserialize)]
 | 
					#[derive(Clone, Debug, Serialize, Deserialize)]
 | 
				
			||||||
pub struct Client {
 | 
					 | 
				
			||||||
    pub pid: u32,
 | 
					 | 
				
			||||||
    pub exe: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Clone, Serialize, Deserialize)]
 | 
					 | 
				
			||||||
pub struct Request {
 | 
					pub struct Request {
 | 
				
			||||||
    pub id: u64,
 | 
					    pub id: u64,
 | 
				
			||||||
    pub clients: Vec<Client>,
 | 
					    pub clients: Vec<Option<Client>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					#[derive(Debug, Serialize, Deserialize)]
 | 
				
			||||||
pub struct RequestResponse {
 | 
					pub struct RequestResponse {
 | 
				
			||||||
    pub id: u64,
 | 
					    pub id: u64,
 | 
				
			||||||
    pub approval: Approval,
 | 
					    pub approval: Approval,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					#[derive(Debug, Serialize, Deserialize)]
 | 
				
			||||||
pub enum Approval {
 | 
					pub enum Approval {
 | 
				
			||||||
    Approved,
 | 
					    Approved,
 | 
				
			||||||
    Denied,
 | 
					    Denied,
 | 
				
			||||||
@@ -40,10 +36,8 @@ pub fn respond(response: RequestResponse, app_state: State<'_, AppState>) -> Res
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[tauri::command]
 | 
					#[tauri::command]
 | 
				
			||||||
pub async fn unlock(passphrase: String, app_state: State<'_, AppState>) -> Result<(), String> {
 | 
					pub async fn unlock(passphrase: String, app_state: State<'_, AppState>) -> Result<(), UnlockError> {
 | 
				
			||||||
    app_state.decrypt(&passphrase)
 | 
					    app_state.decrypt(&passphrase).await
 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .map_err(|e| e.to_string())
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,8 +57,20 @@ pub async fn save_credentials(
 | 
				
			|||||||
    credentials: Credentials,
 | 
					    credentials: Credentials,
 | 
				
			||||||
    passphrase: String,
 | 
					    passphrase: String,
 | 
				
			||||||
    app_state: State<'_, AppState>
 | 
					    app_state: State<'_, AppState>
 | 
				
			||||||
) -> Result<(), String> {
 | 
					) -> Result<(), UnlockError> {
 | 
				
			||||||
    app_state.save_creds(credentials, &passphrase)
 | 
					    app_state.save_creds(credentials, &passphrase).await
 | 
				
			||||||
        .await
 | 
					}
 | 
				
			||||||
        .map_err(|e| e.to_string())
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[tauri::command]
 | 
				
			||||||
 | 
					pub fn get_config(app_state: State<'_, AppState>) -> AppConfig {
 | 
				
			||||||
 | 
					    let config = app_state.config.read().unwrap();
 | 
				
			||||||
 | 
					    config.clone()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[tauri::command]
 | 
				
			||||||
 | 
					pub fn save_config(config: AppConfig, app_state: State<'_, AppState>) {
 | 
				
			||||||
 | 
					    let mut prev_config = app_state.config.write().unwrap();
 | 
				
			||||||
 | 
					    *prev_config = config;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,35 +3,99 @@
 | 
				
			|||||||
    windows_subsystem = "windows"
 | 
					    windows_subsystem = "windows"
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::str::FromStr;
 | 
					use tauri::{AppHandle, Manager, async_runtime as rt};
 | 
				
			||||||
 | 
					use once_cell::sync::OnceCell;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod config;
 | 
				
			||||||
mod errors;
 | 
					mod errors;
 | 
				
			||||||
mod clientinfo;
 | 
					mod clientinfo;
 | 
				
			||||||
mod ipc;
 | 
					mod ipc;
 | 
				
			||||||
mod state;
 | 
					mod state;
 | 
				
			||||||
mod server;
 | 
					mod server;
 | 
				
			||||||
mod storage;
 | 
					mod tray;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::errors::*;
 | 
				
			||||||
 | 
					use state::AppState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub static APP: OnceCell<AppHandle> = OnceCell::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
    let initial_state = match state::AppState::new() {
 | 
					    let initial_state = match rt::block_on(state::AppState::load()) {
 | 
				
			||||||
        Ok(state) => state,
 | 
					        Ok(state) => state,
 | 
				
			||||||
        Err(e) => {eprintln!("{}", e); return;}
 | 
					        Err(e) => {eprintln!("{}", e); return;}
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tauri::Builder::default()
 | 
					    tauri::Builder::default()
 | 
				
			||||||
        .manage(initial_state)
 | 
					        .manage(initial_state)
 | 
				
			||||||
 | 
					        .system_tray(tray::create())
 | 
				
			||||||
 | 
					        .on_system_tray_event(tray::handle_event)
 | 
				
			||||||
        .invoke_handler(tauri::generate_handler![
 | 
					        .invoke_handler(tauri::generate_handler![
 | 
				
			||||||
            ipc::unlock,
 | 
					            ipc::unlock,
 | 
				
			||||||
            ipc::respond,
 | 
					            ipc::respond,
 | 
				
			||||||
            ipc::get_session_status,
 | 
					            ipc::get_session_status,
 | 
				
			||||||
            ipc::save_credentials,
 | 
					            ipc::save_credentials,
 | 
				
			||||||
 | 
					            ipc::get_config,
 | 
				
			||||||
 | 
					            ipc::save_config,
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
        .setup(|app| {
 | 
					        .setup(|app| {
 | 
				
			||||||
            let addr = std::net::SocketAddrV4::from_str("127.0.0.1:12345").unwrap();
 | 
					            APP.set(app.handle()).unwrap();
 | 
				
			||||||
 | 
					            let state = app.state::<AppState>();
 | 
				
			||||||
 | 
					            let config = state.config.read().unwrap();
 | 
				
			||||||
 | 
					            let addr = std::net::SocketAddrV4::new(config.listen_addr, config.listen_port);
 | 
				
			||||||
            tauri::async_runtime::spawn(server::serve(addr, app.handle()));
 | 
					            tauri::async_runtime::spawn(server::serve(addr, app.handle()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if !config.start_minimized {
 | 
				
			||||||
 | 
					                app.get_window("main")
 | 
				
			||||||
 | 
					                    .ok_or(RequestError::NoMainWindow)?
 | 
				
			||||||
 | 
					                    .show()?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            Ok(())
 | 
					            Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .run(tauri::generate_context!())
 | 
					        .build(tauri::generate_context!())
 | 
				
			||||||
        .expect("error while running tauri application");
 | 
					        .expect("error while running tauri application")
 | 
				
			||||||
 | 
					        .run(|app, run_event| match run_event {
 | 
				
			||||||
 | 
					            tauri::RunEvent::WindowEvent { label, event, .. } => match event {
 | 
				
			||||||
 | 
					                tauri::WindowEvent::CloseRequested { api, .. } => {
 | 
				
			||||||
 | 
					                    let _ = app.get_window(&label).map(|w| w.hide());
 | 
				
			||||||
 | 
					                    api.prevent_close();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                _ => ()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => ()
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					macro_rules! get_state {
 | 
				
			||||||
 | 
					    ($prop:ident as $name:ident) => {
 | 
				
			||||||
 | 
					        use tauri::Manager;
 | 
				
			||||||
 | 
					        let app = crate::APP.get().unwrap(); // as long as the app is running, this is fine
 | 
				
			||||||
 | 
					        let state = app.state::<crate::state::AppState>();
 | 
				
			||||||
 | 
					        let $name = state.$prop.read().unwrap(); // only panics if another thread has already panicked
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    (config.$prop:ident as $name:ident) => {
 | 
				
			||||||
 | 
					        use tauri::Manager;
 | 
				
			||||||
 | 
					        let app = crate::APP.get().unwrap();
 | 
				
			||||||
 | 
					        let state = app.state::<crate::state::AppState>();
 | 
				
			||||||
 | 
					        let config = state.config.read().unwrap();
 | 
				
			||||||
 | 
					        let $name = config.$prop;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (mut $prop:ident as $name:ident) => {
 | 
				
			||||||
 | 
					        use tauri::Manager;
 | 
				
			||||||
 | 
					        let app = crate::APP.get().unwrap();
 | 
				
			||||||
 | 
					        let state = app.state::<crate::state::AppState>();
 | 
				
			||||||
 | 
					        let $name = state.$prop.write().unwrap();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    (mut config.$prop:ident as $name:ident) => {
 | 
				
			||||||
 | 
					        use tauri::Manager;
 | 
				
			||||||
 | 
					        let app = crate::APP.get().unwrap();
 | 
				
			||||||
 | 
					        let state = app.state::<crate::state::AppState>();
 | 
				
			||||||
 | 
					        let config = state.config.write().unwrap();
 | 
				
			||||||
 | 
					        let $name = config.$prop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) use get_state;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,28 +1,172 @@
 | 
				
			|||||||
 | 
					use core::time::Duration;
 | 
				
			||||||
use std::io;
 | 
					use std::io;
 | 
				
			||||||
use std::net::SocketAddrV4;
 | 
					use std::net::{SocketAddr, SocketAddrV4};
 | 
				
			||||||
use tokio::net::{TcpListener, TcpStream};
 | 
					use tokio::net::{TcpListener, TcpStream};
 | 
				
			||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
 | 
					use tokio::io::{AsyncReadExt, AsyncWriteExt};
 | 
				
			||||||
use tokio::sync::oneshot;
 | 
					use tokio::sync::oneshot;
 | 
				
			||||||
 | 
					use tokio::time::sleep;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use tauri::{AppHandle, Manager};
 | 
					use tauri::{AppHandle, Manager};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::clientinfo;
 | 
					use crate::{clientinfo, clientinfo::Client};
 | 
				
			||||||
use crate::errors::RequestError;
 | 
					use crate::errors::*;
 | 
				
			||||||
use crate::ipc::{Request, Approval};
 | 
					use crate::ipc::{Request, Approval};
 | 
				
			||||||
 | 
					use crate::state::AppState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Handler {
 | 
				
			||||||
 | 
					    request_id: u64,
 | 
				
			||||||
 | 
					    stream: TcpStream,
 | 
				
			||||||
 | 
					    receiver: Option<oneshot::Receiver<Approval>>,
 | 
				
			||||||
 | 
					    app: AppHandle,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Handler {
 | 
				
			||||||
 | 
					    fn new(stream: TcpStream, app: AppHandle) -> Self {
 | 
				
			||||||
 | 
					        let state = app.state::<AppState>();
 | 
				
			||||||
 | 
					        let (chan_send, chan_recv) = oneshot::channel();
 | 
				
			||||||
 | 
					        let request_id = state.register_request(chan_send);
 | 
				
			||||||
 | 
					        Handler { 
 | 
				
			||||||
 | 
					            request_id,
 | 
				
			||||||
 | 
					            stream,
 | 
				
			||||||
 | 
					            receiver: Some(chan_recv),
 | 
				
			||||||
 | 
					            app
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn handle(mut self) {
 | 
				
			||||||
 | 
					        if let Err(e) = self.try_handle().await {
 | 
				
			||||||
 | 
					            eprintln!("{e}");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let state = self.app.state::<AppState>();
 | 
				
			||||||
 | 
					        state.unregister_request(self.request_id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn try_handle(&mut self) -> Result<(), RequestError> {
 | 
				
			||||||
 | 
					        let _ = self.recv_request().await?;
 | 
				
			||||||
 | 
					        let clients = self.get_clients()?;
 | 
				
			||||||
 | 
					        if self.includes_banned(&clients) {
 | 
				
			||||||
 | 
					            self.stream.write(b"HTTP/1.0 403 Access Denied\r\n\r\n").await?;
 | 
				
			||||||
 | 
					            return Ok(())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let req = Request {id: self.request_id, clients};
 | 
				
			||||||
 | 
					        self.app.emit_all("credentials-request", &req)?;
 | 
				
			||||||
 | 
					        let starting_visibility = self.show_window()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match self.wait_for_response().await? {
 | 
				
			||||||
 | 
					            Approval::Approved => self.send_credentials().await?,
 | 
				
			||||||
 | 
					            Approval::Denied => {
 | 
				
			||||||
 | 
					                let state = self.app.state::<AppState>();
 | 
				
			||||||
 | 
					                for client in req.clients {
 | 
				
			||||||
 | 
					                    state.add_ban(client, self.app.clone());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // only hide the window if a) it was hidden to start with
 | 
				
			||||||
 | 
					        // and b) there are no other pending requests
 | 
				
			||||||
 | 
					        let state = self.app.state::<AppState>();
 | 
				
			||||||
 | 
					        let delay = {
 | 
				
			||||||
 | 
					            let config = state.config.read().unwrap();
 | 
				
			||||||
 | 
					            Duration::from_millis(config.rehide_ms)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        sleep(delay).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if !starting_visibility && state.req_count() == 0 {
 | 
				
			||||||
 | 
					            let window = self.app.get_window("main").ok_or(RequestError::NoMainWindow)?;
 | 
				
			||||||
 | 
					            window.hide()?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn recv_request(&mut self) -> Result<Vec<u8>, RequestError> {
 | 
				
			||||||
 | 
					        let mut buf = vec![0; 8192]; // it's what tokio's BufReader uses
 | 
				
			||||||
 | 
					        let mut n = 0;
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            n += self.stream.read(&mut buf[n..]).await?;
 | 
				
			||||||
 | 
					            if n >= 4 && &buf[(n - 4)..n] == b"\r\n\r\n" {break;}
 | 
				
			||||||
 | 
					            if n == buf.len() {return Err(RequestError::RequestTooLarge);}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if cfg!(debug_assertions) {
 | 
				
			||||||
 | 
					            println!("{}", std::str::from_utf8(&buf).unwrap());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(buf)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_clients(&self) -> Result<Vec<Option<Client>>, RequestError> {
 | 
				
			||||||
 | 
					        let peer_addr = match self.stream.peer_addr()? {
 | 
				
			||||||
 | 
					            SocketAddr::V4(addr) => addr,
 | 
				
			||||||
 | 
					            _ => unreachable!(), // we only listen on IPv4
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let clients = clientinfo::get_clients(peer_addr.port())?;
 | 
				
			||||||
 | 
					        Ok(clients)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn includes_banned(&self, clients: &Vec<Option<Client>>) -> bool {
 | 
				
			||||||
 | 
					        let state = self.app.state::<AppState>();
 | 
				
			||||||
 | 
					        clients.iter().any(|c| state.is_banned(c))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn show_window(&self) -> Result<bool, RequestError> {
 | 
				
			||||||
 | 
					        let window = self.app.get_window("main").ok_or(RequestError::NoMainWindow)?;
 | 
				
			||||||
 | 
					        let starting_visibility = window.is_visible()?;
 | 
				
			||||||
 | 
					        if !starting_visibility {
 | 
				
			||||||
 | 
					            window.unminimize()?;
 | 
				
			||||||
 | 
					            window.show()?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        window.set_focus()?;
 | 
				
			||||||
 | 
					        Ok(starting_visibility)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn wait_for_response(&mut self) -> Result<Approval, RequestError> {
 | 
				
			||||||
 | 
					        self.stream.write(b"HTTP/1.0 200 OK\r\n").await?;
 | 
				
			||||||
 | 
					        self.stream.write(b"Content-Type: application/json\r\n").await?;
 | 
				
			||||||
 | 
					        self.stream.write(b"X-Creddy-delaying-tactic: ").await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #[allow(unreachable_code)] // seems necessary for type inference
 | 
				
			||||||
 | 
					        let stall = async {
 | 
				
			||||||
 | 
					            let delay = std::time::Duration::from_secs(1);
 | 
				
			||||||
 | 
					            loop {
 | 
				
			||||||
 | 
					                tokio::time::sleep(delay).await;
 | 
				
			||||||
 | 
					                self.stream.write(b"x").await?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Ok(Approval::Denied)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // this is the only place we even read this field, so it's safe to unwrap
 | 
				
			||||||
 | 
					        let receiver = self.receiver.take().unwrap();
 | 
				
			||||||
 | 
					        tokio::select!{
 | 
				
			||||||
 | 
					            r = receiver => Ok(r.unwrap()), // only panics if the sender is dropped without sending, which shouldn't be possible
 | 
				
			||||||
 | 
					            e = stall => e,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn send_credentials(&mut self) -> Result<(), RequestError> {
 | 
				
			||||||
 | 
					        let state = self.app.state::<AppState>();
 | 
				
			||||||
 | 
					        let creds = state.get_creds_serialized()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.stream.write(b"\r\nContent-Length: ").await?;
 | 
				
			||||||
 | 
					        self.stream.write(creds.as_bytes().len().to_string().as_bytes()).await?;
 | 
				
			||||||
 | 
					        self.stream.write(b"\r\n\r\n").await?;
 | 
				
			||||||
 | 
					        self.stream.write(creds.as_bytes()).await?;
 | 
				
			||||||
 | 
					        self.stream.write(b"\r\n\r\n").await?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()> {
 | 
					pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()> {
 | 
				
			||||||
    let listener = TcpListener::bind(&addr).await?;
 | 
					    let listener = TcpListener::bind(&addr).await?;
 | 
				
			||||||
    println!("Listening on {addr}");
 | 
					    println!("Listening on {addr}");
 | 
				
			||||||
    loop {
 | 
					    loop {
 | 
				
			||||||
        let new_handle = app_handle.app_handle();
 | 
					 | 
				
			||||||
        match listener.accept().await {
 | 
					        match listener.accept().await {
 | 
				
			||||||
            Ok((stream, _)) => {
 | 
					            Ok((stream, _)) => {
 | 
				
			||||||
                tokio::spawn(async {
 | 
					                let handler = Handler::new(stream, app_handle.app_handle());
 | 
				
			||||||
                    if let Err(e) = handle(stream, new_handle).await {
 | 
					                tauri::async_runtime::spawn(handler.handle());
 | 
				
			||||||
                        eprintln!("{e}");
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            Err(e) => {
 | 
					            Err(e) => {
 | 
				
			||||||
                eprintln!("Error accepting connection: {e}");
 | 
					                eprintln!("Error accepting connection: {e}");
 | 
				
			||||||
@@ -30,68 +174,3 @@ pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()>
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// it doesn't really return Approval, we just need to placate the compiler
 | 
					 | 
				
			||||||
async fn stall(stream: &mut TcpStream) -> Result<Approval, 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 (chan_send, chan_recv) = oneshot::channel();
 | 
					 | 
				
			||||||
    let app_state = app_handle.state::<crate::state::AppState>();
 | 
					 | 
				
			||||||
    let request_id = app_state.register_request(chan_send);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let peer_addr = match stream.peer_addr()? {
 | 
					 | 
				
			||||||
        std::net::SocketAddr::V4(addr) => addr,
 | 
					 | 
				
			||||||
        _ => unreachable!(), // we only listen on IPv4
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    let clients = clientinfo::get_clients(peer_addr.port())?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Do we want to panic if this fails? Does that mean the frontend is dead?
 | 
					 | 
				
			||||||
    let req = Request {id: request_id, clients};
 | 
					 | 
				
			||||||
    app_handle.emit_all("credentials-request", req).unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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 approval = tokio::select!{
 | 
					 | 
				
			||||||
        e = stall(&mut stream) => e?, // this will never return Ok, just Err if it can't write to the stream
 | 
					 | 
				
			||||||
        r = chan_recv => r.unwrap(), // only panics if the sender is dropped without sending, which shouldn't happen
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if matches!(approval, Approval::Denied) {
 | 
					 | 
				
			||||||
        // because we own the stream, it gets closed when we return. 
 | 
					 | 
				
			||||||
        // Unfortunately we've already signaled 200 OK, there's no way around this -
 | 
					 | 
				
			||||||
        // we have to write the status code first thing, and we have to assume that the user
 | 
					 | 
				
			||||||
        // might need more time than that gives us (especially if entering the passphrase).
 | 
					 | 
				
			||||||
        // Fortunately most AWS libs automatically retry if the request dies uncompleted, allowing
 | 
					 | 
				
			||||||
        // us to respond with a proper error status.
 | 
					 | 
				
			||||||
        return Ok(());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let creds = app_state.get_creds_serialized()?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,10 @@
 | 
				
			|||||||
use std::collections::HashMap;
 | 
					use core::time::Duration;
 | 
				
			||||||
 | 
					use std::collections::{HashMap, HashSet};
 | 
				
			||||||
use std::sync::RwLock;
 | 
					use std::sync::RwLock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use serde::{Serialize, Deserialize};
 | 
					use serde::{Serialize, Deserialize};
 | 
				
			||||||
use tokio::sync::oneshot::Sender;
 | 
					use tokio::sync::oneshot::Sender;
 | 
				
			||||||
 | 
					use tokio::time::sleep;
 | 
				
			||||||
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, sqlite::SqliteConnectOptions};
 | 
					use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, sqlite::SqliteConnectOptions};
 | 
				
			||||||
use sodiumoxide::crypto::{
 | 
					use sodiumoxide::crypto::{
 | 
				
			||||||
        pwhash,
 | 
					        pwhash,
 | 
				
			||||||
@@ -11,12 +13,15 @@ use sodiumoxide::crypto::{
 | 
				
			|||||||
        secretbox::{Nonce, Key}
 | 
					        secretbox::{Nonce, Key}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use tauri::async_runtime as runtime;
 | 
					use tauri::async_runtime as runtime;
 | 
				
			||||||
 | 
					use tauri::Manager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{config, config::AppConfig};
 | 
				
			||||||
use crate::ipc;
 | 
					use crate::ipc;
 | 
				
			||||||
 | 
					use crate::clientinfo::Client;
 | 
				
			||||||
use crate::errors::*;
 | 
					use crate::errors::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					#[derive(Debug, Serialize, Deserialize)]
 | 
				
			||||||
#[serde(untagged)]
 | 
					#[serde(untagged)]
 | 
				
			||||||
pub enum Credentials {
 | 
					pub enum Credentials {
 | 
				
			||||||
    #[serde(rename_all = "PascalCase")]
 | 
					    #[serde(rename_all = "PascalCase")]
 | 
				
			||||||
@@ -34,6 +39,7 @@ pub enum Credentials {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct LockedCredentials {
 | 
					pub struct LockedCredentials {
 | 
				
			||||||
    access_key_id: String,
 | 
					    access_key_id: String,
 | 
				
			||||||
    secret_key_enc: Vec<u8>,
 | 
					    secret_key_enc: Vec<u8>,
 | 
				
			||||||
@@ -42,6 +48,7 @@ pub struct LockedCredentials {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub enum Session {
 | 
					pub enum Session {
 | 
				
			||||||
    Unlocked(Credentials),
 | 
					    Unlocked(Credentials),
 | 
				
			||||||
    Locked(LockedCredentials),
 | 
					    Locked(LockedCredentials),
 | 
				
			||||||
@@ -49,36 +56,34 @@ pub enum Session {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// #[derive(Serialize, Deserialize)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
// pub enum SessionStatus {
 | 
					 | 
				
			||||||
//   Unlocked,
 | 
					 | 
				
			||||||
//   Locked,
 | 
					 | 
				
			||||||
//   Empty,
 | 
					 | 
				
			||||||
// }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct AppState {
 | 
					pub struct AppState {
 | 
				
			||||||
 | 
					    pub config: RwLock<AppConfig>,
 | 
				
			||||||
    pub session: RwLock<Session>,
 | 
					    pub session: RwLock<Session>,
 | 
				
			||||||
    pub request_count: RwLock<u64>,
 | 
					    pub request_count: RwLock<u64>,
 | 
				
			||||||
    pub open_requests: RwLock<HashMap<u64, Sender<ipc::Approval>>>,
 | 
					    pub open_requests: RwLock<HashMap<u64, Sender<ipc::Approval>>>,
 | 
				
			||||||
 | 
					    pub bans: RwLock<std::collections::HashSet<Option<Client>>>,
 | 
				
			||||||
    pool: SqlitePool,
 | 
					    pool: SqlitePool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl AppState {
 | 
					impl AppState {
 | 
				
			||||||
    pub fn new() -> Result<Self, SetupError> {
 | 
					    pub async fn load() -> Result<Self, SetupError> {
 | 
				
			||||||
        let conn_opts = SqliteConnectOptions::new()
 | 
					        let conn_opts = SqliteConnectOptions::new()
 | 
				
			||||||
            .filename("creddy.db")
 | 
					            .filename(config::get_or_create_db_path())
 | 
				
			||||||
            .create_if_missing(true);
 | 
					            .create_if_missing(true);
 | 
				
			||||||
        let pool_opts = SqlitePoolOptions::new();
 | 
					        let pool_opts = SqlitePoolOptions::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let pool: SqlitePool = runtime::block_on(pool_opts.connect_with(conn_opts))?;
 | 
					        let pool: SqlitePool = pool_opts.connect_with(conn_opts).await?;
 | 
				
			||||||
        runtime::block_on(sqlx::migrate!().run(&pool))?;
 | 
					        sqlx::migrate!().run(&pool).await?;
 | 
				
			||||||
        let creds = runtime::block_on(Self::load_creds(&pool))?;
 | 
					        let creds = Self::load_creds(&pool).await?;
 | 
				
			||||||
 | 
					        let conf = config::load(&pool).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let state = AppState {
 | 
					        let state = AppState {
 | 
				
			||||||
 | 
					            config: RwLock::new(conf),
 | 
				
			||||||
            session: RwLock::new(creds),
 | 
					            session: RwLock::new(creds),
 | 
				
			||||||
            request_count: RwLock::new(0),
 | 
					            request_count: RwLock::new(0),
 | 
				
			||||||
            open_requests: RwLock::new(HashMap::new()),
 | 
					            open_requests: RwLock::new(HashMap::new()),
 | 
				
			||||||
 | 
					            bans: RwLock::new(HashSet::new()),
 | 
				
			||||||
            pool,
 | 
					            pool,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -86,7 +91,7 @@ impl AppState {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn load_creds(pool: &SqlitePool) -> Result<Session, SetupError> {
 | 
					    async fn load_creds(pool: &SqlitePool) -> Result<Session, SetupError> {
 | 
				
			||||||
        let res = sqlx::query!("SELECT * FROM credentials")
 | 
					        let res = sqlx::query!("SELECT * FROM credentials ORDER BY created_at desc")
 | 
				
			||||||
            .fetch_optional(pool)
 | 
					            .fetch_optional(pool)
 | 
				
			||||||
            .await?;
 | 
					            .await?;
 | 
				
			||||||
        let row = match res {
 | 
					        let row = match res {
 | 
				
			||||||
@@ -110,13 +115,17 @@ impl AppState {
 | 
				
			|||||||
        Ok(Session::Locked(creds))
 | 
					        Ok(Session::Locked(creds))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn save_creds(&self, creds: Credentials, passphrase: &str) -> Result<(), sqlx::error::Error> {
 | 
					    pub async fn save_creds(&self, creds: Credentials, passphrase: &str) -> Result<(), UnlockError> {
 | 
				
			||||||
        let (key_id, secret_key) = match creds {
 | 
					        let (key_id, secret_key) = match creds {
 | 
				
			||||||
            Credentials::LongLived {access_key_id, secret_access_key} => {
 | 
					            Credentials::LongLived {access_key_id, secret_access_key} => {
 | 
				
			||||||
                (access_key_id, secret_access_key)
 | 
					                (access_key_id, secret_access_key)
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            _ => unreachable!(),
 | 
					            _ => unreachable!(),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // do this first so that if it fails we don't save bad credentials
 | 
				
			||||||
 | 
					        self.new_session(&key_id, &secret_key).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let salt = pwhash::gen_salt();
 | 
					        let salt = pwhash::gen_salt();
 | 
				
			||||||
        let mut key_buf = [0; secretbox::KEYBYTES];
 | 
					        let mut key_buf = [0; secretbox::KEYBYTES];
 | 
				
			||||||
        pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &salt).unwrap();
 | 
					        pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &salt).unwrap();
 | 
				
			||||||
@@ -124,16 +133,19 @@ impl AppState {
 | 
				
			|||||||
        // not sure we need both salt AND nonce given that we generate a
 | 
					        // not sure we need both salt AND nonce given that we generate a
 | 
				
			||||||
        // fresh salt every time we encrypt, but better safe than sorry
 | 
					        // fresh salt every time we encrypt, but better safe than sorry
 | 
				
			||||||
        let nonce = secretbox::gen_nonce();
 | 
					        let nonce = secretbox::gen_nonce();
 | 
				
			||||||
        let key_enc = secretbox::seal(secret_key.as_bytes(), &nonce, &key);
 | 
					        let secret_key_enc = secretbox::seal(secret_key.as_bytes(), &nonce, &key);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // insert into database
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // eventually replace this with a temporary session
 | 
					        sqlx::query(
 | 
				
			||||||
        let mut session = self.session.write().unwrap();
 | 
					            "INSERT INTO credentials (access_key_id, secret_key_enc, salt, nonce, created_at)
 | 
				
			||||||
        *session = Session::Unlocked(Credentials::LongLived {
 | 
					            VALUES (?, ?, ?, ?, strftime('%s'))"
 | 
				
			||||||
            access_key_id: key_id,
 | 
					        )
 | 
				
			||||||
            secret_access_key: secret_key,
 | 
					            .bind(&key_id)
 | 
				
			||||||
        });
 | 
					            .bind(&secret_key_enc)
 | 
				
			||||||
 | 
					            .bind(&salt.0[0..])
 | 
				
			||||||
 | 
					            .bind(&nonce.0[0..])
 | 
				
			||||||
 | 
					            .execute(&self.pool)
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -150,6 +162,16 @@ impl AppState {
 | 
				
			|||||||
        *count
 | 
					        *count
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn unregister_request(&self, id: u64) {
 | 
				
			||||||
 | 
					        let mut open_requests = self.open_requests.write().unwrap();
 | 
				
			||||||
 | 
					        open_requests.remove(&id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn req_count(&self) -> usize {
 | 
				
			||||||
 | 
					        let open_requests = self.open_requests.read().unwrap();
 | 
				
			||||||
 | 
					        open_requests.len()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn send_response(&self, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
 | 
					    pub fn send_response(&self, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
 | 
				
			||||||
        let mut open_requests = self.open_requests.write().unwrap();
 | 
					        let mut open_requests = self.open_requests.write().unwrap();
 | 
				
			||||||
        let chan = open_requests
 | 
					        let chan = open_requests
 | 
				
			||||||
@@ -161,7 +183,25 @@ impl AppState {
 | 
				
			|||||||
            .map_err(|_e| SendResponseError::Abandoned)
 | 
					            .map_err(|_e| SendResponseError::Abandoned)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn add_ban(&self, client: Option<Client>, app: tauri::AppHandle) {
 | 
				
			||||||
 | 
					        let mut bans = self.bans.write().unwrap();
 | 
				
			||||||
 | 
					        bans.insert(client.clone());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        runtime::spawn(async move {
 | 
				
			||||||
 | 
					            sleep(Duration::from_secs(5)).await;
 | 
				
			||||||
 | 
					            let state = app.state::<AppState>();
 | 
				
			||||||
 | 
					            let mut bans = state.bans.write().unwrap();
 | 
				
			||||||
 | 
					            bans.remove(&client);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn is_banned(&self, client: &Option<Client>) -> bool {
 | 
				
			||||||
 | 
					        self.bans.read().unwrap().contains(&client)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn decrypt(&self, passphrase: &str) -> Result<(), UnlockError> {
 | 
					    pub async fn decrypt(&self, passphrase: &str) -> Result<(), UnlockError> {
 | 
				
			||||||
 | 
					        let (key_id, secret) = {
 | 
				
			||||||
 | 
					            // do this all in a block so that we aren't holding a lock across an await
 | 
				
			||||||
            let session = self.session.read().unwrap();
 | 
					            let session = self.session.read().unwrap();
 | 
				
			||||||
            let locked = match *session {
 | 
					            let locked = match *session {
 | 
				
			||||||
                Session::Empty => {return Err(UnlockError::NoCredentials);},
 | 
					                Session::Empty => {return Err(UnlockError::NoCredentials);},
 | 
				
			||||||
@@ -176,12 +216,11 @@ impl AppState {
 | 
				
			|||||||
                .map_err(|_e| UnlockError::BadPassphrase)?;
 | 
					                .map_err(|_e| UnlockError::BadPassphrase)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let secret_str = String::from_utf8(decrypted).map_err(|_e| UnlockError::InvalidUtf8)?;
 | 
					            let secret_str = String::from_utf8(decrypted).map_err(|_e| UnlockError::InvalidUtf8)?;
 | 
				
			||||||
        let mut session = self.session.write().unwrap();
 | 
					            (locked.access_key_id.clone(), secret_str)
 | 
				
			||||||
        let creds = Credentials::LongLived {
 | 
					 | 
				
			||||||
            access_key_id: locked.access_key_id.clone(),
 | 
					 | 
				
			||||||
            secret_access_key: secret_str,
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        *session = Session::Unlocked(creds);
 | 
					
 | 
				
			||||||
 | 
					        self.new_session(&key_id, &secret).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -193,4 +232,56 @@ impl AppState {
 | 
				
			|||||||
            Session::Empty => Err(GetCredentialsError::Empty),
 | 
					            Session::Empty => Err(GetCredentialsError::Empty),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn new_session(&self, key_id: &str, secret_key: &str) -> Result<(), GetSessionError> {
 | 
				
			||||||
 | 
					        let creds = aws_sdk_sts::Credentials::new(
 | 
				
			||||||
 | 
					            key_id,
 | 
				
			||||||
 | 
					            secret_key,
 | 
				
			||||||
 | 
					            None, // token
 | 
				
			||||||
 | 
					            None, // expiration
 | 
				
			||||||
 | 
					            "creddy", // "provider name" apparently
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        let config = aws_config::from_env()
 | 
				
			||||||
 | 
					            .credentials_provider(creds)
 | 
				
			||||||
 | 
					            .load()
 | 
				
			||||||
 | 
					            .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let client = aws_sdk_sts::Client::new(&config);
 | 
				
			||||||
 | 
					        let resp = client.get_session_token()
 | 
				
			||||||
 | 
					            .duration_seconds(43_200)
 | 
				
			||||||
 | 
					            .send()
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let aws_session = resp.credentials().ok_or(GetSessionError::NoCredentials)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let access_key_id = aws_session.access_key_id()
 | 
				
			||||||
 | 
					            .ok_or(GetSessionError::NoCredentials)?
 | 
				
			||||||
 | 
					            .to_string();
 | 
				
			||||||
 | 
					        let secret_access_key = aws_session.secret_access_key()
 | 
				
			||||||
 | 
					            .ok_or(GetSessionError::NoCredentials)?
 | 
				
			||||||
 | 
					            .to_string();
 | 
				
			||||||
 | 
					        let token = aws_session.session_token()
 | 
				
			||||||
 | 
					            .ok_or(GetSessionError::NoCredentials)?
 | 
				
			||||||
 | 
					            .to_string();
 | 
				
			||||||
 | 
					        let expiration = aws_session.expiration()
 | 
				
			||||||
 | 
					            .ok_or(GetSessionError::NoCredentials)?
 | 
				
			||||||
 | 
					            .fmt(aws_smithy_types::date_time::Format::DateTime)
 | 
				
			||||||
 | 
					            .unwrap(); // only fails if the d/t is out of range, which it can't be for this format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut app_session = self.session.write().unwrap();
 | 
				
			||||||
 | 
					        let session_creds = Credentials::ShortLived {
 | 
				
			||||||
 | 
					                access_key_id,
 | 
				
			||||||
 | 
					                secret_access_key,
 | 
				
			||||||
 | 
					                token,
 | 
				
			||||||
 | 
					                expiration,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if cfg!(debug_assertions) {
 | 
				
			||||||
 | 
					            println!("Got new session:\n{}", serde_json::to_string(&session_creds).unwrap());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        *app_session = Session::Unlocked(session_creds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
use sodiumoxide::crypto::{pwhash, secretbox};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::state;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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")
 | 
					 | 
				
			||||||
// }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn load(passphrase: &str) -> state::Credentials {
 | 
					 | 
				
			||||||
    state::Credentials::ShortLived {
 | 
					 | 
				
			||||||
        access_key_id: "ASIAZ7WSVLORKQI27QGB".to_string(),
 | 
					 | 
				
			||||||
        secret_access_key: "blah".to_string(),
 | 
					 | 
				
			||||||
        token: "gah".to_string(),
 | 
					 | 
				
			||||||
        expiration: "2022-11-29T10:45:12Z".to_string(),
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										36
									
								
								src-tauri/src/tray.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src-tauri/src/tray.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					use tauri::{
 | 
				
			||||||
 | 
					    AppHandle,
 | 
				
			||||||
 | 
					    Manager,
 | 
				
			||||||
 | 
					    SystemTray,
 | 
				
			||||||
 | 
					    SystemTrayEvent,
 | 
				
			||||||
 | 
					    SystemTrayMenu,
 | 
				
			||||||
 | 
					    CustomMenuItem,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create() -> SystemTray {
 | 
				
			||||||
 | 
					    let show = CustomMenuItem::new("show".to_string(), "Show");
 | 
				
			||||||
 | 
					    let quit = CustomMenuItem::new("exit".to_string(), "Exit");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let menu = SystemTrayMenu::new()
 | 
				
			||||||
 | 
					        .add_item(show)
 | 
				
			||||||
 | 
					        .add_item(quit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SystemTray::new().with_menu(menu)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn handle_event(app: &AppHandle, event: SystemTrayEvent) {
 | 
				
			||||||
 | 
					    match event {
 | 
				
			||||||
 | 
					        SystemTrayEvent::MenuItemClick{ id, .. } => {
 | 
				
			||||||
 | 
					            match id.as_str() {
 | 
				
			||||||
 | 
					                "exit" => app.exit(0),
 | 
				
			||||||
 | 
					                "show" => {
 | 
				
			||||||
 | 
					                    let _ = app.get_window("main").map(|w| w.show());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                _ => (),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        _ => (),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -29,7 +29,7 @@
 | 
				
			|||||||
        "icons/icon.icns",
 | 
					        "icons/icon.icns",
 | 
				
			||||||
        "icons/icon.ico"
 | 
					        "icons/icon.ico"
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "identifier": "com.tauri.dev",
 | 
					      "identifier": "creddy",
 | 
				
			||||||
      "longDescription": "",
 | 
					      "longDescription": "",
 | 
				
			||||||
      "macOS": {
 | 
					      "macOS": {
 | 
				
			||||||
        "entitlements": null,
 | 
					        "entitlements": null,
 | 
				
			||||||
@@ -58,9 +58,15 @@
 | 
				
			|||||||
        "fullscreen": false,
 | 
					        "fullscreen": false,
 | 
				
			||||||
        "height": 600,
 | 
					        "height": 600,
 | 
				
			||||||
        "resizable": true,
 | 
					        "resizable": true,
 | 
				
			||||||
 | 
					        "label": "main",
 | 
				
			||||||
        "title": "Creddy",
 | 
					        "title": "Creddy",
 | 
				
			||||||
        "width": 800
 | 
					        "width": 800,
 | 
				
			||||||
 | 
					        "visible": false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "systemTray": {
 | 
				
			||||||
 | 
					      "iconPath": "icons/icon.png",
 | 
				
			||||||
 | 
					      "iconAsTemplate": true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,37 +1,22 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
import { emit, listen } from '@tauri-apps/api/event';
 | 
					import { emit, listen } from '@tauri-apps/api/event';
 | 
				
			||||||
import queue from './lib/queue.js';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const VIEWS = import.meta.glob('./views/*.svelte', {eager: true});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
window.emit = emit;
 | 
					 | 
				
			||||||
window.queue = queue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var appState = {
 | 
					 | 
				
			||||||
    currentRequest: null,
 | 
					 | 
				
			||||||
    pendingRequests: queue(),
 | 
					 | 
				
			||||||
    credentialStatus: 'locked',
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
window.appState = appState;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { invoke } from '@tauri-apps/api/tauri';
 | 
					import { invoke } from '@tauri-apps/api/tauri';
 | 
				
			||||||
window.invoke = invoke;
 | 
					
 | 
				
			||||||
 | 
					import { appState } from './lib/state.js';
 | 
				
			||||||
 | 
					import { navigate, currentView } from './lib/routing.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var currentView = VIEWS['./views/Home.svelte'].default;
 | 
					invoke('get_config').then(config => $appState.config = config);
 | 
				
			||||||
window.currentView = currentView;
 | 
					 | 
				
			||||||
window.VIEWS = VIEWS;
 | 
					 | 
				
			||||||
function navigate(svelteEvent) {
 | 
					 | 
				
			||||||
    const moduleName = `./views/${svelteEvent.detail.target}.svelte`;
 | 
					 | 
				
			||||||
    currentView = VIEWS[moduleName].default;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
window.navigate = navigate;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
listen('credentials-request', (tauriEvent) => {
 | 
					listen('credentials-request', (tauriEvent) => {
 | 
				
			||||||
    appState.pendingRequests.put(tauriEvent.payload);
 | 
					    $appState.pendingRequests.put(tauriEvent.payload);
 | 
				
			||||||
    console.log('Received request.');
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// can't set this in routing.js directly for some reason
 | 
				
			||||||
 | 
					if (!$currentView) {
 | 
				
			||||||
 | 
					    navigate('Home');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<svelte:component this={currentView} on:navigate={navigate} bind:appState={appState} />
 | 
					
 | 
				
			||||||
 | 
					<svelte:component this="{$currentView}" />
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								src/lib/errors.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/lib/errors.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					export function getRootCause(error) {
 | 
				
			||||||
 | 
					    if (error.source) {
 | 
				
			||||||
 | 
					        return getRootCause(error.source);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        return error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/lib/routing.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/lib/routing.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import { writable } from 'svelte/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const VIEWS = import.meta.glob('../views/*.svelte', {eager: true});
 | 
				
			||||||
 | 
					export let currentView = writable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function navigate(viewName) {
 | 
				
			||||||
 | 
					    let view = VIEWS[`../views/${viewName}.svelte`].default;
 | 
				
			||||||
 | 
					    currentView.set(view);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getView(viewName) {
 | 
				
			||||||
 | 
					    return VIEWS[`../views/${viewName}.svelte`].default;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/lib/state.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/lib/state.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { writable } from 'svelte/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import queue from './queue.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export let appState = writable({
 | 
				
			||||||
 | 
					    currentRequest: null,
 | 
				
			||||||
 | 
					    pendingRequests: queue(),
 | 
				
			||||||
 | 
					    credentialStatus: 'locked',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/ui/Button.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/ui/Button.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    import Icon from './Icon.svelte';
 | 
				
			||||||
 | 
					    export let icon = null;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<button>
 | 
				
			||||||
 | 
					    {#if icon}<Icon name={icon} class="w-4 text-gray-200" />{/if}
 | 
				
			||||||
 | 
					    <slot></slot>
 | 
				
			||||||
 | 
					</button>
 | 
				
			||||||
							
								
								
									
										67
									
								
								src/ui/ErrorAlert.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/ui/ErrorAlert.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    import { onMount } from 'svelte';
 | 
				
			||||||
 | 
					    import { slide } from 'svelte/transition';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let extraClasses;
 | 
				
			||||||
 | 
					    export {extraClasses as class};
 | 
				
			||||||
 | 
					    export let slideDuration = 150;
 | 
				
			||||||
 | 
					    let animationClass = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export function shake() {
 | 
				
			||||||
 | 
					        animationClass = 'shake';
 | 
				
			||||||
 | 
					        window.setTimeout(() => animationClass = "", 400);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					    /* animation from https://svelte.dev/repl/e606c27c864045e5a9700691a7417f99?version=3.58.0 */
 | 
				
			||||||
 | 
					    @keyframes shake {
 | 
				
			||||||
 | 
					        0% {
 | 
				
			||||||
 | 
					            transform: translateX(0px);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        20% {
 | 
				
			||||||
 | 
					            transform: translateX(10px);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        40% {
 | 
				
			||||||
 | 
					            transform: translateX(-10px);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        60% {
 | 
				
			||||||
 | 
					            transform: translateX(5px);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        80% {
 | 
				
			||||||
 | 
					            transform: translateX(-5px);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        90% {
 | 
				
			||||||
 | 
					            transform: translateX(2px);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        95% {
 | 
				
			||||||
 | 
					            transform: translateX(-2px);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        100% {
 | 
				
			||||||
 | 
					            transform: translateX(0px);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .shake {
 | 
				
			||||||
 | 
					        animation-name: shake;
 | 
				
			||||||
 | 
					        animation-play-state: running;
 | 
				
			||||||
 | 
					        animation-duration: 0.4s;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div in:slide="{{duration: slideDuration}}" class="alert alert-error shadow-lg {animationClass} {extraClasses}">
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
 | 
				
			||||||
 | 
					        <span>
 | 
				
			||||||
 | 
					            <slot></slot>
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {#if $$slots.buttons}
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <slot name="buttons"></slot>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    {/if}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/ui/Icon.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/ui/Icon.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    const ICONS = import.meta.glob('./icons/*.svelte', {eager: true});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export let name;
 | 
				
			||||||
 | 
					    let classes = "";
 | 
				
			||||||
 | 
					    export {classes as class};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let svg = ICONS[`./icons/${name}.svelte`].default;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svelte:component this={svg} class={classes} />
 | 
				
			||||||
							
								
								
									
										43
									
								
								src/ui/Link.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/ui/Link.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    import { navigate, currentView } from '../lib/routing.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export let target;
 | 
				
			||||||
 | 
					    export let hotkey = null;
 | 
				
			||||||
 | 
					    export let ctrl = false
 | 
				
			||||||
 | 
					    export let alt = false;
 | 
				
			||||||
 | 
					    export let shift = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function click() {
 | 
				
			||||||
 | 
					        if (typeof target === 'string') {
 | 
				
			||||||
 | 
					            navigate(target);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (typeof target === 'function') {
 | 
				
			||||||
 | 
					            target();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            throw(`Link target is not a string or a function: ${target}`)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function handleHotkey(event) {
 | 
				
			||||||
 | 
					        if (!hotkey) return;
 | 
				
			||||||
 | 
					        if (ctrl && !event.ctrlKey) return;
 | 
				
			||||||
 | 
					        if (alt && !event.altKey) return;
 | 
				
			||||||
 | 
					        if (shift && !event.shiftKey) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (event.code === hotkey) {
 | 
				
			||||||
 | 
					            click();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (hotkey === 'Enter' && event.code === 'NumpadEnter') {
 | 
				
			||||||
 | 
					            click();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svelte:window on:keydown={handleHotkey} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<a href="#" on:click="{click}">
 | 
				
			||||||
 | 
					    <slot></slot>
 | 
				
			||||||
 | 
					</a>
 | 
				
			||||||
							
								
								
									
										23
									
								
								src/ui/Nav.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/ui/Nav.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    import Link from './Link.svelte';
 | 
				
			||||||
 | 
					    import Icon from './Icon.svelte';
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<nav class="fixed top-0 grid grid-cols-2 w-full p-2">
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <Link target="Home">
 | 
				
			||||||
 | 
					            <button class="btn btn-squre btn-ghost align-middle">
 | 
				
			||||||
 | 
					                <Icon name="home" class="w-8 h-8 stroke-2" />
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					        </Link>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="justify-self-end">
 | 
				
			||||||
 | 
					        <Link target="Settings">
 | 
				
			||||||
 | 
					            <button class="align-middle btn btn-square btn-ghost">
 | 
				
			||||||
 | 
					                <Icon name="cog-8-tooth" class="w-8 h-8 stroke-2" />
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					        </Link>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</nav>
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/ui/icons/check-circle.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/ui/icons/check-circle.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    let classes = "";
 | 
				
			||||||
 | 
					    export {classes as class};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svg class={classes} 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>
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/ui/icons/cog-8-tooth.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/ui/icons/cog-8-tooth.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    let classes = "";
 | 
				
			||||||
 | 
					    export {classes as class};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svg class="w-6 h-6 {classes}" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
 | 
				
			||||||
 | 
					  <path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" />
 | 
				
			||||||
 | 
					  <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/ui/icons/home.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/ui/icons/home.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    let classes = "";
 | 
				
			||||||
 | 
					    export {classes as class};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svg class="w-6 h-6 {classes}" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
 | 
				
			||||||
 | 
					  <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/ui/icons/x-circle.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/ui/icons/x-circle.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    let classes = "";
 | 
				
			||||||
 | 
					    export {classes as class};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svg class={classes} 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>
 | 
				
			||||||
							
								
								
									
										62
									
								
								src/ui/settings/NumericSetting.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/ui/settings/NumericSetting.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import Setting from './Setting.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export let title;
 | 
				
			||||||
 | 
					    export let value;
 | 
				
			||||||
 | 
					    export let unit = '';
 | 
				
			||||||
 | 
					    export let min = null;
 | 
				
			||||||
 | 
					    export let max = null;
 | 
				
			||||||
 | 
					    export let decimal = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dispatch = createEventDispatcher();
 | 
				
			||||||
 | 
					    let error = null;
 | 
				
			||||||
 | 
					    function validate(event) {
 | 
				
			||||||
 | 
					        let v = event.target.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (v === '') {
 | 
				
			||||||
 | 
					            error = null;
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let num = parseFloat(v);
 | 
				
			||||||
 | 
					        if (Number.isNaN(num)) {
 | 
				
			||||||
 | 
					            error = `"${v}" is not a number`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (num % 1 !== 0 && !decimal) {
 | 
				
			||||||
 | 
					            error = `${num} is not a whole number`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (min && num < min) {
 | 
				
			||||||
 | 
					            error = `Too low (minimum ${min})`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (max && num > max) {
 | 
				
			||||||
 | 
					            error = `Too large (maximum ${max})`
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            error = null;
 | 
				
			||||||
 | 
					            value = num;
 | 
				
			||||||
 | 
					            dispatch('update', {value})
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Setting {title} {error}>
 | 
				
			||||||
 | 
					    <div slot="input">
 | 
				
			||||||
 | 
					        {#if unit}
 | 
				
			||||||
 | 
					            <span class="mr-2">{unit}:</span>
 | 
				
			||||||
 | 
					        {/if}
 | 
				
			||||||
 | 
					        <div class="tooltip tooltip-error" class:tooltip-open={error !== null} data-tip={error}>
 | 
				
			||||||
 | 
					            <input 
 | 
				
			||||||
 | 
					                type="text" 
 | 
				
			||||||
 | 
					                class="input input-sm input-bordered text-right max-w-[4rem]" 
 | 
				
			||||||
 | 
					                class:input-error={error} 
 | 
				
			||||||
 | 
					                value={value} 
 | 
				
			||||||
 | 
					                on:input="{validate}"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <slot name="description" slot="description"></slot>
 | 
				
			||||||
 | 
					</Setting>
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/ui/settings/Setting.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/ui/settings/Setting.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    import { slide } from 'svelte/transition';
 | 
				
			||||||
 | 
					    import ErrorAlert from '../ErrorAlert.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export let title;
 | 
				
			||||||
 | 
					    export let error = null;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="divider"></div>
 | 
				
			||||||
 | 
					<div class="flex justify-between">
 | 
				
			||||||
 | 
					    <h3 class="text-lg font-bold">{title}</h3>
 | 
				
			||||||
 | 
					    <slot name="input"></slot>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p class="mt-3">
 | 
				
			||||||
 | 
					    <slot name="description"></slot>
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/ui/settings/ToggleSetting.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/ui/settings/ToggleSetting.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import Setting from './Setting.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export let title;
 | 
				
			||||||
 | 
					    export let value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dispatch = createEventDispatcher();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Setting title="Start minimized">
 | 
				
			||||||
 | 
					    <input 
 | 
				
			||||||
 | 
					        slot="input" 
 | 
				
			||||||
 | 
					        type="checkbox" 
 | 
				
			||||||
 | 
					        class="toggle toggle-success"
 | 
				
			||||||
 | 
					        bind:checked={value}
 | 
				
			||||||
 | 
					        on:change={e => dispatch('update', {value: e.target.checked})}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    <slot name="description" slot="description"></slot>
 | 
				
			||||||
 | 
					</Setting>
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/ui/settings/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/ui/settings/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					export { default as Setting } from './Setting.svelte';
 | 
				
			||||||
 | 
					export { default as ToggleSetting } from './ToggleSetting.svelte';
 | 
				
			||||||
 | 
					export { default as NumericSetting } from './NumericSetting.svelte';
 | 
				
			||||||
@@ -1,39 +1,59 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
    import { createEventDispatcher } from 'svelte';
 | 
					 | 
				
			||||||
    import { invoke } from '@tauri-apps/api/tauri';
 | 
					    import { invoke } from '@tauri-apps/api/tauri';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let appState;
 | 
					    import { navigate } from '../lib/routing.js';
 | 
				
			||||||
 | 
					    import { appState } from '../lib/state.js';
 | 
				
			||||||
 | 
					    import Link from '../ui/Link.svelte';
 | 
				
			||||||
 | 
					    import Icon from '../ui/Icon.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dispatch = createEventDispatcher();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function approve() {
 | 
					    async function approve() {
 | 
				
			||||||
        let status = await invoke('get_session_status');
 | 
					        let status = await invoke('get_session_status');
 | 
				
			||||||
        if (status === 'unlocked') {
 | 
					        if (status === 'unlocked') {
 | 
				
			||||||
            dispatch('navigate', {target: 'ShowApproved'});
 | 
					            navigate('ShowApproved');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (status === 'locked') {
 | 
					        else if (status === 'locked') {
 | 
				
			||||||
            dispatch('navigate', {target: 'Unlock'})
 | 
					            navigate('Unlock');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            dispatch('navigate', {target: 'EnterCredentials'});
 | 
					            navigate('EnterCredentials');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function deny() {
 | 
					    var appName = null;
 | 
				
			||||||
        dispatch('navigate', {target: 'ShowDenied'});
 | 
					    if ($appState.currentRequest.clients.length === 1) {
 | 
				
			||||||
 | 
					        let path = $appState.currentRequest.clients[0].exe;
 | 
				
			||||||
 | 
					        let m = path.match(/\/([^/]+?$)|\\([^\\]+?$)/);
 | 
				
			||||||
 | 
					        appName = m[1] || m[2];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h2 class="text-3xl text-gray-200">An application would like to access your AWS credentials.</h2>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
<button on:click={approve}>
 | 
					<div class="flex flex-col space-y-4 p-4 m-auto max-w-max h-screen justify-center">
 | 
				
			||||||
    <svg class="w-32 stroke-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1">
 | 
					    <!-- <div class="p-4 rounded-box border-2 border-neutral-content"> -->
 | 
				
			||||||
      <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
 | 
					        <div class="space-y-1 mb-4">
 | 
				
			||||||
    </svg>
 | 
					            <h2 class="text-xl font-bold">{appName ? `"${appName}"` : 'An appplication'} would like to access your AWS credentials.</h2>
 | 
				
			||||||
</button>
 | 
					            {#each $appState.currentRequest.clients as client}
 | 
				
			||||||
 | 
					                <p>Path: {client ? client.exe : 'Unknown'}</p>
 | 
				
			||||||
 | 
					                <p>PID: {client ? client.pid : 'Unknown'}</p>
 | 
				
			||||||
 | 
					            {/each}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<button on:click={deny}>
 | 
					        <div class="grid grid-cols-2">
 | 
				
			||||||
    <svg class="w-32 stroke-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1">
 | 
					            <Link target="ShowDenied" hotkey="Escape">
 | 
				
			||||||
        <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" />
 | 
					                <button class="btn btn-error justify-self-start">
 | 
				
			||||||
    </svg>
 | 
					                    Deny
 | 
				
			||||||
</button>
 | 
					                    <kbd class="ml-2 normal-case px-1 py-0.5 rounded border border-neutral">Esc</kbd>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </Link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <Link target="{approve}" hotkey="Enter" shift="{true}">
 | 
				
			||||||
 | 
					                <button class="btn btn-success justify-self-end">
 | 
				
			||||||
 | 
					                    Approve
 | 
				
			||||||
 | 
					                    <kbd class="ml-2 normal-case px-1 py-0.5 rounded border border-neutral">Shift</kbd>
 | 
				
			||||||
 | 
					                    <span class="mx-0.5">+</span>
 | 
				
			||||||
 | 
					                    <kbd class="normal-case px-1 py-0.5 rounded border border-neutral">Enter</kbd>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </Link>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@@ -1,41 +1,62 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
    import { onMount, createEventDispatcher } from 'svelte';
 | 
					    import { onMount } from 'svelte';
 | 
				
			||||||
    import { invoke } from '@tauri-apps/api/tauri';
 | 
					    import { invoke } from '@tauri-apps/api/tauri';
 | 
				
			||||||
 | 
					    import { getRootCause } from '../lib/errors.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let appState;
 | 
					    import { appState } from '../lib/state.js';
 | 
				
			||||||
 | 
					    import { navigate } from '../lib/routing.js';
 | 
				
			||||||
 | 
					    import Link from '../ui/Link.svelte';
 | 
				
			||||||
 | 
					    import ErrorAlert from '../ui/ErrorAlert.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dispatch = createEventDispatcher();
 | 
					
 | 
				
			||||||
 | 
					    let errorMsg = null;
 | 
				
			||||||
 | 
					    let alert;
 | 
				
			||||||
    let AccessKeyId, SecretAccessKey, passphrase
 | 
					    let AccessKeyId, SecretAccessKey, passphrase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async function save() {
 | 
					    async function save() {
 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
        console.log('Saving credentials.');
 | 
					        console.log('Saving credentials.');
 | 
				
			||||||
        let credentials = {AccessKeyId, SecretAccessKey};
 | 
					        let credentials = {AccessKeyId, SecretAccessKey};
 | 
				
			||||||
            console.log(credentials);
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            await invoke('save_credentials', {credentials, passphrase});
 | 
					            await invoke('save_credentials', {credentials, passphrase});
 | 
				
			||||||
            if (appState.currentRequest) {
 | 
					            if ($appState.currentRequest) {
 | 
				
			||||||
                dispatch('navigate', {target: 'ShowApproved'})
 | 
					                navigate('ShowApproved');
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else {
 | 
					            else {
 | 
				
			||||||
                dispatch('navigate', {target: 'Home'})
 | 
					                navigate('Home');
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (e) {
 | 
					        catch (e) {
 | 
				
			||||||
            console.log("Error saving credentials:", e);
 | 
					            if (e.code === "GetSession") {
 | 
				
			||||||
 | 
					                let root = getRootCause(e);
 | 
				
			||||||
 | 
					                errorMsg = `Error response from AWS (${root.code}): ${root.msg}`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                errorMsg = e.msg;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (alert) {
 | 
				
			||||||
 | 
					                alert.shake();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<form action="#" on:submit|preventDefault="{save}">
 | 
					 | 
				
			||||||
    <div class="text-gray-200">AWS Access Key ID</div>
 | 
					 | 
				
			||||||
    <input class="text-gray-200 bg-zinc-800" type="text" bind:value="{AccessKeyId}" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="text-gray-200">AWS Secret Access Key</div>
 | 
					<form action="#" on:submit|preventDefault="{save}" class="form-control space-y-4 max-w-sm m-auto p-4 h-screen justify-center">
 | 
				
			||||||
    <input class="text-gray-200 bg-zinc-800" type="text" bind:value="{SecretAccessKey}" />
 | 
					    <h2 class="text-2xl font-bold text-center">Enter your credentials</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="text-gray-200">Passphrase</div>
 | 
					    {#if errorMsg}
 | 
				
			||||||
    <input class="text-gray-200 bg-zinc-800" type="text" bind:value="{passphrase}" />
 | 
					        <ErrorAlert bind:this="{alert}">{errorMsg}</ErrorAlert>
 | 
				
			||||||
 | 
					    {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <input class="text-gray-200" type="submit" />
 | 
					    <input type="text" placeholder="AWS Access Key ID" bind:value="{AccessKeyId}" class="input input-bordered" />
 | 
				
			||||||
 | 
					    <input type="password" placeholder="AWS Secret Access Key" bind:value="{SecretAccessKey}" class="input input-bordered" />
 | 
				
			||||||
 | 
					    <input type="password" placeholder="Passphrase" bind:value="{passphrase}" class="input input-bordered" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <input type="submit" class="btn btn-primary" />
 | 
				
			||||||
 | 
					    <Link target="Home" hotkey="Escape">
 | 
				
			||||||
 | 
					        <button class="btn btn-sm btn-outline w-full">Cancel</button>
 | 
				
			||||||
 | 
					    </Link>
 | 
				
			||||||
</form>
 | 
					</form>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,42 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
    import { onMount, createEventDispatcher } from 'svelte';
 | 
					    import { onMount } from 'svelte';
 | 
				
			||||||
 | 
					    import { invoke } from '@tauri-apps/api/tauri';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let appState;
 | 
					    import { appState } from '../lib/state.js';
 | 
				
			||||||
 | 
					    import { navigate } from '../lib/routing.js';
 | 
				
			||||||
 | 
					    import Nav from '../ui/Nav.svelte';
 | 
				
			||||||
 | 
					    import Icon from '../ui/Icon.svelte';
 | 
				
			||||||
 | 
					    import Link from '../ui/Link.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dispatch = createEventDispatcher();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onMount(async () => {
 | 
					    onMount(async () => {
 | 
				
			||||||
        // will block until a request comes in
 | 
					        // will block until a request comes in
 | 
				
			||||||
        let req = await appState.pendingRequests.get();
 | 
					        let req = await $appState.pendingRequests.get();
 | 
				
			||||||
        appState.currentRequest = req;
 | 
					        $appState.currentRequest = req;
 | 
				
			||||||
        console.log('Got credentials request from queue:');
 | 
					        navigate('Approve');
 | 
				
			||||||
        console.log(req);
 | 
					 | 
				
			||||||
        dispatch('navigate', {target: 'Approve'});
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h1 class="text-4xl text-gray-300">Creddy</h1>
 | 
					
 | 
				
			||||||
 | 
					<Nav />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="flex flex-col h-screen justify-center items-center space-y-4">
 | 
				
			||||||
 | 
					    {#await invoke('get_session_status') then status}
 | 
				
			||||||
 | 
					        {#if status === 'locked'}
 | 
				
			||||||
 | 
					            <img src="/static/padlock-closed.svg" alt="A locked padlock" class="w-32" />
 | 
				
			||||||
 | 
					            <h2 class="text-2xl font-bold">Creddy is locked</h2>
 | 
				
			||||||
 | 
					            <Link target="Unlock">
 | 
				
			||||||
 | 
					                <button class="btn btn-primary">Unlock</button>
 | 
				
			||||||
 | 
					            </Link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {:else if status === 'unlocked'}
 | 
				
			||||||
 | 
					            <img src="/static/padlock-open.svg" alt="An unlocked padlock" class="w-24" />
 | 
				
			||||||
 | 
					            <h2 class="text-2xl font-bold">Waiting for requests</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {:else if status === 'empty'}
 | 
				
			||||||
 | 
					            <Link target="EnterCredentials">
 | 
				
			||||||
 | 
					                <button class="btn btn-primary">Enter Credentials</button>
 | 
				
			||||||
 | 
					            </Link>
 | 
				
			||||||
 | 
					        {/if}
 | 
				
			||||||
 | 
					    {/await}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/views/Settings.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/views/Settings.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    import { invoke } from '@tauri-apps/api/tauri';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import { appState } from '../lib/state.js';
 | 
				
			||||||
 | 
					    import Nav from '../ui/Nav.svelte';
 | 
				
			||||||
 | 
					    import Link from '../ui/Link.svelte';
 | 
				
			||||||
 | 
					    import ErrorAlert from '../ui/ErrorAlert.svelte';
 | 
				
			||||||
 | 
					    // import Setting from '../ui/settings/Setting.svelte';
 | 
				
			||||||
 | 
					    import { Setting, ToggleSetting, NumericSetting } from '../ui/settings';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function save() {
 | 
				
			||||||
 | 
					        await invoke('save_config', {config: $appState.config});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Nav />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#await invoke('get_config') then config}
 | 
				
			||||||
 | 
					    <div class="mx-auto mt-3 max-w-md">
 | 
				
			||||||
 | 
					        <h2 class="text-2xl font-bold text-center">Settings</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ToggleSetting title="Start minimized" bind:value={$appState.config.start_minimized} on:update={save}>
 | 
				
			||||||
 | 
					            <svelte:fragment slot="description">
 | 
				
			||||||
 | 
					                Minimize to the system tray at startup.
 | 
				
			||||||
 | 
					            </svelte:fragment>
 | 
				
			||||||
 | 
					        </ToggleSetting>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <NumericSetting title="Re-hide delay" bind:value={$appState.config.rehide_ms} min={0} unit="Milliseconds" on:update={save}>
 | 
				
			||||||
 | 
					            <svelte:fragment slot="description">
 | 
				
			||||||
 | 
					                How long to wait after a request is approved/denied before minimizing
 | 
				
			||||||
 | 
					                the window to tray. Only applicable if the window was minimized
 | 
				
			||||||
 | 
					                to tray before the request was received.
 | 
				
			||||||
 | 
					            </svelte:fragment>
 | 
				
			||||||
 | 
					        </NumericSetting>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Setting title="Update credentials">
 | 
				
			||||||
 | 
					            <Link slot="input" target="EnterCredentials">
 | 
				
			||||||
 | 
					                <button class="btn btn-sm btn-primary">Update</button>
 | 
				
			||||||
 | 
					            </Link>
 | 
				
			||||||
 | 
					            <svelte:fragment slot="description">
 | 
				
			||||||
 | 
					                Update or re-enter your encrypted credentials.
 | 
				
			||||||
 | 
					            </svelte:fragment>
 | 
				
			||||||
 | 
					        </Setting>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					{/await}
 | 
				
			||||||
@@ -1,21 +1,75 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
    import { onMount, createEventDispatcher } from 'svelte';
 | 
					    import { onMount } from 'svelte';
 | 
				
			||||||
 | 
					    import { draw, fade } from 'svelte/transition';
 | 
				
			||||||
    import { emit } from '@tauri-apps/api/event';
 | 
					    import { emit } from '@tauri-apps/api/event';
 | 
				
			||||||
    import { invoke } from '@tauri-apps/api/tauri';
 | 
					    import { invoke } from '@tauri-apps/api/tauri';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let appState;
 | 
					    import { appState } from '../lib/state.js';
 | 
				
			||||||
 | 
					    import { navigate } from '../lib/routing.js';
 | 
				
			||||||
 | 
					    import ErrorAlert from '../ui/ErrorAlert.svelte';
 | 
				
			||||||
 | 
					    import Icon from '../ui/Icon.svelte';
 | 
				
			||||||
 | 
					    import Link from '../ui/Link.svelte';
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    onMount(async () => {
 | 
					    let success = false;
 | 
				
			||||||
 | 
					    let error = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function respond() {
 | 
				
			||||||
        let response = {
 | 
					        let response = {
 | 
				
			||||||
            id: appState.currentRequest.id,
 | 
					            id: $appState.currentRequest.id,
 | 
				
			||||||
            approval: 'Approved',
 | 
					            approval: 'Approved',
 | 
				
			||||||
        }
 | 
					        };
 | 
				
			||||||
        await invoke('respond', {response});
 | 
					 | 
				
			||||||
        appState.currentRequest = null;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dispatch = createEventDispatcher();
 | 
					        try {
 | 
				
			||||||
    window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 3000);
 | 
					            await invoke('respond', {response});
 | 
				
			||||||
 | 
					            success = true;
 | 
				
			||||||
 | 
					            $appState.currentRequest = null;
 | 
				
			||||||
 | 
					            window.setTimeout(() => navigate('Home'), 1000);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (e) {
 | 
				
			||||||
 | 
					            error = e;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onMount(respond);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h1 class="text-4xl text-gray-300">Approved!</h1>
 | 
					<style>
 | 
				
			||||||
 | 
					    :global(body) {
 | 
				
			||||||
 | 
					        overflow: hidden;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if error}
 | 
				
			||||||
 | 
					    <div class="flex flex-col h-screen items-center justify-center m-auto max-w-lg">
 | 
				
			||||||
 | 
					        <ErrorAlert>
 | 
				
			||||||
 | 
					            {error}
 | 
				
			||||||
 | 
					            <svelte:fragment slot="buttons">
 | 
				
			||||||
 | 
					                <Link target="Home">
 | 
				
			||||||
 | 
					                    <button class="btn btn-sm bg-transparent hover:bg-[#cd5a5a] border border-error-content text-error-content">
 | 
				
			||||||
 | 
					                        Ok
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </Link>
 | 
				
			||||||
 | 
					            </svelte:fragment>
 | 
				
			||||||
 | 
					        </ErrorAlert>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					{:else if success}
 | 
				
			||||||
 | 
					    <div class="flex flex-col h-screen items-center justify-center max-w-max m-auto">
 | 
				
			||||||
 | 
					        <svg xmlns="http://www.w3.org/2000/svg" class="w-36 h-36" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
 | 
				
			||||||
 | 
					          <path in:draw="{{duration: 500}}" stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
 | 
				
			||||||
 | 
					        </svg>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div in:fade="{{delay: 200, duration: 300}}" class="text-2xl font-bold">Approved!</div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 
 | 
				
			||||||
 | 
					{#if error}
 | 
				
			||||||
 | 
					    <div class="text-red-400">{error}</div>
 | 
				
			||||||
 | 
					{:else}
 | 
				
			||||||
 | 
					    <h1 class="text-4xl text-gray-300">Approved!</h1>
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
 | 
					 -->
 | 
				
			||||||
@@ -1,21 +1,56 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
    import { onMount, createEventDispatcher } from 'svelte';
 | 
					    import { onMount } from 'svelte';
 | 
				
			||||||
 | 
					    import { draw, fade } from 'svelte/transition';
 | 
				
			||||||
    import { emit } from '@tauri-apps/api/event';
 | 
					    import { emit } from '@tauri-apps/api/event';
 | 
				
			||||||
    import { invoke } from '@tauri-apps/api/tauri';
 | 
					    import { invoke } from '@tauri-apps/api/tauri';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let appState;
 | 
					    import { appState } from '../lib/state.js';
 | 
				
			||||||
 | 
					    import { navigate } from '../lib/routing.js';
 | 
				
			||||||
 | 
					    import ErrorAlert from '../ui/ErrorAlert.svelte';
 | 
				
			||||||
 | 
					    import Icon from '../ui/Icon.svelte';
 | 
				
			||||||
 | 
					    import Link from '../ui/Link.svelte';
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    onMount(async () => {
 | 
					    let error = null;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    async function respond() {
 | 
				
			||||||
        let response = {
 | 
					        let response = {
 | 
				
			||||||
            id: appState.currentRequest.id,
 | 
					            id: $appState.currentRequest.id,
 | 
				
			||||||
            approval: 'Denied',
 | 
					            approval: 'Denied',
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        await invoke('respond', {response});
 | 
					 | 
				
			||||||
        appState.currentRequest = null;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dispatch = createEventDispatcher();
 | 
					        try {
 | 
				
			||||||
    window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 3000);
 | 
					            await invoke('respond', {response});
 | 
				
			||||||
 | 
					            $appState.currentRequest = null;
 | 
				
			||||||
 | 
					            window.setTimeout(() => navigate('Home'), 1000);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (e) {
 | 
				
			||||||
 | 
					            error = e;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onMount(respond);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<h1 class="text-4xl text-gray-300">Denied!</h1>
 | 
					{#if error}
 | 
				
			||||||
 | 
					    <div class="flex flex-col h-screen items-center justify-center m-auto max-w-lg">
 | 
				
			||||||
 | 
					        <ErrorAlert>
 | 
				
			||||||
 | 
					            {error}
 | 
				
			||||||
 | 
					            <svelte:fragment slot="buttons">
 | 
				
			||||||
 | 
					                <Link target="Home">
 | 
				
			||||||
 | 
					                    <button class="btn btn-sm bg-transparent hover:bg-[#cd5a5a] border border-error-content text-error-content" on:click="{() => navigate('Home')}">
 | 
				
			||||||
 | 
					                        Ok
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </Link>
 | 
				
			||||||
 | 
					            </svelte:fragment>
 | 
				
			||||||
 | 
					        </ErrorAlert>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					{:else}
 | 
				
			||||||
 | 
					    <div class="flex flex-col items-center justify-center h-screen max-w-max m-auto">
 | 
				
			||||||
 | 
					        <svg xmlns="http://www.w3.org/2000/svg" class="w-36 h-36" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
 | 
				
			||||||
 | 
					            <path in:draw="{{duration: 500}}" stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
 | 
				
			||||||
 | 
					        </svg>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div in:fade="{{delay: 200, duration: 300}}" class="text-2xl font-bold">Denied!</div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
@@ -1,25 +1,56 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
    import { invoke } from '@tauri-apps/api/tauri';
 | 
					    import { invoke } from '@tauri-apps/api/tauri';
 | 
				
			||||||
    import { createEventDispatcher } from 'svelte';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let appState;
 | 
					    import { appState } from '../lib/state.js';
 | 
				
			||||||
 | 
					    import { navigate } from '../lib/routing.js';
 | 
				
			||||||
 | 
					    import { getRootCause } from '../lib/errors.js';
 | 
				
			||||||
 | 
					    import ErrorAlert from '../ui/ErrorAlert.svelte';
 | 
				
			||||||
 | 
					    import Link from '../ui/Link.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dispatch = createEventDispatcher();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let errorMsg = null;
 | 
				
			||||||
 | 
					    let alert;
 | 
				
			||||||
    let passphrase = '';
 | 
					    let passphrase = '';
 | 
				
			||||||
    async function unlock() {
 | 
					    async function unlock() {
 | 
				
			||||||
        console.log('invoking unlock command.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            await invoke('unlock', {passphrase});
 | 
					            let r = await invoke('unlock', {passphrase});
 | 
				
			||||||
 | 
					            $appState.credentialStatus = 'unlocked';
 | 
				
			||||||
 | 
					            if ($appState.currentRequest) {
 | 
				
			||||||
 | 
					                navigate('ShowApproved');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                navigate('Home');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (e) {
 | 
					        catch (e) {
 | 
				
			||||||
            console.log('Unlock error:', e);
 | 
					            window.error = e;
 | 
				
			||||||
 | 
					            if (e.code === 'GetSession') {
 | 
				
			||||||
 | 
					                let root = getRootCause(e);
 | 
				
			||||||
 | 
					                errorMsg = `Error response from AWS (${root.code}): ${root.msg}`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                errorMsg = e.msg;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (alert) {
 | 
				
			||||||
 | 
					                alert.shake();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<form action="#" on:submit|preventDefault="{unlock}">
 | 
					
 | 
				
			||||||
    <div class="text-gray-200">Enter your passphrase:</div>
 | 
					<form action="#" on:submit|preventDefault="{unlock}" class="form-control space-y-4 max-w-sm m-auto p-4 h-screen justify-center">
 | 
				
			||||||
    <input class="text-gray-200 bg-zinc-800" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" />
 | 
					    <h2 class="font-bold text-2xl text-center">Enter your passphrase</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {#if errorMsg}
 | 
				
			||||||
 | 
					        <ErrorAlert bind:this="{alert}">{errorMsg}</ErrorAlert>
 | 
				
			||||||
 | 
					    {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <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" />
 | 
				
			||||||
 | 
					    <Link target="Home" hotkey="Escape">
 | 
				
			||||||
 | 
					        <button class="btn btn-outline btn-sm w-full">Cancel</button>
 | 
				
			||||||
 | 
					    </Link>
 | 
				
			||||||
</form>
 | 
					</form>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										430
									
								
								static/padlock-closed.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										430
									
								
								static/padlock-closed.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 34 KiB  | 
							
								
								
									
										467
									
								
								static/padlock-open.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										467
									
								
								static/padlock-open.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 46 KiB  | 
@@ -7,5 +7,7 @@ module.exports = {
 | 
				
			|||||||
  theme: {
 | 
					  theme: {
 | 
				
			||||||
    extend: {},
 | 
					    extend: {},
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  plugins: [],
 | 
					  plugins: [
 | 
				
			||||||
 | 
					    require('daisyui'),
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user