🔨 chore: explain mc auth flow

This commit is contained in:
Pauline 2024-07-28 20:39:33 +02:00
parent 4539cb5388
commit a73f4b0397
No known key found for this signature in database
GPG key ID: 722BB181973296AE
8 changed files with 27 additions and 135 deletions

View file

@ -26,8 +26,8 @@ All rights reserved. Copyright (C) 2022~2024 Polyfrost
This includes, but may not be limited to, the following files:
* [`apps/desktop/.icons/*`](./apps/desktop/.icons/)
* [`apps/desktop/src/assets/licensed/*`](./apps/desktop/src/assets/licensed/)
* [`packages/distribution/icons/*`](./packages/distribution/icons/)
* [`apps/frontend/src/assets/licensed/*`](./apps/frontend/src/assets/licensed/)
## Additional Libraries

View file

@ -6,7 +6,8 @@ fn main() {
"onelauncher",
tauri_build::InlinedPlugin::new().commands(&[
// User
"msa_auth",
"begin_msa",
"finish_msa",
"get_users",
"get_user",
"remove_user",

View file

@ -2,7 +2,8 @@
description = "Default permissions for the plugin"
permissions = [
# User
"allow-msa-auth",
"allow-begin-msa",
"allow-finish-msa",
"allow-get-users",
"allow-get-user",
"allow-remove-user",

View file

@ -4,12 +4,10 @@ use interpulse::api::minecraft::Version;
use onelauncher::constants::{NATIVE_ARCH, TARGET_OS, VERSION};
use onelauncher::data::{Loader, ManagedPackage, MinecraftCredentials, PackageData, Settings};
use onelauncher::package::content;
use onelauncher::store::{Cluster, ClusterPath};
use onelauncher::store::{Cluster, ClusterPath, MinecraftLogin};
use onelauncher::{cluster, minecraft, processor, settings};
use serde::{Deserialize, Serialize};
use specta::Type;
use tauri::AppHandle;
use tauri_plugin_shell::ShellExt;
use uuid::Uuid;
#[macro_export]
@ -23,7 +21,8 @@ macro_rules! collect_commands {
)
.commands(tauri_specta::collect_commands![
// User
msa_auth,
begin_msa,
finish_msa,
get_users,
get_user,
remove_user,
@ -215,13 +214,14 @@ pub async fn get_user(uuid: Uuid) -> Result<MinecraftCredentials, String> {
#[specta::specta]
#[tauri::command]
pub async fn msa_auth(handle: AppHandle) -> Result<MinecraftCredentials, String> {
let login = minecraft::begin().await?;
handle.shell().open(login.redirect_uri.clone(), None).map_err(|err| err.to_string())?;
pub async fn begin_msa() -> Result<MinecraftLogin, String> {
Ok(minecraft::begin().await?)
}
Err("not implemented".to_string())
// Ok(minecraft::finish(code.as_str(), login).await?)
#[specta::specta]
#[tauri::command]
pub async fn finish_msa(code: String, login: MinecraftLogin) -> Result<MinecraftCredentials, String> {
Ok(minecraft::finish(code.as_str(), login).await?)
}
#[specta::specta]

View file

@ -17,13 +17,21 @@ async fn main() -> onelauncher::Result<()> {
Ok(())
}
/// pauline's epic gamer minecraft auth flow
/// opens a browser tab in which the user sees xbox login and logs in with microsoft and gets redirected
/// to a new url. this new url contains the ?code=.... parameter that we need to finish authentication.
/// in this testing environment we can just copy the final url from our browser and into the testing console
/// and it will parse the url and find the code and complete the authenticaiton process.
/// in production/frontend, it works the same way but we get the url and code parameter automatically.
pub async fn authenticate_mc() -> onelauncher::Result<MinecraftCredentials> {
// begins login flow, opens browser
println!("a browser will open, follow login flow");
let login = minecraft::begin().await?;
println!("url is {}", login.redirect_uri.as_str());
webbrowser::open(login.redirect_uri.as_str())?;
// after user is done with auth, paste the final code into console below this line
println!("enter flow url: ");
let mut input = String::new();
std::io::stdin()
@ -32,6 +40,7 @@ pub async fn authenticate_mc() -> onelauncher::Result<MinecraftCredentials> {
println!("{}", input.trim());
// parses the url code query param using the same logic we should use in frontend
let parsed = url::Url::parse(input.trim()).expect("idk");
let code = if let Some((_, code)) = parsed.query_pairs().find(|x| x.0 == "code") {
let code = code.clone();
@ -41,6 +50,7 @@ pub async fn authenticate_mc() -> onelauncher::Result<MinecraftCredentials> {
};
let creds = minecraft::finish(code.as_str(), login).await?;
// mc auth flow is complete and added to state
println!("logged in {}", creds.username);
Ok(creds)
}

View file

@ -23,8 +23,6 @@ pub const DUMMY_REPLACE_NEWLINE: &str = "\n";
pub const DISCORD_RPC_CLIENT_ID: &str = "1234567890000000"; // todo
/// Our Microsoft client ID.
pub const MICROSOFT_CLIENT_ID: &str = "9eac3a4e-8cdd-43ef-863e-49cd601b1f03";
/// The port to run the localhost server on.
pub const MSA_SERVER_PORT: u16 = 13523;
/// Mojang/Microsoft client ID.
pub const MINECRAFT_CLIENT_ID: &str = "00000000402b5328";
/// Mojang/Microsoft login redirect URI.

View file

@ -1,33 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OneLauncher</title>
<style>
* {
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Ubuntu, Cantarell, sans-serif;
}
body {
opacity: 0;
animation: fadeIn 1s forwards 0.3s;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 80vh;
font-size: 24px;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
</style>
</head>
<body>
<h1>%TITLE%</h1>
<p>%CONTENT%</p>
</body>
</html>

View file

@ -310,89 +310,6 @@ impl MinecraftState {
}
}
// pub struct LocalServer {
// listener: TcpListener,
// sender: tokio::sync::mpsc::Sender<bool>,
// receiver: tokio::sync::mpsc::Receiver<bool>,
// }
// impl LocalServer {
// pub async fn new() -> crate::Result<Self> {
// let listener = TcpListener::bind(format!("127.0.0.1:{}", constants::MSA_SERVER_PORT)).await?;
// let (sender, receiver) = tokio::sync::mpsc::channel::<bool>(1);
// Ok(Self {
// listener,
// sender,
// receiver,
// })
// }
// pub async fn wait_for_code(&self) -> crate::Result<String> {
// }
// async fn handle_request(&self, stream: &mut TcpStream) -> crate::Result<String> {
// use tokio::io::{AsyncReadExt, AsyncWriteExt};
// let code: Option<String>;
// let code_result = {
// let mut buf = vec![0; 1024];
// let size = stream.read(&mut buf).await?;
// if size == 0 {
// return Err(MinecraftAuthError::MSACodeFetchError.into());
// }
// let request = String::from_utf8_lossy(&buf[..size]);
// code = Some(
// request
// .split(' ')
// .find(|x| x.starts_with("code="))
// .ok_or(anyhow::anyhow!("No code found"))?
// .split('=')
// .nth(1)
// .ok_or(anyhow::anyhow!("No code found"))?
// .to_string()
// );
// Ok::<(), String>(())
// };
// let errored = code_result.is_err();
// let html = include_str!("./auth_server.html");
// let result = match code_result {
// Ok(()) => {
// html
// .replace("%TITLE%", "Successfully Authenticated")
// .replace("%CONTENT%", "You can now close this tab.")
// }
// Err(e) => {
// html
// .replace("%TITLE%", "Error")
// .replace("%CONTENT%", &format!("An error has occurred: {}", e.as_str()))
// }
// };
// let response = format!(
// "HTTP/1.1 {}\r\nContent-Length: {}\r\n\r\n{}",
// if errored { "400 Bad Request" } else { "200 OK" },
// result.len(),
// result
// );
// stream.write_all(response.as_bytes()).await?;
// if errored {
// return Err(MinecraftAuthError::MSACodeFetchError.into());
// }
// match code {
// Some(code) => Ok(code),
// None => Err(MinecraftAuthError::MSACodeFetchError.into()),
// }
// }
// }
/// A [`reqwest::Request`] with a [`DateTime<Utc>`] attached to it.
struct RequestWithDate<T> {
pub date: DateTime<Utc>,
@ -1117,7 +1034,5 @@ pub enum MinecraftAuthError {
#[error("failed to read user hash")]
HashError,
#[error("failed to read user xbox session ID")]
SessionIdError,
#[error("failed to read user MSA code")]
MSACodeFetchError,
SessionIdError
}