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

View File

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

View File

@@ -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,
}

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
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");
}
}

View File

@@ -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