at main 104 lines 3.4 kB view raw
1mod storage; 2pub use storage::AuthStore; 3 4mod state; 5pub use state::AuthState; 6 7use crate::fetch::Fetcher; 8use dioxus::prelude::*; 9#[cfg(all(feature = "fullstack-server", feature = "server"))] 10use jacquard::oauth::types::OAuthClientMetadata; 11 12/// Result of attempting to restore a session 13#[derive(Debug, Clone, Copy, PartialEq, Eq)] 14pub enum RestoreResult { 15 /// Session was successfully restored 16 Restored, 17 /// No saved session was found 18 NoSession, 19 /// Session was found but expired/invalid and has been cleared 20 SessionExpired, 21} 22 23#[cfg(all(feature = "fullstack-server", feature = "server"))] 24#[get("/oauth-client-metadata.json")] 25pub async fn client_metadata() -> Result<axum::Json<serde_json::Value>> { 26 use jacquard::oauth::atproto::atproto_client_metadata; 27 28 use crate::CONFIG; 29 30 let atproto_metadata = atproto_client_metadata(CONFIG.oauth.clone(), &None)?; 31 32 Ok(axum::response::Json(serde_json::to_value( 33 atproto_metadata, 34 )?)) 35} 36 37#[cfg(not(target_arch = "wasm32"))] 38pub async fn restore_session(_fetcher: Fetcher, _auth_state: Signal<AuthState>) -> RestoreResult { 39 RestoreResult::NoSession 40} 41 42#[cfg(target_arch = "wasm32")] 43pub async fn restore_session(fetcher: Fetcher, mut auth_state: Signal<AuthState>) -> RestoreResult { 44 use std::collections::BTreeMap; 45 46 use gloo_storage::{LocalStorage, Storage}; 47 use jacquard::oauth::authstore::ClientAuthStore; 48 use jacquard::smol_str::SmolStr; 49 use jacquard::types::string::Did; 50 51 // Skip restore if already authenticated (e.g., just completed callback flow) 52 if auth_state.read().is_authenticated() { 53 return RestoreResult::Restored; 54 } 55 56 // Look for session keys in localStorage (format: oauth_session_{did}_{session_id}) 57 let entries = match LocalStorage::get_all::<BTreeMap<SmolStr, serde_json::Value>>() { 58 Ok(e) => e, 59 Err(e) => { 60 tracing::warn!("restore_session: localStorage.get_all failed: {:?}", e); 61 return RestoreResult::NoSession; 62 } 63 }; 64 65 let mut found_session: Option<(String, String)> = None; 66 for key in entries.keys() { 67 if key.starts_with("oauth_session_") { 68 let parts: Vec<&str> = key 69 .strip_prefix("oauth_session_") 70 .unwrap() 71 .split('_') 72 .collect(); 73 if parts.len() >= 2 { 74 found_session = Some((parts[0].to_string(), parts[1..].join("_"))); 75 break; 76 } 77 } 78 } 79 80 let Some((did_str, session_id)) = found_session else { 81 return RestoreResult::NoSession; 82 }; 83 84 let Ok(did) = Did::new_owned(did_str.clone()) else { 85 tracing::warn!("restore_session: invalid DID format: {}", did_str); 86 return RestoreResult::NoSession; 87 }; 88 89 match fetcher.client.oauth_client.restore(&did, &session_id).await { 90 Ok(session) => { 91 let (restored_did, session_id) = session.session_info().await; 92 auth_state 93 .write() 94 .set_authenticated(restored_did, session_id); 95 fetcher.upgrade_to_authenticated(session).await; 96 RestoreResult::Restored 97 } 98 Err(e) => { 99 tracing::warn!("restore_session: failed, clearing dead session: {e}"); 100 let _ = AuthStore::new().delete_session(&did, &session_id).await; 101 RestoreResult::SessionExpired 102 } 103 } 104}