From 4aa9a537f0c3c450f91fae3ec14ce21c1c0e5c07 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Mon, 29 Jun 2026 19:13:41 +0200 Subject: [PATCH] add login with oskeychain --- .claude/settings.local.json | 5 +- src-tauri/Cargo.lock | 826 ++++++++++++++++++++- src-tauri/Cargo.toml | 2 + src-tauri/src/git.rs | 160 +++- src-tauri/src/main.rs | 16 +- src/App.svelte | 140 +++- src/app.css | 13 + src/lib/components/CredentialDialog.svelte | 27 +- src/lib/credentials.ts | 57 ++ src/lib/git.ts | 22 + src/lib/types.ts | 6 + 11 files changed, 1234 insertions(+), 40 deletions(-) create mode 100644 src/lib/credentials.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 07e60a0..22f46cf 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -20,7 +20,10 @@ "Bash(grep -qE \"\\(Running \\\\`target|error\\\\[|panicked|terminated\\)\" /tmp/claude-1000/-mnt-data-Development-GitLite/71a8b49f-ac61-4ad9-aaba-dc0219044038/tasks/b7nyub68s.output)", "Bash(npm install *)", "Bash(cargo check *)", - "Bash(npx vite *)" + "Bash(npx vite *)", + "Bash(cargo tree *)", + "Bash(jobs)", + "Bash(npx svelte-check *)" ] } } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 106f85d..beb09b5 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8,6 +8,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -47,6 +58,148 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "atk" version = "0.18.2" @@ -133,6 +286,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.6.2" @@ -142,6 +304,19 @@ dependencies = [ "objc2", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "brotli" version = "8.0.4" @@ -266,6 +441,15 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.65" @@ -309,6 +493,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.45" @@ -321,6 +511,16 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "combine" version = "4.6.7" @@ -331,6 +531,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "cookie" version = "0.18.1" @@ -341,6 +550,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -364,7 +583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", "foreign-types", "libc", @@ -377,7 +596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -508,6 +727,24 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "dbus-secret-service" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +dependencies = [ + "aes", + "block-padding", + "cbc", + "dbus", + "fastrand", + "hkdf", + "num", + "once_cell", + "sha2", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -546,6 +783,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -701,6 +939,33 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -718,6 +983,37 @@ dependencies = [ "typeid", ] +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -839,6 +1135,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.32" @@ -1193,12 +1502,36 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html5ever" version = "0.38.0" @@ -1466,6 +1799,16 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1589,6 +1932,23 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "dbus-secret-service", + "log", + "secret-service", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zbus", + "zeroize", +] + [[package]] name = "libappindicator" version = "0.9.0" @@ -1647,6 +2007,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -1772,12 +2138,89 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2017,6 +2460,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "pango" version = "0.18.3" @@ -2042,6 +2495,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2130,6 +2589,17 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -2175,6 +2645,20 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -2190,6 +2674,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -2288,6 +2781,36 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -2436,6 +2959,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -2508,6 +3044,61 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "secret-service" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand", + "serde", + "sha2", + "zbus", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.36.1" @@ -2695,6 +3286,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2712,6 +3314,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -2800,6 +3412,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_cache" version = "0.9.0" @@ -2830,6 +3448,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -2903,7 +3527,7 @@ checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ "bitflags 2.13.0", "block2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dbus", @@ -3227,12 +3851,27 @@ dependencies = [ name = "tauri_git_lite" version = "0.1.0" dependencies = [ + "keyring", "serde", + "serde_json", "tauri", "tauri-build", "tauri-plugin-dialog", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "tendril" version = "0.5.0" @@ -3537,9 +4176,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -3589,6 +4240,17 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -4121,6 +4783,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -4463,6 +5134,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "yoke" version = "0.8.3" @@ -4486,6 +5167,88 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "zerofrom" version = "0.1.8" @@ -4507,6 +5270,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "zerotrie" version = "0.2.4" @@ -4545,3 +5328,40 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c7747ef..5e4358d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -8,8 +8,10 @@ build = "build.rs" [dependencies] serde = { version = "1", features = ["derive"] } +serde_json = "1" tauri = { version = "2", features = [] } tauri-plugin-dialog = "=2.7.0" +keyring = { version = "3", features = ["apple-native", "windows-native", "async-secret-service", "crypto-rust", "async-io"] } [build-dependencies] tauri-build = { version = "2", features = [] } diff --git a/src-tauri/src/git.rs b/src-tauri/src/git.rs index 356ccff..e764d09 100644 --- a/src-tauri/src/git.rs +++ b/src-tauri/src/git.rs @@ -1,4 +1,4 @@ -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, @@ -365,6 +365,142 @@ pub fn push( status_for_repo(&repo) } +// ── Credential storage (OS keychain) ──────────────────────────────────────── + +const CRED_SERVICE: &str = "tauri_git_lite"; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StoredCredential { + pub username: String, + pub password: String, + #[serde(default, rename = "expiresAt", skip_serializing_if = "Option::is_none")] + pub expires_at: Option, +} + +fn cred_entry(key: &str) -> Result { + let key = key.trim(); + if key.is_empty() { + return Err("Kein Schlüssel für die Zugangsdaten angegeben.".to_string()); + } + keyring::Entry::new(CRED_SERVICE, key) + .map_err(|err| format!("Schlüsselbund nicht verfügbar: {err}")) +} + +/// Returns the remote URL used for auth key derivation (upstream remote of the +/// current branch, falling back to `origin`, then the first configured remote). +#[tauri::command] +pub fn get_remote_url(path: String) -> Result, String> { + let repo = resolve_repo(&path)?; + let remote = upstream_remote_name(&repo).unwrap_or_else(|| "origin".to_string()); + + if let Some(url) = remote_url_for(&repo, &remote) { + return Ok(Some(url)); + } + // origin missing → try the first configured remote + if let Some(first) = first_remote_name(&repo) { + if first != remote { + if let Some(url) = remote_url_for(&repo, &first) { + return Ok(Some(url)); + } + } + } + Ok(None) +} + +fn remote_url_for(repo: &Path, remote: &str) -> Option { + let out = Command::new("git") + .arg("-C") + .arg(repo) + .args(["remote", "get-url", remote]) + .output() + .ok()?; + if !out.status.success() { + return None; + } + let url = String::from_utf8_lossy(&out.stdout).trim().to_string(); + if url.is_empty() { + None + } else { + Some(url) + } +} + +fn upstream_remote_name(repo: &Path) -> Option { + let branch = run_git(repo, ["rev-parse", "--abbrev-ref", "HEAD"]).ok()?; + let branch = String::from_utf8_lossy(&branch).trim().to_string(); + if branch.is_empty() || branch == "HEAD" { + return None; + } + let out = Command::new("git") + .arg("-C") + .arg(repo) + .args(["config", &format!("branch.{branch}.remote")]) + .output() + .ok()?; + if !out.status.success() { + return None; + } + let name = String::from_utf8_lossy(&out.stdout).trim().to_string(); + if name.is_empty() { + None + } else { + Some(name) + } +} + +fn first_remote_name(repo: &Path) -> Option { + let out = run_git(repo, ["remote"]).ok()?; + String::from_utf8_lossy(&out) + .lines() + .map(str::trim) + .find(|line| !line.is_empty()) + .map(str::to_string) +} + +#[tauri::command] +pub fn cred_load(key: String) -> Result, String> { + let entry = cred_entry(&key)?; + match entry.get_password() { + Ok(json) => { + let cred = serde_json::from_str::(&json) + .map_err(|err| format!("Gespeicherte Zugangsdaten unlesbar: {err}"))?; + Ok(Some(cred)) + } + Err(keyring::Error::NoEntry) => Ok(None), + Err(err) => Err(format!("Schlüsselbund-Zugriff fehlgeschlagen: {err}")), + } +} + +#[tauri::command] +pub fn cred_save( + key: String, + username: String, + password: String, + expires_at: Option, +) -> Result<(), String> { + let entry = cred_entry(&key)?; + let expires_at = expires_at.filter(|value| !value.trim().is_empty()); + let cred = StoredCredential { + username, + password, + expires_at, + }; + let json = serde_json::to_string(&cred) + .map_err(|err| format!("Zugangsdaten konnten nicht serialisiert werden: {err}"))?; + entry + .set_password(&json) + .map_err(|err| format!("Speichern im Schlüsselbund fehlgeschlagen: {err}")) +} + +#[tauri::command] +pub fn cred_delete(key: String) -> Result<(), String> { + let entry = cred_entry(&key)?; + match entry.delete_credential() { + Ok(()) | Err(keyring::Error::NoEntry) => Ok(()), + Err(err) => Err(format!("Löschen im Schlüsselbund fehlgeschlagen: {err}")), + } +} + #[tauri::command] pub fn merge_branch(path: String, branch: String) -> Result { let repo = resolve_repo(&path)?; @@ -1824,9 +1960,31 @@ where } else { "Unbekannter Fehler".to_string() }; + if is_auth_error(&details) { + return Err(format!("AUTH_FAILED:{details}")); + } Err(format!("Git-Befehl fehlgeschlagen: {details}")) } +/// Heuristic: did git fail because the credentials were rejected/expired, +/// as opposed to a network or merge error? Used by the frontend to drop the +/// stored credential and re-prompt the login. +fn is_auth_error(details: &str) -> bool { + let d = details.to_lowercase(); + d.contains("authentication failed") + || d.contains("could not read username") + || d.contains("could not read password") + || d.contains("invalid username or password") + || d.contains("terminal prompts disabled") + || d.contains("permission denied") + || d.contains("access denied") + || d.contains("403 forbidden") + || d.contains(" 403") + || d.contains(" 401") + || d.contains("authorization failed") + || d.contains("authentication required") +} + fn run_git_with_paths( repo: &Path, base_args: &[&str], diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c5e5f62..5ef6de6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,11 +4,11 @@ mod git; use git::{ cancel_code_search, checkout_branch, commit, compare_commits, compare_file_to_head, - diff_file_against_working_tree, get_status, list_branches, list_commits, list_file_history, - list_repository_files, merge_branch, open_repository, pull, push, read_conflict, - resolve_conflict, resolve_conflict_side, restore_file_from_commit, restore_files, - restore_to_commit, search_code_introductions, stage_files, unstage_files, - SearchCancellationState, + cred_delete, cred_load, cred_save, diff_file_against_working_tree, get_remote_url, get_status, + list_branches, list_commits, list_file_history, list_repository_files, merge_branch, + open_repository, pull, push, read_conflict, resolve_conflict, resolve_conflict_side, + restore_file_from_commit, restore_files, restore_to_commit, search_code_introductions, + stage_files, unstage_files, SearchCancellationState, }; fn main() { @@ -39,7 +39,11 @@ fn main() { cancel_code_search, read_conflict, resolve_conflict, - resolve_conflict_side + resolve_conflict_side, + get_remote_url, + cred_load, + cred_save, + cred_delete ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/App.svelte b/src/App.svelte index a1ea7ca..dd90f4f 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -32,6 +32,10 @@ openRepository, pull, push, + getRemoteUrl, + credLoad, + credSave, + credDelete, readConflict, resolveConflict, resolveConflictSide, @@ -57,8 +61,16 @@ GitSearchHit, GitStatus, PreparedResolution, + StoredCredential, } from "./lib/types"; + import { + orgKeyFromUrl, + isCredentialExpired, + isAuthError, + stripAuthPrefix, + } from "./lib/credentials"; + // ── State ────────────────────────────────────────────────────────────────── let repoPath = ""; @@ -95,7 +107,7 @@ let credDialogOpen = false; let credDialogAction: "push" | "pull" | null = null; let credDialogError = ""; - let sessionCredentials: { username: string; password: string } | null = null; + let credDialogKey: string | null = null; let lastStatusFingerprint = ""; const AUTO_REFRESH_INTERVAL = 4000; let autoRefreshTimer: ReturnType | undefined; @@ -306,14 +318,69 @@ }); } - function openCredentialDialog(action: "push" | "pull") { + // Resolve the keychain key (host/org) for the active repo's remote. + async function currentCredKey(): Promise { + if (!activeRepoPath) return null; + try { + const url = await getRemoteUrl(activeRepoPath); + return url ? orgKeyFromUrl(url) : null; + } catch { + return null; + } + } + + async function loadStoredCredential(key: string | null): Promise { + if (!key) return null; + try { + return await credLoad(key); + } catch { + return null; + } + } + + async function openCredentialDialog(action: "push" | "pull", key?: string | null) { if (!activeRepoPath) return; credDialogError = ""; credDialogAction = action; + credDialogKey = key === undefined ? await currentCredKey() : key; credDialogOpen = true; } - async function doActualPull(username: string, password: string) { + // Post-process a pull/push result: surface errors, and on rejected/expired + // credentials drop the stored entry and re-open the login dialog. + function handleRemoteResult(action: "push" | "pull", key: string | null, fromStore: boolean) { + if (!errorMessage) { + credDialogOpen = false; + credDialogAction = null; + return; + } + const auth = isAuthError(errorMessage); + const message = stripAuthPrefix(errorMessage); + errorMessage = ""; + + if (fromStore) { + if (auth) { + if (key) void credDelete(key).catch(() => {}); + credDialogError = + "Zugangsdaten wurden abgelehnt oder sind abgelaufen. Bitte erneut anmelden."; + credDialogAction = action; + credDialogKey = key; + credDialogOpen = true; + } else { + // Non-auth failure (e.g. network) – keep the stored credential, show it inline. + errorMessage = message; + } + } else { + credDialogError = message || "Anmeldung fehlgeschlagen."; + } + } + + async function doActualPull( + username: string, + password: string, + key: string | null, + fromStore: boolean, + ) { errorMessage = ""; await runOperation("Pulling", async () => { applyStatus(await pull(activeRepoPath, username, password)); @@ -322,11 +389,15 @@ await refreshExplorerFiles(activeRepoPath); await refreshFileHistory(activeRepoPath); }); - if (errorMessage) { credDialogError = errorMessage; errorMessage = ""; } - else { credDialogOpen = false; credDialogAction = null; } + handleRemoteResult("pull", key, fromStore); } - async function doActualPush(username: string, password: string) { + async function doActualPush( + username: string, + password: string, + key: string | null, + fromStore: boolean, + ) { errorMessage = ""; await runOperation("Pushing", async () => { applyStatus(await push(activeRepoPath, username, password)); @@ -334,32 +405,51 @@ await refreshCommitHistory(activeRepoPath); await refreshFileHistory(activeRepoPath); }); - if (errorMessage) { credDialogError = errorMessage; errorMessage = ""; } - else { credDialogOpen = false; credDialogAction = null; } + handleRemoteResult("push", key, fromStore); } - async function handleCredentialSubmit(username: string, password: string, save: boolean) { - if (credDialogAction === "pull") await doActualPull(username, password); - else if (credDialogAction === "push") await doActualPush(username, password); - if (!credDialogOpen && save) sessionCredentials = { username, password }; + async function handleCredentialSubmit( + username: string, + password: string, + save: boolean, + expiresAt: string | null, + ) { + const key = credDialogKey; + if (credDialogAction === "pull") await doActualPull(username, password, key, false); + else if (credDialogAction === "push") await doActualPush(username, password, key, false); + + // Only persist once the operation actually succeeded (dialog has closed). + if (!credDialogOpen && save && key) { + try { + await credSave(key, username, password, expiresAt); + } catch (error) { + errorMessage = errorToMessage(error); + } + } + } + + async function startRemoteAction(action: "push" | "pull") { + if (!activeRepoPath) return; + const key = await currentCredKey(); + const stored = await loadStoredCredential(key); + + if (stored && !isCredentialExpired(stored)) { + if (action === "pull") await doActualPull(stored.username, stored.password, key, true); + else await doActualPush(stored.username, stored.password, key, true); + return; + } + + // Expired entry → clean it up before prompting again. + if (stored && key) await credDelete(key).catch(() => {}); + await openCredentialDialog(action, key); } async function pullRepo() { - if (!activeRepoPath) return; - if (sessionCredentials) { - await doActualPull(sessionCredentials.username, sessionCredentials.password); - } else { - openCredentialDialog("pull"); - } + await startRemoteAction("pull"); } async function pushRepo() { - if (!activeRepoPath) return; - if (sessionCredentials) { - await doActualPush(sessionCredentials.username, sessionCredentials.password); - } else { - openCredentialDialog("push"); - } + await startRemoteAction("push"); } // ── File staging / restore ───────────────────────────────────────────────── @@ -881,7 +971,7 @@ error={credDialogError} {isBusy} onSubmit={handleCredentialSubmit} - onCancel={() => { credDialogOpen = false; credDialogAction = null; credDialogError = ""; }} + onCancel={() => { credDialogOpen = false; credDialogAction = null; credDialogError = ""; credDialogKey = null; }} /> {/if} diff --git a/src/app.css b/src/app.css index b77c3d1..6b6d776 100644 --- a/src/app.css +++ b/src/app.css @@ -1359,6 +1359,19 @@ color: var(--color-accent); } + .cred-expiry { display: flex; flex-direction: column; gap: 5px; } + .cred-expiry input[type="date"] { + height: 40px; + padding: 0 11px; + border-radius: 9px; + border: 1px solid rgba(65,209,255,0.22); + background: rgba(7, 8, 16, 0.7); + color: var(--color-ink); + font-size: 13.5px; + color-scheme: dark; + } + .cred-expiry-hint { font-size: 11.5px; color: var(--color-ink-faint); line-height: 1.5; } + .cred-error { display: flex; align-items: flex-start; diff --git a/src/lib/components/CredentialDialog.svelte b/src/lib/components/CredentialDialog.svelte index 452f136..fec7a3e 100644 --- a/src/lib/components/CredentialDialog.svelte +++ b/src/lib/components/CredentialDialog.svelte @@ -17,7 +17,7 @@ action: "push" | "pull"; error: string; isBusy: boolean; - onSubmit: (username: string, password: string, save: boolean) => void; + onSubmit: (username: string, password: string, save: boolean, expiresAt: string | null) => void; onCancel: () => void; } @@ -36,6 +36,7 @@ let password = $state(""); let showPassword = $state(false); let saveSession = $state(false); + let expiresAt = $state(""); let canSubmit = $derived( !isBusy && @@ -51,7 +52,12 @@ function handleSubmit(e: SubmitEvent) { e.preventDefault(); if (!canSubmit) return; - onSubmit(mode === "token" ? "oauth2" : username, password, saveSession); + onSubmit( + mode === "token" ? "oauth2" : username, + password, + saveSession, + saveSession && expiresAt ? expiresAt : null, + ); } @@ -83,7 +89,7 @@
@@ -176,10 +182,23 @@ {/if} + {#if saveSession} +
+ + + Nach diesem Datum wird automatisch erneut nach dem Login gefragt. +
+ {/if} +