Completely rework backend, now work to authentificate user, still need a bit of work to got MC Game Profile

This commit is contained in:
Quentin Legot 2023-01-08 19:33:11 +01:00
parent dd89761fcc
commit 9e518db87c
4 changed files with 144 additions and 80 deletions

2
src-tauri/Cargo.lock generated
View File

@ -6,7 +6,9 @@ version = 3
name = "Launcher-tauri"
version = "0.0.0"
dependencies = [
"anyhow",
"log4rs",
"rand 0.8.5",
"reqwest",
"serde",
"serde_json",

View File

@ -20,9 +20,11 @@ tauri = {version = "1.2", features = ["api-all"] }
tokio = { version = "1", features = ["full"] }
uuid = "1.2.2"
log4rs = "1.2.0"
reqwest = "0.11.13"
reqwest = { version = "0.11.13", default-features = true, features = ["json"] }
urlencoding = "2.1.2"
warp = "0.3.3"
anyhow = "1.0.66"
rand = "0.8.5"
[features]
# by default Tauri runs in production mode

View File

@ -1,11 +1,13 @@
use std::{fmt, net::TcpListener, io::Result};
use std::{fmt, net::TcpListener, sync::Arc};
use log4rs::Handle;
use reqwest::header::{CONTENT_TYPE, CONNECTION};
use rand::{thread_rng, Rng};
use reqwest::{header::{CONTENT_TYPE, CONNECTION, ACCEPT}, Client};
use serde_json::{Value, json};
use tokio::sync::mpsc;
use urlencoding::encode;
use serde::Deserialize;
use warp::{Filter, http::Response};
use anyhow::{bail, Result, anyhow};
pub enum Prompt {
Login,
@ -25,49 +27,51 @@ impl fmt::Display for Prompt {
}
}
pub struct Token {
pub struct OauthToken {
client_id: String,
redirect: String,
prompt: Prompt
prompt: Arc<Prompt>
}
struct AccessRefreshToken {
access_token: String,
refresh_token: String
}
struct XboxAuthData {
token: String,
uhs: String
}
#[derive(Deserialize, Clone, Debug)]
struct ReceivedCode {
pub code: String,
pub state: String,
}
pub struct Authentification {
logger: Handle
pub struct ReceivedCode {
pub code: String,
pub state: String,
}
pub struct Authentification;
impl Authentification {
pub fn new(logger: Handle) -> Self {
Authentification { logger }
}
pub fn mojang_auth_token(prompt: Prompt) -> Token {
Token {
client_id: String::from("00000000402b5328"),
redirect: String::from("https://localhost:PORT/api/auth/redirect"),
prompt
fn mojang_auth_token(prompt: Prompt, port: u16) -> OauthToken {
OauthToken {
client_id: String::from("89db80b6-8a97-4d00-97e8-48b18f377871"),
redirect: String::from(format!("http://localhost:{}/api/auth/redirect", port)),
prompt: Arc::new(prompt)
}
}
pub fn create_link(token: Token)-> String {
format!("https://login.live.com/oauth20_authorize.srf?client_id={}&response_type=code&redirect_uri={}&scope=XboxLive.signin%20offline_access&prompt={}", token.client_id, encode(token.redirect.as_str()), token.prompt)
fn create_link(token: &OauthToken, state: &String)-> String {
format!("https://login.live.com/oauth20_authorize.srf?client_id={}&response_type=code&redirect_uri={}&scope=Xboxlive.signin+Xboxlive.offline_access&prompt={}&state={}", token.client_id, encode(token.redirect.as_str()), token.prompt, state)
}
pub fn create_link_from_prompt(prompt: Prompt) -> String {
Self::create_link(Self::mojang_auth_token(prompt))
}
async fn fetch_oauth2_token(prompt: Prompt, app: tauri::AppHandle) -> Result<(ReceivedCode, OauthToken)> {
let state: String = thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(16)
.map(char::from)
.collect();
pub async fn launch(prompt: Prompt, app: tauri::AppHandle) -> Result<()> {
// let reqwest_client = ReqwestClient::new();
let token = Self::mojang_auth_token(prompt);
let reqwest_client = reqwest::Client::new();
let mut port_holder = None;
let mut port = 0;
for i in 7878..65535 {
@ -78,10 +82,10 @@ impl Authentification {
}
};
if port_holder.is_none() {
Err(())
bail!("Cannot create port")
}
let redirect_uri = token.redirect.replace("PORT", &port.to_string());
let link = Self::create_link_from_prompt(token.prompt);
let token_data = Self::mojang_auth_token(prompt, port);
let link = Self::create_link(&token_data, &state);
let second_window = tauri::WindowBuilder::new(
&app,
@ -89,13 +93,91 @@ impl Authentification {
tauri::WindowUrl::External(link.parse().unwrap())
).build().expect("Failed to build window");
let received = Self::listen(port_holder.unwrap()).await?;
second_window.close()?;
if received.state != state {
bail!("CSRF check fail")
}
Ok((received, token_data))
}
// fn create_link_from_prompt(prompt: Prompt) -> String {
// Self::create_link(&Self::mojang_auth_token(prompt))
// }
async fn fetch_token(oauth_token: ReceivedCode, token_data: OauthToken, reqwest_client: &Client) -> Result<AccessRefreshToken> {
let request_body = format!("\
client_id={}\
&code={}\
&grant_type=authorization_code\
&redirect_uri={}", token_data.client_id, oauth_token.code, token_data.redirect);
let received : Value = reqwest_client
.post("https://login.live.com/oauth20_token.srf")
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.body(request_body.into_bytes())
.send()
.await?
.json()
.await?;
let token = AccessRefreshToken {
access_token: received["access_token"].to_string(),
refresh_token: received["refresh_token"].to_string()
};
Ok(token)
}
async fn auth_xbox_live(access_refresh_token: AccessRefreshToken, reqwest_client: &Client) -> Result<XboxAuthData> {
let request_body: Value = json!({
"Properties": {
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": format!("d={}", access_refresh_token.access_token)
},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT"
});
let received: Value = reqwest_client
.post("https://user.auth.xboxlive.com/user/authenticate")
.header(CONTENT_TYPE, "application/json")
.header(ACCEPT, "application/json")
.json(&request_body)
.send()
.await?
.json()
.await?;
let xbox_auth = XboxAuthData {
token: received["Token"].to_string(),
uhs: received["DisplayClaims"]["xui"][0]["uhs"].to_string()
};
Ok(xbox_auth)
}
async fn fetch_xsts_token() -> Result<()> {
bail!("Not implemented yet")
}
pub async fn launch(prompt: Prompt, app: tauri::AppHandle) -> Result<()> {
let reqwest_client = Client::new();
let oauth_token = Self::fetch_oauth2_token(prompt, app).await?;
let access_refresh_token = Self::fetch_token(oauth_token.0, oauth_token.1, &reqwest_client).await?;
let xbox_auth = Self::auth_xbox_live(access_refresh_token, &reqwest_client).await?;
Ok(())
}
pub async fn listen(port_holder: TcpListener) -> Result<ReceivedCode> {
async fn listen(port_holder: TcpListener) -> Result<ReceivedCode> {
let (tx, mut rx) = mpsc::channel::<ReceivedCode>(2);
let route = warp::query::<ReceivedCode>()
.and(warp::header::<String>("accept-language"))
.and_then(move |r: ReceivedCode, accept_lang: String| {
@ -108,11 +190,11 @@ impl Authentification {
if !accept_lang.is_empty() {
let langs = accept_lang.split(",");
for lang in langs {
if lang.starts_with("fr_FR") {
if lang.starts_with("fr_") { // also include canadian, belgium, etc. french
message = "Vous pouvez maintenant fermer l'onglet!";
break;
}
else if lang.starts_with("en") {
else {
message = "You can close this tab now!";
break;
}
@ -127,49 +209,23 @@ impl Authentification {
.header(CONNECTION, "close")
.body(format!("<h1>{}</h1>", message))
)
}
else {
} else {
Err(warp::reject())
}
}
});
Ok(ReceivedCode { code: "".to_string(), state: "".to_string() })
let port = port_holder.local_addr()?.port();
drop(port_holder);
let server = warp::serve(route).bind(([127, 0, 0, 1], port));
tokio::select! {
_ = server => bail!("Serve went down unexpectedly!"),
r = rx.recv() => r.ok_or(anyhow!("Can't receive code!")),
_ = async {
tokio::time::sleep(tokio::time::Duration::from_secs(120)).await;
} => bail!("Wait for too much time"),
}
}
pub async fn xbox_auth(&self, access_token: String) -> () {
let str = format!(r#"{{
"Properties": {{
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": "d={}"
}},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT"
}}
"#, access_token);
let req = reqwest::Client::new();
let r_xbox_live = req.post("https://user.auth.xboxlive.com/user/authenticate")
.body(str)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.header(reqwest::header::ACCEPT, "application/json")
.send().await;
match r_xbox_live {
Ok(response) => {
let content = response.text().await;
match content {
Ok(text) => {
println!("Sucess: {:?}", text);
},
Err(err) => {
eprintln!("error 2: {:?}", err);
}
};
},
Err(err) => {
eprintln!("Error 1: {:?}", err);
}
};
}
}

View File

@ -6,6 +6,7 @@
pub mod authentification;
use authentification::{Authentification, Prompt};
use anyhow::Result;
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
@ -14,9 +15,12 @@ fn greet(name: &str) -> String {
}
#[tauri::command]
async fn second_window(app: tauri::AppHandle, window: tauri::Window) -> Result<(), String> {
Authentification::launch(Prompt::SelectAccount, app);
Ok(())
async fn second_window(app: tauri::AppHandle, _window: tauri::Window) -> Result<(), String> {
let result = Authentification::launch(Prompt::SelectAccount, app).await;
match result {
Ok(_) => Ok(()),
Err(err) => Err(err.to_string())
}
}
#[tokio::main]