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
// https://github.com/rvagg/ghauth/blob/master/ghauth.js

extern crate dialoguer;
extern crate directories;
extern crate failure;
extern crate mkdirp;
extern crate reqwest;
extern crate serde;
extern crate serde_json;

use self::dialoguer::{Input, PasswordInput};
use self::directories::ProjectDirs;
use self::failure::Error;
use self::mkdirp::mkdirp;
use self::reqwest::{ Client, header::{
  Headers, UserAgent, ContentType
  } 
};
use std::collections::HashMap;

const GITHUB_URL: &'static str = "https://api.github.com/authorizations";

/// Configuration passed to create a new GitHub auth instance.
#[derive(Debug, Default)]
pub struct Config {
  /// GitHub auth scopes. E.g. `['user']`.
  // FIXME: convert scopes into a Vector of an Enum. Implement serializability
  // on it too.
  pub scopes: Option<Vec<String>>,

  /// Saved with the token on GitHub. Allows you to identify the purpose of this
  /// token from the GitHub UI.
  pub note: String,

  /// User agent used to make a request.
  pub user_agent: Option<String>,
}

/// A GitHub auth instance.
#[derive(Debug)]
pub struct Authenticator {
  name: String,
  config: Config,
}

/// An authentication returned by a GitHub auth instance.
#[derive(Debug)]
pub struct Authentication {
  /// The User's username.
  user: String,
  /// The token for the User.
  token: String,
}

impl Authenticator {
  /// Create a new instance.
  pub fn new(name: String, config: Config) -> Self {
    Authenticator { name, config }
  }

  /// Authenticate with GitHub.
  pub fn auth(&self) -> Result<Authentication, Error> {
    // Get CLI input.
    let username = Input::new("GitHub username").interact()?;
    let password = PasswordInput::new("GitHub password").interact()?;
    let otp = Input::new("GitHub OTP (optional)").interact()?;

    // Perform HTTP request.
    let client = Client::new();
    let mut headers = Headers::new();
    headers.set_raw("X-GitHub-OTP", otp);
    headers.set(UserAgent::new("Rust GH Auth client"));
    headers.set(ContentType::json());
    let mut body = HashMap::new();
    // if let Some(scopes) = self.config.scopes {
    //   body.insert("scopes", *scopes);
    // }
    body.insert("note", &self.config.note);
    let mut res = client
      .post(GITHUB_URL)
      .json(&body)
      .headers(headers)
      .basic_auth(username, Some(password))
      .send()?;

    // Parse request output.
    let status = res.status();
    ensure!(
      status.is_success(),
      format!(
        "{:?} {:?}",
        res.text().unwrap(),
        status.canonical_reason().unwrap()
      )
    );

    let dirs = ProjectDirs::from("com", "GitHub Auth", &self.name);
    let dir = dirs.data_dir();
    mkdirp(&dir)?;
    unimplemented!();
  }
}

impl Default for Authenticator {
  /// Create a new instance of
  fn default() -> Self {
    let mut config = Config::default();
    config.note = String::from("An unidentified token.");
    Authenticator {
      name: String::from("GitHub Auth"),
      config,
    }
  }
}