flickr_api/
login.rs

1use crate::*;
2use futures::Future;
3use std::process::Command;
4use tokio::sync::{mpsc, oneshot};
5use warp::Filter;
6
7#[derive(Debug, Default, Deserialize, Clone)]
8#[serde(default)]
9struct CallbackQuery {
10    oauth_token: String,
11    oauth_verifier: String,
12}
13
14fn setup_server() -> (u32, impl Future<Output = CallbackQuery>) {
15    let (answer_tx, mut answer_rx) = mpsc::unbounded_channel::<CallbackQuery>();
16    let authorization = warp::get()
17        .and(warp::path!("authorization"))
18        .and(warp::query::<CallbackQuery>())
19        .and(warp::any().map(move || answer_tx.clone()))
20        .and_then(
21            |data, sender: mpsc::UnboundedSender<CallbackQuery>| async move {
22                sender.send(data).ok();
23                Result::<String, warp::Rejection>::Ok("<!DOCTYPE html><html><head>Authentication succeeded. You may close this window.<script>window.close();</script></head><body></body></html>".into())
24            },
25        ).map(|reply| {
26        warp::reply::with_header(reply, "Content-Type", "text/html")
27    });
28
29    let (plug_tx, plug_rx) = oneshot::channel();
30    let (_addr, server) =
31        warp::serve(authorization).bind_with_graceful_shutdown(([127, 0, 0, 1], 8200), async {
32            plug_rx.await.ok();
33        });
34
35    tokio::spawn(server);
36
37    (8200, async move {
38        let query = answer_rx.recv().await.unwrap();
39        plug_tx.send(()).ok();
40        query
41    })
42}
43
44impl FlickrAPI {
45    /// Top-level method enacting the procesure to receive an access token from a set of API keys
46    ///
47    /// This method opens an HTTP server on port 8200. It will log an url to connect to for the user to
48    /// accept the token, as well as use a generic open method to open the webpage (`open` on macos and
49    /// `xdg-open` on linux)
50    pub async fn login(self) -> Result<Self, Box<dyn Error>> {
51        // Open an HTTP server on localhost to point the callback to
52        let (port, answer) = setup_server();
53        let callback_url = format!("http://localhost:{}/authorization", port);
54
55        // Use the api keys to ask for a request token
56        let response: oauth::OauthTokenAnswer = {
57            let mut params = vec![("oauth_callback", callback_url)];
58            oauth::build_request(
59                oauth::RequestTarget::Get(URL_REQUEST),
60                &mut params,
61                &self.data.key,
62                None,
63            );
64            let request = reqwest::Url::parse_with_params(URL_REQUEST, &params)?;
65            let query = self.data.client.get(request).send().await?;
66            serde_urlencoded::from_str(&query.text().await?)?
67        };
68
69        let request_token = response.to_result()?;
70
71        // Prepare the link for the user to grant permission
72        {
73            let params = vec![
74                ("oauth_token", request_token.token.clone()),
75                ("perms", "write".to_string()),
76            ];
77            let url = reqwest::Url::parse_with_params(URL_AUTHORIZE, params)?.to_string();
78
79            log::info!("OAuth link: {url}");
80
81            #[cfg(target_os = "macos")]
82            Command::new("open").args(vec![url]).spawn()?;
83
84            #[cfg(target_os = "linux")]
85            Command::new("xdg-open").args(vec![url]).spawn()?;
86        }
87
88        // Wait for the HTTP server to receive the callback query once the user accepted
89        let callback_data = answer.await;
90
91        // Exchange the request token for an access token with the verifier received above
92        let response: oauth::OauthAccessAnswer = {
93            let mut params = vec![("oauth_verifier", callback_data.oauth_verifier)];
94            oauth::build_request(
95                oauth::RequestTarget::Get(URL_ACCESS),
96                &mut params,
97                &self.data.key,
98                Some(&request_token),
99            );
100            let access = reqwest::Url::parse_with_params(URL_ACCESS, &params)?;
101            let query = self.data.client.get(access).send().await?;
102            serde_urlencoded::from_str(&query.text().await?)?
103        };
104
105        let token = response.to_result()?;
106        let mut data = (*self.data).clone();
107        data.token = Some(token);
108
109        Ok(FlickrAPI {
110            data: Rc::new(data),
111        })
112    }
113}