tide_openidconnect/
redirect_strategy.rs

1//! Browser redirect strategies.
2//!
3//! Provides multiple browser redirect strategies for use by the
4//! [`authenticated()`](crate::OpenIdConnectRouteExt::authenticated)
5//! route extension:
6//!
7//! - [`HttpRedirect`] is a standard HTTP redirect that uses a `302
8//!   Found` response with a `Location` header. This strategy works well
9//!   if all of your requests are `GET` operations, or if your Identity
10//!   Provider returns the proper CORS preflight headers.
11//! - [`ClientSideRefresh`] allows you to use *client-side* code to
12//!   redirect the browser and can be used to avoid CORS issues, but may
13//!   add additional latency and browser window flashing.
14
15use tide::{
16    http::{
17        headers::{HeaderName, HeaderValues, ToHeaderValues},
18        mime,
19    },
20    Redirect, Response,
21};
22
23/// Redirect the browser to another location.
24pub trait RedirectStrategy: Send + Sync {
25    /// Redirects the browser to the location configured in the
26    /// strategy.
27    fn redirect(&self) -> Response;
28}
29
30/// HTTP-level redirect: `302 Found` with a `Location` header.
31#[derive(Debug)]
32pub struct HttpRedirect {
33    path: String,
34}
35
36impl HttpRedirect {
37    /// Create a new instance, with the location to which this strategy
38    /// will redirect the browser.
39    pub fn new(path: impl AsRef<str>) -> Self {
40        Self {
41            path: path.as_ref().to_string(),
42        }
43    }
44}
45
46impl RedirectStrategy for HttpRedirect {
47    fn redirect(&self) -> Response {
48        Redirect::new(self.path.clone()).into()
49    }
50}
51
52/// Client-side "redirect:" by default, a meta refresh tag, but can be
53/// configured to return a custom response.
54#[derive(Debug)]
55pub struct ClientSideRefresh {
56    body: String,
57    headers: Vec<(HeaderName, HeaderValues)>,
58}
59
60impl ClientSideRefresh {
61    /// Create a new instance, with the location to which this strategy
62    /// will redirect the browser.
63    ///
64    /// The redirect will be implemented as client-side "Meta Refresh"
65    /// that instructs the browser to navigate to the given path
66    /// immediately after loading the page.
67    pub fn from_path(path: impl AsRef<str>) -> Self {
68        let body = format!("<!DOCTYPE html><html><head><meta http-equiv=\"refresh\" content=\"0;URL='{0}'\" /></head><body></body></html>", path.as_ref());
69        ClientSideRefresh::from_body(body)
70    }
71
72    /// Create a new instance, with the raw HTML body that will be
73    /// returned to the browser in order to trigger the refresh.
74    pub fn from_body(body: impl AsRef<str>) -> Self {
75        Self {
76            body: body.as_ref().to_string(),
77            headers: Vec::new(),
78        }
79    }
80
81    /// Adds a header to the client-side refresh response, usually in
82    /// cases where a client-side framework is using the
83    /// `XMLHttpRequest` or `fetch` APIs to send requests, and watches
84    /// for a specific response header in order to effect a client-side
85    /// redirect.
86    pub fn with_header(mut self, name: impl Into<HeaderName>, values: impl ToHeaderValues) -> Self {
87        self.headers.push((
88            name.into(),
89            values
90                .to_header_values()
91                .expect("Invalid header value.")
92                .collect(),
93        ));
94        self
95    }
96}
97
98impl RedirectStrategy for ClientSideRefresh {
99    fn redirect(&self) -> Response {
100        let mut res = Response::builder(200)
101            .body(self.body.clone())
102            .content_type(mime::HTML);
103
104        for (name, values) in self.headers.iter() {
105            res = res.header(name, values);
106        }
107
108        res.build()
109    }
110}