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(Debug, serde::Deserialize)]
40struct SelfDescription {
41 #[serde(rename = "type")]
42 kind: Option<String>,
43}
44
45#[derive(Clone, PartialEq, Eq)]
48pub struct ApiKeySecret(String);
49
50impl ApiKeySecret {
51 #[must_use]
52 pub fn expose(&self) -> &str {
53 &self.0
54 }
55}
56
57impl fmt::Debug for ApiKeySecret {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.write_str("ApiKeySecret(<redacted>)")
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum CredentialKind {
69 ApiKey,
70 GoogleServiceAccount,
71}
72
73impl CredentialKind {
74 #[must_use]
75 pub const fn as_str(self) -> &'static str {
76 match self {
77 Self::ApiKey => "api_key",
78 Self::GoogleServiceAccount => "google_service_account",
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
90pub enum ProviderCredential {
91 ApiKey(ApiKeySecret),
92 GoogleServiceAccount(Box<ServiceAccountKey>),
93}
94
95impl ProviderCredential {
96 pub fn parse(secret: &str) -> Result<Self, CredentialError> {
97 let self_described = serde_json::from_str::<SelfDescription>(secret)
98 .is_ok_and(|probe| probe.kind.as_deref() == Some(SERVICE_ACCOUNT_TYPE));
99 if !self_described {
100 return Ok(Self::ApiKey(ApiKeySecret(secret.to_owned())));
101 }
102 serde_json::from_str::<ServiceAccountKey>(secret)
103 .map(|key| Self::GoogleServiceAccount(Box::new(key)))
104 .map_err(CredentialError::Malformed)
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}