Skip to main content

raps_kernel/auth/
two_leg.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2025 Dmytro Yemelianov
3
4//! 2-legged OAuth (client credentials) flow
5
6use anyhow::{Context, Result};
7use std::time::{Duration, Instant};
8
9use super::AuthClient;
10use super::types::{CachedToken, TokenResponse};
11
12impl AuthClient {
13    /// Get a valid 2-legged access token
14    pub async fn get_token(&self) -> Result<String> {
15        // Check if we have a valid cached token
16        {
17            let cache = self.cached_2leg_token.read().await;
18            if let Some(ref token) = *cache
19                && token.is_valid()
20            {
21                return Ok(token.access_token.clone());
22            }
23        }
24
25        // Fetch new token
26        let new_token = self.fetch_2leg_token().await?;
27
28        // Cache the new token
29        {
30            let mut cache = self.cached_2leg_token.write().await;
31            *cache = Some(CachedToken {
32                access_token: new_token.access_token.clone(),
33                expires_at: Instant::now() + Duration::from_secs(new_token.expires_in),
34            });
35        }
36
37        Ok(new_token.access_token)
38    }
39
40    /// Fetch a new 2-legged token
41    async fn fetch_2leg_token(&self) -> Result<TokenResponse> {
42        self.config.require_credentials()?;
43
44        let url = self.config.auth_url();
45
46        let params = [
47            ("grant_type", "client_credentials"),
48            (
49                "scope",
50                "data:read data:write data:create bucket:read bucket:create bucket:delete code:all",
51            ),
52        ];
53
54        let _auth_start = std::time::Instant::now();
55        let response = self
56            .http_client
57            .post(&url)
58            .basic_auth(&self.config.client_id, Some(&self.config.client_secret))
59            .form(&params)
60            .send()
61            .await
62            .context("Failed to send authentication request")?;
63        crate::profiler::record_http_request(_auth_start.elapsed());
64
65        if !response.status().is_success() {
66            let status = response.status();
67            let error_text = response.text().await.unwrap_or_default();
68            anyhow::bail!(
69                "Authentication failed with status {}: {}",
70                status,
71                crate::logging::redact_secrets(&error_text)
72            );
73        }
74
75        let token_response: TokenResponse = response
76            .json()
77            .await
78            .context("Failed to parse token response")?;
79
80        Ok(token_response)
81    }
82
83    /// Test 2-legged authentication
84    pub async fn test_auth(&self) -> Result<()> {
85        self.get_token().await?;
86        Ok(())
87    }
88
89    /// Clear the cached 2-legged token
90    #[allow(dead_code)]
91    pub async fn clear_cache(&self) {
92        let mut cache = self.cached_2leg_token.write().await;
93        *cache = None;
94    }
95}