The user system has been refactored

This commit is contained in:
Shiroyasha 2024-06-15 19:27:50 +03:00
parent 3d64d60b2b
commit 703a2727e7
Signed by: shiroyashik
GPG key ID: E4953D3940D7860A
6 changed files with 149 additions and 87 deletions

View file

@ -44,13 +44,16 @@ motd = """
maxAvatarSize = 100000
maxAvatars = 10
# Shiroyashik
[advancedUsers.66004548-4de5-49de-bade-9c3933d8eb97]
username = "Shiroyashik"
authSystem = "elyby"
special = [0,0,0,1,0,0] # 6
pride = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] # 25
# Your nickname here
# [advancedUsers.your uuid here]
# username = "Bot"
# authSystem = "mojang" # can be: mojang, elyby, internal (cant be authenticated)
# special = [0,1,0,0,0,0] # and set badges what you want! :D
# pride = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

View file

@ -1,3 +1,5 @@
use std::{str::FromStr, sync::Arc};
use crate::utils::*;
use anyhow::anyhow;
use axum::{
@ -8,6 +10,7 @@ use axum::{
routing::get,
Router,
};
use dashmap::DashMap;
use ring::digest::{self, digest};
use serde::Deserialize;
use tracing::{debug, error, info, trace};
@ -35,8 +38,8 @@ async fn id(
) -> String {
let server_id =
hex::encode(&digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, &rand()).as_ref()[0..20]);
let state = state.pending;
state.insert(server_id.clone(), query.username);
let state = state.user_manager;
state.pending_insert(server_id.clone(), query.username);
server_id
}
@ -52,7 +55,7 @@ async fn verify(
State(state): State<AppState>,
) -> Response {
let server_id = query.id.clone();
let username = state.pending.remove(&server_id).unwrap().1; // TODO: Add error check
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) => {
@ -62,14 +65,15 @@ async fn verify(
};
if let Some((uuid, auth_system)) = userinfo {
info!("[Authentication] {username} logged in using {auth_system:?}");
let authenticated = state.authenticated;
let authenticated = state.user_manager;
authenticated.insert(
uuid,
server_id.clone(),
crate::Userinfo {
Userinfo {
username,
uuid,
auth_system,
token: Some(server_id.clone()),
},
);
(StatusCode::OK, server_id.to_string()).into_response()
@ -82,7 +86,7 @@ async fn verify(
pub async fn status(Token(token): Token, State(state): State<AppState>) -> Response {
match token {
Some(token) => {
if state.authenticated.contains_token(&token) {
if state.user_manager.is_authenticated(&token) {
(StatusCode::OK, "ok".to_string()).into_response()
} else {
@ -121,9 +125,9 @@ where
}
// End Extractor
// Work with external APIs
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub enum AuthSystem {
Internal,
ElyBy,
Mojang,
}
@ -131,21 +135,37 @@ pub enum AuthSystem {
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<Self, Self::Err> {
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<Uuid> {
@ -199,11 +219,71 @@ pub async fn has_joined(
} else {
panic!("Impossible error!")
}
// FOR DELETE
// tokio::select! {
// Ok(res) = tokio::spawn(fetch_json(AuthSystem::ElyBy, server_id, username)) => {Ok(res)},
// Ok(res) = tokio::spawn(fetch_json(AuthSystem::Mojang, server_id, username)) => {Ok(res)},
// else => {Err(anyhow!("Something went wrong in external apis request process"))}
// }
}
// End of work with external APIs
// User manager
#[derive(Debug, Clone)]
pub struct UManager {
/// Users with incomplete authentication
pending: Arc<DashMap<String, String>>, // <SHA1 serverId, USERNAME> TODO: Add automatic purge
/// Authenticated users
authenticated: Arc<DashMap<String, Uuid>>, // <SHA1 serverId, Userinfo> NOTE: In the future, try it in a separate LockRw branch
/// Registered users
registered: Arc<DashMap<Uuid, Userinfo>>,
}
#[derive(Debug, Clone)]
pub struct Userinfo {
pub username: String,
pub uuid: Uuid,
pub auth_system: AuthSystem,
pub token: Option<String>,
}
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<Userinfo> {
self.authenticated.insert(token, uuid);
self.registered.insert(uuid, userinfo)
}
pub fn insert_user(&self, uuid: Uuid, userinfo: Userinfo) -> Option<Userinfo> {
self.registered.insert(uuid, userinfo)
}
pub fn get(
&self,
token: &String,
) -> Option<dashmap::mapref::one::Ref<'_, Uuid, Userinfo>> {
let uuid = self.authenticated.get(token)?;
self.registered.get(uuid.value())
}
pub fn get_by_uuid(
&self,
uuid: &Uuid,
) -> Option<dashmap::mapref::one::Ref<'_, Uuid, Userinfo>> {
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

View file

@ -3,6 +3,7 @@ use axum::{
extract::DefaultBodyLimit, middleware::from_extractor, routing::{delete, get, post, put}, Router
};
use dashmap::DashMap;
use utils::collect_advanced_users;
use std::sync::Arc;
use tokio::sync::{broadcast, Mutex};
use tower_http::trace::TraceLayer;
@ -15,7 +16,7 @@ use ws::handler;
// API: Auth
mod auth;
use auth as api_auth;
use auth::{self as api_auth, UManager};
// API: Server info
mod info;
@ -31,64 +32,17 @@ mod utils;
// Config
mod config;
#[derive(Debug, Clone)]
pub struct Userinfo {
username: String,
uuid: Uuid,
auth_system: api_auth::AuthSystem,
}
#[derive(Debug, Clone)]
struct Authenticated {
user_data: DashMap<String, Userinfo>,
uuid: DashMap<Uuid, String>,
}
impl Authenticated {
fn new() -> Self {
Self {
user_data: DashMap::new(),
uuid: DashMap::new(),
}
}
pub fn insert(&self, uuid: Uuid, token: String, userinfo: Userinfo) -> Option<Userinfo> {
self.uuid.insert(uuid, token.clone());
self.user_data.insert(token, userinfo)
}
pub fn get(
&self,
token: &String,
) -> Option<dashmap::mapref::one::Ref<'_, std::string::String, Userinfo>> {
self.user_data.get(token)
}
pub fn get_by_uuid(
&self,
uuid: &Uuid,
) -> Option<dashmap::mapref::one::Ref<'_, std::string::String, Userinfo>> {
if let Some(token) = self.uuid.get(uuid) {
self.user_data.get(&token.clone())
} else {
None
}
}
pub fn contains_token(&self, token: &String) -> bool {
self.user_data.contains_key(token)
}
pub fn remove(&self, uuid: &Uuid) {
let token = self.uuid.remove(uuid).unwrap().1;
self.user_data.remove(&token);
}
}
#[derive(Debug, Clone)]
pub struct AppState {
// Users with incomplete authentication
pending: Arc<DashMap<String, String>>, // <SHA1 serverId, USERNAME>
// Authenticated users
authenticated: Arc<Authenticated>, // <SHA1 serverId, Userinfo> NOTE: In the future, try it in a separate LockRw branch
// Ping broadcasts for WebSocket connections
/// Users with incomplete authentication
//pending: Arc<DashMap<String, String>>, // <SHA1 serverId, USERNAME>
/// Authenticated users
//authenticated: Arc<Authenticated>, // <SHA1 serverId, Userinfo> NOTE: In the future, try it in a separate LockRw branch
/// User manager
user_manager: Arc<UManager>,
/// Ping broadcasts for WebSocket connections
broadcasts: Arc<DashMap<Uuid, broadcast::Sender<Vec<u8>>>>,
// Current configuration
/// Current configuration
config: Arc<Mutex<config::Config>>,
}
@ -117,25 +71,32 @@ async fn main() -> Result<()> {
// State
let state = AppState {
pending: Arc::new(DashMap::new()),
authenticated: Arc::new(Authenticated::new()),
user_manager: Arc::new(UManager::new()),
broadcasts: Arc::new(DashMap::new()),
config: config,
};
// Automatic update of configuration while the server is running
let config_update = state.config.clone();
let user_manager = Arc::clone(&state.user_manager);
tokio::spawn(async move {
loop {
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
let new_config = config::Config::parse(config_file.clone().into());
let mut config = config_update.lock().await;
if new_config != *config {
info!("Server configuration modification detected!");
*config = new_config;
// let collected = collect_advanced_users(&config.advanced_users);
// for (uuid, userinfo) in collected {
// user_manager.insert_user(uuid, userinfo);
// }
}
let collected = collect_advanced_users(&config.advanced_users);
for (uuid, userinfo) in collected {
user_manager.insert_user(uuid, userinfo);
}
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
}
});

View file

@ -29,7 +29,7 @@ pub async fn user_info(
let avatar_file = format!("avatars/{}.moon", formatted_uuid);
let auth_system = match state.authenticated.get_by_uuid(&uuid) {
let auth_system = match state.user_manager.get_by_uuid(&uuid) {
Some(d) => d.auth_system.to_string(),
None => return Json(json!("err")),
};
@ -113,7 +113,7 @@ pub async fn upload_avatar(
None => http_error_ret!(UNAUTHORIZED, "Authentication error!"),
};
if let Some(user_info) = state.authenticated.get(&token) {
if let Some(user_info) = state.user_manager.get(&token) {
tracing::info!(
"{} ({}) trying to upload an avatar",
user_info.uuid,
@ -128,7 +128,7 @@ pub async fn upload_avatar(
pub async fn equip_avatar(Token(token): Token, State(state): State<AppState>) -> String {
debug!("[API] S2C : Equip");
let uuid = state.authenticated.get(&token.unwrap()).unwrap().uuid;
let uuid = state.user_manager.get(&token.unwrap()).unwrap().uuid;
if state
.broadcasts
.get(&uuid)
@ -147,7 +147,7 @@ pub async fn delete_avatar(Token(token): Token, State(state): State<AppState>) -
Some(t) => t,
None => http_error_ret!(UNAUTHORIZED, "Authentication error!"),
};
if let Some(user_info) = state.authenticated.get(&token) {
if let Some(user_info) = state.user_manager.get(&token) {
tracing::info!(
"{} ({}) is trying to delete the avatar",
user_info.uuid,

View file

@ -1,10 +1,12 @@
use std::{fs::File, io::Read};
use std::{fs::File, io::Read, str::FromStr};
use base64::prelude::*;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use ring::digest::{self, digest};
use uuid::Uuid;
use crate::auth::{AuthSystem, Userinfo};
// Core functions
pub fn rand() -> [u8; 50] {
let mut rng = thread_rng();
@ -34,6 +36,22 @@ pub fn get_correct_array(value: &toml::Value) -> Vec<u8> {
.map(move |x| x.as_integer().unwrap() as u8)
.collect()
}
pub fn collect_advanced_users(value: &toml::Table) -> Vec<(Uuid, Userinfo)> {
value
.iter()
.map( |(uuid, userdata)| {
let auth_system = AuthSystem::from_str(userdata.as_table().unwrap().get("authSystem").expect("Can't find authSystem in advancedUser!").as_str().unwrap()).unwrap();
let username = userdata.as_table().unwrap().get("username").expect("Can't find username in advancedUser!").as_str().unwrap().to_string();
(
Uuid::parse_str(uuid).unwrap(),
Userinfo { username,
uuid: Uuid::parse_str(uuid).unwrap(),
auth_system,
token: None
}
)})
.collect()
}
pub fn format_uuid(uuid: &Uuid) -> String {
// let uuid = Uuid::parse_str(&uuid)?; TODO: Вероятно format_uuid стоит убрать

View file

@ -59,7 +59,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) {
info!("[WebSocket{}] Connection successfully closed!", owner.name());
if let Some(u) = owner.0 {
state.broadcasts.remove(&u.uuid);
state.authenticated.remove(&u.uuid);
state.user_manager.remove(&u.uuid);
}
return;
}
@ -68,7 +68,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) {
debug!("[WebSocket{}] Receive error! Connection terminated!", owner.name());
if let Some(u) = owner.0 {
state.broadcasts.remove(&u.uuid);
state.authenticated.remove(&u.uuid);
state.user_manager.remove(&u.uuid);
}
return;
};
@ -82,7 +82,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) {
error!("[WebSocket{}] This message is not from Figura! {e:?}", owner.name());
if let Some(u) = owner.0 {
state.broadcasts.remove(&u.uuid);
state.authenticated.remove(&u.uuid);
state.user_manager.remove(&u.uuid);
}
return;
},
@ -94,7 +94,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) {
C2SMessage::Token(token) => {
debug!("[WebSocket{}] C2S : Token", owner.name());
let token = String::from_utf8(token.to_vec()).unwrap();
match state.authenticated.get(&token) { // The principle is simple: if there is no token in authenticated, then it's "dirty hacker" :D
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) => {
//username = t.username.clone();
owner.0 = Some(WSUser { username: t.username.clone(), token, uuid: t.uuid });
@ -115,7 +115,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) {
debug!("[WebSocket] Tried to log in with {token}"); // Tried to log in with token: {token}
if let Some(u) = owner.0 {
state.broadcasts.remove(&u.uuid);
state.authenticated.remove(&u.uuid);
state.user_manager.remove(&u.uuid);
}
return; // TODO: Define the trip code
},
@ -170,7 +170,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) {
warn!("[WebSocket{}] Send error! Connection terminated!", owner.name());
if let Some(u) = owner.0 {
state.broadcasts.remove(&u.uuid);
state.authenticated.remove(&u.uuid);
state.user_manager.remove(&u.uuid);
}
return;
}
@ -184,7 +184,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) {
warn!("[WebSocketSubscriber{}] Send error! Connection terminated!", owner.name());
if let Some(u) = owner.0 {
state.broadcasts.remove(&u.uuid);
state.authenticated.remove(&u.uuid);
state.user_manager.remove(&u.uuid);
}
return;
}