systemprompt_security/credential/
mod.rs1pub mod cache;
26mod error;
27pub(crate) mod http;
28mod scope;
29
30use std::fmt;
31
32pub use error::CredentialError;
33pub use scope::{
34 AuthHeader, AuthScheme, CredentialScope, PROJECT_PLACEHOLDER, REGION_PLACEHOLDER, fill_endpoint,
35};
36
37use crate::google::{SERVICE_ACCOUNT_TYPE, ServiceAccountKey, access_token};
38
39#[derive(Clone, PartialEq, Eq)]
42pub struct ApiKeySecret(String);
43
44impl ApiKeySecret {
45 #[must_use]
46 pub fn expose(&self) -> &str {
47 &self.0
48 }
49}
50
51impl fmt::Debug for ApiKeySecret {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.write_str("ApiKeySecret(<redacted>)")
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum CredentialKind {
63 ApiKey,
64 GoogleServiceAccount,
65}
66
67impl CredentialKind {
68 #[must_use]
69 pub const fn as_str(self) -> &'static str {
70 match self {
71 Self::ApiKey => "api_key",
72 Self::GoogleServiceAccount => "google_service_account",
73 }
74 }
75}
76
77#[derive(Debug, Clone)]
84pub enum ProviderCredential {
85 ApiKey(ApiKeySecret),
86 GoogleServiceAccount(Box<ServiceAccountKey>),
87}
88
89impl ProviderCredential {
90 pub fn parse(secret: &str) -> Result<Self, CredentialError> {
91 let Ok(value) = serde_json::from_str::<serde_json::Value>(secret) else {
92 return Ok(Self::ApiKey(ApiKeySecret(secret.to_owned())));
93 };
94 if value.get("type").and_then(serde_json::Value::as_str) != Some(SERVICE_ACCOUNT_TYPE) {
95 return Ok(Self::ApiKey(ApiKeySecret(secret.to_owned())));
96 }
97 serde_json::from_value::<ServiceAccountKey>(value)
98 .map(|key| Self::GoogleServiceAccount(Box::new(key)))
99 .map_err(|e| CredentialError::Malformed(e.to_string()))
100 }
101
102 #[must_use]
103 pub const fn kind(&self) -> CredentialKind {
104 match self {
105 Self::ApiKey(_) => CredentialKind::ApiKey,
106 Self::GoogleServiceAccount(_) => CredentialKind::GoogleServiceAccount,
107 }
108 }
109
110 #[must_use]
111 pub fn scope(&self) -> CredentialScope {
112 match self {
113 Self::ApiKey(_) => CredentialScope::empty(),
114 Self::GoogleServiceAccount(key) => CredentialScope {
115 project: Some(key.project_id.clone()),
116 region: None,
117 principal: Some(key.client_email.clone()),
118 },
119 }
120 }
121
122 pub async fn bearer(&self, cache_key: &str) -> Result<AuthHeader, CredentialError> {
123 match self {
124 Self::ApiKey(key) => Ok(AuthHeader {
125 scheme: AuthScheme::ApiKey,
126 value: key.expose().to_owned(),
127 }),
128 Self::GoogleServiceAccount(key) => {
129 let key_id = format!("{}:{cache_key}", self.kind().as_str());
130 Ok(AuthHeader {
131 scheme: AuthScheme::Bearer,
132 value: access_token(&key_id, key).await?,
133 })
134 },
135 }
136 }
137
138 pub fn fill_endpoint(&self, template: &str) -> Result<String, CredentialError> {
139 fill_endpoint(template, &self.scope())
140 }
141}