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"
|
name = "Launcher-tauri"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"log4rs",
|
"log4rs",
|
||||||
|
"rand 0.8.5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -20,9 +20,11 @@ tauri = {version = "1.2", features = ["api-all"] }
|
|||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
uuid = "1.2.2"
|
uuid = "1.2.2"
|
||||||
log4rs = "1.2.0"
|
log4rs = "1.2.0"
|
||||||
reqwest = "0.11.13"
|
reqwest = { version = "0.11.13", default-features = true, features = ["json"] }
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
warp = "0.3.3"
|
warp = "0.3.3"
|
||||||
|
anyhow = "1.0.66"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# 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 rand::{thread_rng, Rng};
|
||||||
use reqwest::header::{CONTENT_TYPE, CONNECTION};
|
use reqwest::{header::{CONTENT_TYPE, CONNECTION, ACCEPT}, Client};
|
||||||
|
use serde_json::{Value, json};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use urlencoding::encode;
|
use urlencoding::encode;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use warp::{Filter, http::Response};
|
use warp::{Filter, http::Response};
|
||||||
|
use anyhow::{bail, Result, anyhow};
|
||||||
|
|
||||||
pub enum Prompt {
|
pub enum Prompt {
|
||||||
Login,
|
Login,
|
||||||
@ -25,49 +27,51 @@ impl fmt::Display for Prompt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Token {
|
pub struct OauthToken {
|
||||||
client_id: String,
|
client_id: String,
|
||||||
redirect: 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)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
struct ReceivedCode {
|
pub struct ReceivedCode {
|
||||||
pub code: String,
|
pub code: String,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Authentification {
|
pub struct Authentification;
|
||||||
logger: Handle
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Authentification {
|
impl Authentification {
|
||||||
|
|
||||||
pub fn new(logger: Handle) -> Self {
|
fn mojang_auth_token(prompt: Prompt, port: u16) -> OauthToken {
|
||||||
Authentification { logger }
|
OauthToken {
|
||||||
}
|
client_id: String::from("89db80b6-8a97-4d00-97e8-48b18f377871"),
|
||||||
|
redirect: String::from(format!("http://localhost:{}/api/auth/redirect", port)),
|
||||||
pub fn mojang_auth_token(prompt: Prompt) -> Token {
|
prompt: Arc::new(prompt)
|
||||||
Token {
|
|
||||||
client_id: String::from("00000000402b5328"),
|
|
||||||
redirect: String::from("https://localhost:PORT/api/auth/redirect"),
|
|
||||||
prompt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_link(token: Token)-> String {
|
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)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_link_from_prompt(prompt: Prompt) -> String {
|
async fn fetch_oauth2_token(prompt: Prompt, app: tauri::AppHandle) -> Result<(ReceivedCode, OauthToken)> {
|
||||||
Self::create_link(Self::mojang_auth_token(prompt))
|
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_holder = None;
|
||||||
let mut port = 0;
|
let mut port = 0;
|
||||||
for i in 7878..65535 {
|
for i in 7878..65535 {
|
||||||
@ -78,10 +82,10 @@ impl Authentification {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if port_holder.is_none() {
|
if port_holder.is_none() {
|
||||||
Err(())
|
bail!("Cannot create port")
|
||||||
}
|
}
|
||||||
let redirect_uri = token.redirect.replace("PORT", &port.to_string());
|
let token_data = Self::mojang_auth_token(prompt, port);
|
||||||
let link = Self::create_link_from_prompt(token.prompt);
|
let link = Self::create_link(&token_data, &state);
|
||||||
|
|
||||||
let second_window = tauri::WindowBuilder::new(
|
let second_window = tauri::WindowBuilder::new(
|
||||||
&app,
|
&app,
|
||||||
@ -89,13 +93,91 @@ impl Authentification {
|
|||||||
tauri::WindowUrl::External(link.parse().unwrap())
|
tauri::WindowUrl::External(link.parse().unwrap())
|
||||||
).build().expect("Failed to build window");
|
).build().expect("Failed to build window");
|
||||||
let received = Self::listen(port_holder.unwrap()).await?;
|
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(())
|
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 (tx, mut rx) = mpsc::channel::<ReceivedCode>(2);
|
||||||
|
|
||||||
let route = warp::query::<ReceivedCode>()
|
let route = warp::query::<ReceivedCode>()
|
||||||
.and(warp::header::<String>("accept-language"))
|
.and(warp::header::<String>("accept-language"))
|
||||||
.and_then(move |r: ReceivedCode, accept_lang: String| {
|
.and_then(move |r: ReceivedCode, accept_lang: String| {
|
||||||
@ -108,11 +190,11 @@ impl Authentification {
|
|||||||
if !accept_lang.is_empty() {
|
if !accept_lang.is_empty() {
|
||||||
let langs = accept_lang.split(",");
|
let langs = accept_lang.split(",");
|
||||||
for lang in langs {
|
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!";
|
message = "Vous pouvez maintenant fermer l'onglet!";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if lang.starts_with("en") {
|
else {
|
||||||
message = "You can close this tab now!";
|
message = "You can close this tab now!";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -127,49 +209,23 @@ impl Authentification {
|
|||||||
.header(CONNECTION, "close")
|
.header(CONNECTION, "close")
|
||||||
.body(format!("<h1>{}</h1>", message))
|
.body(format!("<h1>{}</h1>", message))
|
||||||
)
|
)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Err(warp::reject())
|
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;
|
pub mod authentification;
|
||||||
|
|
||||||
use authentification::{Authentification, Prompt};
|
use authentification::{Authentification, Prompt};
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -14,9 +15,12 @@ fn greet(name: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn second_window(app: tauri::AppHandle, window: tauri::Window) -> Result<(), String> {
|
async fn second_window(app: tauri::AppHandle, _window: tauri::Window) -> Result<(), String> {
|
||||||
Authentification::launch(Prompt::SelectAccount, app);
|
let result = Authentification::launch(Prompt::SelectAccount, app).await;
|
||||||
Ok(())
|
match result {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) => Err(err.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
Reference in New Issue
Block a user