Split Token into AccessToken, RefreshToken, TokenPair
This commit is contained in:
parent
1441cbfb10
commit
c088b96f0d
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
40
src/lib.rs
40
src/lib.rs
|
@ -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};
|
||||||
|
|
73
src/token.rs
73
src/token.rs
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue