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