open auth portal

This commit is contained in:
2025-11-26 00:52:53 +08:00
parent b22fdac39c
commit 6fe397e868
9 changed files with 228 additions and 14 deletions

4
src-tauri/.env.example Normal file
View File

@@ -0,0 +1,4 @@
API_BASE_URL=http://127.0.0.1:3000
AUTH_URL=https://auth.example.com/realms/friendolls/protocol/openid-connect/auth
JWT_AUDIENCE=friendolls-desktop
REDIRECT_URI=http://localhost:8582/callback

126
src-tauri/Cargo.lock generated
View File

@@ -656,6 +656,27 @@ dependencies = [
"syn 2.0.110", "syn 2.0.110",
] ]
[[package]]
name = "dbus"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190b6255e8ab55a7b568df5a883e9497edc3e4821c06396612048b430e5ad1e9"
dependencies = [
"libc",
"libdbus-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "dbus-secret-service"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
dependencies = [
"dbus",
"zeroize",
]
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.5.5" version = "0.5.5"
@@ -774,6 +795,12 @@ dependencies = [
"syn 2.0.110", "syn 2.0.110",
] ]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]] [[package]]
name = "dpi" name = "dpi"
version = "0.1.2" version = "0.1.2"
@@ -1016,11 +1043,15 @@ dependencies = [
name = "friendolls-desktop" name = "friendolls-desktop"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64 0.22.1",
"device_query", "device_query",
"once_cell", "dotenvy",
"keyring",
"rand 0.9.2",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"sha2",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-global-shortcut", "tauri-plugin-global-shortcut",
@@ -1028,6 +1059,7 @@ dependencies = [
"tauri-plugin-positioner", "tauri-plugin-positioner",
"tokio", "tokio",
"ts-rs", "ts-rs",
"url",
] ]
[[package]] [[package]]
@@ -1047,6 +1079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@@ -1943,6 +1976,21 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]]
name = "keyring"
version = "3.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
dependencies = [
"byteorder",
"dbus-secret-service",
"log",
"security-framework 2.11.1",
"security-framework 3.5.1",
"windows-sys 0.60.2",
"zeroize",
]
[[package]] [[package]]
name = "kuchikiki" name = "kuchikiki"
version = "0.8.8-speedreader" version = "0.8.8-speedreader"
@@ -1991,6 +2039,15 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libdbus-sys"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cbe856efeb50e4681f010e9aaa2bf0a644e10139e54cde10fc83a307c23bd9f"
dependencies = [
"pkg-config",
]
[[package]] [[package]]
name = "libloading" name = "libloading"
version = "0.7.4" version = "0.7.4"
@@ -2160,7 +2217,7 @@ dependencies = [
"openssl-probe", "openssl-probe",
"openssl-sys", "openssl-sys",
"schannel", "schannel",
"security-framework", "security-framework 2.11.1",
"security-framework-sys", "security-framework-sys",
"tempfile", "tempfile",
] ]
@@ -3014,6 +3071,16 @@ dependencies = [
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.2.2" version = "0.2.2"
@@ -3034,6 +3101,16 @@ dependencies = [
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.5.1" version = "0.5.1"
@@ -3052,6 +3129,15 @@ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.16",
] ]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.4",
]
[[package]] [[package]]
name = "rand_hc" name = "rand_hc"
version = "0.2.0" version = "0.2.0"
@@ -3166,6 +3252,7 @@ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
"encoding_rs", "encoding_rs",
"futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2", "h2",
@@ -3369,6 +3456,19 @@ dependencies = [
"security-framework-sys", "security-framework-sys",
] ]
[[package]]
name = "security-framework"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
dependencies = [
"bitflags 2.10.0",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]] [[package]]
name = "security-framework-sys" name = "security-framework-sys"
version = "2.15.0" version = "2.15.0"
@@ -4542,9 +4642,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]] [[package]]
name = "ts-rs" name = "ts-rs"
version = "11.0.1" version = "11.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be" checksum = "4994acea2522cd2b3b85c1d9529a55991e3ad5e25cdcd3de9d505972c4379424"
dependencies = [ dependencies = [
"thiserror 2.0.17", "thiserror 2.0.17",
"ts-rs-macros", "ts-rs-macros",
@@ -4552,9 +4652,9 @@ dependencies = [
[[package]] [[package]]
name = "ts-rs-macros" name = "ts-rs-macros"
version = "11.0.1" version = "11.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a" checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -5683,6 +5783,20 @@ name = "zeroize"
version = "1.8.2" version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.110",
]
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"

View File

@@ -25,10 +25,15 @@ serde_json = "1"
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
tauri-plugin-global-shortcut = "2" tauri-plugin-global-shortcut = "2"
tauri-plugin-positioner = "2" tauri-plugin-positioner = "2"
reqwest = { version = "0.12.23", features = ["json", "native-tls"] } reqwest = { version = "0.12.23", features = ["json", "native-tls", "blocking"] }
ts-rs = "11.0.1" ts-rs = "11.0.1"
device_query = "4.0.1" device_query = "4.0.1"
once_cell = "1.21.3" dotenvy = "0.15.7"
keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] }
url = "2.5.7"
rand = "0.9.2"
sha2 = "0.10.9"
base64 = "0.22.1"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-global-shortcut = "2" tauri-plugin-global-shortcut = "2"

View File

@@ -41,4 +41,5 @@ pub async fn initialize_session() {
webview_window.open_devtools(); webview_window.open_devtools();
println!("Scene window initialized."); println!("Scene window initialized.");
crate::core::services::auth::get_auth_code();
} }

View File

@@ -1,8 +1,17 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
#[ts(export)]
pub struct AuthConfig {
pub audience: String,
pub auth_url: String,
pub redirect_uri: String,
}
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)] #[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
#[ts(export)] #[ts(export)]
pub struct AppConfig { pub struct AppConfig {
pub api_base_url: Option<String>, pub api_base_url: Option<String>,
pub auth: AuthConfig,
} }

View File

@@ -0,0 +1,66 @@
use crate::{core::state::FDOLL, lock_r, APP_HANDLE};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use rand::{distr::Alphanumeric, Rng};
use sha2::{Digest, Sha256};
use tauri_plugin_opener::OpenerExt;
/// Generate a random code verifier (PKCE spec: 43 to 128 chars, here defaulting to 64)
fn generate_code_verifier(length: usize) -> String {
rand::rng()
.sample_iter(&Alphanumeric)
.take(length)
.map(char::from)
.collect()
}
/// Generate code challenge from a code verifier
fn generate_code_challenge(code_verifier: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(code_verifier.as_bytes());
let result = hasher.finalize();
URL_SAFE_NO_PAD.encode(&result)
}
/// Returns the auth pass object, including
/// access token, refresh token, expire time etc.
#[allow(dead_code)]
pub fn get_tokens() {
todo!();
}
/// Opens the auth portal in the browser,
/// and returns auth code after user logged in.
pub fn get_auth_code() {
let app_config = lock_r!(FDOLL)
.app_config
.clone()
.expect("Invalid app config");
let opener = APP_HANDLE.get().unwrap().opener();
let code_verifier = generate_code_verifier(64);
let code_challenge = generate_code_challenge(&code_verifier);
let state = generate_code_verifier(16);
let mut url = url::Url::parse(&app_config.auth.auth_url.as_str()).expect("Invalid app config");
url.query_pairs_mut()
.append_pair("client_id", &app_config.auth.audience.as_str())
.append_pair("response_type", "code")
.append_pair("redirect_uri", &app_config.auth.redirect_uri.as_str())
.append_pair("scope", "openid email profile")
.append_pair("state", &state)
.append_pair("code_challenge", &code_challenge)
.append_pair("code_challenge_method", "S256");
match opener.open_url(url, None::<&str>) {
Ok(_) => (),
Err(e) => panic!("Failed to open auth portal: {}", e),
}
}
/// Accepts a refresh token and
/// returns a new access token.
#[allow(dead_code)]
pub fn refresh_token() {
todo!();
}

View File

@@ -1 +1 @@
pub mod auth;

View File

@@ -1,7 +1,13 @@
// in app-core/src/state.rs // in app-core/src/state.rs
use crate::{core::models::app_config::AppConfig, lock_w}; use crate::{
core::models::app_config::{AppConfig, AuthConfig},
lock_w,
};
use reqwest::Client; use reqwest::Client;
use std::sync::{Arc, LazyLock, RwLock}; use std::{
env,
sync::{Arc, LazyLock, RwLock},
};
#[derive(Default)] #[derive(Default)]
pub struct AppState { pub struct AppState {
@@ -10,7 +16,6 @@ pub struct AppState {
} }
// Global application state // Global application state
// FDOLL = Multiplayer Todo App
// Read / write this state via the `lock_r!` / `lock_w!` macros from `fdoll-core::utilities` // Read / write this state via the `lock_r!` / `lock_w!` macros from `fdoll-core::utilities`
pub static FDOLL: LazyLock<Arc<RwLock<AppState>>> = pub static FDOLL: LazyLock<Arc<RwLock<AppState>>> =
LazyLock::new(|| Arc::new(RwLock::new(AppState::default()))); LazyLock::new(|| Arc::new(RwLock::new(AppState::default())));
@@ -18,9 +23,18 @@ pub static FDOLL: LazyLock<Arc<RwLock<AppState>>> =
pub fn init_fdoll_state() { pub fn init_fdoll_state() {
{ {
let mut guard = lock_w!(FDOLL); let mut guard = lock_w!(FDOLL);
dotenvy::dotenv().ok();
guard.app_config = Some(AppConfig { guard.app_config = Some(AppConfig {
api_base_url: Some("http://sandbox:3000".to_string()), api_base_url: Some(env::var("API_BASE_URL").expect("API_BASE_URL must be set")),
auth: AuthConfig {
audience: env::var("JWT_AUDIENCE").expect("JWT_AUDIENCE must be set"),
auth_url: env::var("AUTH_URL").expect("AUTH_URL must be set"),
redirect_uri: env::var("REDIRECT_URI").expect("REDIRECT_URI must be set"),
},
}); });
guard.http_client = reqwest::Client::new(); guard.http_client = reqwest::ClientBuilder::new()
.redirect(reqwest::redirect::Policy::none())
.build()
.expect("Client should build");
} }
} }

View File

@@ -37,6 +37,7 @@ pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_positioner::init()) .plugin(tauri_plugin_positioner::init())
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![channel_cursor_positions]) .invoke_handler(tauri::generate_handler![channel_cursor_positions])
.setup(|app| { .setup(|app| {
APP_HANDLE APP_HANDLE