diff --git a/README.md b/README.md index c73d84f..b1303c3 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,55 @@ Yes, another rewrite was needed. Again. ## TODO -- Server - - Auth -- Client - - Overview - - Graph - - Calendar - - Todo +- EASY: Add option to pin/unpin notes +- EASY: Improve mobile experience + - Instead of dialogs, use drawers as these are easier to use on mobile. + - Add more quick actions to the mobile bar, maybe context dependant. + +- Overview page + - Show some stats about the "vault" + - Show upcoming tasks, once we have those + - Show upcoming events, maybe a small calendar with a list under it? +- Ctrl+k navigation/search menu +- Support images in the text note type +- Change styling in text note type + - No header image by default. + - Allow for image in header instead of gradient. +- Make term links work correctly, instead of directing to an empty page +- More text note type features + - Code blocks + - Lists + - Latex + - Modify todo item state by clicking on the indicator. +- Better text formatting in text note type + - Allow nested formatting, such as bold and italic, to be created in any order instead of only in outer-to-inner order. + - Allow combinations of formatted text with other text types, such as bold links. + - Don't apply formatting in certain parts/destroy formatting in some cases, such as inside the URL part of links, or terms. +- Move notes between collections + - What happens to properties set on notes when this is done? Do we try to migrate them and delete them otherwise, or do we keep them as is, but just ignore them unless moved back. +- Note links +- Graph page +- Calendar page + - STRETCH: Pull ICal subscriptions into calendar data. + - STRETCH: Caldav/calendar support. Might be easier to write a small android wrapper app that is able to sync and pull/push the data into the metadata structure. +- Todo page + - Gather note todos into the metadata while editing + - Gather todos from the metadata of each note, and render them in a nice list. + - STRETCH: Allow editing of the todo state from this list. This is hard, since the client might not have the full document. To achieve this todo's might + need to be fully stored in the metadata with a reference to them in the editor text. Might require a sub editor or something like that. +- More collection views + - Ability to view type per collection. + - Maybe all options should be available and this should just be a default state? + - Table like view + - Allows for quick viewing editing of properties of all notes. + - Detailed card view with certain properties shown on each note card +- STRETCH: Contacts support. Same notes as caldav support. + + +## Known bugs +- Offline edits in the canvas (excalidraw) note type cause all state to be lost. This might be even more severe, not requiring offline edits. + + +## Ideas +- Maybe rename Todos to Tasks, since they also contain doing, done, deadline, and idea types. Plus it's just a nicer name. + diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..0ae4e89 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,14 @@ +# Obtained from your auth provider +AUTH_ISSUER_URL=https://auth.example.com +AUTH_CLIENT_ID=client_id +AUTH_CLIENT_SECRET=client_secret + +# Can be generated with `openssl rand -hex 64` +AUTH_SECRET=someSecretValue + +# Where the app data will be stored +DATA_DIR=/data/docs +FRONTEND_DIR=/app/frontend + +# The URL of the website, used for redirect URL during auth +APP_URL=https://example.com diff --git a/backend/.gitignore b/backend/.gitignore index 5d1f033..d516497 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,3 @@ target/ data/ +.env diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 5d42001..b0d385d 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "arc-swap" version = "1.7.1" @@ -56,15 +71,39 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" + [[package]] name = "bindgen" version = "0.69.5" @@ -74,13 +113,13 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "itertools", + "itertools 0.12.1", "lazy_static", "lazycell", "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn", ] @@ -154,6 +193,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[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.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -165,6 +225,28 @@ dependencies = [ "libloading", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -174,6 +256,18 @@ dependencies = [ "libc", ] +[[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" @@ -184,12 +278,95 @@ dependencies = [ "typenum", ] +[[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", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "digest" version = "0.10.7" @@ -197,7 +374,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -211,12 +390,83 @@ dependencies = [ "syn", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[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 = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[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", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -238,9 +488,25 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] +[[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 = "fnv" version = "1.0.7" @@ -318,6 +584,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -329,7 +596,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", ] @@ -345,6 +626,17 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[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 = "h2" version = "0.3.26" @@ -357,13 +649,19 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.8.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.2" @@ -376,7 +674,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "headers-core", "http 0.2.12", @@ -394,6 +692,30 @@ dependencies = [ "http 0.2.12", ] +[[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 = "http" version = "0.2.12" @@ -427,6 +749,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.10.1" @@ -451,7 +796,7 @@ dependencies = [ "futures-util", "h2", "http 0.2.12", - "http-body", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -463,6 +808,85 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.3.1", + "hyper 1.6.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -581,6 +1005,12 @@ dependencies = [ "syn", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -602,6 +1032,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.8.0" @@ -609,7 +1050,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", ] [[package]] @@ -646,12 +1103,37 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + [[package]] name = "knotes-backend" version = "0.1.0" dependencies = [ + "base64 0.22.1", + "chrono", + "cookie", + "dotenvy", "futures-util", + "hmac", + "jwt", + "openidconnect", + "rand 0.9.0", "rocksdb", + "serde", + "sha2", "tokio", "warp", "yrs", @@ -665,6 +1147,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lazycell" @@ -685,9 +1170,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "librocksdb-sys" version = "0.16.0+8.10.0" @@ -791,7 +1282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -823,6 +1314,79 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64 0.22.1", + "chrono", + "getrandom 0.2.15", + "http 1.3.1", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + [[package]] name = "object" version = "0.36.7" @@ -838,6 +1402,70 @@ version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +[[package]] +name = "openidconnect" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd50d4a5e7730e754f94d977efe61f611aadd3131f6a2b464f6e3a4167e8ef7" +dependencies = [ + "base64 0.21.7", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac", + "http 1.3.1", + "itertools 0.10.5", + "log", + "oauth2", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde-value", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2", + "subtle", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -858,7 +1486,16 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", ] [[package]] @@ -899,12 +1536,39 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -914,6 +1578,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -923,6 +1596,60 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +dependencies = [ + "bytes", + "getrandom 0.3.2", + "rand 0.9.0", + "ring", + "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys", +] + [[package]] name = "quote" version = "1.0.40" @@ -932,6 +1659,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -939,8 +1672,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy", ] [[package]] @@ -950,7 +1694,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -959,7 +1713,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", ] [[package]] @@ -1000,6 +1763,73 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys", +] + [[package]] name = "rocksdb" version = "0.22.0" @@ -1010,6 +1840,26 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1022,6 +1872,70 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" version = "1.0.20" @@ -1040,6 +1954,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[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 = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" @@ -1049,6 +1983,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.219" @@ -1072,6 +2016,25 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1084,6 +2047,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.8.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1095,6 +2088,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1110,6 +2114,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 = "slab" version = "0.4.9" @@ -1150,12 +2164,34 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strsim" +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 = "syn" version = "2.0.100" @@ -1167,6 +2203,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -1184,7 +2229,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -1198,6 +2252,48 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -1208,6 +2304,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +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.44.1" @@ -1237,6 +2348,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.21.0" @@ -1262,6 +2383,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -1318,9 +2460,9 @@ dependencies = [ "http 1.3.1", "httparse", "log", - "rand", + "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -1343,6 +2485,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -1352,6 +2500,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -1404,7 +2553,7 @@ dependencies = [ "futures-util", "headers", "http 0.2.12", - "hyper", + "hyper 0.14.32", "log", "mime", "mime_guess", @@ -1428,6 +2577,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1436,6 +2594,7 @@ checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] @@ -1453,6 +2612,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -1485,13 +2657,86 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1500,14 +2745,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1516,48 +2777,105 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "write16" version = "1.0.0" @@ -1607,7 +2925,7 @@ dependencies = [ "serde_json", "smallstr", "smallvec", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1617,7 +2935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98d4ff429e242c38b6b31115f177b52f6f77a1c0cf553bd2e0bb7fbcc90e3429" dependencies = [ "smallvec", - "thiserror", + "thiserror 1.0.69", "yrs", ] @@ -1686,6 +3004,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 1fc788f..2aeaac3 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -4,8 +4,18 @@ version = "0.1.0" edition = "2021" [dependencies] +base64 = "0.22.1" +chrono = { version = "0.4.40", features = ["serde"] } +cookie = "0.18.1" +dotenvy = "0.15.7" futures-util = "0.3.31" +hmac = "0.12.1" +jwt = "0.16.0" +openidconnect = "4.0.0" +rand = "0.9.0" rocksdb = "0.22.0" +serde = { version = "1.0.219", features = ["derive"] } +sha2 = "0.10.8" tokio = { version = "1.44.1", features = ["full"] } warp = "0.3.7" yrs = { version = "0.19.2", features = ["sync"] } diff --git a/backend/src/main.rs b/backend/src/main.rs index 59e323e..fd42356 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,11 +1,37 @@ +use base64::prelude::*; +use chrono::serde::ts_seconds; +use chrono::{DateTime, Local, Utc}; +use cookie::time::{Duration, OffsetDateTime}; +use dotenvy::dotenv; use futures_util::StreamExt; +use hmac::{Hmac, Mac}; +use jwt::{SignWithKey, VerifyWithKey}; +use openidconnect::{ + core::{ + CoreAuthDisplay, CoreAuthPrompt, CoreClient, CoreErrorResponseType, CoreGenderClaim, + CoreIdTokenVerifier, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm, + CoreProviderMetadata, CoreResponseType, CoreRevocableToken, CoreRevocationErrorResponse, + CoreTokenIntrospectionResponse, CoreTokenResponse, + }, + reqwest, AuthenticationFlow, AuthorizationCode, Client, ClientId, ClientSecret, CsrfToken, + EmptyAdditionalClaims, EndpointMaybeSet, EndpointNotSet, EndpointSet, IssuerUrl, Nonce, + RedirectUrl, Scope, StandardErrorResponse, +}; +use rand::{distr::Alphanumeric, rng, Rng}; use rocksdb::TransactionDB; +use sha2::Sha256; use std::{ + char, collections::HashMap, + env, + path::PathBuf, + str::FromStr, sync::{Arc, Weak}, }; use tokio::sync::{Mutex, RwLock}; use warp::{ + http::{header, Response, StatusCode}, + reject::Reject, ws::{WebSocket, Ws}, Filter, Rejection, Reply, }; @@ -17,10 +43,6 @@ use yrs_warp::{ ws::{WarpSink, WarpStream}, }; -const DATA_DIR: &str = "data"; -const STATIC_DIR: &str = "../frontend/dist"; -const INDEX_HTML: &str = "../frontend/dist/index.html"; - // Web socket flow: // -> Handle connection (ws_handler) // -> Check permissions (Not doing for new) @@ -29,6 +51,49 @@ const INDEX_HTML: &str = "../frontend/dist/index.html"; // // +type OpenidClient = Client< + EmptyAdditionalClaims, + CoreAuthDisplay, + CoreGenderClaim, + CoreJweContentEncryptionAlgorithm, + CoreJsonWebKey, + CoreAuthPrompt, + StandardErrorResponse, + CoreTokenResponse, + CoreTokenIntrospectionResponse, + CoreRevocableToken, + CoreRevocationErrorResponse, + EndpointSet, + EndpointNotSet, + EndpointNotSet, + EndpointNotSet, + EndpointMaybeSet, + EndpointMaybeSet, +>; + +const COOKIE_AUTH_TOKEN: &str = "knotes_auth_token"; +const COOKIE_AUTH_NONCE: &str = "knotes_auth_nonce"; +const COOKIE_AUTH_CSRF: &str = "knotes_auth_csrf"; + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct UserToken { + display_name: String, + email: String, + + // Unused for now, might be checked later do allow some basic session management. + // Issued At Time + #[allow(dead_code)] + #[serde(with = "ts_seconds")] + iat: DateTime, + // JWT Id + #[allow(dead_code)] + jti: String, +} + +#[derive(Debug)] +struct AuthError; +impl Reject for AuthError {} + struct Connection { bcast: BroadcastGroup, // This is purely here to keep the connection to the DB alive while there is at least one @@ -41,19 +106,32 @@ struct Server { // clients disconnect, but for now we don't bother. pub open_docs: RwLock>>, pub db: Arc, + + pub http_client: reqwest::Client, + pub openid_client: OpenidClient, + + pub signing_key: Hmac, } impl Server { - pub fn new(db: TransactionDB) -> Self { + pub fn new( + db: TransactionDB, + http_client: reqwest::Client, + openid_client: OpenidClient, + signing_key: Hmac, + ) -> Self { Self { open_docs: RwLock::default(), db: Arc::new(db), + http_client, + openid_client, + signing_key, } } pub async fn get_or_create_doc(&self, name: String) -> Arc { let open_docs = self.open_docs.read().await; match open_docs.get(&name).and_then(Weak::upgrade) { - Some(group) => group.clone(), + Some(connection) => connection.clone(), None => { drop(open_docs); let mut open_docs = self.open_docs.write().await; @@ -97,41 +175,295 @@ impl Server { } } +async fn create_openid_client( + http_client: &reqwest::Client, + issuer_url: IssuerUrl, + client_id: ClientId, + client_secret: ClientSecret, + redirect_url: RedirectUrl, +) -> OpenidClient { + let provider_metadata = CoreProviderMetadata::discover_async(issuer_url, http_client) + .await + .expect("Failed to discover OpenID Connect provider metadata"); + + CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret)) + .set_redirect_uri(redirect_url) +} + #[tokio::main] async fn main() { - let db = TransactionDB::open_default(DATA_DIR).expect("Failed to open DB"); - let server = Arc::new(Server::new(db)); + dotenv().expect("Failed to load .env file"); - let ws = warp::path("sync") - .and(warp::path::param()) - .and(warp::ws()) - .and(warp::any().map(move || server.clone())) - .and_then(ws_handler); + let data_dir = env::var("DATA_DIR").expect("DATA_DIR not set"); + let data_dir = PathBuf::from_str(&data_dir).expect("DATA_DIR is not a valid path"); + let frontend_dir = env::var("FRONTEND_DIR").expect("FRONTEND_DIR not set"); + let frontend_dir = PathBuf::from_str(&frontend_dir).expect("FRONTEND_DIR is not a valid path"); + let index_file = frontend_dir.join("index.html"); - let static_files = warp::fs::dir(STATIC_DIR); + let signing_key = env::var("AUTH_SECRET").expect("AUTH_SECRET not set"); + let signing_key = + Hmac::new_from_slice(signing_key.as_bytes()).expect("AUTH_SECRET is not a valid HMAC key"); - let index = warp::fs::file(INDEX_HTML); + let app_url = env::var("APP_URL").expect("APP_URL not set"); - let routes = ws.or(static_files).or(index); + let http_client = reqwest::ClientBuilder::new() + // Following redirects opens the client up to SSRF vulnerabilities. + .redirect(reqwest::redirect::Policy::none()) + .build() + .expect("Failed to create HTTP client"); + + let openid_client = create_openid_client( + &http_client, + IssuerUrl::new(env::var("AUTH_ISSUER_URL").expect("AUTH_ISSUER_URL not set")) + .expect("Issuer URL invalid"), + ClientId::new(env::var("AUTH_CLIENT_ID").expect("AUTH_CLIENT_ID not set")), + ClientSecret::new(env::var("AUTH_CLIENT_SECRET").expect("AUTH_CLIENT_SECRET not set")), + RedirectUrl::new(format!("{}/api/auth/callback", app_url)) + .expect("Redirect URL is invalid (app url?)"), + ) + .await; + + let db = TransactionDB::open_default(&data_dir).expect("Failed to open DB"); + let server = Arc::new(Server::new(db, http_client, openid_client, signing_key)); + + let ws = { + let server = server.clone(); + warp::path("sync") + .and(warp::path::param()) + .and(warp::ws()) + .and(warp::any().map(move || server.clone())) + .and(warp::cookie(COOKIE_AUTH_TOKEN)) + .and_then(ws_handler) + }; + + let api_routes = { + let login_route = { + let server = server.clone(); + warp::path!("api" / "auth" / "login") + .map(move || server.clone()) + .and(warp::query()) + .and_then(handle_auth_login) + }; + + let callback_route = { + let server = server.clone(); + warp::path!("api" / "auth" / "callback") + .map(move || server.clone()) + .and(warp::cookie(COOKIE_AUTH_NONCE)) + .and(warp::cookie(COOKIE_AUTH_CSRF)) + .and(warp::query()) + .and_then(handle_auth_callback) + }; + + login_route.or(callback_route) + }; + + let frontend_files = warp::fs::dir(frontend_dir); + + let index = warp::fs::file(index_file); + + let routes = api_routes.or(ws).or(frontend_files).or(index); warp::serve(routes).run(([0, 0, 0, 0], 9000)).await; } -async fn ws_handler(name: String, ws: Ws, server: Arc) -> Result { - // TODO: Check permissions before upgrading +async fn ws_handler( + name: String, + ws: Ws, + server: Arc, + token: String, +) -> Result { + let token: UserToken = token + .verify_with_key(&server.signing_key) + .map_err(|_| warp::reject())?; - println!("ws_handler: {}", name); - let connection = server.get_or_create_doc(name).await; - Ok(ws.on_upgrade(move |socket| peer(socket, connection))) + println!( + "[INFO] User {} <{}> connected to document {}", + token.display_name, token.email, name + ); + let doc_id = format!("{}/{}", token.email, name); + let connection = server.get_or_create_doc(doc_id).await; + Ok(ws.on_upgrade(move |socket| peer(token, name, socket, connection))) } -async fn peer(ws: WebSocket, connection: Arc) { +async fn peer(token: UserToken, doc_name: String, ws: WebSocket, connection: Arc) { let (sink, stream) = ws.split(); let sink = Arc::new(Mutex::new(WarpSink::from(sink))); let stream = WarpStream::from(stream); let sub = connection.bcast.subscribe(sink, stream); - match sub.completed().await { - Ok(_) => println!("broadcasting for channel finished successfully"), - Err(e) => eprintln!("broadcasting for channel finished abruptly: {}", e), - } + let _ = sub.completed().await; + println!( + "[INFO] User {} <{}> disconnected from document {}", + token.display_name, token.email, doc_name + ); +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct AuthState { + redirect_url: Option, + csrf_token: String, +} + +#[derive(Debug, serde::Deserialize)] +struct AuthLoginParams { + redirect_url: Option, +} + +async fn handle_auth_login( + server: Arc, + params: AuthLoginParams, +) -> Result { + let csrf_token = CsrfToken::new_random(); + let state = AuthState { + redirect_url: params.redirect_url, + csrf_token: csrf_token.clone().into_secret(), + }; + let state = CsrfToken::new( + BASE64_URL_SAFE.encode( + state + .sign_with_key(&server.signing_key) + .map_err(|_| warp::reject::custom(AuthError))?, + ), + ); + // From what I understand I don't need the csrf_token + let (auth_url, _csrf_token, nonce) = server + .openid_client + .authorize_url( + AuthenticationFlow::::AuthorizationCode, + || state, + Nonce::new_random, + ) + .add_scope(Scope::new("openid".to_string())) + .add_scope(Scope::new("profile".to_string())) + .add_scope(Scope::new("email".to_string())) + .url(); + + Ok(Response::builder() + .header( + header::SET_COOKIE, + cookie::Cookie::build((COOKIE_AUTH_NONCE, nonce.secret())) + .path("/") + .http_only(true) + .build() + .to_string(), + ) + .header( + header::SET_COOKIE, + cookie::Cookie::build((COOKIE_AUTH_CSRF, csrf_token.secret())) + .path("/") + .http_only(true) + .build() + .to_string(), + ) + .header(header::LOCATION, auth_url.as_str()) + .status(StatusCode::FOUND) + .body("Login") + .unwrap()) +} + +#[derive(Debug, serde::Deserialize)] +struct AuthCallbackParams { + code: String, + state: String, +} + +async fn handle_auth_callback( + server: Arc, + nonce: String, + csrf_token: String, + params: AuthCallbackParams, +) -> Result { + let code = AuthorizationCode::new(params.code); + let nonce = Nonce::new(nonce); + let state: AuthState = String::from_utf8( + BASE64_URL_SAFE + .decode(params.state) + .map_err(|_| warp::reject::custom(AuthError))?, + ) + .map_err(|_| warp::reject::custom(AuthError))? + .verify_with_key(&server.signing_key) + .map_err(|_| warp::reject::custom(AuthError))?; + + if state.csrf_token != csrf_token { + return Err(warp::reject::custom(AuthError)); + } + + let token_response = server + .openid_client + .exchange_code(code) + .map_err(|_| warp::reject::custom(AuthError))? + .request_async(&server.http_client) + .await + .map_err(|_| warp::reject::custom(AuthError))?; + + let id_token_verifier: CoreIdTokenVerifier = server.openid_client.id_token_verifier(); + let id_token_claims = token_response + .extra_fields() + .id_token() + .ok_or(warp::reject::custom(AuthError))? + .claims(&id_token_verifier, &nonce) + .map_err(|_| warp::reject::custom(AuthError))?; + + let token_id: String = rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + let display_name = id_token_claims + .given_name() + .ok_or(warp::reject::custom(AuthError))? + .get(None) + .ok_or(warp::reject::custom(AuthError))? + .to_string(); + let email = id_token_claims + .email() + .ok_or(warp::reject::custom(AuthError))? + .to_string(); + + println!("[INFO] Authenticated: {} <{}>", display_name, email); + + let user_token = UserToken { + display_name, + email, + iat: Utc::now(), + jti: token_id, + }; + let user_token = user_token + .sign_with_key(&server.signing_key) + .map_err(|_| warp::reject::custom(AuthError))?; + + Ok(Response::builder() + .header( + header::SET_COOKIE, + cookie::Cookie::build((COOKIE_AUTH_NONCE, "")) + .path("/") + .http_only(true) + .build() + .to_string(), + ) + .header( + header::SET_COOKIE, + cookie::Cookie::build((COOKIE_AUTH_CSRF, "")) + .expires(OffsetDateTime::UNIX_EPOCH) + .path("/") + .http_only(true) + .build() + .to_string(), + ) + .header( + header::SET_COOKIE, + cookie::Cookie::build((COOKIE_AUTH_TOKEN, user_token)) + .expires(OffsetDateTime::now_utc() + Duration::days(365)) + .path("/") + .build() + .to_string(), + ) + .header( + header::LOCATION, + state.redirect_url.unwrap_or("/app".to_string()), + ) + .status(StatusCode::FOUND) + .body("") + .unwrap()) } diff --git a/frontend/package.json b/frontend/package.json index 98f53ba..cd87789 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "start": "vite --port 3000", + "start": "vite --port 9000", "build": "vite build && tsc", "serve": "vite preview", "test": "vitest run" @@ -33,6 +33,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.0.0", + "js-cookie": "^3.0.5", "lexical": "^0.25.0", "lucide-react": "^0.483.0", "react": "^19.0.0", @@ -54,6 +55,7 @@ "@testing-library/react": "^16.2.0", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", + "@types/js-cookie": "^3.0.6", "@vitejs/plugin-react": "^4.3.4", "jsdom": "^26.0.0", "serwist": "^9.0.12", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index f7db696..95a1589 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: cmdk: specifier: 1.0.0 version: 1.0.0(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 lexical: specifier: ^0.25.0 version: 0.25.0 @@ -132,6 +135,9 @@ importers: '@testing-library/react': specifier: ^16.2.0 version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 '@types/react': specifier: ^19.0.8 version: 19.0.11 @@ -1557,6 +1563,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + '@types/mdast@3.0.15': resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} @@ -2283,6 +2292,10 @@ packages: react: optional: true + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4706,6 +4719,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/js-cookie@3.0.6': {} + '@types/mdast@3.0.15': dependencies: '@types/unist': 2.0.11 @@ -5454,6 +5469,8 @@ snapshots: '@types/react': 19.0.11 react: 19.0.0 + js-cookie@3.0.5: {} + js-tokens@4.0.0: {} jsdom@26.0.0: diff --git a/frontend/src/components/app_sidebar.tsx b/frontend/src/components/app_sidebar.tsx index bdb84a7..532436e 100644 --- a/frontend/src/components/app_sidebar.tsx +++ b/frontend/src/components/app_sidebar.tsx @@ -14,6 +14,7 @@ import { UserAvatar } from "~/components/user/user_avatar"; import { Link } from "@tanstack/react-router"; import { useTheme } from "~/hooks/use-theme"; import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group"; +import { useAuth } from "~/hooks/use-auth"; export function AppSidebar() { const collections = useCollections(); @@ -117,8 +118,13 @@ function CollectionButton(props: CollectionButtonProps) { params={{ id: props.id }} className="w-full flex items-center gap-2 group/item" > -
- {icon && } +
+ {icon + ? + :
+ }
{ - // dbs.forEach((db) => { - // db.name && indexedDB.deleteDatabase(db.name); - // }); - // }); + signOut(); } return ( @@ -205,8 +199,8 @@ function NavUser() { >
- {name} - {email} + {user.name} + {user.email}
@@ -221,8 +215,8 @@ function NavUser() {
- {name} - {email} + {user.name} + {user.email}
diff --git a/frontend/src/hooks/use-auth.tsx b/frontend/src/hooks/use-auth.tsx new file mode 100644 index 0000000..2ee20a2 --- /dev/null +++ b/frontend/src/hooks/use-auth.tsx @@ -0,0 +1,76 @@ +import { createContext, useContext } from "react"; +import Cookies from "js-cookie"; +import { useNavigate } from "@tanstack/react-router"; +import { useRedraw } from "./use-redraw"; + +type AuthContextType = { + jwt?: { + display_name: string; + email: string; + + iat: number, + jti: string, + }, + signOut: () => void; +}; + +const AUTH_TOKEN_COOKIE_NAME = "knotes_auth_token"; + +const AuthContext = createContext(null); + +export function AuthProvider(props: { children: React.ReactNode }) { + const redraw = useRedraw(); + const navigate = useNavigate(); + const authToken = Cookies.get(AUTH_TOKEN_COOKIE_NAME); + const jwt: AuthContextType["jwt"] = authToken ? parseJwt(authToken) : null; + + function signOut() { + // Remove the auth token + Cookies.remove(AUTH_TOKEN_COOKIE_NAME); + + // Redirect to the landing page + navigate({ to: "/" }).then(() => { + // Force refresh the auth token from the cookie + redraw(); + + // After navigation completes, delete all locally stored data. + // We do this because otherwise the providers might still be mounted + // and might resync the data before they are unmounted. + indexedDB.databases().then((dbs) => { + dbs.forEach((db) => { + db.name && indexedDB.deleteDatabase(db.name); + }); + }); + }); + } + + return ( + + {props.children} + + ); +} + +export function useAuth() { + const userData = useContext(AuthContext); + + if (!userData) { + throw new Error("useAuth must be used within an AuthProvider"); + } + + return { + isAuthenticated: !!userData?.jwt, + data: userData.jwt, + signOut: userData.signOut, + }; +} + +function parseJwt(token: string) { + var base64Url = token.split('.')[1]; + var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + + return JSON.parse(jsonPayload); +} diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index 82d6975..720a93e 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -2,6 +2,7 @@ import { createRootRoute, Outlet } from "@tanstack/react-router"; // import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import { useEffect } from "react"; import { getSerwist } from "virtual:serwist"; +import { AuthProvider } from "~/hooks/use-auth"; import { ThemeProvider } from "~/hooks/use-theme"; export const Route = createRootRoute({ @@ -26,8 +27,10 @@ function RootLayout() { }, []); return ( - - {/**/} + + + {/**/} + ); } diff --git a/frontend/src/routes/app.tsx b/frontend/src/routes/app.tsx index 8143588..22661c2 100644 --- a/frontend/src/routes/app.tsx +++ b/frontend/src/routes/app.tsx @@ -1,8 +1,10 @@ import { createFileRoute, Outlet } from "@tanstack/react-router"; import { PanelLeft } from "lucide-react"; +import { useEffect } from "react"; import { AppSidebar } from "~/components/app_sidebar"; import { Button } from "~/components/ui/button"; import { SidebarInset, SidebarProvider, useSidebar } from "~/components/ui/sidebar"; +import { useAuth } from "~/hooks/use-auth"; import { MetadataProvider } from "~/hooks/use-metadata"; import { useIsMobile } from "~/hooks/use-mobile"; @@ -11,6 +13,22 @@ export const Route = createFileRoute("/app")({ }); export function AppLayout() { + const { isAuthenticated } = useAuth(); + + useEffect(() => { + if (isAuthenticated) { + return; + } + + // Redirect to the login page using the browser navigator, + // since we are leaving the SPA for auth here. + document.location.href = `/api/auth/login?redirect_url=${encodeURIComponent(document.location.href)}`; + }, [isAuthenticated]); + + if (!isAuthenticated) { + return null; + } + return ( <>