oxide_auth_rouille/
lib.rs

1//! Offers bindings for the code_grant module with rouille servers.
2//!
3//! Following the simplistic and minimal style of rouille, this module defines only the
4//! implementations for `WebRequest` and `WebResponse` and re-exports the available flows.
5#![warn(missing_docs)]
6
7use core::ops::Deref;
8use std::borrow::Cow;
9
10use oxide_auth::endpoint::{QueryParameter, WebRequest, WebResponse};
11
12use rouille;
13use url::Url;
14
15// In the spirit of simplicity, this module does not implement any wrapper structures.  In order to
16// allow efficient and intuitive usage, we simply re-export common structures.
17pub use oxide_auth::frontends::simple::endpoint::{FnSolicitor, Generic as GenericEndpoint, Vacant};
18
19/// Something went wrong with the rouille http request or response.
20#[derive(Debug)]
21pub enum WebError {
22    /// A parameter was encoded incorrectly.
23    ///
24    /// This may happen for example due to a query parameter that is not valid utf8 when the query
25    /// parameters are necessary for OAuth processing.
26    Encoding,
27}
28
29#[derive(Debug)]
30/// The Request type used by Oxide Auth to extract required information
31pub struct Request<'a> {
32    inner: &'a rouille::Request,
33}
34
35#[derive(Debug)]
36/// The type Oxide Auth provides in response to a request.
37pub struct Response {
38    inner: rouille::Response,
39}
40
41impl<'a> Request<'a> {
42    /// Create a new Request from a `rouille::Request`
43    pub fn new(inner: &'a rouille::Request) -> Self {
44        Request { inner }
45    }
46}
47
48impl Response {
49    /// Produce a `rouille::Response` from a `Response`
50    pub fn into_inner(self) -> rouille::Response {
51        self.inner
52    }
53}
54
55impl From<rouille::Response> for Response {
56    fn from(inner: rouille::Response) -> Self {
57        Response { inner }
58    }
59}
60
61impl From<Response> for rouille::Response {
62    fn from(response: Response) -> Self {
63        response.inner
64    }
65}
66
67impl<'a> WebRequest for Request<'a> {
68    type Error = WebError;
69    type Response = Response;
70
71    fn query(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
72        let query = self.inner.raw_query_string();
73        let data = serde_urlencoded::from_str(query).map_err(|_| WebError::Encoding)?;
74        Ok(Cow::Owned(data))
75    }
76
77    fn urlbody(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
78        match self.inner.header("Content-Type") {
79            None | Some("application/x-www-form-urlencoded") => (),
80            _ => return Err(WebError::Encoding),
81        }
82
83        let body = self.inner.data().ok_or(WebError::Encoding)?;
84        let data = serde_urlencoded::from_reader(body).map_err(|_| WebError::Encoding)?;
85        Ok(Cow::Owned(data))
86    }
87
88    fn authheader(&mut self) -> Result<Option<Cow<str>>, Self::Error> {
89        Ok(self.inner.header("Authorization").map(|st| st.into()))
90    }
91}
92
93impl WebResponse for Response {
94    type Error = WebError;
95
96    fn ok(&mut self) -> Result<(), Self::Error> {
97        self.inner.status_code = 200;
98        Ok(())
99    }
100
101    fn redirect(&mut self, url: Url) -> Result<(), Self::Error> {
102        self.inner.status_code = 302;
103        self.inner
104            .headers
105            .retain(|header| !header.0.eq_ignore_ascii_case("Location"));
106        self.inner
107            .headers
108            .push(("Location".into(), String::from(url).into()));
109        Ok(())
110    }
111
112    fn client_error(&mut self) -> Result<(), Self::Error> {
113        self.inner.status_code = 400;
114        Ok(())
115    }
116
117    fn unauthorized(&mut self, kind: &str) -> Result<(), Self::Error> {
118        self.inner.status_code = 401;
119        self.inner
120            .headers
121            .retain(|header| !header.0.eq_ignore_ascii_case("www-authenticate"));
122        self.inner
123            .headers
124            .push(("WWW-Authenticate".into(), kind.to_string().into()));
125        Ok(())
126    }
127
128    fn body_text(&mut self, text: &str) -> Result<(), Self::Error> {
129        self.inner
130            .headers
131            .retain(|header| !header.0.eq_ignore_ascii_case("Content-Type"));
132        self.inner
133            .headers
134            .push(("Content-Type".into(), "text/plain".into()));
135        self.inner.data = rouille::ResponseBody::from_string(text);
136        Ok(())
137    }
138
139    fn body_json(&mut self, data: &str) -> Result<(), Self::Error> {
140        self.inner
141            .headers
142            .retain(|header| !header.0.eq_ignore_ascii_case("Content-Type"));
143        self.inner
144            .headers
145            .push(("Content-Type".into(), "application/json".into()));
146        self.inner.data = rouille::ResponseBody::from_string(data);
147        Ok(())
148    }
149}
150
151impl Deref for Request<'_> {
152    type Target = rouille::Request;
153
154    fn deref(&self) -> &Self::Target {
155        self.inner
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn multi_query() {
165        let request =
166            &rouille::Request::fake_http("GET", "/authorize?fine=val&param=a&param=b", vec![], vec![]);
167        let mut request = Request::new(request);
168        let query = WebRequest::query(&mut request).unwrap();
169
170        assert_eq!(Some(Cow::Borrowed("val")), query.unique_value("fine"));
171        assert_eq!(None, query.unique_value("param"));
172    }
173}