Create stubs for a lot of routes; rework structs.rs to module
This commit is contained in:
parent
1e2b69815a
commit
50c849282a
@ -9,19 +9,21 @@ license = "GPL3.0-or-later"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
|
async-std = { version = "1.12.0", features = ["attributes"] }
|
||||||
base64 = "0.21.2"
|
base64 = "0.21.2"
|
||||||
|
bcrypt = "0.14.0"
|
||||||
colored = "2.0.0"
|
colored = "2.0.0"
|
||||||
driftwood = "0.0.7"
|
driftwood = "0.0.7"
|
||||||
femme = "2.2.1"
|
femme = "2.2.1"
|
||||||
json = "0.12.4"
|
json = "0.12.4"
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
random-string = "1.0.0"
|
||||||
rsa = "0.9.2"
|
rsa = "0.9.2"
|
||||||
serde = { version = "1.0.164", features = ["derive"] }
|
serde = { version = "1.0.164", features = ["derive"] }
|
||||||
serde_json = "1.0.97"
|
serde_json = "1.0.97"
|
||||||
sha2 = "0.10.7"
|
sha2 = "0.10.7"
|
||||||
sqlx = { version = "0.6.3", features = ["sqlite", "runtime-tokio-native-tls"] }
|
sqlx = { version = "0.6.3", features = ["sqlite", "runtime-async-std-native-tls"] }
|
||||||
tide = "0.16.0"
|
tide = "0.16.0"
|
||||||
time = "0.3.22"
|
time = "0.3.22"
|
||||||
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
|
|
||||||
toml = "0.7.4"
|
toml = "0.7.4"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
CREATE TABLE IF NOT EXISTS accounts (
|
CREATE TABLE IF NOT EXISTS accounts (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY NOT NULL UNIQUE,
|
||||||
email TEXT NOT NULL UNIQUE,
|
email TEXT NOT NULL UNIQUE,
|
||||||
password_hash TEXT NOT NULL,
|
password_hash TEXT NOT NULL,
|
||||||
language TEXT NOT NULL,
|
language TEXT NOT NULL,
|
||||||
@ -9,20 +9,20 @@ CREATE TABLE IF NOT EXISTS accounts (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS blocked_servers (
|
CREATE TABLE IF NOT EXISTS blocked_servers (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY NOT NULL UNIQUE,
|
||||||
pattern TEXT NOT NULL UNIQUE,
|
pattern TEXT NOT NULL UNIQUE,
|
||||||
sha1 TEXT NOT NULL UNIQUE,
|
sha1 TEXT NOT NULL UNIQUE,
|
||||||
reason TEXT
|
reason TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS capes (
|
CREATE TABLE IF NOT EXISTS capes (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY NOT NULL UNIQUE,
|
||||||
friendly_id TEXT NOT NULL UNIQUE,
|
friendly_id TEXT NOT NULL UNIQUE,
|
||||||
alias TEXT NOT NULL
|
alias TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS profiles (
|
CREATE TABLE IF NOT EXISTS profiles (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY NOT NULL UNIQUE,
|
||||||
uuid TEXT NOT NULL UNIQUE,
|
uuid TEXT NOT NULL UNIQUE,
|
||||||
created INTEGER NOT NULL,
|
created INTEGER NOT NULL,
|
||||||
owner INTEGER NOT NULL,
|
owner INTEGER NOT NULL,
|
||||||
@ -37,7 +37,7 @@ CREATE TABLE IF NOT EXISTS profiles (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY NOT NULL UNIQUE,
|
||||||
profile INTEGER NOT NULL,
|
profile INTEGER NOT NULL,
|
||||||
server_id TEXT NOT NULL,
|
server_id TEXT NOT NULL,
|
||||||
ip_addr TEXT NOT NULL,
|
ip_addr TEXT NOT NULL,
|
||||||
@ -45,7 +45,7 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tokens (
|
CREATE TABLE IF NOT EXISTS tokens (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY NOT NULL UNIQUE,
|
||||||
access TEXT NOT NULL UNIQUE,
|
access TEXT NOT NULL UNIQUE,
|
||||||
client TEXT NOT NULL,
|
client TEXT NOT NULL,
|
||||||
account INTEGER NOT NULL,
|
account INTEGER NOT NULL,
|
||||||
|
@ -13,14 +13,12 @@
|
|||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use log::{debug, error, info, log, trace, warn};
|
use log::{debug, error, info, log, trace, warn};
|
||||||
use tokio::spawn;
|
|
||||||
|
|
||||||
use yggdrasil::*;
|
use yggdrasil::*;
|
||||||
|
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
|
#[async_std::main]
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// Early catch
|
// Early catch
|
||||||
if std::env::var("DATABASE_URL").is_err() {
|
if std::env::var("DATABASE_URL").is_err() {
|
||||||
@ -39,8 +37,8 @@ async fn main() -> Result<()> {
|
|||||||
info!("Database URL: {}", std::env::var("DATABASE_URL")?);
|
info!("Database URL: {}", std::env::var("DATABASE_URL")?);
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
let server_thread = spawn(server::start(db));
|
let server_thread = async_std::task::spawn(server::start(db));
|
||||||
server_thread.await??;
|
server_thread.await?;
|
||||||
|
|
||||||
warn!("Server stopped!");
|
warn!("Server stopped!");
|
||||||
|
|
||||||
|
@ -14,10 +14,17 @@ pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After};
|
|||||||
|
|
||||||
pub use yggdrasil::*;
|
pub use yggdrasil::*;
|
||||||
|
|
||||||
|
mod profiles;
|
||||||
|
mod skin;
|
||||||
|
|
||||||
pub fn nest(db: Database) -> tide::Server<Database> {
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
info!("Loading nest");
|
info!("Loading nest");
|
||||||
|
|
||||||
let mut nest = tide::with_state(db);
|
let mut nest = tide::with_state(db);
|
||||||
|
|
||||||
|
nest.at("profiles/minecraft").post(profiles::profiles);
|
||||||
|
nest.at("profile/skins").put(skin::put_skin);
|
||||||
|
nest.at("profile/active").delete(skin::delete_skin);
|
||||||
|
|
||||||
nest
|
nest
|
||||||
}
|
}
|
||||||
|
21
src/server/account/profiles.rs
Normal file
21
src/server/account/profiles.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn profiles(req: Request<Database>) -> Result {
|
||||||
|
|
||||||
|
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
27
src/server/account/skin.rs
Normal file
27
src/server/account/skin.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn delete_skin(req: Request<Database>) -> Result {
|
||||||
|
// let authorization = req.header("authorization");
|
||||||
|
// if (authorization.is_none()) { return Err(YggdrasilError::new_base().into()) }
|
||||||
|
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: file uploading
|
||||||
|
pub async fn put_skin(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
@ -17,9 +17,11 @@ pub use yggdrasil::*;
|
|||||||
pub fn nest(db: Database) -> tide::Server<Database> {
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
info!("Loading nest");
|
info!("Loading nest");
|
||||||
|
|
||||||
let mut nest = tide::with_state(db);
|
let mut nest = tide::with_state(db.to_owned());
|
||||||
|
|
||||||
nest.at("/").get(authlib_meta);
|
nest.at("/").get(authlib_meta);
|
||||||
|
nest.at("/authserver").nest(super::authserver::nest(db.to_owned()));
|
||||||
|
nest.at("/sessionserver").nest(super::sessionserver::nest(db.to_owned()));
|
||||||
|
|
||||||
nest
|
nest
|
||||||
}
|
}
|
||||||
|
92
src/server/authserver/authenticate.rs
Normal file
92
src/server/authserver/authenticate.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::*;
|
||||||
|
use yggdrasil::errors::YggdrasilError;
|
||||||
|
use yggdrasil::structs::{Account::Account, Cape::Cape, Token::Token};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Agent {
|
||||||
|
pub name: String,
|
||||||
|
pub version: i64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct AuthenticateBody {
|
||||||
|
pub agent: Agent,
|
||||||
|
pub username: String,
|
||||||
|
pub password: String, // hashed?
|
||||||
|
#[serde(rename = "clientToken")]
|
||||||
|
pub client_token: Option<String>,
|
||||||
|
#[serde(rename = "requestUser")]
|
||||||
|
pub request_user: Option<bool>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn authenticate(mut req: Request<Database>) -> Result {
|
||||||
|
let Ok(body) = req.body_json::<AuthenticateBody>().await else {
|
||||||
|
return Err(YggdrasilError::new_bad_request("Bad Request").into());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check current agent
|
||||||
|
if body.agent.name != "Minecraft" || body.agent.version != 1 {
|
||||||
|
return Err(YggdrasilError::new_bad_request("Unsupported game.").into());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get account
|
||||||
|
let account = Account::from_email(req.state(), body.username).await;
|
||||||
|
|
||||||
|
// Account doesn't exist
|
||||||
|
let Some(account) = account else {
|
||||||
|
return Err(YggdrasilError::new_forbidden("Invalid credentials. Invalid username or password.").into())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Password incorrect
|
||||||
|
if account.password_hash != body.password {
|
||||||
|
return Err(YggdrasilError::new_forbidden("Invalid credentials. Invalid username or password.").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
let client_token = match body.client_token {
|
||||||
|
None => Token::random_token(),
|
||||||
|
Some(t) => t
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut response = json!({
|
||||||
|
"clientToken": client_token,
|
||||||
|
"accessToken": "", // TODO: register_token
|
||||||
|
"availableProfiles": [], // TODO: get account profiles
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give selected profile if it exists
|
||||||
|
if account.selected_profile.is_some() {
|
||||||
|
let profile = account.to_owned().selected_profile.unwrap();
|
||||||
|
|
||||||
|
response["selectedProfile"] = json!({
|
||||||
|
"uuid": profile.uuid,
|
||||||
|
"name": profile.name,
|
||||||
|
"name_history": profile.name_history,
|
||||||
|
"skin_variant": profile.skin_variant,
|
||||||
|
"capes": match profile.capes {
|
||||||
|
Some(capes) => Cape::capes_to_string(capes),
|
||||||
|
None => "".to_string()
|
||||||
|
},
|
||||||
|
"active_cape": profile.active_cape.unwrap(),
|
||||||
|
"attributes": profile.attributes.to_json()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give user if requested
|
||||||
|
if body.request_user.unwrap_or(false) { response["user"] = account.to_user() }
|
||||||
|
|
||||||
|
Ok(response.into())
|
||||||
|
}
|
19
src/server/authserver/invalidate.rs
Normal file
19
src/server/authserver/invalidate.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn invalidate(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
@ -14,10 +14,21 @@ pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After};
|
|||||||
|
|
||||||
pub use yggdrasil::*;
|
pub use yggdrasil::*;
|
||||||
|
|
||||||
|
mod authenticate;
|
||||||
|
mod invalidate;
|
||||||
|
mod refresh;
|
||||||
|
mod signout;
|
||||||
|
mod validate;
|
||||||
|
|
||||||
pub fn nest(db: Database) -> tide::Server<Database> {
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
info!("Loading nest");
|
info!("Loading nest");
|
||||||
|
|
||||||
let mut nest = tide::with_state(db);
|
let mut nest = tide::with_state(db);
|
||||||
|
nest.at("authenticate").post(authenticate::authenticate);
|
||||||
|
nest.at("invalidate").post(invalidate::invalidate);
|
||||||
|
nest.at("refresh").post(refresh::refresh);
|
||||||
|
nest.at("signout").post(signout::signout);
|
||||||
|
nest.at("validate").post(validate::validate);
|
||||||
|
|
||||||
nest
|
nest
|
||||||
}
|
}
|
19
src/server/authserver/refresh.rs
Normal file
19
src/server/authserver/refresh.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn refresh(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
19
src/server/authserver/signout.rs
Normal file
19
src/server/authserver/signout.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn signout(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
19
src/server/authserver/validate.rs
Normal file
19
src/server/authserver/validate.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn validate(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
24
src/server/minecraft/capes.rs
Normal file
24
src/server/minecraft/capes.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn upload_cape(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn delete_cape(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
@ -14,10 +14,14 @@ pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After};
|
|||||||
|
|
||||||
pub use yggdrasil::*;
|
pub use yggdrasil::*;
|
||||||
|
|
||||||
|
mod capes;
|
||||||
|
|
||||||
pub fn nest(db: Database) -> tide::Server<Database> {
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
info!("Loading nest");
|
info!("Loading nest");
|
||||||
|
|
||||||
let mut nest = tide::with_state(db);
|
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);
|
||||||
|
|
||||||
nest
|
nest
|
||||||
}
|
}
|
@ -9,15 +9,16 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use log::info;
|
||||||
use tide::{Request, Response, utils::After};
|
use tide::{Request, Response, utils::After};
|
||||||
|
|
||||||
use yggdrasil::*;
|
use yggdrasil::*;
|
||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
mod auth;
|
mod authserver;
|
||||||
mod authlib;
|
mod authlib;
|
||||||
mod services;
|
mod minecraft;
|
||||||
mod session;
|
mod sessionserver;
|
||||||
|
|
||||||
pub async fn start(db: Database) -> anyhow::Result<()> {
|
pub async fn start(db: Database) -> anyhow::Result<()> {
|
||||||
let mut app = tide::with_state(db.to_owned());
|
let mut app = tide::with_state(db.to_owned());
|
||||||
@ -31,6 +32,8 @@ pub async fn start(db: Database) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// TODO: pass through
|
// TODO: pass through
|
||||||
// err.3: bool
|
// err.3: bool
|
||||||
|
} else if let Some(err) = res.error() {
|
||||||
|
res.set_body(format!("{}\n", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@ -43,11 +46,11 @@ pub async fn start(db: Database) -> anyhow::Result<()> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
app.at("/account/").nest(account::nest(db.to_owned()));
|
|
||||||
app.at("/auth/").nest(auth::nest(db.to_owned()));
|
|
||||||
app.at("/services/").nest(services::nest(db.to_owned()));
|
|
||||||
app.at("/session/").nest(session::nest(db.to_owned()));
|
|
||||||
app.at("/authlib/").nest(authlib::nest(db.to_owned()));
|
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("/authserver/").nest(authserver::nest(db.to_owned()));
|
||||||
|
app.at("/sessionserver/").nest(sessionserver::nest(db.to_owned()));
|
||||||
|
|
||||||
// Listen
|
// Listen
|
||||||
app.listen(format!("{}:{}", &db.config.address, &db.config.port)).await?;
|
app.listen(format!("{}:{}", &db.config.address, &db.config.port)).await?;
|
||||||
|
19
src/server/sessionserver/has_joined.rs
Normal file
19
src/server/sessionserver/has_joined.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn has_joined(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
19
src/server/sessionserver/join.rs
Normal file
19
src/server/sessionserver/join.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn join(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
@ -14,10 +14,17 @@ pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After};
|
|||||||
|
|
||||||
pub use yggdrasil::*;
|
pub use yggdrasil::*;
|
||||||
|
|
||||||
|
mod has_joined;
|
||||||
|
mod join;
|
||||||
|
mod profile;
|
||||||
|
|
||||||
pub fn nest(db: Database) -> tide::Server<Database> {
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
info!("Loading nest");
|
info!("Loading nest");
|
||||||
|
|
||||||
let mut nest = tide::with_state(db);
|
let mut nest = tide::with_state(db);
|
||||||
|
nest.at("hasJoined").get(has_joined::has_joined);
|
||||||
|
nest.at("join").post(join::join);
|
||||||
|
nest.at("profile/:uuid").get(profile::profile);
|
||||||
|
|
||||||
nest
|
nest
|
||||||
}
|
}
|
19
src/server/sessionserver/profile.rs
Normal file
19
src/server/sessionserver/profile.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use tide::{prelude::*, Request, Result};
|
||||||
|
|
||||||
|
use yggdrasil::Database;
|
||||||
|
|
||||||
|
pub async fn profile(req: Request<Database>) -> Result {
|
||||||
|
Err(tide::Error::new(501, anyhow!("Not implemented yet")).into())
|
||||||
|
}
|
@ -10,9 +10,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use sqlx::sqlite::SqlitePoolOptions;
|
use sqlx::{ConnectOptions, SqlitePool};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
@ -24,10 +27,14 @@ pub struct Database {
|
|||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
pub async fn init(config: Config) -> Result<Database> {
|
pub async fn init(config: Config) -> Result<Database> {
|
||||||
|
let mut options = SqliteConnectOptions::from_str(std::env::var("DATABASE_URL")?.as_str())?;
|
||||||
|
options.log_statements(log::LevelFilter::Off);
|
||||||
|
options.log_slow_statements(log::LevelFilter::Info, Duration::from_secs(2));
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
pool: SqlitePoolOptions::new()
|
pool: SqlitePoolOptions::new()
|
||||||
.max_connections(5)
|
.max_connections(5)
|
||||||
.connect(std::env::var("DATABASE_URL")?.as_str())
|
.connect_with(options)
|
||||||
.await?,
|
.await?,
|
||||||
config,
|
config,
|
||||||
})
|
})
|
||||||
|
@ -13,6 +13,8 @@ use std::{error::Error, fmt};
|
|||||||
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::errors::YggdrasilErrorType::{BadRequestException, BaseYggdrasilException, ForbiddenOperationException, IllegalArgumentException};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct YggdrasilError(pub YggdrasilErrorType, pub String, pub u16, pub bool);
|
pub struct YggdrasilError(pub YggdrasilErrorType, pub String, pub u16, pub bool);
|
||||||
// error type, cause, status code, do pass through
|
// error type, cause, status code, do pass through
|
||||||
@ -48,4 +50,41 @@ impl YggdrasilError {
|
|||||||
"developerMessage": self.1.to_owned()
|
"developerMessage": self.1.to_owned()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_base(msg: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
0: BaseYggdrasilException,
|
||||||
|
1: msg.to_string(),
|
||||||
|
2: 500,
|
||||||
|
3: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_forbidden(msg: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
0: ForbiddenOperationException,
|
||||||
|
1: msg.to_string(),
|
||||||
|
2: 403,
|
||||||
|
3: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_bad_request(msg: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
0: BadRequestException,
|
||||||
|
1: msg.to_string(),
|
||||||
|
2: 400,
|
||||||
|
3: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_illegal_argument(msg: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
0: IllegalArgumentException,
|
||||||
|
1: msg.to_string(),
|
||||||
|
2: 500,
|
||||||
|
3: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,333 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::time::UNIX_EPOCH;
|
|
||||||
|
|
||||||
use json::{JsonValue, object};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use tide::convert::Serialize;
|
|
||||||
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
// Account
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct Account {
|
|
||||||
pub id: i64,
|
|
||||||
pub email: String,
|
|
||||||
pub password_hash: String,
|
|
||||||
pub language: String,
|
|
||||||
pub country: String,
|
|
||||||
pub selected_profile: Option<Profile>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Account {
|
|
||||||
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
|
||||||
let record = sqlx::query!("SELECT * FROM accounts WHERE id = $1", id)
|
|
||||||
.fetch_one(&db.pool)
|
|
||||||
.await;
|
|
||||||
match record {
|
|
||||||
Ok(a) => Some(Self {
|
|
||||||
id,
|
|
||||||
email: a.email,
|
|
||||||
password_hash: a.password_hash,
|
|
||||||
language: a.language,
|
|
||||||
country: a.country,
|
|
||||||
selected_profile: match a.selected_profile {
|
|
||||||
None => None,
|
|
||||||
Some(profile_id) => Profile::from_id(db, profile_id).await,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attributes
|
|
||||||
// (technically not database struct but whatever)
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct ProfileAttributes {
|
|
||||||
pub can_chat: bool,
|
|
||||||
pub can_play_multiplayer: bool,
|
|
||||||
pub can_play_realms: bool,
|
|
||||||
pub use_filter: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProfileAttributes {
|
|
||||||
pub fn to_json(&self) -> JsonValue {
|
|
||||||
json::object! {
|
|
||||||
privileges: {
|
|
||||||
onlineChat: { enabled: self.can_chat },
|
|
||||||
multiplayerServer: { enabled: self.can_play_multiplayer },
|
|
||||||
multiplayerRealms: { enabled: self.can_play_realms },
|
|
||||||
telemetry: { enabled: false },
|
|
||||||
},
|
|
||||||
profanityFilterPreferences: {
|
|
||||||
profanityFilterOn: self.use_filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blocked Server
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct BlockedServer {
|
|
||||||
pub id: i64,
|
|
||||||
pub pattern: String,
|
|
||||||
pub sha1: String,
|
|
||||||
pub reason: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockedServer {
|
|
||||||
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
|
||||||
let record = sqlx::query!("SELECT * FROM blocked_servers WHERE id = $1", id)
|
|
||||||
.fetch_one(&db.pool)
|
|
||||||
.await;
|
|
||||||
match record {
|
|
||||||
Ok(s) => Some(Self {
|
|
||||||
id,
|
|
||||||
pattern: s.pattern,
|
|
||||||
sha1: s.sha1,
|
|
||||||
reason: s.reason,
|
|
||||||
}),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cape
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct Cape {
|
|
||||||
pub id: i64,
|
|
||||||
pub friendly_id: String,
|
|
||||||
pub alias: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cape {
|
|
||||||
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
|
||||||
let record = sqlx::query!("SELECT * FROM capes WHERE id = $1", id)
|
|
||||||
.fetch_one(&db.pool)
|
|
||||||
.await;
|
|
||||||
match record {
|
|
||||||
Ok(c) => Some(Self {
|
|
||||||
id,
|
|
||||||
friendly_id: c.friendly_id,
|
|
||||||
alias: c.alias,
|
|
||||||
}),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Profile
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct Profile {
|
|
||||||
pub id: i64,
|
|
||||||
pub uuid: String,
|
|
||||||
|
|
||||||
pub created: i64, // unix timestamp / 1000
|
|
||||||
|
|
||||||
pub owner: i64,
|
|
||||||
pub name: String,
|
|
||||||
pub name_history: String,
|
|
||||||
|
|
||||||
pub skin_variant: String,
|
|
||||||
pub capes: Option<Vec<Cape>>,
|
|
||||||
pub active_cape: Option<Cape>,
|
|
||||||
|
|
||||||
pub attributes: ProfileAttributes,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Profile {
|
|
||||||
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
|
||||||
let record = sqlx::query!("SELECT * FROM profiles WHERE id = $1", id)
|
|
||||||
.fetch_one(&db.pool)
|
|
||||||
.await;
|
|
||||||
match record {
|
|
||||||
Ok(p) => Some(Self {
|
|
||||||
id,
|
|
||||||
uuid: p.uuid,
|
|
||||||
created: p.created,
|
|
||||||
owner: p.owner,
|
|
||||||
name: p.name,
|
|
||||||
name_history: p.name_history,
|
|
||||||
skin_variant: p.skin_variant,
|
|
||||||
capes: match p.capes {
|
|
||||||
None => None,
|
|
||||||
Some(capes) => Some(
|
|
||||||
json::parse(capes.as_str())
|
|
||||||
.map(|c| {
|
|
||||||
serde_json::from_str::<Cape>(c.to_string().as_str())
|
|
||||||
.expect("Couldn't parse cape")
|
|
||||||
})
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
active_cape: match p.active_cape {
|
|
||||||
None => None,
|
|
||||||
Some(active_cape) => Cape::from_id(db, active_cape).await,
|
|
||||||
},
|
|
||||||
attributes: serde_json::from_str(p.attributes.as_str())
|
|
||||||
.expect("Couldn't parse profile attributes"),
|
|
||||||
}),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_skin(&self, db: &Database) -> Option<String> {
|
|
||||||
// TODO: skin overrides
|
|
||||||
if self.skin_variant == "NONE" {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(format!(
|
|
||||||
"{}/textures/skins/{}",
|
|
||||||
db.config.external_base_url, self.uuid
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_cape(&self, db: &Database) -> Option<String> {
|
|
||||||
// TODO: cape overrides
|
|
||||||
if self.active_cape.is_none() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cape = self.active_cape.as_ref().unwrap();
|
|
||||||
Some(format!(
|
|
||||||
"{}/textures/capes/{}",
|
|
||||||
db.config.external_base_url, cape.alias
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct Session {
|
|
||||||
pub id: i64,
|
|
||||||
pub profile: Profile,
|
|
||||||
pub server_id: String,
|
|
||||||
pub ip_addr: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Session {
|
|
||||||
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
|
||||||
let record = sqlx::query!("SELECT * FROM sessions WHERE id = $1", id)
|
|
||||||
.fetch_one(&db.pool)
|
|
||||||
.await;
|
|
||||||
match record {
|
|
||||||
Ok(s) => Some(Self {
|
|
||||||
id,
|
|
||||||
profile: Profile::from_id(db, s.profile).await.unwrap(),
|
|
||||||
server_id: s.server_id,
|
|
||||||
ip_addr: s.ip_addr,
|
|
||||||
}),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Textures
|
|
||||||
|
|
||||||
pub struct TexturedObject {}
|
|
||||||
|
|
||||||
impl TexturedObject {
|
|
||||||
pub async fn from_profile(db: &Database, profile: &Profile) -> JsonValue {
|
|
||||||
let mut object = object! {
|
|
||||||
timestamp: std::time::SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards?!").as_millis() as u64,
|
|
||||||
profile_id: profile.uuid.to_owned(),
|
|
||||||
profile_name: profile.name.to_owned(),
|
|
||||||
textures: object!{}
|
|
||||||
};
|
|
||||||
|
|
||||||
if profile.skin_variant != "NONE" {
|
|
||||||
let skin_url = profile.get_skin(db).await;
|
|
||||||
|
|
||||||
if skin_url.is_some() {
|
|
||||||
object["textures"]["SKIN"] = object! { url: skin_url };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile.active_cape.is_some() {
|
|
||||||
let cape_url = profile.get_cape(db).await;
|
|
||||||
|
|
||||||
if cape_url.is_some() {
|
|
||||||
object["textures"]["CAPE"] = object! { url: cape_url };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object! {
|
|
||||||
id: profile.uuid.replace("-", ""),
|
|
||||||
name: profile.name.to_owned(),
|
|
||||||
properties: [
|
|
||||||
// TODO: signing textures
|
|
||||||
// unsigned ? encode : sign
|
|
||||||
Self::encode_textures(&object)
|
|
||||||
// Self::sign_textures(object)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode_textures(textures: &JsonValue) -> JsonValue {
|
|
||||||
use base64::{Engine, engine::general_purpose::URL_SAFE as base64};
|
|
||||||
|
|
||||||
let serialized = textures.to_string();
|
|
||||||
let mut encoded = String::new();
|
|
||||||
base64.encode_string(serialized, &mut encoded);
|
|
||||||
|
|
||||||
object! {
|
|
||||||
name: "textures",
|
|
||||||
value: encoded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sign_textures(textures: &JsonValue) -> JsonValue {
|
|
||||||
// TODO: signing textures
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tokens
|
|
||||||
|
|
||||||
pub struct Token {
|
|
||||||
id: i64,
|
|
||||||
access: String,
|
|
||||||
client: String,
|
|
||||||
account: Account,
|
|
||||||
issued: i64,
|
|
||||||
expires: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token {
|
|
||||||
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
|
||||||
let record = sqlx::query!("SELECT * FROM tokens WHERE id = $1", id)
|
|
||||||
.fetch_one(&db.pool)
|
|
||||||
.await;
|
|
||||||
match record {
|
|
||||||
Ok(t) => Some(Self {
|
|
||||||
id,
|
|
||||||
access: t.access,
|
|
||||||
client: t.client,
|
|
||||||
account: Account::from_id(db, t.account)
|
|
||||||
.await
|
|
||||||
.expect("No account associated with token"),
|
|
||||||
issued: t.issued,
|
|
||||||
expires: t.expires,
|
|
||||||
}),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
109
src/util/structs/Account.rs
Normal file
109
src/util/structs/Account.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
use structs::Profile::{Profile, ProfileRaw};
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct Account {
|
||||||
|
pub id: i64,
|
||||||
|
pub email: String,
|
||||||
|
pub password_hash: String,
|
||||||
|
pub language: String,
|
||||||
|
pub country: String,
|
||||||
|
pub selected_profile: Option<Profile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Account {
|
||||||
|
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
||||||
|
let record = sqlx::query_as!(AccountRaw, "SELECT * FROM accounts WHERE id = $1", id)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Ok(r) => Some(r.complete(db).await),
|
||||||
|
Err(_) => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_email(db: &Database, email: String) -> Option<Self> {
|
||||||
|
let record = sqlx::query_as!(AccountRaw, "SELECT * FROM accounts WHERE email = $1", email)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Ok(r) => Some(r.complete(db).await),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_profiles(&self, db: &Database) -> Option<Vec<Profile>> {
|
||||||
|
let record = sqlx::query_as!(ProfileRaw, "SELECT * FROM profiles WHERE owner = $1", self.id).fetch_all(&db.pool).await;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Ok(r) => {
|
||||||
|
let mut collection = vec![];
|
||||||
|
for re in r {
|
||||||
|
collection.push(re.complete(db).await)
|
||||||
|
}
|
||||||
|
Some(collection)
|
||||||
|
} // oh boy
|
||||||
|
Err(_) => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_user(&self) -> Value {
|
||||||
|
json!({
|
||||||
|
"id": self.id,
|
||||||
|
"username": self.email,
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "preferredLanguage",
|
||||||
|
"value": self.language
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "registrationCountry",
|
||||||
|
"value": self.country
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct AccountRaw {
|
||||||
|
pub id: i64,
|
||||||
|
pub email: String,
|
||||||
|
pub password_hash: String,
|
||||||
|
pub language: String,
|
||||||
|
pub country: String,
|
||||||
|
pub selected_profile: Option<i64>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountRaw {
|
||||||
|
pub async fn complete(self, db: &Database) -> Account {
|
||||||
|
Account {
|
||||||
|
id: self.id,
|
||||||
|
email: self.email,
|
||||||
|
password_hash: self.password_hash,
|
||||||
|
language: self.language,
|
||||||
|
country: self.country,
|
||||||
|
selected_profile: match self.selected_profile {
|
||||||
|
None => None,
|
||||||
|
Some(id) => Profile::from_id(db, id).await
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
src/util/structs/BlockedServer.rs
Normal file
35
src/util/structs/BlockedServer.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct BlockedServer {
|
||||||
|
pub id: i64,
|
||||||
|
pub pattern: String,
|
||||||
|
pub sha1: String,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockedServer {
|
||||||
|
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
||||||
|
let record = sqlx::query_as!(BlockedServer, "SELECT * FROM blocked_servers WHERE id = $1", id)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Ok(r) => Some(r),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/util/structs/Cape.rs
Normal file
38
src/util/structs/Cape.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct Cape {
|
||||||
|
pub id: i64,
|
||||||
|
pub friendly_id: String,
|
||||||
|
pub alias: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cape {
|
||||||
|
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
||||||
|
let record = sqlx::query_as!(Cape, "SELECT * FROM capes WHERE id = $1", id)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Ok(r) => Some(r),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capes_to_string(capes: Vec<Cape>) -> String {
|
||||||
|
capes.into_iter().map(|c| c.alias).collect::<Vec<String>>().join(",")
|
||||||
|
}
|
||||||
|
}
|
144
src/util/structs/Profile.rs
Normal file
144
src/util/structs/Profile.rs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use structs::{Cape::Cape, ProfileAttributes::ProfileAttributes};
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct Profile {
|
||||||
|
pub id: i64,
|
||||||
|
pub uuid: String,
|
||||||
|
|
||||||
|
pub created: i64, // unix timestamp / 1000
|
||||||
|
|
||||||
|
pub owner: i64,
|
||||||
|
pub name: String,
|
||||||
|
pub name_history: String,
|
||||||
|
|
||||||
|
pub skin_variant: String,
|
||||||
|
pub capes: Option<Vec<Cape>>,
|
||||||
|
pub active_cape: Option<Cape>,
|
||||||
|
|
||||||
|
pub attributes: ProfileAttributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Profile {
|
||||||
|
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
||||||
|
let record = sqlx::query_as!(ProfileRaw, "SELECT * FROM profiles WHERE id = $1", id)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Ok(r) => Some(r.complete(db).await),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_uuid(db: &Database, uuid: String) -> Option<Self> {
|
||||||
|
let record = sqlx::query_as!(ProfileRaw, "SELECT * FROM profiles WHERE uuid = $1", uuid)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Ok(r) => Some(r.complete(db).await),
|
||||||
|
Err(_) => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_name(db: &Database, name: String) -> Option<Self> {
|
||||||
|
let record = sqlx::query_as!(ProfileRaw, "SELECT * FROM profiles WHERE name = $1", name)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Ok(r) => Some(r.complete(db).await),
|
||||||
|
Err(_) => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_skin(&self, db: &Database) -> Option<String> {
|
||||||
|
// TODO: skin overrides
|
||||||
|
if self.skin_variant == "NONE" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(format!(
|
||||||
|
"{}/textures/skins/{}",
|
||||||
|
db.config.external_base_url, self.uuid
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_cape(&self, db: &Database) -> Option<String> {
|
||||||
|
// TODO: cape overrides
|
||||||
|
if self.active_cape.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cape = self.active_cape.as_ref().unwrap();
|
||||||
|
Some(format!(
|
||||||
|
"{}/textures/capes/{}",
|
||||||
|
db.config.external_base_url, cape.alias
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct ProfileRaw {
|
||||||
|
pub id: i64,
|
||||||
|
pub uuid: String,
|
||||||
|
|
||||||
|
pub created: i64,
|
||||||
|
|
||||||
|
pub owner: i64,
|
||||||
|
pub name: String,
|
||||||
|
pub name_history: String,
|
||||||
|
|
||||||
|
pub skin_variant: String,
|
||||||
|
pub capes: Option<String>,
|
||||||
|
pub active_cape: Option<i64>,
|
||||||
|
|
||||||
|
pub attributes: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProfileRaw {
|
||||||
|
pub async fn complete(self, db: &Database) -> Profile {
|
||||||
|
Profile {
|
||||||
|
id: self.id,
|
||||||
|
uuid: self.uuid,
|
||||||
|
created: self.created,
|
||||||
|
owner: self.owner,
|
||||||
|
name: self.name,
|
||||||
|
name_history: self.name_history,
|
||||||
|
skin_variant: self.skin_variant,
|
||||||
|
capes: match self.capes {
|
||||||
|
None => None,
|
||||||
|
Some(capes) => Some(
|
||||||
|
json::parse(capes.as_str())
|
||||||
|
.map(|c| {
|
||||||
|
serde_json::from_str::<Cape>(c.to_string().as_str())
|
||||||
|
.expect("Couldn't parse cape")
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
active_cape: match self.active_cape {
|
||||||
|
None => None,
|
||||||
|
Some(active_cape) => Cape::from_id(db, active_cape).await,
|
||||||
|
},
|
||||||
|
attributes: serde_json::from_str(self.attributes.as_str())
|
||||||
|
.expect("Couldn't parse profile attributes"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/util/structs/ProfileAttributes.rs
Normal file
38
src/util/structs/ProfileAttributes.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct ProfileAttributes {
|
||||||
|
pub can_chat: bool,
|
||||||
|
pub can_play_multiplayer: bool,
|
||||||
|
pub can_play_realms: bool,
|
||||||
|
pub use_filter: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProfileAttributes {
|
||||||
|
pub fn to_json(&self) -> Value {
|
||||||
|
json!({
|
||||||
|
"privileges": {
|
||||||
|
"onlineChat": { "enabled": self.can_chat },
|
||||||
|
"multiplayerServer": { "enabled": self.can_play_multiplayer },
|
||||||
|
"multiplayerRealms": { "enabled": self.can_play_realms },
|
||||||
|
"telemetry": { "enabled": false },
|
||||||
|
},
|
||||||
|
"profanityFilterPreferences": {
|
||||||
|
"profanityFilterOn": self.use_filter
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
57
src/util/structs/Session.rs
Normal file
57
src/util/structs/Session.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use structs::Profile::Profile;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct Session {
|
||||||
|
pub id: i64,
|
||||||
|
pub profile: Profile,
|
||||||
|
pub server_id: String,
|
||||||
|
pub ip_addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
||||||
|
let record = sqlx::query_as!(RawSession, "SELECT * FROM sessions WHERE id = $1", id)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Ok(r) => Some(r.complete(db).await),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct RawSession {
|
||||||
|
pub id: i64,
|
||||||
|
pub profile: i64,
|
||||||
|
pub server_id: String,
|
||||||
|
pub ip_addr: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawSession {
|
||||||
|
pub async fn complete(self, db: &Database) -> Session {
|
||||||
|
Session {
|
||||||
|
id: self.id,
|
||||||
|
profile: Profile::from_id(db, self.profile).await.expect("Couldn't resolve session profile"),
|
||||||
|
server_id: self.server_id,
|
||||||
|
ip_addr: self.ip_addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
77
src/util/structs/TexturedObject.rs
Normal file
77
src/util/structs/TexturedObject.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::time::UNIX_EPOCH;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use json::{object, JsonValue};
|
||||||
|
use structs::Profile::Profile;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct TexturedObject {}
|
||||||
|
|
||||||
|
impl TexturedObject {
|
||||||
|
pub async fn from_profile(db: &Database, profile: &Profile) -> JsonValue {
|
||||||
|
let mut object = object! {
|
||||||
|
timestamp: std::time::SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards?!").as_millis() as u64,
|
||||||
|
profile_id: profile.uuid.to_owned(),
|
||||||
|
profile_name: profile.name.to_owned(),
|
||||||
|
textures: object!{}
|
||||||
|
};
|
||||||
|
|
||||||
|
if profile.skin_variant != "NONE" {
|
||||||
|
let skin_url = profile.get_skin(db).await;
|
||||||
|
|
||||||
|
if skin_url.is_some() {
|
||||||
|
object["textures"]["SKIN"] = object! { url: skin_url };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if profile.active_cape.is_some() {
|
||||||
|
let cape_url = profile.get_cape(db).await;
|
||||||
|
|
||||||
|
if cape_url.is_some() {
|
||||||
|
object["textures"]["CAPE"] = object! { url: cape_url };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object! {
|
||||||
|
id: profile.uuid.replace("-", ""),
|
||||||
|
name: profile.name.to_owned(),
|
||||||
|
properties: [
|
||||||
|
// TODO: signing textures
|
||||||
|
// unsigned ? encode : sign
|
||||||
|
Self::encode_textures(&object)
|
||||||
|
// Self::sign_textures(object)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_textures(textures: &JsonValue) -> JsonValue {
|
||||||
|
use base64::{Engine, engine::general_purpose::URL_SAFE as base64};
|
||||||
|
|
||||||
|
let serialized = textures.to_string();
|
||||||
|
let mut encoded = String::new();
|
||||||
|
base64.encode_string(serialized, &mut encoded);
|
||||||
|
|
||||||
|
object! {
|
||||||
|
name: "textures",
|
||||||
|
value: encoded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign_textures(textures: &JsonValue) -> JsonValue {
|
||||||
|
// TODO: signing textures
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
64
src/util/structs/Token.rs
Normal file
64
src/util/structs/Token.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use structs::Account::Account;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct Token {
|
||||||
|
id: i64,
|
||||||
|
access: String,
|
||||||
|
client: String,
|
||||||
|
account: Account,
|
||||||
|
issued: i64,
|
||||||
|
expires: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Token {
|
||||||
|
pub async fn from_id(db: &Database, id: i64) -> Option<Self> {
|
||||||
|
let record = sqlx::query_as!(RawToken, "SELECT * FROM tokens WHERE id = $1", id)
|
||||||
|
.fetch_one(&db.pool)
|
||||||
|
.await;
|
||||||
|
match record {
|
||||||
|
Ok(t) => Some(t.complete(db).await),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_token() -> String {
|
||||||
|
random_string::generate(128, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RawToken {
|
||||||
|
id: i64,
|
||||||
|
access: String,
|
||||||
|
client: String,
|
||||||
|
account: i64,
|
||||||
|
issued: i64,
|
||||||
|
expires: i64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawToken {
|
||||||
|
pub async fn complete(self, db: &Database) -> Token {
|
||||||
|
Token {
|
||||||
|
id: self.id,
|
||||||
|
access: self.access,
|
||||||
|
client: self.client,
|
||||||
|
account: Account::from_id(db, self.account).await.expect("Couldn't resolve token owner"),
|
||||||
|
issued: self.issued,
|
||||||
|
expires: self.expires,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/util/structs/mod.rs
Normal file
21
src/util/structs/mod.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// yeah yeah snake_case whatever
|
||||||
|
pub mod Account;
|
||||||
|
pub mod BlockedServer;
|
||||||
|
pub mod Cape;
|
||||||
|
pub mod Profile;
|
||||||
|
pub mod ProfileAttributes;
|
||||||
|
pub mod Session;
|
||||||
|
pub mod TexturedObject;
|
||||||
|
pub mod Token;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user