Files
sentryagent-idp/sdk-rust/src/credentials.rs
SentryAgent.ai Developer a4aab1b5b3 feat(phase-5): WS1 — Rust SDK
Implements the sentryagent-idp Rust SDK crate (sdk-rust/) with:
- TokenManager with Arc<Mutex<TokenCache>> for thread-safe token caching
- AgentIdPClient with full method coverage: agents, oauth2, credentials, audit, marketplace, delegation
- Error hierarchy via thiserror (AgentIdPError enum)
- All model types with serde derive
- 429 RateLimited handling with Retry-After parsing; zero unwrap() calls
- Unit tests (mockito), doc tests, and integration tests (#[ignore])
- quickstart example, full README, cargo doc clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 02:48:14 +00:00

99 lines
2.9 KiB
Rust

//! Credential management methods for `AgentIdPClient`.
//!
//! Covers `POST /agents/{id}/credentials` (generate),
//! `POST /agents/{id}/credentials/rotate`, and
//! `DELETE /agents/{id}/credentials/{cred_id}`.
use crate::agents::parse_response;
use crate::client::AgentIdPClient;
use crate::error::AgentIdPError;
use crate::models::Credentials;
impl AgentIdPClient {
/// Generates a new set of credentials (client ID + secret) for an agent.
///
/// `POST /agents/{id}/credentials` → `201 Credentials`
///
/// The `client_secret` field in the response is the **only time** the
/// plaintext secret is returned — store it securely.
pub async fn generate_credentials(
&self,
agent_id: &str,
) -> Result<Credentials, AgentIdPError> {
let auth = self.get_auth_header().await?;
let url = format!("{}/agents/{}/credentials", self.base_url, agent_id);
let resp = self
.http
.post(&url)
.header("Authorization", auth)
.header("Content-Length", "0")
.send()
.await?;
parse_response(resp).await
}
/// Rotates the credentials for an agent, invalidating the previous secret.
///
/// `POST /agents/{id}/credentials/rotate` → `200 Credentials`
///
/// The new `client_secret` is returned in the response and will not be
/// retrievable again.
pub async fn rotate_credentials(
&self,
agent_id: &str,
) -> Result<Credentials, AgentIdPError> {
let auth = self.get_auth_header().await?;
let url = format!(
"{}/agents/{}/credentials/rotate",
self.base_url, agent_id
);
let resp = self
.http
.post(&url)
.header("Authorization", auth)
.header("Content-Length", "0")
.send()
.await?;
parse_response(resp).await
}
/// Revokes a specific credential set for an agent.
///
/// `DELETE /agents/{id}/credentials/{cred_id}` → `204 No Content`
///
/// # Errors
///
/// Returns [`crate::error::AgentIdPError::NotFound`] when the agent or
/// credential ID does not exist.
pub async fn revoke_credentials(
&self,
agent_id: &str,
cred_id: &str,
) -> Result<(), AgentIdPError> {
let auth = self.get_auth_header().await?;
let url = format!(
"{}/agents/{}/credentials/{}",
self.base_url, agent_id, cred_id
);
let resp = self
.http
.delete(&url)
.header("Authorization", auth)
.send()
.await?;
if resp.status().as_u16() == 204 {
return Ok(());
}
// Delegate error handling to parse_response; the Ok branch is unreachable.
let _: Credentials = parse_response(resp).await?;
Ok(())
}
}