1use crate::{
6 idle_manager::{IdleManager, IdleManagerOptions},
7 storage::{
8 AuthClientStorage, AuthClientStorageType, KEY_STORAGE_DELEGATION, KEY_STORAGE_KEY, KEY_VECTOR,
9 },
10 util::{delegation_chain::DelegationChain, sleep::sleep},
11};
12use ed25519_consensus::SigningKey;
13use gloo_events::EventListener;
14use gloo_utils::{format::JsValueSerdeExt, window};
15use ic_agent::{
16 export::Principal,
17 identity::{AnonymousIdentity, BasicIdentity, DelegatedIdentity, SignedDelegation, DelegationError},
18 Identity,
19};
20use rand::thread_rng;
21use serde::{Deserialize, Serialize};
22use serde_wasm_bindgen::from_value;
23use std::{
24 collections::HashMap,
25 fmt,
26 sync::{Arc, Mutex},
27 cell::RefCell,
28 sync::atomic::{AtomicUsize, Ordering, AtomicBool},
29};
30use storage::StoredKey;
31#[cfg(target_family = "wasm")]
32use wasm_bindgen_futures::spawn_local;
33#[cfg(not(target_family = "wasm"))]
34use tokio::task::spawn_local;
35use web_sys::{Location, MessageEvent, wasm_bindgen::{JsCast, JsValue}};
36
37pub mod idle_manager;
38pub mod storage;
39mod util;
40
41pub use util::delegation_chain;
42
43thread_local! {
44 static EVENT_HANDLERS: RefCell<HashMap<usize, EventListener>> = RefCell::new(HashMap::new());
45 static IDP_WINDOWS: RefCell<HashMap<usize, web_sys::Window>> = RefCell::new(HashMap::new());
46}
47
48type OnSuccess = Arc<Mutex<Box<dyn FnMut(AuthResponseSuccess) + Send>>>;
49type OnError = Arc<Mutex<Box<dyn FnMut(Option<String>) + Send>>>;
50
51const IDENTITY_PROVIDER_DEFAULT: &str = "https://identity.ic0.app";
52const IDENTITY_PROVIDER_ENDPOINT: &str = "#authorize";
53
54const ED25519_KEY_LABEL: &str = "Ed25519";
55
56const INTERRUPT_CHECK_INTERVAL: u64 = 500;
57pub const ERROR_USER_INTERRUPT: &str = "UserInterrupt";
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
65#[serde(rename_all = "camelCase")]
66struct InternetIdentityAuthRequest {
67 pub kind: String,
69 pub session_public_key: Vec<u8>,
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub max_time_to_live: Option<u64>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub allow_pin_authentication: Option<bool>,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub derivation_origin: Option<String>,
80}
81
82
83#[derive(Debug, Clone)]
88pub struct AuthResponseSuccess {
89 pub delegations: Vec<SignedDelegation>,
91 pub user_public_key: Vec<u8>,
93 pub authn_method: String,
95}
96
97#[derive(Debug, Clone, Deserialize)]
103#[serde(rename_all = "camelCase")]
104struct IdentityServiceResponseMessage {
105 kind: String,
107
108 delegations: Option<Vec<SignedDelegation>>,
110
111 user_public_key: Option<Vec<u8>>,
113
114 authn_method: Option<String>,
116
117 text: Option<String>,
119}
120
121impl IdentityServiceResponseMessage {
122 pub fn kind(&self) -> Result<IdentityServiceResponseKind, String> {
124 match self.kind.as_str() {
125 "authorize-ready" => Ok(IdentityServiceResponseKind::Ready),
126 "authorize-client-success" => Ok(IdentityServiceResponseKind::AuthSuccess(
127 AuthResponseSuccess {
128 delegations: self.delegations.clone().unwrap_or_default(),
129 user_public_key: self.user_public_key.clone().unwrap_or_default(),
130 authn_method: self.authn_method.clone().unwrap_or_default(),
131 }
132 )),
133 "authorize-client-failure" => Ok(IdentityServiceResponseKind::AuthFailure(
134 self.text.clone().unwrap_or_default()
135 )),
136 other => Err(format!("Unknown response kind: {}", other)),
137 }
138 }
139}
140
141#[derive(Debug, Clone)]
143enum IdentityServiceResponseKind {
144 Ready,
146 AuthSuccess(AuthResponseSuccess),
148 AuthFailure(String),
150}
151
152#[derive(Clone, Debug)]
155pub struct AuthClient {
156 identity: ArcIdentity,
158 key: ArcIdentity,
160 storage: AuthClientStorageType,
162 chain: Arc<Mutex<Option<DelegationChain>>>,
164 pub idle_manager: Option<IdleManager>,
166 idle_options: Option<IdleOptions>,
168 id: Arc<usize>,
171 login_complete: Arc<AtomicBool>,
173}
174
175impl Drop for AuthClient {
176 fn drop(&mut self) {
177 if Arc::strong_count(&self.id) == 1 {
179 let id_val = *self.id; EVENT_HANDLERS.with(|cell| {
183 match cell.try_borrow_mut() {
184 Ok(mut map) => {
185 map.remove(&id_val);
186 }
187 Err(_) => {
188 eprintln!("AuthClient::drop: Could not remove event handler for id {} (already borrowed)", id_val);
189 }
190 }
191 });
192
193 IDP_WINDOWS.with(|cell| {
194 match cell.try_borrow_mut() {
195 Ok(mut map) => {
196 if let Some(window) = map.remove(&id_val) {
198 let _ = window.close();
200 }
201 }
202 Err(_) => {
203 eprintln!("AuthClient::drop: Could not remove IDP window for id {} (already borrowed)", id_val);
204 }
205 }
206 });
207 }
208 }
209}
210
211impl AuthClient {
212 fn set_event_handler(&self, handler: EventListener) {
214 EVENT_HANDLERS.with(|cell| {
215 let mut map = cell.borrow_mut();
216 map.insert(*self.id, handler);
217 });
218 }
219
220 fn take_event_handler(&self) -> Option<EventListener> {
222 EVENT_HANDLERS.with(|cell| {
223 let mut map = cell.borrow_mut();
224 map.remove(&self.id)
225 })
226 }
227
228 fn set_idp_window(&self, window: web_sys::Window) {
230 IDP_WINDOWS.with(|cell| {
231 let mut map = cell.borrow_mut();
232 map.insert(*self.id, window);
233 });
234 }
235
236 fn get_idp_window(&self) -> Option<web_sys::Window> {
238 IDP_WINDOWS.with(|cell| {
239 let map = cell.borrow();
240 map.get(&self.id).cloned()
241 })
242 }
243
244 fn take_idp_window(&self) -> Option<web_sys::Window> {
246 IDP_WINDOWS.with(|cell| {
247 let mut map = cell.borrow_mut();
248 map.remove(&self.id)
249 })
250 }
251
252 const DEFAULT_TIME_TO_LIVE: u64 = 8 * 60 * 60 * 1_000_000_000;
254
255 pub fn builder() -> AuthClientBuilder {
257 AuthClientBuilder::new()
258 }
259
260 pub async fn new() -> Result<Self, DelegationError> {
262 Self::new_with_options(AuthClientCreateOptions::default()).await
263 }
264
265 pub async fn new_with_options(options: AuthClientCreateOptions) -> Result<Self, DelegationError> {
267 let mut storage = options.storage.unwrap_or_default();
268
269 let mut key: Option<ArcIdentity> = None;
270
271 if let Some(identity) = options.identity.clone() {
272 key = Some(identity);
273 } else {
274 let maybe_local_storage = storage.get(KEY_STORAGE_KEY).await;
275
276 if let Some(maybe_local_storage) = maybe_local_storage {
277 let private_key = maybe_local_storage.decode();
278
279 match private_key {
280 Ok(private_key) => {
281 key = Some(
282 ArcIdentity::Ed25519(Arc::new(
283 BasicIdentity::from_signing_key(private_key),
284 ))
285 );
286 }
287 Err(e) => {
288 return Err(
289 DelegationError::IdentityError(format!(
290 "Failed to decode private key: {}",
291 e
292 ))
293 );
294 }
295 }
296 }
297 }
298
299 let mut identity = ArcIdentity::Anonymous(Arc::new(AnonymousIdentity));
300 let mut chain: Arc<Mutex<Option<DelegationChain>>> = Arc::new(Mutex::new(None));
301
302 let options_identity_is_some = options.identity.is_some();
303
304 if key.is_none() {
306 let private_key = SigningKey::new(thread_rng());
308
309 storage
311 .set(KEY_STORAGE_KEY, StoredKey::encode(&private_key))
312 .await;
313
314 key = Some(ArcIdentity::Ed25519(Arc::new(
315 BasicIdentity::from_signing_key(private_key),
316 )));
317 }
318
319 let chain_storage = storage.get(KEY_STORAGE_DELEGATION).await;
321
322 if let Some(op_identity) = options.identity {
323 identity = op_identity;
324 } else if let Some(chain_storage) = chain_storage {
325 match chain_storage {
326 StoredKey::String(chain_storage) => {
327 let chain_result = DelegationChain::from_json(&chain_storage);
329 chain = Arc::new(Mutex::new(Some(chain_result)));
330
331 let delegation_data = {
333 if let Ok(guard) = chain.lock() {
334 if let Some(chain_inner) = guard.as_ref() {
335 if chain_inner.is_delegation_valid(None) {
336 let public_key = chain_inner.public_key.clone();
338 let delegations = chain_inner.delegations.clone();
339 Some((public_key, delegations))
340 } else {
341 None
343 }
344 } else {
345 Some((Vec::new(), Vec::new()))
347 }
348 } else {
349 Some((Vec::new(), Vec::new()))
351 }
352 };
353
354 match delegation_data {
356 Some((public_key, delegations)) => {
357 if !public_key.is_empty() {
358 identity = ArcIdentity::Delegated(Arc::new(DelegatedIdentity::new_unchecked(
360 public_key,
361 Box::new(key.clone().unwrap().as_arc_identity()),
362 delegations,
363 )));
364 }
365 }
366 None => {
367 eprintln!("Found invalid delegation chain in storage - clearing credentials");
369 Self::delete_storage(&mut storage).await;
370
371 identity = ArcIdentity::Anonymous(Arc::new(AnonymousIdentity));
373 chain = Arc::new(Mutex::new(None));
374 }
375 }
376 }
377 }
378 }
379
380 let mut idle_manager: Option<IdleManager> = None;
381 if !options
382 .idle_options
383 .as_ref()
384 .and_then(|o| o.disable_idle)
385 .unwrap_or(false)
386 && (chain.lock().unwrap().is_some() || options_identity_is_some)
387 {
388 let idle_manager_options: Option<IdleManagerOptions> = options
389 .idle_options
390 .as_ref()
391 .map(|o| o.idle_manager_options.clone());
392 idle_manager = Some(IdleManager::new(idle_manager_options));
393 }
394
395 if key.is_none() {
396 let private_key = SigningKey::new(thread_rng());
397
398 storage
399 .set(KEY_STORAGE_KEY, StoredKey::encode(&private_key))
400 .await;
401
402 key = Some(ArcIdentity::Ed25519(Arc::new(
403 BasicIdentity::from_signing_key(private_key),
404 )));
405 }
406
407 let id = {
409 static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
410 Arc::new(NEXT_ID.fetch_add(1, Ordering::Relaxed))
412 };
413
414 Ok(
415 Self {
416 identity,
417 key: key.unwrap(),
418 storage,
419 chain,
420 idle_manager,
421 idle_options: options.idle_options,
422 id,
423 login_complete: Arc::new(AtomicBool::new(false)),
424 }
425 )
426 }
427
428 fn register_default_idle_callback(
430 identity: ArcIdentity,
431 storage: AuthClientStorageType,
432 chain: Arc<Mutex<Option<DelegationChain>>>,
433 idle_manager: Option<IdleManager>,
434 idle_options: Option<IdleOptions>,
435 ) {
436 if let Some(options) = idle_options.as_ref() {
437 if options.disable_default_idle_callback.unwrap_or_default() {
438 return;
439 }
440
441 if options
442 .idle_manager_options
443 .on_idle
444 .as_ref()
445 .lock()
446 .unwrap()
447 .is_empty()
448 {
449 if let Some(idle_manager) = idle_manager.as_ref() {
450 let identity = identity.clone();
451 let storage = storage.clone();
452 let chain = chain.clone();
453 let callback = move || {
454 let mut identity = identity.clone();
455 let storage = storage.clone();
456 let chain = chain.clone();
457 spawn_local(async move {
458 Self::logout_core(&mut identity, storage, chain, None).await;
459 window().location().reload().unwrap();
460 });
461 };
462 idle_manager.register_callback(callback);
463 }
464 }
465 }
466 }
467
468 async fn handle_success(
470 &mut self,
471 message: AuthResponseSuccess,
472 on_success: Option<OnSuccess>,
473 ) -> Result<(), DelegationError>
474 {
475 self.login_complete.store(true, Ordering::SeqCst);
478
479 if let Some(w) = self.take_idp_window() {
481 let _ = w.close();
483 };
484 self.take_event_handler(); let delegations = message.delegations.clone();
487 let user_public_key = message.user_public_key.clone();
488
489 let delegation_chain = DelegationChain {
491 delegations: delegations.clone(),
492 public_key: user_public_key.clone(),
493 };
494
495 let chain_json = delegation_chain.to_json();
497
498 self.storage
501 .set(KEY_STORAGE_DELEGATION, chain_json.clone())
502 .await;
503
504 self.chain = Arc::new(Mutex::new(Some(delegation_chain.clone())));
506
507 self.identity = ArcIdentity::Delegated(Arc::new(
508 DelegatedIdentity::new_unchecked(
509 user_public_key.clone(),
510 Box::new(self.key.as_arc_identity()),
511 delegations.clone(),
512 )
513 ));
514
515 let is_auth = self.is_authenticated();
517 if !is_auth {
518 eprintln!("CRITICAL: is_authenticated() returned false after successful login");
521
522 let is_not_anonymous = self
524 .identity()
525 .sender()
526 .map(|s| s != Principal::anonymous())
527 .unwrap_or(false);
528
529 let has_chain = if let Ok(guard) = self.chain.lock() {
530 guard.is_some()
531 } else {
532 false
533 };
534
535 eprintln!("Debug is_authenticated(): is_not_anonymous={}, has_chain={}", is_not_anonymous, has_chain);
536
537 if let Ok(mut guard) = self.chain.lock() {
540 *guard = Some(DelegationChain::from_json(&chain_json));
541 }
542
543 let is_auth_retry = self.is_authenticated();
545 eprintln!("After fix attempt: is_authenticated() = {}", is_auth_retry);
546
547 if !is_auth_retry {
550 if let Ok(principal) = self.identity().sender() {
551 eprintln!("Current principal: {}", principal);
552 }
553
554 self.identity = ArcIdentity::Delegated(Arc::new(
556 DelegatedIdentity::new_unchecked(
557 user_public_key.clone(),
558 Box::new(self.key.as_arc_identity()),
559 delegations.clone(),
560 )
561 ));
562
563 let final_auth_check = self.is_authenticated();
565 eprintln!("Final check: is_authenticated() = {}", final_auth_check);
566 }
567 }
568
569 let disable_idle = match self.idle_options.as_ref() {
572 Some(options) => options.disable_idle.unwrap_or(false),
573 None => false,
574 };
575 if self.idle_manager.is_none() && !disable_idle {
576 let idle_manager_options = self.idle_options.as_ref().map(|o| o.idle_manager_options.clone());
577 let new_idle_manager = IdleManager::new(idle_manager_options);
578 self.idle_manager = Some(new_idle_manager);
579
580 if let Some(idle_manager) = self.idle_manager.as_ref() {
582 Self::register_default_idle_callback(
583 self.identity.clone(),
584 self.storage.clone(),
585 self.chain.clone(),
586 Some(idle_manager.clone()),
587 self.idle_options.clone(),
588 );
589 }
590 }
591
592 if let Some(on_success_cb) = on_success {
595 if let Ok(mut guard) = on_success_cb.try_lock() {
597 (*guard)(message);
598 } else {
599 eprintln!("Failed to acquire lock on on_success callback");
600 }
601 }
602
603 Ok(())
604 }
605
606 pub fn identity(&self) -> Arc<dyn Identity> {
608 self.identity.as_arc_identity()
609 }
610
611 pub fn principal(&self) -> Result<Principal, String> {
612 self.identity().sender()
613 }
614
615 pub fn is_authenticated(&self) -> bool {
617 let is_not_anonymous = self
618 .identity()
619 .sender()
620 .map(|s| s != Principal::anonymous())
621 .unwrap_or(false);
622
623 let has_chain = self.chain.lock().unwrap().is_some();
624
625 is_not_anonymous && has_chain
626 }
627
628 pub fn login(&mut self) {
630 self.login_with_options(AuthClientLoginOptions::default());
631 }
632
633 pub fn login_with_options(&mut self, options: AuthClientLoginOptions) {
635 self.login_complete.store(false, Ordering::SeqCst);
637
638 let window = web_sys::window().unwrap();
639
640 let identity_provider_url: web_sys::Url = options
642 .identity_provider
643 .clone()
644 .unwrap_or_else(|| web_sys::Url::new(IDENTITY_PROVIDER_DEFAULT).unwrap());
645
646 identity_provider_url.set_hash(IDENTITY_PROVIDER_ENDPOINT);
648
649 if let Some(idp_window) = self.take_idp_window() {
652 let _ = idp_window.close();
654 }
655 self.take_event_handler();
656
657 let window_handle_result = window
659 .open_with_url_and_target_and_features(
660 &identity_provider_url.href(),
661 "idpWindow",
662 options.window_opener_features.as_deref().unwrap_or(""),
663 );
664
665 match window_handle_result {
666 Ok(Some(window_handle)) => {
667 self.set_idp_window(window_handle);
668
669 let handler = self.get_event_handler(identity_provider_url.clone(), options.clone());
671 self.set_event_handler(handler);
672
673 self.check_interruption(options.on_error.clone(), self.login_complete.clone());
675 }
676 Ok(None) => {
677 self.login_complete.store(true, Ordering::SeqCst); if let Some(on_error) = options.on_error {
680 on_error.lock().unwrap()(Some("Failed to open IdP window. Check popup blocker.".to_string()));
681 } else {
682 eprintln!("Failed to open IdP window. Check popup blocker.");
683 }
684 self.take_event_handler();
686 self.take_idp_window();
687 }
688 Err(e) => {
689 self.login_complete.store(true, Ordering::SeqCst); let error_message = format!("Error opening IdP window: {:?}", e);
692 if let Some(on_error) = options.on_error {
693 on_error.lock().unwrap()(Some(error_message.clone()));
694 } else {
695 eprintln!("{}", error_message);
696 }
697 self.take_event_handler();
699 self.take_idp_window();
700 }
701 }
702 }
703
704 fn check_interruption(&self, on_error: Option<OnError>, login_complete: Arc<AtomicBool>) {
706 let client_id = *self.id;
707 let idp_window = self.get_idp_window();
708 let login_complete_clone = login_complete.clone();
709
710 spawn_local({
711 async move {
712 if let Some(idp_window_ref) = idp_window {
713 sleep(1000).await;
715
716 while !idp_window_ref.closed().unwrap_or(true)
718 && !login_complete_clone.load(Ordering::SeqCst)
719 {
720 sleep(INTERRUPT_CHECK_INTERVAL).await;
721 }
722
723 if idp_window_ref.closed().unwrap_or(true) && !login_complete_clone.load(Ordering::SeqCst) {
726 let _ = idp_window_ref.close(); EVENT_HANDLERS.with(|cell| {
731 if let Ok(mut map) = cell.try_borrow_mut() {
733 map.remove(&client_id);
734 } else {
735 eprintln!("AuthClient::check_interruption: Could not remove event handler for id {} (already borrowed)", client_id);
736 }
737 });
738
739 IDP_WINDOWS.with(|cell| {
741 if let Ok(mut map) = cell.try_borrow_mut() {
742 map.remove(&client_id);
743 } else {
744 eprintln!("AuthClient::check_interruption: Could not remove IDP window for id {} (already borrowed)", client_id);
745 }
746 });
747
748 if !login_complete_clone.load(Ordering::SeqCst) {
751 if let Some(on_error) = on_error {
753 login_complete_clone.store(true, Ordering::SeqCst);
755 on_error.lock().unwrap()(Some(ERROR_USER_INTERRUPT.to_string()));
756 } else {
757 login_complete_clone.store(true, Ordering::SeqCst);
759 }
760 }
761 } else {
762 }
765 }
766 }
767 });
768 }
769
770 fn get_event_handler(
772 &mut self,
773 identity_provider_url: web_sys::Url,
774 options: AuthClientLoginOptions,
775 ) -> EventListener {
776 let client = self.clone();
777
778 let callback = move |event: &web_sys::Event| {
779 let event = event.dyn_ref::<MessageEvent>().unwrap();
780
781 if event.origin() != identity_provider_url.origin() {
782 return;
784 }
785
786 let message = from_value::<IdentityServiceResponseMessage>(event.data()).map_err(|e| e.to_string());
787
788 let max_time_to_live = options
789 .max_time_to_live
790 .unwrap_or(Self::DEFAULT_TIME_TO_LIVE);
791
792 let handle_error_wrapper = |error: String| {
793 let login_complete = client.login_complete.clone();
795 let on_error = options.on_error.clone();
796 let client_id = *client.id; spawn_local(async move {
799 login_complete.store(true, Ordering::SeqCst);
801
802 if let Some(window) = IDP_WINDOWS.with(|map| map.borrow_mut().remove(&client_id)) {
804 let _ = window.close();
805 }
806 EVENT_HANDLERS.with(|map| map.borrow_mut().remove(&client_id));
807
808 if let Some(on_error_cb) = on_error {
810 if let Ok(mut guard) = on_error_cb.try_lock() {
811 (*guard)(Some(error));
812 } else {
813 eprintln!("Failed to acquire lock on on_error callback in event handler");
814 }
815 } else {
816 eprintln!("AuthClient login failed in event handler: {}", error);
817 }
818 });
819 };
820
821 match message.and_then(|m| m.kind()) {
822 Ok(kind) => match kind {
823 IdentityServiceResponseKind::Ready => {
824 use web_sys::js_sys::{Reflect, Uint8Array};
825
826 let request = InternetIdentityAuthRequest {
827 kind: "authorize-client".to_string(),
828 session_public_key: client
829 .key
830 .public_key()
831 .expect("Failed to get public key"),
832 max_time_to_live: Some(max_time_to_live),
833 allow_pin_authentication: options.allow_pin_authentication,
834 derivation_origin: options
835 .derivation_origin
836 .clone()
837 .map(|d| d.to_string().into()),
838 };
839 let request_js_value = match JsValue::from_serde(&request) {
840 Ok(value) => value,
841 Err(err) => {
842 handle_error_wrapper(format!("Failed to serialize request: {}", err));
843 return;
844 }
845 };
846
847 let session_public_key_js = Uint8Array::from(&request.session_public_key[..]).into();
848 if Reflect::set(
849 &request_js_value,
850 &JsValue::from_str("sessionPublicKey"),
851 &session_public_key_js,
852 ).is_err() {
853 handle_error_wrapper("Failed to set sessionPublicKey on request".to_string());
854 return;
855 }
856
857 if let Some(custom_values) = options.custom_values.clone() {
858 for (k, v) in custom_values {
859 match JsValue::from_serde(&v) {
860 Ok(value) => {
861 if Reflect::set(&request_js_value, &JsValue::from_str(&k), &value).is_err() {
862 handle_error_wrapper(format!("Failed to set custom value '{}'", k));
863 }
864 }
865 Err(err) => {
866 handle_error_wrapper(format!("Failed to serialize custom value '{}': {}", k, err));
867 }
868 }
869 }
870 }
871
872 if let Some(idp_window) = client.get_idp_window() {
873 if idp_window
874 .post_message(&request_js_value, &identity_provider_url.origin())
875 .is_err() {
876 handle_error_wrapper("Failed to post message to IdP window".to_string());
877 }
878 } else {
879 handle_error_wrapper("IdP window not found when trying to post message".to_string());
881 }
882 }
883 IdentityServiceResponseKind::AuthSuccess(response) => {
884 let mut client_clone = client.clone();
885 let on_success = options.on_success.clone();
886 let on_error = options.on_error.clone();
887 spawn_local(async move {
888 if let Err(e) = client_clone.handle_success(response, on_success).await {
889 eprintln!("Error during handle_success: {}", e);
891 if let Some(on_error_cb) = on_error {
893 if let Ok(mut guard) = on_error_cb.try_lock() {
894 (*guard)(Some(format!("Error processing successful login: {:?}", e)));
895 }
896 }
897 }
898 });
899 }
900 IdentityServiceResponseKind::AuthFailure(error_message) => {
901 handle_error_wrapper(error_message);
902 }
903 },
904 Err(e) => {
905 handle_error_wrapper(e);
906 }
907 }
908 };
909
910 EventListener::new(&window(), "message", callback)
911 }
912
913 async fn logout_core(
915 identity: &mut ArcIdentity,
916 mut storage: AuthClientStorageType,
917 chain: Arc<Mutex<Option<DelegationChain>>>,
918 return_to: Option<Location>,
919 ) {
920 Self::delete_storage(&mut storage).await;
921
922 *identity = ArcIdentity::Anonymous(Arc::new(AnonymousIdentity));
924 if let Ok(mut guard) = chain.try_lock() {
925 guard.take();
926 } else {
927 eprintln!("Failed to acquire lock on delegation chain during logout");
928 }
929
930 if let Some(return_to) = return_to {
932 if let Some(window) = web_sys::window() {
933 let href_result = return_to.href();
934 if let Ok(href) = href_result {
935 if let Ok(history) = window.history() {
936 if history.push_state_with_url(&JsValue::null(), "", Some(&href)).is_err() && window.location().set_href(&href).is_err() {
937 eprintln!("Failed to set href during logout");
938 }
939 } else if window.location().set_href(&href).is_err() {
940 eprintln!("Failed to set href during logout (no history)");
941 }
942 } else {
943 eprintln!("Failed to get href from return_to location during logout");
944 }
945 }
946 }
947 }
948
949 pub async fn logout(&mut self, return_to: Option<Location>) {
952 if let Some(idle_manager) = self.idle_manager.take() {
953 drop(idle_manager);
954 }
955
956 Self::logout_core(
957 &mut self.identity,
958 self.storage.clone(),
959 self.chain.clone(),
960 return_to,
961 )
962 .await;
963 }
964
965 async fn delete_storage<S>(storage: &mut S)
967 where
968 S: AuthClientStorage,
969 {
970 storage.remove(KEY_STORAGE_KEY).await;
971 storage.remove(KEY_STORAGE_DELEGATION).await;
972 storage.remove(KEY_VECTOR).await;
973 }
974}
975
976#[derive(Default)]
978pub struct AuthClientBuilder {
979 identity: Option<ArcIdentity>,
980 storage: Option<AuthClientStorageType>,
981 key_type: Option<BaseKeyType>,
982 idle_options: Option<IdleOptions>,
983}
984
985impl AuthClientBuilder {
986 fn new() -> Self {
988 Self::default()
989 }
990
991 pub fn identity(mut self, identity: ArcIdentity) -> Self {
993 self.identity = Some(identity);
994 self
995 }
996
997 pub fn storage(mut self, storage: AuthClientStorageType) -> Self {
999 self.storage = Some(storage);
1000 self
1001 }
1002
1003 pub fn key_type(mut self, key_type: BaseKeyType) -> Self {
1005 self.key_type = Some(key_type);
1006 self
1007 }
1008
1009 pub fn idle_options(mut self, idle_options: IdleOptions) -> Self {
1011 self.idle_options = Some(idle_options);
1012 self
1013 }
1014
1015 fn idle_options_mut(&mut self) -> &mut IdleOptions {
1019 self.idle_options.get_or_insert_with(IdleOptions::default)
1020 }
1021
1022 pub fn disable_idle(mut self, disable_idle: bool) -> Self {
1024 self.idle_options_mut().disable_idle = Some(disable_idle);
1025 self
1026 }
1027
1028 pub fn disable_default_idle_callback(mut self, disable_default_idle_callback: bool) -> Self {
1030 self.idle_options_mut().disable_default_idle_callback = Some(disable_default_idle_callback);
1031 self
1032 }
1033
1034 pub fn idle_manager_options(mut self, idle_manager_options: IdleManagerOptions) -> Self {
1036 self.idle_options_mut().idle_manager_options = idle_manager_options;
1037 self
1038 }
1039
1040 pub fn on_idle(mut self, on_idle: fn()) -> Self {
1043 self.idle_options_mut().idle_manager_options.on_idle = Arc::new(Mutex::new(vec![Box::new(on_idle) as Box<dyn FnMut() + Send>]));
1044 self
1045 }
1046
1047 pub fn add_on_idle<F>(mut self, on_idle: F) -> Self
1049 where
1050 F: FnMut() + Send + 'static,
1051 {
1052 let options = self.idle_options_mut();
1053 if Arc::strong_count(&options.idle_manager_options.on_idle) == 0 {
1055 options.idle_manager_options.on_idle = Arc::new(Mutex::new(Vec::new()));
1057 }
1058 if let Ok(mut guard) = options.idle_manager_options.on_idle.lock() {
1060 guard.push(Box::new(on_idle));
1061 } else {
1062 eprintln!("Failed to lock on_idle callbacks to add new one.");
1063 }
1064 self
1065 }
1066
1067 pub fn idle_timeout(mut self, idle_timeout: u32) -> Self {
1069 self.idle_options_mut().idle_manager_options.idle_timeout = Some(idle_timeout);
1070 self
1071 }
1072
1073 pub fn scroll_debounce(mut self, scroll_debounce: u32) -> Self {
1075 self.idle_options_mut().idle_manager_options.scroll_debounce = Some(scroll_debounce);
1076 self
1077 }
1078
1079 pub fn capture_scroll(mut self, capture_scroll: bool) -> Self {
1081 self.idle_options_mut().idle_manager_options.capture_scroll = Some(capture_scroll);
1082 self
1083 }
1084
1085 pub async fn build(self) -> Result<AuthClient, DelegationError> {
1087 let options = AuthClientCreateOptions {
1088 identity: self.identity,
1089 storage: self.storage,
1090 key_type: self.key_type,
1091 idle_options: self.idle_options,
1092 };
1093
1094 AuthClient::new_with_options(options).await
1095 }
1096}
1097
1098#[derive(Clone)]
1099pub enum ArcIdentity {
1100 Anonymous(Arc<AnonymousIdentity>),
1101 Ed25519(Arc<BasicIdentity>),
1102 Delegated(Arc<DelegatedIdentity>),
1103}
1104
1105impl Default for ArcIdentity {
1106 fn default() -> Self {
1107 ArcIdentity::Anonymous(Arc::new(AnonymousIdentity))
1108 }
1109}
1110
1111impl fmt::Debug for ArcIdentity {
1112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1113 match self {
1114 ArcIdentity::Anonymous(_) => write!(f, "ArcIdentity::Anonymous"),
1115 ArcIdentity::Ed25519(_) => write!(f, "ArcIdentity::Ed25519"),
1116 ArcIdentity::Delegated(_) => write!(f, "ArcIdentity::Delegated"),
1117 }
1118 }
1119}
1120
1121impl ArcIdentity {
1122 fn as_arc_identity(&self) -> Arc<dyn Identity> {
1123 match self {
1124 ArcIdentity::Anonymous(id) => id.clone(),
1125 ArcIdentity::Ed25519(id) => id.clone(),
1126 ArcIdentity::Delegated(id) => id.clone(),
1127 }
1128 }
1129
1130 fn public_key(&self) -> Option<Vec<u8>> {
1131 match self {
1132 ArcIdentity::Anonymous(id) => id.public_key(),
1133 ArcIdentity::Ed25519(id) => id.public_key(),
1134 ArcIdentity::Delegated(id) => id.public_key(),
1135 }
1136 }
1137}
1138
1139impl From<AnonymousIdentity> for ArcIdentity {
1140 fn from(identity: AnonymousIdentity) -> Self {
1141 ArcIdentity::Anonymous(Arc::new(identity))
1142 }
1143}
1144
1145impl From<BasicIdentity> for ArcIdentity {
1146 fn from(identity: BasicIdentity) -> Self {
1147 ArcIdentity::Ed25519(Arc::new(identity))
1148 }
1149}
1150
1151impl From<DelegatedIdentity> for ArcIdentity {
1152 fn from(identity: DelegatedIdentity) -> Self {
1153 ArcIdentity::Delegated(Arc::new(identity))
1154 }
1155}
1156
1157#[derive(Clone, Default)]
1159pub struct AuthClientLoginOptions {
1160 identity_provider: Option<web_sys::Url>,
1162
1163 max_time_to_live: Option<u64>,
1165
1166 allow_pin_authentication: Option<bool>,
1170
1171 derivation_origin: Option<web_sys::Url>,
1175
1176 window_opener_features: Option<String>,
1183
1184 on_success: Option<OnSuccess>,
1186
1187 on_error: Option<OnError>,
1189
1190 custom_values: Option<HashMap<String, serde_json::Value>>,
1192}
1193
1194impl AuthClientLoginOptions {
1195 pub fn builder() -> AuthClientLoginOptionsBuilder {
1197 AuthClientLoginOptionsBuilder::new()
1198 }
1199}
1200
1201pub struct AuthClientLoginOptionsBuilder {
1203 identity_provider: Option<web_sys::Url>,
1204 max_time_to_live: Option<u64>,
1205 allow_pin_authentication: Option<bool>,
1206 derivation_origin: Option<web_sys::Url>,
1207 window_opener_features: Option<String>,
1208 on_success: Option<Box<dyn FnMut(AuthResponseSuccess) + Send>>,
1209 on_error: Option<Box<dyn FnMut(Option<String>) + Send>>,
1210 custom_values: Option<HashMap<String, serde_json::Value>>,
1211}
1212
1213impl AuthClientLoginOptionsBuilder {
1214 fn new() -> Self {
1215 Self {
1216 identity_provider: None,
1217 max_time_to_live: None,
1218 allow_pin_authentication: None,
1219 derivation_origin: None,
1220 window_opener_features: None,
1221 on_success: None,
1222 on_error: None,
1223 custom_values: None,
1224 }
1225 }
1226
1227 pub fn identity_provider(mut self, identity_provider: web_sys::Url) -> Self {
1229 self.identity_provider = Some(identity_provider);
1230 self
1231 }
1232
1233 pub fn max_time_to_live(mut self, max_time_to_live: u64) -> Self {
1235 self.max_time_to_live = Some(max_time_to_live);
1236 self
1237 }
1238
1239 pub fn allow_pin_authentication(mut self, allow_pin_authentication: bool) -> Self {
1243 self.allow_pin_authentication = Some(allow_pin_authentication);
1244 self
1245 }
1246
1247 pub fn derivation_origin(mut self, derivation_origin: web_sys::Url) -> Self {
1251 self.derivation_origin = Some(derivation_origin);
1252 self
1253 }
1254
1255 pub fn window_opener_features(mut self, window_opener_features: String) -> Self {
1262 self.window_opener_features = Some(window_opener_features);
1263 self
1264 }
1265
1266 pub fn on_success<F>(mut self, on_success: F) -> Self
1268 where
1269 F: FnMut(AuthResponseSuccess) + Send + 'static,
1270 {
1271 self.on_success = Some(Box::new(on_success));
1272 self
1273 }
1274
1275 pub fn on_error<F>(mut self, on_error: F) -> Self
1277 where
1278 F: FnMut(Option<String>) + Send + 'static,
1279 {
1280 self.on_error = Some(Box::new(on_error));
1281 self
1282 }
1283
1284 pub fn custom_values(mut self, custom_values: HashMap<String, serde_json::Value>) -> Self {
1286 self.custom_values = Some(custom_values);
1287 self
1288 }
1289
1290 pub fn build(self) -> AuthClientLoginOptions {
1292 AuthClientLoginOptions {
1293 identity_provider: self.identity_provider,
1294 max_time_to_live: self.max_time_to_live,
1295 allow_pin_authentication: self.allow_pin_authentication,
1296 derivation_origin: self.derivation_origin,
1297 window_opener_features: self.window_opener_features,
1298 on_success: self.on_success.map(|f| Arc::new(Mutex::new(f))),
1299 on_error: self.on_error.map(|f| Arc::new(Mutex::new(f))),
1300 custom_values: self.custom_values,
1301 }
1302 }
1303}
1304
1305#[derive(Default, Clone)]
1307pub struct AuthClientCreateOptions {
1308 pub identity: Option<ArcIdentity>,
1310 pub storage: Option<AuthClientStorageType>,
1312 pub key_type: Option<BaseKeyType>,
1314 pub idle_options: Option<IdleOptions>,
1316}
1317
1318#[derive(Default, Clone, Debug)]
1320pub struct IdleOptions {
1321 pub disable_idle: Option<bool>,
1323 pub disable_default_idle_callback: Option<bool>,
1325 pub idle_manager_options: IdleManagerOptions,
1327}
1328
1329impl IdleOptions {
1330 pub fn builder() -> IdleOptionsBuilder {
1332 IdleOptionsBuilder::new()
1333 }
1334}
1335
1336pub struct IdleOptionsBuilder {
1338 disable_idle: Option<bool>,
1339 disable_default_idle_callback: Option<bool>,
1340 idle_manager_options: IdleManagerOptions,
1341}
1342
1343impl IdleOptionsBuilder {
1344 fn new() -> Self {
1345 Self {
1346 disable_idle: None,
1347 disable_default_idle_callback: None,
1348 idle_manager_options: IdleManagerOptions::default(),
1349 }
1350 }
1351
1352 pub fn disable_idle(mut self, disable_idle: bool) -> Self {
1354 self.disable_idle = Some(disable_idle);
1355 self
1356 }
1357
1358 pub fn disable_default_idle_callback(mut self, disable_default_idle_callback: bool) -> Self {
1360 self.disable_default_idle_callback = Some(disable_default_idle_callback);
1361 self
1362 }
1363
1364 pub fn idle_manager_options(mut self, idle_manager_options: IdleManagerOptions) -> Self {
1366 self.idle_manager_options = idle_manager_options;
1367 self
1368 }
1369
1370 pub fn on_idle(mut self, on_idle: fn()) -> Self {
1373 self.idle_manager_options.on_idle = Arc::new(Mutex::new(vec![Box::new(on_idle) as Box<dyn FnMut() + Send>]));
1374 self
1375 }
1376
1377 pub fn add_on_idle<F>(mut self, on_idle: F) -> Self
1379 where
1380 F: FnMut() + Send + 'static,
1381 {
1382 if Arc::strong_count(&self.idle_manager_options.on_idle) == 0 {
1384 self.idle_manager_options.on_idle = Arc::new(Mutex::new(Vec::new()));
1386 }
1387 if let Ok(mut guard) = self.idle_manager_options.on_idle.lock() {
1389 guard.push(Box::new(on_idle));
1390 } else {
1391 eprintln!("Failed to lock on_idle callbacks to add new one.");
1392 }
1393 self
1394 }
1395
1396 pub fn idle_timeout(mut self, idle_timeout: u32) -> Self {
1398 self.idle_manager_options.idle_timeout = Some(idle_timeout);
1399 self
1400 }
1401
1402 pub fn scroll_debounce(mut self, scroll_debounce: u32) -> Self {
1404 self.idle_manager_options.scroll_debounce = Some(scroll_debounce);
1405 self
1406 }
1407
1408 pub fn capture_scroll(mut self, capture_scroll: bool) -> Self {
1410 self.idle_manager_options.capture_scroll = Some(capture_scroll);
1411 self
1412 }
1413
1414 pub fn build(self) -> IdleOptions {
1416 IdleOptions {
1417 disable_idle: self.disable_idle,
1418 disable_default_idle_callback: self.disable_default_idle_callback,
1419 idle_manager_options: self.idle_manager_options,
1420 }
1421 }
1422}
1423
1424#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Default)]
1428pub enum BaseKeyType {
1429 #[default]
1431 Ed25519,
1432}
1433
1434impl fmt::Display for BaseKeyType {
1435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1436 match self {
1437 BaseKeyType::Ed25519 => write!(f, "{}", ED25519_KEY_LABEL),
1438 }
1439 }
1440}
1441
1442#[allow(dead_code)]
1443#[cfg(test)]
1444mod tests {
1445 use super::*;
1446 use wasm_bindgen_test::*;
1447
1448 #[wasm_bindgen_test]
1449 fn test_idle_options_builder() {
1450 let options = IdleOptionsBuilder::new()
1451 .disable_idle(true)
1452 .disable_default_idle_callback(true)
1453 .on_idle(|| {})
1454 .idle_timeout(1000)
1455 .scroll_debounce(500)
1456 .capture_scroll(true)
1457 .build();
1458 assert_eq!(options.disable_idle, Some(true));
1459 assert_eq!(options.disable_default_idle_callback, Some(true));
1460 assert!(options.idle_manager_options.on_idle.as_ref().lock().unwrap().is_empty());
1461 assert_eq!(options.idle_manager_options.idle_timeout, Some(1000));
1462 assert_eq!(options.idle_manager_options.scroll_debounce, Some(500));
1463 assert_eq!(options.idle_manager_options.capture_scroll, Some(true));
1464 }
1465
1466 #[wasm_bindgen_test]
1467 fn test_base_key_type_display() {
1468 assert_eq!(BaseKeyType::Ed25519.to_string(), ED25519_KEY_LABEL);
1469 }
1470
1471 #[wasm_bindgen_test]
1472 fn test_base_key_type_default() {
1473 assert_eq!(BaseKeyType::default(), BaseKeyType::Ed25519);
1474 }
1475
1476 #[wasm_bindgen_test]
1477 async fn test_auth_client_builder() {
1478 let private_key = SigningKey::new(thread_rng());
1479 let identity = ArcIdentity::Ed25519(Arc::new(BasicIdentity::from_signing_key(private_key)));
1480
1481 let idle_options = IdleOptions::builder()
1482 .disable_idle(true)
1483 .disable_default_idle_callback(true)
1484 .on_idle(|| {})
1485 .idle_timeout(1000)
1486 .scroll_debounce(500)
1487 .capture_scroll(true)
1488 .build();
1489
1490 let auth_client = AuthClient::builder()
1491 .identity(identity.clone())
1492 .idle_options(idle_options)
1493 .build()
1494 .await
1495 .unwrap();
1496
1497 assert!(!auth_client.is_authenticated());
1498 assert_eq!(auth_client.identity().sender().unwrap(), identity.as_arc_identity().sender().unwrap()); }
1500
1501 #[wasm_bindgen_test]
1502 fn test_auth_client_login_options_builder() {
1503 let custom_values = vec![("key".to_string(), "value".into())].into_iter().collect();
1504
1505 let options = AuthClientLoginOptions::builder()
1506 .allow_pin_authentication(true)
1507 .custom_values(custom_values)
1508 .on_error(|_| {})
1509 .on_success(|_| {})
1510 .build();
1511
1512 assert_eq!(options.allow_pin_authentication, Some(true));
1513 assert!(options.on_error.is_some());
1514 assert!(options.on_success.is_some());
1515 assert!(options.custom_values.is_some());
1516 }
1517}