Skip to main content

raps_kernel/auth/
token_ops.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2025 Dmytro Yemelianov
3
4//! Token storage, retrieval, validation, and login-with-token operations
5
6use anyhow::{Context, Result};
7use colored::Colorize;
8
9use super::AuthClient;
10use super::types::UserInfo;
11use crate::config::Config;
12use crate::storage::{StorageBackend, TokenStorage};
13use crate::types::StoredToken;
14
15impl AuthClient {
16    /// Get token storage instance
17    pub(crate) fn token_storage(&self) -> TokenStorage {
18        let backend = StorageBackend::from_env();
19        TokenStorage::new(backend)
20    }
21
22    /// Load token from persistent storage (static version for initialization)
23    pub(crate) fn load_stored_token_static(_config: &Config) -> Option<StoredToken> {
24        let backend = StorageBackend::from_env();
25        let storage = TokenStorage::new(backend);
26        storage.load().ok().flatten()
27    }
28
29    /// Save token to persistent storage
30    pub(crate) fn save_token(&self, token: &StoredToken) -> Result<()> {
31        let storage = self.token_storage();
32        storage.save(token)
33    }
34
35    /// Load token from persistent storage
36    #[allow(dead_code)]
37    fn load_stored_token(&self) -> Result<StoredToken> {
38        let storage = self.token_storage();
39        storage
40            .load()?
41            .ok_or_else(|| anyhow::anyhow!("No stored token found"))
42    }
43
44    /// Delete stored token
45    pub fn delete_stored_token(&self) -> Result<()> {
46        let storage = self.token_storage();
47        storage.delete()
48    }
49
50    /// Login with a provided access token (for CI/CD scenarios)
51    pub async fn login_with_token(
52        &self,
53        access_token: String,
54        refresh_token: Option<String>,
55        expires_in: u64,
56        scopes: Vec<String>,
57    ) -> Result<StoredToken> {
58        // Validate token by fetching user info
59        let user_info = self.get_user_info_with_token(&access_token).await?;
60
61        println!(
62            "{} Token validated for user: {}",
63            "OK".green().bold(),
64            user_info.email.as_deref().unwrap_or("unknown")
65        );
66
67        // Store the token
68        let stored = StoredToken {
69            access_token: access_token.clone(),
70            refresh_token,
71            expires_at: chrono::Utc::now().timestamp() + expires_in as i64,
72            scopes,
73        };
74
75        self.save_token(&stored)?;
76
77        // Update cache
78        {
79            let mut cache = self.cached_3leg_token.lock().await;
80            cache.token = Some(stored.clone());
81        }
82
83        Ok(stored)
84    }
85
86    /// Get user info with a provided token (for validation)
87    pub(crate) async fn get_user_info_with_token(&self, token: &str) -> Result<UserInfo> {
88        let url = if self.config.base_url != "https://developer.api.autodesk.com" {
89            format!("{}/userinfo", self.config.base_url)
90        } else {
91            "https://api.userprofile.autodesk.com/userinfo".to_string()
92        };
93        let _auth_start = std::time::Instant::now();
94        let response = self
95            .http_client
96            .get(url)
97            .bearer_auth(token)
98            .send()
99            .await
100            .context("Failed to fetch user info")?;
101        crate::profiler::record_http_request(_auth_start.elapsed());
102
103        if !response.status().is_success() {
104            let status = response.status();
105            let error_text = response.text().await.unwrap_or_default();
106            let redacted = crate::logging::redact_secrets(&error_text);
107            anyhow::bail!("Failed to validate token ({status}): {redacted}");
108        }
109
110        let user: UserInfo = response.json().await.context("Failed to parse user info")?;
111
112        Ok(user)
113    }
114}