Skip to content
2 changes: 2 additions & 0 deletions crates/forge_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ pub use forge_api::*;
pub use forge_app::dto::*;
pub use forge_app::{Plan, UsageInfo, UserUsage};
pub use forge_domain::{Agent, *};
// Re-export OAuth callback server for CLI use
pub use forge_infra::start_callback_server;
22 changes: 15 additions & 7 deletions crates/forge_domain/src/auth/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,15 @@ impl AuthDetails {
pub struct OAuthTokens {
pub access_token: AccessToken,
pub refresh_token: Option<RefreshToken>,
pub expires_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Utc>>,
}

impl OAuthTokens {
pub fn new(
access_token: impl ToString,
refresh_token: Option<impl ToString>,
expires_at: DateTime<Utc>,
expires_at: Option<DateTime<Utc>>,
) -> Self {
Self {
access_token: access_token.to_string().into(),
Expand All @@ -111,14 +112,21 @@ impl OAuthTokens {
}

/// Checks if the token is expired or will expire within the given buffer
/// duration
/// duration. Returns false if no expiration is set (token doesn't expire).
pub fn needs_refresh(&self, buffer: chrono::Duration) -> bool {
let now = Utc::now();
now + buffer >= self.expires_at
self.expires_at
.map(|expires_at| {
let now = Utc::now();
now + buffer >= expires_at
})
.unwrap_or(false) // No expiration = doesn't need refresh
}

/// Checks if the token is currently expired
/// Checks if the token is currently expired.
/// Returns false if no expiration is set (token doesn't expire).
pub fn is_expired(&self) -> bool {
Utc::now() >= self.expires_at
self.expires_at
.map(|expires_at| Utc::now() >= expires_at)
.unwrap_or(false) // No expiration = not expired
}
}
4 changes: 3 additions & 1 deletion crates/forge_domain/src/auth/oauth_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ pub struct ClientId(String);
pub struct OAuthConfig {
pub auth_url: Url,
pub token_url: Url,
pub client_id: ClientId,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_id: Option<ClientId>,
#[serde(default)]
pub scopes: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub redirect_uri: Option<String>,
Expand Down
Loading
Loading