1use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6
7use crate::auth::credential::AuthCredential;
8use crate::auth::scheme::AuthScheme;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct AuthConfig {
15 pub auth_scheme: AuthScheme,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub raw_auth_credential: Option<AuthCredential>,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub exchanged_auth_credential: Option<AuthCredential>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub credential_key: Option<String>,
27}
28
29impl AuthConfig {
30 #[must_use]
32 pub fn new(auth_scheme: AuthScheme) -> Self {
33 Self {
34 auth_scheme,
35 raw_auth_credential: None,
36 exchanged_auth_credential: None,
37 credential_key: None,
38 }
39 }
40
41 #[must_use]
43 pub fn with_raw(mut self, raw: AuthCredential) -> Self {
44 self.raw_auth_credential = Some(raw);
45 self
46 }
47
48 #[must_use]
50 pub fn with_key(mut self, key: impl Into<String>) -> Self {
51 self.credential_key = Some(key.into());
52 self
53 }
54
55 #[must_use]
59 pub fn resolve_credential_key(&self) -> String {
60 if let Some(k) = &self.credential_key {
61 return k.clone();
62 }
63 let mut h = Sha256::new();
64 h.update(self.auth_scheme.kind().as_bytes());
65 h.update(b":");
66 if let Ok(j) = serde_json::to_vec(&self.auth_scheme) {
67 h.update(&j);
68 }
69 h.update(b":");
70 if let Some(raw) = &self.raw_auth_credential {
71 if let Ok(j) = serde_json::to_vec(raw) {
72 h.update(&j);
73 }
74 }
75 let digest = h.finalize();
76 let mut hex = String::with_capacity(digest.len() * 2);
77 for b in digest.iter() {
78 use std::fmt::Write as _;
79 let _ = write!(&mut hex, "{b:02x}");
80 }
81 format!("{}_{}", self.auth_scheme.kind(), &hex[..16])
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::auth::credential::AuthCredential;
89 use crate::auth::scheme::ApiKeyLocation;
90
91 #[test]
92 fn key_is_deterministic_for_same_inputs() {
93 let cfg = AuthConfig::new(AuthScheme::ApiKey {
94 location: ApiKeyLocation::Header,
95 name: "X-API-Key".into(),
96 description: None,
97 })
98 .with_raw(AuthCredential::api_key("secret"));
99 let k1 = cfg.resolve_credential_key();
100 let k2 = cfg.resolve_credential_key();
101 assert_eq!(k1, k2);
102 assert!(k1.starts_with("api_key_"));
103 }
104
105 #[test]
106 fn explicit_key_overrides() {
107 let cfg = AuthConfig::new(AuthScheme::ApiKey {
108 location: ApiKeyLocation::Header,
109 name: "X-API-Key".into(),
110 description: None,
111 })
112 .with_key("explicit");
113 assert_eq!(cfg.resolve_credential_key(), "explicit");
114 }
115}