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
646
Cargo.lock
generated
646
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "sculptor"
|
name = "sculptor"
|
||||||
authors = ["Shiroyashik <shiroyashik@shsr.ru>"]
|
authors = ["Shiroyashik <shiroyashik@shsr.ru>"]
|
||||||
version = "0.4.0"
|
version = "0.4.1-dev"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
|
@ -32,14 +32,14 @@ walkdir = "2.5"
|
||||||
indexmap = { version = "2.6", features = ["serde"] }
|
indexmap = { version = "2.6", features = ["serde"] }
|
||||||
zip = "2.2"
|
zip = "2.2"
|
||||||
lazy_static = "1.5"
|
lazy_static = "1.5"
|
||||||
notify = "7.0"
|
notify = "8.0"
|
||||||
|
|
||||||
# Crypto
|
# Crypto
|
||||||
ring = "0.17"
|
ring = "0.17"
|
||||||
rand = "0.8"
|
rand = "0.9"
|
||||||
|
|
||||||
# Web framework
|
# Web
|
||||||
axum = { version = "0.7", features = ["ws", "macros", "http2"] }
|
axum = { version = "0.8", features = ["ws", "macros", "http2"] }
|
||||||
tower-http = { version = "0.6", features = ["trace"] }
|
tower-http = { version = "0.6", features = ["trace"] }
|
||||||
tokio = { version = "1.41", features = ["full"] }
|
tokio = { version = "1.41", features = ["full"] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
## Don't touch this if you running under Docker container
|
## Use port 80 for HTTP or port 443 for HTTPS.
|
||||||
|
## If running in a Docker container, leave this as default.
|
||||||
listen = "0.0.0.0:6665"
|
listen = "0.0.0.0:6665"
|
||||||
|
|
||||||
## Don't touch if you don't know what you're doing
|
## Don't touch if you don't know what you're doing
|
||||||
|
|
@ -71,7 +72,7 @@ customText = """
|
||||||
[limitations]
|
[limitations]
|
||||||
maxAvatarSize = 100 # KB
|
maxAvatarSize = 100 # KB
|
||||||
maxAvatars = 10 # It doesn't look like Figura has any actions implemented with this?
|
maxAvatars = 10 # It doesn't look like Figura has any actions implemented with this?
|
||||||
# P.S. And it doesn't look like the current API allows anything like that...
|
## P.S. And it doesn't look like the current API allows anything like that...
|
||||||
|
|
||||||
[advancedUsers.66004548-4de5-49de-bade-9c3933d8eb97]
|
[advancedUsers.66004548-4de5-49de-bade-9c3933d8eb97]
|
||||||
username = "Shiroyashik"
|
username = "Shiroyashik"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
## Chef
|
## Chef
|
||||||
# FROM clux/muslrust:stable AS chef
|
# FROM clux/muslrust:stable AS chef
|
||||||
FROM rust:1.82.0-alpine3.20 AS chef
|
FROM rust:1.84-alpine3.21 AS chef
|
||||||
USER root
|
USER root
|
||||||
RUN apk add --no-cache musl-dev libressl-dev
|
RUN apk add --no-cache musl-dev libressl-dev
|
||||||
RUN cargo install cargo-chef
|
RUN cargo install cargo-chef
|
||||||
|
|
@ -23,7 +23,7 @@ COPY src src
|
||||||
RUN cargo build --release --target x86_64-unknown-linux-musl --bin sculptor
|
RUN cargo build --release --target x86_64-unknown-linux-musl --bin sculptor
|
||||||
|
|
||||||
## Runtime
|
## Runtime
|
||||||
FROM alpine:3.20.0 AS runtime
|
FROM alpine:3.21 AS runtime
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/sculptor /app/sculptor
|
COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/sculptor /app/sculptor
|
||||||
|
|
||||||
|
|
|
||||||
19
README.md
19
README.md
|
|
@ -35,7 +35,7 @@ To run it you will need a configured reverse proxy server.
|
||||||
|
|
||||||
Make sure that the reverse proxy you are using supports WebSocket and valid certificates are used for HTTPS connections.
|
Make sure that the reverse proxy you are using supports WebSocket and valid certificates are used for HTTPS connections.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!WARNING]
|
||||||
> NGINX requires additional configuration to work with websocket!
|
> NGINX requires additional configuration to work with websocket!
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
@ -57,10 +57,10 @@ See the [pre-built archives](https://github.com/shiroyashik/sculptor/releases/la
|
||||||
A pre-installed Rust will be required for the build
|
A pre-installed Rust will be required for the build
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Clone the latest release
|
# Clone the pre-release
|
||||||
git clone https://github.com/shiroyashik/sculptor.git
|
git clone https://github.com/shiroyashik/sculptor.git
|
||||||
# or a dev release
|
# or clone specific version
|
||||||
git clone --branch dev https://github.com/shiroyashik/sculptor.git
|
git clone --depth 1 --branch v0.4.0 https://github.com/shiroyashik/sculptor.git
|
||||||
# Enter the folder
|
# Enter the folder
|
||||||
cd sculptor
|
cd sculptor
|
||||||
# Copy Sculptor configuration file
|
# Copy Sculptor configuration file
|
||||||
|
|
@ -73,6 +73,13 @@ cargo build --release
|
||||||
cargo run --release
|
cargo run --release
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Compiling from the `master` Branch
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Installing Sculptor directly from the `master` branch is **not recommended** for most users. This branch contains pre-release code that is actively being developed and may include broken or unstable features. Additionally, using the `master` branch could potentially cause issues with data migration when upgrading to future stable releases.
|
||||||
|
>
|
||||||
|
> If you still choose to use the `master` branch, please be aware that you may encounter bugs or unexpected behavior. Your feedback and bug reports are highly appreciated. However, for a more stable and reliable experience, we strongly advise using the **latest official release** instead.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||

|

|
||||||
on
|
on
|
||||||
|
|
@ -92,10 +99,6 @@ If you are a Rust developer, you can modify the code yourself and request a Pull
|
||||||
|
|
||||||
Glad for any help from ideas to PRs. ❤
|
Glad for any help from ideas to PRs. ❤
|
||||||
|
|
||||||
#### P.S.
|
|
||||||
|
|
||||||
The [“master”](https://github.com/shiroyashik/sculptor/tree/master) branch contains the source code of the latest release. [“dev”](https://github.com/shiroyashik/sculptor/tree/dev) branch is used for development.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
The Sculptor is licensed under [GPL-3.0](LICENSE)
|
The Sculptor is licensed under [GPL-3.0](LICENSE)
|
||||||
|
|
|
||||||
19
README.ru.md
19
README.ru.md
|
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
Убедитесь, что используемый вами обратный прокси-сервер поддерживает WebSocket, а для HTTPS-соединений используются действительные сертификаты.
|
Убедитесь, что используемый вами обратный прокси-сервер поддерживает WebSocket, а для HTTPS-соединений используются действительные сертификаты.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!WARNING]
|
||||||
> NGINX требует дополнительной настройки для работы с websocket!
|
> NGINX требует дополнительной настройки для работы с websocket!
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
@ -55,10 +55,10 @@
|
||||||
Для сборки потребуется предустановленный Rust
|
Для сборки потребуется предустановленный Rust
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Клонируем последний релиз
|
# Клонируем пре-релиз
|
||||||
git clone https://github.com/shiroyashik/sculptor.git
|
git clone https://github.com/shiroyashik/sculptor.git
|
||||||
# или из dev ветки
|
# или из выбранного тега
|
||||||
git clone --branch dev https://github.com/shiroyashik/sculptor.git
|
git clone --depth 1 --branch v0.4.0 https://github.com/shiroyashik/sculptor.git
|
||||||
# Переходим в репу
|
# Переходим в репу
|
||||||
cd sculptor
|
cd sculptor
|
||||||
# Меняем имя конфиг файлу
|
# Меняем имя конфиг файлу
|
||||||
|
|
@ -71,6 +71,13 @@ cargo build --release
|
||||||
cargo run --release
|
cargo run --release
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Сборка из `master` ветки
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Сборка Sculptor непосредственно из ветки `master` **не рекомендуется** для большинства пользователей. Эта ветка содержит предрелизный код, который активно разрабатывается и может содержать неработающие или нестабильные функции. Кроме того, использование ветки `master` может привести к проблемам с миграцией данных при обновлении до будущих стабильных релизов.
|
||||||
|
>
|
||||||
|
> Если вы все же решили использовать ветку `master`, пожалуйста, имейте в виду, что вы можете столкнуться с ошибками или некорректным поведением. Тем не менее ваши сообщения об ошибках высоко ценятся. Однако для более стабильной и надежной работы настоятельно рекомендую использовать **последний официальный релиз**.
|
||||||
|
|
||||||
## Вклад в развитие
|
## Вклад в развитие
|
||||||

|

|
||||||
в
|
в
|
||||||
|
|
@ -90,10 +97,6 @@ cargo run --release
|
||||||
|
|
||||||
Буду рад любой вашей помощи! ❤
|
Буду рад любой вашей помощи! ❤
|
||||||
|
|
||||||
#### Постскриптум
|
|
||||||
|
|
||||||
Ветка [“master”](https://github.com/shiroyashik/sculptor/tree/master) содержит код последнего релиза. А [“dev”](https://github.com/shiroyashik/sculptor/tree/dev) ветка дря разработки.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
The Sculptor is licensed under [GPL-3.0](LICENSE)
|
The Sculptor is licensed under [GPL-3.0](LICENSE)
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ use crate::{api::errors::internal_and_log, ApiError, ApiResult, AppState, ASSETS
|
||||||
pub fn router() -> Router<AppState> {
|
pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(versions))
|
.route("/", get(versions))
|
||||||
.route("/:version", get(hashes))
|
.route("/{version}", get(hashes))
|
||||||
.route("/:version/*key", get(download))
|
.route("/{version}/{*path}", get(download))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn versions() -> ApiResult<Json<Value>> {
|
async fn versions() -> ApiResult<Json<Value>> {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use axum::extract::{ws::{Message, WebSocket}, State};
|
use axum::{body::Bytes, extract::{ws::{Message, WebSocket}, State}};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::{broadcast, mpsc};
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::{auth::Userinfo, AppState};
|
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(
|
pub async fn initial(
|
||||||
ws: axum::extract::WebSocketUpgrade,
|
ws: axum::extract::WebSocketUpgrade,
|
||||||
|
|
@ -42,9 +43,8 @@ async fn handle_socket(mut ws: WebSocket, state: AppState) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Starting main worker
|
// Starting main worker
|
||||||
match main_worker(&mut session, &mut ws, &state).await {
|
if let Err(kind) = main_worker(&mut session, &mut ws, &state).await {
|
||||||
Ok(_) => (),
|
tracing::error!("[WebSocket] Main worker halted due to {}.", kind)
|
||||||
Err(kind) => tracing::error!("[WebSocket] Main worker halted due to {}.", kind),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (_, handle) in session.sub_workers_aborthandles {
|
for (_, handle) in session.sub_workers_aborthandles {
|
||||||
|
|
@ -61,9 +61,10 @@ async fn handle_socket(mut ws: WebSocket, state: AppState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closing connection
|
// 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<()> {
|
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);
|
tracing::debug!("WebSocket control for {} is transferred to the main worker", session.user.nickname);
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -90,7 +91,7 @@ async fn main_worker(session: &mut WSSession, ws: &mut WebSocket, state: &AppSta
|
||||||
|
|
||||||
// Echo check
|
// Echo check
|
||||||
if echo {
|
if echo {
|
||||||
ws.send(Message::Binary(s2c_ping.clone())).await?
|
ws.send(Message::Binary(s2c_ping.clone().into())).await?
|
||||||
}
|
}
|
||||||
// Sending to others
|
// Sending to others
|
||||||
let _ = session.subs_tx.send(s2c_ping);
|
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!"))?;
|
let internal_msg = internal_msg.ok_or(anyhow::anyhow!("Unexpected error! Session channel broken!"))?;
|
||||||
match internal_msg {
|
match internal_msg {
|
||||||
SessionMessage::Ping(msg) => {
|
SessionMessage::Ping(msg) => {
|
||||||
ws.send(Message::Binary(msg)).await?
|
ws.send(Message::Binary(msg.into())).await?
|
||||||
},
|
},
|
||||||
SessionMessage::Banned => {
|
SessionMessage::Banned => {
|
||||||
let _ = ban_action(ws).await
|
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)?;
|
let token = String::from_utf8(token.to_vec()).map_err(|_| AuthModeError::ConvertError)?;
|
||||||
match state.user_manager.get(&token) {
|
match state.user_manager.get(&token) {
|
||||||
Some(user) => {
|
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)
|
Err(AuthModeError::SendError)
|
||||||
} else if !user.banned {
|
} else if !user.banned {
|
||||||
Ok(user.clone())
|
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<()> {
|
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;
|
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?;
|
ws.send(Message::Close(Some(axum::extract::ws::CloseFrame { code: 4001, reason: "You're banned!".into() }))).await?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// mod websocket;
|
// mod websocket;
|
||||||
mod handler;
|
mod handler;
|
||||||
mod processor;
|
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
// pub use websocket::*;
|
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,3 +7,23 @@ pub use session::*;
|
||||||
pub use errors::*;
|
pub use errors::*;
|
||||||
pub use c2s::*;
|
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 figura;
|
||||||
pub mod v1;
|
pub mod sculptor;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
|
@ -14,8 +14,8 @@ pub fn router(limit: usize) -> Router<AppState> {
|
||||||
.route("/user/list", get(users::list))
|
.route("/user/list", get(users::list))
|
||||||
.route("/user/sessions", get(users::list_sessions))
|
.route("/user/sessions", get(users::list_sessions))
|
||||||
.route("/user/create", post(users::create_user))
|
.route("/user/create", post(users::create_user))
|
||||||
.route("/user/:uuid/ban", post(users::ban))
|
.route("/user/{uuid}/ban", post(users::ban))
|
||||||
.route("/user/:uuid/unban", post(users::unban))
|
.route("/user/{uuid}/unban", post(users::unban))
|
||||||
.route("/avatar/:uuid", put(avatars::upload_avatar).layer(DefaultBodyLimit::max(limit)))
|
.route("/avatar/{uuid}", put(avatars::upload_avatar).layer(DefaultBodyLimit::max(limit)))
|
||||||
.route("/avatar/:uuid", delete(avatars::delete_avatar))
|
.route("/avatar/{uuid}", delete(avatars::delete_avatar))
|
||||||
}
|
}
|
||||||
|
|
@ -2,11 +2,11 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait, extract::{FromRequestParts, State}, http::{request::Parts, StatusCode}
|
extract::{FromRequestParts, OptionalFromRequestParts, State}, http::{request::Parts, StatusCode}
|
||||||
};
|
};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, instrument, trace, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{ApiError, ApiResult, AppState, TIMEOUT, USER_AGENT};
|
use crate::{ApiError, ApiResult, AppState, TIMEOUT, USER_AGENT};
|
||||||
|
|
@ -18,7 +18,7 @@ use super::types::*;
|
||||||
pub struct Token(pub String);
|
pub struct Token(pub String);
|
||||||
|
|
||||||
impl Token {
|
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 let Some(user) = state.user_manager.get(&self.0) {
|
||||||
if !user.banned {
|
if !user.banned {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -31,7 +31,6 @@ impl Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<S> FromRequestParts<S> for Token
|
impl<S> FromRequestParts<S> for Token
|
||||||
where
|
where
|
||||||
S: Send + Sync,
|
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
|
// End Extractor
|
||||||
|
|
||||||
// Work with external APIs
|
// Work with external APIs
|
||||||
|
|
@ -259,16 +274,33 @@ impl UManager {
|
||||||
}
|
}
|
||||||
// End of User manager
|
// End of User manager
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
#[instrument(skip_all)]
|
||||||
pub async fn check_auth(
|
pub async fn check_auth(
|
||||||
token: Option<Token>,
|
token: Option<Token>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> ApiResult<&'static str> {
|
) -> ApiResult<&'static str> {
|
||||||
debug!("Checking auth actuality...");
|
|
||||||
match token {
|
match token {
|
||||||
Some(token) => {
|
Some(token) => {
|
||||||
token.check_auth(&state).await?;
|
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")
|
Ok("ok")
|
||||||
},
|
}
|
||||||
None => Err(ApiError::BadRequest),
|
}
|
||||||
|
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 {
|
loop {
|
||||||
if !app().await? {
|
if !app().await? {
|
||||||
break;
|
break;
|
||||||
|
|
@ -174,6 +174,7 @@ async fn app() -> Result<bool> {
|
||||||
Arc::clone(&state.session),
|
Arc::clone(&state.session),
|
||||||
Arc::clone(&state.config)
|
Arc::clone(&state.config)
|
||||||
));
|
));
|
||||||
|
// Blacklist auto update
|
||||||
if state.config.read().await.mc_folder.exists() {
|
if state.config.read().await.mc_folder.exists() {
|
||||||
tokio::spawn(update_bans_from_minecraft(
|
tokio::spawn(update_bans_from_minecraft(
|
||||||
state.config.read().await.mc_folder.clone(),
|
state.config.read().await.mc_folder.clone(),
|
||||||
|
|
@ -185,13 +186,13 @@ async fn app() -> Result<bool> {
|
||||||
let api = Router::new()
|
let api = Router::new()
|
||||||
.nest("//auth", api_auth::router()) // => /api//auth ¯\_(ツ)_/¯
|
.nest("//auth", api_auth::router()) // => /api//auth ¯\_(ツ)_/¯
|
||||||
.nest("//assets", api_assets::router())
|
.nest("//assets", api_assets::router())
|
||||||
.nest("/v1", api::v1::router(limit))
|
.nest("/v1", api::sculptor::router(limit))
|
||||||
.route("/limits", get(api_info::limits))
|
.route("/limits", get(api_info::limits))
|
||||||
.route("/version", get(api_info::version))
|
.route("/version", get(api_info::version))
|
||||||
.route("/motd", get(api_info::motd))
|
.route("/motd", get(api_info::motd))
|
||||||
.route("/equip", post(api_profile::equip_avatar))
|
.route("/equip", post(api_profile::equip_avatar))
|
||||||
.route("/:uuid", get(api_profile::user_info))
|
.route("/{uuid}", get(api_profile::user_info))
|
||||||
.route("/:uuid/avatar", get(api_profile::download_avatar))
|
.route("/{uuid}/avatar", get(api_profile::download_avatar))
|
||||||
.route("/avatar", put(api_profile::upload_avatar).layer(DefaultBodyLimit::max(limit)))
|
.route("/avatar", put(api_profile::upload_avatar).layer(DefaultBodyLimit::max(limit)))
|
||||||
.route("/avatar", delete(api_profile::delete_avatar));
|
.route("/avatar", delete(api_profile::delete_avatar));
|
||||||
|
|
||||||
|
|
@ -205,9 +206,11 @@ async fn app() -> Result<bool> {
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(listen).await?;
|
let listener = tokio::net::TcpListener::bind(listen).await?;
|
||||||
tracing::info!("Listening on {}", listener.local_addr()?);
|
tracing::info!("Listening on {}", listener.local_addr()?);
|
||||||
|
|
||||||
axum::serve(listener, app)
|
axum::serve(listener, app)
|
||||||
.with_graceful_shutdown(shutdown_signal())
|
.with_graceful_shutdown(shutdown_signal())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
tracing::info!("Serve stopped.");
|
tracing::info!("Serve stopped.");
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::{fs::File, io::Read, path::{Path, PathBuf}, sync::Arc};
|
||||||
use notify::{Event, Watcher};
|
use notify::{Event, Watcher};
|
||||||
use tokio::{io::AsyncReadExt, sync::RwLock};
|
use tokio::{io::AsyncReadExt, sync::RwLock};
|
||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{rng, Rng};
|
||||||
use ring::digest::{self, digest};
|
use ring::digest::{self, digest};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
|
@ -11,8 +11,8 @@ use chrono::prelude::*;
|
||||||
use crate::{auth::Userinfo, state::{BannedPlayer, Config}, UManager};
|
use crate::{auth::Userinfo, state::{BannedPlayer, Config}, UManager};
|
||||||
|
|
||||||
pub fn rand() -> [u8; 50] {
|
pub fn rand() -> [u8; 50] {
|
||||||
let mut rng = thread_rng();
|
let mut rng = rng();
|
||||||
let distr = rand::distributions::Uniform::new_inclusive(0, 255);
|
let distr = rand::distr::Uniform::new_inclusive(0, 255).expect("rand() failure.");
|
||||||
let mut nums: [u8; 50] = [0u8; 50];
|
let mut nums: [u8; 50] = [0u8; 50];
|
||||||
for x in &mut nums {
|
for x in &mut nums {
|
||||||
*x = rng.sample(distr);
|
*x = rng.sample(distr);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue