open auth portal
This commit is contained in:
4
src-tauri/.env.example
Normal file
4
src-tauri/.env.example
Normal 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
126
src-tauri/Cargo.lock
generated
@@ -656,6 +656,27 @@ dependencies = [
|
||||
"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]]
|
||||
name = "deranged"
|
||||
version = "0.5.5"
|
||||
@@ -774,6 +795,12 @@ dependencies = [
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dpi"
|
||||
version = "0.1.2"
|
||||
@@ -1016,11 +1043,15 @@ dependencies = [
|
||||
name = "friendolls-desktop"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"device_query",
|
||||
"once_cell",
|
||||
"dotenvy",
|
||||
"keyring",
|
||||
"rand 0.9.2",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-global-shortcut",
|
||||
@@ -1028,6 +1059,7 @@ dependencies = [
|
||||
"tauri-plugin-positioner",
|
||||
"tokio",
|
||||
"ts-rs",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1047,6 +1079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1943,6 +1976,21 @@ dependencies = [
|
||||
"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]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.8-speedreader"
|
||||
@@ -1991,6 +2039,15 @@ version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
@@ -2160,7 +2217,7 @@ dependencies = [
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework 2.11.1",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
@@ -3014,6 +3071,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
@@ -3034,6 +3101,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
@@ -3052,6 +3129,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
@@ -3166,6 +3252,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
@@ -3369,6 +3456,19 @@ dependencies = [
|
||||
"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]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.15.0"
|
||||
@@ -4542,9 +4642,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "11.0.1"
|
||||
version = "11.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be"
|
||||
checksum = "4994acea2522cd2b3b85c1d9529a55991e3ad5e25cdcd3de9d505972c4379424"
|
||||
dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"ts-rs-macros",
|
||||
@@ -4552,9 +4652,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "11.0.1"
|
||||
version = "11.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a"
|
||||
checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5683,6 +5783,20 @@ name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "zerotrie"
|
||||
|
||||
@@ -25,10 +25,15 @@ serde_json = "1"
|
||||
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
||||
tauri-plugin-global-shortcut = "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"
|
||||
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]
|
||||
tauri-plugin-global-shortcut = "2"
|
||||
|
||||
@@ -41,4 +41,5 @@ pub async fn initialize_session() {
|
||||
webview_window.open_devtools();
|
||||
|
||||
println!("Scene window initialized.");
|
||||
crate::core::services::auth::get_auth_code();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
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)]
|
||||
#[ts(export)]
|
||||
pub struct AppConfig {
|
||||
pub api_base_url: Option<String>,
|
||||
pub auth: AuthConfig,
|
||||
}
|
||||
|
||||
66
src-tauri/src/core/services/auth.rs
Normal file
66
src-tauri/src/core/services/auth.rs
Normal 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!();
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
|
||||
pub mod auth;
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
// 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 std::sync::{Arc, LazyLock, RwLock};
|
||||
use std::{
|
||||
env,
|
||||
sync::{Arc, LazyLock, RwLock},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AppState {
|
||||
@@ -10,7 +16,6 @@ pub struct AppState {
|
||||
}
|
||||
|
||||
// Global application state
|
||||
// FDOLL = Multiplayer Todo App
|
||||
// Read / write this state via the `lock_r!` / `lock_w!` macros from `fdoll-core::utilities`
|
||||
pub static FDOLL: LazyLock<Arc<RwLock<AppState>>> =
|
||||
LazyLock::new(|| Arc::new(RwLock::new(AppState::default())));
|
||||
@@ -18,9 +23,18 @@ pub static FDOLL: LazyLock<Arc<RwLock<AppState>>> =
|
||||
pub fn init_fdoll_state() {
|
||||
{
|
||||
let mut guard = lock_w!(FDOLL);
|
||||
dotenvy::dotenv().ok();
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_positioner::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![channel_cursor_positions])
|
||||
.setup(|app| {
|
||||
APP_HANDLE
|
||||
|
||||
Reference in New Issue
Block a user