Limitations management and real-time MOTD update added.

- deleted unnecessary comments
- korewaChino artefacts removed
This commit is contained in:
Shiroyasha 2024-06-08 13:34:35 +03:00
parent c53c10cb0a
commit cfea3e6e71
Signed by: shiroyashik
GPG key ID: E4953D3940D7860A
12 changed files with 79 additions and 84 deletions

View file

@ -3,8 +3,8 @@ name: Create and publish a Docker image
on:
workflow_dispatch:
push:
branches:
- master
tags:
- v*.*.*
permissions:
packages: write

View file

@ -1 +1,7 @@
Many thanks to PoolloverNathan (https://github.com/PoolloverNathan) and Martinz64 (https://github.com/Martinz64) for their work on which Sculptor was developed.
Many thanks to:
PoolloverNathan (https://github.com/PoolloverNathan)
Martinz64 (https://github.com/Martinz64)
for their work on which Sculptor was developed.
Contributors:
korewaChino (https://github.com/korewaChino)

2
Cargo.lock generated
View file

@ -1134,7 +1134,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sculptor"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"anyhow",
"anyhow-http",

View file

@ -40,6 +40,10 @@ motd = """
]
"""
[limitations]
maxAvatarSize = 100000
maxAvatars = 10
# Shiroyashik
[advancedUsers.66004548-4de5-49de-bade-9c3933d8eb97]
special = [0,0,0,1,0,0] # 6

View file

@ -6,8 +6,6 @@ Implements Ping transmission functionality via Websocket and full avatar upload
And also a distinctive feature is the possibility of player identification through the third-party authorization system [Ely.By](https://ely.by/)
> This is a personal fork of Sculptor, made to be a workaround for a private server.
### Running with Docker
You will need an already configured Docker with Traefik (you can use any reverse proxy)
@ -28,8 +26,13 @@ To do this, you will need to reverse proxy port 6665 to your domain with SSL
3. Set up your reverse proxy server
4. `cargo run`
### TODO:
- [ ] Realization of storing profiles in the database
- [ ] Frontend for moderation
- [ ] Autonomous working without reverse proxy server
- [ ] and many other...
### Public server
I'm keeping the public server running at the moment!
You can use it if running your own Sculptor instance is difficult for you.
> figura.shsr.ru
For reasons beyond my control, the server is not available in some countries.
[Check server availability](https://figura.shsr.ru/health)

View file

@ -2,17 +2,22 @@ name: sculptor
services:
sculptor:
build: .
image: ghcr.io/korewachino/sculptor:latest
# build: .
image: ghcr.io/shiroyashik/sculptor:latest
container_name: sculptor
restart: unless-stopped
volumes:
- ./Config.toml:/app/Config.toml:ro
- ./avatars:/app/avatars
## Recommended for use with reverse proxy.
# networks:
# - traefik
# labels:
# - traefik.enable=true
# - traefik.http.routers.sculptor.rule=Host(`mc.example.com`)
# - traefik.http.routers.sculptor.entrypoints=websecure, web
# - traefik.http.routers.sculptor.tls=true
# - traefik.http.routers.sculptor.tls.certresolver=production
# networks:
# traefik:
# external: true

View file

@ -56,7 +56,6 @@ async fn verify(
if let Some((uuid, auth_system)) = has_joined(&server_id, &username).await.unwrap() {
info!("[Authorization] {username} logged in using {auth_system:?}");
let authenticated = state.authenticated;
// let link = state.authenticated_link.lock().await; // // Реализация поиска пользователя в HashMap по UUID
authenticated.insert(
uuid,
server_id.clone(),
@ -66,7 +65,6 @@ async fn verify(
auth_system,
},
);
// link.insert(uuid, crate::AuthenticatedLink(server_id.clone())); // Реализация поиска пользователя в HashMap по UUID
server_id.to_string()
} else {
String::from("failed to verify")
@ -135,7 +133,6 @@ impl ToString for AuthSystem {
}
/// Get UUID from JSON response
// Written to be reusable so we don't have to specify the same complex code twice
#[inline]
fn get_id_json(json: &serde_json::Value) -> anyhow::Result<Uuid> {
trace!("json: {json:#?}"); // For debugging, we'll get to this later!
@ -143,8 +140,6 @@ fn get_id_json(json: &serde_json::Value) -> anyhow::Result<Uuid> {
Ok(uuid)
}
// Considering dropping ely.by support here, I don't really want to deal with it
#[inline]
async fn fetch_json(
url: &str,
@ -181,39 +176,6 @@ pub async fn has_joined(
server_id: &str,
username: &str,
) -> anyhow::Result<Option<(Uuid, AuthSystem)>> {
// let client = reqwest::Client::new();
// tokio::select! {
// Ok(Some(res)) = async {
// let res = client.clone().get(
// format!("http://minecraft.ely.by/session/hasJoined?serverId={server_id}&username={username}")).send().await?;
// debug!("{res:?}");
// match res.status().as_u16() {
// 200 => {
// let json = serde_json::from_str::<serde_json::Value>(&res.text().await?)?;
// let uuid = get_id_json(&json)?;
// Ok(Some((uuid, AuthSystem::ElyBy)))
// },
// 401 => Ok(None),
// _ => Err(anyhow::anyhow!("Unknown code: {}", res.status().as_u16()))
// }
// } => {Ok(Some(res))}
// Ok(Some(res)) = async {
// let res = client.clone().get(
// format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?serverId={server_id}&username={username}")).send().await?;
// debug!("{res:?}");
// match res.status().as_u16() {
// 200 => {
// let json = serde_json::from_str::<serde_json::Value>(&res.text().await?)?;
// let uuid = get_id_json(&json)?;
// Ok(Some((uuid, AuthSystem::Mojang)))
// },
// 204 => Ok(None),
// _ => Err(anyhow::anyhow!("Unknown code: {}", res.status().as_u16()))
// }
// } => {Ok(Some(res))}
// else => {Err(anyhow!("Something went wrong in external apis request process"))}
// }
tokio::select! {
Ok(Some(res)) = fetch_json("http://minecraft.ely.by/session/hasJoined", server_id, username) => {Ok(Some(res))},
Ok(Some(res)) = fetch_json("https://sessionserver.mojang.com/session/minecraft/hasJoined", server_id, username) => {Ok(Some(res))},

View file

@ -3,14 +3,22 @@ use std::{io::Read, path::PathBuf};
use serde::Deserialize;
use toml::Table;
#[derive(Deserialize, Clone, Debug)]
#[derive(Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Config {
pub listen: String,
pub motd: String,
pub limitations: Limitations,
pub advanced_users: Table,
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Limitations {
pub max_avatar_size: u64,
pub max_avatars: u64,
}
impl Config {
pub fn parse(path: PathBuf) -> Self {
let mut file = std::fs::File::open(path).expect("Access denied or file doesn't exists!");

View file

@ -1,6 +1,14 @@
use axum::Json;
use axum::{extract::State, Json};
use serde_json::{json, Value};
use crate::AppState;
/// Assert health of the server
/// If times out, the server is considered dead, so we can return basically anything
pub async fn health_check() -> String {
"ok".to_string()
}
pub async fn version() -> Json<Value> {
Json(json!({
"release": "0.1.4",
@ -8,7 +16,12 @@ pub async fn version() -> Json<Value> {
}))
}
pub async fn limits() -> Json<Value> {
pub async fn motd(State(state): State<AppState>) -> String {
state.config.lock().await.motd.clone()
}
pub async fn limits(State(state): State<AppState>) -> Json<Value> {
let state = &state.config.lock().await.limitations;
Json(json!({
"rate": {
"pingSize": 1024,
@ -18,8 +31,8 @@ pub async fn limits() -> Json<Value> {
"upload": 1
},
"limits": {
"maxAvatarSize": 100000,
"maxAvatars": 10,
"maxAvatarSize": state.max_avatar_size,
"maxAvatars": state.max_avatars,
"allowedBadges": {
"special": [0,0,0,0,0,0],
"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

@ -90,18 +90,10 @@ pub struct AppState {
authenticated: Arc<Authenticated>, // <SHA1 serverId, Userinfo> NOTE: In the future, try it in a separate LockRw branch
// Ping broadcasts for WebSocket connections
broadcasts: Arc<DashMap<Uuid, broadcast::Sender<Vec<u8>>>>,
// Advanced configured users
advanced_users: Arc<Mutex<toml::Table>>,
// Current configuration
config: Arc<Mutex<config::Config>>,
}
/// Assert health of the server
/// If times out, the server is considered dead, so we can return basically anything
async fn health_check() -> String {
// tokio::time::sleep(std::time::Duration::from_secs(5)).await;
"ok".to_string()
}
const LOGGER_ENV: &str = "RUST_LOG";
#[tokio::main]
@ -118,29 +110,30 @@ async fn main() -> Result<()> {
let config_file = std::env::var("CONFIG_PATH").unwrap_or_else(|_| "Config.toml".into());
info!("The Sculptor MMSI edition v{}", env!("CARGO_PKG_VERSION"));
info!("The Sculptor v{}", env!("CARGO_PKG_VERSION"));
// Config
let config = config::Config::parse(config_file.clone().into());
let listen = config.listen.as_str();
let config = Arc::new(Mutex::new(config::Config::parse(config_file.clone().into())));
let listen = config.lock().await.listen.clone();
// State
let state = AppState {
pending: Arc::new(DashMap::new()),
authenticated: Arc::new(Authenticated::new()),
broadcasts: Arc::new(DashMap::new()),
advanced_users: Arc::new(Mutex::new(config.advanced_users)),
config: config,
};
// Automatic update of advanced_users while the server is running
let advanced_users = state.advanced_users.clone();
// Automatic update of configuration while the server is running
let config_update = state.config.clone();
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()).advanced_users;
let mut config = advanced_users.lock().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;
}
}
@ -148,20 +141,20 @@ async fn main() -> Result<()> {
let api = Router::new()
.nest("//auth", api_auth::router())
.route("/limits", get(api_info::limits)) // TODO:
.route("/limits", get(api_info::limits))
.route("/version", get(api_info::version))
.route("/motd", get(|| async { config.motd }))
.route("/motd", get(api_info::motd))
.route("/equip", post(api_profile::equip_avatar))
.route("/:uuid", get(api_profile::user_info))
.route("/:uuid/avatar", get(api_profile::download_avatar))
.route("/avatar", put(api_profile::upload_avatar))
.route("/avatar", delete(api_profile::delete_avatar)); // delete Avatar
.route("/avatar", delete(api_profile::delete_avatar));
let app = Router::new()
.nest("/api", api)
.route("/api/", get(api_auth::status))
.route("/ws", get(handler))
.route("/health", get(health_check))
.route("/health", get(api_info::health_check))
.route_layer(from_extractor::<api_auth::Token>())
.with_state(state)
.layer(TraceLayer::new_for_http().on_request(()));

View file

@ -23,7 +23,7 @@ use crate::{
#[debug_handler]
pub async fn user_info(
Path(uuid): Path<Uuid>,
State(state): State<AppState>, // FIXME: Variable doesn't using!
State(state): State<AppState>,
) -> Json<Value> {
tracing::info!("Receiving profile information for {}", uuid);
@ -47,10 +47,10 @@ pub async fn user_info(
},
"version": "0.1.4+1.20.1",
"banned": false,
"authSystem": auth_system // add Trust
"authSystem": auth_system
});
if let Some(settings) = state.advanced_users.lock().await.get(&formatted_uuid) {
if let Some(settings) = state.config.lock().await.advanced_users.get(&formatted_uuid) {
let pride = get_correct_array(settings.get("pride").unwrap());
let special = get_correct_array(settings.get("special").unwrap());
let badges = user_info_response
@ -141,7 +141,7 @@ pub async fn equip_avatar(Token(token): Token, State(state): State<AppState>) ->
.is_err()
{
warn!("[WebSocket] Failed to send Event! Maybe there is no one to send")
// FIXME: Засунуть в Handler
// TODO: Put into Handler
};
"ok".to_string()
}

View file

@ -5,7 +5,7 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
use ring::digest::{self, digest};
use uuid::Uuid;
// Кор функции
// Core functions
pub fn rand() -> [u8; 50] {
let mut rng = thread_rng();
let distr = rand::distributions::Uniform::new_inclusive(0, 255);
@ -15,6 +15,7 @@ pub fn rand() -> [u8; 50] {
}
nums
}
//? What is this guy doing
#[tracing::instrument]
pub fn bytes_into_string(code: &[u8]) -> String {
@ -27,7 +28,7 @@ pub fn bytes_into_string(code: &[u8]) -> String {
// String::from_utf8_lossy(code).to_string() // Tried this, causes corrupted string
}
// Конец кор функций
// End of Core functions
pub fn _generate_hex_string(length: usize) -> String {
// FIXME: Variable doesn't using!