mirror of
https://github.com/shiroyashik/sculptor.git
synced 2025-12-06 13:01:12 +03:00
Upgrade/Migrate to newer dependencies (check diff Cargo.toml)
Some checks failed
Push Dev / docker (push) Has been cancelled
Some checks failed
Push Dev / docker (push) Has been cancelled
+ Rename: api/v1 to api/sculptor
This commit is contained in:
parent
59ca04d5f8
commit
c7c3bd881f
20 changed files with 470 additions and 390 deletions
|
|
@ -12,8 +12,8 @@ use crate::{api::errors::internal_and_log, ApiError, ApiResult, AppState, ASSETS
|
|||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(versions))
|
||||
.route("/:version", get(hashes))
|
||||
.route("/:version/*key", get(download))
|
||||
.route("/{version}", get(hashes))
|
||||
.route("/{version}/{*path}", get(download))
|
||||
}
|
||||
|
||||
async fn versions() -> ApiResult<Json<Value>> {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use anyhow::bail;
|
||||
use axum::extract::{ws::{Message, WebSocket}, State};
|
||||
use axum::{body::Bytes, extract::{ws::{Message, WebSocket}, State}};
|
||||
use dashmap::DashMap;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::{auth::Userinfo, AppState};
|
||||
|
||||
use super::{processor::*, AuthModeError, S2CMessage, C2SMessage, WSSession, SessionMessage, RADError};
|
||||
use super::{AuthModeError, C2SMessage, RADError, RecvAndDecode, S2CMessage, SessionMessage, WSSession};
|
||||
|
||||
pub async fn initial(
|
||||
ws: axum::extract::WebSocketUpgrade,
|
||||
|
|
@ -42,9 +43,8 @@ async fn handle_socket(mut ws: WebSocket, state: AppState) {
|
|||
};
|
||||
|
||||
// Starting main worker
|
||||
match main_worker(&mut session, &mut ws, &state).await {
|
||||
Ok(_) => (),
|
||||
Err(kind) => tracing::error!("[WebSocket] Main worker halted due to {}.", kind),
|
||||
if let Err(kind) = main_worker(&mut session, &mut ws, &state).await {
|
||||
tracing::error!("[WebSocket] Main worker halted due to {}.", kind)
|
||||
}
|
||||
|
||||
for (_, handle) in session.sub_workers_aborthandles {
|
||||
|
|
@ -61,9 +61,10 @@ async fn handle_socket(mut ws: WebSocket, state: AppState) {
|
|||
}
|
||||
|
||||
// Closing connection
|
||||
if let Err(kind) = ws.close().await { tracing::trace!("[WebSocket] Closing fault: {}", kind) }
|
||||
if let Err(kind) = ws.send(Message::Close(None)).await { tracing::trace!("[WebSocket] Closing fault: {}", kind) }
|
||||
}
|
||||
|
||||
#[instrument(skip_all, fields(nickname = %session.user.nickname))]
|
||||
async fn main_worker(session: &mut WSSession, ws: &mut WebSocket, state: &AppState) -> anyhow::Result<()> {
|
||||
tracing::debug!("WebSocket control for {} is transferred to the main worker", session.user.nickname);
|
||||
loop {
|
||||
|
|
@ -90,7 +91,7 @@ async fn main_worker(session: &mut WSSession, ws: &mut WebSocket, state: &AppSta
|
|||
|
||||
// Echo check
|
||||
if echo {
|
||||
ws.send(Message::Binary(s2c_ping.clone())).await?
|
||||
ws.send(Message::Binary(s2c_ping.clone().into())).await?
|
||||
}
|
||||
// Sending to others
|
||||
let _ = session.subs_tx.send(s2c_ping);
|
||||
|
|
@ -127,7 +128,7 @@ async fn main_worker(session: &mut WSSession, ws: &mut WebSocket, state: &AppSta
|
|||
let internal_msg = internal_msg.ok_or(anyhow::anyhow!("Unexpected error! Session channel broken!"))?;
|
||||
match internal_msg {
|
||||
SessionMessage::Ping(msg) => {
|
||||
ws.send(Message::Binary(msg)).await?
|
||||
ws.send(Message::Binary(msg.into())).await?
|
||||
},
|
||||
SessionMessage::Banned => {
|
||||
let _ = ban_action(ws).await
|
||||
|
|
@ -169,7 +170,7 @@ async fn authenticate(socket: &mut WebSocket, state: &AppState) -> Result<Userin
|
|||
let token = String::from_utf8(token.to_vec()).map_err(|_| AuthModeError::ConvertError)?;
|
||||
match state.user_manager.get(&token) {
|
||||
Some(user) => {
|
||||
if socket.send(Message::Binary(S2CMessage::Auth.into())).await.is_err() {
|
||||
if socket.send(Message::Binary(Bytes::from(Into::<Vec<u8>>::into(S2CMessage::Auth)))).await.is_err() {
|
||||
Err(AuthModeError::SendError)
|
||||
} else if !user.banned {
|
||||
Ok(user.clone())
|
||||
|
|
@ -204,7 +205,7 @@ async fn authenticate(socket: &mut WebSocket, state: &AppState) -> Result<Userin
|
|||
}
|
||||
|
||||
async fn ban_action(ws: &mut WebSocket) -> anyhow::Result<()> {
|
||||
ws.send(Message::Binary(S2CMessage::Toast(2, "You're banned!".to_string(), None).into())).await?;
|
||||
ws.send(Message::Binary(Into::<Vec<u8>>::into(S2CMessage::Toast(2, "You're banned!".to_string(), None)).into())).await?;
|
||||
tokio::time::sleep(std::time::Duration::from_secs(6)).await;
|
||||
ws.send(Message::Close(Some(axum::extract::ws::CloseFrame { code: 4001, reason: "You're banned!".into() }))).await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// mod websocket;
|
||||
mod handler;
|
||||
mod processor;
|
||||
mod types;
|
||||
|
||||
// pub use websocket::*;
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
use axum::extract::ws::{Message, WebSocket};
|
||||
|
||||
use super::{C2SMessage, RADError};
|
||||
|
||||
pub trait RecvAndDecode {
|
||||
async fn recv_and_decode(&mut self) -> Result<C2SMessage, RADError>;
|
||||
}
|
||||
|
||||
impl RecvAndDecode for WebSocket {
|
||||
async fn recv_and_decode(&mut self) -> Result<C2SMessage, RADError> {
|
||||
if let Some(msg) = self.recv().await {
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
match msg {
|
||||
Message::Close(frame) => Err(RADError::Close(frame.map(|f| format!("code: {}, reason: {}", f.code, f.reason)))),
|
||||
_ => {
|
||||
match C2SMessage::try_from(msg.clone().into_data().as_slice()) {
|
||||
Ok(decoded) => Ok(decoded),
|
||||
Err(e) => {
|
||||
Err(RADError::DecodeError(e, faster_hex::hex_string(&msg.into_data())))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => Err(RADError::WebSocketError(e)),
|
||||
}
|
||||
} else {
|
||||
Err(RADError::StreamClosed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,4 +6,24 @@ mod session;
|
|||
pub use session::*;
|
||||
pub use errors::*;
|
||||
pub use c2s::*;
|
||||
pub use s2c::*;
|
||||
pub use s2c::*;
|
||||
|
||||
use axum::extract::ws::{Message, WebSocket};
|
||||
|
||||
pub trait RecvAndDecode {
|
||||
async fn recv_and_decode(&mut self) -> Result<C2SMessage, RADError>;
|
||||
}
|
||||
|
||||
impl RecvAndDecode for WebSocket {
|
||||
async fn recv_and_decode(&mut self) -> Result<C2SMessage, RADError> {
|
||||
let msg = self.recv().await.ok_or(RADError::StreamClosed)??;
|
||||
|
||||
if let Message::Close(frame) = msg {
|
||||
return Err(RADError::Close(frame.map(|f| format!("code: {}, reason: {}", f.code, f.reason))));
|
||||
}
|
||||
|
||||
let data = msg.into_data();
|
||||
C2SMessage::try_from(data.as_ref())
|
||||
.map_err(|e| RADError::DecodeError(e, faster_hex::hex_string(&data)))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
pub mod figura;
|
||||
pub mod v1;
|
||||
pub mod sculptor;
|
||||
pub mod errors;
|
||||
|
|
@ -14,8 +14,8 @@ pub fn router(limit: usize) -> Router<AppState> {
|
|||
.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))
|
||||
.route("/avatar/:uuid", put(avatars::upload_avatar).layer(DefaultBodyLimit::max(limit)))
|
||||
.route("/avatar/:uuid", delete(avatars::delete_avatar))
|
||||
.route("/user/{uuid}/ban", post(users::ban))
|
||||
.route("/user/{uuid}/unban", post(users::unban))
|
||||
.route("/avatar/{uuid}", put(avatars::upload_avatar).layer(DefaultBodyLimit::max(limit)))
|
||||
.route("/avatar/{uuid}", delete(avatars::delete_avatar))
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@ use std::sync::Arc;
|
|||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{
|
||||
async_trait, extract::{FromRequestParts, State}, http::{request::Parts, StatusCode}
|
||||
extract::{FromRequestParts, OptionalFromRequestParts, State}, http::{request::Parts, StatusCode}
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error, trace, warn};
|
||||
use tracing::{debug, error, instrument, trace, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{ApiError, ApiResult, AppState, TIMEOUT, USER_AGENT};
|
||||
|
|
@ -18,7 +18,7 @@ use super::types::*;
|
|||
pub struct Token(pub String);
|
||||
|
||||
impl Token {
|
||||
pub async fn check_auth(self, state: &AppState) -> ApiResult<()> {
|
||||
pub async fn _check_auth(self, state: &AppState) -> ApiResult<()> {
|
||||
if let Some(user) = state.user_manager.get(&self.0) {
|
||||
if !user.banned {
|
||||
Ok(())
|
||||
|
|
@ -31,7 +31,6 @@ impl Token {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<S> FromRequestParts<S> for Token
|
||||
where
|
||||
S: Send + Sync,
|
||||
|
|
@ -50,6 +49,22 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> OptionalFromRequestParts<S> for Token
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = StatusCode; // Not required
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Option<Self>, Self::Rejection> {
|
||||
let token = parts
|
||||
.headers
|
||||
.get("token")
|
||||
.and_then(|value| value.to_str().ok());
|
||||
trace!(token = ?token);
|
||||
Ok(token.map(|t| Self(t.to_string())))
|
||||
}
|
||||
}
|
||||
// End Extractor
|
||||
|
||||
// Work with external APIs
|
||||
|
|
@ -259,16 +274,33 @@ impl UManager {
|
|||
}
|
||||
// End of User manager
|
||||
|
||||
#[axum::debug_handler]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn check_auth(
|
||||
token: Option<Token>,
|
||||
State(state): State<AppState>,
|
||||
) -> ApiResult<&'static str> {
|
||||
debug!("Checking auth actuality...");
|
||||
match token {
|
||||
Some(token) => {
|
||||
token.check_auth(&state).await?;
|
||||
Ok("ok")
|
||||
},
|
||||
None => Err(ApiError::BadRequest),
|
||||
match state.user_manager.get(&token.0) {
|
||||
Some(user) => {
|
||||
if user.banned {
|
||||
debug!(nickname = user.nickname, status = "banned", "Token owner is banned");
|
||||
Err(ApiError::Unauthorized)
|
||||
} else {
|
||||
debug!(nickname = user.nickname, status = "ok", "Token verified successfully");
|
||||
Ok("ok")
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!(token = token.0, status = "invalid", "Invalid token");
|
||||
Err(ApiError::Unauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!(status = "not provided", "Token not provided");
|
||||
Err(ApiError::BadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main.rs
11
src/main.rs
|
|
@ -107,7 +107,7 @@ async fn main() -> Result<()> {
|
|||
},
|
||||
}
|
||||
|
||||
// 4. Starting an app() that starts to serve. If app() returns true, the sculptor will be restarted. for future
|
||||
// 4. Starting an app() that starts to serve. If app() returns true, the sculptor will be restarted. TODO: for future
|
||||
loop {
|
||||
if !app().await? {
|
||||
break;
|
||||
|
|
@ -174,6 +174,7 @@ async fn app() -> Result<bool> {
|
|||
Arc::clone(&state.session),
|
||||
Arc::clone(&state.config)
|
||||
));
|
||||
// Blacklist auto update
|
||||
if state.config.read().await.mc_folder.exists() {
|
||||
tokio::spawn(update_bans_from_minecraft(
|
||||
state.config.read().await.mc_folder.clone(),
|
||||
|
|
@ -185,13 +186,13 @@ async fn app() -> Result<bool> {
|
|||
let api = Router::new()
|
||||
.nest("//auth", api_auth::router()) // => /api//auth ¯\_(ツ)_/¯
|
||||
.nest("//assets", api_assets::router())
|
||||
.nest("/v1", api::v1::router(limit))
|
||||
.nest("/v1", api::sculptor::router(limit))
|
||||
.route("/limits", get(api_info::limits))
|
||||
.route("/version", get(api_info::version))
|
||||
.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("/{uuid}", get(api_profile::user_info))
|
||||
.route("/{uuid}/avatar", get(api_profile::download_avatar))
|
||||
.route("/avatar", put(api_profile::upload_avatar).layer(DefaultBodyLimit::max(limit)))
|
||||
.route("/avatar", delete(api_profile::delete_avatar));
|
||||
|
||||
|
|
@ -205,9 +206,11 @@ async fn app() -> Result<bool> {
|
|||
|
||||
let listener = tokio::net::TcpListener::bind(listen).await?;
|
||||
tracing::info!("Listening on {}", listener.local_addr()?);
|
||||
|
||||
axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await?;
|
||||
|
||||
tracing::info!("Serve stopped.");
|
||||
Ok(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{fs::File, io::Read, path::{Path, PathBuf}, sync::Arc};
|
|||
use notify::{Event, Watcher};
|
||||
use tokio::{io::AsyncReadExt, sync::RwLock};
|
||||
use base64::prelude::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rand::{rng, Rng};
|
||||
use ring::digest::{self, digest};
|
||||
use uuid::Uuid;
|
||||
use chrono::prelude::*;
|
||||
|
|
@ -11,8 +11,8 @@ use chrono::prelude::*;
|
|||
use crate::{auth::Userinfo, state::{BannedPlayer, Config}, UManager};
|
||||
|
||||
pub fn rand() -> [u8; 50] {
|
||||
let mut rng = thread_rng();
|
||||
let distr = rand::distributions::Uniform::new_inclusive(0, 255);
|
||||
let mut rng = rng();
|
||||
let distr = rand::distr::Uniform::new_inclusive(0, 255).expect("rand() failure.");
|
||||
let mut nums: [u8; 50] = [0u8; 50];
|
||||
for x in &mut nums {
|
||||
*x = rng.sample(distr);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue