Skip to content
This repository has been archived by the owner on Apr 18, 2021. It is now read-only.

added reqwest support #2

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,128 changes: 2,128 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ name = "serenity-oauth"
readme = "README.md"
repository = "https://github.com/serenity-rs/oauth.git"
version = "0.1.0"
edition = "2018"

[dependencies]
hyper = "~0.10"
Expand All @@ -17,10 +18,15 @@ serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"
serde_urlencoded = "~0.5"
serenity-model = { git = "https://github.com/serenity-rs/model" }
reqwest = { version = "~0.11", features = ["tokio-native-tls", "blocking"] }
async-trait = "^0.1"

[dependencies.serenity]
version = "0.10.2"
#default-features = false
#features = ["tokio"]

[dev-dependencies]
hyper = "~0.10"
hyper-native-tls = "~0.2"
rocket = "~0.3"
rocket_codegen = "~0.3"
rocket = "~0.4"
rocket_codegen = "~0.4"
reqwest = "~0.11"
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ Add the following to your `Cargo.toml`:
serenity-oauth = { git = "https://github.com/serenity-rs/oauth" }
```

And then the following to your `main.rs` or `lib.rs`:

```rust
extern crate serenity_oauth;
```

### Examples

For an example of how to use this in a real-world program, see the [`examples`]
Expand Down
40 changes: 15 additions & 25 deletions examples/rocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,13 @@
//! `$ git clone https://github.com/serenity-rs/oauth`
//! `$ cd oauth`
//! `$ DISCORD_CLIENT_SECRET=my_secret DISCORD_CLIENT_ID=my_client_id cargo run --example rocket`
#![feature(decl_macro)]

#![feature(custom_derive, plugin)]
#![plugin(rocket_codegen)]

extern crate hyper;
extern crate hyper_native_tls;
extern crate serenity_oauth;
#[macro_use]
extern crate rocket;

use hyper::net::HttpsConnector;
use hyper::Client as HyperClient;
use hyper_native_tls::NativeTlsClient;
use rocket::response::Redirect;
use serenity_oauth::model::AccessTokenExchangeRequest;
use serenity_oauth::{DiscordOAuthHyperRequester, Scope};
use serenity_oauth::prelude::*;
use std::env;
use std::error::Error;

Expand All @@ -50,25 +42,25 @@ fn get_client_id() -> u64 {
}

fn get_client_secret() -> String {
env::var("DISCORD_CLIENT_SECRET")
.expect("No DISCORD_CLIENT_SECRET present")
env::var("DISCORD_CLIENT_SECRET").expect("No DISCORD_CLIENT_SECRET present")
}

#[get("/callback?<params>")]
fn get_callback(params: Params) -> Result<String, Box<Error>> {
#[get("/callback?<code>")]
fn get_callback(code: String) -> Result<String, Box<dyn Error>> {
// Exchange the code for an access token.
let ssl = NativeTlsClient::new()?;
let connector = HttpsConnector::new(ssl);
let client = HyperClient::with_connector(connector);
let client = reqwest::blocking::Client::new();

let response = client.exchange_code(&AccessTokenExchangeRequest::new(
get_client_id(),
get_client_secret(),
params.code,
code,
"http://localhost:8000/callback",
))?;

Ok(format!("The user's access token is: {}", response.access_token))
Ok(format!(
"The user's access token is: {}",
response.access_token
))
}

#[get("/")]
Expand All @@ -82,13 +74,11 @@ fn get_redirect() -> Redirect {
"http://localhost:8000/callback",
);

Redirect::to(&url)
Redirect::to(url)
}

fn main() {
rocket::ignite()
.mount("/", routes![
get_callback,
get_redirect,
]).launch();
.mount("/", routes![get_callback, get_redirect,])
.launch();
}
35 changes: 12 additions & 23 deletions src/bridge/hyper.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
//! Bridged support for the `hyper` HTTP client.

