1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use reqwest::Client;

use super::{Error, Mastodon, Result};
use apps::{AppBuilder, Scope};

/// Handles registering your mastodon app to your instance. It is recommended
/// you cache your data struct to avoid registering on every run.
pub struct Registration {
    base: String,
    client: Client,
    client_id: Option<String>,
    client_secret: Option<String>,
    redirect: Option<String>,
    scopes: Scope,
}

#[derive(Deserialize)]
struct OAuth {
    client_id: String,
    client_secret: String,
    redirect_uri: String,
}

#[derive(Deserialize)]
struct AccessToken {
    access_token: String,
}

impl Registration {
    pub fn new<I: Into<String>>(base: I) -> Result<Self> {
        Ok(Registration {
            base: base.into(),
            client: Client::new()?,
            client_id: None,
            client_secret: None,
            redirect: None,
            scopes: Scope::Read,
        })
    }

    /// Register the application with the server from the `base` url.
    ///
    /// ```no_run
    /// # extern crate mammut;
    /// # fn main() {
    /// #    try().unwrap();
    /// # }
    /// # fn try() -> mammut::Result<()> {
    /// use mammut::Registration;
    /// use mammut::apps::{AppBuilder, Scope};
    ///
    /// let app = AppBuilder {
    ///     client_name: "mammut_test",
    ///     redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
    ///     scopes: Scope::Read,
    ///     website: None,
    /// };
    ///
    /// let mut registration = Registration::new("https://mastodon.social")?;
    /// registration.register(app)?;
    /// let url = registration.authorise()?;
    /// // Here you now need to open the url in the browser
    /// // And handle a the redirect url coming back with the code.
    /// let code = String::from("RETURNED_FROM_BROWSER");
    /// let mastodon = registration.create_access_token(code)?;
    ///
    /// println!("{:?}", mastodon.get_home_timeline()?);
    /// # Ok(())
    /// # }
    /// ```
    pub fn register(&mut self, app_builder: AppBuilder) -> Result<()> {
        let url = format!("{}/api/v1/apps", self.base);
        self.scopes = app_builder.scopes;

        let app: OAuth = self.client.post(&url).form(&app_builder).send()?.json()?;

        self.client_id = Some(app.client_id);
        self.client_secret = Some(app.client_secret);
        self.redirect = Some(app.redirect_uri);

        Ok(())
    }

    /// Returns the full url needed for authorisation. This needs to be opened
    /// in a browser.
    pub fn authorise(&mut self) -> Result<String> {
        self.is_registered()?;

        let url = format!(
            "{}/oauth/authorize?client_id={}&redirect_uri={}&scope={}&response_type=code",
            self.base,
            self.client_id.clone().unwrap(),
            self.redirect.clone().unwrap(),
            self.scopes,
        );

        Ok(url)
    }

    fn is_registered(&self) -> Result<()> {
        if self.client_id.is_none() {
            Err(Error::ClientIdRequired)
        } else if self.client_secret.is_none() {
            Err(Error::ClientSecretRequired)
        } else {
            Ok(())
        }
    }

    pub fn create_access_token(self, code: String) -> Result<Mastodon> {
        self.is_registered()?;
        let url = format!(
            "{}/oauth/token?client_id={}&client_secret={}&code={}&grant_type=authorization_code&redirect_uri={}",
            self.base,
            self.client_id.clone().unwrap(),
            self.client_secret.clone().unwrap(),
            code,
            self.redirect.clone().unwrap()
        );

        let token: AccessToken = self.client.post(&url).send()?.json()?;

        Ok(Mastodon::from_registration(self.base,
                                       self.client_id.unwrap(),
                                       self.client_secret.unwrap(),
                                       self.redirect.unwrap(),
                                       token.access_token,
                                       self.client))
    }
}