huskies: merge 609_story_extract_oauth_service
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
//! OAuth flow state types and pure decision logic.
|
||||
//!
|
||||
//! All functions here are pure — no I/O, no network, no clocks.
|
||||
//! Side-effectful operations live exclusively in `io.rs`.
|
||||
|
||||
use crate::llm::oauth::CredentialsFile;
|
||||
|
||||
/// A pending PKCE flow waiting for an OAuth callback.
|
||||
pub struct PendingFlow {
|
||||
/// The PKCE code verifier generated at flow initiation.
|
||||
pub code_verifier: String,
|
||||
/// The redirect URI sent to the authorization endpoint.
|
||||
pub redirect_uri: String,
|
||||
}
|
||||
|
||||
/// Current OAuth credential status, computed without I/O from already-loaded credentials.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlowStatus {
|
||||
/// Whether valid credentials were found on disk.
|
||||
pub authenticated: bool,
|
||||
/// Whether the access token is past its expiry timestamp.
|
||||
pub expired: bool,
|
||||
/// The Unix-epoch millisecond expiry timestamp (0 when unauthenticated).
|
||||
pub expires_at: u64,
|
||||
/// Whether a non-empty refresh token is present.
|
||||
pub has_refresh_token: bool,
|
||||
}
|
||||
|
||||
/// Determine whether `expires_at` (Unix epoch ms) has passed, given `now_ms`.
|
||||
///
|
||||
/// Returns `true` when `now_ms > expires_at`.
|
||||
pub fn is_token_expired(expires_at: u64, now_ms: u64) -> bool {
|
||||
now_ms > expires_at
|
||||
}
|
||||
|
||||
/// Build a `FlowStatus` from loaded credentials and the current time.
|
||||
pub fn build_flow_status(creds: &CredentialsFile, now_ms: u64) -> FlowStatus {
|
||||
let expires_at = creds.claude_ai_oauth.expires_at;
|
||||
FlowStatus {
|
||||
authenticated: true,
|
||||
expired: is_token_expired(expires_at, now_ms),
|
||||
expires_at,
|
||||
has_refresh_token: !creds.claude_ai_oauth.refresh_token.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the unauthenticated `FlowStatus` (no credentials on disk).
|
||||
pub fn unauthenticated_status() -> FlowStatus {
|
||||
FlowStatus {
|
||||
authenticated: false,
|
||||
expired: false,
|
||||
expires_at: 0,
|
||||
has_refresh_token: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_token_expired_when_past_expiry() {
|
||||
assert!(is_token_expired(1000, 2000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_token_not_expired_when_before_expiry() {
|
||||
assert!(!is_token_expired(2000, 1000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_token_not_expired_at_exact_boundary() {
|
||||
// expires_at == now_ms → not expired
|
||||
assert!(!is_token_expired(1000, 1000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unauthenticated_status_is_not_authenticated() {
|
||||
let s = unauthenticated_status();
|
||||
assert!(!s.authenticated);
|
||||
assert!(!s.expired);
|
||||
assert_eq!(s.expires_at, 0);
|
||||
assert!(!s.has_refresh_token);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_flow_status_authenticated_not_expired() {
|
||||
use crate::llm::oauth::{CredentialsFile, OAuthCredentials};
|
||||
let creds = CredentialsFile {
|
||||
claude_ai_oauth: OAuthCredentials {
|
||||
access_token: "tok".to_string(),
|
||||
refresh_token: "ref".to_string(),
|
||||
expires_at: 5000,
|
||||
scopes: vec![],
|
||||
subscription_type: None,
|
||||
rate_limit_tier: None,
|
||||
},
|
||||
};
|
||||
let status = build_flow_status(&creds, 1000);
|
||||
assert!(status.authenticated);
|
||||
assert!(!status.expired);
|
||||
assert_eq!(status.expires_at, 5000);
|
||||
assert!(status.has_refresh_token);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_flow_status_authenticated_expired() {
|
||||
use crate::llm::oauth::{CredentialsFile, OAuthCredentials};
|
||||
let creds = CredentialsFile {
|
||||
claude_ai_oauth: OAuthCredentials {
|
||||
access_token: "tok".to_string(),
|
||||
refresh_token: String::new(),
|
||||
expires_at: 1000,
|
||||
scopes: vec![],
|
||||
subscription_type: None,
|
||||
rate_limit_tier: None,
|
||||
},
|
||||
};
|
||||
let status = build_flow_status(&creds, 9999);
|
||||
assert!(status.authenticated);
|
||||
assert!(status.expired);
|
||||
assert!(!status.has_refresh_token);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user