mirror of
https://github.com/OMGeeky/yup-oauth2.git
synced 2026-01-20 09:11:00 +01:00
Merge branch 'nagisa/id_token' of github.com:nagisa/yup-oauth2 into nagisa-nagisa/id_token
This commit is contained in:
@@ -26,7 +26,7 @@ where
|
||||
let request = http::Request::get("https://example.com")
|
||||
.header(
|
||||
http::header::AUTHORIZATION,
|
||||
format!("Bearer {}", token.as_str()),
|
||||
format!("Bearer {}", token.access_token().ok_or("no access token")?),
|
||||
)
|
||||
.body(hyper::body::Body::empty())?;
|
||||
let response = client.request(request).await?;
|
||||
|
||||
@@ -33,7 +33,7 @@ impl AccessTokenFlow {
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
Ok(TokenInfo {
|
||||
access_token: self.access_token.clone(),
|
||||
access_token: Some(self.access_token.clone()),
|
||||
refresh_token: None,
|
||||
expires_at: None,
|
||||
id_token: None,
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::refresh::RefreshFlow;
|
||||
#[cfg(feature = "service_account")]
|
||||
use crate::service_account::{self, ServiceAccountFlow, ServiceAccountFlowOpts, ServiceAccountKey};
|
||||
use crate::storage::{self, Storage, TokenStorage};
|
||||
use crate::types::{AccessToken, ApplicationSecret, TokenInfo};
|
||||
use crate::types::{ApplicationSecret, Token, TokenInfo};
|
||||
use private::AuthFlow;
|
||||
|
||||
use crate::access_token::{AccessTokenFlow};
|
||||
@@ -69,7 +69,7 @@ where
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
/// Return the current token for the provided scopes.
|
||||
pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result<AccessToken, Error>
|
||||
pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result<Token, Error>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
@@ -80,10 +80,7 @@ where
|
||||
|
||||
/// Return a token for the provided scopes, but don't reuse cached tokens. Instead,
|
||||
/// always fetch a new token from the OAuth server.
|
||||
pub async fn force_refreshed_token<'a, T>(
|
||||
&'a self,
|
||||
scopes: &'a [T],
|
||||
) -> Result<AccessToken, Error>
|
||||
pub async fn force_refreshed_token<'a, T>(&'a self, scopes: &'a [T]) -> Result<Token, Error>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
|
||||
@@ -111,4 +111,4 @@ pub use crate::service_account::ServiceAccountKey;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::error::Error;
|
||||
pub use crate::types::{AccessToken, ApplicationSecret, ConsoleApplicationSecret};
|
||||
pub use crate::types::{ApplicationSecret, ConsoleApplicationSecret, Token};
|
||||
|
||||
@@ -240,7 +240,7 @@ mod tests {
|
||||
const TEST_PRIVATE_KEY_PATH: &'static str = "examples/Sanguine-69411a0c0eea.json";
|
||||
|
||||
// Uncomment this test to verify that we can successfully obtain tokens.
|
||||
//#[tokio::test]
|
||||
// #[tokio::test]
|
||||
#[allow(dead_code)]
|
||||
async fn test_service_account_e2e() {
|
||||
let acc = ServiceAccountFlow::new(ServiceAccountFlowOpts {
|
||||
@@ -262,6 +262,14 @@ mod tests {
|
||||
acc.token(&client, &["https://www.googleapis.com/auth/pubsub"])
|
||||
.await
|
||||
);
|
||||
println!(
|
||||
"{:?}",
|
||||
acc.token(
|
||||
&client,
|
||||
&["https://some.scope/likely-to-hand-out-id-tokens"]
|
||||
)
|
||||
.await
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -449,7 +449,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_disk_storage() {
|
||||
let new_token = |access_token: &str| TokenInfo {
|
||||
access_token: access_token.to_owned(),
|
||||
id_token: None,
|
||||
access_token: Some(access_token.to_owned()),
|
||||
refresh_token: None,
|
||||
expires_at: None,
|
||||
id_token: None,
|
||||
|
||||
93
src/types.rs
93
src/types.rs
@@ -3,49 +3,56 @@ use crate::error::{AuthErrorOr, Error};
|
||||
use time::OffsetDateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents an access token returned by oauth2 servers. All access tokens are
|
||||
/// Bearer tokens. Other types of tokens are not supported.
|
||||
/// Represents a token returned by oauth2 servers. All tokens are Bearer tokens. Other types of
|
||||
/// tokens are not supported.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||
pub struct AccessToken {
|
||||
value: String,
|
||||
pub struct Token {
|
||||
id_token: Option<String>,
|
||||
access_token: Option<String>,
|
||||
expires_at: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl AccessToken {
|
||||
/// A string representation of the access token.
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.value
|
||||
impl Token {
|
||||
/// A string representation of the ID token.
|
||||
pub fn id_token(&self) -> Option<&str> {
|
||||
self.id_token.as_deref()
|
||||
}
|
||||
|
||||
/// The time the access token will expire, if any.
|
||||
/// A string representation of the access token.
|
||||
pub fn access_token(&self) -> Option<&str> {
|
||||
self.access_token.as_deref()
|
||||
}
|
||||
|
||||
/// The time at which the tokens will expire, if any.
|
||||
pub fn expiration_time(&self) -> Option<OffsetDateTime> {
|
||||
self.expires_at
|
||||
}
|
||||
|
||||
/// Determine if the access token is expired.
|
||||
/// This will report that the token is expired 1 minute prior to the
|
||||
/// expiration time to ensure that when the token is actually sent to the
|
||||
/// server it's still valid.
|
||||
///
|
||||
/// This will report that the token is expired 1 minute prior to the expiration time to ensure
|
||||
/// that when the token is actually sent to the server it's still valid.
|
||||
pub fn is_expired(&self) -> bool {
|
||||
// Consider the token expired if it's within 1 minute of it's expiration
|
||||
// time.
|
||||
// Consider the token expired if it's within 1 minute of it's expiration time.
|
||||
self.expires_at
|
||||
.map(|expiration_time| expiration_time - time::Duration::minutes(1) <= OffsetDateTime::now_utc())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for AccessToken {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TokenInfo> for AccessToken {
|
||||
fn from(value: TokenInfo) -> Self {
|
||||
AccessToken {
|
||||
value: value.access_token,
|
||||
expires_at: value.expires_at,
|
||||
impl From<TokenInfo> for Token {
|
||||
fn from(
|
||||
TokenInfo {
|
||||
access_token,
|
||||
id_token,
|
||||
expires_at,
|
||||
..
|
||||
}: TokenInfo,
|
||||
) -> Self {
|
||||
Token {
|
||||
access_token,
|
||||
id_token,
|
||||
expires_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,12 +60,11 @@ impl From<TokenInfo> for AccessToken {
|
||||
/// Represents a token as returned by OAuth2 servers.
|
||||
///
|
||||
/// It is produced by all authentication flows.
|
||||
/// It authenticates certain operations, and must be refreshed once
|
||||
/// it reached it's expiry date.
|
||||
/// It authenticates certain operations, and must be refreshed once it reached it's expiry date.
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
|
||||
pub struct TokenInfo {
|
||||
/// used when authenticating calls to oauth2 enabled services.
|
||||
pub access_token: String,
|
||||
/// used when authorizing calls to oauth2 enabled services.
|
||||
pub access_token: Option<String>,
|
||||
/// used to refresh an expired access_token.
|
||||
pub refresh_token: Option<String>,
|
||||
/// The time when the token expires.
|
||||
@@ -74,9 +80,9 @@ impl TokenInfo {
|
||||
pub(crate) fn from_json(json_data: &[u8]) -> Result<TokenInfo, Error> {
|
||||
#[derive(Deserialize)]
|
||||
struct RawToken {
|
||||
access_token: String,
|
||||
access_token: Option<String>,
|
||||
refresh_token: Option<String>,
|
||||
token_type: String,
|
||||
token_type: Option<String>,
|
||||
expires_in: Option<i64>,
|
||||
id_token: Option<String>,
|
||||
}
|
||||
@@ -92,26 +98,29 @@ impl TokenInfo {
|
||||
id_token,
|
||||
} = <AuthErrorOr<RawToken>>::deserialize(raw_token)?.into_result()?;
|
||||
|
||||
if token_type.to_lowercase().as_str() != "bearer" {
|
||||
use std::io;
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
r#"unknown token type returned; expected "bearer" found {}"#,
|
||||
token_type
|
||||
),
|
||||
)
|
||||
.into());
|
||||
match token_type {
|
||||
Some(token_ty) if !token_ty.eq_ignore_ascii_case("bearer") => {
|
||||
use std::io;
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
r#"unknown token type returned; expected "bearer" found {}"#,
|
||||
token_ty
|
||||
),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let expires_at = expires_in
|
||||
.map(|seconds_from_now| OffsetDateTime::now_utc() + time::Duration::seconds(seconds_from_now));
|
||||
|
||||
Ok(TokenInfo {
|
||||
id_token,
|
||||
access_token,
|
||||
refresh_token,
|
||||
expires_at,
|
||||
id_token,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ async fn test_device_success() {
|
||||
.token(&["https://www.googleapis.com/scope/1"])
|
||||
.await
|
||||
.expect("token failed");
|
||||
assert_eq!("accesstoken", token.as_str());
|
||||
assert_eq!("accesstoken", token.access_token().expect("should have access token"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -255,7 +255,7 @@ async fn test_installed_interactive_success() {
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.expect("failed to get token");
|
||||
assert_eq!("accesstoken", tok.as_str());
|
||||
assert_eq!("accesstoken", tok.access_token().expect("should have access token"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -284,7 +284,7 @@ async fn test_installed_redirect_success() {
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.expect("failed to get token");
|
||||
assert_eq!("accesstoken", tok.as_str());
|
||||
assert_eq!("accesstoken", tok.access_token().expect("should have access token"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -353,7 +353,7 @@ async fn test_service_account_success() {
|
||||
.token(&["https://www.googleapis.com/auth/pubsub"])
|
||||
.await
|
||||
.expect("token failed");
|
||||
assert!(tok.as_str().contains("ya29.c.ElouBywiys0Ly"));
|
||||
assert!(tok.access_token().expect("should have access token").contains("ya29.c.ElouBywiys0Ly"));
|
||||
assert!(OffsetDateTime::now_utc() + time::Duration::seconds(3600) >= tok.expiration_time().unwrap());
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ async fn test_refresh() {
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.expect("failed to get token");
|
||||
assert_eq!("accesstoken", tok.as_str());
|
||||
assert_eq!("accesstoken", tok.access_token().expect("should have access token"));
|
||||
|
||||
server.expect(
|
||||
Expectation::matching(all_of![
|
||||
@@ -425,7 +425,7 @@ async fn test_refresh() {
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.expect("failed to get token");
|
||||
assert_eq!("accesstoken2", tok.as_str());
|
||||
assert_eq!("accesstoken2", tok.access_token().expect("should have access token"));
|
||||
|
||||
server.expect(
|
||||
Expectation::matching(all_of![
|
||||
@@ -446,7 +446,7 @@ async fn test_refresh() {
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.expect("failed to get token");
|
||||
assert_eq!("accesstoken3", tok.as_str());
|
||||
assert_eq!("accesstoken3", tok.access_token().expect("should have access token"));
|
||||
|
||||
// Refresh fails, but renewing the token succeeds.
|
||||
// PR #165
|
||||
@@ -516,7 +516,7 @@ async fn test_memory_storage() {
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.expect("failed to get token");
|
||||
assert_eq!(token1.as_str(), "accesstoken");
|
||||
assert_eq!(token1.access_token().expect("should have access token"), "accesstoken");
|
||||
assert_eq!(token1, token2);
|
||||
|
||||
// Create a new authenticator. This authenticator does not share a cache
|
||||
@@ -542,7 +542,7 @@ async fn test_memory_storage() {
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.expect("failed to get token");
|
||||
assert_eq!(token3.as_str(), "accesstoken2");
|
||||
assert_eq!(token3.access_token().expect("should have access token"), "accesstoken2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -584,7 +584,7 @@ async fn test_disk_storage() {
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.expect("failed to get token");
|
||||
assert_eq!(token1.as_str(), "accesstoken");
|
||||
assert_eq!(token1.access_token().expect("should have access token"), "accesstoken");
|
||||
assert_eq!(token1, token2);
|
||||
}
|
||||
|
||||
@@ -606,7 +606,7 @@ async fn test_disk_storage() {
|
||||
.token(&["https://googleapis.com/some/scope"])
|
||||
.await
|
||||
.expect("failed to get token");
|
||||
assert_eq!(token1.as_str(), "accesstoken");
|
||||
assert_eq!(token1.access_token().expect("should have access token"), "accesstoken");
|
||||
assert_eq!(token1, token2);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user