Create stubs for a lot of routes; rework structs.rs to module

This commit is contained in:
0xf8 2023-06-20 00:52:08 -04:00
parent 1e2b69815a
commit 50c849282a
Signed by: 0xf8
GPG Key ID: 446580D758689584
32 changed files with 985 additions and 358 deletions

View File

@ -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"

View File

@ -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,

View File

@ -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!");

View File

@ -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
} }

View 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())
}

View 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())
}

View File

@ -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
} }
@ -39,4 +41,4 @@ async fn authlib_meta(req: Request<Database>) -> Result {
"skinDomains": config.skin_domains "skinDomains": config.skin_domains
// TODO: public key signature // TODO: public key signature
}).into()) }).into())
} }

View 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())
}

View 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())
}

View File

@ -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
} }

View 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())
}

View 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())
}

View 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())
}

View 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())
}

View File

@ -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
} }

View File

@ -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?;

View 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())
}

View 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())
}

View File

@ -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
} }

View 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())
}

View File

@ -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,
}) })

View File

@ -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
}
}
} }

View File

@ -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
View 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
},
}
}
}

View 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
View 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
View 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"),
}
}
}

View 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
}
})
}
}

View 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,
}
}
}

View 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
View 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
View 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;