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 #[must_use]
91 pub fn api_key(key: impl Into<String>) -> Self {
92 Self::ApiKey(ApiKeySecret(key.into()))
93 }
94
95 pub fn parse(secret: &str) -> Result<Self, CredentialError> {
96 let Ok(value) = serde_json::from_str::<serde_json::Value>(secret) else {
97 return Ok(Self::ApiKey(ApiKeySecret(secret.to_owned())));
98 };
99 if value.get("type").and_then(serde_json::Value::as_str) != Some(SERVICE_ACCOUNT_TYPE) {
100 return Ok(Self::ApiKey(ApiKeySecret(secret.to_owned())));
101 }
102 serde_json::from_value::<ServiceAccountKey>(value)
103 .map(|key| Self::GoogleServiceAccount(Box::new(key)))
104 .map_err(|e| CredentialError::Malformed(e.to_string()))
105 }
106
107 #[must_use]
108 pub const fn kind(&self) -> CredentialKind {
109 match self {
110 Self::ApiKey(_) => CredentialKind::ApiKey,
111 Self::GoogleServiceAccount(_) => CredentialKind::GoogleServiceAccount,
112 }
113 }
114
115 #[must_use]
116 pub fn scope(&self) -> CredentialScope {
117 match self {
118 Self::ApiKey(_) => CredentialScope::empty(),
119 Self::GoogleServiceAccount(key) => CredentialScope {
120 project: Some(key.project_id.clone()),
121 region: None,
122 principal: Some(key.client_email.clone()),
123 },
124 }
125 }
126
127 pub async fn bearer(&self, cache_key: &str) -> Result<AuthHeader, CredentialError> {
128 match self {
129 Self::ApiKey(key) => Ok(AuthHeader {
130 scheme: AuthScheme::ApiKey,
131 value: key.expose().to_owned(),
132 }),
133 Self::GoogleServiceAccount(key) => {
134 let key_id = format!("{}:{cache_key}", self.kind().as_str());
135 Ok(AuthHeader {
136 scheme: AuthScheme::Bearer,
137 value: access_token(&key_id, key).await?,
138 })
139 },
140 }
141 }
142
143 pub fn fill_endpoint(&self, template: &str) -> Result<String, CredentialError> {
144 fill_endpoint(template, &self.scope())
145 }
146}