Begin rewrite
This commit is contained in:
parent
fcd1945dba
commit
ac156bc7b4
|
@ -1,24 +0,0 @@
|
|||
extern crate inth_oauth2;
|
||||
|
||||
use std::io;
|
||||
use inth_oauth2::{Client, GitHub};
|
||||
|
||||
fn main() {
|
||||
let client = Client::<GitHub>::new(
|
||||
Default::default(),
|
||||
"01774654cd9a6051e478",
|
||||
"9f14d16d95d605e715ec1a9aecec220d2565fd5c",
|
||||
Some("https://cmcenroe.me/oauth2-paste/")
|
||||
);
|
||||
|
||||
let auth_uri = client.auth_uri(Some("user"), None).unwrap();
|
||||
|
||||
println!("{}", auth_uri);
|
||||
|
||||
let mut code = String::new();
|
||||
io::stdin().read_line(&mut code).unwrap();
|
||||
|
||||
let token_pair = client.request_token(code.trim()).unwrap();
|
||||
|
||||
println!("{:?}", token_pair);
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
extern crate inth_oauth2;
|
||||
|
||||
use std::io;
|
||||
use inth_oauth2::{Client, Google};
|
||||
|
||||
fn main() {
|
||||
let client = Client::<Google>::new(
|
||||
Default::default(),
|
||||
"143225766783-ip2d9qv6sdr37276t77luk6f7bhd6bj5.apps.googleusercontent.com",
|
||||
"3kZ5WomzHFlN2f_XbhkyPd3o",
|
||||
Some("urn:ietf:wg:oauth:2.0:oob")
|
||||
);
|
||||
|
||||
let auth_uri = client.auth_uri(
|
||||
Some("https://www.googleapis.com/auth/userinfo.email"),
|
||||
None
|
||||
).unwrap();
|
||||
|
||||
println!("{}", auth_uri);
|
||||
|
||||
let mut code = String::new();
|
||||
io::stdin().read_line(&mut code).unwrap();
|
||||
|
||||
let token_pair = client.request_token(code.trim()).unwrap();
|
||||
|
||||
println!("{:?}", token_pair);
|
||||
|
||||
let refreshed = client.refresh_token(token_pair.refresh.unwrap(), None).unwrap();
|
||||
|
||||
println!("{:?}", refreshed);
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
extern crate inth_oauth2;
|
||||
|
||||
use std::io;
|
||||
use inth_oauth2::{Client, Imgur};
|
||||
|
||||
fn main() {
|
||||
let client = Client::<Imgur>::new(
|
||||
Default::default(),
|
||||
"505c8ca804230e0",
|
||||
"c898d8cf28404102752b2119a3a1c6aab49899c8",
|
||||
Some("https://cmcenroe.me/oauth2-paste/")
|
||||
);
|
||||
|
||||
let auth_uri = client.auth_uri(None, None).unwrap();
|
||||
|
||||
println!("{}", auth_uri);
|
||||
|
||||
let mut code = String::new();
|
||||
io::stdin().read_line(&mut code).unwrap();
|
||||
|
||||
let token_pair = client.request_token(code.trim()).unwrap();
|
||||
|
||||
println!("{:?}", token_pair);
|
||||
}
|
206
src/client.rs
206
src/client.rs
|
@ -1,206 +0,0 @@
|
|||
use std::io::Read;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use chrono::{UTC, Duration};
|
||||
use hyper::{self, header, mime};
|
||||
use rustc_serialize::json;
|
||||
use url::{Url, form_urlencoded};
|
||||
|
||||
use super::Provider;
|
||||
use super::{TokenPair, AccessTokenType, AccessToken, RefreshToken};
|
||||
use super::error::{Error, Result, OAuth2Error, OAuth2ErrorCode};
|
||||
|
||||
/// OAuth 2.0 client.
|
||||
///
|
||||
/// Performs HTTP requests using the provided `hyper::Client`.
|
||||
///
|
||||
/// See [RFC6749 section 4.1](http://tools.ietf.org/html/rfc6749#section-4.1).
|
||||
pub struct Client<P: Provider> {
|
||||
http_client: hyper::Client,
|
||||
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
redirect_uri: Option<String>,
|
||||
|
||||
provider: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: Provider> Client<P> {
|
||||
/// Creates an OAuth 2.0 client.
|
||||
pub fn new<S>(
|
||||
http_client: hyper::Client,
|
||||
client_id: S,
|
||||
client_secret: S,
|
||||
redirect_uri: Option<S>
|
||||
) -> Self where S: Into<String> {
|
||||
Client {
|
||||
http_client: http_client,
|
||||
client_id: client_id.into(),
|
||||
client_secret: client_secret.into(),
|
||||
redirect_uri: redirect_uri.map(Into::into),
|
||||
provider: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RustcDecodable)]
|
||||
struct TokenResponse {
|
||||
access_token: String,
|
||||
token_type: String,
|
||||
expires_in: Option<i64>,
|
||||
refresh_token: Option<String>,
|
||||
scope: Option<String>,
|
||||
}
|
||||
|
||||
impl Into<TokenPair> for TokenResponse {
|
||||
fn into(self) -> TokenPair {
|
||||
TokenPair {
|
||||
access: AccessToken {
|
||||
token: self.access_token,
|
||||
token_type: match &self.token_type[..] {
|
||||
"Bearer" | "bearer" => AccessTokenType::Bearer,
|
||||
_ => AccessTokenType::Unrecognized(self.token_type),
|
||||
},
|
||||
expires: self.expires_in.map(|s| UTC::now() + Duration::seconds(s)),
|
||||
scope: self.scope,
|
||||
},
|
||||
refresh: self.refresh_token.map(|t| RefreshToken { token: t }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RustcDecodable)]
|
||||
struct ErrorResponse {
|
||||
error: String,
|
||||
error_description: Option<String>,
|
||||
error_uri: Option<String>,
|
||||
}
|
||||
|
||||
impl Into<OAuth2Error> for ErrorResponse {
|
||||
fn into(self) -> OAuth2Error {
|
||||
let code = match &self.error[..] {
|
||||
"invalid_request" => OAuth2ErrorCode::InvalidRequest,
|
||||
"invalid_client" => OAuth2ErrorCode::InvalidClient,
|
||||
"invalid_grant" => OAuth2ErrorCode::InvalidGrant,
|
||||
"unauthorized_client" => OAuth2ErrorCode::UnauthorizedClient,
|
||||
"unsupported_grant_type" => OAuth2ErrorCode::UnsupportedGrantType,
|
||||
"invalid_scope" => OAuth2ErrorCode::InvalidScope,
|
||||
_ => OAuth2ErrorCode::Unrecognized(self.error),
|
||||
};
|
||||
OAuth2Error {
|
||||
code: code,
|
||||
description: self.error_description,
|
||||
uri: self.error_uri,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Provider> Client<P> {
|
||||
/// Constructs an authorization request URI.
|
||||
///
|
||||
/// See [RFC6749 section 4.1.1](http://tools.ietf.org/html/rfc6749#section-4.1.1).
|
||||
pub fn auth_uri(&self, scope: Option<&str>, state: Option<&str>) -> Result<String> {
|
||||
let mut uri = try!(Url::parse(P::auth_uri()));
|
||||
|
||||
let mut query_pairs = vec![
|
||||
("response_type", "code"),
|
||||
("client_id", &self.client_id),
|
||||
];
|
||||
if let Some(ref redirect_uri) = self.redirect_uri {
|
||||
query_pairs.push(("redirect_uri", redirect_uri));
|
||||
}
|
||||
if let Some(scope) = scope {
|
||||
query_pairs.push(("scope", scope));
|
||||
}
|
||||
if let Some(state) = state {
|
||||
query_pairs.push(("state", state));
|
||||
}
|
||||
|
||||
uri.set_query_from_pairs(query_pairs);
|
||||
|
||||
Ok(uri.serialize())
|
||||
}
|
||||
|
||||
fn auth_header(&self) -> header::Authorization<header::Basic> {
|
||||
header::Authorization(
|
||||
header::Basic {
|
||||
username: self.client_id.clone(),
|
||||
password: Some(self.client_secret.clone()),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn accept_header(&self) -> header::Accept {
|
||||
header::Accept(vec![
|
||||
header::qitem(
|
||||
mime::Mime(
|
||||
mime::TopLevel::Application,
|
||||
mime::SubLevel::Json,
|
||||
vec![]
|
||||
)
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
fn token_post(&self, body_pairs: Vec<(&str, &str)>) -> Result<TokenPair> {
|
||||
let post_body = form_urlencoded::serialize(body_pairs);
|
||||
let request = self.http_client.post(P::token_uri())
|
||||
.header(self.auth_header())
|
||||
.header(self.accept_header())
|
||||
.header(header::ContentType::form_url_encoded())
|
||||
.body(&post_body);
|
||||
|
||||
let mut response = try!(request.send());
|
||||
let mut body = String::new();
|
||||
try!(response.read_to_string(&mut body));
|
||||
|
||||
let token = json::decode::<TokenResponse>(&body);
|
||||
if let Ok(token) = token {
|
||||
return Ok(token.into());
|
||||
}
|
||||
|
||||
let error: ErrorResponse = try!(json::decode(&body));
|
||||
Err(Error::OAuth2(error.into()))
|
||||
}
|
||||
|
||||
/// Requests an access token using an authorization code.
|
||||
///
|
||||
/// See [RFC6749 section 4.1.3](http://tools.ietf.org/html/rfc6749#section-4.1.3).
|
||||
pub fn request_token(&self, code: &str) -> Result<TokenPair> {
|
||||
let mut body_pairs = vec![
|
||||
("grant_type", "authorization_code"),
|
||||
("code", code),
|
||||
];
|
||||
if let Some(ref redirect_uri) = self.redirect_uri {
|
||||
body_pairs.push(("redirect_uri", redirect_uri));
|
||||
}
|
||||
self.token_post(body_pairs)
|
||||
}
|
||||
|
||||
/// Refreshes an access token.
|
||||
///
|
||||
/// The returned `TokenPair` will always have a `refresh`.
|
||||
///
|
||||
/// See [RFC6749 section 6](http://tools.ietf.org/html/rfc6749#section-6).
|
||||
pub fn refresh_token(&self, refresh: RefreshToken, scope: Option<&str>) -> Result<TokenPair> {
|
||||
let mut result = {
|
||||
let mut body_pairs = vec![
|
||||
("grant_type", "refresh_token"),
|
||||
("refresh_token", &refresh.token),
|
||||
];
|
||||
if let Some(scope) = scope {
|
||||
body_pairs.push(("scope", scope));
|
||||
}
|
||||
|
||||
self.token_post(body_pairs)
|
||||
};
|
||||
|
||||
if let Ok(ref mut pair) = result {
|
||||
if pair.refresh.is_none() {
|
||||
pair.refresh = Some(refresh);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
135
src/error.rs
135
src/error.rs
|
@ -1,135 +0,0 @@
|
|||
use std::{error, fmt, io, result};
|
||||
|
||||
use hyper;
|
||||
use rustc_serialize::json;
|
||||
use url;
|
||||
|
||||
/// OAuth 2.0 error codes.
|
||||
///
|
||||
/// See [RFC6749 section 5.2](http://tools.ietf.org/html/rfc6749#section-5.2).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OAuth2ErrorCode {
|
||||
/// The request is missing a required parameter, includes an unsupported parameter value (other
|
||||
/// than grant type), repeats a parameter, includes multiple credentials, utilizes more than
|
||||
/// one mechanism for authenticating the client, or is otherwise malformed.
|
||||
InvalidRequest,
|
||||
|
||||
/// Client authentication failed (e.g., unknown client, no client authentication included, or
|
||||
/// unsupported authentication method).
|
||||
InvalidClient,
|
||||
|
||||
/// The provided authorization grant (e.g., authorization code, resource owner credentials) or
|
||||
/// refresh token is invalid, expired, revoked, does not match the redirection URI used in the
|
||||
/// authorization request, or was issued to another client.
|
||||
InvalidGrant,
|
||||
|
||||
/// The authenticated client is not authorized to use this authorization grant type.
|
||||
UnauthorizedClient,
|
||||
|
||||
/// The authorization grant type is not supported by the authorization server.
|
||||
UnsupportedGrantType,
|
||||
|
||||
/// The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the
|
||||
/// resource owner.
|
||||
InvalidScope,
|
||||
|
||||
/// An unrecognized error code, not defined in RFC6749.
|
||||
Unrecognized(String),
|
||||
}
|
||||
|
||||
/// OAuth 2.0 error.
|
||||
///
|
||||
/// See [RFC6749 section 5.2](http://tools.ietf.org/html/rfc6749#section-5.2).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OAuth2Error {
|
||||
/// Error code.
|
||||
pub code: OAuth2ErrorCode,
|
||||
|
||||
/// Human-readable text providing additional information about the error.
|
||||
pub description: Option<String>,
|
||||
|
||||
/// A URI identifying a human-readable web page with information about the error.
|
||||
pub uri: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for OAuth2Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
try!(write!(f, "{:?}", self.code));
|
||||
if let Some(ref description) = self.description {
|
||||
try!(write!(f, ": {}", description));
|
||||
}
|
||||
if let Some(ref uri) = self.uri {
|
||||
try!(write!(f, " ({})", uri));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for OAuth2Error {
|
||||
fn description(&self) -> &str {
|
||||
"OAuth2 API error"
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur during authentication flow.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
Url(url::ParseError),
|
||||
Hyper(hyper::Error),
|
||||
Json(json::DecoderError),
|
||||
OAuth2(OAuth2Error),
|
||||
}
|
||||
|
||||
/// Result type returned from authentication flow methods.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Io(ref err) => write!(f, "{}", err),
|
||||
Error::Url(ref err) => write!(f, "{}", err),
|
||||
Error::Hyper(ref err) => write!(f, "{}", err),
|
||||
Error::Json(ref err) => write!(f, "{}", err),
|
||||
Error::OAuth2(ref err) => write!(f, "{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::Io(_) => "OAuth2 IO error",
|
||||
Error::Url(_) => "OAuth2 URL error",
|
||||
Error::Hyper(_) => "OAuth2 Hyper error",
|
||||
Error::Json(_) => "OAuth2 JSON error",
|
||||
Error::OAuth2(_) => "OAuth2 API error",
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
match *self {
|
||||
Error::Io(ref err) => Some(err),
|
||||
Error::Url(ref err) => Some(err),
|
||||
Error::Hyper(ref err) => Some(err),
|
||||
Error::Json(ref err) => Some(err),
|
||||
Error::OAuth2(ref err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from {
|
||||
($v:path, $t:ty) => {
|
||||
impl From<$t> for Error {
|
||||
fn from(err: $t) -> Error {
|
||||
$v(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_from!(Error::Io, io::Error);
|
||||
impl_from!(Error::Url, url::ParseError);
|
||||
impl_from!(Error::Hyper, hyper::Error);
|
||||
impl_from!(Error::Json, json::DecoderError);
|
||||
impl_from!(Error::OAuth2, OAuth2Error);
|
132
src/lib.rs
132
src/lib.rs
|
@ -1,136 +1,4 @@
|
|||
//! # "It's not that hard" OAuth2 Client
|
||||
//!
|
||||
//! OAuth 2.0 really isn't that hard, you know?
|
||||
//!
|
||||
//! Implementation of [RFC6749](http://tools.ietf.org/html/rfc6749).
|
||||
//!
|
||||
//! `inth_oauth2` is on [Crates.io][crate] and [GitHub][github].
|
||||
//!
|
||||
//! [crate]: https://crates.io/crates/inth-oauth2
|
||||
//! [github]: https://github.com/programble/inth-oauth2
|
||||
//!
|
||||
//! ## Providers
|
||||
//!
|
||||
//! `inth_oauth2` supports the following OAuth 2.0 providers:
|
||||
//!
|
||||
//! - `Google`
|
||||
//! - `GitHub`
|
||||
//! - `Imgur`
|
||||
//!
|
||||
//! Support for others can be added by implementing the `Provider` trait.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### Creating a client
|
||||
//!
|
||||
//! ```
|
||||
//! use inth_oauth2::{Client, Google};
|
||||
//!
|
||||
//! let client = Client::<Google>::new(
|
||||
//! Default::default(),
|
||||
//! "CLIENT_ID",
|
||||
//! "CLIENT_SECRET",
|
||||
//! Some("REDIRECT_URI")
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! ### Constructing an authorization URI
|
||||
//!
|
||||
//! ```
|
||||
//! # use inth_oauth2::{Client, Google};
|
||||
//! # let client = Client::<Google>::new(Default::default(), "", "", None);
|
||||
//! let auth_uri = client.auth_uri(Some("scope"), Some("state")).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Direct the user to an authorization URI to have them authorize your application.
|
||||
//!
|
||||
//! ### Requesting an access token
|
||||
//!
|
||||
//! Request an access token using a code obtained from the redirect of the authorization URI.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use inth_oauth2::{Client, Google};
|
||||
//! # let client = Client::<Google>::new(Default::default(), "", "", None);
|
||||
//! # let code = String::new();
|
||||
//! let token_pair = client.request_token(&code).unwrap();
|
||||
//! println!("{}", token_pair.access.token);
|
||||
//! ```
|
||||
//!
|
||||
//! ### Refreshing an access token
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use inth_oauth2::{Client, Google};
|
||||
//! # let client = Client::<Google>::new(Default::default(), "", "", None);
|
||||
//! # let mut token_pair = client.request_token("").unwrap();
|
||||
//! if token_pair.expired() {
|
||||
//! if let Some(refresh) = token_pair.refresh {
|
||||
//! token_pair = client.refresh_token(refresh, None).unwrap();
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Using bearer access tokens
|
||||
//!
|
||||
//! If the obtained token is of the `Bearer` type, a Hyper `Authorization` header can be created
|
||||
//! from it.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # extern crate hyper;
|
||||
//! # extern crate inth_oauth2;
|
||||
//! # fn main() {
|
||||
//! # use inth_oauth2::{Client, Google};
|
||||
//! # let client = Client::<Google>::new(Default::default(), "", "", None);
|
||||
//! # let mut token_pair = client.request_token("").unwrap();
|
||||
//! let client = hyper::Client::new();
|
||||
//! let res = client.get("https://example.com/resource")
|
||||
//! .header(token_pair.to_bearer_header().unwrap())
|
||||
//! .send()
|
||||
//! .unwrap();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Persisting tokens
|
||||
//!
|
||||
//! `TokenPair` implements `Encodable` and `Decodable` from `rustc_serialize`, so can be persisted
|
||||
//! as JSON.
|
||||
//!
|
||||
//! ```
|
||||
//! # extern crate inth_oauth2;
|
||||
//! # extern crate rustc_serialize;
|
||||
//! # extern crate chrono;
|
||||
//! use inth_oauth2::{TokenPair, AccessTokenType, AccessToken, RefreshToken};
|
||||
//! use rustc_serialize::json;
|
||||
//! # use chrono::{UTC, Timelike};
|
||||
//! # fn main() {
|
||||
//! # let token_pair = TokenPair {
|
||||
//! # access: AccessToken {
|
||||
//! # token: String::from("AAAAAAAA"),
|
||||
//! # token_type: AccessTokenType::Bearer,
|
||||
//! # expires: Some(UTC::now().with_nanosecond(0).unwrap()),
|
||||
//! # scope: None,
|
||||
//! # },
|
||||
//! # refresh: Some(RefreshToken { token: String::from("BBBBBBBB") }),
|
||||
//! # };
|
||||
//!
|
||||
//! let json = json::encode(&token_pair).unwrap();
|
||||
//! let decoded: TokenPair = json::decode(&json).unwrap();
|
||||
//! assert_eq!(token_pair, decoded);
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
extern crate chrono;
|
||||
extern crate hyper;
|
||||
extern crate rustc_serialize;
|
||||
extern crate url;
|
||||
|
||||
pub use client::Client;
|
||||
pub mod client;
|
||||
|
||||
pub use provider::{Provider, Google, GitHub, Imgur};
|
||||
pub mod provider;
|
||||
|
||||
pub use token::{TokenPair, AccessTokenType, AccessToken, RefreshToken};
|
||||
pub mod token;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
pub mod error;
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
/// An OAuth 2.0 provider.
|
||||
pub trait Provider {
|
||||
/// The authorization endpoint URI.
|
||||
fn auth_uri() -> &'static str;
|
||||
|
||||
/// The token endpoint URI.
|
||||
fn token_uri() -> &'static str;
|
||||
}
|
||||
|
||||
/// Google OAuth 2.0 provider.
|
||||
///
|
||||
/// See [Using OAuth 2.0 to Access Google
|
||||
/// APIs](https://developers.google.com/identity/protocols/OAuth2).
|
||||
pub struct Google;
|
||||
impl Provider for Google {
|
||||
fn auth_uri() -> &'static str { "https://accounts.google.com/o/oauth2/auth" }
|
||||
fn token_uri() -> &'static str { "https://accounts.google.com/o/oauth2/token" }
|
||||
}
|
||||
|
||||
/// GitHub OAuth 2.0 provider.
|
||||
///
|
||||
/// See [OAuth, GitHub API](https://developer.github.com/v3/oauth/).
|
||||
pub struct GitHub;
|
||||
impl Provider for GitHub {
|
||||
fn auth_uri() -> &'static str { "https://github.com/login/oauth/authorize" }
|
||||
fn token_uri() -> &'static str { "https://github.com/login/oauth/access_token" }
|
||||
}
|
||||
|
||||
/// Imgur OAuth 2.0 provider.
|
||||
///
|
||||
/// See [OAuth 2.0, Imgur](https://api.imgur.com/oauth2).
|
||||
pub struct Imgur;
|
||||
impl Provider for Imgur {
|
||||
fn auth_uri() -> &'static str { "https://api.imgur.com/oauth2/authorize" }
|
||||
fn token_uri() -> &'static str { "https://api.imgur.com/oauth2/token" }
|
||||
}
|
122
src/token.rs
122
src/token.rs
|
@ -1,122 +0,0 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use chrono::{DateTime, UTC, TimeZone};
|
||||
use hyper::header;
|
||||
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder};
|
||||
|
||||
/// OAuth 2.0 access token and refresh token pair.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, RustcEncodable, RustcDecodable)]
|
||||
pub struct TokenPair {
|
||||
/// The access token.
|
||||
pub access: AccessToken,
|
||||
/// The refresh token.
|
||||
pub refresh: Option<RefreshToken>,
|
||||
}
|
||||
|
||||
/// OAuth 2.0 access token type.
|
||||
///
|
||||
/// See [RFC6749 section 7.1](http://tools.ietf.org/html/rfc6749#section-7.1).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, RustcEncodable, RustcDecodable)]
|
||||
pub enum AccessTokenType {
|
||||
/// The bearer token type.
|
||||
///
|
||||
/// See [RFC6750](http://tools.ietf.org/html/rfc6750).
|
||||
Bearer,
|
||||
|
||||
/// An unrecognized token type.
|
||||
Unrecognized(String),
|
||||
}
|
||||
|
||||
/// OAuth 2.0 access token.
|
||||
///
|
||||
/// See [RFC6749 section 5](http://tools.ietf.org/html/rfc6749#section-5).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccessToken {
|
||||
/// The access token issued by the authorization server.
|
||||
pub token: String,
|
||||
|
||||
/// The type of the token issued.
|
||||
pub token_type: AccessTokenType,
|
||||
|
||||
/// The expiry time of the access token.
|
||||
pub expires: Option<DateTime<UTC>>,
|
||||
|
||||
/// The scope of the access token.
|
||||
pub scope: Option<String>,
|
||||
}
|
||||
|
||||
/// OAuth 2.0 refresh token.
|
||||
///
|
||||
/// See [RFC6749 section 1.5](http://tools.ietf.org/html/rfc6749#section-1.5).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, RustcEncodable, RustcDecodable)]
|
||||
pub struct RefreshToken {
|
||||
/// The refresh token issued by the authorization server.
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
impl AccessToken {
|
||||
/// Returns true if token is expired.
|
||||
pub fn expired(&self) -> bool {
|
||||
self.expires.map_or(false, |dt| dt < UTC::now())
|
||||
}
|
||||
|
||||
/// Creates an Authorization header.
|
||||
///
|
||||
/// Returns `None` if `token_type` is not `Bearer`.
|
||||
pub fn to_bearer_header(&self) -> Option<header::Authorization<header::Bearer>> {
|
||||
if self.token_type == AccessTokenType::Bearer {
|
||||
Some(header::Authorization(header::Bearer { token: self.token.clone() }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TokenPair {
|
||||
type Target = AccessToken;
|
||||
|
||||
fn deref(&self) -> &AccessToken {
|
||||
&self.access
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RustcEncodable, RustcDecodable)]
|
||||
struct SerializableAccessToken {
|
||||
token: String,
|
||||
token_type: AccessTokenType,
|
||||
expires: Option<i64>,
|
||||
scope: Option<String>,
|
||||
}
|
||||
|
||||
impl SerializableAccessToken {
|
||||
fn from_access_token(access: &AccessToken) -> Self {
|
||||
SerializableAccessToken {
|
||||
token: access.token.clone(),
|
||||
token_type: access.token_type.clone(),
|
||||
expires: access.expires.as_ref().map(DateTime::timestamp),
|
||||
scope: access.scope.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_access_token(self) -> AccessToken {
|
||||
AccessToken {
|
||||
token: self.token,
|
||||
token_type: self.token_type,
|
||||
expires: self.expires.map(|t| UTC.timestamp(t, 0)),
|
||||
scope: self.scope,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for AccessToken {
|
||||
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
|
||||
SerializableAccessToken::from_access_token(self).encode(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for AccessToken {
|
||||
fn decode<D: Decoder>(d: &mut D) -> Result<Self, D::Error> {
|
||||
SerializableAccessToken::decode(d)
|
||||
.map(SerializableAccessToken::into_access_token)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue