ruma_client_api/uiaa/
get_uiaa_fallback_page.rs

1//! `GET /_matrix/client/*/auth/{auth_type}/fallback/web?session={session_id}`
2//!
3//! Get UIAA fallback web page.
4
5pub mod v3 {
6    //! `/v3/` ([spec])
7    //!
8    //! [spec]: https://spec.matrix.org/latest/client-server-api/#fallback
9
10    use ruma_common::{api::request, metadata};
11
12    use crate::uiaa::AuthType;
13
14    metadata! {
15        method: GET,
16        rate_limited: false,
17        authentication: NoAuthentication,
18        history: {
19            1.0 => "/_matrix/client/r0/auth/{auth_type}/fallback/web",
20            1.1 => "/_matrix/client/v3/auth/{auth_type}/fallback/web",
21        }
22    }
23
24    /// Request type for the `authorize_fallback` endpoint.
25    #[request(error = crate::Error)]
26    pub struct Request {
27        /// The type name (`m.login.dummy`, etc.) of the UIAA stage to get a fallback page for.
28        #[ruma_api(path)]
29        pub auth_type: AuthType,
30
31        /// The ID of the session given by the homeserver.
32        #[ruma_api(query)]
33        pub session: String,
34    }
35
36    impl Request {
37        /// Creates a new `Request` with the given auth type and session ID.
38        pub fn new(auth_type: AuthType, session: String) -> Self {
39            Self { auth_type, session }
40        }
41    }
42
43    /// Response type for the `authorize_fallback` endpoint.
44    #[derive(Debug, Clone)]
45    #[allow(clippy::exhaustive_enums)]
46    pub enum Response {
47        /// The response is a redirect.
48        Redirect(Redirect),
49
50        /// The response is an HTML page.
51        Html(HtmlPage),
52    }
53
54    /// The data of a redirect.
55    #[derive(Debug, Clone)]
56    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
57    pub struct Redirect {
58        /// The URL to redirect the user to.
59        pub url: String,
60    }
61
62    /// The data of a HTML page.
63    #[derive(Debug, Clone)]
64    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
65    pub struct HtmlPage {
66        /// The body of the HTML page.
67        pub body: Vec<u8>,
68    }
69
70    impl Response {
71        /// Creates a new HTML `Response` with the given HTML body.
72        pub fn html(body: Vec<u8>) -> Self {
73            Self::Html(HtmlPage { body })
74        }
75
76        /// Creates a new HTML `Response` with the given redirect URL.
77        pub fn redirect(url: String) -> Self {
78            Self::Redirect(Redirect { url })
79        }
80    }
81
82    #[cfg(feature = "server")]
83    impl ruma_common::api::OutgoingResponse for Response {
84        fn try_into_http_response<T: Default + bytes::BufMut>(
85            self,
86        ) -> Result<http::Response<T>, ruma_common::api::error::IntoHttpError> {
87            match self {
88                Response::Redirect(Redirect { url }) => Ok(http::Response::builder()
89                    .status(http::StatusCode::FOUND)
90                    .header(http::header::LOCATION, url)
91                    .body(T::default())?),
92                Response::Html(HtmlPage { body }) => Ok(http::Response::builder()
93                    .status(http::StatusCode::OK)
94                    .header(http::header::CONTENT_TYPE, "text/html; charset=utf-8")
95                    .body(ruma_common::serde::slice_to_buf(&body))?),
96            }
97        }
98    }
99
100    #[cfg(feature = "client")]
101    impl ruma_common::api::IncomingResponse for Response {
102        type EndpointError = crate::Error;
103
104        fn try_from_http_response<T: AsRef<[u8]>>(
105            response: http::Response<T>,
106        ) -> Result<Self, ruma_common::api::error::FromHttpResponseError<Self::EndpointError>>
107        {
108            use ruma_common::api::{
109                error::{DeserializationError, FromHttpResponseError, HeaderDeserializationError},
110                EndpointError,
111            };
112
113            if response.status().as_u16() >= 400 {
114                return Err(FromHttpResponseError::Server(
115                    Self::EndpointError::from_http_response(response),
116                ));
117            }
118
119            if response.status() == http::StatusCode::FOUND {
120                let Some(location) = response.headers().get(http::header::LOCATION) else {
121                    return Err(DeserializationError::Header(
122                        HeaderDeserializationError::MissingHeader(
123                            http::header::LOCATION.to_string(),
124                        ),
125                    )
126                    .into());
127                };
128
129                let url = location.to_str()?;
130                return Ok(Self::Redirect(Redirect { url: url.to_owned() }));
131            }
132
133            let body = response.into_body().as_ref().to_owned();
134            Ok(Self::Html(HtmlPage { body }))
135        }
136    }
137
138    #[cfg(all(test, any(feature = "client", feature = "server")))]
139    mod tests {
140        use assert_matches2::assert_matches;
141        use http::header::{CONTENT_TYPE, LOCATION};
142        #[cfg(feature = "client")]
143        use ruma_common::api::IncomingResponse;
144        #[cfg(feature = "server")]
145        use ruma_common::api::OutgoingResponse;
146
147        use super::Response;
148
149        #[cfg(feature = "client")]
150        #[test]
151        fn incoming_redirect() {
152            use super::Redirect;
153
154            let http_response = http::Response::builder()
155                .status(http::StatusCode::FOUND)
156                .header(LOCATION, "http://localhost/redirect")
157                .body(Vec::<u8>::new())
158                .unwrap();
159
160            let response = Response::try_from_http_response(http_response).unwrap();
161            assert_matches!(response, Response::Redirect(Redirect { url }));
162            assert_eq!(url, "http://localhost/redirect");
163        }
164
165        #[cfg(feature = "client")]
166        #[test]
167        fn incoming_html() {
168            use super::HtmlPage;
169
170            let http_response = http::Response::builder()
171                .status(http::StatusCode::OK)
172                .header(CONTENT_TYPE, "text/html; charset=utf-8")
173                .body(b"<h1>My Page</h1>")
174                .unwrap();
175
176            let response = Response::try_from_http_response(http_response).unwrap();
177            assert_matches!(response, Response::Html(HtmlPage { body }));
178            assert_eq!(body, b"<h1>My Page</h1>");
179        }
180
181        #[cfg(feature = "server")]
182        #[test]
183        fn outgoing_redirect() {
184            let response = Response::redirect("http://localhost/redirect".to_owned());
185
186            let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
187
188            assert_eq!(http_response.status(), http::StatusCode::FOUND);
189            assert_eq!(
190                http_response.headers().get(LOCATION).unwrap().to_str().unwrap(),
191                "http://localhost/redirect"
192            );
193            assert!(http_response.into_body().is_empty());
194        }
195
196        #[cfg(feature = "server")]
197        #[test]
198        fn outgoing_html() {
199            let response = Response::html(b"<h1>My Page</h1>".to_vec());
200
201            let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
202
203            assert_eq!(http_response.status(), http::StatusCode::OK);
204            assert_eq!(
205                http_response.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap(),
206                "text/html; charset=utf-8"
207            );
208            assert_eq!(http_response.into_body(), b"<h1>My Page</h1>");
209        }
210    }
211}