Split Token into AccessToken, RefreshToken, TokenPair

This commit is contained in:
Curtis McEnroe 2015-11-30 21:55:07 -05:00
parent 1441cbfb10
commit c088b96f0d
5 changed files with 104 additions and 77 deletions

View File

@ -18,7 +18,7 @@ fn main() {
let mut code = String::new(); let mut code = String::new();
io::stdin().read_line(&mut code).unwrap(); io::stdin().read_line(&mut code).unwrap();
let token = client.request_token(code.trim()).unwrap(); let token_pair = client.request_token(code.trim()).unwrap();
println!("{:?}", token); println!("{:?}", token_pair);
} }

View File

@ -21,11 +21,11 @@ fn main() {
let mut code = String::new(); let mut code = String::new();
io::stdin().read_line(&mut code).unwrap(); io::stdin().read_line(&mut code).unwrap();
let token = client.request_token(code.trim()).unwrap(); let token_pair = client.request_token(code.trim()).unwrap();
println!("{:?}", token); println!("{:?}", token_pair);
let refreshed = client.refresh_token(&token, None).unwrap(); let refreshed = client.refresh_token(token_pair.refresh.unwrap(), None).unwrap();
println!("{:?}", refreshed); println!("{:?}", refreshed);
} }

View File

@ -5,7 +5,7 @@ use hyper::{self, header, mime};
use rustc_serialize::json; use rustc_serialize::json;
use url::{Url, form_urlencoded}; use url::{Url, form_urlencoded};
use super::Token; use super::{TokenPair, AccessToken, RefreshToken};
use super::error::{Error, Result, OAuth2Error, OAuth2ErrorCode}; use super::error::{Error, Result, OAuth2Error, OAuth2ErrorCode};
/// OAuth 2.0 client. /// OAuth 2.0 client.
@ -33,14 +33,16 @@ struct TokenResponse {
scope: Option<String>, scope: Option<String>,
} }
impl Into<Token> for TokenResponse { impl Into<TokenPair> for TokenResponse {
fn into(self) -> Token { fn into(self) -> TokenPair {
Token { TokenPair {
access_token: self.access_token, access: AccessToken {
token_type: self.token_type, token: self.access_token,
expires: self.expires_in.map(|s| UTC::now() + Duration::seconds(s)), token_type: self.token_type,
refresh_token: self.refresh_token, expires: self.expires_in.map(|s| UTC::now() + Duration::seconds(s)),
scope: self.scope, scope: self.scope,
},
refresh: self.refresh_token.map(|t| RefreshToken { token: t }),
} }
} }
} }
@ -179,7 +181,7 @@ impl Client {
]) ])
} }
fn token_post(&self, body_pairs: Vec<(&str, &str)>) -> Result<Token> { fn token_post(&self, body_pairs: Vec<(&str, &str)>) -> Result<TokenPair> {
let post_body = form_urlencoded::serialize(body_pairs); let post_body = form_urlencoded::serialize(body_pairs);
let request = self.http_client.post(&self.token_uri) let request = self.http_client.post(&self.token_uri)
.header(self.auth_header()) .header(self.auth_header())
@ -203,7 +205,7 @@ impl Client {
/// Requests an access token using an authorization code. /// Requests an access token using an authorization code.
/// ///
/// See [RFC6749 section 4.1.3](http://tools.ietf.org/html/rfc6749#section-4.1.3). /// See [RFC6749 section 4.1.3](http://tools.ietf.org/html/rfc6749#section-4.1.3).
pub fn request_token(&self, code: &str) -> Result<Token> { pub fn request_token(&self, code: &str) -> Result<TokenPair> {
let mut body_pairs = vec![ let mut body_pairs = vec![
("grant_type", "authorization_code"), ("grant_type", "authorization_code"),
("code", code), ("code", code),
@ -216,27 +218,25 @@ impl Client {
/// Refreshes an access token. /// Refreshes an access token.
/// ///
/// The returned `TokenPair` will always have a `refresh`.
///
/// See [RFC6749 section 6](http://tools.ietf.org/html/rfc6749#section-6). /// See [RFC6749 section 6](http://tools.ietf.org/html/rfc6749#section-6).
/// pub fn refresh_token(&self, refresh: RefreshToken, scope: Option<&str>) -> Result<TokenPair> {
/// # Panics let mut result = {
/// let mut body_pairs = vec![
/// Panics if `token` does not contain a `refresh_token`. ("grant_type", "refresh_token"),
pub fn refresh_token(&self, token: &Token, scope: Option<&str>) -> Result<Token> { ("refresh_token", &refresh.token),
let refresh_token = token.refresh_token.as_ref().unwrap(); ];
if let Some(scope) = scope {
body_pairs.push(("scope", scope));
}
let mut body_pairs = vec![ self.token_post(body_pairs)
("grant_type", "refresh_token"), };
("refresh_token", refresh_token),
];
if let Some(scope) = scope {
body_pairs.push(("scope", scope));
}
let mut result = self.token_post(body_pairs); if let Ok(ref mut pair) = result {
if pair.refresh.is_none() {
if let Ok(ref mut token) = result { pair.refresh = Some(refresh);
if token.refresh_token.is_none() {
token.refresh_token = Some(refresh_token.clone());
} }
} }

View File

@ -70,8 +70,8 @@
//! # use inth_oauth2::Client as OAuth2; //! # use inth_oauth2::Client as OAuth2;
//! # let auth = OAuth2::google(Default::default(), "", "", None); //! # let auth = OAuth2::google(Default::default(), "", "", None);
//! # let code = String::new(); //! # let code = String::new();
//! let token = auth.request_token(&code).unwrap(); //! let token_pair = auth.request_token(&code).unwrap();
//! println!("{}", token.access_token); //! println!("{}", token_pair.access.token);
//! ``` //! ```
//! //!
//! ## Refreshing an access token //! ## Refreshing an access token
@ -81,36 +81,40 @@
//! ```no_run //! ```no_run
//! # use inth_oauth2::Client as OAuth2; //! # use inth_oauth2::Client as OAuth2;
//! # let auth = OAuth2::google(Default::default(), "", "", None); //! # let auth = OAuth2::google(Default::default(), "", "", None);
//! # let mut token = auth.request_token("").unwrap(); //! # let mut token_pair = auth.request_token("").unwrap();
//! if token.expired() { //! if token_pair.expired() {
//! token = auth.refresh_token(&token, None).unwrap(); //! if let Some(refresh) = token_pair.refresh {
//! token_pair = auth.refresh_token(refresh, None).unwrap();
//! }
//! } //! }
//! ``` //! ```
//! //!
//! ## Persisting tokens //! ## Persisting tokens
//! //!
//! `Token` implements `Encodable` and `Decodable` from `rustc_serialize`, so can be persisted in //! `TokenPair` implements `Encodable` and `Decodable` from `rustc_serialize`, so can be persisted
//! JSON. //! as JSON.
//! //!
//! ``` //! ```
//! # extern crate inth_oauth2; //! # extern crate inth_oauth2;
//! # extern crate rustc_serialize; //! # extern crate rustc_serialize;
//! # extern crate chrono; //! # extern crate chrono;
//! use inth_oauth2::Token; //! use inth_oauth2::{TokenPair, AccessToken, RefreshToken};
//! use rustc_serialize::json; //! use rustc_serialize::json;
//! # use chrono::{UTC, Timelike}; //! # use chrono::{UTC, Timelike};
//! # fn main() { //! # fn main() {
//! # let token = Token { //! # let token_pair = TokenPair {
//! # access_token: String::from("AAAAAAAA"), //! # access: AccessToken {
//! # token_type: String::from("bearer"), //! # token: String::from("AAAAAAAA"),
//! # expires: Some(UTC::now().with_nanosecond(0).unwrap()), //! # token_type: String::from("bearer"),
//! # refresh_token: Some(String::from("BBBBBBB")), //! # expires: Some(UTC::now().with_nanosecond(0).unwrap()),
//! # scope: None, //! # scope: None,
//! # },
//! # refresh: Some(RefreshToken { token: String::from("BBBBBBBB") }),
//! # }; //! # };
//! //!
//! let json = json::encode(&token).unwrap(); //! let json = json::encode(&token_pair).unwrap();
//! let decoded: Token = json::decode(&json).unwrap(); //! let decoded: TokenPair = json::decode(&json).unwrap();
//! assert_eq!(token, decoded); //! assert_eq!(token_pair, decoded);
//! # } //! # }
//! ``` //! ```
@ -122,7 +126,7 @@ extern crate url;
pub use client::Client; pub use client::Client;
pub mod client; pub mod client;
pub use token::Token; pub use token::{TokenPair, AccessToken, RefreshToken};
pub mod token; pub mod token;
pub use error::{Error, Result}; pub use error::{Error, Result};

View File

@ -1,13 +1,24 @@
use std::ops::Deref;
use chrono::{DateTime, UTC, TimeZone}; use chrono::{DateTime, UTC, TimeZone};
use rustc_serialize::{Encodable, Encoder, Decodable, Decoder}; 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. /// OAuth 2.0 access token.
/// ///
/// See [RFC6749 section 5](http://tools.ietf.org/html/rfc6749#section-5). /// See [RFC6749 section 5](http://tools.ietf.org/html/rfc6749#section-5).
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token { pub struct AccessToken {
/// The access token issued by the authorization server. /// The access token issued by the authorization server.
pub access_token: String, pub token: String,
/// The type of the token issued. /// The type of the token issued.
/// ///
@ -17,59 +28,71 @@ pub struct Token {
/// The expiry time of the access token. /// The expiry time of the access token.
pub expires: Option<DateTime<UTC>>, pub expires: Option<DateTime<UTC>>,
/// The refresh token, which can be used to obtain new access tokens.
pub refresh_token: Option<String>,
/// The scope of the access token. /// The scope of the access token.
pub scope: Option<String>, pub scope: Option<String>,
} }
impl Token { /// 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. /// Returns true if token is expired.
pub fn expired(&self) -> bool { pub fn expired(&self) -> bool {
self.expires.map_or(false, |dt| dt < UTC::now()) self.expires.map_or(false, |dt| dt < UTC::now())
} }
} }
impl Deref for TokenPair {
type Target = AccessToken;
fn deref<'a>(&'a self) -> &'a AccessToken {
&self.access
}
}
#[derive(RustcEncodable, RustcDecodable)] #[derive(RustcEncodable, RustcDecodable)]
struct SerializableToken { struct SerializableAccessToken {
access_token: String, token: String,
token_type: String, token_type: String,
expires: Option<i64>, expires: Option<i64>,
refresh_token: Option<String>,
scope: Option<String>, scope: Option<String>,
} }
impl SerializableToken { impl SerializableAccessToken {
fn from_token(token: &Token) -> Self { fn from_access_token(access: &AccessToken) -> Self {
SerializableToken { SerializableAccessToken {
access_token: token.access_token.clone(), token: access.token.clone(),
token_type: token.token_type.clone(), token_type: access.token_type.clone(),
expires: token.expires.as_ref().map(DateTime::timestamp), expires: access.expires.as_ref().map(DateTime::timestamp),
refresh_token: token.refresh_token.clone(), scope: access.scope.clone(),
scope: token.scope.clone(),
} }
} }
fn into_token(self) -> Token { fn into_access_token(self) -> AccessToken {
Token { AccessToken {
access_token: self.access_token, token: self.token,
token_type: self.token_type, token_type: self.token_type,
expires: self.expires.map(|t| UTC.timestamp(t, 0)), expires: self.expires.map(|t| UTC.timestamp(t, 0)),
refresh_token: self.refresh_token,
scope: self.scope, scope: self.scope,
} }
} }
} }
impl Encodable for Token { impl Encodable for AccessToken {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> { fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
SerializableToken::from_token(self).encode(s) SerializableAccessToken::from_access_token(self).encode(s)
} }
} }
impl Decodable for Token { impl Decodable for AccessToken {
fn decode<D: Decoder>(d: &mut D) -> Result<Self, D::Error> { fn decode<D: Decoder>(d: &mut D) -> Result<Self, D::Error> {
SerializableToken::decode(d).map(SerializableToken::into_token) SerializableAccessToken::decode(d)
.map(SerializableAccessToken::into_access_token)
} }
} }