//! Client-side login redirect handling. //! //! The OIDC callback always returns the browser to the configured //! post-login redirect (usually `/`), and the backend's login flow has no //! per-request `next` support. So when the API answers `401`, the page the //! user is on is saved in `sessionStorage` and restored once a session //! exists again. use sycamore_router::navigate_replace; /// `sessionStorage` key holding the page to return to after login. const RETURN_TO_KEY: &str = "tavolo.login.return_to"; fn session_storage() -> Option { web_sys::window()?.session_storage().ok().flatten() } /// The current page (path plus query), the URL to come back to after /// login. pub fn current_path() -> String { let Some(location) = web_sys::window().map(|w| w.location()) else { return String::new(); }; format!( "{}{}", location.pathname().unwrap_or_default(), location.search().unwrap_or_default() ) } /// Whether a path is a page worth returning to: not the login flow /// itself, and not an absolute/protocol-relative URL. fn is_restorable(path: &str) -> bool { path.starts_with('/') && !path.starts_with("//") && !path.starts_with("/auth/") && !path.starts_with("/api/") && path != "/login" } /// Handle an expired session: remember the current page and go to the /// login page. /// /// The navigation is deferred to a timer so the caller can finish handling /// the failed response (e.g. set an error) before its page is unmounted. pub fn redirect_to_login() { let path = current_path(); if is_restorable(&path) { if let Some(storage) = session_storage() { let _ = storage.set_item(RETURN_TO_KEY, &path); } } gloo_timers::callback::Timeout::new(0, move || { // Several in-flight requests may fail at once; only the first // takes us to the login page. if current_path() != "/login" { navigate_replace("/login"); } }) .forget(); } /// The page saved by [`redirect_to_login`], if any. pub fn saved_return_to() -> Option { let storage = session_storage()?; let path = storage.get_item(RETURN_TO_KEY).ok().flatten()?; is_restorable(&path).then_some(path) } /// Drop the saved page, e.g. after it has been restored. pub fn clear_return_to() { if let Some(storage) = session_storage() { let _ = storage.remove_item(RETURN_TO_KEY); } }