youtube_api/api/
auth.rs

1use failure::{ensure, Error, format_err};
2use oauth2::basic::BasicClient;
3use oauth2::{PkceCodeVerifier, TokenUrl, RedirectUrl, ClientId, AuthUrl, ClientSecret};
4use tokio::fs::{read_to_string, write};
5
6use crate::auth::{get_oauth_url, perform_oauth, request_token};
7use crate::YoutubeApi;
8use crate::token::AuthToken;
9use crate::api::YoutubeOAuth;
10use reqwest::Client;
11
12pub static CODE_REDIRECT_URI: &str = "urn:ietf:wg:oauth:2.0:oob";
13
14impl YoutubeApi {
15    pub fn new_with_oauth<S: Into<String>>(api_key: S, client_id: String, client_secret: String, redirect_uri: Option<&str>) -> Result<Self, failure::Error> {
16        let oauth_client = BasicClient::new(
17            ClientId::new(client_id.clone()),
18            Some(ClientSecret::new(client_secret.clone())),
19            AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string())?,
20            Some(TokenUrl::new(
21                "https://www.googleapis.com/oauth2/v3/token".to_string(),
22            )?),
23        )
24            .set_redirect_url(RedirectUrl::new(
25                redirect_uri.unwrap_or(CODE_REDIRECT_URI).to_string(),
26            )?);
27
28        let oauth = YoutubeOAuth {
29            client_id,
30            client_secret,
31            client: oauth_client,
32            token: AuthToken::new(),
33        };
34
35        Ok(YoutubeApi {
36            api_key: api_key.into(),
37            oauth: Some(oauth),
38            client: Client::new(),
39        })
40    }
41
42    /**
43     * Perform an OAuth Login
44     *
45     * Available handlers:
46     * * [auth::stdio_login](auth/fn.stdio_login.html)
47     *
48     * # Example
49     * ```rust,no_run
50     * use youtube::{YoutubeApi, auth::stdio_login};
51     *
52     * #[tokio::main]
53     * async fn main() {
54     *   let api = YoutubeApi::new_with_oauth("", String::new(), String::new(), None).unwrap();
55     *
56     *   api.login(stdio_login).await.unwrap();
57     * }
58     * ```
59     */
60    pub async fn login<H>(&self, handler: H) -> Result<(), Error>
61        where
62            H: Fn(String) -> String,
63    {
64        let oauth = self.oauth.as_ref().ok_or_else(|| format_err!("OAuth client not configured"))?;
65        let token = perform_oauth(&oauth.client, handler).await?;
66        oauth.token.set_token(token).await;
67        Ok(())
68    }
69
70    pub fn get_oauth_url(&self) -> Result<(String, String), Error> {
71        let oauth = self.oauth.as_ref().ok_or_else(|| format_err!("OAuth client not configured"))?;
72        let (url, verifier) = get_oauth_url(&oauth.client);
73
74        Ok((url, verifier.secret().clone()))
75    }
76
77    pub async fn request_token(&mut self, code: String, verifier: String) -> Result<(), Error> {
78        let oauth = self.oauth.as_ref().ok_or_else(|| format_err!("OAuth client not configured"))?;
79        let verifier = PkceCodeVerifier::new(verifier);
80
81        let token = request_token(&oauth.client, code, verifier).await?;
82        oauth.token.set_token(token).await;
83
84        Ok(())
85    }
86
87    pub fn has_token(&self) -> bool {
88        self.oauth.as_ref().map(|oauth| oauth.token.has_token()).unwrap_or_default()
89    }
90
91    /**
92     * Stores the auth and refresh token in a `.google-auth.json` file for login without user input.
93     */
94    pub async fn store_token(&self) -> Result<(), Error> {
95        let oauth = self.oauth.as_ref().ok_or_else(|| format_err!("OAuth client not configured"))?;
96        ensure!(oauth.token.has_token(), "No token available to persist");
97        let token = serde_json::to_string(&oauth.token.get_token().await?)?;
98        write(".youtube-auth.json", token).await?; // TODO: configure file path
99        Ok(())
100    }
101
102    /**
103     * Stores the auth and refresh token from a `.google-auth.json` file for login without user input.
104     */
105    pub async fn load_token(&self) -> Result<(), Error> {
106        let oauth = self.oauth.as_ref().ok_or_else(|| format_err!("OAuth client not configured"))?;
107        let token = read_to_string(".youtube-auth.json").await?;
108        let token = serde_json::from_str(&token)?;
109        oauth.token.set_token(token).await;
110        Ok(())
111    }
112}