Compare commits
1
Commits
wincon
..
a8aafa1519
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8aafa1519 |
No files matched your search
@@ -5,4 +5,3 @@ src-tauri/target/
|
||||
|
||||
# just in case
|
||||
credentials*
|
||||
!credentials.rs
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "creddy",
|
||||
"version": "0.2.2",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
DATABASE_URL=sqlite://C:/Users/Joe/AppData/Roaming/creddy/creddy.dev.db
|
||||
DATABASE_URL=sqlite://creddy.db?mode=rwc
|
||||
Generated
+1010
-1488
File diff suppressed because it is too large.
Load diff
+8
-17
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "creddy"
|
||||
version = "0.2.2"
|
||||
description = "A friendly AWS credentials manager"
|
||||
authors = ["Joseph Montanaro"]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "creddy"
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
rust-version = "1.57"
|
||||
|
||||
@@ -17,8 +17,7 @@ tauri-build = { version = "1.0.4", features = [] }
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.2", features = ["cli", "dialog", "os-all", "system-tray"] }
|
||||
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
||||
tauri = { version = "1.0.5", features = ["api-all", "system-tray"] }
|
||||
sodiumoxide = "0.2.7"
|
||||
tokio = { version = ">=1.19", features = ["full"] }
|
||||
sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls"] }
|
||||
@@ -32,22 +31,14 @@ thiserror = "1.0.38"
|
||||
once_cell = "1.16.0"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
auto-launch = "0.4.0"
|
||||
dirs = "5.0"
|
||||
clap = { version = "3.2.23", features = ["derive"] }
|
||||
# is-terminal = "0.4.7"
|
||||
argon2 = { version = "0.5.0", features = ["std"] }
|
||||
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||
windows = { version = "0.48", features = ["Win32_Foundation", "Win32_System_Console"] }
|
||||
gag = "1.0"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||
default = ["custom-protocol"]
|
||||
default = [ "custom-protocol" ]
|
||||
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||
# DO NOT remove this
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
||||
|
||||
# [profile.dev.build-override]
|
||||
# opt-level = 3
|
||||
@@ -8,7 +8,7 @@ CREATE TABLE credentials (
|
||||
);
|
||||
|
||||
CREATE TABLE config (
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
use std::error::Error;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use sqlx::{
|
||||
SqlitePool,
|
||||
sqlite::SqlitePoolOptions,
|
||||
sqlite::SqliteConnectOptions,
|
||||
};
|
||||
use tauri::{
|
||||
App,
|
||||
AppHandle,
|
||||
Manager,
|
||||
async_runtime as rt,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::{self, AppConfig},
|
||||
credentials::Session,
|
||||
ipc,
|
||||
server::Server,
|
||||
errors::*,
|
||||
state::AppState,
|
||||
tray,
|
||||
};
|
||||
|
||||
|
||||
pub static APP: OnceCell<AppHandle> = OnceCell::new();
|
||||
|
||||
|
||||
pub fn run() -> tauri::Result<()> {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
|
||||
app.get_window("main")
|
||||
.map(|w| w.show().error_popup("Failed to show main window"));
|
||||
}))
|
||||
.system_tray(tray::create())
|
||||
.on_system_tray_event(tray::handle_event)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
ipc::unlock,
|
||||
ipc::respond,
|
||||
ipc::get_session_status,
|
||||
ipc::save_credentials,
|
||||
ipc::get_config,
|
||||
ipc::save_config,
|
||||
])
|
||||
.setup(|app| rt::block_on(setup(app)))
|
||||
.build(tauri::generate_context!())?
|
||||
.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();
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
_ => ()
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn connect_db() -> Result<SqlitePool, SetupError> {
|
||||
let conn_opts = SqliteConnectOptions::new()
|
||||
.filename(config::get_or_create_db_path()?)
|
||||
.create_if_missing(true);
|
||||
let pool_opts = SqlitePoolOptions::new();
|
||||
let pool: SqlitePool = pool_opts.connect_with(conn_opts).await?;
|
||||
sqlx::migrate!().run(&pool).await?;
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
|
||||
async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
|
||||
APP.set(app.handle()).unwrap();
|
||||
|
||||
let pool = connect_db().await?;
|
||||
let conf = AppConfig::load(&pool).await?;
|
||||
let session = Session::load(&pool).await?;
|
||||
let srv = Server::new(conf.listen_addr, conf.listen_port, app.handle()).await?;
|
||||
|
||||
config::set_auto_launch(conf.start_on_login)?;
|
||||
if !conf.start_minimized {
|
||||
app.get_window("main")
|
||||
.ok_or(HandlerError::NoMainWindow)?
|
||||
.show()?;
|
||||
}
|
||||
|
||||
let state = AppState::new(conf, session, srv, pool);
|
||||
app.manage(state);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
use std::process::Command as ChildCommand;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
use clap::{
|
||||
Command,
|
||||
Arg,
|
||||
ArgMatches,
|
||||
ArgAction
|
||||
};
|
||||
use tokio::{
|
||||
net::TcpStream,
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
};
|
||||
|
||||
use crate::app;
|
||||
use crate::config::AppConfig;
|
||||
use crate::credentials::{BaseCredentials, SessionCredentials};
|
||||
use crate::errors::*;
|
||||
|
||||
|
||||
pub fn parser() -> Command<'static> {
|
||||
Command::new("creddy")
|
||||
.about("A friendly AWS credentials manager")
|
||||
// we don't want the default help handling because it early-exits,
|
||||
// and on Windows we need to setup the console before we can output
|
||||
.disable_help_flag(true)
|
||||
.arg(
|
||||
Arg::new("help")
|
||||
.short('h')
|
||||
.long("help")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Print this message or the help of the given subcommand(s)")
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("run")
|
||||
.about("Launch Creddy")
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("show")
|
||||
.about("Fetch and display AWS credentials")
|
||||
.arg(
|
||||
Arg::new("base")
|
||||
.short('b')
|
||||
.long("base")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Use base credentials instead of session credentials")
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("exec")
|
||||
.about("Inject AWS credentials into the environment of another command")
|
||||
.trailing_var_arg(true)
|
||||
.arg(
|
||||
Arg::new("base")
|
||||
.short('b')
|
||||
.long("base")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Use base credentials instead of session credentials")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("command")
|
||||
.multiple_values(true)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
pub fn show(args: &ArgMatches) -> Result<(), CliError> {
|
||||
let base = args.get_one("base").unwrap_or(&false);
|
||||
let creds = get_credentials(*base)?;
|
||||
println!("{creds}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn exec(args: &ArgMatches) -> Result<(), CliError> {
|
||||
let base = *args.get_one("base").unwrap_or(&false);
|
||||
let mut cmd_line = args.get_many("command")
|
||||
.ok_or(ExecError::NoCommand)?;
|
||||
|
||||
let cmd_name: &String = cmd_line.next().unwrap(); // Clap guarantees that there will be at least one
|
||||
let mut cmd = ChildCommand::new(cmd_name);
|
||||
cmd.args(cmd_line);
|
||||
|
||||
if base {
|
||||
let creds: BaseCredentials = serde_json::from_str(&get_credentials(base)?)
|
||||
.map_err(|_| RequestError::InvalidJson)?;
|
||||
cmd.env("AWS_ACCESS_KEY_ID", creds.access_key_id);
|
||||
cmd.env("AWS_SECRET_ACCESS_KEY", creds.secret_access_key);
|
||||
}
|
||||
else {
|
||||
let creds: SessionCredentials = serde_json::from_str(&get_credentials(base)?)
|
||||
.map_err(|_| RequestError::InvalidJson)?;
|
||||
cmd.env("AWS_ACCESS_KEY_ID", creds.access_key_id);
|
||||
cmd.env("AWS_SECRET_ACCESS_KEY", creds.secret_access_key);
|
||||
cmd.env("AWS_SESSION_TOKEN", creds.token);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let e = cmd.exec(); // never returns if successful
|
||||
Err(ExecError::ExecutionFailed(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut child = cmd.spawn()
|
||||
.map_err(|e| ExecError::ExecutionFailed(e))?;
|
||||
let status = child.wait()
|
||||
.map_err(|e| ExecError::ExecutionFailed(e))?;
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn get_credentials(base: bool) -> Result<String, RequestError> {
|
||||
let pool = app::connect_db().await?;
|
||||
let config = AppConfig::load(&pool).await?;
|
||||
let path = if base {"/creddy/base-credentials"} else {"/"};
|
||||
|
||||
let mut stream = TcpStream::connect((config.listen_addr, config.listen_port)).await?;
|
||||
let req = format!("GET {path} HTTP/1.0\r\n\r\n");
|
||||
stream.write_all(req.as_bytes()).await?;
|
||||
|
||||
// some day we'll have a proper HTTP parser
|
||||
let mut buf = vec![0; 8192];
|
||||
stream.read_to_end(&mut buf).await?;
|
||||
|
||||
let status = buf.split(|&c| &[c] == b" ")
|
||||
.skip(1)
|
||||
.next()
|
||||
.ok_or(RequestError::MalformedHttpResponse)?;
|
||||
|
||||
if status != b"200" {
|
||||
let s = String::from_utf8_lossy(status).to_string();
|
||||
return Err(RequestError::Failed(s));
|
||||
}
|
||||
|
||||
let break_idx = buf.windows(4)
|
||||
.position(|w| w == b"\r\n\r\n")
|
||||
.ok_or(RequestError::MalformedHttpResponse)?;
|
||||
let body = &buf[(break_idx + 4)..];
|
||||
|
||||
let creds_str = std::str::from_utf8(body)
|
||||
.map_err(|_| RequestError::MalformedHttpResponse)?
|
||||
.to_string();
|
||||
|
||||
if creds_str == "Denied!" {
|
||||
return Err(RequestError::Rejected);
|
||||
}
|
||||
Ok(creds_str)
|
||||
}
|
||||
+12
-24
@@ -1,37 +1,25 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use netstat2::{AddressFamilyFlags, ProtocolFlags, ProtocolSocketInfo};
|
||||
use tauri::Manager;
|
||||
use sysinfo::{System, SystemExt, Pid, PidExt, ProcessExt};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::{
|
||||
app::APP,
|
||||
errors::*,
|
||||
config::AppConfig,
|
||||
state::AppState,
|
||||
};
|
||||
use crate::errors::*;
|
||||
use crate::get_state;
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||
pub struct Client {
|
||||
pub pid: u32,
|
||||
pub exe: PathBuf,
|
||||
pub exe: String,
|
||||
}
|
||||
|
||||
|
||||
async fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::error::Error> {
|
||||
let state = APP.get().unwrap().state::<AppState>();
|
||||
let AppConfig {
|
||||
listen_addr: app_listen_addr,
|
||||
listen_port: app_listen_port,
|
||||
..
|
||||
} = *state.config.read().await;
|
||||
|
||||
fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::error::Error> {
|
||||
let sockets_iter = netstat2::iterate_sockets_info(
|
||||
AddressFamilyFlags::IPV4,
|
||||
ProtocolFlags::TCP
|
||||
)?;
|
||||
|
||||
get_state!(config as app_config);
|
||||
for item in sockets_iter {
|
||||
let sock_info = item?;
|
||||
let proto_info = match sock_info.protocol_socket_info {
|
||||
@@ -40,9 +28,9 @@ async fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::erro
|
||||
};
|
||||
|
||||
if proto_info.local_port == local_port
|
||||
&& proto_info.remote_port == app_listen_port
|
||||
&& proto_info.local_addr == app_listen_addr
|
||||
&& proto_info.remote_addr == app_listen_addr
|
||||
&& proto_info.remote_port == app_config.listen_port
|
||||
&& proto_info.local_addr == app_config.listen_addr
|
||||
&& proto_info.remote_addr == app_config.listen_addr
|
||||
{
|
||||
return Ok(sock_info.associated_pids)
|
||||
}
|
||||
@@ -52,10 +40,10 @@ async fn get_associated_pids(local_port: u16) -> Result<Vec<u32>, netstat2::erro
|
||||
|
||||
|
||||
// Theoretically, on some systems, multiple processes can share a socket
|
||||
pub async fn get_clients(local_port: u16) -> Result<Vec<Option<Client>>, ClientInfoError> {
|
||||
pub fn get_clients(local_port: u16) -> Result<Vec<Option<Client>>, ClientInfoError> {
|
||||
let mut clients = Vec::new();
|
||||
let mut sys = System::new();
|
||||
for p in get_associated_pids(local_port).await? {
|
||||
for p in get_associated_pids(local_port)? {
|
||||
let pid = Pid::from_u32(p);
|
||||
sys.refresh_process(pid);
|
||||
let proc = sys.process(pid)
|
||||
@@ -63,7 +51,7 @@ pub async fn get_clients(local_port: u16) -> Result<Vec<Option<Client>>, ClientI
|
||||
|
||||
let client = Client {
|
||||
pid: p,
|
||||
exe: proc.exe().to_path_buf(),
|
||||
exe: proc.exe().to_string_lossy().into_owned(),
|
||||
};
|
||||
clients.push(Some(client));
|
||||
}
|
||||
|
||||
+24
-66
@@ -1,7 +1,6 @@
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use auto_launch::AutoLaunchBuilder;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
@@ -18,8 +17,6 @@ pub struct AppConfig {
|
||||
pub rehide_ms: u64,
|
||||
#[serde(default = "default_start_minimized")]
|
||||
pub start_minimized: bool,
|
||||
#[serde(default = "default_start_on_login")]
|
||||
pub start_on_login: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -30,79 +27,40 @@ impl Default for AppConfig {
|
||||
listen_port: default_listen_port(),
|
||||
rehide_ms: default_rehide_ms(),
|
||||
start_minimized: default_start_minimized(),
|
||||
start_on_login: default_start_on_login(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl AppConfig {
|
||||
pub async fn load(pool: &SqlitePool) -> Result<AppConfig, SetupError> {
|
||||
let res = sqlx::query!("SELECT * from config where name = 'main'")
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
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()),
|
||||
};
|
||||
let row = match res {
|
||||
Some(row) => row,
|
||||
None => return Ok(AppConfig::default()),
|
||||
};
|
||||
|
||||
Ok(serde_json::from_str(&row.data)?)
|
||||
}
|
||||
|
||||
pub async fn save(&self, pool: &SqlitePool) -> Result<(), sqlx::error::Error> {
|
||||
let data = serde_json::to_string(self).unwrap();
|
||||
sqlx::query(
|
||||
"INSERT INTO config (name, data) VALUES ('main', ?)
|
||||
ON CONFLICT (name) DO UPDATE SET data = ?"
|
||||
)
|
||||
.bind(&data)
|
||||
.bind(&data)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(serde_json::from_str(&row.data)?)
|
||||
}
|
||||
|
||||
|
||||
pub fn set_auto_launch(is_configured: bool) -> Result<(), SetupError> {
|
||||
let path_buf = std::env::current_exe()
|
||||
.map_err(|e| auto_launch::Error::Io(e))?;
|
||||
let path = path_buf
|
||||
.to_string_lossy();
|
||||
|
||||
let auto = AutoLaunchBuilder::new()
|
||||
.set_app_name("Creddy")
|
||||
.set_app_path(&path)
|
||||
.build()?;
|
||||
|
||||
let is_enabled = auto.is_enabled()?;
|
||||
if is_configured && !is_enabled {
|
||||
auto.enable()?;
|
||||
}
|
||||
else if !is_configured && is_enabled {
|
||||
auto.disable()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn get_or_create_db_path() -> Result<PathBuf, DataDirError> {
|
||||
let mut path = dirs::data_dir()
|
||||
.ok_or(DataDirError::NotFound)?;
|
||||
path.push("Creddy");
|
||||
|
||||
std::fs::create_dir_all(&path)?;
|
||||
pub fn get_or_create_db_path() -> PathBuf {
|
||||
if cfg!(debug_assertions) {
|
||||
path.push("creddy.dev.db");
|
||||
}
|
||||
else {
|
||||
path.push("creddy.db");
|
||||
return PathBuf::from("./creddy.db");
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +74,7 @@ fn default_listen_port() -> u16 {
|
||||
}
|
||||
|
||||
fn default_listen_addr() -> Ipv4Addr { Ipv4Addr::LOCALHOST }
|
||||
|
||||
fn default_rehide_ms() -> u64 { 1000 }
|
||||
// start minimized and on login only in production mode
|
||||
fn default_start_minimized() -> bool { !cfg!(debug_assertions) }
|
||||
fn default_start_on_login() -> bool { !cfg!(debug_assertions) }
|
||||
|
||||
fn default_start_minimized() -> bool { !cfg!(debug_assertions) } // default to start-minimized in production only
|
||||
@@ -1,321 +0,0 @@
|
||||
use std::fmt::{self, Formatter};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use aws_smithy_types::date_time::{DateTime, Format};
|
||||
use argon2::{
|
||||
Argon2,
|
||||
Algorithm,
|
||||
Version,
|
||||
ParamsBuilder,
|
||||
password_hash::rand_core::{RngCore, OsRng},
|
||||
};
|
||||
use chacha20poly1305::{
|
||||
XChaCha20Poly1305,
|
||||
XNonce,
|
||||
aead::{
|
||||
Aead,
|
||||
AeadCore,
|
||||
KeyInit,
|
||||
Error as AeadError,
|
||||
generic_array::GenericArray,
|
||||
},
|
||||
};
|
||||
use serde::{
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Serializer,
|
||||
Deserializer,
|
||||
};
|
||||
use serde::de::{self, Visitor};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
|
||||
use crate::errors::*;
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Session {
|
||||
Unlocked{
|
||||
base: BaseCredentials,
|
||||
session: SessionCredentials,
|
||||
},
|
||||
Locked(LockedCredentials),
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub async fn load(pool: &SqlitePool) -> Result<Self, SetupError> {
|
||||
let res = sqlx::query!("SELECT * FROM credentials ORDER BY created_at desc")
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
let row = match res {
|
||||
Some(r) => r,
|
||||
None => {return Ok(Session::Empty);}
|
||||
};
|
||||
|
||||
let salt: [u8; 32] = row.salt
|
||||
.try_into()
|
||||
.map_err(|_e| SetupError::InvalidRecord)?;
|
||||
let nonce = XNonce::from_exact_iter(row.nonce.into_iter())
|
||||
.ok_or(SetupError::InvalidRecord)?;
|
||||
|
||||
let creds = LockedCredentials {
|
||||
access_key_id: row.access_key_id,
|
||||
secret_key_enc: row.secret_key_enc,
|
||||
salt,
|
||||
nonce,
|
||||
};
|
||||
Ok(Session::Locked(creds))
|
||||
}
|
||||
|
||||
pub async fn renew_if_expired(&mut self) -> Result<bool, GetSessionError> {
|
||||
match self {
|
||||
Session::Unlocked{ref base, ref mut session} => {
|
||||
if !session.is_expired() {
|
||||
return Ok(false);
|
||||
}
|
||||
*session = SessionCredentials::from_base(base).await?;
|
||||
Ok(true)
|
||||
},
|
||||
Session::Locked(_) => Err(GetSessionError::CredentialsLocked),
|
||||
Session::Empty => Err(GetSessionError::CredentialsEmpty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LockedCredentials {
|
||||
pub access_key_id: String,
|
||||
pub secret_key_enc: Vec<u8>,
|
||||
pub salt: [u8; 32],
|
||||
pub nonce: XNonce,
|
||||
}
|
||||
|
||||
impl LockedCredentials {
|
||||
pub async fn save(&self, pool: &SqlitePool) -> Result<(), sqlx::Error> {
|
||||
sqlx::query(
|
||||
"INSERT INTO credentials (access_key_id, secret_key_enc, salt, nonce, created_at)
|
||||
VALUES (?, ?, ?, ?, strftime('%s'))"
|
||||
)
|
||||
.bind(&self.access_key_id)
|
||||
.bind(&self.secret_key_enc)
|
||||
.bind(&self.salt[..])
|
||||
.bind(&self.nonce[..])
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, passphrase: &str) -> Result<BaseCredentials, UnlockError> {
|
||||
let crypto = Crypto::new(passphrase, &self.salt)
|
||||
.map_err(|e| CryptoError::Argon2(e))?;
|
||||
let decrypted = crypto.decrypt(&self.nonce, &self.secret_key_enc)
|
||||
.map_err(|e| CryptoError::Aead(e))?;
|
||||
let secret_access_key = String::from_utf8(decrypted)
|
||||
.map_err(|_| UnlockError::InvalidUtf8)?;
|
||||
|
||||
let creds = BaseCredentials {
|
||||
access_key_id: self.access_key_id.clone(),
|
||||
secret_access_key,
|
||||
};
|
||||
Ok(creds)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct BaseCredentials {
|
||||
pub access_key_id: String,
|
||||
pub secret_access_key: String,
|
||||
}
|
||||
|
||||
impl BaseCredentials {
|
||||
pub fn encrypt(&self, passphrase: &str) -> Result<LockedCredentials, CryptoError> {
|
||||
let salt = Crypto::salt();
|
||||
let crypto = Crypto::new(passphrase, &salt)?;
|
||||
let (nonce, secret_key_enc) = crypto.encrypt(self.secret_access_key.as_bytes())?;
|
||||
|
||||
let locked = LockedCredentials {
|
||||
access_key_id: self.access_key_id.clone(),
|
||||
secret_key_enc,
|
||||
salt,
|
||||
nonce,
|
||||
};
|
||||
Ok(locked)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct SessionCredentials {
|
||||
pub access_key_id: String,
|
||||
pub secret_access_key: String,
|
||||
pub token: String,
|
||||
#[serde(serialize_with = "serialize_expiration")]
|
||||
#[serde(deserialize_with = "deserialize_expiration")]
|
||||
pub expiration: DateTime,
|
||||
}
|
||||
|
||||
impl SessionCredentials {
|
||||
pub async fn from_base(base: &BaseCredentials) -> Result<Self, GetSessionError> {
|
||||
let req_creds = aws_sdk_sts::Credentials::new(
|
||||
&base.access_key_id,
|
||||
&base.secret_access_key,
|
||||
None, // token
|
||||
None, //expiration
|
||||
"Creddy", // "provider name" apparently
|
||||
);
|
||||
let config = aws_config::from_env()
|
||||
.credentials_provider(req_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::EmptyResponse)?;
|
||||
|
||||
let access_key_id = aws_session.access_key_id()
|
||||
.ok_or(GetSessionError::EmptyResponse)?
|
||||
.to_string();
|
||||
let secret_access_key = aws_session.secret_access_key()
|
||||
.ok_or(GetSessionError::EmptyResponse)?
|
||||
.to_string();
|
||||
let token = aws_session.session_token()
|
||||
.ok_or(GetSessionError::EmptyResponse)?
|
||||
.to_string();
|
||||
let expiration = aws_session.expiration()
|
||||
.ok_or(GetSessionError::EmptyResponse)?
|
||||
.clone();
|
||||
|
||||
let session_creds = SessionCredentials {
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
token,
|
||||
expiration,
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
println!("Got new session:\n{}", serde_json::to_string(&session_creds).unwrap());
|
||||
|
||||
Ok(session_creds)
|
||||
}
|
||||
|
||||
pub fn is_expired(&self) -> bool {
|
||||
let current_ts = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap() // doesn't panic because UNIX_EPOCH won't be later than now()
|
||||
.as_secs();
|
||||
|
||||
let expire_ts = self.expiration.secs();
|
||||
let remaining = expire_ts - (current_ts as i64);
|
||||
remaining < 60
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_expiration<S>(exp: &DateTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
// this only fails if the d/t is out of range, which it can't be for this format
|
||||
let time_str = exp.fmt(Format::DateTime).unwrap();
|
||||
serializer.serialize_str(&time_str)
|
||||
}
|
||||
|
||||
|
||||
struct DateTimeVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for DateTimeVisitor {
|
||||
type Value = DateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
write!(formatter, "an RFC 3339 UTC string, e.g. \"2014-01-05T10:17:34Z\"")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, v: &str) -> Result<DateTime, E> {
|
||||
DateTime::from_str(v, Format::DateTime)
|
||||
.map_err(|_| E::custom(format!("Invalid date/time: {v}")))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn deserialize_expiration<'de, D>(deserializer: D) -> Result<DateTime, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
{
|
||||
deserializer.deserialize_str(DateTimeVisitor)
|
||||
}
|
||||
|
||||
|
||||
struct Crypto {
|
||||
cipher: XChaCha20Poly1305,
|
||||
}
|
||||
|
||||
impl Crypto {
|
||||
/// Argon2 params rationale:
|
||||
///
|
||||
/// m_cost is measured in KiB, so 128 * 1024 gives us 128MiB.
|
||||
/// This should roughly double the memory usage of the application
|
||||
/// while deriving the key.
|
||||
///
|
||||
/// p_cost is irrelevant since (at present) there isn't any parallelism
|
||||
/// implemented, so we leave it at 1.
|
||||
///
|
||||
/// With the above m_cost, t_cost = 8 results in about 800ms to derive
|
||||
/// a key on my (somewhat older) CPU. This is probably overkill, but
|
||||
/// given that it should only have to happen ~once a day for most
|
||||
/// usage, it should be acceptable.
|
||||
#[cfg(not(debug_assertions))]
|
||||
const MEM_COST: u32 = 128 * 1024;
|
||||
#[cfg(not(debug_assertions))]
|
||||
const TIME_COST: u32 = 8;
|
||||
|
||||
/// But since this takes a million years without optimizations,
|
||||
/// we turn it way down in debug builds.
|
||||
#[cfg(debug_assertions)]
|
||||
const MEM_COST: u32 = 48 * 1024;
|
||||
#[cfg(debug_assertions)]
|
||||
const TIME_COST: u32 = 1;
|
||||
|
||||
|
||||
fn new(passphrase: &str, salt: &[u8]) -> argon2::Result<Crypto> {
|
||||
let params = ParamsBuilder::new()
|
||||
.m_cost(Self::MEM_COST)
|
||||
.p_cost(1)
|
||||
.t_cost(Self::TIME_COST)
|
||||
.build()
|
||||
.unwrap(); // only errors if the given params are invalid
|
||||
|
||||
let hasher = Argon2::new(
|
||||
Algorithm::Argon2id,
|
||||
Version::V0x13,
|
||||
params,
|
||||
);
|
||||
|
||||
let mut key = [0; 32];
|
||||
hasher.hash_password_into(passphrase.as_bytes(), &salt, &mut key)?;
|
||||
let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(&key));
|
||||
Ok(Crypto { cipher })
|
||||
}
|
||||
|
||||
fn salt() -> [u8; 32] {
|
||||
let mut salt = [0; 32];
|
||||
OsRng.fill_bytes(&mut salt);
|
||||
salt
|
||||
}
|
||||
|
||||
fn encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec<u8>), AeadError> {
|
||||
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||
let ciphertext = self.cipher.encrypt(&nonce, data)?;
|
||||
Ok((nonce, ciphertext))
|
||||
}
|
||||
|
||||
fn decrypt(&self, nonce: &XNonce, data: &[u8]) -> Result<Vec<u8>, AeadError> {
|
||||
self.cipher.decrypt(nonce, data)
|
||||
}
|
||||
}
|
||||
+33
-113
@@ -1,9 +1,9 @@
|
||||
use std::error::Error;
|
||||
use std::convert::AsRef;
|
||||
use std::sync::mpsc;
|
||||
use strum_macros::AsRefStr;
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
use aws_sdk_sts::{
|
||||
types::SdkError as AwsSdkError,
|
||||
error::GetSessionTokenError,
|
||||
@@ -12,29 +12,32 @@ use sqlx::{
|
||||
error::Error as SqlxError,
|
||||
migrate::MigrateError,
|
||||
};
|
||||
use tauri::api::dialog::{
|
||||
MessageDialogBuilder,
|
||||
MessageDialogKind,
|
||||
};
|
||||
|
||||
use serde::{Serialize, Serializer, ser::SerializeMap};
|
||||
|
||||
|
||||
pub trait ErrorPopup {
|
||||
fn error_popup(self, title: &str);
|
||||
}
|
||||
// pub struct SerializeError<E> {
|
||||
// pub err: E,
|
||||
// }
|
||||
|
||||
impl<E: Error> ErrorPopup for Result<(), E> {
|
||||
fn error_popup(self, title: &str) {
|
||||
if let Err(e) = self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
MessageDialogBuilder::new(title, format!("{e}"))
|
||||
.kind(MessageDialogKind::Error)
|
||||
.show(move |_| tx.send(true).unwrap());
|
||||
// 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()
|
||||
// }
|
||||
// }
|
||||
|
||||
rx.recv().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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>
|
||||
@@ -84,21 +87,6 @@ pub enum SetupError {
|
||||
MigrationError(#[from] MigrateError),
|
||||
#[error("Error parsing configuration from database")]
|
||||
ConfigParseError(#[from] serde_json::Error),
|
||||
#[error("Failed to set up start-on-login: {0}")]
|
||||
AutoLaunchError(#[from] auto_launch::Error),
|
||||
#[error("Failed to start listener: {0}")]
|
||||
ServerSetupError(#[from] std::io::Error),
|
||||
#[error("Failed to resolve data directory: {0}")]
|
||||
DataDir(#[from] DataDirError),
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum DataDirError {
|
||||
#[error("Could not determine data directory")]
|
||||
NotFound,
|
||||
#[error("Failed to create data directory: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
|
||||
@@ -106,23 +94,20 @@ pub enum DataDirError {
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum SendResponseError {
|
||||
#[error("The specified credentials request was not found")]
|
||||
NotFound,
|
||||
NotFound, // no request with the given id
|
||||
#[error("The specified request was already closed by the client")]
|
||||
Abandoned,
|
||||
#[error("Could not renew AWS sesssion: {0}")]
|
||||
SessionRenew(#[from] GetSessionError),
|
||||
Abandoned, // request has already been closed by client
|
||||
}
|
||||
|
||||
|
||||
// errors encountered while handling an HTTP request
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum HandlerError {
|
||||
pub enum RequestError {
|
||||
#[error("Error writing to stream: {0}")]
|
||||
StreamIOError(#[from] std::io::Error),
|
||||
// #[error("Received invalid UTF-8 in request")]
|
||||
// InvalidUtf8,
|
||||
#[error("HTTP request malformed")]
|
||||
BadRequest(Vec<u8>),
|
||||
// MalformedHttpRequest,
|
||||
#[error("HTTP request too large")]
|
||||
RequestTooLarge,
|
||||
#[error("Error accessing credentials: {0}")]
|
||||
@@ -148,13 +133,9 @@ pub enum GetCredentialsError {
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum GetSessionError {
|
||||
#[error("Request completed successfully but no credentials were returned")]
|
||||
EmptyResponse, // SDK returned successfully but credentials are None
|
||||
NoCredentials, // SDK returned successfully but credentials are None
|
||||
#[error("Error response from AWS SDK: {0}")]
|
||||
SdkError(#[from] AwsSdkError<GetSessionTokenError>),
|
||||
#[error("Could not construt session: credentials are locked")]
|
||||
CredentialsLocked,
|
||||
#[error("Could not construct session: no credentials are known")]
|
||||
CredentialsEmpty,
|
||||
}
|
||||
|
||||
|
||||
@@ -164,8 +145,8 @@ pub enum UnlockError {
|
||||
NotLocked,
|
||||
#[error("No saved credentials were found")]
|
||||
NoCredentials,
|
||||
#[error(transparent)]
|
||||
Crypto(#[from] CryptoError),
|
||||
#[error("Invalid passphrase")]
|
||||
BadPassphrase,
|
||||
#[error("Data was found to be corrupt after decryption")]
|
||||
InvalidUtf8, // Somehow we got invalid utf-8 even though decryption succeeded
|
||||
#[error("Database error: {0}")]
|
||||
@@ -175,15 +156,6 @@ pub enum UnlockError {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum CryptoError {
|
||||
#[error(transparent)]
|
||||
Argon2(#[from] argon2::Error),
|
||||
#[error("Invalid passphrase")] // I think this is the only way decryption fails
|
||||
Aead(#[from] chacha20poly1305::aead::Error),
|
||||
}
|
||||
|
||||
|
||||
// Errors encountered while trying to figure out who's on the other end of a request
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum ClientInfoError {
|
||||
@@ -194,43 +166,6 @@ pub enum ClientInfoError {
|
||||
}
|
||||
|
||||
|
||||
// Errors encountered while requesting credentials via CLI (creddy show, creddy exec)
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum RequestError {
|
||||
#[error("Credentials request failed: HTTP {0}")]
|
||||
Failed(String),
|
||||
#[error("Credentials request was rejected")]
|
||||
Rejected,
|
||||
#[error("Couldn't interpret the server's response")]
|
||||
MalformedHttpResponse,
|
||||
#[error("The server did not respond with valid JSON")]
|
||||
InvalidJson,
|
||||
#[error("Error reading/writing stream: {0}")]
|
||||
StreamIOError(#[from] std::io::Error),
|
||||
#[error("Error loading configuration data: {0}")]
|
||||
Setup(#[from] SetupError),
|
||||
}
|
||||
|
||||
|
||||
// Errors encountered while running a subprocess (via creddy exec)
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum ExecError {
|
||||
#[error("Please specify a command")]
|
||||
NoCommand,
|
||||
#[error("Failed to execute command: {0}")]
|
||||
ExecutionFailed(#[from] std::io::Error)
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, ThisError, AsRefStr)]
|
||||
pub enum CliError {
|
||||
#[error(transparent)]
|
||||
Request(#[from] RequestError),
|
||||
#[error(transparent)]
|
||||
Exec(#[from] ExecError),
|
||||
}
|
||||
|
||||
|
||||
// =========================
|
||||
// Serialize implementations
|
||||
// =========================
|
||||
@@ -252,35 +187,20 @@ impl Serialize for SerializeWrapper<&GetSessionTokenError> {
|
||||
|
||||
|
||||
impl_serialize_basic!(SetupError);
|
||||
impl_serialize_basic!(SendResponseError);
|
||||
impl_serialize_basic!(GetCredentialsError);
|
||||
impl_serialize_basic!(ClientInfoError);
|
||||
|
||||
|
||||
impl Serialize for HandlerError {
|
||||
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 {
|
||||
HandlerError::NoCredentials(src) => map.serialize_entry("source", &src)?,
|
||||
HandlerError::ClientInfo(src) => map.serialize_entry("source", &src)?,
|
||||
_ => serialize_upstream_err(self, &mut map)?,
|
||||
}
|
||||
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Serialize for SendResponseError {
|
||||
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 {
|
||||
SendResponseError::SessionRenew(src) => map.serialize_entry("source", &src)?,
|
||||
RequestError::NoCredentials(src) => map.serialize_entry("source", &src)?,
|
||||
RequestError::ClientInfo(src) => map.serialize_entry("source", &src)?,
|
||||
_ => serialize_upstream_err(self, &mut map)?,
|
||||
}
|
||||
|
||||
|
||||
+19
-23
@@ -1,18 +1,16 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use tauri::State;
|
||||
|
||||
use crate::config::AppConfig;
|
||||
use crate::credentials::{Session,BaseCredentials};
|
||||
use crate::errors::*;
|
||||
use crate::config::AppConfig;
|
||||
use crate::clientinfo::Client;
|
||||
use crate::state::AppState;
|
||||
use crate::state::{AppState, Session, Credentials};
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Request {
|
||||
pub id: u64,
|
||||
pub clients: Vec<Option<Client>>,
|
||||
pub base: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -31,50 +29,48 @@ pub enum Approval {
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn respond(response: RequestResponse, app_state: State<'_, AppState>) -> Result<(), SendResponseError> {
|
||||
app_state.send_response(response).await
|
||||
pub fn respond(response: RequestResponse, app_state: State<'_, AppState>) -> Result<(), String> {
|
||||
app_state.send_response(response)
|
||||
.map_err(|e| format!("Error responding to request: {e}"))
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unlock(passphrase: String, app_state: State<'_, AppState>) -> Result<(), UnlockError> {
|
||||
app_state.unlock(&passphrase).await
|
||||
app_state.decrypt(&passphrase).await
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_session_status(app_state: State<'_, AppState>) -> Result<String, ()> {
|
||||
let session = app_state.session.read().await;
|
||||
let status = match *session {
|
||||
pub fn get_session_status(app_state: State<'_, AppState>) -> String {
|
||||
let session = app_state.session.read().unwrap();
|
||||
match *session {
|
||||
Session::Locked(_) => "locked".into(),
|
||||
Session::Unlocked{..} => "unlocked".into(),
|
||||
Session::Unlocked(_) => "unlocked".into(),
|
||||
Session::Empty => "empty".into()
|
||||
};
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_credentials(
|
||||
credentials: BaseCredentials,
|
||||
credentials: Credentials,
|
||||
passphrase: String,
|
||||
app_state: State<'_, AppState>
|
||||
) -> Result<(), UnlockError> {
|
||||
app_state.new_creds(credentials, &passphrase).await
|
||||
app_state.save_creds(credentials, &passphrase).await
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_config(app_state: State<'_, AppState>) -> Result<AppConfig, ()> {
|
||||
let config = app_state.config.read().await;
|
||||
Ok(config.clone())
|
||||
pub fn get_config(app_state: State<'_, AppState>) -> AppConfig {
|
||||
let config = app_state.config.read().unwrap();
|
||||
config.clone()
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_config(config: AppConfig, app_state: State<'_, AppState>) -> Result<(), String> {
|
||||
app_state.update_config(config)
|
||||
.await
|
||||
.map_err(|e| format!("Error saving config: {e}"))?;
|
||||
Ok(())
|
||||
pub fn save_config(config: AppConfig, app_state: State<'_, AppState>) {
|
||||
let mut prev_config = app_state.config.write().unwrap();
|
||||
*prev_config = config;
|
||||
}
|
||||
+79
-90
@@ -3,25 +3,10 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
#[cfg(windows)]
|
||||
use {
|
||||
std::fs::File,
|
||||
std::os::windows::io::FromRawHandle,
|
||||
std::os::raw::c_void,
|
||||
gag::Redirect,
|
||||
windows::Win32::System::Console::{
|
||||
AllocConsole,
|
||||
AttachConsole,
|
||||
GetStdHandle,
|
||||
STD_OUTPUT_HANDLE,
|
||||
STD_ERROR_HANDLE,
|
||||
}
|
||||
};
|
||||
use tauri::{AppHandle, Manager, async_runtime as rt};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
mod app;
|
||||
mod cli;
|
||||
mod config;
|
||||
mod credentials;
|
||||
mod errors;
|
||||
mod clientinfo;
|
||||
mod ipc;
|
||||
@@ -29,84 +14,88 @@ mod state;
|
||||
mod server;
|
||||
mod tray;
|
||||
|
||||
use crate::errors::ErrorPopup;
|
||||
use crate::errors::*;
|
||||
use state::AppState;
|
||||
|
||||
|
||||
pub static APP: OnceCell<AppHandle> = OnceCell::new();
|
||||
|
||||
use std::io::Write;
|
||||
fn main() {
|
||||
let args = cli::parser().get_matches();
|
||||
let help = matches!(args.get_one::<bool>("help"), Some(true));
|
||||
|
||||
// This is the only case that doesn't need a console
|
||||
if let None | Some(("run", _)) = args.subcommand() {
|
||||
if !help {
|
||||
app::run().error_popup("Creddy failed to start");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// on Windows, need to do the whole allocate-a-console thing
|
||||
#[cfg(windows)]
|
||||
attach_console();
|
||||
// let (out, err) = setup_console();
|
||||
println!("Testing stdout");
|
||||
// writeln!(&mut out, "Testing allocated file");
|
||||
|
||||
if help {
|
||||
// if we can't print help, we can't print an error, so just panic
|
||||
dbg!(args.get_one::<bool>("help"));
|
||||
cli::parser().print_help().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||
return;
|
||||
}
|
||||
|
||||
let res = match args.subcommand() {
|
||||
Some(("show", m)) => cli::show(m),
|
||||
Some(("exec", m)) => cli::exec(m),
|
||||
// clap ensures that subcommand is either None or run/show/exec
|
||||
_ => unreachable!(),
|
||||
let initial_state = match rt::block_on(state::AppState::load()) {
|
||||
Ok(state) => state,
|
||||
Err(e) => {eprintln!("{}", e); return;}
|
||||
};
|
||||
|
||||
if let Err(e) = res {
|
||||
eprintln!("Error: {e}");
|
||||
tauri::Builder::default()
|
||||
.manage(initial_state)
|
||||
.system_tray(tray::create())
|
||||
.on_system_tray_event(tray::handle_event)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
ipc::unlock,
|
||||
ipc::respond,
|
||||
ipc::get_session_status,
|
||||
ipc::save_credentials,
|
||||
ipc::get_config,
|
||||
ipc::save_config,
|
||||
])
|
||||
.setup(|app| {
|
||||
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()));
|
||||
|
||||
if !config.start_minimized {
|
||||
app.get_window("main")
|
||||
.ok_or(RequestError::NoMainWindow)?
|
||||
.show()?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn attach_console() {
|
||||
unsafe { AttachConsole(u32::MAX); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn setup_console() -> (Redirect<File>, Redirect<File>) {
|
||||
let (mut stdout, stderr) = unsafe {
|
||||
AllocConsole();
|
||||
// if we can't get handles to stdout/err, we can't display these errors,
|
||||
// so there's no point in doing anything other than panicking
|
||||
let stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE).unwrap();
|
||||
let stderr_handle = GetStdHandle(STD_ERROR_HANDLE).unwrap();
|
||||
|
||||
let mut f = File::create("C:\\Users\\Joe\\Downloads\\debug.txt").unwrap();
|
||||
writeln!(&mut f, "{stdout_handle:?}\n{:?}", stdout_handle.0 as *mut c_void).unwrap();
|
||||
|
||||
(
|
||||
File::from_raw_handle(stdout_handle.0 as *mut c_void),
|
||||
File::from_raw_handle(stderr_handle.0 as *mut c_void),
|
||||
)
|
||||
};
|
||||
|
||||
writeln!(&mut stdout, "Testing stdout before redirect")
|
||||
.map_err(|e| log_err(e))
|
||||
.unwrap();
|
||||
|
||||
(
|
||||
Redirect::stdout(stdout).unwrap(),
|
||||
Redirect::stderr(stderr).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn log_err(e: impl std::error::Error) {
|
||||
let mut f = File::create("C:\\Users\\Joe\\Downloads\\log.txt").unwrap();
|
||||
writeln!(&mut f, "{e}").unwrap();
|
||||
}
|
||||
pub(crate) use get_state;
|
||||
+44
-111
@@ -1,21 +1,12 @@
|
||||
use core::time::Duration;
|
||||
use std::io;
|
||||
use std::net::{
|
||||
Ipv4Addr,
|
||||
SocketAddr,
|
||||
SocketAddrV4,
|
||||
};
|
||||
use tokio::net::{
|
||||
TcpListener,
|
||||
TcpStream,
|
||||
};
|
||||
use std::net::{SocketAddr, SocketAddrV4};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri::async_runtime as rt;
|
||||
use tauri::async_runtime::JoinHandle;
|
||||
|
||||
use crate::{clientinfo, clientinfo::Client};
|
||||
use crate::errors::*;
|
||||
@@ -31,10 +22,10 @@ struct Handler {
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
async fn new(stream: TcpStream, app: AppHandle) -> Self {
|
||||
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).await;
|
||||
let request_id = state.register_request(chan_send);
|
||||
Handler {
|
||||
request_id,
|
||||
stream,
|
||||
@@ -48,40 +39,28 @@ impl Handler {
|
||||
eprintln!("{e}");
|
||||
}
|
||||
let state = self.app.state::<AppState>();
|
||||
state.unregister_request(self.request_id).await;
|
||||
state.unregister_request(self.request_id);
|
||||
}
|
||||
|
||||
async fn try_handle(&mut self) -> Result<(), HandlerError> {
|
||||
let req_path = self.recv_request().await?;
|
||||
let clients = self.get_clients().await?;
|
||||
if self.includes_banned(&clients).await {
|
||||
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 base = req_path == b"/creddy/base-credentials";
|
||||
|
||||
let req = Request {id: self.request_id, clients, base};
|
||||
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 => {
|
||||
let state = self.app.state::<AppState>();
|
||||
let creds = if base {
|
||||
state.serialize_base_creds().await?
|
||||
}
|
||||
else {
|
||||
state.serialize_session_creds().await?
|
||||
};
|
||||
self.send_body(creds.as_bytes()).await?;
|
||||
},
|
||||
Approval::Approved => self.send_credentials().await?,
|
||||
Approval::Denied => {
|
||||
let state = self.app.state::<AppState>();
|
||||
for client in req.clients {
|
||||
state.add_ban(client).await;
|
||||
state.add_ban(client, self.app.clone());
|
||||
}
|
||||
self.send_body(b"Denied!").await?;
|
||||
self.stream.shutdown().await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,62 +68,51 @@ impl Handler {
|
||||
// and b) there are no other pending requests
|
||||
let state = self.app.state::<AppState>();
|
||||
let delay = {
|
||||
let config = state.config.read().await;
|
||||
let config = state.config.read().unwrap();
|
||||
Duration::from_millis(config.rehide_ms)
|
||||
};
|
||||
sleep(delay).await;
|
||||
|
||||
if !starting_visibility && state.req_count().await == 0 {
|
||||
let window = self.app.get_window("main").ok_or(HandlerError::NoMainWindow)?;
|
||||
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>, HandlerError> {
|
||||
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(HandlerError::RequestTooLarge);}
|
||||
if n == buf.len() {return Err(RequestError::RequestTooLarge);}
|
||||
}
|
||||
|
||||
let path = buf.split(|&c| &[c] == b" ")
|
||||
.skip(1)
|
||||
.next()
|
||||
.ok_or(HandlerError::BadRequest(buf.clone()))?;
|
||||
|
||||
#[cfg(debug_assertions)] {
|
||||
println!("Path: {}", std::str::from_utf8(&path).unwrap());
|
||||
if cfg!(debug_assertions) {
|
||||
println!("{}", std::str::from_utf8(&buf).unwrap());
|
||||
}
|
||||
|
||||
Ok(path.into())
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
async fn get_clients(&self) -> Result<Vec<Option<Client>>, HandlerError> {
|
||||
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()).await?;
|
||||
let clients = clientinfo::get_clients(peer_addr.port())?;
|
||||
Ok(clients)
|
||||
}
|
||||
|
||||
async fn includes_banned(&self, clients: &Vec<Option<Client>>) -> bool {
|
||||
fn includes_banned(&self, clients: &Vec<Option<Client>>) -> bool {
|
||||
let state = self.app.state::<AppState>();
|
||||
for client in clients {
|
||||
if state.is_banned(client).await {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
clients.iter().any(|c| state.is_banned(c))
|
||||
}
|
||||
|
||||
fn show_window(&self) -> Result<bool, HandlerError> {
|
||||
let window = self.app.get_window("main").ok_or(HandlerError::NoMainWindow)?;
|
||||
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()?;
|
||||
@@ -154,7 +122,7 @@ impl Handler {
|
||||
Ok(starting_visibility)
|
||||
}
|
||||
|
||||
async fn wait_for_response(&mut self) -> Result<Approval, HandlerError> {
|
||||
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?;
|
||||
@@ -177,66 +145,31 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_body(&mut self, body: &[u8]) -> Result<(), HandlerError> {
|
||||
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(body.len().to_string().as_bytes()).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?;
|
||||
self.stream.write(body).await?;
|
||||
self.stream.shutdown().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Server {
|
||||
addr: Ipv4Addr,
|
||||
port: u16,
|
||||
app_handle: AppHandle,
|
||||
task: JoinHandle<()>,
|
||||
}
|
||||
|
||||
|
||||
impl Server {
|
||||
pub async fn new(addr: Ipv4Addr, port: u16, app_handle: AppHandle) -> io::Result<Server> {
|
||||
let task = Self::start_server(addr, port, app_handle.app_handle()).await?;
|
||||
Ok(Server { addr, port, app_handle, task})
|
||||
}
|
||||
|
||||
pub async fn rebind(&mut self, addr: Ipv4Addr, port: u16) -> io::Result<()> {
|
||||
if addr == self.addr && port == self.port {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let new_task = Self::start_server(addr, port, self.app_handle.app_handle()).await?;
|
||||
self.task.abort();
|
||||
|
||||
self.addr = addr;
|
||||
self.port = port;
|
||||
self.task = new_task;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// construct the listener before spawning the task so that we can return early if it fails
|
||||
async fn start_server(addr: Ipv4Addr, port: u16, app_handle: AppHandle) -> io::Result<JoinHandle<()>> {
|
||||
let sock_addr = SocketAddrV4::new(addr, port);
|
||||
let listener = TcpListener::bind(&sock_addr).await?;
|
||||
let task = rt::spawn(
|
||||
Self::serve(listener, app_handle.app_handle())
|
||||
);
|
||||
Ok(task)
|
||||
}
|
||||
|
||||
async fn serve(listener: TcpListener, app_handle: AppHandle) {
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
let handler = Handler::new(stream, app_handle.app_handle()).await;
|
||||
rt::spawn(handler.handle());
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Error accepting connection: {e}");
|
||||
}
|
||||
pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()> {
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on {addr}");
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
let handler = Handler::new(stream, app_handle.app_handle());
|
||||
tauri::async_runtime::spawn(handler.handle());
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Error accepting connection: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+204
-85
@@ -1,26 +1,59 @@
|
||||
use core::time::Duration;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::Duration;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use tokio::{
|
||||
sync::oneshot::Sender,
|
||||
sync::RwLock,
|
||||
time::sleep,
|
||||
use serde::{Serialize, Deserialize};
|
||||
use tokio::sync::oneshot::Sender;
|
||||
use tokio::time::sleep;
|
||||
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, sqlite::SqliteConnectOptions};
|
||||
use sodiumoxide::crypto::{
|
||||
pwhash,
|
||||
pwhash::Salt,
|
||||
secretbox,
|
||||
secretbox::{Nonce, Key}
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
use tauri::async_runtime as runtime;
|
||||
use tauri::Manager;
|
||||
|
||||
use crate::app::APP;
|
||||
use crate::credentials::{
|
||||
Session,
|
||||
BaseCredentials,
|
||||
SessionCredentials,
|
||||
};
|
||||
use crate::{config, config::AppConfig};
|
||||
use crate::ipc::{self, Approval};
|
||||
use crate::ipc;
|
||||
use crate::clientinfo::Client;
|
||||
use crate::errors::*;
|
||||
use crate::server::Server;
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Credentials {
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
LongLived {
|
||||
access_key_id: String,
|
||||
secret_access_key: String,
|
||||
},
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
ShortLived {
|
||||
access_key_id: String,
|
||||
secret_access_key: String,
|
||||
token: String,
|
||||
expiration: String,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LockedCredentials {
|
||||
access_key_id: String,
|
||||
secret_key_enc: Vec<u8>,
|
||||
salt: Salt,
|
||||
nonce: Nonce,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Session {
|
||||
Unlocked(Credentials),
|
||||
Locked(LockedCredentials),
|
||||
Empty,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -30,79 +63,117 @@ pub struct AppState {
|
||||
pub request_count: RwLock<u64>,
|
||||
pub open_requests: RwLock<HashMap<u64, Sender<ipc::Approval>>>,
|
||||
pub bans: RwLock<std::collections::HashSet<Option<Client>>>,
|
||||
server: RwLock<Server>,
|
||||
pool: sqlx::SqlitePool,
|
||||
pool: SqlitePool,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(config: AppConfig, session: Session, server: Server, pool: SqlitePool) -> AppState {
|
||||
AppState {
|
||||
config: RwLock::new(config),
|
||||
session: RwLock::new(session),
|
||||
pub async fn load() -> Result<Self, SetupError> {
|
||||
let conn_opts = SqliteConnectOptions::new()
|
||||
.filename(config::get_or_create_db_path())
|
||||
.create_if_missing(true);
|
||||
let pool_opts = SqlitePoolOptions::new();
|
||||
|
||||
let pool: SqlitePool = pool_opts.connect_with(conn_opts).await?;
|
||||
sqlx::migrate!().run(&pool).await?;
|
||||
let creds = Self::load_creds(&pool).await?;
|
||||
let conf = config::load(&pool).await?;
|
||||
|
||||
let state = AppState {
|
||||
config: RwLock::new(conf),
|
||||
session: RwLock::new(creds),
|
||||
request_count: RwLock::new(0),
|
||||
open_requests: RwLock::new(HashMap::new()),
|
||||
bans: RwLock::new(HashSet::new()),
|
||||
server: RwLock::new(server),
|
||||
pool,
|
||||
}
|
||||
};
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub async fn new_creds(&self, base_creds: BaseCredentials, passphrase: &str) -> Result<(), UnlockError> {
|
||||
let locked = base_creds.encrypt(passphrase)?;
|
||||
async fn load_creds(pool: &SqlitePool) -> Result<Session, SetupError> {
|
||||
let res = sqlx::query!("SELECT * FROM credentials ORDER BY created_at desc")
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
let row = match res {
|
||||
Some(r) => r,
|
||||
None => {return Ok(Session::Empty);}
|
||||
};
|
||||
|
||||
let salt_buf: [u8; 32] = row.salt
|
||||
.try_into()
|
||||
.map_err(|_e| SetupError::InvalidRecord)?;
|
||||
let nonce_buf: [u8; 24] = row.nonce
|
||||
.try_into()
|
||||
.map_err(|_e| SetupError::InvalidRecord)?;
|
||||
|
||||
let creds = LockedCredentials {
|
||||
access_key_id: row.access_key_id,
|
||||
secret_key_enc: row.secret_key_enc,
|
||||
salt: Salt(salt_buf),
|
||||
nonce: Nonce(nonce_buf),
|
||||
};
|
||||
Ok(Session::Locked(creds))
|
||||
}
|
||||
|
||||
pub async fn save_creds(&self, creds: Credentials, passphrase: &str) -> Result<(), UnlockError> {
|
||||
let (key_id, secret_key) = match creds {
|
||||
Credentials::LongLived {access_key_id, secret_access_key} => {
|
||||
(access_key_id, secret_access_key)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// do this first so that if it fails we don't save bad credentials
|
||||
self.new_session(base_creds).await?;
|
||||
locked.save(&self.pool).await?;
|
||||
self.new_session(&key_id, &secret_key).await?;
|
||||
|
||||
let salt = pwhash::gen_salt();
|
||||
let mut key_buf = [0; secretbox::KEYBYTES];
|
||||
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &salt).unwrap();
|
||||
let key = Key(key_buf);
|
||||
// not sure we need both salt AND nonce given that we generate a
|
||||
// fresh salt every time we encrypt, but better safe than sorry
|
||||
let nonce = secretbox::gen_nonce();
|
||||
let secret_key_enc = secretbox::seal(secret_key.as_bytes(), &nonce, &key);
|
||||
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO credentials (access_key_id, secret_key_enc, salt, nonce, created_at)
|
||||
VALUES (?, ?, ?, ?, strftime('%s'))"
|
||||
)
|
||||
.bind(&key_id)
|
||||
.bind(&secret_key_enc)
|
||||
.bind(&salt.0[0..])
|
||||
.bind(&nonce.0[0..])
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_config(&self, new_config: AppConfig) -> Result<(), SetupError> {
|
||||
let mut live_config = self.config.write().await;
|
||||
|
||||
if new_config.start_on_login != live_config.start_on_login {
|
||||
config::set_auto_launch(new_config.start_on_login)?;
|
||||
}
|
||||
if new_config.listen_addr != live_config.listen_addr
|
||||
|| new_config.listen_port != live_config.listen_port
|
||||
{
|
||||
let mut sv = self.server.write().await;
|
||||
sv.rebind(new_config.listen_addr, new_config.listen_port).await?;
|
||||
}
|
||||
|
||||
new_config.save(&self.pool).await?;
|
||||
*live_config = new_config;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn register_request(&self, chan: Sender<ipc::Approval>) -> u64 {
|
||||
pub fn register_request(&self, chan: Sender<ipc::Approval>) -> u64 {
|
||||
let count = {
|
||||
let mut c = self.request_count.write().await;
|
||||
let mut c = self.request_count.write().unwrap();
|
||||
*c += 1;
|
||||
c
|
||||
};
|
||||
|
||||
let mut open_requests = self.open_requests.write().await;
|
||||
let mut open_requests = self.open_requests.write().unwrap();
|
||||
open_requests.insert(*count, chan); // `count` is the request id
|
||||
*count
|
||||
}
|
||||
|
||||
pub async fn unregister_request(&self, id: u64) {
|
||||
let mut open_requests = self.open_requests.write().await;
|
||||
pub fn unregister_request(&self, id: u64) {
|
||||
let mut open_requests = self.open_requests.write().unwrap();
|
||||
open_requests.remove(&id);
|
||||
}
|
||||
|
||||
pub async fn req_count(&self) -> usize {
|
||||
let open_requests = self.open_requests.read().await;
|
||||
pub fn req_count(&self) -> usize {
|
||||
let open_requests = self.open_requests.read().unwrap();
|
||||
open_requests.len()
|
||||
}
|
||||
|
||||
pub async fn send_response(&self, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
|
||||
if let Approval::Approved = response.approval {
|
||||
let mut session = self.session.write().await;
|
||||
session.renew_if_expired().await?;
|
||||
}
|
||||
|
||||
let mut open_requests = self.open_requests.write().await;
|
||||
pub fn send_response(&self, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
|
||||
let mut open_requests = self.open_requests.write().unwrap();
|
||||
let chan = open_requests
|
||||
.remove(&response.id)
|
||||
.ok_or(SendResponseError::NotFound)
|
||||
@@ -112,57 +183,105 @@ impl AppState {
|
||||
.map_err(|_e| SendResponseError::Abandoned)
|
||||
}
|
||||
|
||||
pub async fn add_ban(&self, client: Option<Client>) {
|
||||
let mut bans = self.bans.write().await;
|
||||
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 app = APP.get().unwrap();
|
||||
let state = app.state::<AppState>();
|
||||
let mut bans = state.bans.write().await;
|
||||
let mut bans = state.bans.write().unwrap();
|
||||
bans.remove(&client);
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn is_banned(&self, client: &Option<Client>) -> bool {
|
||||
self.bans.read().await.contains(&client)
|
||||
pub fn is_banned(&self, client: &Option<Client>) -> bool {
|
||||
self.bans.read().unwrap().contains(&client)
|
||||
}
|
||||
|
||||
pub async fn unlock(&self, passphrase: &str) -> Result<(), UnlockError> {
|
||||
let base_creds = match *self.session.read().await {
|
||||
Session::Empty => {return Err(UnlockError::NoCredentials);},
|
||||
Session::Unlocked{..} => {return Err(UnlockError::NotLocked);},
|
||||
Session::Locked(ref locked) => locked.decrypt(passphrase)?,
|
||||
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 locked = match *session {
|
||||
Session::Empty => {return Err(UnlockError::NoCredentials);},
|
||||
Session::Unlocked(_) => {return Err(UnlockError::NotLocked);},
|
||||
Session::Locked(ref c) => c,
|
||||
};
|
||||
|
||||
let mut key_buf = [0; secretbox::KEYBYTES];
|
||||
// pretty sure this only fails if we're out of memory
|
||||
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &locked.salt).unwrap();
|
||||
let decrypted = secretbox::open(&locked.secret_key_enc, &locked.nonce, &Key(key_buf))
|
||||
.map_err(|_e| UnlockError::BadPassphrase)?;
|
||||
|
||||
let secret_str = String::from_utf8(decrypted).map_err(|_e| UnlockError::InvalidUtf8)?;
|
||||
(locked.access_key_id.clone(), secret_str)
|
||||
};
|
||||
// Read lock is dropped here, so this doesn't deadlock
|
||||
self.new_session(base_creds).await?;
|
||||
|
||||
self.new_session(&key_id, &secret).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn serialize_base_creds(&self) -> Result<String, GetCredentialsError> {
|
||||
let session = self.session.read().await;
|
||||
pub fn get_creds_serialized(&self) -> Result<String, GetCredentialsError> {
|
||||
let session = self.session.read().unwrap();
|
||||
match *session {
|
||||
Session::Unlocked{ref base, ..} => Ok(serde_json::to_string(base).unwrap()),
|
||||
Session::Unlocked(ref creds) => Ok(serde_json::to_string(creds).unwrap()),
|
||||
Session::Locked(_) => Err(GetCredentialsError::Locked),
|
||||
Session::Empty => Err(GetCredentialsError::Empty),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn serialize_session_creds(&self) -> Result<String, GetCredentialsError> {
|
||||
let session = self.session.read().await;
|
||||
match *session {
|
||||
Session::Unlocked{ref session, ..} => Ok(serde_json::to_string(session).unwrap()),
|
||||
Session::Locked(_) => Err(GetCredentialsError::Locked),
|
||||
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);
|
||||
|
||||
async fn new_session(&self, base: BaseCredentials) -> Result<(), GetSessionError> {
|
||||
let session = SessionCredentials::from_base(&base).await?;
|
||||
let mut app_session = self.session.write().await;
|
||||
*app_session = Session::Unlocked {base, session};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,12 @@
|
||||
"distDir": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "Creddy",
|
||||
"version": "0.2.2"
|
||||
"productName": "creddy",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"os": {"all": true}
|
||||
"all": true
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
@@ -47,19 +47,8 @@
|
||||
"timestampUrl": ""
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"description": "A friendly AWS credentials manager",
|
||||
"subcommands": {
|
||||
"run": {
|
||||
"description": "Launch Creddy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"csp": {
|
||||
"default-src": ["'self'"],
|
||||
"style-src": ["'self'", "'unsafe-inline'"]
|
||||
}
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
|
||||
+7
-8
@@ -1,22 +1,21 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { emit, listen } from '@tauri-apps/api/event';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
|
||||
import { appState, acceptRequest } from './lib/state.js';
|
||||
import { views, currentView, navigate } from './lib/routing.js';
|
||||
import { appState } from './lib/state.js';
|
||||
import { navigate, currentView } from './lib/routing.js';
|
||||
|
||||
|
||||
$views = import.meta.glob('./views/*.svelte', {eager: true});
|
||||
navigate('Home');
|
||||
|
||||
invoke('get_config').then(config => $appState.config = config);
|
||||
|
||||
listen('credentials-request', (tauriEvent) => {
|
||||
$appState.pendingRequests.put(tauriEvent.payload);
|
||||
});
|
||||
|
||||
acceptRequest();
|
||||
// can't set this in routing.js directly for some reason
|
||||
if (!$currentView) {
|
||||
navigate('Home');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 273.45667 273.45667"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="vault_door.svg"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
width="273.45667"
|
||||
height="273.45667"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs43" /><sodipodi:namedview
|
||||
id="namedview41"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.054"
|
||||
inkscape:cx="209.83447"
|
||||
inkscape:cy="63.291139"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#161616;}
|
||||
</style>
|
||||
|
||||
<g
|
||||
id="g38"
|
||||
style="fill:#60697a;fill-opacity:1"
|
||||
transform="matrix(-0.65108732,0,0,0.65108732,299.50017,-26.043493)">
|
||||
<g
|
||||
id="g36"
|
||||
style="fill:#60697a;fill-opacity:1">
|
||||
<path
|
||||
class="st1"
|
||||
d="m 102.88,181.03 c -3.49,0 -6.22,4.26 -6.22,9.7 v 118.53 c 0,5.44 2.73,9.7 6.22,9.7 3.49,0 6.22,-4.26 6.22,-9.7 V 190.73 c 0,-5.44 -2.73,-9.7 -6.22,-9.7 z m 2.99,128.24 c 0,3.64 -1.6,6.47 -2.98,6.47 -1.38,0 -2.99,-2.83 -2.99,-6.47 V 190.73 c 0,-3.64 1.61,-6.47 2.99,-6.47 1.38,0 2.98,2.83 2.98,6.47 z"
|
||||
id="path6"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<path
|
||||
class="st1"
|
||||
d="m 250,306.08 c 30.92,0 56.08,-25.16 56.08,-56.08 0,-30.92 -25.16,-56.08 -56.08,-56.08 -30.92,0 -56.08,25.16 -56.08,56.08 0,30.92 25.16,56.08 56.08,56.08 z m -6.47,-3.64 c -10.66,-1.31 -20.35,-5.8 -28.08,-12.49 l 23.33,-21.47 c 1.48,0.9 3.06,1.65 4.75,2.18 z M 231.6,250 c 0,-10.15 8.26,-18.4 18.4,-18.4 10.15,0 18.4,8.26 18.4,18.4 0,10.14 -8.26,18.4 -18.4,18.4 -10.15,0 -18.4,-8.25 -18.4,-18.4 z m 21.64,52.74 c -1.07,0.07 -2.15,0.11 -3.24,0.11 -1.08,0 -2.16,-0.04 -3.23,-0.11 v -31.37 c 1.06,0.16 2.13,0.27 3.23,0.27 1.1,0 2.18,-0.11 3.24,-0.27 z m 3.23,-0.31 v -31.79 c 1.58,-0.5 3.07,-1.18 4.46,-2.01 l 23.37,21.51 c -7.69,6.59 -17.28,11 -27.83,12.29 z m 30.24,-14.47 -23.04,-21.2 c 1.68,-1.37 3.14,-2.99 4.35,-4.79 l 23.06,21.23 c -1.36,1.67 -2.82,3.26 -4.37,4.76 z m 6.33,-7.36 -23.42,-21.55 c 0.57,-1.24 1.03,-2.54 1.36,-3.89 h 31.6 c -0.91,9.43 -4.32,18.12 -9.54,25.44 z m 9.8,-30.6 c 0,0.65 -0.03,1.29 -0.05,1.93 h -31.25 c 0.06,-0.64 0.1,-1.28 0.1,-1.93 0,-1.56 -0.17,-3.07 -0.49,-4.53 h 31.49 c 0.12,1.49 0.2,3 0.2,4.53 z m -0.57,-7.77 h -32.1 c -0.25,-0.64 -0.52,-1.27 -0.83,-1.88 l 23.33,-21.47 c 4.94,6.75 8.32,14.71 9.6,23.35 z m -11.59,-25.91 -23.04,21.2 c -1.26,-1.77 -2.77,-3.34 -4.49,-4.66 l 23.1,-21.26 c 1.57,1.48 3.06,3.05 4.43,4.72 z m -34.21,-18.75 c 10.33,1.27 19.75,5.52 27.35,11.87 l -23.45,21.58 c -1.23,-0.68 -2.54,-1.24 -3.9,-1.67 z m -9.7,-0.31 c 1.07,-0.06 2.15,-0.11 3.23,-0.11 1.09,0 2.16,0.04 3.24,0.11 v 31.37 c -1.06,-0.16 -2.13,-0.27 -3.24,-0.27 -1.1,0 -2.17,0.11 -3.23,0.27 z m -3.24,0.3 v 31.79 c -1.47,0.46 -2.87,1.09 -4.19,1.84 l -23.41,-21.54 c 7.66,-6.48 17.16,-10.81 27.6,-12.09 z m -30.01,14.26 23.07,21.23 c -1.7,1.35 -3.19,2.93 -4.42,4.72 l -23.05,-21.21 c 1.37,-1.67 2.84,-3.25 4.4,-4.74 z m -6.37,7.31 23.37,21.51 c -0.25,0.52 -0.48,1.05 -0.69,1.59 h -32.1 c 1.26,-8.53 4.58,-16.4 9.42,-23.1 z M 197.16,250 c 0,-1.53 0.08,-3.04 0.21,-4.53 h 31.49 c -0.31,1.46 -0.49,2.98 -0.49,4.53 0,0.65 0.04,1.3 0.1,1.93 H 197.2 c -0.02,-0.64 -0.04,-1.28 -0.04,-1.93 z m 0.25,5.17 h 31.6 c 0.31,1.24 0.71,2.45 1.22,3.59 l -23.45,21.59 c -5.12,-7.27 -8.46,-15.87 -9.37,-25.18 z m 34.41,6.52 c 1.18,1.83 2.62,3.47 4.27,4.86 l -23.03,21.19 c -1.54,-1.51 -2.99,-3.11 -4.34,-4.8 z"
|
||||
id="path8"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<path
|
||||
class="st1"
|
||||
d="m 438.65,157.85 5.34,-0.47 c 3.48,-0.31 6.31,-3.4 6.31,-6.89 V 100.67 97.44 57.41 c 0,-5.35 -4.35,-9.7 -9.7,-9.7 h -16.48 c -5.35,0 -9.7,4.35 -9.7,9.7 v 40.24 l -17.36,2.63 C 359.14,63.03 307.22,40 250,40 191.36,40 138.27,64.18 100.14,103.07 l -6.31,-5.81 6.68,-7.38 c 1.16,-1.28 1.75,-2.94 1.66,-4.66 -0.09,-1.73 -0.84,-3.31 -2.12,-4.47 L 84.46,66.63 c -1.19,-1.08 -2.73,-1.67 -4.34,-1.67 -1.83,0 -3.58,0.78 -4.8,2.13 L 68.83,74.26 58.01,64.3 c -0.31,-0.29 -0.73,-0.44 -1.16,-0.43 -0.43,0.02 -0.83,0.2 -1.12,0.52 l -6.57,7.14 c -0.6,0.66 -0.56,1.68 0.09,2.29 l 10.9,10.03 -6.53,7.22 c -2.39,2.65 -2.19,6.74 0.46,9.14 l 15.59,14.1 c 1.19,1.08 2.73,1.67 4.34,1.67 1.83,0 3.58,-0.78 4.8,-2.13 l 6.34,-7 6.21,5.72 C 59.39,149.43 40,197.49 40,250 c 0,52.28 19.22,100.15 50.95,136.95 l -5.99,5.52 -6.15,-6.79 c -1.22,-1.35 -2.97,-2.13 -4.8,-2.13 -1.61,0 -3.15,0.59 -4.34,1.67 l -15.59,14.1 c -2.65,2.39 -2.85,6.49 -0.46,9.14 l 6.34,7.01 -10.71,9.86 c -0.66,0.6 -0.7,1.63 -0.09,2.29 l 6.57,7.14 c 0.29,0.32 0.69,0.5 1.12,0.52 0.02,0 0.04,0 0.07,0 0.4,0 0.8,-0.15 1.09,-0.43 l 10.63,-9.78 6.68,7.38 c 1.22,1.35 2.97,2.13 4.8,2.13 1.61,0 3.15,-0.59 4.34,-1.67 l 15.59,-14.1 c 1.28,-1.16 2.03,-2.75 2.12,-4.47 0.09,-1.73 -0.51,-3.38 -1.66,-4.66 l -6.87,-7.59 6.07,-5.59 c 38.17,39.12 91.42,63.5 150.29,63.5 56.54,0 107.92,-22.48 145.71,-58.95 l 18.71,3.24 v 40.27 c 0,5.35 4.35,9.7 9.7,9.7 h 16.48 c 5.35,0 9.7,-4.35 9.7,-9.7 v -40.01 -3.23 -54.75 c 0,-3.49 -2.83,-6.58 -6.31,-6.89 L 440,339.33 c 12.81,-27.13 20,-57.4 20,-89.33 0,-33.04 -7.7,-64.31 -21.35,-92.15 z m -21,-100.44 c 0,-3.57 2.9,-6.47 6.47,-6.47 h 16.48 c 3.57,0 6.47,2.9 6.47,6.47 V 97.42 H 417.65 Z M 250,43.23 c 55.47,0 105.9,21.97 143.07,57.66 l -26.41,4 C 334.71,79.15 294.13,63.72 250,63.72 c -51.75,0 -98.62,21.23 -132.41,55.41 L 102.51,105.26 C 140.06,67.01 192.3,43.23 250,43.23 Z m 29.31,38.01 c 24.27,4.2 46.8,13.52 66.44,26.82 l -65.25,9.89 c 0.56,-1.31 0.91,-2.78 0.91,-4.35 V 87.72 c 0.01,-2.49 -0.79,-4.76 -2.1,-6.48 z m -11.85,-4.88 c -1.51,-3.4 -4.91,-5.79 -8.87,-5.79 h -16.48 c -3.92,0 -7.29,2.34 -8.82,5.69 -41.67,3.98 -79.11,22.68 -107.06,50.82 l -6.26,-5.76 C 153.17,87.78 199.19,66.95 250,66.95 c 42.27,0 81.2,14.45 112.22,38.61 l -11.79,1.79 C 326.49,90.45 298.14,79.42 267.46,76.36 Z m 10.73,11.36 v 25.88 c 0,2.02 -0.72,3.8 -1.83,4.98 l -8.06,1.22 V 81.25 h 4.95 c 2.72,0 4.94,2.91 4.94,6.47 z m -13.13,-7.44 v 39.79 H 235.64 V 80.28 c 0,-3.57 2.9,-6.47 6.47,-6.47 h 16.48 c 3.57,0 6.47,2.9 6.47,6.47 z m 84.7,312.81 11.62,2.01 c -30.88,23.76 -69.49,37.94 -111.38,37.94 -51.04,0 -97.25,-21 -130.48,-54.81 l 6.26,-5.76 c 28.09,28.49 65.87,47.38 107.94,51.29 1.68,2.9 4.81,4.85 8.39,4.85 h 16.48 c 3.61,0 6.76,-1.99 8.43,-4.92 30.56,-2.97 58.83,-13.87 82.74,-30.6 z m -81.47,26.15 v -0.32 -38.49 h 4.95 c 2.73,0 4.95,2.9 4.95,6.47 v 25.88 c 0,3.57 -2.22,6.47 -4.95,6.47 h -4.95 z m 11.4,-38.29 65.53,11.35 c -19.41,13.03 -41.62,22.19 -65.52,26.39 1.07,-1.64 1.72,-3.69 1.72,-5.92 v -25.88 c 0,-2.23 -0.65,-4.29 -1.73,-5.94 z m -57.18,31.82 v -25.88 c 0,-3.57 2.22,-6.47 4.95,-6.47 h 4.95 v 38.49 0.32 h -4.95 c -2.73,0.01 -4.95,-2.89 -4.95,-6.46 z m 42.55,6.14 c 0,3.57 -2.9,6.47 -6.47,6.47 h -16.48 c -3.57,0 -6.47,-2.9 -6.47,-6.47 V 378.9 h 29.42 z m -32.65,-76.39 v 33.14 1.53 h -4.95 c -4.51,0 -8.18,4.35 -8.18,9.7 v 25.88 c 0,2.28 0.67,4.38 1.79,6.04 -36.03,-6.16 -68.25,-23.58 -92.89,-48.54 l 7.62,-7.01 6.18,6.84 c 1.6,1.76 3.55,2.81 5.22,2.81 0.86,0 1.62,-0.28 2.2,-0.8 l 8.13,-7.36 c 0.96,-0.86 1.27,-2.27 0.86,-3.86 -0.32,-1.25 -1.08,-2.57 -2.12,-3.72 l -6.29,-6.96 54.75,-50.39 c 11.97,10.89 27.86,17.53 45.28,17.53 37.14,0 67.35,-30.21 67.35,-67.35 0,-37.14 -30.21,-67.35 -67.35,-67.35 -17.19,0 -32.88,6.48 -44.8,17.11 l -55.05,-50.66 6.11,-6.75 c 1.04,-1.15 1.79,-2.47 2.12,-3.72 0.41,-1.59 0.1,-3 -0.85,-3.86 l -8.13,-7.36 c -1.71,-1.54 -5.01,-0.65 -7.42,2.01 l -6,6.63 -7.36,-6.77 c 24.69,-24.82 56.89,-42.12 92.87,-48.18 -1.37,1.73 -2.21,4.06 -2.21,6.61 v 25.88 c 0,5.35 3.67,9.7 8.18,9.7 h 4.95 v 0.22 35.72 c 0,2.55 0.98,4.94 2.77,6.72 1.78,1.78 4.16,2.76 6.71,2.76 l 65.28,-1.8 c 12.68,-0.35 22.59,9.43 22.59,21.97 v 122.9 c 0,12.26 -9.97,22.24 -22.23,22.24 l -65.39,-1.08 h -0.16 c -5.29,0.01 -9.58,4.31 -9.58,9.58 z M 78.68,250 c 0,-42.42 15.5,-81.28 41.14,-111.23 l 7.48,6.88 -7.02,7.76 c -1.04,1.15 -1.79,2.47 -2.12,3.72 -0.41,1.59 -0.1,3 0.85,3.86 l 8.13,7.36 c 0.58,0.52 1.34,0.8 2.2,0.8 1.68,0 3.63,-1.05 5.22,-2.81 l 6.91,-7.64 54.94,50.56 c -8.63,11.32 -13.77,25.44 -13.77,40.74 0,15.07 4.98,29.01 13.38,40.24 l -54.73,50.37 -6.73,-7.43 c -2.41,-2.66 -5.71,-3.56 -7.42,-2.01 l -8.13,7.36 c -0.96,0.86 -1.27,2.27 -0.86,3.86 0.32,1.25 1.08,2.57 2.12,3.72 l 6.83,7.55 -7.7,7.09 C 94.02,330.87 78.68,292.19 78.68,250 Z m 38.35,112.94 -6.24,5.74 C 83.48,336.7 66.96,295.25 66.96,250 c 0,-45.48 16.69,-87.12 44.24,-119.17 l 6.25,5.75 c -26.17,30.53 -42,70.16 -42,113.42 0,43.04 15.66,82.48 41.58,112.94 z M 185.88,250 c 0,-35.36 28.76,-64.12 64.12,-64.12 35.36,0 64.12,28.76 64.12,64.12 0,35Line truncated
|
||||
id="path10"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="227.46001"
|
||||
cy="88.870003"
|
||||
r="2.8299999"
|
||||
id="circle12"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="227.46001"
|
||||
cy="112.45"
|
||||
r="2.8299999"
|
||||
id="circle14"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="227.46001"
|
||||
cy="100.66"
|
||||
r="2.8299999"
|
||||
id="circle16"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="272.73999"
|
||||
cy="88.870003"
|
||||
r="2.8299999"
|
||||
id="circle18"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="272.73999"
|
||||
cy="112.45"
|
||||
r="2.8299999"
|
||||
id="circle20"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="272.73999"
|
||||
cy="100.66"
|
||||
r="2.8299999"
|
||||
id="circle22"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="227.46001"
|
||||
cy="388.04001"
|
||||
r="2.8299999"
|
||||
id="circle24"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="227.46001"
|
||||
cy="411.62"
|
||||
r="2.8299999"
|
||||
id="circle26"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="227.46001"
|
||||
cy="399.82999"
|
||||
r="2.8299999"
|
||||
id="circle28"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="272.73999"
|
||||
cy="388.04001"
|
||||
r="2.8299999"
|
||||
id="circle30"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="272.73999"
|
||||
cy="411.62"
|
||||
r="2.8299999"
|
||||
id="circle32"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
<circle
|
||||
class="st1"
|
||||
cx="272.73999"
|
||||
cy="399.82999"
|
||||
r="2.8299999"
|
||||
id="circle34"
|
||||
style="fill:#60697a;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 14 KiB |
+8
-5
@@ -1,11 +1,14 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
|
||||
export let views = writable();
|
||||
const VIEWS = import.meta.glob('../views/*.svelte', {eager: true});
|
||||
export let currentView = writable();
|
||||
export let previousView = writable();
|
||||
|
||||
export function navigate(viewName) {
|
||||
let v = get(views)[`./views/${viewName}.svelte`].default;
|
||||
currentView.set(v)
|
||||
let view = VIEWS[`../views/${viewName}.svelte`].default;
|
||||
currentView.set(view);
|
||||
}
|
||||
|
||||
export function getView(viewName) {
|
||||
return VIEWS[`../views/${viewName}.svelte`].default;
|
||||
}
|
||||
+1
-25
@@ -1,33 +1,9 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
import queue from './queue.js';
|
||||
import { navigate, currentView, previousView } from './routing.js';
|
||||
|
||||
|
||||
export let appState = writable({
|
||||
currentRequest: null,
|
||||
pendingRequests: queue(),
|
||||
credentialStatus: 'locked',
|
||||
});
|
||||
|
||||
|
||||
export async function acceptRequest() {
|
||||
let req = await get(appState).pendingRequests.get();
|
||||
appState.update($appState => {
|
||||
$appState.currentRequest = req;
|
||||
return $appState;
|
||||
});
|
||||
previousView.set(get(currentView));
|
||||
navigate('Approve');
|
||||
}
|
||||
|
||||
|
||||
export function completeRequest() {
|
||||
appState.update($appState => {
|
||||
$appState.currentRequest = null;
|
||||
return $appState;
|
||||
});
|
||||
currentView.set(get(previousView));
|
||||
previousView.set(null);
|
||||
acceptRequest();
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.btn-alert-error {
|
||||
@apply bg-transparent hover:bg-[#cd5a5a] border border-error-content text-error-content
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
let extraClasses = "";
|
||||
export {extraClasses as class};
|
||||
export let slideDuration = 150;
|
||||
let animationClass = "";
|
||||
|
||||
@@ -51,7 +49,7 @@
|
||||
</style>
|
||||
|
||||
|
||||
<div in:slide="{{duration: slideDuration}}" class="alert alert-error shadow-lg {animationClass} {extraClasses}">
|
||||
<div in:slide="{{duration: slideDuration}}" class="alert alert-error shadow-lg {animationClass}">
|
||||
<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>
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { navigate } from '../lib/routing.js';
|
||||
import { navigate, currentView } from '../lib/routing.js';
|
||||
|
||||
export let target;
|
||||
export let hotkey = null;
|
||||
@@ -7,9 +7,6 @@
|
||||
export let alt = false;
|
||||
export let shift = false;
|
||||
|
||||
let classes = "";
|
||||
export {classes as class};
|
||||
|
||||
function click() {
|
||||
if (typeof target === 'string') {
|
||||
navigate(target);
|
||||
@@ -29,7 +26,10 @@
|
||||
if (alt && !event.altKey) return;
|
||||
if (shift && !event.shiftKey) return;
|
||||
|
||||
if (event.key === hotkey) {
|
||||
if (event.code === hotkey) {
|
||||
click();
|
||||
}
|
||||
else if (hotkey === 'Enter' && event.code === 'NumpadEnter') {
|
||||
click();
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,6 @@
|
||||
|
||||
<svelte:window on:keydown={handleHotkey} />
|
||||
|
||||
<a href="/{target}" on:click|preventDefault="{click}" class={classes}>
|
||||
<a href="#" on:click="{click}">
|
||||
<slot></slot>
|
||||
</a>
|
||||
+4
-10
@@ -1,27 +1,21 @@
|
||||
<script>
|
||||
import Link from './Link.svelte';
|
||||
import Icon from './Icon.svelte';
|
||||
|
||||
export let position = "sticky";
|
||||
</script>
|
||||
|
||||
|
||||
<nav class="{position} top-0 bg-base-100 w-full flex justify-between items-center p-2">
|
||||
<nav class="fixed top-0 grid grid-cols-2 w-full p-2">
|
||||
<div>
|
||||
<Link target="Home">
|
||||
<button class="btn btn-square btn-ghost align-middle">
|
||||
<button class="btn btn-squre btn-ghost align-middle">
|
||||
<Icon name="home" class="w-8 h-8 stroke-2" />
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{#if $$slots.title}
|
||||
<slot name="title"></slot>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<div class="justify-self-end">
|
||||
<Link target="Settings">
|
||||
<button class="btn btn-square btn-ghost align-middle ">
|
||||
<button class="align-middle btn btn-square btn-ghost">
|
||||
<Icon name="cog-8-tooth" class="w-8 h-8 stroke-2" />
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
export let title;
|
||||
</script>
|
||||
|
||||
|
||||
<div class="divider"></div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<h3 class="text-lg font-bold">{title}</h3>
|
||||
<slot name="input"></slot>
|
||||
</div>
|
||||
<p class="mt-3">
|
||||
<slot name="description"></slot>
|
||||
</p>
|
||||
@@ -1,113 +0,0 @@
|
||||
<script>
|
||||
export let color = 'base-content';
|
||||
export let thickness = '2px';
|
||||
let classes = '';
|
||||
export { classes as class };
|
||||
|
||||
const colorVars = {
|
||||
'primary': 'p',
|
||||
'primary-focus': 'pf',
|
||||
'primary-content': 'pc',
|
||||
'secondary': 's',
|
||||
'secondary-focus': 'sf',
|
||||
'secondary-content': 'sc',
|
||||
'accent': 'a',
|
||||
'accent-focus': 'af',
|
||||
'accent-content': 'ac',
|
||||
'neutral': 'n',
|
||||
'neutral-focus': 'nf',
|
||||
'neutral-content': 'nc',
|
||||
'base-100': 'b1',
|
||||
'base-200': 'b2',
|
||||
'base-300': 'b3',
|
||||
'base-content': 'bc',
|
||||
'info': 'in',
|
||||
'info-content': 'inc',
|
||||
'success': 'su',
|
||||
'success-content': 'suc',
|
||||
'warning': 'wa',
|
||||
'warning-content': 'wac',
|
||||
'error': 'er',
|
||||
'error-content': 'erc',
|
||||
}
|
||||
|
||||
let arcStyle = `border-width: ${thickness};`;
|
||||
arcStyle += `border-color: hsl(var(--${colorVars[color]})) transparent transparent transparent;`;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#spinner {
|
||||
position: relative;
|
||||
|
||||
animation: spin;
|
||||
animation-duration: 1.5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
50% { transform: rotate(225deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.arc {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.arc-top {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.arc-right {
|
||||
animation: spin-right;
|
||||
animation-duration: 3s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.arc-bottom {
|
||||
animation: spin-bottom;
|
||||
animation-duration: 3s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.arc-left {
|
||||
animation: spin-left;
|
||||
animation-duration: 3s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@keyframes spin-top {
|
||||
0% { transform: rotate(-45deg); }
|
||||
50% { transform: rotate(315deg); }
|
||||
100% { transform: rotate(-45deg); }
|
||||
}
|
||||
|
||||
@keyframes spin-right {
|
||||
0% { transform: rotate(45deg); }
|
||||
50% { transform: rotate(315deg); }
|
||||
100% { transform: rotate(405deg); }
|
||||
}
|
||||
|
||||
@keyframes spin-bottom {
|
||||
0% { transform: rotate(135deg); }
|
||||
50% { transform: rotate(315deg); }
|
||||
100% { transform: rotate(495deg); }
|
||||
}
|
||||
|
||||
@keyframes spin-left {
|
||||
0% { transform: rotate(225deg); }
|
||||
50% { transform: rotate(315deg); }
|
||||
100% { transform: rotate(585deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div id="spinner" class="w-6 h-6 {classes}">
|
||||
<div class="arc arc-top w-full h-full" style={arcStyle}></div>
|
||||
<div class="arc arc-right w-full h-full" style={arcStyle}></div>
|
||||
<div class="arc arc-bottom w-full h-full" style={arcStyle}></div>
|
||||
<div class="arc arc-left w-full h-full" style={arcStyle}></div>
|
||||
</div>
|
||||
@@ -1,80 +0,0 @@
|
||||
<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();
|
||||
|
||||
$: localValue = value.toString();
|
||||
let lastInputTime = null;
|
||||
function debounce(event) {
|
||||
lastInputTime = Date.now();
|
||||
localValue = localValue.replace(/[^-0-9.]/g, '');
|
||||
|
||||
const eventTime = lastInputTime;
|
||||
const pendingValue = localValue;
|
||||
window.setTimeout(
|
||||
() => {
|
||||
// if no other inputs have occured since then
|
||||
if (eventTime === lastInputTime) {
|
||||
updateValue(pendingValue);
|
||||
}
|
||||
},
|
||||
500
|
||||
)
|
||||
}
|
||||
|
||||
let error = null;
|
||||
function updateValue(newValue) {
|
||||
// Don't update the value, but also don't error, if it's empty
|
||||
// or if it could be the start of a negative or decimal number
|
||||
if (newValue.match(/^$|^-$|^\.$/) !== null) {
|
||||
error = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const num = parseFloat(newValue);
|
||||
if (num % 1 !== 0 && !decimal) {
|
||||
error = `${num} is not a whole number`;
|
||||
}
|
||||
else if (min !== null && num < min) {
|
||||
error = `Too low (minimum ${min})`;
|
||||
}
|
||||
else if (max !== null && num > max) {
|
||||
error = `Too large (maximum ${max})`
|
||||
}
|
||||
else {
|
||||
error = null;
|
||||
value = num;
|
||||
dispatch('update', {value})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<Setting {title}>
|
||||
<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"
|
||||
size="{Math.max(5, localValue.length)}"
|
||||
class:input-error={error}
|
||||
bind:value={localValue}
|
||||
on:input="{debounce}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<slot name="description" slot="description"></slot>
|
||||
</Setting>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script>
|
||||
import { slide } from 'svelte/transition';
|
||||
import ErrorAlert from '../ErrorAlert.svelte';
|
||||
|
||||
export let title;
|
||||
</script>
|
||||
|
||||
|
||||
<div class="divider"></div>
|
||||
<div class="flex justify-between">
|
||||
<h3 class="text-lg font-bold">{title}</h3>
|
||||
<slot name="input"></slot>
|
||||
</div>
|
||||
|
||||
{#if $$slots.description}
|
||||
<p class="mt-3">
|
||||
<slot name="description"></slot>
|
||||
</p>
|
||||
{/if}
|
||||
@@ -1,22 +0,0 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import Setting from './Setting.svelte';
|
||||
|
||||
export let title;
|
||||
export let value;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
|
||||
<Setting {title}>
|
||||
<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>
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as Setting } from './Setting.svelte';
|
||||
export { default as ToggleSetting } from './ToggleSetting.svelte';
|
||||
export { default as NumericSetting } from './NumericSetting.svelte';
|
||||
+14
-81
@@ -1,35 +1,16 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
|
||||
import { navigate } from '../lib/routing.js';
|
||||
import { appState, completeRequest } from '../lib/state.js';
|
||||
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
||||
import { appState } from '../lib/state.js';
|
||||
import Link from '../ui/Link.svelte';
|
||||
import Icon from '../ui/Icon.svelte';
|
||||
|
||||
|
||||
// Send response to backend, display error if applicable
|
||||
let error, alert;
|
||||
async function respond() {
|
||||
let {id, approval} = $appState.currentRequest;
|
||||
try {
|
||||
await invoke('respond', {response: {id, approval}});
|
||||
navigate('ShowResponse');
|
||||
}
|
||||
catch (e) {
|
||||
if (error) {
|
||||
alert.shake();
|
||||
}
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
// Approval has one of several outcomes depending on current credential state
|
||||
async function approve() {
|
||||
$appState.currentRequest.approval = 'Approved';
|
||||
let status = await invoke('get_session_status');
|
||||
if (status === 'unlocked') {
|
||||
await respond();
|
||||
navigate('ShowApproved');
|
||||
}
|
||||
else if (status === 'locked') {
|
||||
navigate('Unlock');
|
||||
@@ -39,81 +20,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Denial has only one
|
||||
async function deny() {
|
||||
$appState.currentRequest.approval = 'Denied';
|
||||
await respond();
|
||||
}
|
||||
|
||||
// Extract executable name from full path
|
||||
let appName = null;
|
||||
var appName = null;
|
||||
if ($appState.currentRequest.clients.length === 1) {
|
||||
let path = $appState.currentRequest.clients[0].exe;
|
||||
let m = path.match(/\/([^/]+?$)|\\([^\\]+?$)/);
|
||||
appName = m[1] || m[2];
|
||||
}
|
||||
|
||||
// Executable paths can be long, so ensure they only break on \ or /
|
||||
function breakPath(client) {
|
||||
return client.exe.replace(/(\\|\/)/g, '$1<wbr>');
|
||||
}
|
||||
|
||||
// if the request has already been approved/denied, send response immediately
|
||||
onMount(async () => {
|
||||
if ($appState.currentRequest.approval) {
|
||||
await respond();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<!-- Don't render at all if we're just going to immediately proceed to the next screen -->
|
||||
{#if error || !$appState.currentRequest.approval}
|
||||
<div class="flex flex-col space-y-4 p-4 m-auto max-w-xl h-screen items-center justify-center">
|
||||
{#if error}
|
||||
<ErrorAlert bind:this={alert}>
|
||||
{error}
|
||||
<svelte:fragment slot="buttons">
|
||||
<button class="btn btn-sm btn-alert-error" on:click={completeRequest}>Cancel</button>
|
||||
<button class="btn btn-sm btn-alert-error" on:click={respond}>Retry</button>
|
||||
</svelte:fragment>
|
||||
</ErrorAlert>
|
||||
{/if}
|
||||
|
||||
{#if $appState.currentRequest.base}
|
||||
<div class="alert alert-warning shadow-lg">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
<span>
|
||||
WARNING: This application is requesting your base (long-lived) AWS credentials.
|
||||
These credentials are less secure than session credentials, since they don't expire automatically.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col space-y-4 p-4 m-auto max-w-max h-screen justify-center">
|
||||
<!-- <div class="p-4 rounded-box border-2 border-neutral-content"> -->
|
||||
<div class="space-y-1 mb-4">
|
||||
<h2 class="text-xl font-bold">{appName ? `"${appName}"` : 'An appplication'} would like to access your AWS credentials.</h2>
|
||||
|
||||
<div class="grid grid-cols-[auto_1fr] gap-x-3">
|
||||
{#each $appState.currentRequest.clients as client}
|
||||
<div class="text-right">Path:</div>
|
||||
<code class="">{@html client ? breakPath(client) : 'Unknown'}</code>
|
||||
<div class="text-right">PID:</div>
|
||||
<code>{client ? client.pid : 'Unknown'}</code>
|
||||
{/each}
|
||||
</div>
|
||||
{#each $appState.currentRequest.clients as client}
|
||||
<p>Path: {client ? client.exe : 'Unknown'}</p>
|
||||
<p>PID: {client ? client.pid : 'Unknown'}</p>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="w-full flex justify-between">
|
||||
<Link target={deny} hotkey="Escape">
|
||||
<div class="grid grid-cols-2">
|
||||
<Link target="ShowDenied" hotkey="Escape">
|
||||
<button class="btn btn-error justify-self-start">
|
||||
Deny
|
||||
<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}">
|
||||
<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>
|
||||
@@ -122,5 +56,4 @@
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -7,32 +7,20 @@
|
||||
import { navigate } from '../lib/routing.js';
|
||||
import Link from '../ui/Link.svelte';
|
||||
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
||||
import Spinner from '../ui/Spinner.svelte';
|
||||
|
||||
|
||||
let errorMsg = null;
|
||||
let alert;
|
||||
let AccessKeyId, SecretAccessKey, passphrase, confirmPassphrase
|
||||
let AccessKeyId, SecretAccessKey, passphrase
|
||||
|
||||
function confirm() {
|
||||
if (passphrase !== confirmPassphrase) {
|
||||
errorMsg = 'Passphrases do not match.'
|
||||
}
|
||||
}
|
||||
|
||||
let saving = false;
|
||||
async function save() {
|
||||
if (passphrase !== confirmPassphrase) {
|
||||
alert.shake();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Saving credentials.');
|
||||
let credentials = {AccessKeyId, SecretAccessKey};
|
||||
|
||||
try {
|
||||
saving = true;
|
||||
await invoke('save_credentials', {credentials, passphrase});
|
||||
if ($appState.currentRequest) {
|
||||
navigate('Approve');
|
||||
navigate('ShowApproved');
|
||||
}
|
||||
else {
|
||||
navigate('Home');
|
||||
@@ -50,8 +38,6 @@
|
||||
if (alert) {
|
||||
alert.shake();
|
||||
}
|
||||
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -68,15 +54,8 @@
|
||||
<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="password" placeholder="Re-enter passphrase" bind:value={confirmPassphrase} class="input input-bordered" on:change={confirm} />
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{#if saving}
|
||||
<Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
|
||||
{:else}
|
||||
Submit
|
||||
{/if}
|
||||
</button>
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
<Link target="Home" hotkey="Escape">
|
||||
<button class="btn btn-sm btn-outline w-full">Cancel</button>
|
||||
</Link>
|
||||
|
||||
+14
-21
@@ -8,41 +8,34 @@
|
||||
import Icon from '../ui/Icon.svelte';
|
||||
import Link from '../ui/Link.svelte';
|
||||
|
||||
import vaultDoorSvg from '../assets/vault_door.svg?raw';
|
||||
|
||||
|
||||
// onMount(async () => {
|
||||
// // will block until a request comes in
|
||||
// let req = await $appState.pendingRequests.get();
|
||||
// $appState.currentRequest = req;
|
||||
// navigate('Approve');
|
||||
// });
|
||||
onMount(async () => {
|
||||
// will block until a request comes in
|
||||
let req = await $appState.pendingRequests.get();
|
||||
$appState.currentRequest = req;
|
||||
navigate('Approve');
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<Nav position="fixed">
|
||||
<h2 slot="title" class="text-3xl font-bold">Creddy</h2>
|
||||
</Nav>
|
||||
<Nav />
|
||||
|
||||
<div class="flex flex-col h-screen items-center justify-center p-4 space-y-4">
|
||||
<div class="flex flex-col h-screen justify-center items-center space-y-4">
|
||||
{#await invoke('get_session_status') then status}
|
||||
{#if status === 'locked'}
|
||||
|
||||
{@html vaultDoorSvg}
|
||||
<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" hotkey="Enter" class="w-64">
|
||||
<button class="btn btn-primary w-full">Unlock</button>
|
||||
<Link target="Unlock">
|
||||
<button class="btn btn-primary">Unlock</button>
|
||||
</Link>
|
||||
|
||||
{:else if status === 'unlocked'}
|
||||
{@html vaultDoorSvg}
|
||||
<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'}
|
||||
{@html vaultDoorSvg}
|
||||
<h2 class="text-2xl font-bold">No credentials found</h2>
|
||||
<Link target="EnterCredentials" hotkey="Enter" class="w-64">
|
||||
<button class="btn btn-primary w-full">Enter Credentials</button>
|
||||
<Link target="EnterCredentials">
|
||||
<button class="btn btn-primary">Enter Credentials</button>
|
||||
</Link>
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
+38
-67
@@ -1,94 +1,65 @@
|
||||
<script>
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { type } from '@tauri-apps/api/os';
|
||||
window.invoke = invoke;
|
||||
|
||||
import { appState } from '../lib/state.js';
|
||||
import Nav from '../ui/Nav.svelte';
|
||||
import Link from '../ui/Link.svelte';
|
||||
import Setting from '../ui/Setting.svelte';
|
||||
import ErrorAlert from '../ui/ErrorAlert.svelte';
|
||||
import { Setting, ToggleSetting, NumericSetting } from '../ui/settings';
|
||||
|
||||
import { fly } from 'svelte/transition';
|
||||
import { backInOut } from 'svelte/easing';
|
||||
|
||||
|
||||
let error = null;
|
||||
async function save() {
|
||||
try {
|
||||
await invoke('save_config', {config: $appState.config});
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
$appState.config = await invoke('get_config');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let osType = '';
|
||||
type().then(t => osType = t);
|
||||
</script>
|
||||
|
||||
|
||||
<Nav>
|
||||
<h2 slot="title" class="text-2xl font-bold">Settings</h2>
|
||||
</Nav>
|
||||
<Nav />
|
||||
|
||||
{#await invoke('get_config') then config}
|
||||
<div class="max-w-md mx-auto mt-1.5 p-4">
|
||||
<!-- <h2 class="text-2xl font-bold text-center">Settings</h2> -->
|
||||
<div class="mx-auto mt-3 max-w-md">
|
||||
<h2 class="text-2xl font-bold text-center">Settings</h2>
|
||||
|
||||
<ToggleSetting title="Start on login" bind:value={$appState.config.start_on_login} on:update={save}>
|
||||
<svelte:fragment slot="description">
|
||||
Start Creddy when you log in to your computer.
|
||||
</svelte:fragment>
|
||||
</ToggleSetting>
|
||||
|
||||
<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>
|
||||
|
||||
<NumericSetting
|
||||
title="Listen port"
|
||||
bind:value={$appState.config.listen_port}
|
||||
min={osType === 'Windows_NT' ? 1 : 0}
|
||||
on:update={save}
|
||||
>
|
||||
<svelte:fragment slot="description">
|
||||
Listen for credentials requests on this port.
|
||||
(Should be used with <code>$AWS_CONTAINER_CREDENTIALS_FULL_URI</code>)
|
||||
</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 title="Start minimized">
|
||||
<input slot="input" type="checkbox" bind:checked="{$appState.config.start_minimized}" class="justify-self-end toggle toggle-success" />
|
||||
<svelte:fragment slot="description">Minimize to the system tray at startup.</svelte:fragment>
|
||||
</Setting>
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
{#if error}
|
||||
<div transition:fly={{y: 100, easing: backInOut, duration: 400}} class="toast">
|
||||
<div class="alert alert-error no-animation">
|
||||
<div>
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<h3 class="text-lg font-bold">Start minimized</h3>
|
||||
<input type="checkbox" bind:checked="{$appState.config.start_minimized}" class="justify-self-end toggle toggle-success" />
|
||||
</div>
|
||||
<p class="mt-3">Minimize to the system tray at startup.</p>
|
||||
|
||||
<div>
|
||||
<button class="btn btn-sm btn-alert-error" on:click={() => error = null}>Ok</button>
|
||||
<div class="divider"></div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<h3 class="text-lg font-bold">Re-hide delay</h3>
|
||||
<div class="justify-self-end">
|
||||
<span class="mr-2">(Milliseconds)</span>
|
||||
<input type="text" bind:value="{$appState.config.rehide_ms}" class="input input-sm input-bordered text-right max-w-[4rem]" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="divider"></div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<h3 class="text-lg font-bold">Update credentials</h3>
|
||||
<div class="justify-self-end">
|
||||
<Link target="EnterCredentials">
|
||||
<button class="btn btn-sm btn-primary">Update</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3">Update or re-enter your encrypted credentials.</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
@@ -0,0 +1,75 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { draw, fade } from 'svelte/transition';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
|
||||
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';
|
||||
|
||||
let success = false;
|
||||
let error = null;
|
||||
|
||||
async function respond() {
|
||||
let response = {
|
||||
id: $appState.currentRequest.id,
|
||||
approval: 'Approved',
|
||||
};
|
||||
|
||||
try {
|
||||
await invoke('respond', {response});
|
||||
success = true;
|
||||
$appState.currentRequest = null;
|
||||
window.setTimeout(() => navigate('Home'), 1000);
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onMount(respond);
|
||||
</script>
|
||||
|
||||
<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}
|
||||
-->
|
||||
@@ -0,0 +1,56 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { draw, fade } from 'svelte/transition';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
|
||||
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';
|
||||
|
||||
let error = null;
|
||||
|
||||
async function respond() {
|
||||
let response = {
|
||||
id: $appState.currentRequest.id,
|
||||
approval: 'Denied',
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('respond', {response});
|
||||
$appState.currentRequest = null;
|
||||
window.setTimeout(() => navigate('Home'), 1000);
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onMount(respond);
|
||||
</script>
|
||||
|
||||
{#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,38 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { draw, fade } from 'svelte/transition';
|
||||
|
||||
import { appState, completeRequest } from '../lib/state.js';
|
||||
|
||||
let success = false;
|
||||
let error = null;
|
||||
|
||||
let drawDuration = $appState.config.rehide_ms >= 750 ? 500 : 0;
|
||||
let fadeDuration = drawDuration * 0.6;
|
||||
let fadeDelay = drawDuration * 0.4;
|
||||
|
||||
onMount(() => {
|
||||
window.setTimeout(
|
||||
completeRequest,
|
||||
// Extra 50ms so the window can finish disappearing before the redraw
|
||||
Math.min(5000, $appState.config.rehide_ms + 50),
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex flex-col h-screen items-center justify-center max-w-max m-auto">
|
||||
{#if $appState.currentRequest.approval === 'Approved'}
|
||||
<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: drawDuration}}" 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>
|
||||
{:else}
|
||||
<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>
|
||||
{/if}
|
||||
|
||||
<div in:fade="{{duration: fadeDuration, delay: fadeDelay}}" class="text-2xl font-bold">
|
||||
{$appState.currentRequest.approval}!
|
||||
</div>
|
||||
</div>
|
||||
+2
-27
@@ -1,33 +1,22 @@
|
||||
<script>
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
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';
|
||||
import Spinner from '../ui/Spinner.svelte';
|
||||
|
||||
|
||||
let errorMsg = null;
|
||||
let alert;
|
||||
let passphrase = '';
|
||||
let loadTime = 0;
|
||||
let saving = false;
|
||||
async function unlock() {
|
||||
// The hotkey for navigating here from homepage is Enter, which also
|
||||
// happens to trigger the form submit event
|
||||
if (Date.now() - loadTime < 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
saving = true;
|
||||
let r = await invoke('unlock', {passphrase});
|
||||
$appState.credentialStatus = 'unlocked';
|
||||
if ($appState.currentRequest) {
|
||||
navigate('Approve');
|
||||
navigate('ShowApproved');
|
||||
}
|
||||
else {
|
||||
navigate('Home');
|
||||
@@ -46,14 +35,8 @@
|
||||
if (alert) {
|
||||
alert.shake();
|
||||
}
|
||||
|
||||
saving = true;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadTime = Date.now();
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -64,17 +47,9 @@
|
||||
<ErrorAlert bind:this="{alert}">{errorMsg}</ErrorAlert>
|
||||
{/if}
|
||||
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input autofocus name="password" type="password" placeholder="correct horse battery staple" bind:value="{passphrase}" class="input input-bordered" />
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{#if saving}
|
||||
<Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
|
||||
{:else}
|
||||
Submit
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<input type="submit" class="btn btn-primary" />
|
||||
<Link target="Home" hotkey="Escape">
|
||||
<button class="btn btn-outline btn-sm w-full">Cancel</button>
|
||||
</Link>
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="96.957397mm"
|
||||
height="136.61758mm"
|
||||
viewBox="0 0 96.957397 136.61758"
|
||||
version="1.1"
|
||||
id="svg671"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
sodipodi:docname="padlock-closed.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview673"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.829812"
|
||||
inkscape:cx="290.74025"
|
||||
inkscape:cy="242.64788"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs668">
|
||||
<filter
|
||||
id="aa"
|
||||
x="-0.074089691"
|
||||
y="-0.050412808"
|
||||
width="1.1481794"
|
||||
height="1.1008256">
|
||||
<feGaussianBlur
|
||||
stdDeviation="9.8527"
|
||||
id="feGaussianBlur11" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#e"
|
||||
id="radialGradient447"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.83226,1.428,126.14)"
|
||||
cx="207.08"
|
||||
cy="608.71997"
|
||||
r="156.57001" />
|
||||
<linearGradient
|
||||
id="e">
|
||||
<stop
|
||||
stop-color="#d3bc5f"
|
||||
offset="0"
|
||||
id="stop49" />
|
||||
<stop
|
||||
stop-color="#c8ab37"
|
||||
offset="1"
|
||||
id="stop51" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#d"
|
||||
id="linearGradient449"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="201.44"
|
||||
y1="1021.9"
|
||||
x2="201.78"
|
||||
y2="727.78003" />
|
||||
<linearGradient
|
||||
id="d">
|
||||
<stop
|
||||
stop-color="#806600"
|
||||
offset="0"
|
||||
id="stop44" />
|
||||
<stop
|
||||
stop-color="#806600"
|
||||
stop-opacity="0"
|
||||
offset="1"
|
||||
id="stop46" />
|
||||
</linearGradient>
|
||||
<mask
|
||||
id="b"
|
||||
maskUnits="userSpaceOnUse">
|
||||
<path
|
||||
d="m -6.2978,665.08 h 288.15 a 12.498,12.498 0 0 1 12.498,12.498 v 235.62 a 12.498,12.498 0 0 1 -12.498,12.498 h -288.15 a 12.498,12.498 0 0 1 -12.498,-12.498 v -235.62 a 12.498,12.498 0 0 1 12.498,-12.498"
|
||||
fill="#ffffff"
|
||||
id="path81" />
|
||||
</mask>
|
||||
<filter
|
||||
id="c"
|
||||
x="-0.021572628"
|
||||
y="-0.025823718"
|
||||
width="1.0431453"
|
||||
height="1.0516474">
|
||||
<feGaussianBlur
|
||||
stdDeviation="2.8688"
|
||||
id="feGaussianBlur2" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#a"
|
||||
id="linearGradient451"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="59.094002"
|
||||
x2="357.94" />
|
||||
<linearGradient
|
||||
id="a"
|
||||
x1="59.094002"
|
||||
x2="357.94"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="0"
|
||||
id="stop54" />
|
||||
<stop
|
||||
stop-color="#fea"
|
||||
offset=".5763"
|
||||
id="stop56" />
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="1"
|
||||
id="stop58" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
id="n"
|
||||
x="-0.0021757265"
|
||||
y="-0.0026762109"
|
||||
width="1.0043515"
|
||||
height="1.0053524">
|
||||
<feGaussianBlur
|
||||
stdDeviation=".2656"
|
||||
id="feGaussianBlur5" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="t"
|
||||
cx="207.08"
|
||||
cy="608.71997"
|
||||
r="156.57001"
|
||||
gradientTransform="matrix(0.26458333,0,0,0.22020212,24.14876,-37.223301)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#e" />
|
||||
<linearGradient
|
||||
id="x"
|
||||
x1="516.13"
|
||||
x2="516.47998"
|
||||
y1="1025.9"
|
||||
y2="731.82001"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#d"
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,-59.491322,-71.669405)" />
|
||||
<mask
|
||||
id="mask602"
|
||||
maskUnits="userSpaceOnUse">
|
||||
<path
|
||||
d="m -6.2978,665.08 h 288.15 a 12.498,12.498 0 0 1 12.498,12.498 v 235.62 a 12.498,12.498 0 0 1 -12.498,12.498 h -288.15 a 12.498,12.498 0 0 1 -12.498,-12.498 v -235.62 a 12.498,12.498 0 0 1 12.498,-12.498"
|
||||
fill="#ffffff"
|
||||
id="path600" />
|
||||
</mask>
|
||||
<filter
|
||||
id="filter606"
|
||||
x="-0.021572627"
|
||||
y="-0.025823718"
|
||||
width="1.0431453"
|
||||
height="1.0516474">
|
||||
<feGaussianBlur
|
||||
stdDeviation="2.8688"
|
||||
id="feGaussianBlur604" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#k"
|
||||
id="linearGradient453"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-692.91998"
|
||||
y1="1521.9"
|
||||
x2="-710.91998"
|
||||
y2="1295.6" />
|
||||
<linearGradient
|
||||
id="k">
|
||||
<stop
|
||||
stop-color="#ccc"
|
||||
offset="0"
|
||||
id="stop37" />
|
||||
<stop
|
||||
stop-color="#fff"
|
||||
offset=".6899"
|
||||
id="stop39" />
|
||||
<stop
|
||||
stop-color="#e6e6e6"
|
||||
offset="1"
|
||||
id="stop41" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
id="l"
|
||||
x="-0.063578956"
|
||||
y="-0.35844691"
|
||||
width="1.1271579"
|
||||
height="1.7168938">
|
||||
<feGaussianBlur
|
||||
stdDeviation="3.4308"
|
||||
id="feGaussianBlur34" />
|
||||
</filter>
|
||||
<filter
|
||||
id="p"
|
||||
x="-0.037389143"
|
||||
y="-0.020053786"
|
||||
width="1.0747783"
|
||||
height="1.0401076">
|
||||
<feGaussianBlur
|
||||
result="result8"
|
||||
stdDeviation="4"
|
||||
id="feGaussianBlur20" />
|
||||
<feComposite
|
||||
in="result8"
|
||||
in2="result8"
|
||||
operator="xor"
|
||||
result="result6"
|
||||
id="feComposite22" />
|
||||
<feDisplacementMap
|
||||
in="result6"
|
||||
in2="result6"
|
||||
result="result4"
|
||||
scale="80"
|
||||
id="feDisplacementMap24"
|
||||
xChannelSelector="A"
|
||||
yChannelSelector="A" />
|
||||
<feComposite
|
||||
in="result8"
|
||||
in2="result4"
|
||||
k1="3"
|
||||
operator="arithmetic"
|
||||
result="result2"
|
||||
id="feComposite26"
|
||||
k2="0"
|
||||
k3="0"
|
||||
k4="0" />
|
||||
<feComposite
|
||||
in="result2"
|
||||
in2="result2"
|
||||
operator="in"
|
||||
result="fbSourceGraphic"
|
||||
id="feComposite28" />
|
||||
</filter>
|
||||
<mask
|
||||
id="h"
|
||||
maskUnits="userSpaceOnUse">
|
||||
<path
|
||||
d="m 1242.1,323.25 c -46.21,0.1434 -97.408,19.082 -119.06,62.688 -13.801,27.856 -8.4155,59.986 -10.594,89.938 -0.3932,36.847 -0.6396,73.715 -0.031,110.56 l 21.781,-1.5 -21.219,26.812 c 0.041,1.428 0.081,2.8534 0.125,4.2812 0.1398,29.944 38.758,3.7481 39.438,-16.312 -0.522,-60.445 -1.3603,-121 1.9063,-181.38 6.1489,-28.89 33.689,-48.001 61.812,-51.906 31.318,-5.1896 67.758,-3.053 92.781,18.562 17.831,14.267 21.591,37.115 19.031,58.469 -0.9481,114.24 -1.5396,228.53 -0.6875,342.75 7.8425,31.35 50.552,10.639 39.656,-16.625 -0.048,-118.23 -0.024,-236.48 1.2188,-354.69 -4.9781,-40.123 -37.945,-73.15 -76.125,-84 -16.133,-5.1758 -33.097,-7.6894 -50.031,-7.6562 z"
|
||||
fill="#cccccc"
|
||||
id="path78" />
|
||||
</mask>
|
||||
<filter
|
||||
id="o"
|
||||
x="-0.14145334"
|
||||
y="-0.072653391"
|
||||
width="1.2829067"
|
||||
height="1.1453068">
|
||||
<feGaussianBlur
|
||||
stdDeviation="13.8123"
|
||||
id="feGaussianBlur31" />
|
||||
</filter>
|
||||
<mask
|
||||
id="g"
|
||||
maskUnits="userSpaceOnUse">
|
||||
<path
|
||||
d="m -125.64,141.27 c -46.21,0.12373 -97.408,16.465 -119.06,54.089 -13.801,24.035 -8.4155,51.758 -10.594,77.602 -0.3932,31.793 -0.6396,63.604 -0.031,95.398 l 21.781,-1.2943 -21.219,23.135 c 0.041,1.2321 0.081,2.462 0.125,3.694 0.1398,25.837 38.758,3.234 39.437,-14.075 -0.522,-52.154 -1.3603,-104.4 1.9063,-156.5 6.1489,-24.928 33.689,-41.417 61.812,-44.787 31.318,-4.4778 67.758,-2.6342 92.781,16.016 17.831,12.31 21.591,32.024 19.031,50.449 -0.9481,98.57 -1.5396,197.19 -0.6875,295.74 7.8425,27.05 50.552,9.1799 39.656,-14.345 -0.048,-102.01 -0.024,-204.04 1.2188,-306.04 -4.9781,-34.62 -37.945,-63.117 -76.125,-72.479 -16.133,-4.4659 -33.097,-6.6347 -50.031,-6.6061 z"
|
||||
fill="url(#y)"
|
||||
id="path84" />
|
||||
</mask>
|
||||
<linearGradient
|
||||
id="y"
|
||||
x1="-100.13"
|
||||
x2="-118.13"
|
||||
y1="380.12"
|
||||
y2="153.84"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#k" />
|
||||
<filter
|
||||
id="m"
|
||||
x="-0.03714728"
|
||||
y="-0.023091114"
|
||||
width="1.0742253"
|
||||
height="1.0461927">
|
||||
<feGaussianBlur
|
||||
stdDeviation="3.3491"
|
||||
id="feGaussianBlur8" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#a"
|
||||
id="linearGradient455"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="59.094002"
|
||||
x2="357.94"
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,23.770406,-70.632556)" />
|
||||
<linearGradient
|
||||
id="linearGradient645"
|
||||
x1="59.094002"
|
||||
x2="357.94"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="0"
|
||||
id="stop639" />
|
||||
<stop
|
||||
stop-color="#fea"
|
||||
offset=".5763"
|
||||
id="stop641" />
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="1"
|
||||
id="stop643" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-28.735516,-33.775921)">
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458333,23.770406,-70.60033)"
|
||||
id="g114">
|
||||
<path
|
||||
d="m 64.433,623.66 h 288.15 a 12.498,12.498 0 0 1 12.498,12.498 v 235.62 a 12.498,12.498 0 0 1 -12.498,12.498 H 64.433 A 12.498,12.498 0 0 1 51.935,871.778 V 636.158 A 12.498,12.498 0 0 1 64.433,623.66"
|
||||
fill="url(#f)"
|
||||
id="path104"
|
||||
style="fill:url(#radialGradient447)" />
|
||||
<path
|
||||
d="m 64.433,623.66 h 288.15 a 12.498,12.498 0 0 1 12.498,12.498 v 235.62 a 12.498,12.498 0 0 1 -12.498,12.498 H 64.433 A 12.498,12.498 0 0 1 51.935,871.778 V 636.158 A 12.498,12.498 0 0 1 64.433,623.66"
|
||||
fill="url(#j)"
|
||||
id="path106"
|
||||
style="fill:url(#linearGradient449)" />
|
||||
<path
|
||||
transform="translate(70.731,-41.416)"
|
||||
d="m -6.2998,662.08 c -8.5337,0 -15.5,6.9663 -15.5,15.5 V 913.2 c 0,8.5337 6.9663,15.5 15.5,15.5 h 288.16 c 8.5337,0 15.5,-6.9663 15.5,-15.5 V 677.58 c 0,-8.5337 -6.9663,-15.5 -15.5,-15.5 z m 0,6 h 288.16 c 5.3135,0 9.5,4.1865 9.5,9.5 V 913.2 c 0,5.3135 -4.1865,9.5 -9.5,9.5 h -288.16 c -5.3135,0 -9.5,-4.1865 -9.5,-9.5 V 677.58 c 0,-5.3135 4.1865,-9.5 9.5,-9.5 z"
|
||||
fill="#ffffff"
|
||||
filter="url(#c)"
|
||||
mask="url(#b)"
|
||||
id="path108" />
|
||||
<path
|
||||
d="m 64.438,630.81 c -3.0883,0 -5.3438,2.2554 -5.3438,5.3438 v 235.62 c 0,3.0883 2.2554,5.3438 5.3438,5.3438 h 288.16 c 3.0883,0 5.3438,-2.2554 5.3438,-5.3438 v -235.62 c 0,-3.0883 -2.2554,-5.3438 -5.3438,-5.3438 z"
|
||||
fill="url(#a)"
|
||||
id="path110"
|
||||
style="fill:url(#linearGradient451)" />
|
||||
<path
|
||||
d="m 65.691,637.37 c -0.0543,0.2253 -0.0937,0.4551 -0.0937,0.7188 v 1.3124 c 97.654,-0.4644 195.31,-0.674 292.97,-0.6562 v -0.6562 c 0,-0.033 8.7e-4,-0.0614 0,-0.0938 -97.624,0.0192 -195.25,-0.1507 -292.88,-0.625 z m -0.0937,9.5 v 1.9375 c 34.054,-0.1333 68.102,-0.2451 102.16,-0.3437 63.603,-0.1703 127.21,-0.2222 190.81,-0.1876 v -0.8437 c -63.054,0.0306 -126.1,-0.0178 -189.16,-0.1875 -34.601,-0.1011 -69.213,-0.237 -103.81,-0.375 z m 0,8.0938 v 2.0312 c 97.654,-0.4634 195.31,-0.6454 292.97,-0.625 v -0.75 c -97.655,0.0166 -195.31,-0.1808 -292.97,-0.6562 z m 0,9.5312 v 1.9062 c 34.054,-0.1319 68.102,-0.245 102.16,-0.3437 63.603,-0.1703 127.21,-0.2196 190.81,-0.1875 v -0.8438 c -63.054,0.0332 -126.1,-0.0178 -189.16,-0.1874 -34.601,-0.1011 -69.213,-0.2058 -103.81,-0.3438 z m 0,8.2188 v 1.75 c 97.654,-0.4031 195.31,-0.5301 292.97,-0.4688 v -0.8125 c -97.653,0.0582 -195.32,-0.0547 -292.97,-0.4687 z m 0,9.5 v 1.6874 c 23.617,-0.0835 47.258,-0.1815 70.875,-0.25 74.041,-0.1982 148.05,-0.2323 222.09,-0.1562 v -0.875 c -73.479,0.074 -146.96,0.0415 -220.44,-0.1562 -24.173,-0.0707 -48.358,-0.1632 -72.531,-0.25 z m 0,6.125 v 1.8124 c 97.655,-0.4141 195.31,-0.5537 292.97,-0.5 v -0.8124 c -97.659,0.0505 -195.31,-0.0746 -292.97,-0.5 z m 0,9.5312 v 1.6875 c 25.574,-0.0921 51.145,-0.1759 76.719,-0.25 72.082,-0.1929 144.17,-0.2239 216.25,-0.1563 v -0.875 c -71.532,0.0657 -143.06,0.0363 -214.59,-0.1562 -26.13,-0.0763 -52.245,-0.1543 -78.375,-0.25 z m 0,8.125 v 1.7188 c 97.656,-0.4013 195.31,-0.5308 292.97,-0.4688 v -0.8125 c -97.662,0.0626 -195.31,-0.029 -292.97,-0.4375 z m 0,9.4688 v 1.6562 c 22.961,-0.0807 45.945,-0.1522 68.906,-0.2188 74.686,-0.1999 149.37,-0.2352 224.06,-0.1562 v -0.875 c -74.138,0.0739 -148.27,0.0433 -222.41,-0.1562 -23.517,-0.0687 -47.047,-0.1668 -70.563,-0.25 z m 0,8 v 1.9687 c 97.657,-0.4569 195.31,-0.6169 292.97,-0.5937 v -0.7813 c -97.651,0.0245 -195.32,-0.127 -292.97,-0.5937 z m 0,9.5 v 1.9062 c 32.741,-0.1265 65.509,-0.2489 98.25,-0.3438 64.916,-0.1738 129.8,-0.2268 194.72,-0.1874 v -0.8438 c -64.356,0.0352 -128.71,-0.0143 -193.06,-0.1875 -33.298,-0.0973 -66.609,-0.2127 -99.906,-0.3437 z m 0,8.0937 v 2 c 97.657,-0.4557 195.31,-0.6507 292.97,-0.625 v -0.75 c -97.651,0.0219 -195.32,-0.1574 -292.97,-0.625 z m 0,9.5313 v 1.875 c 32.741,-0.1253 65.509,-0.2489 98.25,-0.3438 64.916,-0.1738 129.8,-0.2243 194.72,-0.1875 V 751.12 c -64.356,0.0377 -128.71,-0.0143 -193.06,-0.1875 -33.298,-0.0973 -66.609,-0.2127 -99.906,-0.3437 z m 0,8.1874 v 1.75 c 97.659,-0.3956 195.31,-0.5349 292.97,-0.4687 v -0.8125 c -97.659,0.0632 -195.31,-0.0622 -292.97,-0.4688 z m 0,9.5313 v 1.625 c 22.314,-0.078 44.624,-0.1541 66.938,-0.2187 75.343,-0.2018 150.69,-0.2383 226.03,-0.1563 v -0.875 c -74.789,0.0797 -149.58,0.045 -224.38,-0.1563 -22.869,-0.0668 -45.725,-0.1375 -68.594,-0.2187 z m 0,10.125 v 1.5313 c 97.659,-0.3484 195.31,-0.4415 292.97,-0.3438 v -0.8125 c -97.656,0.0952 -195.31,-0.0166 -292.97,-0.375 z m 0,9.5 v 1.4687 l 41.531,-0.125 c 83.822,-0.2244 167.62,-0.2482 251.44,-0.125 v -0.9374 c -83.264,0.1203 -166.52,0.099 -249.78,-0.125 -14.393,-0.0421 -28.794,-0.1091 -43.188,-0.1563 z m 0,8.1563 v 1.4687 c 97.65,-0.3367 195.32,-0.4189 292.97,-0.3125 v -0.875 c -97.657,0.1055 -195.31,0.0599 -292.97,-0.2812 z m 0,9.4687 v 1.4375 c 11.23,-0.0355 22.458,-0.0925 33.688,-0.125 86.426,-0.2314 172.85,-0.2308 259.28,-0.0938 v -1 c -85.874,0.1309 -171.75,0.1374 -257.63,-0.0937 -11.784,-0.0344 -23.56,-0.0874 -35.344,-0.125 z m 0,8.0313 v 1.6874 c 97.653,-0.3903 195.31,-0.507 292.97,-0.4374 v -0.8438 c -97.656,0.0699 -195.31,-0.009 -292.97,-0.4062 z m 0,9.4687 v 1.6563 c 21.011,-0.0726 42.02,-0.1579 63.031,-0.2188 76.645,-0.2052 153.29,-0.2442 229.94,-0.1562 v -0.9063 c -76.094,0.0826 -152.19,0.0485 -228.28,-0.1563 -21.567,-0.0629 -43.121,-0.1431 -64.688,-0.2187 z m 0,8.125 v 1.6875 c 97.653,-0.3882 195.31,-0.5087 292.97,-0.4375 V 831.62 c -97.656,0.0682 -195.31,-0.0385 -292.97,-0.4375 z m 0,9.5 v 1.625 c 21.011,-0.0719 42.02,-0.1579 63.031,-0.2187 76.645,-0.2053 153.29,-0.2412 229.94,-0.1563 v -0.875 c -76.094,0.0856 -152.19,0.0485 -228.28,-0.1563 -21.567,-0.0629 -43.121,-0.1431 -64.688,-0.2187 z m 0,8.2187 v 1.4376 c 97.66,-0.3305 195.31,-0.3594 292.97,-0.25 v -0.9063 c -97.657,0.1072 -195.31,0.059 -292.97,-0.2813 z m 0,9.4688 v 1.4062 l 31.75,-0.0937 c 87.078,-0.2331 174.14,-0.2344 261.22,-0.0937 v -1 c -86.522,0.1374 -173.04,0.1391 -259.56,-0.0938 -11.133,-0.0325 -22.273,-0.0895 -33.406,-0.125 z m 0,6.0312 v 1.75 c 97.654,-0.3955 195.31,-0.5349 292.97,-0.4687 V 864.87 c -97.659,0.0632 -195.31,-0.0622 -292.97,-0.4688 z m 0,9.5313 c 0.0444,0.6929 0.3272,1.2501 0.75,1.625 22.064,-0.0769 44.124,-0.1548 66.188,-0.2187 75.207,-0.2014 150.42,-0.2376 225.62,-0.1563 0.16038,-0.2431 0.28202,-0.5509 0.34375,-0.875 -74.771,0.0796 -149.54,0.045 -224.31,-0.1563 -22.869,-0.0668 -45.725,-0.1375 -68.594,-0.2187 z"
|
||||
filter="url(#n)"
|
||||
opacity="0.2202"
|
||||
id="path112" />
|
||||
</g>
|
||||
<path
|
||||
d="m 40.820156,94.409551 h 76.239684 a 3.3067625,3.3067625 0 0 1 3.30676,3.306763 v 62.341126 a 3.3067625,3.3067625 0 0 1 -3.30676,3.30676 H 40.820156 a 3.3067625,3.3067625 0 0 1 -3.306763,-3.30676 V 97.716314 a 3.3067625,3.3067625 0 0 1 3.306763,-3.306763"
|
||||
fill="url(#t)"
|
||||
id="path116"
|
||||
style="fill:url(#t);stroke-width:0.264583" />
|
||||
<path
|
||||
d="m 40.820156,94.409551 h 76.239684 a 3.3067625,3.3067625 0 0 1 3.30676,3.306763 v 62.341126 a 3.3067625,3.3067625 0 0 1 -3.30676,3.30676 H 40.820156 a 3.3067625,3.3067625 0 0 1 -3.306763,-3.30676 V 97.716314 a 3.3067625,3.3067625 0 0 1 3.306763,-3.306763"
|
||||
fill="url(#x)"
|
||||
id="path118"
|
||||
style="fill:url(#x);stroke-width:0.264583" />
|
||||
<path
|
||||
transform="matrix(0.26458333,0,0,0.26458333,42.487031,-81.558472)"
|
||||
d="m -6.2998,662.08 c -8.5337,0 -15.5,6.9663 -15.5,15.5 V 913.2 c 0,8.5337 6.9663,15.5 15.5,15.5 h 288.16 c 8.5337,0 15.5,-6.9663 15.5,-15.5 V 677.58 c 0,-8.5337 -6.9663,-15.5 -15.5,-15.5 z m 0,6 h 288.16 c 5.3135,0 9.5,4.1865 9.5,9.5 V 913.2 c 0,5.3135 -4.1865,9.5 -9.5,9.5 h -288.16 c -5.3135,0 -9.5,-4.1865 -9.5,-9.5 V 677.58 c 0,-5.3135 4.1865,-9.5 9.5,-9.5 z"
|
||||
fill="#ffffff"
|
||||
filter="url(#c)"
|
||||
mask="url(#b)"
|
||||
id="path120" />
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458333,268.30097,-298.58136)"
|
||||
id="g126">
|
||||
<path
|
||||
d="m -718.43,1283 c -46.21,0.1238 -97.408,16.465 -119.06,54.089 -13.801,24.035 -8.4155,51.758 -10.594,77.602 -0.3932,31.793 -0.6396,63.604 -0.031,95.398 l 21.781,-1.2943 -21.219,23.135 c 0.041,1.2322 0.081,2.4621 0.125,3.694 0.1398,25.837 38.758,3.234 39.437,-14.075 -0.522,-52.154 -1.3603,-104.4 1.9063,-156.5 6.1489,-24.928 33.689,-41.417 61.812,-44.787 31.318,-4.4778 67.758,-2.6342 92.781,16.016 17.831,12.31 21.591,32.024 19.031,50.449 -0.9481,98.57 -1.5396,197.19 -0.6875,295.74 7.8425,27.05 50.552,9.1799 39.656,-14.345 -0.048,-102.01 -0.024,-204.04 1.2188,-306.04 -4.9781,-34.62 -37.945,-63.117 -76.125,-72.479 -16.133,-4.4659 -33.097,-6.6347 -50.031,-6.6061 z"
|
||||
fill="url(#i)"
|
||||
id="path122"
|
||||
style="fill:url(#linearGradient453)" />
|
||||
<path
|
||||
d="m -719.39,1290.2 c -35.758,0 -64.743,5.9938 -64.743,13.383 0,3.7664 7.5446,7.1567 19.665,9.5881 11.739,-2.4569 28.043,-3.9782 46.071,-3.9782 17.531,0 33.42,1.4562 45.078,3.7949 11.544,-2.4162 18.693,-5.7411 18.693,-9.4048 0,-7.3892 -29.006,-13.383 -64.764,-13.383 z"
|
||||
fill="#ffffff"
|
||||
filter="url(#l)"
|
||||
opacity="0.7278"
|
||||
id="path124" />
|
||||
</g>
|
||||
<path
|
||||
transform="matrix(0.26458333,0,0,0.22829308,-380.69548,-36.699426)"
|
||||
d="m 1734.4,339.23 c -46.21,0.1434 -97.408,19.082 -119.06,62.688 -13.801,27.856 -8.4155,59.986 -10.594,89.938 -0.3932,36.847 -0.6396,73.715 -0.031,110.56 l 21.781,-1.5 -21.219,26.812 c 0.041,1.428 0.081,2.8534 0.125,4.2812 0.1398,29.944 38.758,3.7481 39.438,-16.312 -0.522,-60.445 -1.3603,-121 1.9063,-181.38 6.1489,-28.89 33.689,-48.001 61.812,-51.906 31.318,-5.1896 67.758,-3.053 92.781,18.562 17.831,14.267 21.591,37.115 19.031,58.469 -0.9481,114.24 -1.5396,228.53 -0.6875,342.75 7.8425,31.35 50.552,10.639 39.656,-16.625 -0.048,-118.23 -0.024,-236.48 1.2188,-354.69 -4.9781,-40.123 -37.945,-73.15 -76.125,-84 -16.134,-5.1758 -33.097,-7.6894 -50.031,-7.6562 z"
|
||||
fill="#cccccc"
|
||||
filter="url(#p)"
|
||||
id="path128" />
|
||||
<path
|
||||
transform="matrix(0.26458333,0,0,0.22817402,-250.41994,-32.98203)"
|
||||
d="m 1242.1,334.47 c -43.163,0.13395 -89.956,17.995 -109.06,56.438 v 0.0312 c -11.662,23.566 -7.2063,53.469 -9.4062,85.062 -0.3508,32.868 -0.5102,65.68 -0.125,98.438 l 9.875,-0.6875 a 11.211,11.211 0 0 1 7.7812,2.4375 c -0.4624,-52.634 -0.8032,-105.47 2.0625,-158.44 a 11.211,11.211 0 0 1 0.2187,-1.75 c 7.3342,-34.459 38.973,-56.043 70.938,-60.594 v -0.0312 c 0.1012,-0.0168 0.2111,-0.0146 0.3124,-0.0312 33.012,-5.4196 72.604,-3.6311 101.31,20.938 v -0.0312 c 0.077,0.0618 0.1419,0.12544 0.2187,0.1875 l 0.094,0.0937 c 21.121,17.144 25.459,43.852 22.938,67.031 -3e-4,0.0312 2e-4,0.0625 0,0.0937 -0.9432,113.66 -1.5218,227.34 -0.6876,340.91 1.2462,4.1697 2.7143,5.1796 4.0938,5.75 1.505,0.62221 4.0586,0.66583 6.9062,-0.625 2.8476,-1.29083 5.5447,-3.8207 6.875,-6.5312 1.3303,-2.7105 1.7621,-5.3878 0.1563,-9.4062 a 11.211,11.211 0 0 1 -0.8125,-4.1562 c -0.048,-117.93 -0.015,-235.92 1.2188,-353.91 -4.5894,-34.613 -34.106,-64.377 -67.969,-74 a 11.211,11.211 0 0 1 -0.375,-0.0937 c -14.962,-4.8001 -30.752,-7.156 -46.562,-7.125 z m -100.84,259.56 -16.969,21.469 v 0.1875 a 11.211,11.211 0 0 1 0,0.28125 c 0.01,1.215 0.1339,1.1748 0.25,1.7812 0.7478,-0.19089 1.4986,-0.32129 2.6875,-0.96875 3.2667,-1.779 7.2221,-5.135 9.9687,-8.6875 2.7466,-3.5525 4.0428,-7.2429 4.0938,-8.75 -0.015,-1.7663 -0.015,-3.5455 -0.031,-5.3125 z"
|
||||
fill="#ffffff"
|
||||
filter="url(#o)"
|
||||
mask="url(#h)"
|
||||
opacity="0.7"
|
||||
id="path130" />
|
||||
<path
|
||||
transform="matrix(0.26458333,0,0,0.26458333,111.46126,3.3690732)"
|
||||
d="m -125.64,141.27 c -46.21,0.12373 -97.408,16.465 -119.06,54.089 -13.801,24.035 -8.4155,51.758 -10.594,77.602 -0.3932,31.793 -0.6396,63.604 -0.031,95.398 l 21.781,-1.2943 -21.219,23.135 c 0.041,1.2321 0.081,2.462 0.125,3.694 0.1398,25.837 38.758,3.234 39.437,-14.075 -0.522,-52.154 -1.3603,-104.4 1.9063,-156.5 6.1489,-24.928 33.689,-41.417 61.812,-44.787 31.318,-4.4778 67.758,-2.6342 92.781,16.016 17.831,12.31 21.591,32.024 19.031,50.449 -0.9481,98.57 -1.5396,197.19 -0.6875,295.74 7.8425,27.05 50.552,9.1799 39.656,-14.345 -0.048,-102.01 -0.024,-204.04 1.2188,-306.04 -4.9781,-34.62 -37.945,-63.117 -76.125,-72.479 -16.133,-4.4659 -33.097,-6.6347 -50.031,-6.6061 z"
|
||||
fill="none"
|
||||
filter="url(#m)"
|
||||
mask="url(#g)"
|
||||
stroke="#000000"
|
||||
stroke-width="3"
|
||||
id="path132" />
|
||||
<path
|
||||
d="m 40.819627,96.269254 c -0.817113,0 -1.413881,0.596742 -1.413881,1.413881 v 62.341125 c 0,0.81711 0.596741,1.41388 1.413881,1.41388 h 76.242333 c 0.81711,0 1.41388,-0.59674 1.41388,-1.41388 V 97.683135 c 0,-0.817113 -0.59674,-1.413881 -1.41388,-1.413881 z"
|
||||
fill="url(#a)"
|
||||
id="path134"
|
||||
style="fill:url(#linearGradient455);stroke-width:0.264583" />
|
||||
<path
|
||||
d="m 113.59909,95.780093 c 1.06101,0.0093 2.12225,0.01588 3.1832,0.02482 -0.0816,-0.0131 -0.16098,-0.02482 -0.24804,-0.02482 h -2.93529 z m -74.339976,0.421693 c -0.02551,0.0264 -0.05116,0.05416 -0.07441,0.08268 7.481888,0.180472 14.965892,0.371105 22.448308,0.529167 14.260513,0.301254 17.355608,0.327263 31.675917,0.553958 17.771791,0.212963 14.241191,0.190791 24.639321,0.23151 v -0.0083 C 107.5742,97.550235 111.08654,97.563914 93.325593,97.351083 78.653389,97.118779 76.274785,97.112878 61.676135,96.780562 54.203773,96.610461 46.732469,96.403213 39.2609,96.201786 Z m 69.725646,2.265495 c -23.368794,0.01275 -46.738646,0.205316 -70.106646,0.471302 v 0.0744 C 65.232995,98.645795 91.591051,98.480192 117.9462,98.616109 v -0.140547 c -2.98821,-0.0042 -5.97456,-0.0099 -8.96276,-0.0083 z m 8.96276,1.620569 c -11.68215,0.0338 -23.367206,0.0883 -35.049354,0.19844 -22.163352,0.26083 -15.260373,0.13867 -37.380333,0.56224 -2.212605,0.0424 -4.426744,0.0875 -6.639454,0.13229 v 0.0165 c 8.422746,-0.17489 16.844698,-0.34459 25.267708,-0.47128 6.243108,-0.0939 12.492566,-0.14975 18.735675,-0.22325 11.687968,-0.0939 23.377528,-0.1646 35.065228,-0.2067 v -0.008 z m -79.068083,3.23295 v 0.0331 c 16.594402,0.0845 33.189333,0.14893 49.784,0.2894 4.524639,0.0315 9.043458,0.0672 13.568093,0.091 4.22884,0.0222 10.1653,0.0393 15.71784,0.0413 v -0.14054 c -9.73111,0.0371 -19.4646,-0.008 -29.194123,-0.0661 -16.624564,-0.1157 -33.250187,-0.16804 -49.873958,-0.24807 z m 0,0.43823 v 0.0165 c 26.348531,0.83103 52.710291,1.1253 79.068083,1.33946 v -0.0662 c -26.361761,-0.13425 -52.718229,-0.46022 -79.068083,-1.28984 z m 0,1.77765 v 0.0744 c 6.776244,0.16415 13.554869,0.33639 20.331642,0.47956 14.260512,0.30123 17.355608,0.32726 31.675916,0.55398 18.952895,0.22704 13.686365,0.18555 27.061585,0.23977 v -0.008 c -13.35326,-0.0541 -8.10075,-0.0208 -27.04571,-0.24807 -14.672204,-0.23228 -17.050808,-0.23813 -31.649458,-0.5705 -6.79159,-0.15459 -13.582121,-0.338 -20.372916,-0.52091 z m 67.683063,2.20763 c -22.561815,0.0123 -45.122042,0.19516 -67.683063,0.44649 v 0.0661 c 18.969831,-0.25521 37.946541,-0.40838 56.917166,-0.41341 7.050357,-0.003 14.099907,0.0185 21.150267,0.0579 0.33371,0.003 0.66675,0.0127 1.00044,0.0165 v -0.15708 l -0.80201,-0.008 c -3.52848,-0.006 -7.05485,-0.0102 -10.58333,-0.008 z m 11.38528,0.41341 c -9.66602,0.0358 -19.331248,0.0908 -28.995683,0.18191 -22.163352,0.26085 -15.260373,0.13866 -37.380333,0.56224 -4.230158,0.081 -8.461375,0.16107 -12.691798,0.24804 v 0.0248 c 10.440194,-0.21571 20.879594,-0.44651 31.321375,-0.60356 6.243108,-0.0939 12.4841,-0.14148 18.727473,-0.21498 9.671579,-0.0777 19.349776,-0.13967 29.022146,-0.1819 v -0.0165 z m -79.068078,3.43139 v 0.0331 l 5.1181,0.18193 c 15.994327,0.51223 31.996062,0.81147 47.998062,1.02526 -15.994327,-0.20574 -31.993417,-0.53137 -47.979541,-1.04183 -1.712437,-0.0646 -3.422121,-0.13229 -5.134505,-0.19843 z m 53.115103,1.24026 c 1.581865,0.0211 3.164152,0.0468 4.746096,0.0661 -3.467894,-0.0143 -6.941873,-0.029 -10.409767,-0.0496 -15.817585,-0.11007 -31.633582,-0.15815 -47.450374,-0.23151 v 0.0248 c 15.787687,0.0786 31.572729,0.14751 47.360416,0.28112 4.52464,0.0315 9.05166,0.0672 13.5763,0.0909 3.067054,0.0161 7.058554,0.0331 11.137374,0.0414 2.33153,0.0223 4.66328,0.0377 6.99505,0.0579 v -0.0413 l -1.81901,-0.008 h 1.81901 v -0.15711 c -6.36693,0.0336 -12.73254,0.0224 -19.099741,0 -2.284968,-0.0249 -4.569354,-0.045 -6.854295,-0.0744 z m -53.115103,0.59531 v 0.0909 c 8.79422,0.20828 17.589235,0.43434 26.383985,0.6201 14.260512,0.30125 17.355608,0.33552 31.675916,0.56224 15.995917,0.19166 13.996197,0.18791 21.009507,0.215 v -0.0166 c -7.00088,-0.027 -5.00513,-0.0235 -20.993103,-0.21495 -14.672204,-0.23233 -17.050808,-0.24643 -31.649458,-0.5788 -8.809302,-0.2005 -17.617016,-0.44151 -26.42526,-0.67797 z m 73.736728,2.37297 c -24.579262,0.0137 -49.159583,0.22442 -73.736728,0.51263 v 0.0744 c 26.354352,-0.38587 52.712937,-0.56346 79.068078,-0.45474 v -0.12401 c -1.7781,-0.001 -3.55494,-0.009 -5.33294,-0.008 z m 5.33294,1.62885 c -10.47247,0.0351 -20.947061,0.0914 -31.419269,0.19016 -22.163352,0.26088 -15.260373,0.13864 -37.380333,0.56224 -3.42265,0.0655 -6.846358,0.13642 -10.269008,0.20672 v 0.008 c 9.634008,-0.20024 19.263254,-0.40079 28.897791,-0.5457 6.243109,-0.0939 12.492567,-0.14147 18.735675,-0.21498 10.478029,-0.0841 20.957644,-0.14763 31.435144,-0.19018 z m -79.068081,3.20807 v 0.0331 c 17.804077,0.0946 35.610271,0.14695 53.414083,0.29765 4.524639,0.0315 9.051658,0.0755 13.576298,0.0992 3.29883,0.0173 7.68377,0.0338 12.07982,0.0413 v -0.13229 c -8.5217,0.0155 -17.0442,-0.0238 -25.565366,-0.0744 -17.834504,-0.12412 -35.668479,-0.17307 -53.504041,-0.26459 z m 0,0.32245 v 0.0248 c 0.900959,0.0318 1.802765,0.0678 2.703777,0.0992 11.693525,0.37452 23.384933,0.63519 35.081104,0.83511 -11.692202,-0.20029 -23.376731,-0.47845 -35.065229,-0.85162 -0.90678,-0.0342 -1.813427,-0.0728 -2.720181,-0.1075 z m 37.851291,0.95912 c 13.737167,0.23447 27.47963,0.38047 41.21679,0.4960Line truncated
|
||||
fill="#1a1a1a"
|
||||
opacity="0.2"
|
||||
stroke="#000000"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="bevel"
|
||||
stroke-width="0.145381"
|
||||
id="path136" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,467 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="82.855278mm"
|
||||
height="141.74763mm"
|
||||
viewBox="0 0 82.855278 141.74763"
|
||||
version="1.1"
|
||||
id="svg1352"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
sodipodi:docname="padlock-open.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1354"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.2938725"
|
||||
inkscape:cx="5.4101158"
|
||||
inkscape:cy="224.90624"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1349">
|
||||
<filter
|
||||
id="ab"
|
||||
x="-0.063887693"
|
||||
y="-0.038440254"
|
||||
width="1.1277754"
|
||||
height="1.0768805">
|
||||
<feGaussianBlur
|
||||
stdDeviation="8.4959"
|
||||
id="feGaussianBlur14" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="f"
|
||||
cx="207.08"
|
||||
cy="608.71997"
|
||||
r="156.57001"
|
||||
gradientTransform="matrix(1,0,0,0.83226,1.428,126.14)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#e" />
|
||||
<linearGradient
|
||||
id="e">
|
||||
<stop
|
||||
stop-color="#d3bc5f"
|
||||
offset="0"
|
||||
id="stop49" />
|
||||
<stop
|
||||
stop-color="#c8ab37"
|
||||
offset="1"
|
||||
id="stop51" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="j"
|
||||
x1="201.44"
|
||||
x2="201.78"
|
||||
y1="1021.9"
|
||||
y2="727.78003"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#d" />
|
||||
<linearGradient
|
||||
id="d">
|
||||
<stop
|
||||
stop-color="#806600"
|
||||
offset="0"
|
||||
id="stop44" />
|
||||
<stop
|
||||
stop-color="#806600"
|
||||
stop-opacity="0"
|
||||
offset="1"
|
||||
id="stop46" />
|
||||
</linearGradient>
|
||||
<mask
|
||||
id="b"
|
||||
maskUnits="userSpaceOnUse">
|
||||
<path
|
||||
d="m -6.2978,665.08 h 288.15 a 12.498,12.498 0 0 1 12.498,12.498 v 235.62 a 12.498,12.498 0 0 1 -12.498,12.498 h -288.15 a 12.498,12.498 0 0 1 -12.498,-12.498 v -235.62 a 12.498,12.498 0 0 1 12.498,-12.498"
|
||||
fill="#ffffff"
|
||||
id="path81" />
|
||||
</mask>
|
||||
<filter
|
||||
id="c"
|
||||
x="-0.021572628"
|
||||
y="-0.025823718"
|
||||
width="1.0431453"
|
||||
height="1.0516474">
|
||||
<feGaussianBlur
|
||||
stdDeviation="2.8688"
|
||||
id="feGaussianBlur2" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#a"
|
||||
id="linearGradient457"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="59.094002"
|
||||
x2="357.94" />
|
||||
<linearGradient
|
||||
id="a"
|
||||
x1="59.094002"
|
||||
x2="357.94"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="0"
|
||||
id="stop54" />
|
||||
<stop
|
||||
stop-color="#fea"
|
||||
offset=".5763"
|
||||
id="stop56" />
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="1"
|
||||
id="stop58" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
id="n"
|
||||
x="-0.0021757265"
|
||||
y="-0.0026762109"
|
||||
width="1.0043515"
|
||||
height="1.0053524">
|
||||
<feGaussianBlur
|
||||
stdDeviation=".2656"
|
||||
id="feGaussianBlur5" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="i"
|
||||
x1="-692.91998"
|
||||
x2="-710.91998"
|
||||
y1="1521.9"
|
||||
y2="1295.6"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#k" />
|
||||
<linearGradient
|
||||
id="k">
|
||||
<stop
|
||||
stop-color="#ccc"
|
||||
offset="0"
|
||||
id="stop37" />
|
||||
<stop
|
||||
stop-color="#fff"
|
||||
offset=".6899"
|
||||
id="stop39" />
|
||||
<stop
|
||||
stop-color="#e6e6e6"
|
||||
offset="1"
|
||||
id="stop41" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
id="l"
|
||||
x="-0.063578956"
|
||||
y="-0.35844691"
|
||||
width="1.1271579"
|
||||
height="1.7168938">
|
||||
<feGaussianBlur
|
||||
stdDeviation="3.4308"
|
||||
id="feGaussianBlur34" />
|
||||
</filter>
|
||||
<filter
|
||||
id="p"
|
||||
x="-0.037389143"
|
||||
y="-0.020053786"
|
||||
width="1.0747783"
|
||||
height="1.0401076">
|
||||
<feGaussianBlur
|
||||
result="result8"
|
||||
stdDeviation="4"
|
||||
id="feGaussianBlur20" />
|
||||
<feComposite
|
||||
in="result8"
|
||||
in2="result8"
|
||||
operator="xor"
|
||||
result="result6"
|
||||
id="feComposite22" />
|
||||
<feDisplacementMap
|
||||
in="result6"
|
||||
in2="result6"
|
||||
result="result4"
|
||||
scale="80"
|
||||
id="feDisplacementMap24"
|
||||
xChannelSelector="A"
|
||||
yChannelSelector="A" />
|
||||
<feComposite
|
||||
in="result8"
|
||||
in2="result4"
|
||||
k1="3"
|
||||
operator="arithmetic"
|
||||
result="result2"
|
||||
id="feComposite26"
|
||||
k2="0"
|
||||
k3="0"
|
||||
k4="0" />
|
||||
<feComposite
|
||||
in="result2"
|
||||
in2="result2"
|
||||
operator="in"
|
||||
result="fbSourceGraphic"
|
||||
id="feComposite28" />
|
||||
</filter>
|
||||
<mask
|
||||
id="h"
|
||||
maskUnits="userSpaceOnUse">
|
||||
<path
|
||||
d="m 1242.1,323.25 c -46.21,0.1434 -97.408,19.082 -119.06,62.688 -13.801,27.856 -8.4155,59.986 -10.594,89.938 -0.3932,36.847 -0.6396,73.715 -0.031,110.56 l 21.781,-1.5 -21.219,26.812 c 0.041,1.428 0.081,2.8534 0.125,4.2812 0.1398,29.944 38.758,3.7481 39.438,-16.312 -0.522,-60.445 -1.3603,-121 1.9063,-181.38 6.1489,-28.89 33.689,-48.001 61.812,-51.906 31.318,-5.1896 67.758,-3.053 92.781,18.562 17.831,14.267 21.591,37.115 19.031,58.469 -0.9481,114.24 -1.5396,228.53 -0.6875,342.75 7.8425,31.35 50.552,10.639 39.656,-16.625 -0.048,-118.23 -0.024,-236.48 1.2188,-354.69 -4.9781,-40.123 -37.945,-73.15 -76.125,-84 -16.133,-5.1758 -33.097,-7.6894 -50.031,-7.6562 z"
|
||||
fill="#cccccc"
|
||||
id="path78" />
|
||||
</mask>
|
||||
<filter
|
||||
id="o"
|
||||
x="-0.14145334"
|
||||
y="-0.072653391"
|
||||
width="1.2829067"
|
||||
height="1.1453068">
|
||||
<feGaussianBlur
|
||||
stdDeviation="13.8123"
|
||||
id="feGaussianBlur31" />
|
||||
</filter>
|
||||
<mask
|
||||
id="g"
|
||||
maskUnits="userSpaceOnUse">
|
||||
<path
|
||||
d="m -125.64,141.27 c -46.21,0.12373 -97.408,16.465 -119.06,54.089 -13.801,24.035 -8.4155,51.758 -10.594,77.602 -0.3932,31.793 -0.6396,63.604 -0.031,95.398 l 21.781,-1.2943 -21.219,23.135 c 0.041,1.2321 0.081,2.462 0.125,3.694 0.1398,25.837 38.758,3.234 39.437,-14.075 -0.522,-52.154 -1.3603,-104.4 1.9063,-156.5 6.1489,-24.928 33.689,-41.417 61.812,-44.787 31.318,-4.4778 67.758,-2.6342 92.781,16.016 17.831,12.31 21.591,32.024 19.031,50.449 -0.9481,98.57 -1.5396,197.19 -0.6875,295.74 7.8425,27.05 50.552,9.1799 39.656,-14.345 -0.048,-102.01 -0.024,-204.04 1.2188,-306.04 -4.9781,-34.62 -37.945,-63.117 -76.125,-72.479 -16.133,-4.4659 -33.097,-6.6347 -50.031,-6.6061 z"
|
||||
fill="url(#y)"
|
||||
id="path84" />
|
||||
</mask>
|
||||
<linearGradient
|
||||
id="y"
|
||||
x1="-100.13"
|
||||
x2="-118.13"
|
||||
y1="380.12"
|
||||
y2="153.84"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#k" />
|
||||
<filter
|
||||
id="m"
|
||||
x="-0.03714728"
|
||||
y="-0.023091114"
|
||||
width="1.0742253"
|
||||
height="1.0461927">
|
||||
<feGaussianBlur
|
||||
stdDeviation="3.3491"
|
||||
id="feGaussianBlur8" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="s"
|
||||
cx="207.08"
|
||||
cy="608.71997"
|
||||
r="156.57001"
|
||||
gradientTransform="matrix(0.26458333,0,0,0.22020212,35.514623,57.4525)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#e" />
|
||||
<linearGradient
|
||||
id="w"
|
||||
x1="-586.71002"
|
||||
x2="-586.35999"
|
||||
y1="1121.6"
|
||||
y2="827.53003"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#d"
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,243.66762,-2.316874)" />
|
||||
<mask
|
||||
id="mask1302"
|
||||
maskUnits="userSpaceOnUse">
|
||||
<path
|
||||
d="m -6.2978,665.08 h 288.15 a 12.498,12.498 0 0 1 12.498,12.498 v 235.62 a 12.498,12.498 0 0 1 -12.498,12.498 h -288.15 a 12.498,12.498 0 0 1 -12.498,-12.498 v -235.62 a 12.498,12.498 0 0 1 12.498,-12.498"
|
||||
fill="#ffffff"
|
||||
id="path1300" />
|
||||
</mask>
|
||||
<filter
|
||||
id="filter1306"
|
||||
x="-0.021572627"
|
||||
y="-0.025823718"
|
||||
width="1.0431453"
|
||||
height="1.0516474">
|
||||
<feGaussianBlur
|
||||
stdDeviation="2.8688"
|
||||
id="feGaussianBlur1304" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#a"
|
||||
id="linearGradient459"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="59.094002"
|
||||
x2="357.94"
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,35.136268,24.045415)" />
|
||||
<linearGradient
|
||||
id="linearGradient1315"
|
||||
x1="59.094002"
|
||||
x2="357.94"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="0"
|
||||
id="stop1309" />
|
||||
<stop
|
||||
stop-color="#fea"
|
||||
offset=".5763"
|
||||
id="stop1311" />
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="1"
|
||||
id="stop1313" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#a"
|
||||
id="linearGradient461"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="59.094002"
|
||||
x2="357.94"
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,35.136268,24.045415)" />
|
||||
<linearGradient
|
||||
id="linearGradient1324"
|
||||
x1="59.094002"
|
||||
x2="357.94"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="0"
|
||||
id="stop1318" />
|
||||
<stop
|
||||
stop-color="#fea"
|
||||
offset=".5763"
|
||||
id="stop1320" />
|
||||
<stop
|
||||
stop-color="#d4aa00"
|
||||
offset="1"
|
||||
id="stop1322" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-48.877192,-116.29581)">
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458333,35.136268,24.077694)"
|
||||
id="g152">
|
||||
<path
|
||||
d="m 64.433,623.66 h 288.15 a 12.498,12.498 0 0 1 12.498,12.498 v 235.62 a 12.498,12.498 0 0 1 -12.498,12.498 H 64.433 A 12.498,12.498 0 0 1 51.935,871.778 V 636.158 A 12.498,12.498 0 0 1 64.433,623.66"
|
||||
fill="url(#f)"
|
||||
id="path142"
|
||||
style="fill:url(#f)" />
|
||||
<path
|
||||
d="m 64.433,623.66 h 288.15 a 12.498,12.498 0 0 1 12.498,12.498 v 235.62 a 12.498,12.498 0 0 1 -12.498,12.498 H 64.433 A 12.498,12.498 0 0 1 51.935,871.778 V 636.158 A 12.498,12.498 0 0 1 64.433,623.66"
|
||||
fill="url(#j)"
|
||||
id="path144"
|
||||
style="fill:url(#j)" />
|
||||
<path
|
||||
transform="translate(70.731,-41.416)"
|
||||
d="m -6.2998,662.08 c -8.5337,0 -15.5,6.9663 -15.5,15.5 V 913.2 c 0,8.5337 6.9663,15.5 15.5,15.5 h 288.16 c 8.5337,0 15.5,-6.9663 15.5,-15.5 V 677.58 c 0,-8.5337 -6.9663,-15.5 -15.5,-15.5 z m 0,6 h 288.16 c 5.3135,0 9.5,4.1865 9.5,9.5 V 913.2 c 0,5.3135 -4.1865,9.5 -9.5,9.5 h -288.16 c -5.3135,0 -9.5,-4.1865 -9.5,-9.5 V 677.58 c 0,-5.3135 4.1865,-9.5 9.5,-9.5 z"
|
||||
fill="#ffffff"
|
||||
filter="url(#c)"
|
||||
mask="url(#b)"
|
||||
id="path146" />
|
||||
<path
|
||||
d="m 64.438,630.81 c -3.0883,0 -5.3438,2.2554 -5.3438,5.3438 v 235.62 c 0,3.0883 2.2554,5.3438 5.3438,5.3438 h 288.16 c 3.0883,0 5.3438,-2.2554 5.3438,-5.3438 v -235.62 c 0,-3.0883 -2.2554,-5.3438 -5.3438,-5.3438 z"
|
||||
fill="url(#a)"
|
||||
id="path148"
|
||||
style="fill:url(#linearGradient457)" />
|
||||
<path
|
||||
d="m 65.691,637.37 c -0.0543,0.2253 -0.0937,0.4551 -0.0937,0.7188 v 1.3124 c 97.654,-0.4644 195.31,-0.674 292.97,-0.6562 v -0.6562 c 0,-0.033 8.7e-4,-0.0614 0,-0.0938 -97.624,0.0192 -195.25,-0.1507 -292.88,-0.625 z m -0.0937,9.5 v 1.9375 c 34.054,-0.1333 68.102,-0.2451 102.16,-0.3437 63.603,-0.1703 127.21,-0.2222 190.81,-0.1876 v -0.8437 c -63.054,0.0306 -126.1,-0.0178 -189.16,-0.1875 -34.601,-0.1011 -69.213,-0.237 -103.81,-0.375 z m 0,8.0938 v 2.0312 c 97.654,-0.4634 195.31,-0.6454 292.97,-0.625 v -0.75 c -97.655,0.0166 -195.31,-0.1808 -292.97,-0.6562 z m 0,9.5312 v 1.9062 c 34.054,-0.1319 68.102,-0.245 102.16,-0.3437 63.603,-0.1703 127.21,-0.2196 190.81,-0.1875 v -0.8438 c -63.054,0.0332 -126.1,-0.0178 -189.16,-0.1874 -34.601,-0.1011 -69.213,-0.2058 -103.81,-0.3438 z m 0,8.2188 v 1.75 c 97.654,-0.4031 195.31,-0.5301 292.97,-0.4688 v -0.8125 c -97.653,0.0582 -195.32,-0.0547 -292.97,-0.4687 z m 0,9.5 v 1.6874 c 23.617,-0.0835 47.258,-0.1815 70.875,-0.25 74.041,-0.1982 148.05,-0.2323 222.09,-0.1562 v -0.875 c -73.479,0.074 -146.96,0.0415 -220.44,-0.1562 -24.173,-0.0707 -48.358,-0.1632 -72.531,-0.25 z m 0,6.125 v 1.8124 c 97.655,-0.4141 195.31,-0.5537 292.97,-0.5 v -0.8124 c -97.659,0.0505 -195.31,-0.0746 -292.97,-0.5 z m 0,9.5312 v 1.6875 c 25.574,-0.0921 51.145,-0.1759 76.719,-0.25 72.082,-0.1929 144.17,-0.2239 216.25,-0.1563 v -0.875 c -71.532,0.0657 -143.06,0.0363 -214.59,-0.1562 -26.13,-0.0763 -52.245,-0.1543 -78.375,-0.25 z m 0,8.125 v 1.7188 c 97.656,-0.4013 195.31,-0.5308 292.97,-0.4688 v -0.8125 c -97.662,0.0626 -195.31,-0.029 -292.97,-0.4375 z m 0,9.4688 v 1.6562 c 22.961,-0.0807 45.945,-0.1522 68.906,-0.2188 74.686,-0.1999 149.37,-0.2352 224.06,-0.1562 v -0.875 c -74.138,0.0739 -148.27,0.0433 -222.41,-0.1562 -23.517,-0.0687 -47.047,-0.1668 -70.563,-0.25 z m 0,8 v 1.9687 c 97.657,-0.4569 195.31,-0.6169 292.97,-0.5937 v -0.7813 c -97.651,0.0245 -195.32,-0.127 -292.97,-0.5937 z m 0,9.5 v 1.9062 c 32.741,-0.1265 65.509,-0.2489 98.25,-0.3438 64.916,-0.1738 129.8,-0.2268 194.72,-0.1874 v -0.8438 c -64.356,0.0352 -128.71,-0.0143 -193.06,-0.1875 -33.298,-0.0973 -66.609,-0.2127 -99.906,-0.3437 z m 0,8.0937 v 2 c 97.657,-0.4557 195.31,-0.6507 292.97,-0.625 v -0.75 c -97.651,0.0219 -195.32,-0.1574 -292.97,-0.625 z m 0,9.5313 v 1.875 c 32.741,-0.1253 65.509,-0.2489 98.25,-0.3438 64.916,-0.1738 129.8,-0.2243 194.72,-0.1875 V 751.12 c -64.356,0.0377 -128.71,-0.0143 -193.06,-0.1875 -33.298,-0.0973 -66.609,-0.2127 -99.906,-0.3437 z m 0,8.1874 v 1.75 c 97.659,-0.3956 195.31,-0.5349 292.97,-0.4687 v -0.8125 c -97.659,0.0632 -195.31,-0.0622 -292.97,-0.4688 z m 0,9.5313 v 1.625 c 22.314,-0.078 44.624,-0.1541 66.938,-0.2187 75.343,-0.2018 150.69,-0.2383 226.03,-0.1563 v -0.875 c -74.789,0.0797 -149.58,0.045 -224.38,-0.1563 -22.869,-0.0668 -45.725,-0.1375 -68.594,-0.2187 z m 0,10.125 v 1.5313 c 97.659,-0.3484 195.31,-0.4415 292.97,-0.3438 v -0.8125 c -97.656,0.0952 -195.31,-0.0166 -292.97,-0.375 z m 0,9.5 v 1.4687 l 41.531,-0.125 c 83.822,-0.2244 167.62,-0.2482 251.44,-0.125 v -0.9374 c -83.264,0.1203 -166.52,0.099 -249.78,-0.125 -14.393,-0.0421 -28.794,-0.1091 -43.188,-0.1563 z m 0,8.1563 v 1.4687 c 97.65,-0.3367 195.32,-0.4189 292.97,-0.3125 v -0.875 c -97.657,0.1055 -195.31,0.0599 -292.97,-0.2812 z m 0,9.4687 v 1.4375 c 11.23,-0.0355 22.458,-0.0925 33.688,-0.125 86.426,-0.2314 172.85,-0.2308 259.28,-0.0938 v -1 c -85.874,0.1309 -171.75,0.1374 -257.63,-0.0937 -11.784,-0.0344 -23.56,-0.0874 -35.344,-0.125 z m 0,8.0313 v 1.6874 c 97.653,-0.3903 195.31,-0.507 292.97,-0.4374 v -0.8438 c -97.656,0.0699 -195.31,-0.009 -292.97,-0.4062 z m 0,9.4687 v 1.6563 c 21.011,-0.0726 42.02,-0.1579 63.031,-0.2188 76.645,-0.2052 153.29,-0.2442 229.94,-0.1562 v -0.9063 c -76.094,0.0826 -152.19,0.0485 -228.28,-0.1563 -21.567,-0.0629 -43.121,-0.1431 -64.688,-0.2187 z m 0,8.125 v 1.6875 c 97.653,-0.3882 195.31,-0.5087 292.97,-0.4375 V 831.62 c -97.656,0.0682 -195.31,-0.0385 -292.97,-0.4375 z m 0,9.5 v 1.625 c 21.011,-0.0719 42.02,-0.1579 63.031,-0.2187 76.645,-0.2053 153.29,-0.2412 229.94,-0.1563 v -0.875 c -76.094,0.0856 -152.19,0.0485 -228.28,-0.1563 -21.567,-0.0629 -43.121,-0.1431 -64.688,-0.2187 z m 0,8.2187 v 1.4376 c 97.66,-0.3305 195.31,-0.3594 292.97,-0.25 v -0.9063 c -97.657,0.1072 -195.31,0.059 -292.97,-0.2813 z m 0,9.4688 v 1.4062 l 31.75,-0.0937 c 87.078,-0.2331 174.14,-0.2344 261.22,-0.0937 v -1 c -86.522,0.1374 -173.04,0.1391 -259.56,-0.0938 -11.133,-0.0325 -22.273,-0.0895 -33.406,-0.125 z m 0,6.0312 v 1.75 c 97.654,-0.3955 195.31,-0.5349 292.97,-0.4687 V 864.87 c -97.659,0.0632 -195.31,-0.0622 -292.97,-0.4688 z m 0,9.5313 c 0.0444,0.6929 0.3272,1.2501 0.75,1.625 22.064,-0.0769 44.124,-0.1548 66.188,-0.2187 75.207,-0.2014 150.42,-0.2376 225.62,-0.1563 0.16038,-0.2431 0.28202,-0.5509 0.34375,-0.875 -74.771,0.0796 -149.54,0.045 -224.31,-0.1563 -22.869,-0.0668 -45.725,-0.1375 -68.594,-0.2187 z"
|
||||
filter="url(#n)"
|
||||
opacity="0.2202"
|
||||
id="path150" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458333,279.66947,-220.83625)"
|
||||
id="g158">
|
||||
<path
|
||||
d="m -718.43,1283 c -46.21,0.1238 -97.408,16.465 -119.06,54.089 -13.801,24.035 -8.4155,51.758 -10.594,77.602 -0.3932,31.793 -0.6396,63.604 -0.031,95.398 l 21.781,-1.2943 -21.219,23.135 c 0.041,1.2322 0.081,2.4621 0.125,3.694 0.1398,25.837 38.758,3.234 39.437,-14.075 -0.522,-52.154 -1.3603,-104.4 1.9063,-156.5 6.1489,-24.928 33.689,-41.417 61.812,-44.787 31.318,-4.4778 67.758,-2.6342 92.781,16.016 17.831,12.31 21.591,32.024 19.031,50.449 -0.9481,98.57 -1.5396,197.19 -0.6875,295.74 7.8425,27.05 50.552,9.1799 39.656,-14.345 -0.048,-102.01 -0.024,-204.04 1.2188,-306.04 -4.9781,-34.62 -37.945,-63.117 -76.125,-72.479 -16.133,-4.4659 -33.097,-6.6347 -50.031,-6.6061 z"
|
||||
fill="url(#i)"
|
||||
id="path154"
|
||||
style="fill:url(#i)" />
|
||||
<path
|
||||
d="m -719.39,1290.2 c -35.758,0 -64.743,5.9938 -64.743,13.383 0,3.7664 7.5446,7.1567 19.665,9.5881 11.739,-2.4569 28.043,-3.9782 46.071,-3.9782 17.531,0 33.42,1.4562 45.078,3.7949 11.544,-2.4162 18.693,-5.7411 18.693,-9.4048 0,-7.3892 -29.006,-13.383 -64.764,-13.383 z"
|
||||
fill="#ffffff"
|
||||
filter="url(#l)"
|
||||
opacity="0.7278"
|
||||
id="path156" />
|
||||
</g>
|
||||
<path
|
||||
transform="matrix(0.26458333,0,0,0.22829308,-369.31904,41.045688)"
|
||||
d="m 1734.4,339.23 c -46.21,0.1434 -97.408,19.082 -119.06,62.688 -13.801,27.856 -8.4155,59.986 -10.594,89.938 -0.3932,36.847 -0.6396,73.715 -0.031,110.56 l 21.781,-1.5 -21.219,26.812 c 0.041,1.428 0.081,2.8534 0.125,4.2812 0.1398,29.944 38.758,3.7481 39.438,-16.312 -0.522,-60.445 -1.3603,-121 1.9063,-181.38 6.1489,-28.89 33.689,-48.001 61.812,-51.906 31.318,-5.1896 67.758,-3.053 92.781,18.562 17.831,14.267 21.591,37.115 19.031,58.469 -0.9481,114.24 -1.5396,228.53 -0.6875,342.75 7.8425,31.35 50.552,10.639 39.656,-16.625 -0.048,-118.23 -0.024,-236.48 1.2188,-354.69 -4.9781,-40.123 -37.945,-73.15 -76.125,-84 -16.134,-5.1758 -33.097,-7.6894 -50.031,-7.6562 z"
|
||||
fill="#cccccc"
|
||||
filter="url(#p)"
|
||||
id="path160" />
|
||||
<path
|
||||
transform="matrix(0.26458333,0,0,0.22817402,-239.06467,44.763084)"
|
||||
d="m 1242.1,334.47 c -43.163,0.13395 -89.956,17.995 -109.06,56.438 v 0.0312 c -11.662,23.566 -7.2063,53.469 -9.4062,85.062 -0.3508,32.868 -0.5102,65.68 -0.125,98.438 l 9.875,-0.6875 a 11.211,11.211 0 0 1 7.7812,2.4375 c -0.4624,-52.634 -0.8032,-105.47 2.0625,-158.44 a 11.211,11.211 0 0 1 0.2187,-1.75 c 7.3342,-34.459 38.973,-56.043 70.938,-60.594 v -0.0312 c 0.1012,-0.0168 0.2111,-0.0146 0.3124,-0.0312 33.012,-5.4196 72.604,-3.6311 101.31,20.938 v -0.0312 c 0.077,0.0618 0.1419,0.12544 0.2187,0.1875 l 0.094,0.0937 c 21.121,17.144 25.459,43.852 22.938,67.031 -3e-4,0.0312 2e-4,0.0625 0,0.0937 -0.9432,113.66 -1.5218,227.34 -0.6876,340.91 1.2462,4.1697 2.7143,5.1796 4.0938,5.75 1.505,0.62221 4.0586,0.66583 6.9062,-0.625 2.8476,-1.29083 5.5447,-3.8207 6.875,-6.5312 1.3303,-2.7105 1.7621,-5.3878 0.1563,-9.4062 a 11.211,11.211 0 0 1 -0.8125,-4.1562 c -0.048,-117.93 -0.015,-235.92 1.2188,-353.91 -4.5894,-34.613 -34.106,-64.377 -67.969,-74 a 11.211,11.211 0 0 1 -0.375,-0.0937 c -14.962,-4.8001 -30.752,-7.156 -46.562,-7.125 z m -100.84,259.56 -16.969,21.469 v 0.1875 a 11.211,11.211 0 0 1 0,0.28125 c 0.01,1.215 0.1339,1.1748 0.25,1.7812 0.7478,-0.19089 1.4986,-0.32129 2.6875,-0.96875 3.2667,-1.779 7.2221,-5.135 9.9687,-8.6875 2.7466,-3.5525 4.0428,-7.2429 4.0938,-8.75 -0.015,-1.7663 -0.015,-3.5455 -0.031,-5.3125 z"
|
||||
fill="#ffffff"
|
||||
filter="url(#o)"
|
||||
mask="url(#h)"
|
||||
opacity="0.7"
|
||||
id="path162" />
|
||||
<path
|
||||
transform="matrix(0.26458333,0,0,0.26458333,122.82712,81.114187)"
|
||||
d="m -125.64,141.27 c -46.21,0.12373 -97.408,16.465 -119.06,54.089 -13.801,24.035 -8.4155,51.758 -10.594,77.602 -0.3932,31.793 -0.6396,63.604 -0.031,95.398 l 21.781,-1.2943 -21.219,23.135 c 0.041,1.2321 0.081,2.462 0.125,3.694 0.1398,25.837 38.758,3.234 39.437,-14.075 -0.522,-52.154 -1.3603,-104.4 1.9063,-156.5 6.1489,-24.928 33.689,-41.417 61.812,-44.787 31.318,-4.4778 67.758,-2.6342 92.781,16.016 17.831,12.31 21.591,32.024 19.031,50.449 -0.9481,98.57 -1.5396,197.19 -0.6875,295.74 7.8425,27.05 50.552,9.1799 39.656,-14.345 -0.048,-102.01 -0.024,-204.04 1.2188,-306.04 -4.9781,-34.62 -37.945,-63.117 -76.125,-72.479 -16.133,-4.4659 -33.097,-6.6347 -50.031,-6.6061 z"
|
||||
fill="none"
|
||||
filter="url(#m)"
|
||||
mask="url(#g)"
|
||||
stroke="#000000"
|
||||
stroke-width="3"
|
||||
id="path164" />
|
||||
<path
|
||||
d="M 52.186018,189.088 H 128.4257 a 3.3067625,3.3067625 0 0 1 3.30677,3.30676 v 62.34113 a 3.3067625,3.3067625 0 0 1 -3.30677,3.30676 H 52.186018 a 3.3067625,3.3067625 0 0 1 -3.306762,-3.30676 v -62.34113 a 3.3067625,3.3067625 0 0 1 3.306762,-3.30676"
|
||||
fill="url(#s)"
|
||||
id="path166"
|
||||
style="fill:url(#s);stroke-width:0.264583" />
|
||||
<path
|
||||
d="M 52.186018,189.088 H 128.4257 a 3.3067625,3.3067625 0 0 1 3.30677,3.30676 v 62.34113 a 3.3067625,3.3067625 0 0 1 -3.30677,3.30676 H 52.186018 a 3.3067625,3.3067625 0 0 1 -3.306762,-3.30676 v -62.34113 a 3.3067625,3.3067625 0 0 1 3.306762,-3.30676"
|
||||
fill="url(#w)"
|
||||
id="path168"
|
||||
style="fill:url(#w);stroke-width:0.264583" />
|
||||
<path
|
||||
transform="matrix(0.26458333,0,0,0.26458333,53.850247,13.119711)"
|
||||
d="m -6.2998,662.08 c -8.5337,0 -15.5,6.9663 -15.5,15.5 V 913.2 c 0,8.5337 6.9663,15.5 15.5,15.5 h 288.16 c 8.5337,0 15.5,-6.9663 15.5,-15.5 V 677.58 c 0,-8.5337 -6.9663,-15.5 -15.5,-15.5 z m 0,6 h 288.16 c 5.3135,0 9.5,4.1865 9.5,9.5 V 913.2 c 0,5.3135 -4.1865,9.5 -9.5,9.5 h -288.16 c -5.3135,0 -9.5,-4.1865 -9.5,-9.5 V 677.58 c 0,-5.3135 4.1865,-9.5 9.5,-9.5 z"
|
||||
fill="#ffffff"
|
||||
filter="url(#c)"
|
||||
mask="url(#b)"
|
||||
id="path170" />
|
||||
<path
|
||||
d="m 52.185489,190.94723 c -0.817113,0 -1.413881,0.59674 -1.413881,1.41388 v 62.34112 c 0,0.81711 0.596741,1.41388 1.413881,1.41388 h 76.242331 c 0.81711,0 1.41388,-0.59674 1.41388,-1.41388 v -62.34112 c 0,-0.81712 -0.59674,-1.41388 -1.41388,-1.41388 z"
|
||||
fill="url(#a)"
|
||||
id="path172"
|
||||
style="fill:url(#linearGradient459);stroke-width:0.264583" />
|
||||
<path
|
||||
d="m 124.96495,190.45854 c 1.06101,0.009 2.12225,0.0159 3.18321,0.0248 -0.0816,-0.0131 -0.16099,-0.0248 -0.24805,-0.0248 h -2.93529 z m -74.339973,0.42169 c -0.02551,0.0264 -0.05117,0.0542 -0.07441,0.0827 7.481887,0.18047 14.965891,0.3711 22.448308,0.52916 14.260512,0.30126 17.355608,0.32727 31.675915,0.55396 17.77179,0.21296 14.24119,0.19079 24.63932,0.23151 v -0.008 c -10.37405,-0.0406 -6.8617,-0.0269 -24.62265,-0.23977 -14.672204,-0.2323 -17.050809,-0.2382 -31.649458,-0.57052 -7.472363,-0.1701 -14.943667,-0.37735 -22.415235,-0.57878 z m 69.725643,2.2655 c -23.368792,0.0128 -46.738644,0.20531 -70.106643,0.4713 v 0.0744 c 26.354881,-0.36719 52.712933,-0.53279 79.068083,-0.39687 v -0.14055 c -2.98821,-0.004 -5.97456,-0.01 -8.96276,-0.008 z m 8.96276,1.62057 c -11.68215,0.0338 -23.3672,0.0883 -35.049352,0.19844 -22.163352,0.26082 -15.260373,0.13867 -37.380333,0.56224 -2.212604,0.0424 -4.426743,0.0875 -6.639454,0.13229 v 0.0165 c 8.422746,-0.17489 16.844698,-0.3446 25.267708,-0.47128 6.243108,-0.094 12.492567,-0.14975 18.735675,-0.22326 11.687966,-0.0938 23.377526,-0.16459 35.065226,-0.20669 v -0.008 z m -79.06808,3.23294 v 0.0331 c 16.594401,0.0845 33.189332,0.14893 49.784,0.2894 4.52464,0.0315 9.04346,0.0672 13.5681,0.091 4.22883,0.0222 10.16529,0.0394 15.71783,0.0413 v -0.14055 c -9.73111,0.0371 -19.4646,-0.008 -29.19412,-0.0661 -16.624565,-0.1157 -33.250188,-0.16804 -49.873958,-0.24808 z m 0,0.43823 v 0.0165 c 26.34853,0.83103 52.71029,1.1253 79.06808,1.33945 v -0.0661 C 102.95162,199.59307 76.595153,199.2671 50.2453,198.43747 Z m 0,1.77766 v 0.0744 c 6.776243,0.16414 13.554868,0.33639 20.331641,0.47955 14.260512,0.30123 17.355608,0.32727 31.675919,0.55399 18.95289,0.22704 13.68636,0.18555 27.06158,0.23976 v -0.008 c -13.35326,-0.0541 -8.10075,-0.0208 -27.04571,-0.24807 -14.672202,-0.23228 -17.050806,-0.23813 -31.649456,-0.5705 -6.791589,-0.15459 -13.58212,-0.338 -20.372916,-0.52091 z m 67.68306,2.20763 c -22.561813,0.0123 -45.12204,0.19516 -67.68306,0.44648 v 0.0662 c 18.969831,-0.25522 37.946541,-0.40838 56.91717,-0.41341 7.05035,-0.003 14.09991,0.0185 21.15026,0.0579 0.33372,0.003 0.66675,0.0127 1.00044,0.0165 v -0.15708 l -0.802,-0.008 c -3.52849,-0.006 -7.05485,-0.0102 -10.58334,-0.008 z m 11.38529,0.41341 c -9.66603,0.0358 -19.33126,0.0908 -28.99569,0.1819 -22.163352,0.26086 -15.260373,0.13867 -37.380334,0.56224 -4.230158,0.081 -8.461374,0.16108 -12.691797,0.24805 v 0.0248 c 10.440193,-0.21571 20.879593,-0.44651 31.321374,-0.60357 6.243109,-0.0939 12.4841,-0.14147 18.727477,-0.21497 9.67158,-0.0777 19.34977,-0.13967 29.02214,-0.1819 v -0.0165 z m -79.068086,3.43138 v 0.0331 l 5.1181,0.18192 c 15.994327,0.51224 31.996062,0.81148 47.998066,1.02526 -15.994331,-0.20574 -31.99342,-0.53136 -47.979545,-1.04182 -1.712436,-0.0646 -3.422121,-0.13229 -5.134504,-0.19844 z m 53.115106,1.24026 c 1.58186,0.0211 3.16415,0.0468 4.74609,0.0661 -3.46789,-0.0143 -6.94187,-0.0291 -10.409763,-0.0496 -15.817585,-0.11006 -31.633583,-0.15814 -47.450375,-0.23151 v 0.0248 c 15.787688,0.0786 31.572729,0.14751 47.360417,0.28112 4.524641,0.0315 9.051661,0.0672 13.576301,0.0909 3.06705,0.0161 7.05855,0.033 11.13737,0.0413 2.33153,0.0223 4.66328,0.0377 6.99505,0.0579 v -0.0413 l -1.81901,-0.008 h 1.81901 v -0.15711 c -6.36693,0.0336 -12.73254,0.0224 -19.09974,0 -2.28497,-0.0249 -4.56935,-0.045 -6.85429,-0.0744 z m -53.115106,0.59532 v 0.0909 c 8.794221,0.20828 17.589235,0.43434 26.383985,0.62011 14.260513,0.30125 17.355608,0.33552 31.675921,0.56224 15.99591,0.19166 13.99619,0.1879 21.0095,0.215 v -0.0166 c -7.00088,-0.027 -5.00512,-0.0235 -20.9931,-0.21494 -14.672204,-0.23233 -17.050808,-0.24644 -31.649458,-0.57881 -8.809302,-0.2005 -17.617017,-0.44151 -26.42526,-0.67796 z m 73.736726,2.37296 c -24.57926,0.0137 -49.15958,0.22442 -73.736726,0.51263 v 0.0744 c 26.354352,-0.38587 52.712936,-0.56346 79.068086,-0.45474 v -0.12401 c -1.77811,-0.001 -3.55495,-0.009 -5.33295,-0.008 z m 5.33294,1.62886 c -10.47247,0.0351 -20.94706,0.0914 -31.419266,0.19016 -22.163352,0.26087 -15.260373,0.13864 -37.380333,0.56223 -3.42265,0.0655 -6.846359,0.13642 -10.269009,0.20672 v 0.008 c 9.634009,-0.20024 19.263254,-0.40079 28.897792,-0.5457 6.243108,-0.0939 12.492566,-0.14148 18.735675,-0.21498 10.478031,-0.0841 20.957641,-0.14764 31.435141,-0.19018 z m -79.068078,3.20807 v 0.0331 c 17.804076,0.0946 35.61027,0.14695 53.414078,0.29766 4.52464,0.0315 9.05166,0.0755 13.5763,0.0992 3.29883,0.0173 7.68377,0.0338 12.07982,0.0413 v -0.13229 c -8.5217,0.0155 -17.04419,-0.0238 -25.56536,-0.0744 -17.834508,-0.12412 -35.668482,-0.17307 -53.504045,-0.26459 z m 0,0.32245 v 0.0248 c 0.900959,0.0318 1.802765,0.0678 2.703777,0.0992 11.693525,0.37452 23.384933,0.63519 35.081103,0.8351 -11.692202,-0.20029 -23.376731,-0.47844 -35.065228,-0.85161 -0.90678,-0.0342 -1.813428,-0.0728 -2.720182,-0.1075 z m 37.851291,0.95911 c 13.737167,0.23448 27.479627,0.38048 41.216787,0.4961 v -0.0579 c -13.73901,-0.0793 -27.47962,-0.20325 -41.216787,-0.43823 z m -37.851291,0.85162Line truncated
|
||||
fill="#1a1a1a"
|
||||
opacity="0.2"
|
||||
stroke="#000000"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="bevel"
|
||||
stroke-width="0.145381"
|
||||
id="path174" />
|
||||
<path
|
||||
d="m 52.185489,190.94723 c -0.817113,0 -1.413881,0.59674 -1.413881,1.41388 v 62.34112 c 0,0.81711 0.596741,1.41388 1.413881,1.41388 h 76.242331 c 0.81711,0 1.41388,-0.59674 1.41388,-1.41388 v -62.34112 c 0,-0.81712 -0.59674,-1.41388 -1.41388,-1.41388 z"
|
||||
fill="url(#a)"
|
||||
id="path176"
|
||||
style="fill:url(#linearGradient461);stroke-width:0.264583" />
|
||||
<path
|
||||
d="m 125.00729,190.98771 c 1.061,0.009 2.12225,0.0159 3.1832,0.0248 -0.0816,-0.0131 -0.16099,-0.0248 -0.24805,-0.0248 h -2.93528 z m -74.33998,0.42169 c -0.02551,0.0264 -0.05116,0.0542 -0.07441,0.0827 7.481887,0.18047 14.965891,0.37111 22.448308,0.52917 14.260512,0.30125 17.355608,0.32726 31.675912,0.55396 17.7718,0.21296 14.2412,0.19079 24.63932,0.23151 v -0.008 c -10.37404,-0.0406 -6.8617,-0.0269 -24.62265,-0.23976 -14.672201,-0.23231 -17.050805,-0.23821 -31.649455,-0.57052 -7.472362,-0.1701 -14.943666,-0.37735 -22.415235,-0.57878 z m 69.72564,2.26549 c -23.368789,0.0128 -46.73864,0.20532 -70.10664,0.47131 v 0.0744 c 26.354881,-0.36719 52.71294,-0.53279 79.06808,-0.39688 v -0.14054 c -2.9882,-0.004 -5.97455,-0.01 -8.96276,-0.008 z m 8.96277,1.62058 c -11.68215,0.0338 -23.36721,0.0883 -35.049359,0.19843 -22.163351,0.26083 -15.260372,0.13867 -37.380332,0.56224 -2.212605,0.0424 -4.426744,0.0875 -6.639454,0.1323 v 0.0165 c 8.422745,-0.17489 16.844697,-0.34459 25.267708,-0.47127 6.243108,-0.094 12.492566,-0.14976 18.735674,-0.22326 11.687973,-0.0938 23.377523,-0.1646 35.065233,-0.20669 v -0.008 z m -79.068087,3.23294 v 0.0331 c 16.594402,0.0845 33.189333,0.14894 49.783997,0.28941 4.52464,0.0315 9.04346,0.0672 13.5681,0.091 4.22884,0.0222 10.16529,0.0393 15.71784,0.0413 v -0.14055 c -9.73111,0.0371 -19.4646,-0.008 -29.19413,-0.0662 -16.624562,-0.1157 -33.250184,-0.16803 -49.873955,-0.24807 z m 0,0.43823 v 0.0165 c 26.348531,0.83103 52.710287,1.1253 79.068087,1.33945 v -0.0661 c -26.36176,-0.13425 -52.718233,-0.46021 -79.068087,-1.28984 z m 0,1.77766 v 0.0744 c 6.776244,0.16415 13.554868,0.3364 20.331641,0.47956 14.260513,0.30123 17.355608,0.32726 31.675916,0.55399 18.9529,0.22703 13.68637,0.18555 27.06158,0.23976 v -0.008 c -13.35325,-0.0541 -8.10074,-0.0208 -27.0457,-0.24808 -14.672208,-0.23227 -17.050813,-0.23812 -31.649462,-0.57049 -6.79159,-0.1546 -13.582121,-0.33801 -20.372917,-0.52091 z m 67.683057,2.20763 c -22.56181,0.0123 -45.122037,0.19515 -67.683057,0.44648 v 0.0661 c 18.969831,-0.25522 37.946541,-0.40839 56.917167,-0.41341 7.05035,-0.003 14.09991,0.0185 21.15026,0.0579 0.33372,0.003 0.66675,0.0127 1.00044,0.0165 v -0.15709 l -0.802,-0.008 c -3.52848,-0.006 -7.05485,-0.0102 -10.58333,-0.008 z m 11.38529,0.41341 c -9.66602,0.0358 -19.33125,0.0908 -28.99569,0.1819 -22.163349,0.26085 -15.26037,0.13867 -37.38033,0.56224 -4.230159,0.081 -8.461375,0.16108 -12.691798,0.24805 v 0.0248 c 10.440194,-0.21572 20.879594,-0.44651 31.321375,-0.60357 6.243108,-0.0939 12.484099,-0.14147 18.727473,-0.21497 9.67158,-0.0777 19.34977,-0.13968 29.02214,-0.18191 v -0.0165 z m -79.068083,3.43138 v 0.0331 l 5.1181,0.18193 c 15.994327,0.51223 31.996062,0.81148 47.998063,1.02526 -15.994328,-0.20574 -31.993417,-0.53136 -47.979542,-1.04182 -1.712436,-0.0646 -3.422121,-0.13229 -5.134504,-0.19844 z m 53.115103,1.24026 c 1.58187,0.0211 3.16415,0.0468 4.7461,0.0661 -3.4679,-0.0143 -6.94188,-0.029 -10.40977,-0.0496 -15.817585,-0.11007 -31.633583,-0.15814 -47.450374,-0.23151 v 0.0248 c 15.787687,0.0786 31.572729,0.1475 47.360416,0.28112 4.524638,0.0315 9.051658,0.0672 13.576298,0.0909 3.06705,0.0161 7.05856,0.033 11.13737,0.0413 2.33154,0.0223 4.66328,0.0377 6.99506,0.0579 v -0.0413 l -1.81901,-0.008 h 1.81901 v -0.15711 c -6.36694,0.0336 -12.73255,0.0224 -19.09974,0 -2.28497,-0.0249 -4.56936,-0.045 -6.8543,-0.0744 z m -53.115103,0.59531 v 0.0909 c 8.794221,0.20828 17.589236,0.43434 26.383986,0.6201 14.260512,0.30126 17.355608,0.33552 31.675917,0.56224 15.99591,0.19167 13.99619,0.18791 21.0095,0.215 v -0.0166 c -7.00087,-0.027 -5.00512,-0.0235 -20.9931,-0.21495 -14.672201,-0.23233 -17.050805,-0.24643 -31.649455,-0.5788 -8.809302,-0.2005 -17.617016,-0.44151 -26.42526,-0.67797 z m 73.736733,2.37297 c -24.579267,0.0137 -49.159587,0.22442 -73.736733,0.51263 v 0.0744 c 26.354352,-0.38587 52.712933,-0.56345 79.068083,-0.45474 v -0.12401 c -1.77811,-0.001 -3.55494,-0.009 -5.33294,-0.008 z m 5.33294,1.62886 c -10.47248,0.0351 -20.94706,0.0914 -31.419273,0.19015 -22.163352,0.26088 -15.260373,0.13864 -37.380333,0.56224 -3.42265,0.0655 -6.846358,0.13642 -10.269008,0.20672 v 0.008 c 9.634008,-0.20024 19.263254,-0.4008 28.897791,-0.54571 6.243108,-0.0939 12.492567,-0.14147 18.735675,-0.21497 10.478028,-0.0841 20.957648,-0.14764 31.435148,-0.19018 z m -79.068085,3.20807 v 0.0331 c 17.804077,0.0946 35.61027,0.14695 53.414085,0.29766 4.52464,0.0315 9.05166,0.0755 13.5763,0.0992 3.29882,0.0173 7.68376,0.0338 12.07981,0.0413 v -0.13229 c -8.5217,0.0155 -17.04419,-0.0238 -25.56536,-0.0744 -17.834504,-0.12411 -35.668479,-0.17306 -53.504041,-0.26458 z m 0,0.32245 v 0.0248 c 0.900959,0.0318 1.802765,0.0678 2.703777,0.0992 11.693525,0.37451 23.384933,0.63518 35.081104,0.8351 -11.692202,-0.20029 -23.376731,-0.47845 -35.065229,-0.85161 -0.90678,-0.0342 -1.813428,-0.0728 -2.720181,-0.1075 z m 37.851291,0.95911 c 13.737164,0.23447 27.479624,0.38047 41.216794,0.49609 v -0.0579 c -13.73902,-0.0793 -27.47963,-0.20325 -41.216794,-0.43823 z m -37.851291,0.85Line truncated
|
||||
fill="#1a1a1a"
|
||||
opacity="0.2"
|
||||
stroke="#000000"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="bevel"
|
||||
stroke-width="0.145381"
|
||||
id="path178" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 46 KiB |
Reference in new issue
Block a user