Completely rework backend, now work to authentificate user, still need a bit of work to got MC Game Profile
This commit is contained in:
parent
dd89761fcc
commit
9e518db87c
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@ -6,7 +6,9 @@ version = 3
|
||||
name = "Launcher-tauri"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log4rs",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -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
|
||||
|
@ -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 struct ReceivedCode {
|
||||
pub code: String,
|
||||
pub state: String,
|
||||
}
|
||||
|
||||
pub struct Authentification {
|
||||
logger: Handle
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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]
|
||||
|
Reference in New Issue
Block a user