From 37339e25809f8f8e058c609a3b727462b3d1b5e3 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Fri, 10 May 2024 07:07:44 -0400 Subject: [PATCH] auth: new module a client for cvmauth (the server side bit needed to actually do the magic)! just needs a couple todos and it should be good enough for server usage later on --- Cargo.lock | 426 ++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 6 +- doc/collabvm.toml | 59 +++++-- doc/vm.toml | 2 + src/auth/actor.rs | 107 ++++++++++++ src/auth/client.rs | 59 +++++++ src/auth/mod.rs | 6 + src/auth/result.rs | 20 +++ src/auth/types.rs | 23 +++ src/lib.rs | 6 +- src/main.rs | 23 ++- src/qmp/actor.rs | 10 +- src/qmp/client.rs | 63 +++++-- src/qmp/mod.rs | 8 +- src/qmp/result.rs | 3 - src/user_types.rs | 12 ++ 16 files changed, 770 insertions(+), 63 deletions(-) create mode 100644 src/auth/actor.rs create mode 100644 src/auth/client.rs create mode 100644 src/auth/mod.rs create mode 100644 src/auth/result.rs create mode 100644 src/auth/types.rs create mode 100644 src/user_types.rs diff --git a/Cargo.lock b/Cargo.lock index 25d67f0..4843938 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,18 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.5.0" @@ -159,6 +171,22 @@ dependencies = [ "cc", ] +[[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-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "crc32fast" version = "1.4.0" @@ -173,8 +201,10 @@ name = "cvm12-rs" version = "0.1.0" dependencies = [ "axum", + "reqwest", "serde", "serde_json", + "serde_repr", "thiserror", "tokio", "toml_edit", @@ -184,12 +214,37 @@ dependencies = [ "vnc-rs", ] +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "flate2" version = "1.0.30" @@ -206,6 +261,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -310,6 +380,25 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "h2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -377,6 +466,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", @@ -385,6 +475,23 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] @@ -394,6 +501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", "futures-util", "http", "http-body", @@ -401,6 +509,19 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -413,6 +534,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itoa" version = "1.0.11" @@ -440,6 +567,12 @@ version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + [[package]] name = "lock_api" version = "0.4.12" @@ -494,6 +627,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -529,6 +680,50 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -626,7 +821,49 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags", + "bitflags 2.5.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", ] [[package]] @@ -644,6 +881,35 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustversion" version = "1.0.16" @@ -656,12 +922,44 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.23" @@ -709,6 +1007,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -787,6 +1096,39 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" version = "1.0.60" @@ -817,6 +1159,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +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.37.0" @@ -847,6 +1204,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -975,6 +1342,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "turbojpeg" version = "1.1.0" @@ -998,18 +1371,50 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vnc-rs" version = "0.5.1" @@ -1027,6 +1432,15 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1278,3 +1692,13 @@ checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] diff --git a/Cargo.toml b/Cargo.toml index 3ebabb8..d09769f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,5 +13,9 @@ toml_edit = "0.22.9" tracing = "0.1.40" tracing-subscriber = "0.3.18" thiserror = "1.0.58" -serde = { version = "1.0.197", features = [ "derive" ] } +serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" +serde_repr = "0.1.19" + +# Used for auth support +reqwest = { version = "0.12.4", features = ["json"] } diff --git a/doc/collabvm.toml b/doc/collabvm.toml index 6631263..9d2aabc 100644 --- a/doc/collabvm.toml +++ b/doc/collabvm.toml @@ -1,29 +1,52 @@ -listen = "127.0.0.1:3000" # or /run/cvm.sock ? if warp supports uds i guess +# the listen address +listen = "127.0.0.1:6004" +proxy = true +max_connections_per_ip = 5 -# optional -vm_directory = "vms" +# isn't used currently, will be used for multinode. +vm_dir = "vms" -# nested table. you can define it non-nested if you want -[mod_permissions] -xxx = false -xyz = true +[auth] +# optional. If not provided, auth will be disabled +auth_server = "https://auth.mycollab.xyz" -# passwords hash -mod_password_hash = "md5 hash of password" -admin_password_hash = "md5 hash of password" +# secret key for auth. +auth_secret_key = "[auth secret]" + +# optional if auth server is used +# (if auth server is used these are completely ignored) +mod_hash = "[argon2di hash]" +admin_hash = "[argon2di hash]" + +# Optional if auth is disabled +[guest_permissions] +chat = true +turn = false +vote = false # Command to run to ban a user # $user is replaced with the user, and # $ip is replaced with the ip of the user ban_command = "sexy $user $ip" -# defaults for all VMs -[default_permissions] -turns_enabled = true -votes_enabled = true +[default_config] turn_time = 18 -vote_time = 60 -vote_cooldown_time = 120 -# applied to all vms by default unless they override it in their toml file -motd = "Welcome to my UserVM!" \ No newline at end of file +# vote config +vote_time = 60 +vote_cooldown_time = 60 + +# chat config +chat_message_max_length = 100 +chat_history_length = 50 + +# limits +turn_limit = { same_ip = 2 } +auto_mute = { seconds = 5, messages = 5 } + +# vm configuration +[vm] +id = "vm1" +motd = "Welcome to my UserVM!" +name = "Windows XP SP3 x86 (MyVM 1)" +command_line = "/uvm/vms/vm1.lvm start" \ No newline at end of file diff --git a/doc/vm.toml b/doc/vm.toml index ba04fec..ae85c2b 100644 --- a/doc/vm.toml +++ b/doc/vm.toml @@ -1,3 +1,5 @@ +# Multinode TODO + # A descriptive name. name = "Windows XP SP3 x86 (MyVM 1)" diff --git a/src/auth/actor.rs b/src/auth/actor.rs new file mode 100644 index 0000000..d44802f --- /dev/null +++ b/src/auth/actor.rs @@ -0,0 +1,107 @@ +use reqwest::ClientBuilder; +use tokio::sync::{mpsc, oneshot}; + +use super::{result, types}; + +/// User agent for the auth actor +static AUTH_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); + +#[derive(Debug)] +pub(crate) enum AuthActorMessage { + Shutdown, + + Authenticate { + session_token: String, + ip: String, + + // Oneshot response + tx: oneshot::Sender, + }, +} + +pub(crate) struct AuthActor { + http_client: reqwest::Client, + + auth_server_url: String, + secret_key: String, + + rx: mpsc::Receiver, +} + +impl AuthActor { + fn new(auth_url: String, secret: String, rx: mpsc::Receiver) -> Self { + let client = ClientBuilder::new() + .user_agent(AUTH_USER_AGENT) + .build() + .expect("Failed to build HTTP client for CvmAuth"); + + Self { + http_client: client, + auth_server_url: auth_url, + secret_key: secret, + rx, + } + } + + async fn authenticate( + &mut self, + session_token: String, + ip: String, + ) -> result::Result { + let request_json = types::AuthRequest { + secret_key: self.secret_key.clone(), + session_token: session_token, + ip: ip, + }; + + let raw_response = self + .http_client + .post(format!("{}/api/v1/join", self.auth_server_url)) + .json(&request_json) + .send() + .await? + .error_for_status()?; + + // TODO: Convert JSON error to result::Error arm at some point so that we can match for those errors later >_< + // also it's just a good idea + let deserialized_response = raw_response.json::().await?; + + Ok(deserialized_response) + } + + /// Actor runloop. + async fn run(mut self) -> result::Result<()> { + while let Some(msg) = self.rx.recv().await { + match msg { + // simply break out to shut down the actor. + AuthActorMessage::Shutdown => break, + + AuthActorMessage::Authenticate { + session_token, + ip, + tx, + } => { + let _ = tx.send(self.authenticate(session_token, ip).await?); + } + } + } + Ok(()) + } + + /// Helper function to spawn the actor. + pub(crate) fn spawn_actor(auth_url: String, secret: String) -> mpsc::Sender { + let (tx, rx) = mpsc::channel(8); + let inner = AuthActor::new(auth_url, secret, rx); + + tokio::spawn(async move { + match inner.run().await { + Ok(()) => {} + Err(error) => { + tracing::error!("error in auth actor runloop: {error}"); + } + } + }); + + tx + } +} diff --git a/src/auth/client.rs b/src/auth/client.rs new file mode 100644 index 0000000..433da64 --- /dev/null +++ b/src/auth/client.rs @@ -0,0 +1,59 @@ +use super::{actor, result, types}; +use tokio::sync::{mpsc, oneshot}; + +/// A client for CVMAuth on the server side. +/// This allows authorizing users. +#[derive(Clone)] +pub struct Client { + tx: mpsc::Sender, +} + +impl Client { + /// Creates a new auth client. + /// [auth_url] is the base URL path to the cvmauth backend. + /// [secret_key] is the given secret key. + /// + /// # Example + /// ```rust + /// use cvm12_rs::auth; + /// + /// async fn my_auth() { + /// let client = auth::Client::new("https://cvmauth.mycvm.xyz".into(), "SECRET_KEY_UNSAFE".into()); + /// } + /// ``` + pub fn new(auth_url: String, secret_key: String) -> Self { + Self { + tx: actor::AuthActor::spawn_actor(auth_url, secret_key), + } + } + + /// Shuts down the auth client. Once done this client will no longer be usable. + pub async fn shutdown(&self) { + let _ = self.tx.send(actor::AuthActorMessage::Shutdown).await; + } + + /// Authenticates a user. + pub async fn authenticate( + &self, + token: String, + ip: String, + ) -> result::Result { + let (tx, rx) = oneshot::channel(); + + let _ = self + .tx + .send(actor::AuthActorMessage::Authenticate { + session_token: token, + ip: ip, + tx: tx, + }) + .await; + + match rx.await { + // TODO: Convert JSON failures to an error. + Ok(res) => Ok(res), + // See above FIXME + Err(_err) => Err(result::Error::GeneralFail), + } + } +} diff --git a/src/auth/mod.rs b/src/auth/mod.rs new file mode 100644 index 0000000..0be3035 --- /dev/null +++ b/src/auth/mod.rs @@ -0,0 +1,6 @@ +//! CVMAuth client. +mod actor; +mod types; +mod result; +pub mod client; +pub use client::*; diff --git a/src/auth/result.rs b/src/auth/result.rs new file mode 100644 index 0000000..3faad77 --- /dev/null +++ b/src/auth/result.rs @@ -0,0 +1,20 @@ +use thiserror::Error; + +/// QMP library error. +/// FIXME: more arms +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + IoError(#[from] tokio::io::Error), + + #[error(transparent)] + HttpError(#[from] reqwest::Error), + + #[error(transparent)] + JsonError(#[from] serde_json::Error), + + #[error("general failure")] + GeneralFail, +} + +pub type Result = std::result::Result; \ No newline at end of file diff --git a/src/auth/types.rs b/src/auth/types.rs new file mode 100644 index 0000000..ba6a9ec --- /dev/null +++ b/src/auth/types.rs @@ -0,0 +1,23 @@ +use crate::user_types::Rank; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct AuthRequest { + #[serde(rename = "secretKey")] + pub secret_key: String, + + #[serde(rename = "sessionToken")] + pub session_token: String, + + pub ip: String, +} + +#[derive(Deserialize, Debug)] +pub struct AuthResponse { + pub success: bool, + #[serde(rename = "clientSuccess")] + pub client_sucess: bool, + pub username: Option, + pub error: Option, + pub rank: Rank, +} diff --git a/src/lib.rs b/src/lib.rs index 9cf2988..8ed6163 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ +pub mod auth; pub mod guac; -pub mod qmp; pub mod qemuvm; -pub mod vm; \ No newline at end of file +pub mod qmp; +pub(crate) mod user_types; +pub mod vm; diff --git a/src/main.rs b/src/main.rs index 8e27284..6e92f3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,6 @@ -use cvm12_rs::qmp; - -use tokio::net::UnixStream; - -use std::time::Duration; -use tokio::time; +//use cvm12_rs::qmp; +//use tokio::net::UnixStream; +//use std::time::Duration; #[tokio::main] async fn main() { @@ -13,12 +10,13 @@ async fn main() { tracing::subscriber::set_global_default(subscriber).expect("You Banned"); + /* // Create a stream to connect let stream = UnixStream::connect("/home/lily/vms/xpiss/qmp.sock") .await .expect("Could not connect"); - let client = qmp::QmpClient::new(stream); + let client = qmp::Client::new(stream); // Handshake QMP client.handshake().await.expect("Could not handshake QMP"); @@ -26,17 +24,17 @@ async fn main() { println!("Connected to QMP server"); // Create a copy of the client handle so we can issue a command in this other task. - // This other task waits 10 seconds and closes the client, which causes the actor to stop. + // This other task waits 120 seconds and closes the client, which causes the actor to stop. let copy = client.clone(); tokio::spawn(async move { - tokio::time::sleep(Duration::from_secs(10)).await; - println!("Closing client after 10 seconds"); + tokio::time::sleep(Duration::from_secs(120)).await; + println!("Closing client after 120 seconds"); copy.close().await.expect("Closing shouldn't fail"); }); println!( - "res {}", + "info block command: {}", client .execute_hmp("info block".into()) .await @@ -60,6 +58,5 @@ async fn main() { println!("Result of running: {:?}, {:?}", res1, res2); } - - //println!("Hello, world!"); + */ } diff --git a/src/qmp/actor.rs b/src/qmp/actor.rs index 81d0a2f..57f5b03 100644 --- a/src/qmp/actor.rs +++ b/src/qmp/actor.rs @@ -147,7 +147,6 @@ where self.last_response_id += 1; } - _ => tracing::error!("Unknown message"), } Ok(()) @@ -233,9 +232,12 @@ where let (tx, rx) = mpsc::channel(8); let inner = QmpClientActor::new(socket, rx); tokio::spawn(async move { - inner.run().await.inspect_err(|err| { - tracing::error!("error in actor runloop: {err}"); - }); + match inner.run().await { + Ok(()) => {}, + Err(error) => { + tracing::error!("error in actor runloop: {error}"); + } + } }); tx } diff --git a/src/qmp/client.rs b/src/qmp/client.rs index b91d06f..f8f1d28 100644 --- a/src/qmp/client.rs +++ b/src/qmp/client.rs @@ -1,4 +1,5 @@ //! QMP client. +//! use serde_json::json; use tokio::{ @@ -6,7 +7,7 @@ use tokio::{ sync::{mpsc, oneshot}, }; -use super::{result, actor, types}; +use super::{actor, result, types}; /// A client for the Qemu Machine Protocol (also known as QMP). /// Generic so it works with any Tokio stream type (which fits QEMU's ability to run @@ -16,15 +17,29 @@ use super::{result, actor, types}; /// I might maybe refactor this or make some generic "actor" thingmabob but for now /// it's all in here. #[derive(Clone)] -pub struct QmpClient { +pub struct Client { tx: mpsc::Sender, } -impl QmpClient { +impl Client { /// Creates a new QMP client. /// /// ## Notes /// The stream provided **MUST NOT** have been shut down before being provided (besides, why would you even do that?) + /// + /// ## Example usage + /// + /// ```rust + /// use tokio::net::UnixStream; + /// + /// async fn client_create() { + /// let stream = UnixStream::connect("/ferris/qemu/mon01.sock") + /// .await + /// .expect("Could not connect to QMP socket"); + /// + /// let client = cvm12_rs::qmp::Client::new(stream); + /// } + /// ``` pub fn new(socket: S) -> Self where S: AsyncReadExt + AsyncWriteExt + Unpin + Send + 'static, @@ -36,16 +51,14 @@ impl QmpClient { // TODO handle errors properly - /// Closes this client. + /// Closes this client. All in-flight event channels will be closed, all commands will fail to complete, + /// and the server socket will be closed. A new [Client] will need to be created to reconnect. pub async fn close(&self) -> result::Result<()> { - let _ = self - .tx - .send(actor::QmpActorMessage::QmpDoClose { }) - .await; + let _ = self.tx.send(actor::QmpActorMessage::QmpDoClose {}).await; Ok(()) } - /// Handshakes QMP with the server. This **MUST** be the first operation you do. + /// Handshakes QMP with the server. This **MUST** be the first operation done after creating the client. pub async fn handshake(&self) -> result::Result { let (tx, rx) = oneshot::channel(); @@ -55,7 +68,8 @@ impl QmpClient { .await; match rx.await { Ok(res) => res, - Err(err) => Err(result::QmpError::GeneralFail), + // FIXME: add an arm for this in QmpError + Err(_err) => Err(result::QmpError::GeneralFail), } } @@ -63,27 +77,42 @@ impl QmpClient { // for now this isn't done. /// Executes a single QMP command. - pub async fn execute(&self, command: String, arguments: Option) -> result::Result { + pub async fn execute( + &self, + command: String, + arguments: Option, + ) -> result::Result { let (tx, rx) = oneshot::channel(); let _ = self .tx - .send(actor::QmpActorMessage::QmpDoSend { command: command, arguments: arguments.clone(), tx: tx }) + .send(actor::QmpActorMessage::QmpDoSend { + command: command, + arguments: arguments.clone(), + tx: tx, + }) .await; match rx.await { Ok(res) => Ok(res), - Err(err) => Err(result::QmpError::GeneralFail), + // See above FIXME + Err(_err) => Err(result::QmpError::GeneralFail), } } /// Executes a HMP (QEMU Monitor) command. pub async fn execute_hmp(&self, hmp_line: String) -> result::Result { - let res = self.execute("human-monitor-command".into(), Some(json!({ - "command-line": hmp_line - }))).await?; + let res = self + .execute( + "human-monitor-command".into(), + Some(json!({ + "command-line": hmp_line + })), + ) + .await?; - // This is very nasty, but this is always a string. + // This is very nasty, but this property is always set as a string. + // I'll fix this later I promise Ok(String::from(res["return"].as_str().unwrap())) } diff --git a/src/qmp/mod.rs b/src/qmp/mod.rs index 9f9e1f2..52633d8 100644 --- a/src/qmp/mod.rs +++ b/src/qmp/mod.rs @@ -1,8 +1,8 @@ //! QMP client lib -pub(crate) mod types; +pub mod types; pub mod result; -pub(crate) mod io; -pub(crate) mod actor; -pub mod client; +mod io; +mod actor; +mod client; pub use client::*; \ No newline at end of file diff --git a/src/qmp/result.rs b/src/qmp/result.rs index 932615c..31d5793 100644 --- a/src/qmp/result.rs +++ b/src/qmp/result.rs @@ -7,9 +7,6 @@ pub enum QmpError { #[error(transparent)] IoError(#[from] tokio::io::Error), - #[error(transparent)] - DecodingError(#[from] std::string::FromUtf8Error), - #[error(transparent)] JsonError(#[from] serde_json::Error), diff --git a/src/user_types.rs b/src/user_types.rs new file mode 100644 index 0000000..2d645f7 --- /dev/null +++ b/src/user_types.rs @@ -0,0 +1,12 @@ +//! user-related types + +use serde_repr::*; + +#[derive(Serialize_repr, Deserialize_repr, Debug)] +#[repr(u16)] +pub enum Rank { + Unregistered = 0, + Registered, + Admin, + Moderator +} \ No newline at end of file