From 09de0739a0ea4ee1ff6e022e2a2b9ad4378b10e6 Mon Sep 17 00:00:00 2001 From: Timur Olur Date: Wed, 13 Mar 2024 19:44:18 +0300 Subject: [PATCH] v0.2.0, fix for passphrase generation with rekey support for old databases. Fixed: Some generated database encryption keys fails due incorrect byte/character sequence. Fixed: is_db_created now also checks app db size, if it's 0 byte it's considered as not initialized. Added: Better file and console logging support --- CHANGELOG | 21 +++ docs/src/SUMMARY.md | 2 + docs/src/chad | 1 + docs/src/changelog.md | 3 + docs/src/changelogs/v0_2_0.md | 27 ++++ docs/src/installation.md | 8 +- gui/package.json | 6 +- gui/src/hooks.client.ts | 19 ++- gui/src/lib/logger.ts | 8 +- gui/src/routes/+layout.svelte | 1 + libs/keywich_api/src/api/configs.d.ts | 1 + libs/keywich_lib/Cargo.lock | 79 ++++------ libs/keywich_lib/src/errors.rs | 22 ++- libs/keywich_lib/src/profile.rs | 20 ++- package.json | 1 + pnpm-lock.yaml | 172 +++++++++++----------- tauri_app/Cargo.lock | 199 +++++++++++++++++--------- tauri_app/Cargo.toml | 10 +- tauri_app/src/commands/charsets.rs | 7 +- tauri_app/src/commands/keys.rs | 35 +++-- tauri_app/src/commands/login.rs | 148 +++++++++++++++---- tauri_app/src/commands/password.rs | 20 +-- tauri_app/src/commands/utilities.rs | 70 ++++++--- tauri_app/src/errors.rs | 46 +++--- tauri_app/src/icon_scheme.rs | 4 +- tauri_app/src/main.rs | 56 ++++++-- tauri_app/src/result_log.rs | 30 ++++ tauri_app/tauri.conf.json | 2 +- 28 files changed, 664 insertions(+), 354 deletions(-) create mode 100644 CHANGELOG create mode 100644 docs/src/chad create mode 100644 docs/src/changelog.md create mode 100644 docs/src/changelogs/v0_2_0.md create mode 100644 tauri_app/src/result_log.rs diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..9e1da7a --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,21 @@ +# v0.2.0 - March 14, 2024 +---------------- + +## Added + +- Persisted log files and stdout logging. +- GUI can now be started with different log levels (default is `WARN`) via `keywich gui --log-level `. + +## Fixes + +- **BREAKING** Some master passwords generate invalid database encryption keys. All app databases will be + automatically upgraded to the new passphrase generator. Note that downgrade will not possible after the upgrade. +- A zero-byte `app.db` file no longer breaks profile initialization. + +## Known issues + +- On Windows, the console is detached from GUI window, making the CLI unusable. +- On Linux, `webkit2gtk >= 2.42.x` when used with Nvidia proprietary drivers, may display an empty window + with the error message `KMS: DRM_IOCTL_MODE_CREATE_DUMB failed: Permission denied`. Current known solutions are: + - Set `WEBKIT_DISABLE_DMABUF_RENDERER=1` environment variable. + - Enable `nvidia.drm_modeset=1` kernel parameters. \ No newline at end of file diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 24048f2..8d618c8 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -10,3 +10,5 @@ - [Settings](./settings.md) - [Shortcuts](./shortcuts.md) - [CLI](./cli.md) +- [Changelog](./changelog.md) + - [v0.2.0](./changelogs/v0_2_0.md) diff --git a/docs/src/chad b/docs/src/chad new file mode 100644 index 0000000..825c32f --- /dev/null +++ b/docs/src/chad @@ -0,0 +1 @@ +# Changelog diff --git a/docs/src/changelog.md b/docs/src/changelog.md new file mode 100644 index 0000000..2e1a4bd --- /dev/null +++ b/docs/src/changelog.md @@ -0,0 +1,3 @@ +# Changelogs + +- [**v0.2.0** - *March 14, 2024*](./changelogs/v0_2_0.md) \ No newline at end of file diff --git a/docs/src/changelogs/v0_2_0.md b/docs/src/changelogs/v0_2_0.md new file mode 100644 index 0000000..18f01dd --- /dev/null +++ b/docs/src/changelogs/v0_2_0.md @@ -0,0 +1,27 @@ +# v0.2.0 +---------------- + +
+ +*March 14, 2024* + +
+ +## Added + +- Persisted log files and stdout logging. +- GUI can now be started with different log levels (default is `WARN`) via `keywich gui --log-level `. + +## Fixes + +- **`BREAKING`** Some master passwords generate invalid database encryption keys. All app databases will be + automatically upgraded to the new passphrase generator. Note that downgrade will not possible after the upgrade. +- A zero-byte `app.db` file no longer breaks profile initialization. + +## Known issues + +- On Windows, the console is detached from GUI window, making the CLI unusable. +- On Linux, `webkit2gtk >= 2.42.x` when used with Nvidia proprietary drivers, may display an empty window + with the error message `KMS: DRM_IOCTL_MODE_CREATE_DUMB failed: Permission denied`. Current known solutions are: + - Set `WEBKIT_DISABLE_DMABUF_RENDERER=1` environment variable. + - Enable `nvidia.drm_modeset=1` kernel parameters. \ No newline at end of file diff --git a/docs/src/installation.md b/docs/src/installation.md index 7a76625..ac0b7c8 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -1,10 +1,10 @@ # Installation -See [GitHub release page](https://github.com/SuperioOne/keywich/releases/tag/v0.1.0) for the all available binaries. +See [GitHub release page](https://github.com/SuperioOne/keywich/releases/tag/v0.2.0) for the all available binaries. ## Linux x86_64 (.deb) -1. Download [`.deb`](https://github.com/SuperioOne/keywich/releases/download/v0.1.0/keywich_0.1.0_amd64.deb) package +1. Download [`.deb`](https://github.com/SuperioOne/keywich/releases/download/v0.2.0/keywich_0.2.0_amd64.deb) package from GitHub release page. 2. Use `dpkg` to install the package. @@ -17,7 +17,7 @@ dpkg -i keywich_0.1.0_amd64.deb PKGBUILD is created but not added to AUR yet. -1. Download [`PKGBUILD`](https://github.com/SuperioOne/keywich/releases/download/v0.1.0/PKGBUILD) file +1. Download [`PKGBUILD`](https://github.com/SuperioOne/keywich/releases/download/v0.2.0/PKGBUILD) file from GitHub release page. 2. Use `makepkg` tool to install the package. @@ -28,7 +28,7 @@ makepkg --install ## Windows x86_64 -Download [`.msi`](https://github.com/SuperioOne/keywich/releases/download/v0.1.0/Keywich_0.1.0_x64_en-US.msi) installer +Download [`.msi`](https://github.com/SuperioOne/keywich/releases/download/v0.2.0/Keywich_0.2.0_x64_en-US.msi) installer package and execute it. ## From source diff --git a/gui/package.json b/gui/package.json index ec1f1b6..c499189 100644 --- a/gui/package.json +++ b/gui/package.json @@ -15,7 +15,7 @@ "@skeletonlabs/skeleton": "^2.9.0", "@skeletonlabs/tw-plugin": "^0.3.1", "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/kit": "^2.5.3", + "@sveltejs/kit": "^2.5.4", "@sveltejs/vite-plugin-svelte": "^3.0.2", "@tailwindcss/forms": "^0.5.7", "@types/eslint": "^8.56.5", @@ -27,12 +27,12 @@ "postcss": "^8.4.35", "postcss-load-config": "^5.0.3", "svelte": "^4.2.12", - "svelte-check": "^3.6.6", + "svelte-check": "^3.6.7", "svelte-preprocess": "^5.1.3", "tailwindcss": "^3.4.1", "tslib": "^2.6.2", "typescript": "^5.4.2", - "vite": "^5.1.5", + "vite": "^5.1.6", "vitest": "^1.3.1" }, "type": "module", diff --git a/gui/src/hooks.client.ts b/gui/src/hooks.client.ts index b954118..6c923c4 100644 --- a/gui/src/hooks.client.ts +++ b/gui/src/hooks.client.ts @@ -1,21 +1,18 @@ -import type {LogLevelType} from "$lib"; import { ApplicationSink, ConsoleSink, LoggerConfigurator, try_parse_log_level, LogLevel, configStore, i18nStore, RPC, AppEventBus, Log } from "$lib"; -import {env} from "$env/dynamic/public"; import {goto} from "$app/navigation"; -const LOG_LEVEL: LogLevelType = try_parse_log_level(env.PUBLIC_KW_LOG_LEVEL) ?? LogLevel.INFO; - -LoggerConfigurator([ - ConsoleSink(LOG_LEVEL), - ApplicationSink(LOG_LEVEL, 1000) -]); - RPC.load_configs().then(async (app_config) => { try { if (app_config.configs) { + const LOG_LEVEL = try_parse_log_level(app_config.log_level) ?? LogLevel.INFO; + LoggerConfigurator.setup([ + ConsoleSink(LOG_LEVEL), + ApplicationSink(LOG_LEVEL, 1000) + ]); + configStore.init(app_config.configs); if (app_config.configs?.locale && app_config.locale_keys) { @@ -25,9 +22,11 @@ RPC.load_configs().then(async (app_config) => { available_locales: app_config.available_locales }); } + } else { + console.error("Application config is empty."); } } catch (err) { - Log.error(err); + console.error(err); } await AppEventBus.addListener("unlock_required", async () => { diff --git a/gui/src/lib/logger.ts b/gui/src/lib/logger.ts index 26b337f..4d7bee2 100644 --- a/gui/src/lib/logger.ts +++ b/gui/src/lib/logger.ts @@ -13,9 +13,11 @@ const emitter = new LogEmitter(); * * @param sinks - The sinks to register with the logger. */ -export const LoggerConfigurator = (sinks: LoggerSink[]) => { - for (const sink of sinks) { - emitter.register(sink); +export const LoggerConfigurator = { + setup: (sinks: LoggerSink[]) => { + for (const sink of sinks) { + emitter.register(sink); + } } }; diff --git a/gui/src/routes/+layout.svelte b/gui/src/routes/+layout.svelte index 8bc3b4e..054aa20 100644 --- a/gui/src/routes/+layout.svelte +++ b/gui/src/routes/+layout.svelte @@ -13,6 +13,7 @@ storePopup.set({computePosition, autoUpdate, flip, shift, offset, arrow}); + {}} on:keydown={(event) => { diff --git a/libs/keywich_api/src/api/configs.d.ts b/libs/keywich_api/src/api/configs.d.ts index 080c5f9..5de8a20 100644 --- a/libs/keywich_api/src/api/configs.d.ts +++ b/libs/keywich_api/src/api/configs.d.ts @@ -9,6 +9,7 @@ export interface AppConfig { is_db_created: boolean; locale_keys?: Record; available_locales: string[]; + log_level: "ERROR" | "WARN" | "INFO" | "DEBUG" | "TRACE"; } export interface ConfigRPCApi { diff --git a/libs/keywich_lib/Cargo.lock b/libs/keywich_lib/Cargo.lock index eecada2..e99f109 100644 --- a/libs/keywich_lib/Cargo.lock +++ b/libs/keywich_lib/Cargo.lock @@ -54,16 +54,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atomic-write-file" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8204db279bf648d64fe845bd8840f78b39c8132ed4d6a4194c3b10d4b4cfb0b" -dependencies = [ - "nix", - "rand", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -129,9 +119,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -157,12 +147,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cipher" version = "0.4.4" @@ -698,18 +682,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags 2.4.2", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -955,9 +927,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -1245,9 +1217,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1258,9 +1230,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ "ahash", "atoi", @@ -1268,7 +1240,6 @@ dependencies = [ "bytes", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener", "futures-channel", @@ -1298,9 +1269,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" dependencies = [ "proc-macro2", "quote", @@ -1311,11 +1282,10 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ - "atomic-write-file", "dotenvy", "either", "heck", @@ -1337,9 +1307,9 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", "base64 0.21.7", @@ -1379,9 +1349,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", "base64 0.21.7", @@ -1406,7 +1376,6 @@ dependencies = [ "rand", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlx-core", @@ -1418,9 +1387,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" dependencies = [ "atoi", "flume", @@ -1492,18 +1461,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", @@ -1719,9 +1688,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "whoami" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ "redox_syscall", "wasite", diff --git a/libs/keywich_lib/src/errors.rs b/libs/keywich_lib/src/errors.rs index cdd62ff..ccce5d7 100644 --- a/libs/keywich_lib/src/errors.rs +++ b/libs/keywich_lib/src/errors.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::fmt::{Debug, Display, Formatter}; use std::path::PathBuf; #[derive(Debug)] @@ -16,6 +16,26 @@ pub enum Error { ValidationError(validator::ValidationErrors), } +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidHashOutput => write!(f, "Generated input output length is not valid."), + Error::ParserInvalidRange => write!(f, "Charset range is not valid."), + Error::InvalidInput => write!(f, "Hash input options are not valid."), + Error::DatabaseError(err) => write!(f, "Database action failed. Reason: {}", err), + Error::DatabaseMigrateError(err) => write!(f, "Database migration failed. Reason: {}", err), + Error::InvalidDatabasePath(path) => write!(f, "Database path {:?} is not accessible.", path), + Error::InvalidTime(err) => write!(f, "Unix timestamp input cannot be parsed. {}", err), + Error::InvalidHashFuncVersion => write!(f, "Unsupported hash function version received"), + Error::InvalidJsonError(err) => { + write!(f, "Password json serialization failed. Reason: {}", err) + } + Error::InvalidQrError(err) => write!(f, "Password qr generation failed. Reason: {}", err), + Error::ValidationError(err) => write!(f, "Input validation failed, {}", err), + } + } +} + impl From for Error { fn from(value: validator::ValidationErrors) -> Self { Self::ValidationError(value) diff --git a/libs/keywich_lib/src/profile.rs b/libs/keywich_lib/src/profile.rs index 916ea99..9705f0c 100644 --- a/libs/keywich_lib/src/profile.rs +++ b/libs/keywich_lib/src/profile.rs @@ -15,11 +15,13 @@ pub struct ProfileDB { pub enum SqlitePassphrase { Text(String), + #[deprecated] Hex(String), } pub struct ProfileDBSqliteOptions { pub password: Option, + pub new_password: Option, pub busy_timeout: Option, pub disable_migrate: bool, } @@ -30,6 +32,7 @@ impl Default for ProfileDBSqliteOptions { disable_migrate: false, busy_timeout: Some(Duration::from_secs(30)), password: None, + new_password: None, } } } @@ -47,15 +50,11 @@ impl ProfileDB { let ProfileDBSqliteOptions { busy_timeout, password, + new_password, disable_migrate, } = sqlite_options; let mut options = SqliteConnectOptions::from_str(connection_str)?; - options = options - .create_if_missing(true) - .foreign_keys(true) - .busy_timeout(busy_timeout.unwrap_or(Duration::from_secs(30))) - .read_only(false); options = match password { Some(SqlitePassphrase::Text(raw_text)) => options.pragma("key", raw_text), @@ -63,6 +62,17 @@ impl ProfileDB { None => options, }; + options = match new_password { + Some(SqlitePassphrase::Text(raw_text)) => options.pragma("rekey", raw_text), + _ => options, + }; + + options = options + .create_if_missing(true) + .foreign_keys(true) + .busy_timeout(busy_timeout.unwrap_or(Duration::from_secs(30))) + .read_only(false); + let pool = SqlitePool::connect_with(options).await?; if !disable_migrate { diff --git a/package.json b/package.json index fae9007..b0009f0 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "scripts": { "dev:desktop": "pnpm -C ./tauri_app run dev", "dev:gui": "pnpm -C ./gui run dev", + "dev:docs": "mdbook serve ./docs", "build:desktop": "pnpm -C ./tauri_app run build", "build:gui": "pnpm -C ./gui run build", "build:docs": "mdbook build ./docs" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c172534..433cb4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,13 +37,13 @@ importers: version: 0.3.1(tailwindcss@3.4.1) '@sveltejs/adapter-static': specifier: ^3.0.1 - version: 3.0.1(@sveltejs/kit@2.5.3) + version: 3.0.1(@sveltejs/kit@2.5.4) '@sveltejs/kit': - specifier: ^2.5.3 - version: 2.5.3(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.5) + specifier: ^2.5.4 + version: 2.5.4(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.6) '@sveltejs/vite-plugin-svelte': specifier: ^3.0.2 - version: 3.0.2(svelte@4.2.12)(vite@5.1.5) + version: 3.0.2(svelte@4.2.12)(vite@5.1.6) '@tailwindcss/forms': specifier: ^0.5.7 version: 0.5.7(tailwindcss@3.4.1) @@ -75,8 +75,8 @@ importers: specifier: ^4.2.12 version: 4.2.12 svelte-check: - specifier: ^3.6.6 - version: 3.6.6(postcss-load-config@5.0.3)(postcss@8.4.35)(svelte@4.2.12) + specifier: ^3.6.7 + version: 3.6.7(postcss-load-config@5.0.3)(postcss@8.4.35)(svelte@4.2.12) svelte-preprocess: specifier: ^5.1.3 version: 5.1.3(postcss-load-config@5.0.3)(postcss@8.4.35)(svelte@4.2.12)(typescript@5.4.2) @@ -90,8 +90,8 @@ importers: specifier: ^5.4.2 version: 5.4.2 vite: - specifier: ^5.1.5 - version: 5.1.5 + specifier: ^5.1.6 + version: 5.1.6 vitest: specifier: ^1.3.1 version: 1.3.1 @@ -503,104 +503,104 @@ packages: resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} dev: true - /@rollup/rollup-android-arm-eabi@4.12.1: - resolution: {integrity: sha512-iU2Sya8hNn1LhsYyf0N+L4Gf9Qc+9eBTJJJsaOGUp+7x4n2M9dxTt8UvhJl3oeftSjblSlpCfvjA/IfP3g5VjQ==} + /@rollup/rollup-android-arm-eabi@4.13.0: + resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.12.1: - resolution: {integrity: sha512-wlzcWiH2Ir7rdMELxFE5vuM7D6TsOcJ2Yw0c3vaBR3VOsJFVTx9xvwnAvhgU5Ii8Gd6+I11qNHwndDscIm0HXg==} + /@rollup/rollup-android-arm64@4.13.0: + resolution: {integrity: sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.12.1: - resolution: {integrity: sha512-YRXa1+aZIFN5BaImK+84B3uNK8C6+ynKLPgvn29X9s0LTVCByp54TB7tdSMHDR7GTV39bz1lOmlLDuedgTwwHg==} + /@rollup/rollup-darwin-arm64@4.13.0: + resolution: {integrity: sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.12.1: - resolution: {integrity: sha512-opjWJ4MevxeA8FhlngQWPBOvVWYNPFkq6/25rGgG+KOy0r8clYwL1CFd+PGwRqqMFVQ4/Qd3sQu5t7ucP7C/Uw==} + /@rollup/rollup-darwin-x64@4.13.0: + resolution: {integrity: sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.12.1: - resolution: {integrity: sha512-uBkwaI+gBUlIe+EfbNnY5xNyXuhZbDSx2nzzW8tRMjUmpScd6lCQYKY2V9BATHtv5Ef2OBq6SChEP8h+/cxifQ==} + /@rollup/rollup-linux-arm-gnueabihf@4.13.0: + resolution: {integrity: sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.12.1: - resolution: {integrity: sha512-0bK9aG1kIg0Su7OcFTlexkVeNZ5IzEsnz1ept87a0TUgZ6HplSgkJAnFpEVRW7GRcikT4GlPV0pbtVedOaXHQQ==} + /@rollup/rollup-linux-arm64-gnu@4.13.0: + resolution: {integrity: sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.12.1: - resolution: {integrity: sha512-qB6AFRXuP8bdkBI4D7UPUbE7OQf7u5OL+R94JE42Z2Qjmyj74FtDdLGeriRyBDhm4rQSvqAGCGC01b8Fu2LthQ==} + /@rollup/rollup-linux-arm64-musl@4.13.0: + resolution: {integrity: sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.12.1: - resolution: {integrity: sha512-sHig3LaGlpNgDj5o8uPEoGs98RII8HpNIqFtAI8/pYABO8i0nb1QzT0JDoXF/pxzqO+FkxvwkHZo9k0NJYDedg==} + /@rollup/rollup-linux-riscv64-gnu@4.13.0: + resolution: {integrity: sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.12.1: - resolution: {integrity: sha512-nD3YcUv6jBJbBNFvSbp0IV66+ba/1teuBcu+fBBPZ33sidxitc6ErhON3JNavaH8HlswhWMC3s5rgZpM4MtPqQ==} + /@rollup/rollup-linux-x64-gnu@4.13.0: + resolution: {integrity: sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.12.1: - resolution: {integrity: sha512-7/XVZqgBby2qp/cO0TQ8uJK+9xnSdJ9ct6gSDdEr4MfABrjTyrW6Bau7HQ73a2a5tPB7hno49A0y1jhWGDN9OQ==} + /@rollup/rollup-linux-x64-musl@4.13.0: + resolution: {integrity: sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.12.1: - resolution: {integrity: sha512-CYc64bnICG42UPL7TrhIwsJW4QcKkIt9gGlj21gq3VV0LL6XNb1yAdHVp1pIi9gkts9gGcT3OfUYHjGP7ETAiw==} + /@rollup/rollup-win32-arm64-msvc@4.13.0: + resolution: {integrity: sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.12.1: - resolution: {integrity: sha512-LN+vnlZ9g0qlHGlS920GR4zFCqAwbv2lULrR29yGaWP9u7wF5L7GqWu9Ah6/kFZPXPUkpdZwd//TNR+9XC9hvA==} + /@rollup/rollup-win32-ia32-msvc@4.13.0: + resolution: {integrity: sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.12.1: - resolution: {integrity: sha512-n+vkrSyphvmU0qkQ6QBNXCGr2mKjhP08mPRM/Xp5Ck2FV4NrHU+y6axzDeixUrCBHVUS51TZhjqrKBBsHLKb2Q==} + /@rollup/rollup-win32-x64-msvc@4.13.0: + resolution: {integrity: sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==} cpu: [x64] os: [win32] requiresBuild: true @@ -632,16 +632,16 @@ packages: resolution: {integrity: sha512-Eybgan7ymj5rgK/MrqlqYXTx99qnYc/MTb9RxKJ/vQNonENf594pAwrc1Ow+X3D43HpbKjl5Qv30VXE77Ipp6Q==} dev: false - /@sveltejs/adapter-static@3.0.1(@sveltejs/kit@2.5.3): + /@sveltejs/adapter-static@3.0.1(@sveltejs/kit@2.5.4): resolution: {integrity: sha512-6lMvf7xYEJ+oGeR5L8DFJJrowkefTK6ZgA4JiMqoClMkKq0s6yvsd3FZfCFvX1fQ0tpCD7fkuRVHsnUVgsHyNg==} peerDependencies: '@sveltejs/kit': ^2.0.0 dependencies: - '@sveltejs/kit': 2.5.3(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.5) + '@sveltejs/kit': 2.5.4(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.6) dev: true - /@sveltejs/kit@2.5.3(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.5): - resolution: {integrity: sha512-s6x7HBn/Fp+UNvyhJohjIA0FcJ+BWHGUDQ4PCg1D0EboUlvbimJQYchINu8G6sspLXYmlcsuNsp8bbcrRk85iw==} + /@sveltejs/kit@2.5.4(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.6): + resolution: {integrity: sha512-eDxK2d4EGzk99QsZNoPXe7jlzA5EGqfcCpUwZ912bhnalsZ2ZsG5wGRthkydupVjYyqdmzEanVKFhLxU2vkPSQ==} engines: {node: '>=18.13'} hasBin: true requiresBuild: true @@ -650,7 +650,7 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.3 dependencies: - '@sveltejs/vite-plugin-svelte': 3.0.2(svelte@4.2.12)(vite@5.1.5) + '@sveltejs/vite-plugin-svelte': 3.0.2(svelte@4.2.12)(vite@5.1.6) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 4.3.2 @@ -664,10 +664,10 @@ packages: sirv: 2.0.4 svelte: 4.2.12 tiny-glob: 0.2.9 - vite: 5.1.5 + vite: 5.1.6 dev: true - /@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.5): + /@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.6): resolution: {integrity: sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==} engines: {node: ^18.0.0 || >=20} peerDependencies: @@ -675,30 +675,30 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.0 dependencies: - '@sveltejs/vite-plugin-svelte': 3.0.2(svelte@4.2.12)(vite@5.1.5) + '@sveltejs/vite-plugin-svelte': 3.0.2(svelte@4.2.12)(vite@5.1.6) debug: 4.3.4 svelte: 4.2.12 - vite: 5.1.5 + vite: 5.1.6 transitivePeerDependencies: - supports-color dev: true - /@sveltejs/vite-plugin-svelte@3.0.2(svelte@4.2.12)(vite@5.1.5): + /@sveltejs/vite-plugin-svelte@3.0.2(svelte@4.2.12)(vite@5.1.6): resolution: {integrity: sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==} engines: {node: ^18.0.0 || >=20} peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.0 dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.0.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.5) + '@sveltejs/vite-plugin-svelte-inspector': 2.0.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.6) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.8 svelte: 4.2.12 svelte-hmr: 0.15.3(svelte@4.2.12) - vite: 5.1.5 - vitefu: 0.2.5(vite@5.1.5) + vite: 5.1.6 + vitefu: 0.2.5(vite@5.1.6) transitivePeerDependencies: - supports-color dev: true @@ -1124,7 +1124,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001596 + caniuse-lite: 1.0.30001597 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -1172,8 +1172,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001596 - electron-to-chromium: 1.4.699 + caniuse-lite: 1.0.30001597 + electron-to-chromium: 1.4.703 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) dev: true @@ -1197,8 +1197,8 @@ packages: engines: {node: '>= 6'} dev: true - /caniuse-lite@1.0.30001596: - resolution: {integrity: sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==} + /caniuse-lite@1.0.30001597: + resolution: {integrity: sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==} dev: true /chai@4.4.1: @@ -1374,8 +1374,8 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /electron-to-chromium@1.4.699: - resolution: {integrity: sha512-I7q3BbQi6e4tJJN5CRcyvxhK0iJb34TV8eJQcgh+fR2fQ8miMgZcEInckCo1U9exDHbfz7DLDnFn8oqH/VcRKw==} + /electron-to-chromium@1.4.703: + resolution: {integrity: sha512-094ZZC4nHXPKl/OwPinSMtLN9+hoFkdfQGKnvXbY+3WEAYtVDpz9UhJIViiY6Zb8agvqxiaJzNG9M+pRZWvSZw==} dev: true /emoji-regex@8.0.0: @@ -1460,7 +1460,7 @@ packages: postcss: 8.4.35 postcss-load-config: 3.1.4(postcss@8.4.35) postcss-safe-parser: 6.0.0(postcss@8.4.35) - postcss-selector-parser: 6.0.15 + postcss-selector-parser: 6.0.16 semver: 7.6.0 svelte: 4.2.12 svelte-eslint-parser: 0.33.1(svelte@4.2.12) @@ -1765,8 +1765,8 @@ packages: engines: {node: '>=8'} dev: true - /hasown@2.0.1: - resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 @@ -1825,7 +1825,7 @@ packages: /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: - hasown: 2.0.1 + hasown: 2.0.2 dev: true /is-extglob@2.1.1: @@ -2346,7 +2346,7 @@ packages: postcss: ^8.2.14 dependencies: postcss: 8.4.35 - postcss-selector-parser: 6.0.15 + postcss-selector-parser: 6.0.16 dev: true /postcss-safe-parser@6.0.0(postcss@8.4.35): @@ -2367,8 +2367,8 @@ packages: postcss: 8.4.35 dev: true - /postcss-selector-parser@6.0.15: - resolution: {integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==} + /postcss-selector-parser@6.0.16: + resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} engines: {node: '>=4'} dependencies: cssesc: 3.0.0 @@ -2461,26 +2461,26 @@ packages: glob: 7.2.3 dev: true - /rollup@4.12.1: - resolution: {integrity: sha512-ggqQKvx/PsB0FaWXhIvVkSWh7a/PCLQAsMjBc+nA2M8Rv2/HG0X6zvixAB7KyZBRtifBUhy5k8voQX/mRnABPg==} + /rollup@4.13.0: + resolution: {integrity: sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.12.1 - '@rollup/rollup-android-arm64': 4.12.1 - '@rollup/rollup-darwin-arm64': 4.12.1 - '@rollup/rollup-darwin-x64': 4.12.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.12.1 - '@rollup/rollup-linux-arm64-gnu': 4.12.1 - '@rollup/rollup-linux-arm64-musl': 4.12.1 - '@rollup/rollup-linux-riscv64-gnu': 4.12.1 - '@rollup/rollup-linux-x64-gnu': 4.12.1 - '@rollup/rollup-linux-x64-musl': 4.12.1 - '@rollup/rollup-win32-arm64-msvc': 4.12.1 - '@rollup/rollup-win32-ia32-msvc': 4.12.1 - '@rollup/rollup-win32-x64-msvc': 4.12.1 + '@rollup/rollup-android-arm-eabi': 4.13.0 + '@rollup/rollup-android-arm64': 4.13.0 + '@rollup/rollup-darwin-arm64': 4.13.0 + '@rollup/rollup-darwin-x64': 4.13.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.13.0 + '@rollup/rollup-linux-arm64-gnu': 4.13.0 + '@rollup/rollup-linux-arm64-musl': 4.13.0 + '@rollup/rollup-linux-riscv64-gnu': 4.13.0 + '@rollup/rollup-linux-x64-gnu': 4.13.0 + '@rollup/rollup-linux-x64-musl': 4.13.0 + '@rollup/rollup-win32-arm64-msvc': 4.13.0 + '@rollup/rollup-win32-ia32-msvc': 4.13.0 + '@rollup/rollup-win32-x64-msvc': 4.13.0 fsevents: 2.3.3 dev: true @@ -2657,8 +2657,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /svelte-check@3.6.6(postcss-load-config@5.0.3)(postcss@8.4.35)(svelte@4.2.12): - resolution: {integrity: sha512-b9q9rOHOMYF3U8XllK7LmXTq1LeWQ98waGfEJzrFutViadkNl1tgdEtxIQ8yuPx+VQ4l7YrknYol+0lfZocaZw==} + /svelte-check@3.6.7(postcss-load-config@5.0.3)(postcss@8.4.35)(svelte@4.2.12): + resolution: {integrity: sha512-tKEjemK9FYCySAseCaIt+ps5o0XRvLC7ECjyJXXtO7vOQhR9E6JavgoUbGP1PCulD2OTcB/fi9RjV3nyF1AROw==} hasBin: true peerDependencies: svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 @@ -2803,7 +2803,7 @@ packages: postcss-js: 4.0.1(postcss@8.4.35) postcss-load-config: 4.0.2(postcss@8.4.35) postcss-nested: 6.0.1(postcss@8.4.35) - postcss-selector-parser: 6.0.15 + postcss-selector-parser: 6.0.16 resolve: 1.22.8 sucrase: 3.35.0 transitivePeerDependencies: @@ -2934,7 +2934,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.1.5 + vite: 5.1.6 transitivePeerDependencies: - '@types/node' - less @@ -2946,8 +2946,8 @@ packages: - terser dev: true - /vite@5.1.5: - resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==} + /vite@5.1.6: + resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2976,12 +2976,12 @@ packages: dependencies: esbuild: 0.19.12 postcss: 8.4.35 - rollup: 4.12.1 + rollup: 4.13.0 optionalDependencies: fsevents: 2.3.3 dev: true - /vitefu@0.2.5(vite@5.1.5): + /vitefu@0.2.5(vite@5.1.6): resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} peerDependencies: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -2989,7 +2989,7 @@ packages: vite: optional: true dependencies: - vite: 5.1.5 + vite: 5.1.6 dev: true /vitest@1.3.1: @@ -3034,7 +3034,7 @@ packages: strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.2 - vite: 5.1.5 + vite: 5.1.6 vite-node: 1.3.1 why-is-node-running: 2.2.2 transitivePeerDependencies: diff --git a/tauri_app/Cargo.lock b/tauri_app/Cargo.lock index bfc220e..2ba5228 100644 --- a/tauri_app/Cargo.lock +++ b/tauri_app/Cargo.lock @@ -137,19 +137,21 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "app" -version = "0.1.0" +version = "0.2.0" dependencies = [ "clap", "const_format", - "image", + "image 0.25.0", "keyring", "keywich_lib", + "log", + "log4rs", "percent-encoding", "serde", "serde_json", @@ -166,7 +168,7 @@ checksum = "a2041f1943049c7978768d84e6d0fd95de98b76d6c4727b09e78ec253d29fa58" dependencies = [ "clipboard-win", "core-graphics 0.23.1", - "image", + "image 0.24.9", "log", "objc", "objc-foundation", @@ -178,6 +180,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" + [[package]] name = "async-broadcast" version = "0.5.1" @@ -249,9 +257,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" dependencies = [ "async-lock 3.3.0", "cfg-if", @@ -320,7 +328,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" dependencies = [ - "async-io 2.3.1", + "async-io 2.3.2", "async-lock 2.8.0", "atomic-waker", "cfg-if", @@ -394,16 +402,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atomic-write-file" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8204db279bf648d64fe845bd8840f78b39c8132ed4d6a4194c3b10d4b4cfb0b" -dependencies = [ - "nix 0.28.0", - "rand 0.8.5", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -556,9 +554,9 @@ checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -1200,7 +1198,7 @@ dependencies = [ "cc", "memchr", "rustc_version", - "toml 0.8.10", + "toml 0.8.11", "vswhom", "winreg", ] @@ -2110,13 +2108,36 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", - "jpeg-decoder", "num-traits", "png", - "rgb", "tiff", ] +[[package]] +name = "image" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b4f005360d32e9325029b38ba47ebd7a56f3316df09249368939562d518645" +dependencies = [ + "bytemuck", + "byteorder", + "image-webp", + "num-traits", + "png", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6107a25f04af48ceeb4093eebc9b405ee5a1813a0bab5ecf1805d3eabb3337" +dependencies = [ + "byteorder", + "thiserror", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2412,6 +2433,33 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0816135ae15bd0391cf284eab37e6e3ee0a6ee63d2ceeb659862bd8d0a984ca6" +dependencies = [ + "anyhow", + "arc-swap", + "chrono", + "derivative", + "fnv", + "libc", + "log", + "log-mdc", + "once_cell", + "parking_lot", + "thiserror", + "thread-id", + "winapi", +] + [[package]] name = "loom" version = "0.5.6" @@ -3276,9 +3324,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -3289,7 +3337,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "166f136dfdb199f98186f3649cf7a0536534a61417a1a30221b492b4fb60ce3f" dependencies = [ - "image", + "image 0.24.9", ] [[package]] @@ -3485,15 +3533,6 @@ dependencies = [ "windows 0.37.0", ] -[[package]] -name = "rgb" -version = "0.8.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" -dependencies = [ - "bytemuck", -] - [[package]] name = "rsa" version = "0.9.6" @@ -3740,9 +3779,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" dependencies = [ "base64 0.21.7", "chrono", @@ -3758,9 +3797,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" dependencies = [ "darling", "proc-macro2", @@ -3963,9 +4002,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ "sqlx-core", "sqlx-macros", @@ -3976,9 +4015,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ "ahash", "atoi", @@ -3986,7 +4025,6 @@ dependencies = [ "bytes", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener 2.5.3", "futures-channel", @@ -4016,9 +4054,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" dependencies = [ "proc-macro2", "quote", @@ -4029,11 +4067,10 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ - "atomic-write-file", "dotenvy", "either", "heck 0.4.1", @@ -4055,9 +4092,9 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", "base64 0.21.7", @@ -4097,9 +4134,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", "base64 0.21.7", @@ -4124,7 +4161,6 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlx-core", @@ -4136,9 +4172,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" dependencies = [ "atoi", "flume", @@ -4277,7 +4313,7 @@ dependencies = [ "cfg-expr 0.15.7", "heck 0.4.1", "pkg-config", - "toml 0.8.10", + "toml 0.8.11", "version-compare 0.1.1", ] @@ -4304,7 +4340,7 @@ dependencies = [ "glib", "glib-sys", "gtk", - "image", + "image 0.24.9", "instant", "jni", "lazy_static", @@ -4574,24 +4610,34 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", "syn 2.0.52", ] +[[package]] +name = "thread-id" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -4709,14 +4755,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.6", + "toml_edit 0.22.7", ] [[package]] @@ -4743,9 +4789,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" dependencies = [ "indexmap 2.2.5", "serde", @@ -5303,9 +5349,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "whoami" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ "redox_syscall", "wasite", @@ -5857,6 +5903,21 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "3.15.2" diff --git a/tauri_app/Cargo.toml b/tauri_app/Cargo.toml index c820118..9b523b0 100644 --- a/tauri_app/Cargo.toml +++ b/tauri_app/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "app" -version = "0.1.0" +version = "0.2.0" description = "A Tauri App" authors = ["Timur Olur"] license = "" @@ -17,19 +17,21 @@ strip = true opt-level = 3 [build-dependencies] -tauri-build = { version = "1.2.1", features = [] } +tauri-build = { version = "1.5.1", features = [] } [dependencies] -tauri = { version = "1.2.4", features = ["fs-remove-file", "protocol-asset", "clipboard-write-text", "fs-write-file", "dialog-save", "dialog-open", "fs-exists", "fs-read-file"] } +tauri = { version = "1.6.1", features = ["fs-remove-file", "protocol-asset", "clipboard-write-text", "fs-write-file", "dialog-save", "dialog-open", "fs-exists", "fs-read-file"] } keywich_lib = { version = "0.1.0", path = "../libs/keywich_lib", features = ["profile"] } clap = { version = "4.3.23", features = ["derive"] } serde = { version = "1.0.196", features = ["derive"] } uuid = { version = "1.7.0", features = ["v7"] } -image = { version = "0.24.8", features = ["jpeg", "png", "webp", "rgb"], default-features = false } +image = { version = "0.25.0", features = ["jpeg", "png", "webp"], default-features = false } percent-encoding = "2.3.1" const_format = "0.2.32" keyring = { features = ["platform-freebsd", "platform-linux", "platform-windows", "platform-macos", "platform-openbsd"] } serde_json = { version = "1.0.114" } +log = { version = "0.4.21" } +log4rs = { version = "1.3.0", features = ["console_appender", "file_appender"], default-features = false } [features] # by default Tauri runs in production mode diff --git a/tauri_app/src/commands/charsets.rs b/tauri_app/src/commands/charsets.rs index a3099a1..aeca9a6 100644 --- a/tauri_app/src/commands/charsets.rs +++ b/tauri_app/src/commands/charsets.rs @@ -3,6 +3,7 @@ use crate::{AppDbState, DbNotifier}; use keywich_lib::profile::charsets::CharsetItem; use std::ops::Deref; use tauri::{AppHandle, State}; +use crate::result_log::ResultLog; #[tauri::command(rename_all = "snake_case")] pub async fn get_charsets( @@ -12,7 +13,7 @@ pub async fn get_charsets( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - let result = profile_db.get_charsets().await?; + let result = profile_db.get_charsets().await.log_err()?; Ok(result) } else { let _ = app.emit_unlock_required(); @@ -29,7 +30,7 @@ pub async fn insert_charset( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - let result = profile_db.insert_charset(charset).await?; + let result = profile_db.insert_charset(charset).await.log_err()?; Ok(result) } else { let _ = app.emit_unlock_required(); @@ -46,7 +47,7 @@ pub async fn delete_charset( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - profile_db.delete_charset(&name).await?; + profile_db.delete_charset(&name).await.log_err()?; Ok(()) } else { let _ = app.emit_unlock_required(); diff --git a/tauri_app/src/commands/keys.rs b/tauri_app/src/commands/keys.rs index b58b230..04794be 100644 --- a/tauri_app/src/commands/keys.rs +++ b/tauri_app/src/commands/keys.rs @@ -1,6 +1,7 @@ use crate::errors::AppErrors; +use crate::result_log::ResultLog; use crate::{AppDbState, DbNotifier}; -use keywich_lib::profile::keys::{KeyData, KeyItem, SearchQuery}; +use keywich_lib::profile::keys::{KeyData, KeyItem}; use std::ops::Deref; use tauri::{AppHandle, State}; @@ -12,7 +13,7 @@ pub async fn get_keys( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - let keys = profile_db.get_keys(false).await?; + let keys = profile_db.get_keys(false).await.log_err()?; Ok(keys) } else { let _ = app.emit_unlock_required(); @@ -28,7 +29,7 @@ pub async fn get_pinned_keys( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - let keys = profile_db.get_keys(true).await?; + let keys = profile_db.get_keys(true).await.log_err()?; Ok(keys) } else { let _ = app.emit_unlock_required(); @@ -45,7 +46,7 @@ pub async fn search_keys( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - let keys = profile_db.search_keys(query.into()).await?; + let keys = profile_db.search_keys(query.into()).await.log_err()?; Ok(keys) } else { let _ = app.emit_unlock_required(); @@ -62,11 +63,11 @@ pub async fn delete_key( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - if let Some(key_data) = profile_db.get_key_by_id(key_id).await? { + if let Some(key_data) = profile_db.get_key_by_id(key_id).await.log_err()? { if let Some(icon_name) = key_data.custom_icon { - delete_icon(&app, &icon_name)?; + delete_icon(&app, &icon_name).log_err()?; } - profile_db.delete_key(key_id).await?; + profile_db.delete_key(key_id).await.log_err()?; Ok(()) } else { Err(AppErrors::KeyNotFound) @@ -86,7 +87,7 @@ pub async fn insert_key( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - let result = profile_db.insert_key(data).await?; + let result = profile_db.insert_key(data).await.log_err()?; Ok(result) } else { let _ = app.emit_unlock_required(); @@ -104,14 +105,14 @@ pub async fn update_key( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - if let Some(key_data) = profile_db.get_key_by_id(key_id).await? { + if let Some(key_data) = profile_db.get_key_by_id(key_id).await.log_err()? { match &key_data.custom_icon { ic @ Some(icon_name) if ic.ne(&data.custom_icon) => { - delete_icon(&app, &icon_name)?; + delete_icon(&app, icon_name).log_err()?; } _ => {} } - profile_db.update_key(key_id, data).await?; + profile_db.update_key(key_id, data).await.log_err()?; Ok(()) } else { Err(AppErrors::KeyNotFound) @@ -131,7 +132,7 @@ pub async fn pin_key( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - profile_db.update_pin_status(key_id, true).await?; + profile_db.update_pin_status(key_id, true).await.log_err()?; Ok(()) } else { let _ = app.emit_unlock_required(); @@ -148,7 +149,10 @@ pub async fn unpin_key( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - profile_db.update_pin_status(key_id, false).await?; + profile_db + .update_pin_status(key_id, false) + .await + .log_err()?; Ok(()) } else { let _ = app.emit_unlock_required(); @@ -165,7 +169,7 @@ pub async fn get_key_by_id( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - if let Some(key) = profile_db.get_key_by_id(key_id).await? { + if let Some(key) = profile_db.get_key_by_id(key_id).await.log_err()? { Ok(key) } else { Err(AppErrors::KeyNotFound) @@ -180,7 +184,8 @@ fn delete_icon(handle: &AppHandle, icon_name: &str) -> Result<(), AppErrors> { let mut dest_path = handle .path_resolver() .app_local_data_dir() - .ok_or(AppErrors::LocalDataDirNotFound)?; + .ok_or(AppErrors::LocalDataDirNotFound) + .log_err()?; dest_path.push("contents"); dest_path.push(icon_name); diff --git a/tauri_app/src/commands/login.rs b/tauri_app/src/commands/login.rs index 3b4af3e..044914e 100644 --- a/tauri_app/src/commands/login.rs +++ b/tauri_app/src/commands/login.rs @@ -1,7 +1,10 @@ use crate::errors::AppErrors; +use crate::result_log::ResultLog; use crate::{AppDbState, DbNotifier, KeyState}; +use keywich_lib::charset::Charset; use keywich_lib::profile::{ProfileDB, ProfileDBSqliteOptions, SqlitePassphrase}; use keywich_lib::scrypt::{scrypt, Params}; +use log::{debug, error, info, warn}; use std::path::Path; use tauri::{AppHandle, State}; @@ -14,24 +17,39 @@ pub async fn unlock_db( app: AppHandle, master_pass: String, ) -> Result<(), AppErrors> { - key_state.entry.set_password(&master_pass)?; + key_state.entry.set_password(&master_pass).log_err()?; let local_data_dir = &app .path_resolver() .app_local_data_dir() - .ok_or(AppErrors::LocalDataDirNotFound)?; + .ok_or(AppErrors::LocalDataDirNotFound) + .log_err()?; + + let db_path = Path::join(local_data_dir, APP_DB_NAME); + let path_str = db_path + .to_str() + .ok_or(AppErrors::DbNotInitialized) + .log_err()?; - let db_path = Path::join(&local_data_dir, APP_DB_NAME); - let path_str = db_path.to_str().ok_or(AppErrors::DbNotInitialized)?; let connection_string = format!("sqlite:{}", path_str); - let passphrase = generate_hex_phrase(master_pass.as_bytes())?; - let options = ProfileDBSqliteOptions { - password: Some(SqlitePassphrase::Hex(passphrase)), - disable_migrate: false, - busy_timeout: None, + let connection = match login(&connection_string, &master_pass).await { + Ok(profile_db) => { + info!("Connected with v0.2.0 passphrase generator."); + profile_db + } + Err(e) => { + warn!("Connected with v0.2.0 failed trying with v0.1.0"); + debug!("{}", e); + let db = login_v0_1_0(&connection_string, &master_pass) + .await + .log_err()?; + + warn!("Connection with v0.1.0 succeed and passphrase migrated to v0.2.0"); + + db + } }; - let connection = ProfileDB::connect_with(&connection_string, options).await?; let mut db_connection = state.profile_db.write().await; *db_connection = Some(connection); @@ -52,6 +70,47 @@ pub async fn lock_db( Ok(()) } +async fn login(conn_str: &str, master_pass: &str) -> Result { + let passphrase = generate_phrase(master_pass.as_bytes()).log_err()?; + let options = ProfileDBSqliteOptions { + password: Some(SqlitePassphrase::Text(passphrase)), + new_password: None, + disable_migrate: false, + busy_timeout: None, + }; + let connection = ProfileDB::connect_with(conn_str, options).await.log_err()?; + + Ok(connection) +} + +/// DEPRECATED - Tries to unlock app db with old passphrase generator and migrates to the newer one. +/// # Arguments +/// +/// * `conn_str`: Database connection string +/// * `master_pass`: Master password +/// +/// returns: Result +#[deprecated] +async fn login_v0_1_0(conn_str: &str, master_pass: &str) -> Result { + let passphrase = generate_hex_phrase(master_pass.as_bytes()).log_err()?; + let new_passphrase = generate_phrase(master_pass.as_bytes()).log_err()?; + let options = ProfileDBSqliteOptions { + password: Some(SqlitePassphrase::Hex(passphrase)), + new_password: Some(SqlitePassphrase::Text(new_passphrase)), + disable_migrate: false, + busy_timeout: None, + }; + let connection = ProfileDB::connect_with(conn_str, options).await.log_err()?; + + Ok(connection) +} + +const PHRASE_SIZE: usize = 32usize; +const PHRASE_LOG_N: u8 = 10; +const PHRASE_R: u32 = 8; +const PHRASE_P: u32 = 2; +const PHRASE_S: &[u8] = b"12345"; + macro_rules! to_hex_char { ($val:expr) => {{ let val: u8 = $val; @@ -64,14 +123,32 @@ macro_rules! to_hex_char { }}; } -const PHRASE_SIZE: usize = 32usize; -const PHRASE_LOG_N: u8 = 10; -const PHRASE_R: u32 = 8; -const PHRASE_P: u32 = 2; -const PHRASE_S: &[u8] = b"12345"; +macro_rules! hex_string { + ($content:expr) => {{ + let input: &[u8] = $content; + let mut buffer: Vec = vec![0u8; input.len() * 2]; + let mut idx: usize = 0; + + unsafe { + for c in input.iter() { + let rh = to_hex_char!(*c & 0b0000_1111); + let lh = to_hex_char!((*c >> 4) & 0b0000_1111); + *buffer.get_unchecked_mut(idx) = lh; + idx += 1; + *buffer.get_unchecked_mut(idx) = rh; + idx += 1; + } + + String::from_utf8_unchecked(buffer) + } + }}; +} + +#[deprecated] fn generate_hex_phrase(master_pass: &[u8]) -> Result { - let params = Params::new(PHRASE_LOG_N, PHRASE_R, PHRASE_P, PHRASE_SIZE).map_err(|e| { + let params = Params::new(PHRASE_LOG_N, PHRASE_R, PHRASE_P, 32).map_err(|e| { + error!("Passphrase parameter error {}", e); AppErrors::LibError(String::from( "Passphrase generation parameters are invalid.", )) @@ -79,22 +156,31 @@ fn generate_hex_phrase(master_pass: &[u8]) -> Result { let mut buffer: Vec = vec![0u8; PHRASE_SIZE]; - scrypt(&master_pass, PHRASE_S, ¶ms, &mut buffer) - .map_err(|_| AppErrors::LibError(String::from("Cannot create passphrase for sqlite.")))?; + scrypt(master_pass, PHRASE_S, ¶ms, &mut buffer).map_err(|e| { + error!("Passphrase generator error {}", e); + AppErrors::LibError(String::from("Cannot create passphrase for sqlite.")) + })?; - let mut hex_buff = vec![0u8; PHRASE_SIZE * 2]; - let mut idx: usize = 0; - unsafe { - for c in buffer.iter() { - let rh = to_hex_char!(*c & 0b0000_1111); - let lh = to_hex_char!((*c >> 4) & 0b0000_1111); + Ok(hex_string!(&buffer)) +} - *hex_buff.get_unchecked_mut(idx) = lh; - idx += 1; - *hex_buff.get_unchecked_mut(idx) = rh; - idx += 1; - } +fn generate_phrase(master_pass: &[u8]) -> Result { + let params = Params::new(PHRASE_LOG_N, PHRASE_R, PHRASE_P, 64) + .map_err(|e| { + error!("Passphrase parameter error {}", e); + AppErrors::LibError(String::from( + "Passphrase generation parameters are invalid.", + )) + }) + .log_err()?; + + let charset = Charset::new("a..zA..Z0..9").log_err()?; + let mut buffer: Vec = vec![0u8; PHRASE_SIZE]; + + scrypt(master_pass, PHRASE_S, ¶ms, &mut buffer).map_err(|e| { + error!("Passphrase generator error {}", e); + AppErrors::LibError(String::from("Cannot create passphrase for sqlite.")) + })?; - Ok(String::from_utf8_unchecked(hex_buff)) - } + Ok(charset.transform_bytes(&buffer)) } diff --git a/tauri_app/src/commands/password.rs b/tauri_app/src/commands/password.rs index c41e220..5ebac96 100644 --- a/tauri_app/src/commands/password.rs +++ b/tauri_app/src/commands/password.rs @@ -1,4 +1,5 @@ use crate::errors::AppErrors; +use crate::result_log::ResultLog; use crate::{AppDbState, DbNotifier, KeyState, PasswordOutputType}; use keywich_lib::hash::HashAlgorithm; use serde::Deserialize; @@ -50,9 +51,10 @@ pub async fn generate_password_from( let read_lock = state.profile_db.read().await; if let Some(profile_db) = read_lock.deref() { - if let Some(key) = profile_db.get_key_by_id(profile_id).await? { - let target_len = - usize::try_from(key.target_size).map_err(|_err| AppErrors::InvalidTargetLength)?; + if let Some(key) = profile_db.get_key_by_id(profile_id).await.log_err()? { + let target_len = usize::try_from(key.target_size) + .map_err(|_err| AppErrors::InvalidTargetLength) + .log_err()?; let config = keywich_lib::PasswordConfig { password: &password, revision: key.revision, @@ -85,7 +87,9 @@ pub fn generate_password(request: PasswordGenerateRequest) -> Result Result { let hash_algo = match algo { None => HashAlgorithm::default(), - Some(algo_name) => HashAlgorithm::from_str(algo_name)?, + Some(algo_name) => HashAlgorithm::from_str(algo_name).log_err()?, }; - let pass_result = keywich_lib::generate_password(config, hash_algo)?; + let pass_result = keywich_lib::generate_password(config, hash_algo).log_err()?; let string_response = match output_type { PasswordOutputType::PHC => pass_result.to_phc(), PasswordOutputType::Text => pass_result.pass, PasswordOutputType::Base64 => pass_result.to_base64(), - PasswordOutputType::Json => pass_result.to_json()?, - PasswordOutputType::Qr => pass_result.to_qr()?, + PasswordOutputType::Json => pass_result.to_json().log_err()?, + PasswordOutputType::Qr => pass_result.to_qr().log_err()?, }; Ok(string_response) diff --git a/tauri_app/src/commands/utilities.rs b/tauri_app/src/commands/utilities.rs index 286b315..6fba3ba 100644 --- a/tauri_app/src/commands/utilities.rs +++ b/tauri_app/src/commands/utilities.rs @@ -1,11 +1,14 @@ use crate::errors::AppErrors; +use crate::result_log::ResultLog; +use crate::LogLevel; use image::imageops::FilterType; use image::ImageFormat; +use log::error; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; use std::path::Path; -use tauri::AppHandle; +use tauri::{AppHandle, State}; #[derive(Serialize, Deserialize)] pub struct ConfigFile { @@ -20,6 +23,7 @@ pub struct AppConfig { pub is_db_created: bool, pub locale_keys: Option>, pub available_locales: Vec, + pub log_level: String, } #[tauri::command(rename_all = "snake_case")] @@ -30,7 +34,10 @@ pub async fn get_config_path(handle: AppHandle) -> Result { .ok_or(AppErrors::LocalDataDirNotFound)?; let config_file = Path::join(&local_data_dir, "config.json"); - let path = config_file.to_str().ok_or(AppErrors::ConfigPathFailed)?; + let path = config_file + .to_str() + .ok_or(AppErrors::ConfigPathFailed) + .log_err()?; Ok(String::from(path)) } @@ -40,9 +47,13 @@ pub async fn get_locale_path(handle: AppHandle, locale: String) -> Result Result Result Result { let local_data_dir = handle .path_resolver() .app_local_data_dir() - .ok_or(AppErrors::LocalDataDirNotFound)?; + .ok_or(AppErrors::LocalDataDirNotFound) + .log_err()?; let mut temp_path = Path::join(&local_data_dir, "temp"); if let Ok(false) = temp_path.try_exists() { - fs::create_dir(&temp_path).map_err(|_err| AppErrors::TempFolderFailed)?; + fs::create_dir(&temp_path) + .map_err(|_err| AppErrors::TempFolderFailed) + .log_err()?; } let file_name = uuid::Uuid::now_v7().to_string(); temp_path.push(&file_name); - let path = temp_path.to_str().ok_or(AppErrors::TempFolderFailed)?; + let path = temp_path + .to_str() + .ok_or(AppErrors::TempFolderFailed) + .log_err()?; Ok(String::from(path)) } #[tauri::command(rename_all = "snake_case")] -pub async fn load_configs(app: AppHandle) -> Result { +pub async fn load_configs( + app: AppHandle, + log_level: State<'_, LogLevel>, +) -> Result { let local_data_dir = app .path_resolver() .app_local_data_dir() - .ok_or(AppErrors::LocalDataDirNotFound)?; + .ok_or(AppErrors::LocalDataDirNotFound) + .log_err()?; - let db_path = Path::join(&local_data_dir, crate::commands::login::APP_DB_NAME); + let db_path = Path::join(&local_data_dir, crate::commands::login::APP_DB_NAME).metadata(); let config_file = Path::join(&local_data_dir, "config.json"); let mut app_details = AppConfig { - is_db_created: db_path.is_file(), + is_db_created: db_path.is_ok_and(|metadata| metadata.is_file() && metadata.len() > 0), configs: None, locale_keys: None, // TODO: Convert this to proc macro and generate from locales folder automatically when there are more than 5 locale. For now it's unnecessary. available_locales: vec![String::from("en"), String::from("tr")], + log_level: log_level.level.to_string(), }; match read_json_file::(config_file) { Ok(config) => { app_details.configs = Some(config); } - Err(e) => { - eprintln!("Unable to read config"); + Err(_) => { + error!("Unable to read config."); } } @@ -140,8 +170,8 @@ pub async fn load_configs(app: AppHandle) -> Result { Ok(locale_map) => { app_details.locale_keys = Some(locale_map); } - Err(e) => { - eprintln!("Unable to read locales"); + Err(_) => { + error!("Unable to read locales."); } } } diff --git a/tauri_app/src/errors.rs b/tauri_app/src/errors.rs index bd373ae..3f264bf 100644 --- a/tauri_app/src/errors.rs +++ b/tauri_app/src/errors.rs @@ -19,9 +19,7 @@ pub enum AppErrors { ContentPathFailed, DbNotInitialized, IconReadFailed(String), - RwLockFailed, IconResizeFailed(String), - KeyringFailure(String), NoKeyEntry, DuplicateKeyEntry, @@ -42,7 +40,6 @@ impl AppErrors { AppErrors::KeyNotFound => 202, AppErrors::IconReadFailed(_) => 203, AppErrors::IconResizeFailed(_) => 204, - AppErrors::RwLockFailed => 205, AppErrors::DbNotInitialized => 206, AppErrors::KeyringFailure(_) => 207, AppErrors::NoKeyEntry => 208, @@ -106,29 +103,26 @@ impl From for AppErrors { impl Display for AppErrors { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let message = match self { - AppErrors::OutputError(_) => "OutputError", - AppErrors::InvalidTargetLength => "InvalidTargetLength", - AppErrors::InvalidCharset => "InvalidCharset", - AppErrors::LibError(_) => "LibError", - AppErrors::UnsupportedHashFunc => "UnsupportedHashFunc", - AppErrors::ValidationError(_) => "ValidationError", - AppErrors::KeyNotFound => "KeyNotFound", - AppErrors::LocalDataDirNotFound => "LocalDataDirNotFound", - AppErrors::TempFolderFailed => "TempFolderFailed", - AppErrors::ConfigPathFailed => "ConfigPathFailed", - AppErrors::LocalePathFailed => "LocalePathFailed", - AppErrors::ContentPathFailed => "ContentPathFailed", - AppErrors::IconReadFailed(_) => "IconReadFailed", - AppErrors::IconResizeFailed(_) => "IconResizeFailed", - AppErrors::DbNotInitialized => "DbNotInitialized", - AppErrors::RwLockFailed => "RwLockFailed", - AppErrors::KeyringFailure(_) => "KeyringFailure", - AppErrors::NoKeyEntry => "NoKeyEntry", - AppErrors::DuplicateKeyEntry => "DuplicateKeyEntry", - }; - - f.write_str(message) + match self { + AppErrors::OutputError(err) => write!(f, "Password output generation failed. {}", err), + AppErrors::InvalidTargetLength => write!(f, "Password target length is not valid."), + AppErrors::InvalidCharset => write!(f, "Provided charset syntax is not valid."), + AppErrors::LibError(err) => write!(f, "Unexpected error, {}", err), + AppErrors::UnsupportedHashFunc => write!(f, "Unsupported hash function received."), + AppErrors::ValidationError(err) => write!(f, "Input validation failed {}", err), + AppErrors::KeyNotFound => write!(f, "Requested key does not exists."), + AppErrors::LocalDataDirNotFound => write!(f, "Local data directory not found."), + AppErrors::TempFolderFailed => write!(f, "Unable to access app temp directory."), + AppErrors::ConfigPathFailed => write!(f, "Unable to access app config file."), + AppErrors::LocalePathFailed => write!(f, "Unable to access app locale directory."), + AppErrors::ContentPathFailed => write!(f, "Unable to access app content directory."), + AppErrors::IconReadFailed(err) => write!(f, "Cannot read key icon content, {}", err), + AppErrors::IconResizeFailed(err) => write!(f, "Icon resize operation failed, {}", err), + AppErrors::DbNotInitialized => write!(f, "Database is not initialized."), + AppErrors::KeyringFailure(err) => write!(f, "OS keyring failed, {}", err), + AppErrors::NoKeyEntry => write!(f, "No master key entry found."), + AppErrors::DuplicateKeyEntry => write!(f, "Duplicate master key entry detected."), + } } } diff --git a/tauri_app/src/icon_scheme.rs b/tauri_app/src/icon_scheme.rs index b3f3dfe..dc28930 100644 --- a/tauri_app/src/icon_scheme.rs +++ b/tauri_app/src/icon_scheme.rs @@ -5,9 +5,9 @@ use std::path::Path; use tauri::http::{Request, Response, ResponseBuilder}; use tauri::AppHandle; -pub(crate) const ICON_PROTOCOL: &'static str = "kwicon"; +pub(crate) const ICON_PROTOCOL: &str = "kwicon"; -const PREFIX: &'static str = concatcp!("", ICON_PROTOCOL, "://localhost/"); +const PREFIX: &str = concatcp!("", ICON_PROTOCOL, "://localhost/"); pub(crate) fn icon_protocol_handler( app: &AppHandle, diff --git a/tauri_app/src/main.rs b/tauri_app/src/main.rs index c610492..7023f1a 100644 --- a/tauri_app/src/main.rs +++ b/tauri_app/src/main.rs @@ -6,6 +6,7 @@ mod commands; mod errors; mod icon_scheme; +mod result_log; use crate::commands::password::generate; use crate::icon_scheme::{icon_protocol_handler, ICON_PROTOCOL}; @@ -13,6 +14,12 @@ use clap::{Parser, Subcommand, ValueEnum}; use keyring::Entry; use keywich_lib::profile::ProfileDB; use keywich_lib::PasswordConfig; +use log::{info, LevelFilter}; +use log4rs::append::console::ConsoleAppender; +use log4rs::append::file::FileAppender; +use log4rs::config::{Appender, Root}; +use log4rs::encode::pattern::PatternEncoder; +use log4rs::Config; use serde::Deserialize; use std::env::args; use std::fmt::Debug; @@ -58,10 +65,14 @@ enum KeywichCommand { revision: i64, }, /// Start GUI application - GUI, + Gui { + /// App log level + #[arg(long)] + log_level: Option, + }, } -const DEFAULT_CONFIG: &'static [u8] = br#"{ +const DEFAULT_CONFIG: &[u8] = br#"{ "is_light_theme": false, "color_theme": "crimson", "locale": "en" @@ -84,33 +95,59 @@ pub(crate) struct KeyState { entry: Arc, } +pub(crate) struct LogLevel { + level: Arc, +} + trait DbNotifier { fn emit_unlock_required(&self) -> Result<(), tauri::Error>; } impl DbNotifier for AppHandle { fn emit_unlock_required(&self) -> Result<(), tauri::Error> { - let _ = self.emit_all("unlock_required", ())?; + self.emit_all("unlock_required", ())?; Ok(()) } } fn main() { if args().len() == 1 { - start_gui() + start_gui(LevelFilter::Warn) } else { start_cli() } } -fn start_gui() { +fn start_gui(log_level: LevelFilter) { tauri::Builder::default() - .setup(|app| { + .setup(move |app| { + let log_dir = &app.path_resolver().app_log_dir().unwrap(); + let log_file_path = log_dir.join("app_log.log"); + let console_logger = ConsoleAppender::builder().build(); + let file_logger = FileAppender::builder() + .encoder(Box::new(PatternEncoder::new("{d} {l} {m}{n}"))) + .build(log_file_path) + .unwrap(); + + let config = Config::builder() + .appender(Appender::builder().build("stdout", Box::new(console_logger))) + .appender(Appender::builder().build("logfile", Box::new(file_logger))) + .build( + Root::builder() + .appender("stdout") + .appender("logfile") + .build(log_level), + ) + .unwrap(); + + log4rs::init_config(config).unwrap(); + let local_data_dir = &app.path_resolver().app_local_data_dir().unwrap(); let keyring_entry = Entry::new("keywich", "mdb")?; - let config_file = Path::join(&local_data_dir, "config.json"); + let config_file = Path::join(local_data_dir, "config.json"); if let Ok(false) = config_file.try_exists() { + info!("Creating default config file at {:?}", &config_file); let mut fs = std::fs::File::create(config_file).unwrap(); fs.write_all(DEFAULT_CONFIG).unwrap(); fs.flush().unwrap(); @@ -122,6 +159,9 @@ fn start_gui() { app.manage(KeyState { entry: Arc::from(keyring_entry), }); + app.manage(LogLevel { + level: Arc::from(log_level), + }); Ok(()) }) @@ -171,6 +211,6 @@ fn start_cli() { } } } - KeywichCommand::GUI => start_gui(), + KeywichCommand::Gui { log_level } => start_gui(log_level.unwrap_or(LevelFilter::Warn)), }; } diff --git a/tauri_app/src/result_log.rs b/tauri_app/src/result_log.rs new file mode 100644 index 0000000..a4f3d46 --- /dev/null +++ b/tauri_app/src/result_log.rs @@ -0,0 +1,30 @@ +use log::error; +use std::fmt::Display; + +pub(crate) trait ResultLog { + fn log_err(self) -> Self; +} + +impl ResultLog for Result +where + E: Display, +{ + /// Logs error with [log::error](https://docs.rs/log/latest/log/macro.error.html) when `Result` + /// is `Err(E)` and `E` implements `Display` trait. + /// + /// # Example + /// ```rust + /// some_func().log_err()?; + /// + /// if let Ok(result) = async_func().await.log_err() { + /// // Do other stuff... + /// } + /// ``` + fn log_err(self) -> Self { + if let Err(err) = &self { + error!("{}", err); + } + + self + } +} diff --git a/tauri_app/tauri.conf.json b/tauri_app/tauri.conf.json index 5d971ac..274483c 100644 --- a/tauri_app/tauri.conf.json +++ b/tauri_app/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "Keywich", - "version": "0.1.0" + "version": "0.2.0" }, "tauri": { "allowlist": {