From f8a4ec32774c9538121432b06289d0c5c72a42b3 Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:31:28 +0400 Subject: [PATCH] refactor: require auth for local api --- .vscode/settings.json | 1 + src-tauri/Cargo.lock | 914 +++++++++++++++++++++++++++- src-tauri/Cargo.toml | 5 + src-tauri/build.rs | 8 + src-tauri/capabilities/default.json | 3 +- src-tauri/src/api_server.rs | 194 +++++- src-tauri/src/lib.rs | 3 +- src-tauri/src/settings_manager.rs | 246 +++++++- src/components/settings-dialog.tsx | 77 ++- 9 files changed, 1412 insertions(+), 39 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1f17dae..5a9f887 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,6 +31,7 @@ "dataclasses", "datareporting", "datas", + "DBAPI", "dconf", "devedition", "distro", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4da3575..8d8e586 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -17,6 +17,22 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aes" version = "0.8.4" @@ -28,6 +44,32 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -52,6 +94,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -82,6 +130,30 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "ashpd" version = "0.9.2" @@ -359,6 +431,18 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -371,6 +455,21 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -386,6 +485,26 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq 0.3.1", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -395,6 +514,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -623,6 +751,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.41" @@ -646,6 +798,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -667,6 +820,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-random" version = "0.1.18" @@ -687,6 +846,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -759,6 +924,15 @@ dependencies = [ "libc", ] +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -813,6 +987,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -820,6 +1006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -860,6 +1047,42 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "darling" version = "0.20.11" @@ -895,6 +1118,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "dary_heap" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" + [[package]] name = "deadpool" version = "0.10.0" @@ -919,6 +1148,16 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" version = "0.4.0" @@ -971,6 +1210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -981,7 +1221,16 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", ] [[package]] @@ -990,7 +1239,28 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", ] [[package]] @@ -1001,10 +1271,21 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.2", "windows-sys 0.60.2", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -1077,6 +1358,8 @@ dependencies = [ name = "donutbrowser" version = "0.10.1" dependencies = [ + "aes-gcm", + "argon2", "async-trait", "axum", "base64 0.22.1", @@ -1109,6 +1392,7 @@ dependencies = [ "tauri-plugin-opener", "tauri-plugin-shell", "tauri-plugin-single-instance", + "tauri-plugin-stronghold", "tempfile", "tokio", "tower", @@ -1118,7 +1402,7 @@ dependencies = [ "windows 0.61.3", "winreg", "wiremock", - "zip", + "zip 4.3.0", ] [[package]] @@ -1163,6 +1447,62 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0017d969298eec91e3db7a2985a8cab4df6341d86e6f3a6f5878b13fb7846bc9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embed-resource" version = "3.0.5" @@ -1281,13 +1621,29 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "field-offset" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "memoffset", + "memoffset 0.9.1", "rustc_version", ] @@ -1599,6 +1955,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1619,8 +1976,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1635,6 +1994,16 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1737,6 +2106,17 @@ dependencies = [ "system-deps", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "gtk" version = "0.18.2" @@ -1819,6 +2199,10 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "hashbrown" @@ -1850,6 +2234,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2186,6 +2579,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ + "block-padding", "generic-array", ] @@ -2200,6 +2594,58 @@ dependencies = [ "libc", ] +[[package]] +name = "iota-crypto" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a38db844c910d78825e173c083f2ef416b69cb091bba8ac1055763c6db065b" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "autocfg", + "base64 0.21.7", + "blake2", + "chacha20poly1305", + "cipher", + "curve25519-dalek", + "digest", + "ed25519-zebra", + "generic-array", + "getrandom 0.2.16", + "hkdf", + "hmac", + "iterator-sorted", + "k256", + "pbkdf2", + "rand 0.8.5", + "scrypt", + "serde", + "sha2", + "tiny-keccak", + "unicode-normalization", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "iota_stronghold" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c0d301c7edbc31494d183b7d24c1bb51d3fb10fce2f3793df1baf45b6988e10" +dependencies = [ + "bincode", + "hkdf", + "iota-crypto", + "rust-argon2 1.0.0", + "serde", + "stronghold-derive", + "stronghold-utils", + "stronghold_engine", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -2235,6 +2681,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "iterator-sorted" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d101775d2bc8f99f4ac18bf29b9ed70c0dd138b9a1e88d7b80179470cbbe8bd2" + [[package]] name = "itoa" version = "1.0.15" @@ -2328,6 +2780,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + [[package]] name = "keyboard-types" version = "0.7.0" @@ -2393,6 +2858,30 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libflate" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" +dependencies = [ + "adler32", + "core2", + "crc32fast", + "dary_heap", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" +dependencies = [ + "core2", + "hashbrown 0.14.5", + "rle-decode-fast", +] + [[package]] name = "libloading" version = "0.7.4" @@ -2444,6 +2933,23 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "libsodium-sys-stable" +version = "1.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b023d38f2afdfe36f81e15a9d7232097701d7b107e3a93ba903083985e235239" +dependencies = [ + "cc", + "libc", + "libflate", + "minisign-verify", + "pkg-config", + "tar", + "ureq", + "vcpkg", + "zip 2.4.2", +] + [[package]] name = "libz-rs-sys" version = "0.5.1" @@ -2556,6 +3062,15 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -2581,6 +3096,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minisign-verify" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2700,6 +3221,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + [[package]] name = "nix" version = "0.27.1" @@ -2709,7 +3242,7 @@ dependencies = [ "bitflags 2.9.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.9.1", ] [[package]] @@ -3054,6 +3587,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "open" version = "5.3.2" @@ -3200,6 +3739,23 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.3" @@ -3379,6 +3935,16 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -3425,6 +3991,29 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -3678,6 +4267,17 @@ dependencies = [ "bitflags 2.9.2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -3781,6 +4381,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rfd" version = "0.15.0" @@ -3818,6 +4428,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + +[[package]] +name = "rust-argon2" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50162d19404029c1ceca6f6980fe40d45c8b369f6f44446fa14bb39573b5bb9" +dependencies = [ + "base64 0.13.1", + "blake2b_simd", + "constant_time_eq 0.1.5", + "crossbeam-utils", +] + +[[package]] +name = "rust-argon2" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d9848531d60c9cbbcf9d166c885316c24bc0e2a9d3eba0956bb6cbbd79bc6e8" +dependencies = [ + "base64 0.21.7", + "blake2b_simd", + "constant_time_eq 0.3.1", +] + [[package]] name = "rust-ini" version = "0.21.1" @@ -3902,6 +4541,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3983,6 +4631,31 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -4271,6 +4944,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -4359,6 +5042,16 @@ dependencies = [ "system-deps", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -4396,6 +5089,64 @@ dependencies = [ "quote", ] +[[package]] +name = "stronghold-derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2835db23c4724c05a2f85b81c4681f4aa8ea158edc8a7f4ad791c916fb766c2e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "stronghold-runtime" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18db7cc51450cefdab5f4990e128dd02c98da6d2992b93ffef8992ac0d2f3ddf" +dependencies = [ + "dirs 4.0.0", + "iota-crypto", + "libc", + "libsodium-sys-stable", + "log", + "nix 0.24.3", + "rand 0.8.5", + "serde", + "thiserror 1.0.69", + "windows 0.36.1", + "zeroize", +] + +[[package]] +name = "stronghold-utils" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8300214898af5e153e7f66e49dbd1c6a21585f2d592d9f24f58b969792475ed6" +dependencies = [ + "rand 0.8.5", + "stronghold-derive", +] + +[[package]] +name = "stronghold_engine" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd7371c42e557dd71a7f860bb2ec6b6fdb32f97a97987ccc2435fdd1f3a8615" +dependencies = [ + "anyhow", + "dirs-next", + "hex", + "iota-crypto", + "once_cell", + "paste", + "serde", + "stronghold-runtime", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "strsim" version = "0.11.1" @@ -4584,7 +5335,7 @@ checksum = "352a4bc7bf6c25f5624227e3641adf475a6535707451b09bb83271df8b7a6ac7" dependencies = [ "anyhow", "bytes", - "dirs", + "dirs 6.0.0", "dunce", "embed_plist", "getrandom 0.3.3", @@ -4634,7 +5385,7 @@ checksum = "182d688496c06bf08ea896459bf483eb29cdff35c1c4c115fb14053514303064" dependencies = [ "anyhow", "cargo_toml", - "dirs", + "dirs 6.0.0", "glob", "heck 0.5.0", "json-patch", @@ -4840,6 +5591,27 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-stronghold" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01f6138bd2c7613c946b75f8b9739d1e2486a3c68bbf4189ac7b175ee0c63abf" +dependencies = [ + "hex", + "iota-crypto", + "iota_stronghold", + "log", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "rust-argon2 2.1.0", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.15", + "zeroize", +] + [[package]] name = "tauri-runtime" version = "2.7.1" @@ -5051,6 +5823,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.47.1" @@ -5295,7 +6082,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" dependencies = [ "crossbeam-channel", - "dirs", + "dirs 6.0.0", "libappindicator", "muda", "objc2 0.6.2", @@ -5340,7 +6127,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ - "memoffset", + "memoffset 0.9.1", "tempfile", "winapi", ] @@ -5398,18 +6185,49 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell", + "url", +] + [[package]] name = "url" version = "2.5.4" @@ -5816,6 +6634,19 @@ dependencies = [ "windows-version", ] +[[package]] +name = "windows" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53b97a83176b369b0eb2fd8158d4ae215357d02df9d40c1e1bf1879c5482c80" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + [[package]] name = "windows" version = "0.58.0" @@ -6143,6 +6974,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -6167,6 +7004,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -6203,6 +7046,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -6227,6 +7076,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -6275,6 +7130,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -6428,6 +7289,17 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "xattr" version = "1.5.1" @@ -6495,7 +7367,7 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix", + "nix 0.27.1", "ordered-stream", "rand 0.8.5", "serde", @@ -6584,6 +7456,7 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ + "serde", "zeroize_derive", ] @@ -6631,6 +7504,23 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.10.0", + "memchr", + "thiserror 2.0.15", + "zopfli", +] + [[package]] name = "zip" version = "4.3.0" @@ -6640,7 +7530,7 @@ dependencies = [ "aes", "arbitrary", "bzip2", - "constant_time_eq", + "constant_time_eq 0.3.1", "crc32fast", "deflate64", "flate2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1f24fa4..a01bafc 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -29,6 +29,9 @@ tauri-plugin-shell = "2" tauri-plugin-deep-link = "2" tauri-plugin-dialog = "2" tauri-plugin-macos-permissions = "2" +tauri-plugin-stronghold = "2" + + directories = "6" reqwest = { version = "0.12", features = ["json", "stream"] } tokio = { version = "1", features = ["full", "sync"] } @@ -51,6 +54,8 @@ axum = "0.8.4" tower = "0.5" tower-http = { version = "0.6", features = ["cors"] } rand = "0.9.2" +argon2 = "0.5" +aes-gcm = "0.10" [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies] tauri-plugin-single-instance = { version = "2", features = ["deep-link"] } diff --git a/src-tauri/build.rs b/src-tauri/build.rs index cf5ffd0..c8e794d 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -26,5 +26,13 @@ fn main() { println!("cargo:rustc-env=BUILD_VERSION=dev-{version}"); } + // Inject vault password at build time + if let Ok(vault_password) = std::env::var("DONUT_BROWSER_VAULT_PASSWORD") { + println!("cargo:rustc-env=DONUT_BROWSER_VAULT_PASSWORD={vault_password}"); + } else { + // Use default password if environment variable is not set + println!("cargo:rustc-env=DONUT_BROWSER_VAULT_PASSWORD=donutbrowser-api-vault-password"); + } + tauri_build::build() } diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 2580b72..8a52ff4 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -29,6 +29,7 @@ "macos-permissions:allow-request-microphone-permission", "macos-permissions:allow-request-camera-permission", "macos-permissions:allow-check-microphone-permission", - "macos-permissions:allow-check-camera-permission" + "macos-permissions:allow-check-camera-permission", + "stronghold:default" ] } diff --git a/src-tauri/src/api_server.rs b/src-tauri/src/api_server.rs index cf3dd7a..db70447 100644 --- a/src-tauri/src/api_server.rs +++ b/src-tauri/src/api_server.rs @@ -3,14 +3,16 @@ use crate::profile::manager::ProfileManager; use crate::proxy_manager::PROXY_MANAGER; use crate::tag_manager::TAG_MANAGER; use axum::{ - extract::{Path, State}, - http::StatusCode, - response::Json, + extract::{Path, Query, State}, + http::{HeaderMap, StatusCode}, + middleware::{self, Next}, + response::{Json, Response}, routing::{delete, get, post, put}, Router, }; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::sync::Arc; use tauri::Emitter; use tokio::net::TcpListener; @@ -110,6 +112,19 @@ struct UpdateProxyRequest { proxy_settings: Option, } +#[derive(Debug, Deserialize)] +struct DownloadBrowserRequest { + browser: String, + version: String, +} + +#[derive(Debug, Serialize)] +struct DownloadBrowserResponse { + browser: String, + version: String, + status: String, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ToastPayload { pub message: String, @@ -118,6 +133,13 @@ pub struct ToastPayload { pub description: Option, } +#[derive(Debug, Serialize)] +struct RunProfileResponse { + profile_id: String, + remote_debugging_port: u16, + headless: bool, +} + pub struct ApiServer { port: Option, shutdown_tx: Option>, @@ -174,13 +196,14 @@ impl ApiServer { .map_err(|e| format!("Failed to get local address: {e}"))? .port(); - // Create router with CORS - let app = Router::new() + // Create router with CORS, authentication, and versioning + let v1_routes = Router::new() .route("/profiles", get(get_profiles)) .route("/profiles", post(create_profile)) .route("/profiles/{id}", get(get_profile)) .route("/profiles/{id}", put(update_profile)) .route("/profiles/{id}", delete(delete_profile)) + .route("/profiles/{id}/run", post(run_profile)) .route("/groups", get(get_groups).post(create_group)) .route( "/groups/{id}", @@ -192,6 +215,19 @@ impl ApiServer { "/proxies/{id}", get(get_proxy).put(update_proxy).delete(delete_proxy), ) + .route("/browsers/download", post(download_browser_api)) + .route("/browsers/{browser}/versions", get(get_browser_versions)) + .route( + "/browsers/{browser}/versions/{version}/downloaded", + get(check_browser_downloaded), + ) + .layer(middleware::from_fn_with_state( + state.clone(), + auth_middleware, + )); + + let app = Router::new() + .nest("/v1", v1_routes) .layer(CorsLayer::permissive()) .with_state(state); @@ -225,6 +261,41 @@ impl ApiServer { } } +// Authentication middleware +async fn auth_middleware( + State(state): State, + headers: HeaderMap, + request: axum::extract::Request, + next: Next, +) -> Result { + // Get the Authorization header + let auth_header = headers + .get("Authorization") + .and_then(|h| h.to_str().ok()) + .and_then(|h| h.strip_prefix("Bearer ")); + + let token = match auth_header { + Some(token) => token, + None => return Err(StatusCode::UNAUTHORIZED), + }; + + // Get the stored token + let settings_manager = crate::settings_manager::SettingsManager::instance(); + let stored_token = match settings_manager.get_api_token(&state.app_handle).await { + Ok(Some(stored_token)) => stored_token, + Ok(None) => return Err(StatusCode::UNAUTHORIZED), + Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR), + }; + + // Compare tokens + if token != stored_token { + return Err(StatusCode::UNAUTHORIZED); + } + + // Token is valid, continue with the request + Ok(next.run(request).await) +} + // Global API server instance lazy_static! { pub static ref API_SERVER: Arc> = Arc::new(Mutex::new(ApiServer::new())); @@ -283,7 +354,7 @@ async fn get_profiles() -> Result, StatusCode> { .and_then(|c| serde_json::to_value(c).ok()), group_id: profile.group_id.clone(), tags: profile.tags.clone(), - is_running: false, // For now, set to false - can add running status later + is_running: profile.process_id.is_some(), // Simple check based on process_id }) .collect(); @@ -320,7 +391,7 @@ async fn get_profile( .and_then(|c| serde_json::to_value(c).ok()), group_id: profile.group_id.clone(), tags: profile.tags.clone(), - is_running: false, // Simplified for now to avoid async complexity + is_running: profile.process_id.is_some(), // Simple check based on process_id }, })) } else { @@ -402,7 +473,7 @@ async fn create_profile( } async fn update_profile( - Path(name): Path, + Path(id): Path, State(state): State, Json(request): Json, ) -> Result, StatusCode> { @@ -411,7 +482,7 @@ async fn update_profile( // Update profile fields if let Some(new_name) = request.name { if profile_manager - .rename_profile(&state.app_handle, &name, &new_name) + .rename_profile(&state.app_handle, &id, &new_name) .is_err() { return Err(StatusCode::BAD_REQUEST); @@ -420,7 +491,7 @@ async fn update_profile( if let Some(version) = request.version { if profile_manager - .update_profile_version(&state.app_handle, &name, &version) + .update_profile_version(&state.app_handle, &id, &version) .is_err() { return Err(StatusCode::BAD_REQUEST); @@ -429,7 +500,7 @@ async fn update_profile( if let Some(proxy_id) = request.proxy_id { if profile_manager - .update_profile_proxy(state.app_handle.clone(), &name, Some(proxy_id)) + .update_profile_proxy(state.app_handle.clone(), &id, Some(proxy_id)) .await .is_err() { @@ -443,7 +514,7 @@ async fn update_profile( match config { Ok(config) => { if profile_manager - .update_camoufox_config(state.app_handle.clone(), &name, config) + .update_camoufox_config(state.app_handle.clone(), &id, config) .await .is_err() { @@ -456,7 +527,7 @@ async fn update_profile( if let Some(group_id) = request.group_id { if profile_manager - .assign_profiles_to_group(&state.app_handle, vec![name.clone()], Some(group_id)) + .assign_profiles_to_group(&state.app_handle, vec![id.clone()], Some(group_id)) .is_err() { return Err(StatusCode::BAD_REQUEST); @@ -465,7 +536,7 @@ async fn update_profile( if let Some(tags) = request.tags { if profile_manager - .update_profile_tags(&state.app_handle, &name, tags) + .update_profile_tags(&state.app_handle, &id, tags) .is_err() { return Err(StatusCode::BAD_REQUEST); @@ -480,7 +551,7 @@ async fn update_profile( } // Return updated profile - get_profile(Path(name), State(state)).await + get_profile(Path(id), State(state)).await } async fn delete_profile( @@ -710,3 +781,96 @@ async fn delete_proxy( Err(_) => Err(StatusCode::BAD_REQUEST), } } + +// API Handler - Run Profile with Remote Debugging +async fn run_profile( + Path(id): Path, + Query(params): Query>, + State(state): State, +) -> Result, StatusCode> { + let headless = params + .get("headless") + .and_then(|v| v.parse::().ok()) + .unwrap_or(false); + + let profile_manager = ProfileManager::instance(); + let profiles = profile_manager + .list_profiles() + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let profile = profiles + .iter() + .find(|p| p.id.to_string() == id) + .ok_or(StatusCode::NOT_FOUND)?; + + // Generate a random port for remote debugging + let remote_debugging_port = rand::random::().saturating_add(9000).max(9000); + + // Use the same launch method as the main app, but with remote debugging enabled + match crate::browser_runner::launch_browser_profile_with_debugging( + state.app_handle.clone(), + profile.clone(), + None, + Some(remote_debugging_port), + headless, + ) + .await + { + Ok(updated_profile) => Ok(Json(RunProfileResponse { + profile_id: updated_profile.id.to_string(), + remote_debugging_port, + headless, + })), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +// API Handler - Download Browser +async fn download_browser_api( + State(state): State, + Json(request): Json, +) -> Result, StatusCode> { + let browser_runner = crate::browser_runner::BrowserRunner::instance(); + + match browser_runner + .download_browser_impl( + state.app_handle.clone(), + request.browser.clone(), + request.version.clone(), + ) + .await + { + Ok(_) => Ok(Json(DownloadBrowserResponse { + browser: request.browser, + version: request.version, + status: "downloaded".to_string(), + })), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +// API Handler - Get Browser Versions +async fn get_browser_versions( + Path(browser): Path, + State(_state): State, +) -> Result>, StatusCode> { + let version_manager = crate::browser_version_manager::BrowserVersionManager::instance(); + + match version_manager + .fetch_browser_versions_with_count(&browser, false) + .await + { + Ok(result) => Ok(Json(result.versions)), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } +} + +// API Handler - Check if Browser is Downloaded +async fn check_browser_downloaded( + Path((browser, version)): Path<(String, String)>, + State(_state): State, +) -> Result, StatusCode> { + let browser_runner = crate::browser_runner::BrowserRunner::instance(); + let is_downloaded = browser_runner.is_browser_downloaded(&browser, &version); + Ok(Json(is_downloaded)) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index eea359f..315da4d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +use directories::BaseDirs; use std::env; use std::sync::Mutex; use tauri::{Emitter, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder}; @@ -573,7 +574,7 @@ pub fn run() { // Start API server if enabled in settings let app_handle_api = app.handle().clone(); tauri::async_runtime::spawn(async move { - match crate::settings_manager::get_app_settings().await { + match crate::settings_manager::get_app_settings(app_handle_api.clone()).await { Ok(settings) => { if settings.api_enabled { println!("API is enabled in settings, starting API server..."); diff --git a/src-tauri/src/settings_manager.rs b/src-tauri/src/settings_manager.rs index d55ff98..31af4eb 100644 --- a/src-tauri/src/settings_manager.rs +++ b/src-tauri/src/settings_manager.rs @@ -6,6 +6,12 @@ use std::path::PathBuf; use crate::api_client::ApiClient; use crate::version_updater; +use aes_gcm::{ + aead::{Aead, AeadCore, KeyInit, OsRng}, + Aes256Gcm, Key, Nonce +}; +use argon2::{Argon2, PasswordHasher, password_hash::SaltString}; + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TableSortingSettings { pub column: String, // Column to sort by: "name", "browser", "status" @@ -33,6 +39,8 @@ pub struct AppSettings { pub api_enabled: bool, #[serde(default = "default_api_port")] pub api_port: u16, + #[serde(default)] + pub api_token: Option, // Displayed token for user to copy } fn default_theme() -> String { @@ -51,6 +59,7 @@ impl Default for AppSettings { custom_theme: None, api_enabled: false, api_port: 10108, + api_token: None, } } } @@ -164,22 +173,244 @@ impl SettingsManager { // Always return false - we don't show settings on startup anymore Ok(false) } + + fn get_vault_password() -> String { + env!("DONUT_BROWSER_VAULT_PASSWORD").to_string() + } + + pub async fn generate_api_token( + &self, + app_handle: &tauri::AppHandle, + ) -> Result> { + // Generate a secure random token (base64 encoded for URL safety) + let token_bytes: [u8; 32] = { + use rand::RngCore; + let mut rng = rand::rng(); + let mut bytes = [0u8; 32]; + rng.fill_bytes(&mut bytes); + bytes + }; + use base64::{engine::general_purpose, Engine as _}; + let token = general_purpose::URL_SAFE_NO_PAD.encode(token_bytes); + + // Store token securely + self.store_api_token(app_handle, &token).await?; + + Ok(token) + } + + pub async fn store_api_token( + &self, + _app_handle: &tauri::AppHandle, + token: &str, + ) -> Result<(), Box> { + // Store token in an encrypted file using Argon2 + AES-GCM + let token_file = self.get_settings_dir().join("api_token.dat"); + + // Create directory if it doesn't exist + if let Some(parent) = token_file.parent() { + std::fs::create_dir_all(parent)?; + } + + let vault_password = Self::get_vault_password(); + + // Generate a random salt for Argon2 + let salt = SaltString::generate(&mut OsRng); + + // Use Argon2 to derive a 32-byte key from the vault password + let argon2 = Argon2::default(); + let password_hash = argon2.hash_password(vault_password.as_bytes(), &salt) + .map_err(|e| format!("Argon2 key derivation failed: {}", e))?; + let hash_value = password_hash.hash.unwrap(); + let hash_bytes = hash_value.as_bytes(); + + // Take first 32 bytes for AES-256 key + let key = Key::::from_slice(&hash_bytes[..32]); + let cipher = Aes256Gcm::new(key); + + // Generate a random nonce + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + + // Encrypt the token + let ciphertext = cipher.encrypt(&nonce, token.as_bytes()) + .map_err(|e| format!("Encryption failed: {}", e))?; + + // Create file data with header, salt, nonce, and encrypted data + let mut file_data = Vec::new(); + file_data.extend_from_slice(b"DBAPI"); // 5-byte header + file_data.push(2u8); // Version 2 (Argon2 + AES-GCM) + + // Store salt length and salt + let salt_str = salt.as_str(); + file_data.push(salt_str.len() as u8); + file_data.extend_from_slice(salt_str.as_bytes()); + + // Store nonce (12 bytes for AES-GCM) + file_data.extend_from_slice(&nonce); + + // Store ciphertext length and ciphertext + file_data.extend_from_slice(&(ciphertext.len() as u32).to_le_bytes()); + file_data.extend_from_slice(&ciphertext); + + std::fs::write(token_file, file_data)?; + Ok(()) + } + + pub async fn get_api_token( + &self, + _app_handle: &tauri::AppHandle, + ) -> Result, Box> { + let token_file = self.get_settings_dir().join("api_token.dat"); + + if !token_file.exists() { + return Ok(None); + } + + let file_data = std::fs::read(token_file)?; + + // Validate header + if file_data.len() < 6 || &file_data[0..5] != b"DBAPI" { + return Ok(None); + } + + let version = file_data[5]; + + // Only support Argon2 + AES-GCM (version 2) + if version != 2 { + return Ok(None); + } + + // Argon2 + AES-GCM decryption + let mut offset = 6; + + // Read salt + if offset >= file_data.len() { + return Ok(None); + } + let salt_len = file_data[offset] as usize; + offset += 1; + + if offset + salt_len > file_data.len() { + return Ok(None); + } + let salt_bytes = &file_data[offset..offset + salt_len]; + let salt_str = std::str::from_utf8(salt_bytes).map_err(|_| "Invalid salt encoding")?; + let salt = SaltString::from_b64(salt_str).map_err(|_| "Invalid salt format")?; + offset += salt_len; + + // Read nonce (12 bytes) + if offset + 12 > file_data.len() { + return Ok(None); + } + let nonce_bytes = &file_data[offset..offset + 12]; + let nonce = Nonce::from_slice(nonce_bytes); + offset += 12; + + // Read ciphertext + if offset + 4 > file_data.len() { + return Ok(None); + } + let ciphertext_len = u32::from_le_bytes([ + file_data[offset], file_data[offset + 1], file_data[offset + 2], file_data[offset + 3] + ]) as usize; + offset += 4; + + if offset + ciphertext_len > file_data.len() { + return Ok(None); + } + let ciphertext = &file_data[offset..offset + ciphertext_len]; + + // Derive key using Argon2 + let vault_password = Self::get_vault_password(); + let argon2 = Argon2::default(); + let password_hash = argon2.hash_password(vault_password.as_bytes(), &salt) + .map_err(|e| format!("Argon2 key derivation failed: {}", e))?; + let hash_value = password_hash.hash.unwrap(); + let hash_bytes = hash_value.as_bytes(); + + let key = Key::::from_slice(&hash_bytes[..32]); + let cipher = Aes256Gcm::new(key); + + // Decrypt the token + let plaintext = cipher.decrypt(nonce, ciphertext) + .map_err(|_| "Decryption failed")?; + + match String::from_utf8(plaintext) { + Ok(token) => Ok(Some(token)), + Err(_) => Ok(None), + } + } + + pub async fn remove_api_token( + &self, + _app_handle: &tauri::AppHandle, + ) -> Result<(), Box> { + let token_file = self.get_settings_dir().join("api_token.dat"); + + if token_file.exists() { + std::fs::remove_file(token_file)?; + } + + Ok(()) + } } #[tauri::command] -pub async fn get_app_settings() -> Result { +pub async fn get_app_settings(app_handle: tauri::AppHandle) -> Result { let manager = SettingsManager::instance(); - manager + let mut settings = manager .load_settings() - .map_err(|e| format!("Failed to load settings: {e}")) + .map_err(|e| format!("Failed to load settings: {e}"))?; + + // Always load token for display purposes if it exists + settings.api_token = manager + .get_api_token(&app_handle) + .await + .map_err(|e| format!("Failed to load API token: {e}"))?; + + Ok(settings) } #[tauri::command] -pub async fn save_app_settings(settings: AppSettings) -> Result<(), String> { +pub async fn save_app_settings( + app_handle: tauri::AppHandle, + mut settings: AppSettings, +) -> Result { let manager = SettingsManager::instance(); + + // If API is being enabled ensure token is stored in Stronghold. + if settings.api_enabled { + if let Some(ref token) = settings.api_token { + manager + .store_api_token(&app_handle, token) + .await + .map_err(|e| format!("Failed to store API token: {e}"))?; + } else { + let token = manager + .generate_api_token(&app_handle) + .await + .map_err(|e| format!("Failed to generate API token: {e}"))?; + settings.api_token = Some(token); + } + } + + // If API is being disabled, remove the token + if !settings.api_enabled { + manager + .remove_api_token(&app_handle) + .await + .map_err(|e| format!("Failed to remove API token: {e}"))?; + settings.api_token = None; + } + + // Do not persist api_token in settings file (kept in Stronghold). Save a copy without the token. + let mut persist_settings = settings.clone(); + persist_settings.api_token = None; manager - .save_settings(&settings) - .map_err(|e| format!("Failed to save settings: {e}")) + .save_settings(&persist_settings) + .map_err(|e| format!("Failed to save settings: {e}"))?; + + Ok(settings) } #[tauri::command] @@ -254,6 +485,8 @@ pub async fn clear_all_version_cache_and_refetch( Ok(()) } +// No standalone stronghold commands needed; token ops handled via settings commands + // Global singleton instance lazy_static::lazy_static! { static ref SETTINGS_MANAGER: SettingsManager = SettingsManager::new(); @@ -337,6 +570,7 @@ mod tests { custom_theme: None, api_enabled: false, api_port: 10108, + api_token: None, }; // Save settings diff --git a/src/components/settings-dialog.tsx b/src/components/settings-dialog.tsx index 93a4c8f..836745a 100644 --- a/src/components/settings-dialog.tsx +++ b/src/components/settings-dialog.tsx @@ -54,6 +54,7 @@ interface AppSettings { custom_theme?: Record; api_enabled: boolean; api_port: number; + api_token?: string; } interface CustomThemeState { @@ -81,6 +82,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) { custom_theme: undefined, api_enabled: false, api_port: 10108, + api_token: undefined, }); const [originalSettings, setOriginalSettings] = useState({ set_as_default_browser: false, @@ -88,6 +90,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) { custom_theme: undefined, api_enabled: false, api_port: 10108, + api_token: undefined, }); const [customThemeState, setCustomThemeState] = useState({ selectedThemeId: null, @@ -298,7 +301,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) { setIsSaving(true); try { // Update settings with current custom theme state - const settingsToSave = { + let settingsToSave: AppSettings = { ...settings, custom_theme: settings.theme === "custom" @@ -306,7 +309,12 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) { : settings.custom_theme, }; - await invoke("save_app_settings", { settings: settingsToSave }); + const savedSettings = await invoke("save_app_settings", { + settings: settingsToSave, + }); + // Update settings with any generated tokens + setSettings(savedSettings); + settingsToSave = savedSettings; setTheme(settings.theme === "custom" ? "dark" : settings.theme); // Apply or clear custom variables only on Save @@ -355,7 +363,12 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) { }); // Revert the API enabled setting if start failed settingsToSave.api_enabled = false; - await invoke("save_app_settings", { settings: settingsToSave }); + const revertedSettings = await invoke( + "save_app_settings", + { settings: settingsToSave }, + ); + setSettings(revertedSettings); + settingsToSave = revertedSettings; } } else if (!isApiEnabled && wasApiEnabled) { // Stop API server @@ -764,8 +777,34 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) { { + onCheckedChange={async (checked: boolean) => { updateSetting("api_enabled", checked); + try { + if (checked) { + // Ask backend to enable API and return settings with token (token stored in Stronghold) + const next = await invoke( + "save_app_settings", + { + settings: { ...settings, api_enabled: true }, + }, + ); + setSettings(next); + } else { + const next = await invoke( + "save_app_settings", + { + settings: { + ...settings, + api_enabled: false, + api_token: null, + }, + }, + ); + setSettings(next); + } + } catch (e) { + console.error("Failed to toggle API:", e); + } }} />
@@ -787,6 +826,36 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {

+ + {settings.api_enabled && settings.api_token && ( +
+ +
+ + { + navigator.clipboard.writeText(settings.api_token || ""); + showSuccessToast("API token copied to clipboard"); + }} + > + Copy + +
+

+ Include this token in the Authorization header as "Bearer{" "} + {settings.api_token}" for all API requests. +

+
+ )} {/* Advanced Section */}