Skip to main content

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