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}