mirror of
https://github.com/shiroyashik/sculptor.git
synced 2025-12-06 04:51:13 +03:00
The user system has been refactored
This commit is contained in:
parent
3d64d60b2b
commit
703a2727e7
6 changed files with 149 additions and 87 deletions
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
108
src/auth.rs
108
src/auth.rs
|
|
@ -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
|
||||
81
src/main.rs
81
src/main.rs
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
20
src/utils.rs
20
src/utils.rs
|
|
@ -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 стоит убрать
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue