Partial rewrite
This commit is contained in:
parent
c2f2706582
commit
1e2b69815a
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,5 +6,7 @@ Cargo.lock
|
|||||||
yggdrasilrc
|
yggdrasilrc
|
||||||
yggdrasilrc.old
|
yggdrasilrc.old
|
||||||
yggdrasil.db*
|
yggdrasil.db*
|
||||||
|
priv.key
|
||||||
|
publ.key
|
||||||
|
|
||||||
.env
|
.env
|
@ -9,10 +9,17 @@ license = "GPL3.0-or-later"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
|
base64 = "0.21.2"
|
||||||
colored = "2.0.0"
|
colored = "2.0.0"
|
||||||
driftwood = "0.0.7"
|
driftwood = "0.0.7"
|
||||||
ftlog = "0.2.7"
|
femme = "2.2.1"
|
||||||
|
json = "0.12.4"
|
||||||
|
log = "0.4.19"
|
||||||
|
rand = "0.8.5"
|
||||||
|
rsa = "0.9.2"
|
||||||
serde = { version = "1.0.164", features = ["derive"] }
|
serde = { version = "1.0.164", features = ["derive"] }
|
||||||
|
serde_json = "1.0.97"
|
||||||
|
sha2 = "0.10.7"
|
||||||
sqlx = { version = "0.6.3", features = ["sqlite", "runtime-tokio-native-tls"] }
|
sqlx = { version = "0.6.3", features = ["sqlite", "runtime-tokio-native-tls"] }
|
||||||
tide = "0.16.0"
|
tide = "0.16.0"
|
||||||
time = "0.3.22"
|
time = "0.3.22"
|
||||||
|
55
migrations/0_yggdrasil.sql
Normal file
55
migrations/0_yggdrasil.sql
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS accounts (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
email TEXT NOT NULL UNIQUE,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
language TEXT NOT NULL,
|
||||||
|
country TEXT NOT NULL,
|
||||||
|
selected_profile INTEGER,
|
||||||
|
FOREIGN KEY(selected_profile) REFERENCES profiles(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS blocked_servers (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
pattern TEXT NOT NULL UNIQUE,
|
||||||
|
sha1 TEXT NOT NULL UNIQUE,
|
||||||
|
reason TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS capes (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
friendly_id TEXT NOT NULL UNIQUE,
|
||||||
|
alias TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS profiles (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
uuid TEXT NOT NULL UNIQUE,
|
||||||
|
created INTEGER NOT NULL,
|
||||||
|
owner INTEGER NOT NULL,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
name_history TEXT NOT NULL,
|
||||||
|
skin_variant TEXT NOT NULL,
|
||||||
|
capes TEXT,
|
||||||
|
active_cape INTEGER,
|
||||||
|
attributes TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(owner) REFERENCES accounts(id),
|
||||||
|
FOREIGN KEY(active_cape) REFERENCES capes(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
profile INTEGER NOT NULL,
|
||||||
|
server_id TEXT NOT NULL,
|
||||||
|
ip_addr TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(profile) REFERENCES profiles(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tokens (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
access TEXT NOT NULL UNIQUE,
|
||||||
|
client TEXT NOT NULL,
|
||||||
|
account INTEGER NOT NULL,
|
||||||
|
issued INTEGER NOT NULL,
|
||||||
|
expires INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(account) REFERENCES accounts(id)
|
||||||
|
);
|
@ -1,62 +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::env;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use ftlog::{debug, error, info, log, trace, warn};
|
|
||||||
use sqlx::sqlite::SqlitePoolOptions;
|
|
||||||
use sqlx::SqlitePool;
|
|
||||||
|
|
||||||
use crate::config::Config;
|
|
||||||
|
|
||||||
pub struct Database {
|
|
||||||
db: SqlitePool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Database {
|
|
||||||
pub async fn init(config: &Config) -> Result<Self> {
|
|
||||||
let pool = SqlitePoolOptions::new().max_connections(5).connect("").await?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
db: pool
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn open(config: &Config) -> Result<SqlitePool> {
|
|
||||||
Ok(SqlitePoolOptions::new().max_connections(5).connect(&env::var("DATABASE_URL")?).await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn new(config: &Config) -> Result<SqlitePool> {
|
|
||||||
let pool = Self::open(&config).await?;
|
|
||||||
pool.
|
|
||||||
let mut conn = pool.acquire().await?;
|
|
||||||
|
|
||||||
sqlx::query!(r#"
|
|
||||||
|
|
||||||
"#).execute(&mut conn).await?;
|
|
||||||
|
|
||||||
pool.prepare("").await?;
|
|
||||||
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ensure_db(config: &Config) -> Result<SqlitePool> {
|
|
||||||
let db_path = PathBuf::from(&env::var("DATABASE_URL")?);
|
|
||||||
|
|
||||||
match db_path.try_exists()? {
|
|
||||||
true => Self::open(&config).await,
|
|
||||||
false => Self::new(&config).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
17
src/lib.rs
Normal file
17
src/lib.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![feature(fs_try_exists)]
|
||||||
|
|
||||||
|
pub use util::*;
|
||||||
|
|
||||||
|
mod util;
|
||||||
|
|
37
src/main.rs
37
src/main.rs
@ -11,40 +11,39 @@
|
|||||||
|
|
||||||
#![feature(fs_try_exists)]
|
#![feature(fs_try_exists)]
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use ftlog::{debug, error, info, log, trace, warn};
|
use log::{debug, error, info, log, trace, warn};
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
|
|
||||||
use crate::config::Config;
|
use yggdrasil::*;
|
||||||
use crate::server::start_server;
|
|
||||||
|
|
||||||
mod database;
|
|
||||||
mod server;
|
mod server;
|
||||||
mod config;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// Build logger
|
// Early catch
|
||||||
let time_format = time::format_description::parse_owned::<1>("[[[year]-[month]-[day] [hour]:[minute]:[second]]")?;
|
if std::env::var("DATABASE_URL").is_err() {
|
||||||
|
bail!("DATABASE_URL needs to be set.")
|
||||||
|
}
|
||||||
|
|
||||||
ftlog::builder()
|
// Start logger
|
||||||
.time_format(time_format)
|
femme::start();
|
||||||
.bounded(100_000, false)
|
|
||||||
.try_init()
|
|
||||||
.expect("Failed to initialize logger");
|
|
||||||
|
|
||||||
// Load config
|
// Load config
|
||||||
let config = Config::load()?;
|
let config = Config::load()?;
|
||||||
info!("Config location: {}", config.location.display());
|
info!("Config location: {}", config.location.display());
|
||||||
|
|
||||||
// Start server
|
// Load database
|
||||||
info!("Starting yggdrasil server!");
|
let db = Database::init(config).await?;
|
||||||
|
info!("Database URL: {}", std::env::var("DATABASE_URL")?);
|
||||||
|
|
||||||
let server_thread = spawn(start_server(config.to_owned()));
|
// Start server
|
||||||
|
let server_thread = spawn(server::start(db));
|
||||||
server_thread.await??;
|
server_thread.await??;
|
||||||
|
|
||||||
info!("Server stopped!");
|
warn!("Server stopped!");
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
Ok(ftlog::logger().flush())
|
Ok(log::logger().flush())
|
||||||
}
|
}
|
||||||
|
23
src/server/account/mod.rs
Normal file
23
src/server/account/mod.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub use log::{info, log, warn};
|
||||||
|
pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After};
|
||||||
|
|
||||||
|
pub use yggdrasil::*;
|
||||||
|
|
||||||
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
|
info!("Loading nest");
|
||||||
|
|
||||||
|
let mut nest = tide::with_state(db);
|
||||||
|
|
||||||
|
nest
|
||||||
|
}
|
23
src/server/auth/mod.rs
Normal file
23
src/server/auth/mod.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub use log::{info, log, warn};
|
||||||
|
pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After};
|
||||||
|
|
||||||
|
pub use yggdrasil::*;
|
||||||
|
|
||||||
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
|
info!("Loading nest");
|
||||||
|
|
||||||
|
let mut nest = tide::with_state(db);
|
||||||
|
|
||||||
|
nest
|
||||||
|
}
|
42
src/server/authlib/mod.rs
Normal file
42
src/server/authlib/mod.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub use log::{info, log, warn};
|
||||||
|
pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After};
|
||||||
|
|
||||||
|
pub use yggdrasil::*;
|
||||||
|
|
||||||
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
|
info!("Loading nest");
|
||||||
|
|
||||||
|
let mut nest = tide::with_state(db);
|
||||||
|
|
||||||
|
nest.at("/").get(authlib_meta);
|
||||||
|
|
||||||
|
nest
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn authlib_meta(req: Request<Database>) -> Result {
|
||||||
|
let config = &req.state().config;
|
||||||
|
Ok(json!({
|
||||||
|
"meta": {
|
||||||
|
"implementationName": std::env!("CARGO_PKG_NAME"),
|
||||||
|
"implementationVersion": std::env!("CARGO_PKG_VERSION"),
|
||||||
|
"feature.no_mojang_namespace": config.no_mojang_namespace,
|
||||||
|
"links": {
|
||||||
|
"homepage": config.external_base_url
|
||||||
|
},
|
||||||
|
"serverName": config.server_name,
|
||||||
|
},
|
||||||
|
"skinDomains": config.skin_domains
|
||||||
|
// TODO: public key signature
|
||||||
|
}).into())
|
||||||
|
}
|
56
src/server/mod.rs
Normal file
56
src/server/mod.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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::{Request, Response, utils::After};
|
||||||
|
|
||||||
|
use yggdrasil::*;
|
||||||
|
|
||||||
|
mod account;
|
||||||
|
mod auth;
|
||||||
|
mod authlib;
|
||||||
|
mod services;
|
||||||
|
mod session;
|
||||||
|
|
||||||
|
pub async fn start(db: Database) -> anyhow::Result<()> {
|
||||||
|
let mut app = tide::with_state(db.to_owned());
|
||||||
|
|
||||||
|
// Error handling middleware
|
||||||
|
app.with(After(|mut res: Response| async move {
|
||||||
|
if let Some(err) = res.downcast_error::<errors::YggdrasilError>() {
|
||||||
|
let body = err.to_json();
|
||||||
|
res.set_status(err.2);
|
||||||
|
res.set_body(body);
|
||||||
|
|
||||||
|
// TODO: pass through
|
||||||
|
// err.3: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Index
|
||||||
|
app.at("/").get(|mut req: Request<Database>| async move {
|
||||||
|
req.append_header("x-authlib-injector-api-location", "/authlib/");
|
||||||
|
Ok("Yggdrasil")
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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()));
|
||||||
|
|
||||||
|
// Listen
|
||||||
|
app.listen(format!("{}:{}", &db.config.address, &db.config.port)).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
23
src/server/services/mod.rs
Normal file
23
src/server/services/mod.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub use log::{info, log, warn};
|
||||||
|
pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After};
|
||||||
|
|
||||||
|
pub use yggdrasil::*;
|
||||||
|
|
||||||
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
|
info!("Loading nest");
|
||||||
|
|
||||||
|
let mut nest = tide::with_state(db);
|
||||||
|
|
||||||
|
nest
|
||||||
|
}
|
23
src/server/session/mod.rs
Normal file
23
src/server/session/mod.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub use log::{info, log, warn};
|
||||||
|
pub use tide::{Middleware, prelude::*, Request, Response, Result, utils::After};
|
||||||
|
|
||||||
|
pub use yggdrasil::*;
|
||||||
|
|
||||||
|
pub fn nest(db: Database) -> tide::Server<Database> {
|
||||||
|
info!("Loading nest");
|
||||||
|
|
||||||
|
let mut nest = tide::with_state(db);
|
||||||
|
|
||||||
|
nest
|
||||||
|
}
|
@ -14,15 +14,35 @@ use std::path::PathBuf;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ftlog::{debug, error, info, log, trace, warn};
|
use log::{debug, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub location: PathBuf,
|
pub location: PathBuf,
|
||||||
|
|
||||||
|
#[serde(rename = "http.port")]
|
||||||
pub port: i16,
|
pub port: i16,
|
||||||
|
#[serde(rename = "http.address")]
|
||||||
pub address: String,
|
pub address: String,
|
||||||
|
#[serde(rename = "http.external_base_url")]
|
||||||
|
pub external_base_url: String,
|
||||||
|
|
||||||
|
#[serde(rename = "authlib.server_name")]
|
||||||
|
pub server_name: String,
|
||||||
|
#[serde(rename = "authlib.no_mojang_namespace")]
|
||||||
|
pub no_mojang_namespace: bool,
|
||||||
|
#[serde(rename = "authlib.skin_domains")]
|
||||||
|
pub skin_domains: Vec<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "signing.bits")]
|
||||||
|
pub key_bits: u64,
|
||||||
|
#[serde(rename = "signing.priv.location")]
|
||||||
|
pub key_priv_location: PathBuf,
|
||||||
|
#[serde(rename = "signing.publ.location")]
|
||||||
|
pub key_publ_location: PathBuf,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
@ -31,8 +51,22 @@ impl Default for Config {
|
|||||||
location: std::env::current_dir()
|
location: std::env::current_dir()
|
||||||
.expect("Couldn't get current directory")
|
.expect("Couldn't get current directory")
|
||||||
.join("yggdrasilrc"),
|
.join("yggdrasilrc"),
|
||||||
|
|
||||||
port: 8081,
|
port: 8081,
|
||||||
address: "0.0.0.0".parse().expect("Couldn't parse string"),
|
address: "0.0.0.0".to_string(),
|
||||||
|
external_base_url: format!("http://localhost:8081"),
|
||||||
|
|
||||||
|
server_name: "Yggdrasil".to_string(),
|
||||||
|
no_mojang_namespace: true,
|
||||||
|
skin_domains: vec!["localhost:8081".to_string(), ".localhost:8081".to_string()],
|
||||||
|
|
||||||
|
key_bits: 4096,
|
||||||
|
key_priv_location: std::env::current_dir()
|
||||||
|
.expect("Couldn't get current directory")
|
||||||
|
.join("priv.key"),
|
||||||
|
key_publ_location: std::env::current_dir()
|
||||||
|
.expect("Couldn't get current directory")
|
||||||
|
.join("publ.key"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,24 +9,27 @@
|
|||||||
* 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 anyhow::Result;
|
use anyhow::Result;
|
||||||
use ftlog::{debug, error, info, log, trace, warn};
|
use sqlx::sqlite::SqlitePoolOptions;
|
||||||
use tide::prelude::*;
|
use sqlx::SqlitePool;
|
||||||
use tide::Request;
|
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::*;
|
||||||
|
|
||||||
pub async fn start_server(config: Config) -> Result<()> {
|
#[derive(Clone, Debug)]
|
||||||
let mut app = tide::new();
|
pub struct Database {
|
||||||
app.with(driftwood::DevLogger);
|
pub pool: SqlitePool,
|
||||||
|
pub config: Config,
|
||||||
app.at("/test").get(test);
|
|
||||||
|
|
||||||
app.listen(format!("{}:{}", config.address, config.port)).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn test(mut req: Request<()>) -> tide::Result {
|
impl Database {
|
||||||
Ok(format!("Hello, world!").into())
|
pub async fn init(config: Config) -> Result<Database> {
|
||||||
|
Ok(Self {
|
||||||
|
pool: SqlitePoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.connect(std::env::var("DATABASE_URL")?.as_str())
|
||||||
|
.await?,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
51
src/util/errors.rs
Normal file
51
src/util/errors.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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::{error::Error, fmt};
|
||||||
|
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct YggdrasilError(pub YggdrasilErrorType, pub String, pub u16, pub bool);
|
||||||
|
// error type, cause, status code, do pass through
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum YggdrasilErrorType {
|
||||||
|
BaseYggdrasilException,
|
||||||
|
ForbiddenOperationException,
|
||||||
|
BadRequestException,
|
||||||
|
IllegalArgumentException,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for YggdrasilError {}
|
||||||
|
|
||||||
|
impl fmt::Display for YggdrasilError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use YggdrasilErrorType::*;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
ForbiddenOperationException => write!(f, "FORBIDDEN"),
|
||||||
|
BadRequestException => write!(f, "BAD_REQUEST"),
|
||||||
|
_ => write!(f, "INTERNAL_SERVER_ERROR"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YggdrasilError {
|
||||||
|
pub fn to_json(&self) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"errorType": format!("{}", self),
|
||||||
|
"error": format!("{:?}", self.0),
|
||||||
|
"errorMessage": self.1.to_owned(),
|
||||||
|
"developerMessage": self.1.to_owned()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
22
src/util/mod.rs
Normal file
22
src/util/mod.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub use config::Config;
|
||||||
|
pub use database::Database;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
// TODO: fix signing
|
||||||
|
// https://github.com/RustCrypto/RSA/blob/master/tests/proptests.rs
|
||||||
|
// mod signing;
|
||||||
|
mod database;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod structs;
|
||||||
|
|
91
src/util/signing.rs
Normal file
91
src/util/signing.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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::fs::{read, read_to_string, try_exists, write};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use rsa::{
|
||||||
|
Oaep,
|
||||||
|
pkcs1::DecodeRsaPublicKey,
|
||||||
|
pkcs1::LineEnding,
|
||||||
|
pkcs1v15,
|
||||||
|
pkcs1v15::SigningKey,
|
||||||
|
pkcs8::{EncodePrivateKey, EncodePublicKey},
|
||||||
|
pkcs8::DecodePrivateKey,
|
||||||
|
RsaPrivateKey,
|
||||||
|
RsaPublicKey,
|
||||||
|
signature::{SignatureEncoding, Signer}
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Signing {
|
||||||
|
private: RsaPrivateKey,
|
||||||
|
pub public: RsaPublicKey,
|
||||||
|
pub signing: SigningKey<sha2::Sha256>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signing {
|
||||||
|
pub fn init(config: &Config) -> Result<Self> {
|
||||||
|
Self::ensure(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure(config: &Config) -> Result<Self> {
|
||||||
|
if config.key_publ_location.try_exists()? && config.key_priv_location.try_exists()? {
|
||||||
|
Self::load(config)
|
||||||
|
} else {
|
||||||
|
Self::new(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(config: &Config) -> Result<Self> {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let private = RsaPrivateKey::new(&mut rng, 4096).expect("Couldn't generate rsa key");
|
||||||
|
let public = RsaPublicKey::from(&private);
|
||||||
|
let signing = SigningKey::<sha2::Sha256>::new(private.to_owned());
|
||||||
|
|
||||||
|
let signing = Self { private, public, signing };
|
||||||
|
|
||||||
|
signing.save(config)?;
|
||||||
|
|
||||||
|
Ok(signing)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(config: &Config) -> Result<Self> {
|
||||||
|
let private = DecodePrivateKey::read_pkcs8_der_file(config.key_priv_location.to_owned())?;
|
||||||
|
let public = DecodeRsaPublicKey::read_pkcs1_pem_file(config.key_publ_location.to_owned())?;
|
||||||
|
let signing = SigningKey::<sha2::Sha256>::new(private.to_owned());
|
||||||
|
|
||||||
|
Ok(Self { private, public, signing })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&self, config: &Config) -> Result<()> {
|
||||||
|
let der = self.private.to_pkcs8_der()?;
|
||||||
|
der.write_der_file(config.key_priv_location.to_owned())?;
|
||||||
|
|
||||||
|
let pem = self.public.to_public_key_pem(LineEnding::LF)?;
|
||||||
|
write(config.key_publ_location.to_owned(), pem)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
// let pad = Pss::new();
|
||||||
|
// Ok(self.private.sign(pad, data)?)
|
||||||
|
|
||||||
|
// Ok(self.signing)
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
333
src/util/structs.rs
Normal file
333
src/util/structs.rs
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
/*
|
||||||
|
* 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +0,0 @@
|
|||||||
port = 8081
|
|
||||||
address = "0.0.0.0"
|
|
Loading…
Reference in New Issue
Block a user