From a1f9eba502c294733141a258dfb32587eef39fe6 Mon Sep 17 00:00:00 2001 From: shiroyashik Date: Mon, 1 Jul 2024 03:43:06 +0300 Subject: [PATCH] The system design has changed, but the refactor is not over. In future commits, I will strive to maintain a consistent style. --- src/api/figura/auth.rs | 61 +++++ src/{ => api/figura}/info.rs | 0 src/api/figura/mod.rs | 7 + src/{ => api/figura}/profile.rs | 2 +- src/api/figura/types/auth.rs | 11 + src/{ws => api/figura}/types/c2s.rs | 0 src/{ws => api/figura}/types/errors.rs | 0 src/{ws => api/figura}/types/mod.rs | 3 +- src/{ws => api/figura}/types/s2c.rs | 0 src/{ws => api/figura}/websocket.rs | 6 +- src/api/mod.rs | 2 + src/api/v1/auth.rs | 22 ++ src/{ws/http.rs => api/v1/http2ws.rs} | 25 +- src/api/v1/mod.rs | 16 ++ src/api/v1/types.rs | 7 + src/api/v1/users.rs | 24 ++ src/auth.rs | 309 ------------------------- src/auth/auth.rs | 151 ++++++++++++ src/auth/mod.rs | 5 + src/auth/types.rs | 56 +++++ src/main.rs | 58 +++-- src/{ => state}/config.rs | 0 src/state/mod.rs | 4 + src/state/state.rs | 0 src/ws/mod.rs | 8 - 25 files changed, 410 insertions(+), 367 deletions(-) create mode 100644 src/api/figura/auth.rs rename src/{ => api/figura}/info.rs (100%) create mode 100644 src/api/figura/mod.rs rename src/{ => api/figura}/profile.rs (99%) create mode 100644 src/api/figura/types/auth.rs rename src/{ws => api/figura}/types/c2s.rs (100%) rename src/{ws => api/figura}/types/errors.rs (100%) rename src/{ws => api/figura}/types/mod.rs (70%) rename src/{ws => api/figura}/types/s2c.rs (100%) rename src/{ws => api/figura}/websocket.rs (99%) create mode 100644 src/api/mod.rs create mode 100644 src/api/v1/auth.rs rename src/{ws/http.rs => api/v1/http2ws.rs} (86%) create mode 100644 src/api/v1/mod.rs create mode 100644 src/api/v1/types.rs create mode 100644 src/api/v1/users.rs delete mode 100644 src/auth.rs create mode 100644 src/auth/auth.rs create mode 100644 src/auth/mod.rs create mode 100644 src/auth/types.rs rename src/{ => state}/config.rs (100%) create mode 100644 src/state/mod.rs create mode 100644 src/state/state.rs delete mode 100644 src/ws/mod.rs diff --git a/src/api/figura/auth.rs b/src/api/figura/auth.rs new file mode 100644 index 0000000..f069aac --- /dev/null +++ b/src/api/figura/auth.rs @@ -0,0 +1,61 @@ +use axum::{debug_handler, extract::{Query, State}, response::{IntoResponse, Response}, routing::get, Router}; +use reqwest::StatusCode; +use ring::digest::{self, digest}; +use tracing::{error, info}; + +use crate::{auth::{has_joined, Userinfo}, utils::rand, AppState}; +use super::types::auth::*; + +pub fn router() -> Router { + Router::new() + .route("/id", get(id)) + .route("/verify", get(verify)) +} + +#[debug_handler] +async fn id( + // First stage of authentication + Query(query): Query, + State(state): State, +) -> String { + let server_id = + hex::encode(&digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, &rand()).as_ref()[0..20]); + let state = state.user_manager; + state.pending_insert(server_id.clone(), query.username); + server_id +} + +#[debug_handler] +async fn verify( + // Second stage of authentication + Query(query): Query, + State(state): State, +) -> Response { + let server_id = query.id.clone(); + let username = state.user_manager.pending_remove(&server_id).unwrap().1; // TODO: Add error check + let userinfo = match has_joined(&server_id, &username).await { + Ok(d) => d, + Err(e) => { + error!("[Authentication] {e}"); + return (StatusCode::INTERNAL_SERVER_ERROR, "internal verify error".to_string()).into_response(); + }, + }; + if let Some((uuid, auth_system)) = userinfo { + info!("[Authentication] {username} logged in using {auth_system:?}"); + let authenticated = state.user_manager; + authenticated.insert( + uuid, + server_id.clone(), + Userinfo { + username, + uuid, + auth_system, + token: Some(server_id.clone()), + }, + ); + (StatusCode::OK, server_id.to_string()).into_response() + } else { + info!("[Authentication] failed to verify {username}"); + (StatusCode::BAD_REQUEST, "failed to verify".to_string()).into_response() + } +} \ No newline at end of file diff --git a/src/info.rs b/src/api/figura/info.rs similarity index 100% rename from src/info.rs rename to src/api/figura/info.rs diff --git a/src/api/figura/mod.rs b/src/api/figura/mod.rs new file mode 100644 index 0000000..d098984 --- /dev/null +++ b/src/api/figura/mod.rs @@ -0,0 +1,7 @@ +mod types; +mod websocket; +pub mod auth; +pub mod profile; +pub mod info; + +pub use websocket::handler as ws; \ No newline at end of file diff --git a/src/profile.rs b/src/api/figura/profile.rs similarity index 99% rename from src/profile.rs rename to src/api/figura/profile.rs index 07fc760..7432e89 100644 --- a/src/profile.rs +++ b/src/api/figura/profile.rs @@ -15,9 +15,9 @@ use uuid::Uuid; use crate::{ auth::Token, utils::{calculate_file_sha256, format_uuid, get_correct_array}, - ws::S2CMessage, AppState, }; +use super::types::S2CMessage; pub async fn user_info( Path(uuid): Path, diff --git a/src/api/figura/types/auth.rs b/src/api/figura/types/auth.rs new file mode 100644 index 0000000..a236bb4 --- /dev/null +++ b/src/api/figura/types/auth.rs @@ -0,0 +1,11 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Id { + pub username: String, +} + +#[derive(Deserialize)] +pub struct Verify { + pub id: String, +} \ No newline at end of file diff --git a/src/ws/types/c2s.rs b/src/api/figura/types/c2s.rs similarity index 100% rename from src/ws/types/c2s.rs rename to src/api/figura/types/c2s.rs diff --git a/src/ws/types/errors.rs b/src/api/figura/types/errors.rs similarity index 100% rename from src/ws/types/errors.rs rename to src/api/figura/types/errors.rs diff --git a/src/ws/types/mod.rs b/src/api/figura/types/mod.rs similarity index 70% rename from src/ws/types/mod.rs rename to src/api/figura/types/mod.rs index d26b7db..db4eb5c 100644 --- a/src/ws/types/mod.rs +++ b/src/api/figura/types/mod.rs @@ -1,7 +1,8 @@ mod c2s; mod errors; mod s2c; +pub mod auth; pub use c2s::C2SMessage; pub use errors::MessageLoadError; -pub use s2c::S2CMessage; +pub use s2c::S2CMessage; \ No newline at end of file diff --git a/src/ws/types/s2c.rs b/src/api/figura/types/s2c.rs similarity index 100% rename from src/ws/types/s2c.rs rename to src/api/figura/types/s2c.rs diff --git a/src/ws/websocket.rs b/src/api/figura/websocket.rs similarity index 99% rename from src/ws/websocket.rs rename to src/api/figura/websocket.rs index e835f97..a08da57 100644 --- a/src/ws/websocket.rs +++ b/src/api/figura/websocket.rs @@ -15,10 +15,8 @@ use tokio::sync::{ }; use uuid::Uuid; -use crate::{ - ws::{C2SMessage, S2CMessage}, - AppState, -}; +use crate::AppState; +use super::types::{C2SMessage, S2CMessage}; pub async fn handler(ws: WebSocketUpgrade, State(state): State) -> Response { ws.on_upgrade(|socket| handle_socket(socket, state)) diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..94107d5 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,2 @@ +pub mod figura; +pub mod v1; \ No newline at end of file diff --git a/src/api/v1/auth.rs b/src/api/v1/auth.rs new file mode 100644 index 0000000..54a5491 --- /dev/null +++ b/src/api/v1/auth.rs @@ -0,0 +1,22 @@ +use axum::{extract::State, http::StatusCode, response::{IntoResponse, Response}}; + +use crate::{auth::Token, AppState}; + +pub async fn status( + Token(token): Token, + State(state): State +) -> Response { + match token { + Some(token) => { + if state.user_manager.is_authenticated(&token) { + (StatusCode::OK, "ok".to_string()).into_response() + } else { + + (StatusCode::UNAUTHORIZED, "unauthorized".to_string()).into_response() + } + } + None => { + (StatusCode::BAD_REQUEST, "bad request".to_string()).into_response() + } + } +} \ No newline at end of file diff --git a/src/ws/http.rs b/src/api/v1/http2ws.rs similarity index 86% rename from src/ws/http.rs rename to src/api/v1/http2ws.rs index 5860f1b..f11bb11 100644 --- a/src/ws/http.rs +++ b/src/api/v1/http2ws.rs @@ -1,29 +1,14 @@ use axum::{ extract::{Query, State}, http::StatusCode, - response::{IntoResponse, Response}, - routing::{get, post}, - Router + response::{IntoResponse, Response} }; -use serde::Deserialize; use tracing::debug; -use uuid::Uuid; use crate::{auth::Token, AppState}; +use super::types::UserUuid; -pub fn router() -> Router { - Router::new() - .route("/verify", get(verify)) - .route("/raw", post(raw)) - .route("/sub/raw", post(sub_raw)) -} - -#[derive(Deserialize)] -struct UserUuid { - uuid: Option, -} - -async fn verify( +pub(super) async fn verify( Token(token): Token, State(state): State, ) -> Response { @@ -32,7 +17,7 @@ async fn verify( .unwrap_or_else(|x| x) } -async fn raw( +pub(super) async fn raw( Token(token): Token, Query(query): Query, State(state): State, @@ -68,7 +53,7 @@ async fn raw( } } -async fn sub_raw( +pub(super) async fn sub_raw( Token(token): Token, Query(query): Query, State(state): State, diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs new file mode 100644 index 0000000..2412d92 --- /dev/null +++ b/src/api/v1/mod.rs @@ -0,0 +1,16 @@ +use axum::{routing::{get, post}, Router}; +use crate::AppState; + +mod http2ws; +mod users; +mod auth; +mod types; + +pub fn router() -> Router { + Router::new() + .route("/verify", get(http2ws::verify)) + .route("/raw", post(http2ws::raw)) + .route("/sub/raw", post(http2ws::sub_raw)) + .route("/auth/", get(auth::status)) + .route("/users/create", post(users::create_user)) +} \ No newline at end of file diff --git a/src/api/v1/types.rs b/src/api/v1/types.rs new file mode 100644 index 0000000..0523ae6 --- /dev/null +++ b/src/api/v1/types.rs @@ -0,0 +1,7 @@ +use serde::Deserialize; +use uuid::Uuid; + +#[derive(Deserialize)] +pub(super) struct UserUuid { + pub uuid: Option, +} \ No newline at end of file diff --git a/src/api/v1/users.rs b/src/api/v1/users.rs new file mode 100644 index 0000000..2832c4a --- /dev/null +++ b/src/api/v1/users.rs @@ -0,0 +1,24 @@ +use axum::{ + extract::State, + http::StatusCode, + response::{IntoResponse, Response}, + Json +}; +use tracing::debug; + +use crate::{auth::{Token, Userinfo}, AppState}; + +pub(super) async fn create_user( + Token(token): Token, + State(state): State, + Json(json): Json +) -> Response { + debug!("Json: {json:?}"); + match state.config.lock().await.clone().verify_token(&token) { + Ok(_) => {}, + Err(e) => return e, + } + + state.user_manager.insert_user(json.uuid, json); + (StatusCode::OK, "ok".to_string()).into_response() +} \ No newline at end of file diff --git a/src/auth.rs b/src/auth.rs deleted file mode 100644 index faf1f3d..0000000 --- a/src/auth.rs +++ /dev/null @@ -1,309 +0,0 @@ -use std::{str::FromStr, sync::Arc}; - -use crate::utils::*; -use anyhow::anyhow; -use axum::{ - async_trait, debug_handler, extract::{FromRequestParts, Query, State}, http::{request::Parts, StatusCode}, response::{IntoResponse, Response}, routing::{get, post}, Json, Router -}; -use dashmap::DashMap; -use ring::digest::{self, digest}; -use serde::Deserialize; -use tracing::{debug, error, info, trace}; -use uuid::Uuid; - -use crate::AppState; - -pub fn router() -> Router { - Router::new() - .route("/id", get(id)) - .route("/verify", get(verify)) -} - -pub fn router_v1() -> Router { - Router::new() - .route("/create", post(create_user)) -} - -// Web -#[derive(Deserialize)] -struct Id { - username: String, -} - -#[debug_handler] -async fn id( - // First stage of authentication - Query(query): Query, - State(state): State, -) -> String { - let server_id = - hex::encode(&digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, &rand()).as_ref()[0..20]); - let state = state.user_manager; - state.pending_insert(server_id.clone(), query.username); - server_id -} - -#[derive(Deserialize)] -struct Verify { - id: String, -} - -#[debug_handler] -async fn verify( - // Second stage of authentication - Query(query): Query, - State(state): State, -) -> Response { - let server_id = query.id.clone(); - let username = state.user_manager.pending_remove(&server_id).unwrap().1; // TODO: Add error check - let userinfo = match has_joined(&server_id, &username).await { - Ok(d) => d, - Err(e) => { - error!("[Authentication] {e}"); - return (StatusCode::INTERNAL_SERVER_ERROR, "internal verify error".to_string()).into_response(); - }, - }; - if let Some((uuid, auth_system)) = userinfo { - info!("[Authentication] {username} logged in using {auth_system:?}"); - let authenticated = state.user_manager; - authenticated.insert( - uuid, - server_id.clone(), - Userinfo { - username, - uuid, - auth_system, - token: Some(server_id.clone()), - }, - ); - (StatusCode::OK, server_id.to_string()).into_response() - } else { - info!("[Authentication] failed to verify {username}"); - (StatusCode::BAD_REQUEST, "failed to verify".to_string()).into_response() - } -} - -pub async fn status( - Token(token): Token, - State(state): State -) -> Response { - match token { - Some(token) => { - if state.user_manager.is_authenticated(&token) { - (StatusCode::OK, "ok".to_string()).into_response() - } else { - - (StatusCode::UNAUTHORIZED, "unauthorized".to_string()).into_response() - } - } - None => { - (StatusCode::BAD_REQUEST, "bad request".to_string()).into_response() - } - } -} - -pub async fn create_user( - Token(token): Token, - State(state): State, - Json(json): Json -) -> Response { - debug!("Json: {json:?}"); - match state.config.lock().await.clone().verify_token(&token) { - Ok(_) => {}, - Err(e) => return e, - } - - state.user_manager.insert_user(json.uuid, json); - (StatusCode::OK, "ok".to_string()).into_response() -} -// Web End - -// It's an extractor that pulls a token from the Header. -#[derive(PartialEq, Debug)] -pub struct Token(pub Option); - -#[async_trait] -impl FromRequestParts for Token -where - S: Send + Sync, -{ - type Rejection = StatusCode; - - async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { - let token = parts - .headers - .get("token") - .and_then(|value| value.to_str().ok()); - trace!(token = ?token); - match token { - Some(token) => Ok(Self(Some(token.to_string()))), - None => Ok(Self(None)), - } - } -} -// End Extractor - -#[derive(Debug, Clone, PartialEq, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum AuthSystem { - Internal, - ElyBy, - Mojang, -} - -impl ToString for AuthSystem { - fn to_string(&self) -> String { - match self { - AuthSystem::Internal => String::from("internal"), - AuthSystem::ElyBy => String::from("elyby"), - AuthSystem::Mojang => String::from("mojang"), - } - } -} - -impl FromStr for AuthSystem { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - match s { - "internal" => Ok(Self::Internal), - "elyby" => Ok(Self::ElyBy), - "mojang" => Ok(Self::Mojang), - _ => Err(anyhow!("No auth system called: {s}")) - } - } -} - -impl AuthSystem { - fn get_url(&self) -> String { - match self { - AuthSystem::Internal => panic!("Can't get internal URL!"), - AuthSystem::ElyBy => String::from("http://minecraft.ely.by/session/hasJoined"), - AuthSystem::Mojang => String::from("https://sessionserver.mojang.com/session/minecraft/hasJoined"), - } - } -} - -// Work with external APIs -/// Get UUID from JSON response -#[inline] -fn get_id_json(json: &serde_json::Value) -> anyhow::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) -} - -#[inline] -async fn fetch_json( - auth_system: AuthSystem, - server_id: &str, - username: &str, -) -> anyhow::Result> { - let client = reqwest::Client::new(); - let url = auth_system.get_url(); - - let res = client - .get(url) - .query(&[("serverId", server_id), ("username", username)]) - .send() - .await?; - debug!("{res:?}"); - match res.status().as_u16() { - 200 => { - let json = serde_json::from_str::(&res.text().await?)?; - let uuid = get_id_json(&json)?; - Ok(Some((uuid, auth_system))) - } - 401 => Ok(None), // Ely.By None - 204 => Ok(None), // Mojang None - _ => Err(anyhow!("Unknown code: {}", res.status().as_u16())), - } -} - -pub async fn has_joined( - server_id: &str, - username: &str, -) -> anyhow::Result> { - let (elyby, mojang) = ( - fetch_json(AuthSystem::ElyBy,server_id, username).await?, - fetch_json(AuthSystem::Mojang, server_id, username).await? - ); - - if elyby.is_none() && mojang.is_none() { - Ok(None) - } else if mojang.is_some() { - Ok(mojang) - } else if elyby.is_some() { - Ok(elyby) - } else { - panic!("Impossible error!") - } -} -// End of work with external APIs - -// User manager -#[derive(Debug, Clone)] -pub struct UManager { - /// Users with incomplete authentication - pending: Arc>, // TODO: Add automatic purge - /// Authenticated users - authenticated: Arc>, // NOTE: In the future, try it in a separate LockRw branch - /// Registered users - registered: Arc>, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Userinfo { - pub username: String, - pub uuid: Uuid, - pub auth_system: AuthSystem, - pub token: Option, -} - -impl UManager { - pub fn new() -> Self { - Self { - pending: Arc::new(DashMap::new()), - registered: Arc::new(DashMap::new()), - authenticated: Arc::new(DashMap::new()), - } - } - pub fn pending_insert(&self, server_id: String, username: String) { - self.pending.insert(server_id, username); - } - pub fn pending_remove(&self, server_id: &str) -> std::option::Option<(std::string::String, std::string::String)> { - self.pending.remove(server_id) - } - pub fn insert(&self, uuid: Uuid, token: String, userinfo: Userinfo) -> Option { - self.authenticated.insert(token, uuid); - self.registered.insert(uuid, userinfo) - } - pub fn insert_user(&self, uuid: Uuid, userinfo: Userinfo) -> Option { - self.registered.insert(uuid, userinfo) - } - pub fn get( - &self, - token: &String, - ) -> Option> { - let uuid = self.authenticated.get(token)?; - self.registered.get(uuid.value()) - } - pub fn get_by_uuid( - &self, - uuid: &Uuid, - ) -> Option> { - self.registered.get(uuid) - } - pub fn is_authenticated(&self, token: &String) -> bool { - self.authenticated.contains_key(token) - } - pub fn _is_registered(&self, uuid: &Uuid) -> bool { - self.registered.contains_key(uuid) - } - pub fn remove(&self, uuid: &Uuid) { - let token = self.registered.remove(uuid).unwrap().1.token.unwrap(); - self.authenticated.remove(&token); - } -} -// End of User manager \ No newline at end of file diff --git a/src/auth/auth.rs b/src/auth/auth.rs new file mode 100644 index 0000000..748a805 --- /dev/null +++ b/src/auth/auth.rs @@ -0,0 +1,151 @@ +use std::sync::Arc; + +use anyhow::anyhow; +use axum::{ + async_trait, extract::FromRequestParts, http::{request::Parts, StatusCode} +}; +use dashmap::DashMap; +use tracing::{debug, trace}; +use uuid::Uuid; + +use super::types::*; + +// It's an extractor that pulls a token from the Header. +#[derive(PartialEq, Debug)] +pub struct Token(pub Option); + +#[async_trait] +impl FromRequestParts for Token +where + S: Send + Sync, +{ + type Rejection = StatusCode; + + async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { + let token = parts + .headers + .get("token") + .and_then(|value| value.to_str().ok()); + trace!(token = ?token); + match token { + Some(token) => Ok(Self(Some(token.to_string()))), + None => Ok(Self(None)), + } + } +} +// End Extractor + +// Work with external APIs +/// Get UUID from JSON response +#[inline] +fn get_id_json(json: &serde_json::Value) -> anyhow::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) +} + +#[inline] +async fn fetch_json( + auth_system: AuthSystem, + server_id: &str, + username: &str, +) -> anyhow::Result> { + let client = reqwest::Client::new(); + let url = auth_system.get_url(); + + let res = client + .get(url) + .query(&[("serverId", server_id), ("username", username)]) + .send() + .await?; + debug!("{res:?}"); + match res.status().as_u16() { + 200 => { + let json = serde_json::from_str::(&res.text().await?)?; + let uuid = get_id_json(&json)?; + Ok(Some((uuid, auth_system))) + } + 401 => Ok(None), // Ely.By None + 204 => Ok(None), // Mojang None + _ => Err(anyhow!("Unknown code: {}", res.status().as_u16())), + } +} + +pub async fn has_joined( + server_id: &str, + username: &str, +) -> anyhow::Result> { + let (elyby, mojang) = ( + fetch_json(AuthSystem::ElyBy,server_id, username).await?, + fetch_json(AuthSystem::Mojang, server_id, username).await? + ); + + if elyby.is_none() && mojang.is_none() { + Ok(None) + } else if mojang.is_some() { + Ok(mojang) + } else if elyby.is_some() { + Ok(elyby) + } else { + panic!("Impossible error!") + } +} +// End of work with external APIs + +// User manager +#[derive(Debug, Clone)] +pub struct UManager { + /// Users with incomplete authentication + pending: Arc>, // TODO: Add automatic purge + /// Authenticated users TODO: Change name to sessions + authenticated: Arc>, // NOTE: In the future, try it in a separate LockRw branch + /// Registered users + registered: Arc>, +} + +impl UManager { + pub fn new() -> Self { + Self { + pending: Arc::new(DashMap::new()), + registered: Arc::new(DashMap::new()), + authenticated: Arc::new(DashMap::new()), + } + } + pub fn pending_insert(&self, server_id: String, username: String) { + self.pending.insert(server_id, username); + } + pub fn pending_remove(&self, server_id: &str) -> std::option::Option<(std::string::String, std::string::String)> { + self.pending.remove(server_id) + } + pub fn insert(&self, uuid: Uuid, token: String, userinfo: Userinfo) -> Option { + self.authenticated.insert(token, uuid); + self.registered.insert(uuid, userinfo) + } + pub fn insert_user(&self, uuid: Uuid, userinfo: Userinfo) -> Option { + self.registered.insert(uuid, userinfo) + } + pub fn get( + &self, + token: &String, + ) -> Option> { + let uuid = self.authenticated.get(token)?; + self.registered.get(uuid.value()) + } + pub fn get_by_uuid( + &self, + uuid: &Uuid, + ) -> Option> { + self.registered.get(uuid) + } + pub fn is_authenticated(&self, token: &String) -> bool { + self.authenticated.contains_key(token) + } + pub fn _is_registered(&self, uuid: &Uuid) -> bool { + self.registered.contains_key(uuid) + } + pub fn remove(&self, uuid: &Uuid) { + let token = self.registered.remove(uuid).unwrap().1.token.unwrap(); + self.authenticated.remove(&token); + } +} +// End of User manager \ No newline at end of file diff --git a/src/auth/mod.rs b/src/auth/mod.rs new file mode 100644 index 0000000..d24c6e7 --- /dev/null +++ b/src/auth/mod.rs @@ -0,0 +1,5 @@ +mod auth; +mod types; + +pub use auth::*; +pub use types::*; diff --git a/src/auth/types.rs b/src/auth/types.rs new file mode 100644 index 0000000..b88a16a --- /dev/null +++ b/src/auth/types.rs @@ -0,0 +1,56 @@ +use std::str::FromStr; + +use serde::Deserialize; +use uuid::Uuid; +use anyhow::anyhow; + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Userinfo { + pub username: String, + pub uuid: Uuid, + pub auth_system: AuthSystem, + pub token: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum AuthSystem { + Internal, + ElyBy, + Mojang, +} + +impl ToString for AuthSystem { + fn to_string(&self) -> String { + match self { + AuthSystem::Internal => String::from("internal"), + AuthSystem::ElyBy => String::from("elyby"), + AuthSystem::Mojang => String::from("mojang"), + } + } +} + +impl FromStr for AuthSystem { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "internal" => Ok(Self::Internal), + "elyby" => Ok(Self::ElyBy), + "mojang" => Ok(Self::Mojang), + _ => Err(anyhow!("No auth system called: {s}")) + } + } +} + +impl AuthSystem { + pub(super) fn get_url(&self) -> String { + match self { + AuthSystem::Internal => panic!("Can't get internal URL!"), + AuthSystem::ElyBy => String::from("http://minecraft.ely.by/session/hasJoined"), + AuthSystem::Mojang => String::from("https://sessionserver.mojang.com/session/minecraft/hasJoined"), + } + } +} + diff --git a/src/main.rs b/src/main.rs index 53625bc..49ca1cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,29 +9,44 @@ use tower_http::trace::TraceLayer; use tracing::info; use uuid::Uuid; -// WebSocket worker -mod ws; -use ws::handler; +// // WebSocket worker +// mod ws; +// use ws::handler; -// API: Auth +// // API: Auth +// mod auth; +// use auth::{self as api_auth, UManager}; + +// // API: Server info +// mod info; +// use info as api_info; + +// // API: Profile +// mod profile; +// use profile as api_profile; + +// API +mod api; +use api::{ + figura::{ws, info as api_info, profile as api_profile, auth as api_auth}, + // v1::{}, +}; + +// Auth mod auth; -use auth::{self as api_auth, UManager}; +use auth::UManager; -// API: Server info -mod info; -use info as api_info; - -// API: Profile -mod profile; -use profile as api_profile; +// Config +mod state; +use state::Config; // Utils mod utils; use utils::update_advanced_users; -// Config -mod config; -use config::Config; +// // Config +// mod config; +// use config::Config; #[derive(Debug, Clone)] pub struct AppState { @@ -42,7 +57,7 @@ pub struct AppState { /// Ping broadcasts for WebSocket connections broadcasts: Arc>>>, /// Current configuration - config: Arc>, + config: Arc>, } const LOGGER_ENV: &'static str = "RUST_LOG"; @@ -94,13 +109,9 @@ async fn main() -> Result<()> { } }); - let v1 = Router::new() - .nest("/", ws::http2ws_router()) - .nest("/user", api_auth::router_v1()); - let api = Router::new() .nest("//auth", api_auth::router()) - .nest("/v1", v1) + .nest("/v1", api::v1::router()) .route("/limits", get(api_info::limits)) .route("/version", get(api_info::version)) .route("/motd", get(api_info::motd)) @@ -112,10 +123,9 @@ async fn main() -> Result<()> { let app = Router::new() .nest("/api", api) - .route("/api/", get(api_auth::status)) - .route("/ws", get(handler)) + .route("/ws", get(ws)) .route("/health", get(|| async { "ok" })) - .route_layer(from_extractor::()) + .route_layer(from_extractor::()) .with_state(state) .layer(TraceLayer::new_for_http().on_request(())); diff --git a/src/config.rs b/src/state/config.rs similarity index 100% rename from src/config.rs rename to src/state/config.rs diff --git a/src/state/mod.rs b/src/state/mod.rs new file mode 100644 index 0000000..31bcd43 --- /dev/null +++ b/src/state/mod.rs @@ -0,0 +1,4 @@ +mod config; +mod state; + +pub use config::*; \ No newline at end of file diff --git a/src/state/state.rs b/src/state/state.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ws/mod.rs b/src/ws/mod.rs deleted file mode 100644 index ace8544..0000000 --- a/src/ws/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod types; -mod websocket; -mod http; - -pub use types::C2SMessage; -pub use types::S2CMessage; -pub use websocket::handler; -pub use http::router as http2ws_router; \ No newline at end of file