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
This commit is contained in:
Lily Tsuru 2024-05-10 07:07:44 -04:00
parent 0ecbda47bb
commit 37339e2580
16 changed files with 770 additions and 63 deletions

426
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"] }

View file

@ -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
# 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"

View file

@ -1,3 +1,5 @@
# Multinode TODO
# A descriptive name.
name = "Windows XP SP3 x86 (MyVM 1)"

107
src/auth/actor.rs Normal file
View file

@ -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<types::AuthResponse>,
},
}
pub(crate) struct AuthActor {
http_client: reqwest::Client,
auth_server_url: String,
secret_key: String,
rx: mpsc::Receiver<AuthActorMessage>,
}
impl AuthActor {
fn new(auth_url: String, secret: String, rx: mpsc::Receiver<AuthActorMessage>) -> 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<types::AuthResponse> {
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::<types::AuthResponse>().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<AuthActorMessage> {
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
}
}

59
src/auth/client.rs Normal file
View file

@ -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<actor::AuthActorMessage>,
}
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<types::AuthResponse> {
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),
}
}
}

6
src/auth/mod.rs Normal file
View file

@ -0,0 +1,6 @@
//! CVMAuth client.
mod actor;
mod types;
mod result;
pub mod client;
pub use client::*;

20
src/auth/result.rs Normal file
View file

@ -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<T> = std::result::Result<T, Error>;

23
src/auth/types.rs Normal file
View file

@ -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<String>,
pub error: Option<String>,
pub rank: Rank,
}

View file

@ -1,4 +1,6 @@
pub mod auth;
pub mod guac;
pub mod qmp;
pub mod qemuvm;
pub mod qmp;
pub(crate) mod user_types;
pub mod vm;

View file

@ -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!");
*/
}

View file

@ -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
}

View file

@ -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<actor::QmpActorMessage>,
}
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<S>(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<types::QmpHandshake> {
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<serde_json::Value>) -> result::Result<serde_json::Value> {
pub async fn execute(
&self,
command: String,
arguments: Option<serde_json::Value>,
) -> result::Result<serde_json::Value> {
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<String> {
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()))
}

View file

@ -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::*;

View file

@ -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),

12
src/user_types.rs Normal file
View file

@ -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
}