mirror of
https://github.com/shiroyashik/sculptor.git
synced 2025-12-06 13:01:12 +03:00
The system design has changed, but the refactor is not over.
In future commits, I will strive to maintain a consistent style.
This commit is contained in:
parent
7594e3d615
commit
a1f9eba502
25 changed files with 410 additions and 367 deletions
151
src/auth/auth.rs
Normal file
151
src/auth/auth.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::{
|
||||
async_trait, extract::FromRequestParts, http::{request::Parts, StatusCode}
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
use tracing::{debug, trace};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::types::*;
|
||||
|
||||
// It's an extractor that pulls a token from the Header.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Token(pub Option<String>);
|
||||
|
||||
#[async_trait]
|
||||
impl<S> FromRequestParts<S> for Token
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = StatusCode;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||
let token = parts
|
||||
.headers
|
||||
.get("token")
|
||||
.and_then(|value| value.to_str().ok());
|
||||
trace!(token = ?token);
|
||||
match token {
|
||||
Some(token) => Ok(Self(Some(token.to_string()))),
|
||||
None => Ok(Self(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
// End Extractor
|
||||
|
||||
// Work with external APIs
|
||||
/// Get UUID from JSON response
|
||||
#[inline]
|
||||
fn get_id_json(json: &serde_json::Value) -> anyhow::Result<Uuid> {
|
||||
trace!("json: {json:#?}"); // For debugging, we'll get to this later!
|
||||
let uuid = Uuid::parse_str(json.get("id").unwrap().as_str().unwrap())?;
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn fetch_json(
|
||||
auth_system: AuthSystem,
|
||||
server_id: &str,
|
||||
username: &str,
|
||||
) -> anyhow::Result<Option<(Uuid, AuthSystem)>> {
|
||||
let client = reqwest::Client::new();
|
||||
let url = auth_system.get_url();
|
||||
|
||||
let res = client
|
||||
.get(url)
|
||||
.query(&[("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, auth_system)))
|
||||
}
|
||||
401 => Ok(None), // Ely.By None
|
||||
204 => Ok(None), // Mojang None
|
||||
_ => Err(anyhow!("Unknown code: {}", res.status().as_u16())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn has_joined(
|
||||
server_id: &str,
|
||||
username: &str,
|
||||
) -> anyhow::Result<Option<(Uuid, AuthSystem)>> {
|
||||
let (elyby, mojang) = (
|
||||
fetch_json(AuthSystem::ElyBy,server_id, username).await?,
|
||||
fetch_json(AuthSystem::Mojang, server_id, username).await?
|
||||
);
|
||||
|
||||
if elyby.is_none() && mojang.is_none() {
|
||||
Ok(None)
|
||||
} else if mojang.is_some() {
|
||||
Ok(mojang)
|
||||
} else if elyby.is_some() {
|
||||
Ok(elyby)
|
||||
} else {
|
||||
panic!("Impossible error!")
|
||||
}
|
||||
}
|
||||
// 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 TODO: Change name to sessions
|
||||
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>>,
|
||||
}
|
||||
|
||||
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
|
||||
5
src/auth/mod.rs
Normal file
5
src/auth/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod auth;
|
||||
mod types;
|
||||
|
||||
pub use auth::*;
|
||||
pub use types::*;
|
||||
56
src/auth/types.rs
Normal file
56
src/auth/types.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
use anyhow::anyhow;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Userinfo {
|
||||
pub username: String,
|
||||
pub uuid: Uuid,
|
||||
pub auth_system: AuthSystem,
|
||||
pub token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AuthSystem {
|
||||
Internal,
|
||||
ElyBy,
|
||||
Mojang,
|
||||
}
|
||||
|
||||
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 {
|
||||
pub(super) 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue