feat: auth

This commit is contained in:
LynithDev 2025-04-11 16:06:03 +02:00
parent 6c80a84972
commit a8eb841bd7
13 changed files with 1298 additions and 22 deletions

121
Cargo.lock generated
View file

@ -294,6 +294,12 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64"
version = "0.21.7"
@ -892,6 +898,18 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "crypto-bigint"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array",
"rand_core 0.6.4",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -1152,6 +1170,20 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
"digest",
"elliptic-curve",
"rfc6979",
"signature",
"spki",
]
[[package]]
name = "either"
version = "1.13.0"
@ -1161,6 +1193,26 @@ dependencies = [
"serde",
]
[[package]]
name = "elliptic-curve"
version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"crypto-bigint",
"digest",
"ff",
"generic-array",
"group",
"pem-rfc7468",
"pkcs8",
"rand_core 0.6.4",
"sec1",
"subtle",
"zeroize",
]
[[package]]
name = "embed-resource"
version = "2.5.0"
@ -1259,6 +1311,16 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "ff"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
dependencies = [
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "field-offset"
version = "0.3.6"
@ -1592,6 +1654,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
"zeroize",
]
[[package]]
@ -1730,6 +1793,17 @@ dependencies = [
"system-deps",
]
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "gtk"
version = "0.18.1"
@ -2934,6 +3008,7 @@ dependencies = [
"async-trait",
"async_zip",
"base64 0.22.1",
"byteorder",
"bytes",
"chrono",
"digest",
@ -2950,6 +3025,7 @@ dependencies = [
"onelauncher_gamemode",
"onelauncher_macro",
"onelauncher_migration",
"p256",
"paste",
"rand 0.8.5",
"regex",
@ -3066,6 +3142,18 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "p256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"sha2",
]
[[package]]
name = "pango"
version = "0.18.3"
@ -3406,6 +3494,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "primeorder"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
dependencies = [
"elliptic-curve",
]
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
@ -3834,6 +3931,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560"
[[package]]
name = "rfc6979"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
dependencies = [
"hmac",
"subtle",
]
[[package]]
name = "ring"
version = "0.17.8"
@ -4211,6 +4318,20 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
"generic-array",
"pkcs8",
"subtle",
"zeroize",
]
[[package]]
name = "selectors"
version = "0.22.0"

View file

@ -66,6 +66,7 @@ rand = { workspace = true }
url = { workspace = true }
async-trait = { workspace = true }
paste = { workspace = true }
byteorder = { workspace = true }
# integration
discord-rich-presence = { workspace = true }
@ -76,6 +77,7 @@ sha1 = { workspace = true }
sha2 = { workspace = true }
murmur2 = { workspace = true }
md-5 = { workspace = true }
p256 = { workspace = true }
# io
zip = { workspace = true }

View file

@ -0,0 +1,25 @@
use onelauncher_core::{api::{credentials, proxy::ProxyDynamic}, error::LauncherResult, initialize_core, store::CoreOptions, utils::io::IOError};
use tokio::io::AsyncBufReadExt;
#[tokio::main]
pub async fn main() -> LauncherResult<()> {
initialize_core(CoreOptions::default(), ProxyDynamic::new()).await?;
let flow = credentials::begin().await?;
println!("{flow:#?}");
let stdin = tokio::io::stdin();
let mut reader = tokio::io::BufReader::new(stdin);
let mut code = String::new();
println!("Please enter the code you received:");
reader.read_line(&mut code).await.map_err(IOError::from)?;
println!("Please wait");
code = code.trim().to_string();
let creds = credentials::finish(&code, flow).await?;
println!("Credentials: {creds:#?}");
Ok(())
}

View file

@ -0,0 +1,87 @@
//! Authentication flow manager
use crate::{error::LauncherResult, store::{credentials::{MinecraftCredentials, MinecraftLogin}, State}};
/// Begin a Microsoft authentication flow.
#[tracing::instrument]
pub async fn begin() -> LauncherResult<MinecraftLogin> {
let state = State::get().await?;
let mut store = state.credentials.write().await;
store.begin().await
}
/// Complete a Microsoft authentication flow to recieve [`MinecraftCredentials`].
#[tracing::instrument]
pub async fn finish(code: &str, flow: MinecraftLogin) -> LauncherResult<MinecraftCredentials> {
let state = State::get().await?;
let mut store = state.credentials.write().await;
store.finish(code, flow).await
}
/// Get the current default user if it exists by [`uuid::Uuid`].
#[tracing::instrument]
pub async fn get_default_user() -> LauncherResult<Option<uuid::Uuid>> {
let state = State::get().await?;
let store = state.credentials.read().await;
Ok(store.default_user)
}
/// Set the current default user by [`uuid::Uuid`].
#[tracing::instrument]
pub async fn set_default_user(user: Option<uuid::Uuid>) -> LauncherResult<()> {
let user = match user {
Some(user) => Some(get_user(user).await?.id),
None => None,
};
let state = State::get().await?;
let mut store = state.credentials.write().await;
store.default_user = user;
store.save().await?;
Ok(())
}
/// Remove a user account by its [`uuid::Uuid`] from the global store.
#[tracing::instrument]
pub async fn remove_user(user: uuid::Uuid) -> LauncherResult<()> {
let state = State::get().await?;
let mut store = state.credentials.write().await;
store.remove(user).await?;
if store.default_user == Some(user) {
set_default_user(Some(user)).await?;
}
Ok(())
}
/// Get a list of user [`MinecraftCredentials`].
#[tracing::instrument]
pub async fn users() -> LauncherResult<Vec<MinecraftCredentials>> {
let state = State::get().await?;
let store = state.credentials.read().await;
Ok(store.users.values().cloned().collect())
}
/// Get a specifc user's [`MinecraftCredentials`] by their [`uuid::Uuid`].
/// Use [`crate::store::MinecraftState#refresh`] instead.
#[tracing::instrument]
pub async fn get_user(user: uuid::Uuid) -> LauncherResult<MinecraftCredentials> {
let state = State::get().await?;
let store = state.credentials.read().await;
let user = store
.users
.get(&user)
.ok_or_else(|| anyhow::anyhow!("failed to get nonexistent user with uuid {user}"))?
.clone();
Ok(user)
}

View file

@ -1,4 +1,5 @@
pub mod cluster;
pub mod credentials;
pub mod ingress;
pub mod game;
pub mod proxy;

View file

@ -32,22 +32,6 @@ pub const MCLOGS_API_URL: &str = "https://api.mclo.gs/1";
/// <https://skyclient.co/> metadata base url.
pub const SKYCLIENT_BASE_URL: &str = "https://raw.githubusercontent.com/SkyblockClient/SkyblockClient-REPO/refs/heads/main/v1";
// =========== Paths ===========
/// The public `settings.json` file used to store the global [`Settings`] state.
///
/// [`Settings`]: crate::store::Settings
pub const SETTINGS_FILE: &str = "settings.json";
/// The public `authentication.json` file used to store the global [`MinecraftAuth`] state.
///
/// [`MinecraftAuth`]: crate::store::MinecraftAuth
pub const AUTH_FILE: &str = "authentication.json";
/// The current [`Settings`] format version, bumped for breaking changes.
/// If updated, a config file migration logic **NEEDS** to be implemented.
///
/// [`Settings`]: crate::store::Settings
pub const CURRENT_SETTINGS_FORMAT_VERSION: u32 = 2;
// =========== Hacky Mojang-spec OS constants ===========
#[cfg(target_os = "windows")]
pub const TARGET_OS: &str = "windows";

View file

@ -15,9 +15,11 @@ pub enum LauncherError {
#[error(transparent)]
MetadataError(#[from] crate::store::metadata::MetadataError),
#[error(transparent)]
DaoError(#[from] DaoError),
#[error(transparent)]
ClusterError(#[from] crate::api::cluster::ClusterError),
#[error(transparent)]
MinecraftAuthError(#[from] crate::store::credentials::MinecraftAuthError),
#[error(transparent)]
DaoError(#[from] DaoError),
#[error(transparent)]
SerdeError(#[from] serde_json::Error),

File diff suppressed because it is too large Load diff

View file

@ -70,6 +70,7 @@ macro_rules! dirs_impl {
dirs_impl! {
db_file => |this| this.base_dir.join("user_data.db"),
settings_file => |this| this.base_dir.join("settings.json"),
auth_file => |this| this.base_dir.join("auth.json"),
launcher_logs_dir => |this| this.base_dir.join("logs"),
clusters_dir => |this| this.base_dir.join("clusters"),

View file

@ -4,6 +4,7 @@ pub mod ingress;
pub mod proxy;
pub mod semaphore;
pub mod metadata;
pub mod credentials;
mod core_options;
mod dirs;
mod settings;

View file

@ -1,7 +1,7 @@
use onelauncher_entity::setting_profiles;
use serde::{Deserialize, Serialize};
use crate::{constants, send_warning, utils::io, LauncherResult};
use crate::{send_warning, utils::io, LauncherResult};
use super::Dirs;
@ -25,7 +25,7 @@ impl Default for Settings {
discord_enabled: false,
enable_gamemode: false,
max_concurrent_requests: 25,
settings_version: constants::CURRENT_SETTINGS_FORMAT_VERSION,
settings_version: 1,
}
}
}

View file

@ -5,6 +5,7 @@ use tokio::sync::{OnceCell, RwLock};
use crate::LauncherResult;
use super::credentials::CredentialsStore;
use super::Settings;
use super::discord::DiscordRPC;
use super::ingress::IngressProcessor;
@ -17,6 +18,7 @@ pub struct State {
pub ingress_processor: IngressProcessor,
pub settings: RwLock<Settings>,
pub db: DatabaseConnection,
pub credentials: RwLock<CredentialsStore>,
pub metadata: RwLock<Metadata>,
pub rpc: Option<DiscordRPC>,
}
@ -40,8 +42,8 @@ impl State {
let ingress_processor = IngressProcessor::new();
let settings = Settings::new().await;
let db = super::db::create_pool().await?;
let credentials = CredentialsStore::initialize().await?;
let metadata = Metadata::new();
let rpc = match DiscordRPC::initialize() {
Ok(rpc) => Some(rpc),
Err(err) => {
@ -54,6 +56,7 @@ impl State {
ingress_processor,
settings: RwLock::new(settings),
db,
credentials: RwLock::new(credentials),
metadata: RwLock::new(metadata),
rpc,
}))

View file

@ -29,7 +29,7 @@ impl FetchSemaphore {
}
}
static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
pub(crate) static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
reqwest::ClientBuilder::new()
.tcp_keepalive(Some(std::time::Duration::from_secs(15)))
.use_rustls_tls()