Skip to content

Commit

Permalink
fix: handle cross-origin redirects in server function redirect hook
Browse files Browse the repository at this point in the history
In client-side navigation we now handle redirects returned from
server functions the same way how the browser would handle them.
This fixes cross-origin redirects.

Note: this is a breaking change for relative locations such as
`Location: foo`. Previously that was resolved to `<origin>/foo`.
Now it's resolved as a URI-reference (RFC 3986) relative to the
server function URL's (typically `<origin>/api/something`),
resulting in `<origin>/api/foo`. In case you require the old
behavior, make sure that relative locations start with a single
slash.
  • Loading branch information
haslersn committed Feb 8, 2024
1 parent 5559c77 commit 7106b17
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 11 deletions.
26 changes: 16 additions & 10 deletions router/src/components/router.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#[cfg(not(feature = "ssr"))]
use crate::unescape;
use crate::{
create_location, matching::resolve_path, scroll_to_el, use_location,
use_navigate, Branch, History, Location, LocationChange, RouteContext,
RouterIntegrationContext, State,
RouterIntegrationContext, State, Url,
};
#[cfg(not(feature = "ssr"))]
use crate::{unescape, Url};
use cfg_if::cfg_if;
use leptos::{
server_fn::{
Expand All @@ -24,6 +24,7 @@ use std::{
use thiserror::Error;
#[cfg(not(feature = "ssr"))]
use wasm_bindgen::JsCast;
use wasm_bindgen::UnwrapThrowExt;

static GLOBAL_ROUTERS_COUNT: AtomicUsize = AtomicUsize::new(0);

Expand Down Expand Up @@ -57,14 +58,19 @@ pub fn Router(
let navigate = use_navigate();
let navigate = SendWrapper::new(navigate);
let router_hook = Box::new(move |path: &str| {
let path = path.to_string();
// delay by a tick here, so that the Action updates *before* the redirect
request_animation_frame({
let current_origin =
leptos_dom::helpers::location().origin().unwrap_throw();
let url = Url::try_from(path).unwrap_throw();
if url.origin == current_origin {
let navigate = navigate.clone();
move || {
navigate(&path, Default::default());
}
});
// delay by a tick here, so that the Action updates *before* the redirect
request_animation_frame(move || {
navigate(&url.pathname, Default::default());
});
// Use set_href() if the conditions for client-side navigation were not satisfied
} else if let Err(e) = leptos_dom::helpers::location().set_href(path) {
leptos::logging::error!("Failed to redirect: {e:#?}");
}
}) as RedirectHook;
_ = server_fn::redirect::set_redirect_hook(router_hook);

Expand Down
19 changes: 18 additions & 1 deletion server_fn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub use serde;
#[cfg(feature = "serde-lite")]
pub use serde_lite;
use std::{fmt::Display, future::Future, pin::Pin, str::FromStr, sync::Arc};
use wasm_bindgen::UnwrapThrowExt;
#[doc(hidden)]
pub use xxhash_rust;

Expand Down Expand Up @@ -328,7 +329,23 @@ where
// if redirected, call the redirect hook (if that's been set)
if let Some(redirect_hook) = redirect_hook {
if (300..=399).contains(&status) || has_redirect_header {
redirect_hook(&location);
let origin = web_sys::window()
.unwrap_throw()
.location()
.origin()
.unwrap_throw();
let path = Self::PATH;
let base = format!("{origin}{path}");
match web_sys::Url::new_with_base(&location, &base) {
Ok(path) => redirect_hook(&path.href()),
Err(e) => {
let msg = format!(
"invalid redirect location: {}",
e.as_string().unwrap_or_default()
);
return Err(ServerFnError::Response(msg));
}
}
}
}
res
Expand Down

0 comments on commit 7106b17

Please sign in to comment.