Skip to main content

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}