use crate::constants::BASE_TOKEN_URI;
use crate::model::{AccessTokenExchangeRequest, AccessTokenResponse, RefreshTokenRequest};
use crate::Result;
use hyper::client::{Body, Client as HyperClient};
use hyper::header::ContentType;
use serde_json;
use serde_urlencoded;
use ::constants::BASE_TOKEN_URI;
use ::model::{
AccessTokenExchangeRequest,
AccessTokenResponse,
RefreshTokenRequest,
};
use ::Result;

/// A trait used that implements methods for interacting with Discord's OAuth2
/// API on Hyper's client.
Expand Down Expand Up @@ -48,12 +44,9 @@ pub trait DiscordOAuthHyperRequester {
/// Exchange a code for an access token:
///
/// ```rust,no_run
/// extern crate hyper;
/// extern crate serenity_oauth;
///
/// # use std::error::Error;
/// #
/// # fn try_main() -> Result<(), Box<Error>> {
/// # fn try_main() -> Result<(), Box<dyn Error>> {
/// use hyper::Client;
/// use serenity_oauth::model::AccessTokenExchangeRequest;
/// use serenity_oauth::DiscordOAuthHyperRequester;
Expand All @@ -76,8 +69,7 @@ pub trait DiscordOAuthHyperRequester {
/// # try_main().unwrap();
/// # }
/// ```
fn exchange_code(&self, request: &AccessTokenExchangeRequest)
-> Result<AccessTokenResponse>;
fn exchange_code(&self, request: &AccessTokenExchangeRequest) -> Result<AccessTokenResponse>;

/// Exchanges a refresh token, returning a new refresh token and fresh
/// access token.
Expand All @@ -87,8 +79,6 @@ pub trait DiscordOAuthHyperRequester {
/// Exchange a refresh token:
///
/// ```rust,no_run
/// extern crate hyper;
/// extern crate serenity_oauth;
///
/// # use std::error::Error;
/// #
Expand All @@ -115,28 +105,27 @@ pub trait DiscordOAuthHyperRequester {
/// # try_main().unwrap();
/// # }
/// ```
fn exchange_refresh_token(&self, request: &RefreshTokenRequest)
-> Result<AccessTokenResponse>;
fn exchange_refresh_token(&self, request: &RefreshTokenRequest) -> Result<AccessTokenResponse>;
}

impl DiscordOAuthHyperRequester for HyperClient {
fn exchange_code(&self, request: &AccessTokenExchangeRequest)
-> Result<AccessTokenResponse> {
fn exchange_code(&self, request: &AccessTokenExchangeRequest) -> Result<AccessTokenResponse> {
let body = serde_urlencoded::to_string(request)?;

let response = self.post(BASE_TOKEN_URI)
let response = self
.post(BASE_TOKEN_URI)
.header(ContentType::form_url_encoded())
.body(Body::BufBody(body.as_bytes(), body.len()))
.send()?;

serde_json::from_reader(response).map_err(From::from)
}

fn exchange_refresh_token(&self, request: &RefreshTokenRequest)
-> Result<AccessTokenResponse> {
fn exchange_refresh_token(&self, request: &RefreshTokenRequest) -> Result<AccessTokenResponse> {
let body = serde_json::to_string(request)?;

let response = self.post(BASE_TOKEN_URI)
let response = self
.post(BASE_TOKEN_URI)
.body(Body::BufBody(body.as_bytes(), body.len()))
.send()?;

Expand Down
1 change: 1 addition & 0 deletions src/bridge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
//! functions that create one-off clients for ease of use.

pub mod hyper;
pub mod reqwest;
130 changes: 130 additions & 0 deletions src/bridge/reqwest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! Bridged support for the `reqwest` HTTP client.
use crate::constants::BASE_TOKEN_URI;
use crate::model::{AccessTokenExchangeRequest, AccessTokenResponse, RefreshTokenRequest};
use crate::{Error, Result};
use reqwest::blocking::Client as ReqwestClient;
use reqwest::header::CONTENT_TYPE;
use serde_json;
use serde_json::Error as JsonError;
use serde_urlencoded;

// TODO Update this
/// A trait used that implements methods for interacting with Discord's OAuth2
/// API on Reqwest's client.
///
/// # Examples
///
/// Bringing in the trait and creating a client. Since the trait is in scope,
/// the instance of hyper's Client will have those methods available:
///
/// ```rust,no_run
/// # fn main() {
/// use reqwest::blocking::Client;
///
/// let client = Client::new();
///
/// // At this point, the methods defined by the trait are not in scope. By
/// // using the trait, they will be.
/// use serenity_oauth::DiscordOAuthReqwestRequester;
///
/// // The methods defined by `DiscordOAuthHyperRequester` are now in scope and
/// // implemented on the instance of hyper's `Client`.
/// # }
/// ```
///
/// For examples of how to use the trait with the Client, refer to the trait's
/// methods.
pub trait DiscordOAuthReqwestRequester {
/// Exchanges a code for the user's access token.
///
/// # Examples
///
/// Exchange a code for an access token:
///
/// ```rust,no_run
/// # use std::error::Error;
/// #
/// # fn try_main() -> Result<(), Box<dyn Error>> {
/// use reqwest::blocking::Client;
/// use serenity_oauth::model::AccessTokenExchangeRequest;
/// use serenity_oauth::DiscordOAuthReqwestRequester;
///
/// let request_data = AccessTokenExchangeRequest::new(
/// 249608697955745802,
/// "dd99opUAgs7SQEtk2kdRrTMU5zagR2a4",
/// "user code here",
/// "https://myapplication.website",
/// );
///
/// let client = Client::new();
/// let response = client.exchange_code(&request_data)?;
///
/// println!("Access token: {}", response.access_token);
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
fn exchange_code(&self, request: &AccessTokenExchangeRequest) -> Result<AccessTokenResponse>;

/// Exchanges a refresh token, returning a new refresh token and fresh
/// access token.
///
/// # Examples
///
/// Exchange a refresh token:
///
/// ```rust,no_run
/// # use std::error::Error;
/// #
/// # fn try_main() -> Result<(), Box<dyn Error>> {
/// use reqwest::blocking::Client;
/// use serenity_oauth::model::RefreshTokenRequest;
/// use serenity_oauth::DiscordOAuthReqwestRequester;
///
/// let request_data = RefreshTokenRequest::new(
/// 249608697955745802,
/// "dd99opUAgs7SQEtk2kdRrTMU5zagR2a4",
/// "user code here",
/// "https://myapplication.website",
/// );
///
/// let client = Client::new();
/// let response = client.exchange_refresh_token(&request_data)?;
///
/// println!("Fresh access token: {}", response.access_token);
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
fn exchange_refresh_token(&self, request: &RefreshTokenRequest) -> Result<AccessTokenResponse>;
}

impl DiscordOAuthReqwestRequester for ReqwestClient {
fn exchange_code(&self, request: &AccessTokenExchangeRequest) -> Result<AccessTokenResponse> {
let body = serde_urlencoded::to_string(request)?;

let response = self
.post(BASE_TOKEN_URI)
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.body(body)
.send()?;
let body = response.text().unwrap();

serde_json::from_str(&*body).map_err(From::from)
}

fn exchange_refresh_token(&self, request: &RefreshTokenRequest) -> Result<AccessTokenResponse> {
let body = serde_json::to_string(request)?;

let response = self.post(BASE_TOKEN_URI).body(body).send()?;
let body = response.text().unwrap();

serde_json::from_str(&*body).map_err(From::from)
}
}
6 changes: 3 additions & 3 deletions src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! A set of constants around the OAuth2 API.

/// The base authorization URI, used for authorizing an application.
pub const BASE_AUTHORIZE_URI: &str = "https://discordapp.com/api/oauth2/authorize";
pub const BASE_AUTHORIZE_URI: &str = "https://discord.com/api/oauth2/authorize";
/// The revocation URL, used to revoke an access token.
pub const BASE_REVOKE_URI: &str = "https://discordapp.com/api/oauth2/revoke";
pub const BASE_REVOKE_URI: &str = "https://discord.com/api/oauth2/revoke";
/// The token URI, used for exchanging a refresh token for a fresh access token
/// and new refresh token.
pub const BASE_TOKEN_URI: &str = "https://discordapp.com/api/oauth2/token";
pub const BASE_TOKEN_URI: &str = "https://discord.com/api/oauth2/token";
Loading