diff --git a/.github/release-body.md b/.github/release-body.md index fbae9de..066e12e 100644 --- a/.github/release-body.md +++ b/.github/release-body.md @@ -1,14 +1,8 @@ -## Release ☆ ~('▽^人) - -> [!CAUTION] -> **Update your Config.toml according to the example in the repository!** +## Bug fix What's added: -- Ability to change authentication providers; -- Display information about Sculptor in MOTD; -- Checking updates for Sculptor (displayed in CLI at startup) and for Figura (reported in mod UI); -- Saving log files in a separate directory; -- User bans and Minecraft blacklist parser; -- Implemented a special API for backend manipulation by third-party software. +- Prohibit creation of two sessions on one user +- Fixed avatar refreshing when deleting an avatar +- Fixed session relevance check -**Full Changelog**: https://github.com/shiroyashik/sculptor/compare/v0.2.3...v0.3.0 \ No newline at end of file +**Full Changelog**: https://github.com/shiroyashik/sculptor/compare/v0.3.0...v0.3.1 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 30007b6..8badd8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,19 +43,19 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -149,7 +149,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -220,9 +220,9 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.1.13" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ "shlex", ] @@ -330,9 +330,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -405,9 +405,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", @@ -415,6 +415,7 @@ dependencies = [ "lock_api", "once_cell", "parking_lot_core", + "serde", ] [[package]] @@ -517,9 +518,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fnv" @@ -631,7 +632,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.4.0", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -755,9 +756,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", @@ -788,9 +789,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" dependencies = [ "bytes", "futures-channel", @@ -857,9 +858,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -867,9 +868,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is_ci" @@ -1087,7 +1088,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1175,7 +1176,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1246,9 +1247,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1285,9 +1286,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] @@ -1349,9 +1350,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f6ddd79dc661ade721873783d159ec87d91d556ce92933e342bae8b87c48c0" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64 0.22.1", "bytes", @@ -1413,18 +1414,18 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -1435,9 +1436,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "rustls-pki-types", @@ -1464,9 +1465,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1487,11 +1488,11 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1502,7 +1503,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sculptor" -version = "0.3.0" +version = "0.3.1" dependencies = [ "anyhow", "axum", @@ -1560,22 +1561,22 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1589,9 +1590,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -1743,9 +1744,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1769,9 +1770,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -1833,7 +1834,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1894,9 +1895,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -1918,7 +1919,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1956,9 +1957,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2003,7 +2004,7 @@ version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", @@ -2087,7 +2088,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -2179,9 +2180,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -2279,7 +2280,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -2313,7 +2314,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2525,7 +2526,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7b3c3d5..dd73afc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sculptor" authors = ["Shiroyashik "] -version = "0.3.0" +version = "0.3.1" edition = "2021" publish = false @@ -21,7 +21,7 @@ serde_json = "1.0.117" toml = "0.8.13" # Other -dashmap = "6.0.1" +dashmap = { version = "6.0.1", features = ["serde"] } hex = "0.4.3" uuid = { version = "1.8.0", features = ["serde"] } base64 = "0.22.1" diff --git a/src/api/figura/auth.rs b/src/api/figura/auth.rs index fbe20de..5d6efd2 100644 --- a/src/api/figura/auth.rs +++ b/src/api/figura/auth.rs @@ -1,7 +1,7 @@ use axum::{debug_handler, extract::{Query, State}, response::{IntoResponse, Response}, routing::get, Router}; use reqwest::StatusCode; use ring::digest::{self, digest}; -use tracing::info; +use tracing::{error, info}; use crate::{auth::{has_joined, Userinfo}, utils::rand, AppState}; use super::types::auth::*; @@ -51,17 +51,23 @@ async fn verify( return (StatusCode::BAD_REQUEST, "You're banned!".to_string()).into_response(); } info!("[Authentication] {username} logged in using {}", auth_provider.name); - umanager.insert( + let userinfo = Userinfo { + username, uuid, - server_id.clone(), - Userinfo { - username, - uuid, - token: Some(server_id.clone()), - auth_provider, - ..Default::default() - }, - ); + token: Some(server_id.clone()), + auth_provider, + ..Default::default() + }; + match umanager.insert(uuid, server_id.clone(), userinfo.clone()) { + Ok(_) => {}, + Err(_) => { + umanager.remove(&uuid); + if umanager.insert(uuid, server_id.clone(), userinfo).is_err() { + error!("Old token error after attempting to remove it! Unexpected behavior!"); + return (StatusCode::BAD_REQUEST, "second session detected".to_string()).into_response(); + }; + } + } (StatusCode::OK, server_id.to_string()).into_response() } else { info!("[Authentication] failed to verify {username}"); diff --git a/src/api/figura/profile.rs b/src/api/figura/profile.rs index 9281edc..1c1148f 100644 --- a/src/api/figura/profile.rs +++ b/src/api/figura/profile.rs @@ -1,14 +1,11 @@ -use std::sync::Arc; - use axum::{ body::Bytes, extract::{Path, State}, Json }; -use dashmap::DashMap; use tracing::debug; use serde_json::{json, Value}; use tokio::{ fs, - io::{self, AsyncReadExt, BufWriter}, sync::broadcast::Sender, + io::{self, AsyncReadExt, BufWriter}, }; use uuid::Uuid; @@ -115,7 +112,7 @@ pub async fn upload_avatar( pub async fn equip_avatar(Token(token): Token, State(state): State) -> ApiResult<&'static str> { debug!("[API] S2C : Equip"); let uuid = state.user_manager.get(&token).ok_or_else(|| ApiError::Unauthorized)?.uuid; - send_event(&state.broadcasts, &uuid); + send_event(&state, &uuid).await; Ok("ok") } @@ -128,18 +125,27 @@ pub async fn delete_avatar(Token(token): Token, State(state): State) - ); let avatar_file = format!("avatars/{}.moon", user_info.uuid); fs::remove_file(avatar_file).await.map_err(|err| internal_and_log(err))?; - send_event(&state.broadcasts, &user_info.uuid); + send_event(&state, &user_info.uuid).await; } // let avatar_file = format!("avatars/{}.moon",user_info.uuid); Ok("ok".to_string()) } -pub fn send_event(broadcasts: &Arc>>>, uuid: &Uuid) { - if let Some(broadcast) = broadcasts.get(&uuid) { +pub async fn send_event(state: &AppState, uuid: &Uuid) { + // To user subscribers + if let Some(broadcast) = state.broadcasts.get(&uuid) { if broadcast.send(S2CMessage::Event(*uuid).to_vec()).is_err() { debug!("[WebSocket] Failed to send Event! There is no one to send. UUID: {uuid}") }; } else { debug!("[WebSocket] Failed to send Event! Can't find UUID: {uuid}") }; + // To user + if let Some(session) = state.session.get(&uuid) { + if session.send(S2CMessage::Event(*uuid).to_vec()).await.is_err() { + debug!("[WebSocket] Failed to send Event! WS doesn't connected? UUID: {uuid}") + }; + } else { + debug!("[WebSocket] Failed to send Event! Can't find UUID: {uuid}") + }; } \ No newline at end of file diff --git a/src/api/figura/websocket.rs b/src/api/figura/websocket.rs index 2e15693..b661b8e 100644 --- a/src/api/figura/websocket.rs +++ b/src/api/figura/websocket.rs @@ -91,7 +91,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { match newmsg { C2SMessage::Token(token) => { - debug!("[WebSocket{}] C2S : Token", owner.name()); + trace!("[WebSocket{}] C2S : Token", owner.name()); let token = String::from_utf8(token.to_vec()).unwrap(); match state.user_manager.get(&token) { // The principle is simple: if there is no token in authenticated, then it's "dirty hacker" :D Some(t) => { @@ -119,7 +119,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { }; }, C2SMessage::Ping(_, _, _) => { - debug!("[WebSocket{}] C2S : Ping", owner.name()); + trace!("[WebSocket{}] C2S : Ping", owner.name()); let data = into_s2c_ping(msg_vec, owner.clone().unwrap().uuid); match bctx.clone().unwrap().send(data) { Ok(_) => (), @@ -129,7 +129,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { }, // Subscribing C2SMessage::Sub(uuid) => { // TODO: Eliminate the possibility of using SUB without authentication - debug!("[WebSocket{}] C2S : Sub", owner.name()); + trace!("[WebSocket{}] C2S : Sub", owner.name()); // Ignoring self Sub if uuid == owner.clone().unwrap().uuid { continue; @@ -152,7 +152,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { }, // Unsubscribing C2SMessage::Unsub(uuid) => { - debug!("[WebSocket{}] C2S : Unsub", owner.name()); + trace!("[WebSocket{}] C2S : Unsub", owner.name()); // Ignoring self Unsub if uuid == owner.clone().unwrap().uuid { continue; @@ -186,11 +186,13 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { } // Closing connection if let Some(u) = owner { + debug!("[WebSocket ({})] Removing session data", u.username); state.session.remove(&u.uuid); // FIXME: Temporary solution // state.broadcasts.remove(&u.uuid); // NOTE: Create broadcasts manager ?? state.user_manager.remove(&u.uuid); + } else { + debug!("[WebSocket] Nothing to remove"); } - } async fn subscribe( diff --git a/src/api/v1/avatars.rs b/src/api/v1/avatars.rs index 9e31212..5c434b9 100644 --- a/src/api/v1/avatars.rs +++ b/src/api/v1/avatars.rs @@ -23,7 +23,7 @@ pub async fn upload_avatar( let avatar_file = format!("avatars/{}.moon", &uuid); let mut file = BufWriter::new(fs::File::create(&avatar_file).await.unwrap()); io::copy(&mut request_data.as_ref(), &mut file).await.unwrap(); - send_event(&state.broadcasts, &uuid); + send_event(&state, &uuid).await; Ok("ok") } @@ -48,7 +48,7 @@ pub async fn delete_avatar( return Err(crate::ApiError::NotFound) } }; - send_event(&state.broadcasts, &uuid); + send_event(&state, &uuid).await; Ok("ok") } \ No newline at end of file diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index 3d061aa..ddef510 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -11,6 +11,8 @@ pub fn router() -> Router { .route("/verify", get(http2ws::verify)) .route("/raw", post(http2ws::raw)) .route("/sub/raw", post(http2ws::sub_raw)) + .route("/user/list", get(users::list)) + .route("/user/sessions", get(users::list_sessions)) .route("/user/create", post(users::create_user)) .route("/user/:uuid/ban", post(users::ban)) .route("/user/:uuid/unban", post(users::unban)) diff --git a/src/api/v1/users.rs b/src/api/v1/users.rs index b020227..73745bf 100644 --- a/src/api/v1/users.rs +++ b/src/api/v1/users.rs @@ -5,7 +5,7 @@ use axum::{ use tracing::{debug, info}; use uuid::Uuid; -use crate::{auth::{Token, Userinfo}, ApiResult, AppState}; +use crate::{api::errors::internal_and_log, auth::{Token, Userinfo}, ApiResult, AppState}; pub(super) async fn create_user( Token(token): Token, @@ -44,4 +44,22 @@ pub(super) async fn unban( state.user_manager.unban(&uuid); Ok("ok") +} + +pub(super) async fn list( + Token(token): Token, + State(state): State, +) -> ApiResult { + state.config.read().await.clone().verify_token(&token)?; + + serde_json::to_string_pretty(&state.user_manager.get_all_registered()).map_err(|err| { internal_and_log(err) }) +} + +pub(super) async fn list_sessions( + Token(token): Token, + State(state): State, +) -> ApiResult { + state.config.read().await.clone().verify_token(&token)?; + + serde_json::to_string_pretty(&state.user_manager.get_all_authenticated()).map_err(|err| { internal_and_log(err) }) } \ No newline at end of file diff --git a/src/auth/auth.rs b/src/auth/auth.rs index 78a950e..62cfeb7 100644 --- a/src/auth/auth.rs +++ b/src/auth/auth.rs @@ -1,11 +1,12 @@ use std::sync::Arc; -use anyhow::anyhow; +use anyhow::{anyhow, Context}; use axum::{ async_trait, extract::{FromRequestParts, State}, http::{request::Parts, StatusCode} }; use dashmap::DashMap; -use tracing::{debug, error, trace}; +use thiserror::Error; +use tracing::{debug, error, trace, warn}; use uuid::Uuid; use crate::{ApiError, ApiResult, AppState, TIMEOUT, USER_AGENT}; @@ -49,18 +50,28 @@ where // Work with external APIs /// Get UUID from JSON response -#[inline] -fn get_id_json(json: &serde_json::Value) -> anyhow::Result { +fn get_id_json(json: &serde_json::Value) -> Result { trace!("json: {json:#?}"); // For debugging, we'll get to this later! let uuid = Uuid::parse_str(json.get("id").unwrap().as_str().unwrap())?; Ok(uuid) } +#[derive(Debug, Error)] +enum FetchError { + #[error("invalid response code (expected 200), found {0}.\n Response: {1:#?}")] + WrongResponse(u16, Result), + #[error(transparent)] + SendError(#[from] reqwest::Error), + #[error(transparent)] + Other(#[from] anyhow::Error), + +} + async fn fetch_json( auth_provider: &AuthProvider, server_id: &str, username: &str, -) -> anyhow::Result> { +) -> Result<(Uuid, AuthProvider), FetchError> { let client = reqwest::Client::builder().timeout(TIMEOUT).user_agent(USER_AGENT).build().unwrap(); let url = auth_provider.url.clone(); @@ -72,11 +83,11 @@ async fn fetch_json( trace!("{res:?}"); match res.status().as_u16() { 200 => { - let json = serde_json::from_str::(&res.text().await?)?; - let uuid = get_id_json(&json)?; - Ok(Ok((uuid, auth_provider.clone()))) + let json = serde_json::from_str::(&res.text().await?).with_context(|| format!("Cant deserialize"))?; + let uuid = get_id_json(&json).with_context(|| format!("Cant get UUID"))?; + Ok((uuid, auth_provider.clone())) } - _ => Ok(Err(anyhow!("notOK: {} data: {:?}", res.status().as_u16(), res.text().await))), + _ => Err(FetchError::WrongResponse(res.status().as_u16(), res.text().await)), } } @@ -100,14 +111,15 @@ pub async fn has_joined( let mut prov_count: usize = authproviders.len(); while prov_count > 0 { if let Some(fetch_res) = rx.recv().await { - if let Ok(user_res) = fetch_res { - if let Ok(data) = user_res { - return Ok(Some(data)) - } else { - misses.push(user_res.unwrap_err()); - } - } else { - errors.push(fetch_res.unwrap_err()); + match fetch_res { + Ok(data) => return Ok(Some(data)), + Err(err) => { + match err { + FetchError::WrongResponse(code, data) => misses.push((code, data)), + FetchError::SendError(err) => errors.push(err.to_string()), + FetchError::Other(err) => errors.push(err.to_string()), + } + }, } } else { error!("Unexpected behavior!"); @@ -134,7 +146,7 @@ async fn fetch_and_send( provider: AuthProvider, server_id: String, username: String, - tx: tokio::sync::mpsc::Sender>> + tx: tokio::sync::mpsc::Sender> ) { let _ = tx.send(fetch_json(&provider, &server_id, &username).await) .await.map_err( |err| trace!("fetch_and_send error [note: ok res returned and mpsc clossed]: {err:?}")); @@ -159,15 +171,34 @@ impl UManager { authenticated: Arc::new(DashMap::new()), } } + pub fn get_all_registered(&self) -> DashMap { + self.registered.as_ref().clone() + } + pub fn get_all_authenticated(&self) -> DashMap { + self.authenticated.as_ref().clone() + } pub fn pending_insert(&self, server_id: String, username: String) { self.pending.insert(server_id, username); } pub fn pending_remove(&self, server_id: &str) -> Option<(String, String)> { self.pending.remove(server_id) } - pub fn insert(&self, uuid: Uuid, token: String, userinfo: Userinfo) { + pub fn insert(&self, uuid: Uuid, token: String, userinfo: Userinfo) -> Result<(), ()> { + // Check for the presence of an active session. + if let Some(userinfo) = self.registered.get(&uuid) { + if let Some(token) = &userinfo.token { + if self.authenticated.contains_key(token) { + warn!("Rejected attempt to create a second session for the same user!"); + return Err(()) + } + debug!("`{}` already have token in registered profile (old token already removed from 'authenticated')", userinfo.username); + } + } + + // Adding a user self.authenticated.insert(token, uuid); self.insert_user(uuid, userinfo); + Ok(()) } pub fn insert_user(&self, uuid: Uuid, userinfo: Userinfo) { // self.registered.insert(uuid, userinfo) @@ -228,7 +259,7 @@ pub async fn check_auth( token: Option, State(state): State, ) -> ApiResult<&'static str> { - + debug!("Checking auth actuality..."); match token { Some(token) => { token.check_auth(&state).await?; diff --git a/src/auth/types.rs b/src/auth/types.rs index 7fcb0ab..7fb3c96 100644 --- a/src/auth/types.rs +++ b/src/auth/types.rs @@ -1,8 +1,8 @@ use chrono::Utc; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use uuid::Uuid; -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Userinfo { pub uuid: Uuid, @@ -33,7 +33,7 @@ impl Default for Userinfo { // new part -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AuthProvider { pub name: String, diff --git a/src/main.rs b/src/main.rs index e2e50b9..34e7c0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,9 +140,8 @@ async fn main() -> Result<()> { } let api = Router::new() - .nest("//auth", api_auth::router()) + .nest("//auth", api_auth::router()) // => /api//auth ¯\_(ツ)_/¯ .nest("/v1", api::v1::router()) - .route("/", get(check_auth)) .route("/limits", get(api_info::limits)) .route("/version", get(api_info::version)) .route("/motd", get(api_info::motd)) @@ -154,10 +153,11 @@ async fn main() -> Result<()> { let app = Router::new() .nest("/api", api) + .route("/api/", get(check_auth)) .route("/ws", get(ws)) - .route("/health", get(|| async { "ok" })) .with_state(state) - .layer(TraceLayer::new_for_http().on_request(())); + .layer(TraceLayer::new_for_http().on_request(())) + .route("/health", get(|| async { "ok" })); let listener = tokio::net::TcpListener::bind(listen).await?; info!("Listening on {}", listener.local_addr()?);