diff --git a/README.md b/README.md index f236285..c145d59 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,4 @@ $ sqlx migrate run - Query (Can have multiple) - `dump`: Dumps a table in the database (Mostly for debug) - - Table name (`accounts`, `profiles`, `sessions`, `tokens`) \ No newline at end of file + - Table name (`accounts`, `profiles`, `sessions`, `tokens`) diff --git a/src/lib.rs b/src/lib.rs index b5fe741..0b4c27c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,4 +14,3 @@ pub use util::*; mod util; - diff --git a/src/server/minecraft/mod.rs b/src/server/aliases/mod.rs similarity index 83% rename from src/server/minecraft/mod.rs rename to src/server/aliases/mod.rs index 35acf8a..917672f 100644 --- a/src/server/minecraft/mod.rs +++ b/src/server/aliases/mod.rs @@ -14,14 +14,12 @@ pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After}; pub use yggdrasil::*; -mod capes; - pub fn nest(db: Database) -> tide::Server { info!("Loading nest"); - let mut nest = tide::with_state(db); - nest.at("/profiles/capes/active").put(capes::upload_cape); - nest.at("/profiles/capes/active").delete(capes::delete_cape); + let mut nest = tide::with_state(db.to_owned()); + + nest.at("/user/profile/").nest(super::profile::nest(db.to_owned())); nest -} +} \ No newline at end of file diff --git a/src/server/account/profiles.rs b/src/server/api/mod.rs similarity index 72% rename from src/server/account/profiles.rs rename to src/server/api/mod.rs index f0f7a31..1fd6552 100644 --- a/src/server/account/profiles.rs +++ b/src/server/api/mod.rs @@ -9,13 +9,17 @@ * You should have received a copy of the GNU General Public License along with this program. If not, see . */ -use anyhow::anyhow; -use tide::{Request, Result}; +pub use log::{info, log, warn}; +pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After}; -use yggdrasil::Database; +pub use yggdrasil::*; -pub async fn profiles(req: Request) -> Result { +pub fn nest(db: Database) -> tide::Server { + info!("Loading nest"); + + let mut nest = tide::with_state(db.to_owned()); - Err(tide::Error::new(501, anyhow!("Not implemented yet")).into()) + + nest } \ No newline at end of file diff --git a/src/server/auth/authenticate.rs b/src/server/auth/authenticate.rs index a544144..eb07512 100644 --- a/src/server/auth/authenticate.rs +++ b/src/server/auth/authenticate.rs @@ -88,7 +88,6 @@ pub async fn authenticate(mut req: Request) -> Result { None => "".to_string() }, "active_cape": profile.active_cape, - "attributes": profile.attributes.to_json() }); } diff --git a/src/server/authlib/mod.rs b/src/server/authlib/mod.rs index 04a460a..e39194f 100644 --- a/src/server/authlib/mod.rs +++ b/src/server/authlib/mod.rs @@ -21,7 +21,9 @@ pub fn nest(db: Database) -> tide::Server { nest.at("/").get(authlib_meta); nest.at("/authserver").nest(super::auth::nest(db.to_owned())); - nest.at("/sessionserver").nest(super::session::nest(db.to_owned())); + nest.at("/sessionserver/session/minecraft").nest(super::session::nest(db.to_owned())); + + nest.at("/api/profiles/minecraft").nest(super::profile::nest(db.to_owned())); nest } diff --git a/src/server/mod.rs b/src/server/mod.rs index 1b19247..d26ab08 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -14,11 +14,11 @@ use tide::{Request, Response, utils::After}; use yggdrasil::*; -mod account; -mod auth; -mod authlib; -mod minecraft; -mod session; +pub mod auth; +pub mod authlib; +pub mod profile; +pub mod session; +pub mod aliases; pub async fn start(db: &Database) -> anyhow::Result<()> { let mut app = tide::with_state(db.to_owned()); @@ -53,12 +53,14 @@ pub async fn start(db: &Database) -> anyhow::Result<()> { }); // Routes - app.at("/authlib/").nest(authlib::nest(db.to_owned())); - app.at("/account/").nest(account::nest(db.to_owned())); - app.at("/minecraft/").nest(minecraft::nest(db.to_owned())); app.at("/auth/").nest(auth::nest(db.to_owned())); app.at("/session/").nest(session::nest(db.to_owned())); + app.at("/authlib/").nest(authlib::nest(db.to_owned())); + + app.at("/").nest(aliases::nest(db.to_owned())); + + // Listen app.listen(format!("{}:{}", &db.config.address, &db.config.port)).await?; diff --git a/src/server/minecraft/capes.rs b/src/server/profile/cape.rs similarity index 88% rename from src/server/minecraft/capes.rs rename to src/server/profile/cape.rs index 83623f4..be4beaa 100644 --- a/src/server/minecraft/capes.rs +++ b/src/server/profile/cape.rs @@ -18,6 +18,10 @@ pub async fn upload_cape(req: Request) -> Result { Err(tide::Error::new(501, anyhow!("Not implemented yet")).into()) } +pub async fn change_cape(req: Request) -> Result { + Err(tide::Error::new(501, anyhow!("Not implemented yet")).into()) +} + pub async fn delete_cape(req: Request) -> Result { Err(tide::Error::new(501, anyhow!("Not implemented yet")).into()) } \ No newline at end of file diff --git a/src/server/profile/mod.rs b/src/server/profile/mod.rs new file mode 100644 index 0000000..ded2b30 --- /dev/null +++ b/src/server/profile/mod.rs @@ -0,0 +1,41 @@ +/* + * Yggdrasil: Minecraft authentication server + * Copyright (C) 2023 0xf8.dev@proton.me + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ + + +pub use log::{info, log, warn}; +pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After}; + +pub use yggdrasil::*; + +mod profile; +mod skin; +mod cape; + +pub fn nest(db: Database) -> tide::Server { + info!("Loading nest"); + + let mut nest = tide::with_state(db); + + nest.at("/").post(profile::profiles); + + nest.at("/:uuid").get(profile::profile); + + nest.at("/:uuid/names").get(profile::name_history); + + nest.at("/:uuid/skin").put(skin::put_skin); + nest.at("/:uuid/skin/:skin").delete(skin::delete_skin); + + nest.at("/:uuid/capes").put(cape::upload_cape); + nest.at("/:uuid/cape/active").put(cape::change_cape); + nest.at("/:uuid/cape/active").delete(cape::delete_cape); + + nest +} diff --git a/src/server/profile/profile.rs b/src/server/profile/profile.rs new file mode 100644 index 0000000..f6f9233 --- /dev/null +++ b/src/server/profile/profile.rs @@ -0,0 +1,67 @@ +/* + * Yggdrasil: Minecraft authentication server + * Copyright (C) 2023 0xf8.dev@proton.me + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ + +use anyhow::anyhow; +use log::{debug, info}; +use serde_json::Value; +use tide::{prelude::*, Request, Result}; + +use yggdrasil::Database; +use yggdrasil::errors::YggdrasilError; +use yggdrasil::structs::game_profile::GameProfile; +use yggdrasil::structs::profile::Profile; +use yggdrasil::structs::token::Token; + +pub async fn profile(mut req: Request) -> Result { +// TODO: unsigned? + let Ok(uuid) = req.param("uuid") else { + // No uuid + debug!("No uuid"); + return Err(YggdrasilError::new_bad_request("One or more required fields was missing.").into()) + }; + + // Re-hyphenate if needed + let uuid = match uuid.find("-") { + None => Token::rehyphenate(uuid.to_string()), + Some(_) => uuid.to_string(), + }; + + let Some(profile) = Profile::from_uuid(req.state(), uuid).await else { + return Err(YggdrasilError::new_bad_request("Profile does not exist").into()) + }; + + Ok(GameProfile::from_profile(req.state(), &profile).await.into()) +} + +pub async fn profiles(mut req: Request) -> Result { + let Ok(body) = req.body_json::>().await else { + return Err(YggdrasilError::new_bad_request("One or more required fields was missing.").into()) + }; + + let mut response: Vec = Vec::new(); + + for uuid in body { + let profile = Profile::from_uuid(req.state(), uuid).await; + + match profile { + None => continue, + Some(p) => { + response.push(GameProfile::from_profile(req.state(), &p).await) + } + } + } + + Ok(serde_json::to_string(&response)?.into()) +} + +pub async fn name_history(req: Request) -> Result { + Err(tide::Error::new(501, anyhow!("Not implemented yet")).into()) +} diff --git a/src/server/account/skin.rs b/src/server/profile/skin.rs similarity index 90% rename from src/server/account/skin.rs rename to src/server/profile/skin.rs index a4bac06..918153c 100644 --- a/src/server/account/skin.rs +++ b/src/server/profile/skin.rs @@ -24,4 +24,8 @@ pub async fn delete_skin(req: Request) -> Result { // TODO: file uploading pub async fn put_skin(req: Request) -> Result { Err(tide::Error::new(501, anyhow!("Not implemented yet")).into()) +} + +pub async fn get_skins(req: Request) -> Result { + Err(tide::Error::new(501, anyhow!("Not implemented yet")).into()) } \ No newline at end of file diff --git a/src/server/session/has_joined.rs b/src/server/session/has_joined.rs index eb732d7..268eb17 100644 --- a/src/server/session/has_joined.rs +++ b/src/server/session/has_joined.rs @@ -16,7 +16,7 @@ use yggdrasil::Database; use yggdrasil::errors::YggdrasilError; use yggdrasil::structs::profile::Profile; use yggdrasil::structs::session::Session; -use yggdrasil::structs::textured_object::TexturedObject; +use yggdrasil::structs::game_profile::GameProfile; #[derive(Deserialize, Debug)] struct HasJoinedBody { @@ -54,5 +54,5 @@ pub async fn has_joined(mut req: Request) -> Result { // Remove session session.delete(req.state()).await?; - Ok(TexturedObject::from_profile(req.state(), &profile).await.into()) + Ok(GameProfile::from_profile(req.state(), &profile).await.into()) } \ No newline at end of file diff --git a/src/server/session/mod.rs b/src/server/session/mod.rs index a622ba5..6597cf0 100644 --- a/src/server/session/mod.rs +++ b/src/server/session/mod.rs @@ -21,10 +21,11 @@ mod profile; pub fn nest(db: Database) -> tide::Server { info!("Loading nest"); - let mut nest = tide::with_state(db); - nest.at("minecraft/hasJoined").get(has_joined::has_joined); - nest.at("minecraft/join").post(join::join); - nest.at("profile/:uuid").get(profile::profile); + let mut nest = tide::with_state(db.to_owned()); + + nest.at("hasJoined").get(has_joined::has_joined); + nest.at("join").post(join::join); + nest.at("profile").nest(super::profile::nest(db.to_owned())); nest } diff --git a/src/server/session/profile.rs b/src/server/session/profile.rs index 93112d7..a93cefa 100644 --- a/src/server/session/profile.rs +++ b/src/server/session/profile.rs @@ -16,7 +16,7 @@ use tide::{prelude::*, Request, Result}; use yggdrasil::Database; use yggdrasil::errors::YggdrasilError; use yggdrasil::structs::profile::Profile; -use yggdrasil::structs::textured_object::TexturedObject; +use yggdrasil::structs::game_profile::GameProfile; use yggdrasil::structs::token::Token; // TODO: unsigned? @@ -36,5 +36,5 @@ pub async fn profile(mut req: Request) -> Result { return Err(YggdrasilError::new_bad_request("Profile does not exist").into()) }; - Ok(TexturedObject::from_profile(req.state(), &profile).await.into()) + Ok(GameProfile::from_profile(req.state(), &profile).await.into()) } \ No newline at end of file diff --git a/src/server/account/mod.rs b/src/server/user/mod.rs similarity index 80% rename from src/server/account/mod.rs rename to src/server/user/mod.rs index 0bf65ab..c30675c 100644 --- a/src/server/account/mod.rs +++ b/src/server/user/mod.rs @@ -14,17 +14,13 @@ pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After}; pub use yggdrasil::*; -mod profiles; -mod skin; - pub fn nest(db: Database) -> tide::Server { info!("Loading nest"); - let mut nest = tide::with_state(db); + let mut nest = tide::with_state(db.to_owned()); - nest.at("profiles/minecraft").post(profiles::profiles); - nest.at("profile/skins").put(skin::put_skin); - nest.at("profile/active").delete(skin::delete_skin); + nest.at("/profile").nest(super::profile::nest(db.to_owned())); + nest.at("/profiles").nest(super::profile::nest(db.to_owned())); nest } diff --git a/src/util/structs/textured_object.rs b/src/util/structs/game_profile.rs similarity index 95% rename from src/util/structs/textured_object.rs rename to src/util/structs/game_profile.rs index dd42f5b..d257146 100644 --- a/src/util/structs/textured_object.rs +++ b/src/util/structs/game_profile.rs @@ -18,10 +18,11 @@ use structs::profile::Profile; use crate::*; #[derive(Deserialize, Serialize, Debug)] -pub struct TexturedObject {} +pub struct GameProfile {} -impl TexturedObject { +impl GameProfile { pub async fn from_profile(db: &Database, profile: &Profile) -> Value { + // Textures object let mut object = json!({ "timestamp": get_unix_timestamp() as u64, "profile_id": profile.uuid.to_owned(), @@ -45,6 +46,7 @@ impl TexturedObject { } } + // This is the actual game profile json!({ "id": profile.uuid.replace("-", ""), "name": profile.name.to_owned(), diff --git a/src/util/structs/mod.rs b/src/util/structs/mod.rs index ac4d865..ed22351 100644 --- a/src/util/structs/mod.rs +++ b/src/util/structs/mod.rs @@ -16,6 +16,6 @@ pub mod cape; pub mod profile; pub mod profile_attributes; pub mod session; -pub mod textured_object; +pub mod game_profile; pub mod token;