Skip to content

Commit

Permalink
Merge pull request #27 from unbekanntes-pferd/feature/access-token
Browse files Browse the repository at this point in the history
feat(auth): simple access token client
  • Loading branch information
unbekanntes-pferd authored Apr 28, 2024
2 parents 92c31f5 + afd1db7 commit a2349de
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 1 deletion.
56 changes: 55 additions & 1 deletion src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use reqwest::{Client, Url};
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
use retry_policies::Jitter;
use std::{marker::PhantomData, time::Duration};
use std::{marker::PhantomData, time::Duration, u64::MAX};
use tracing::{debug, error};

use base64::{
Expand Down Expand Up @@ -35,6 +35,7 @@ pub enum OAuth2Flow {
PasswordFlow(String, String),
AuthCodeFlow(String),
RefreshToken(String),
Simple(String),
}

impl OAuth2Flow {
Expand All @@ -49,6 +50,10 @@ impl OAuth2Flow {
pub fn refresh_token(refresh_token: impl Into<String>) -> Self {
OAuth2Flow::RefreshToken(refresh_token.into())
}

pub fn simple(token: impl Into<String>) -> Self {
OAuth2Flow::Simple(token.into())
}
}

/// connected state of [DracoonClient]
Expand Down Expand Up @@ -90,6 +95,13 @@ impl Connection {

pub fn is_expired(&self) -> bool {
let now = Utc::now();

// this handles OAuth2Flow::Simple (expires_in is not known)
// the access token is valid or fails with 401
if self.expires_in == MAX {
return false;
}

let expires_at = self.connected_at
+ chrono::Duration::try_seconds(self.expires_in as i64)
.expect("overflow creating seconds");
Expand All @@ -102,6 +114,15 @@ impl Connection {
self.expires_in = connection.expires_in;
self.connected_at = connection.connected_at;
}

pub fn new_from_access_token(access_token: String) -> Self {
Self {
access_token,
refresh_token: String::new(),
expires_in: MAX,
connected_at: Utc::now(),
}
}
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -393,6 +414,7 @@ impl DracoonClient<Disconnected> {
debug!("Connecting with refresh token flow");
self.connect_refresh_token(&token).await?
}
OAuth2Flow::Simple(token) => Connection::new_from_access_token(token),
};

if let Some(token_rotation) = self.token_rotation {
Expand Down Expand Up @@ -654,6 +676,13 @@ impl DracoonClient<Connected> {
.refresh_token
.clone();

// this happens for OAuth2Flow::Simple (no refresh token provided)
if refresh_token.is_empty() {
return Err(DracoonClientError::Auth(
DracoonAuthErrorResponse::new_unauthorized(),
));
}

let auth =
OAuth2RefreshTokenFlow::new(&self.client_id, &self.client_secret, &refresh_token);

Expand Down Expand Up @@ -1384,4 +1413,29 @@ mod tests {

auth_mock.assert();
}

#[tokio::test]
async fn test_simple_connection() {
let dracoon = DracoonClient::builder()
.with_base_url("https://test.dracoon.com")
.with_client_id("client_id")
.with_client_secret("client_secret")
.build()
.expect("valid client config")
.connect(OAuth2Flow::simple("access_token"))
.await;

assert!(dracoon.is_ok());

let connected_client = dracoon.unwrap();

let access_token = connected_client.get_auth_header().await.unwrap();

assert_eq!(access_token, "Bearer access_token");

let conn = connected_client.connection.get().await.unwrap();
assert_eq!(conn.access_token, "access_token");
assert_eq!(conn.refresh_token, "");
assert_eq!(conn.expires_in, std::u64::MAX);
}
}
9 changes: 9 additions & 0 deletions src/auth/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ pub struct DracoonAuthErrorResponse {
error_description: Option<String>,
}

impl DracoonAuthErrorResponse {
pub fn new_unauthorized() -> Self {
Self {
error: "Unauthorized".to_string(),
error_description: None,
}
}
}

impl Display for DracoonAuthErrorResponse {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
Expand Down
26 changes: 26 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,32 @@
//! }
//! ```
//!
//! ### Simple
//!
//! ```no_run
//! use dco3::{Dracoon, OAuth2Flow};
//!
//! #[tokio::main]
//! async fn main() {
//!
//! // you can also pass the access token directly
//! let dracoon = Dracoon::builder()
//! .with_base_url("https://dracoon.team")
//! .with_client_id("client_id")
//! .with_client_secret("client_secret")
//! .build()
//! .unwrap()
//! .connect(OAuth2Flow::simple("access_token"))
//! .await
//! .unwrap();
//!
//! // be aware that the access token refresh will *not* work
//! // once the token is expired, you need to pass a new token
//!
//! }
//! ```
//!
//!
//! ## Error handling
//!
//! All errors are wrapped in the [DracoonClientError] enum.
Expand Down

0 comments on commit a2349de

Please sign in to comment.