orbital_shell/auth_context.rs
1//! Reactive authentication context for Orbital applications.
2//!
3//! This module gives Orbital apps one shared place to store the current
4//! [`AuthSession`] and a lightweight refresh token used to refetch it from the
5//! server. Most apps provide it once near the router root and then access it with
6//! [`use_auth_context`], [`use_auth_state`], or [`use_authenticated_user`].
7//!
8//! ## Typical setup
9//!
10//! ```rust,ignore
11//! use leptos::prelude::*;
12//! use orbital::{init_auth_resource, provide_auth_context, AuthSession};
13//!
14//! #[component]
15//! fn AppRoot() -> impl IntoView {
16//! let auth = provide_auth_context(AuthSession::default());
17//! let _session_resource = init_auth_resource(auth.clone());
18//!
19//! view! { <main>"App shell"</main> }
20//! }
21//! ```
22//!
23//! After a sign-in, sign-out, or account change, call [`AuthContext::trigger_refresh`]
24//! to ask the client-side session resource to load the latest backend session state.
25
26use crate::auth_models::{AuthSession, AuthenticatedUser};
27use leptos::prelude::*;
28
29/// Shared authentication context containing session state and refresh controls.
30#[derive(Clone)]
31pub struct AuthContext {
32 session: RwSignal<AuthSession>,
33 reload_token: RwSignal<u64>,
34}
35
36impl AuthContext {
37 /// Return the signal that stores the latest authentication session.
38 pub fn session(&self) -> RwSignal<AuthSession> {
39 self.session
40 }
41
42 /// Return the internal token used to trigger auth refetches.
43 pub fn reload_token(&self) -> RwSignal<u64> {
44 self.reload_token
45 }
46
47 /// Force dependent auth resources to refresh.
48 ///
49 /// This is the common way to tell [`crate::init_auth_resource`] that the backend session may have changed.
50 pub fn trigger_refresh(&self) {
51 self.reload_token
52 .update(|value| *value = value.wrapping_add(1));
53 }
54}
55
56/// Provide [`AuthContext`] to the component tree and return it.
57///
58/// Most applications call this once near the root layout or router.
59pub fn provide_auth_context(initial: AuthSession) -> AuthContext {
60 let context = AuthContext {
61 session: RwSignal::new(initial),
62 reload_token: RwSignal::new(0),
63 };
64 provide_context(context.clone());
65 context
66}
67
68/// Access the shared [`AuthContext`].
69///
70/// Panics when called outside a subtree where [`provide_auth_context`] has been run.
71pub fn use_auth_context() -> AuthContext {
72 use_context::<AuthContext>().expect("AuthContext should be provided near the application root")
73}
74
75/// Reactive memo for the current [`AuthSession`].
76///
77/// Use this when a component needs to branch on anonymous vs authenticated state.
78pub fn use_auth_state() -> Memo<AuthSession> {
79 let auth = use_auth_context();
80 let session = auth.session();
81 Memo::new(move |_| session.get())
82}
83
84/// Reactive memo for the authenticated user profile, if present.
85///
86/// This is the most ergonomic accessor for UI that only cares about the logged-in user details and not anonymous-state metadata.
87pub fn use_authenticated_user() -> Memo<Option<AuthenticatedUser>> {
88 let auth = use_auth_context();
89 let session = auth.session();
90 Memo::new(move |_| session.with(|auth| auth.user().cloned()))
91}