batata_client/auth/
credentials.rs1use base64::Engine;
2use hmac::{Hmac, Mac};
3use sha1::Sha1;
4
5use crate::common::current_time_millis;
6
7#[derive(Clone, Debug, Default)]
9pub struct Credentials {
10 pub username: Option<String>,
12 pub password: Option<String>,
14 pub access_key: Option<String>,
16 pub secret_key: Option<String>,
18}
19
20impl Credentials {
21 pub fn new() -> Self {
23 Self::default()
24 }
25
26 pub fn with_username_password(username: impl Into<String>, password: impl Into<String>) -> Self {
28 Self {
29 username: Some(username.into()),
30 password: Some(password.into()),
31 ..Default::default()
32 }
33 }
34
35 pub fn with_access_key(access_key: impl Into<String>, secret_key: impl Into<String>) -> Self {
37 Self {
38 access_key: Some(access_key.into()),
39 secret_key: Some(secret_key.into()),
40 ..Default::default()
41 }
42 }
43
44 pub fn is_configured(&self) -> bool {
46 self.has_basic_auth() || self.has_ak_sk_auth()
47 }
48
49 pub fn has_basic_auth(&self) -> bool {
51 self.username.is_some() && self.password.is_some()
52 }
53
54 pub fn has_ak_sk_auth(&self) -> bool {
56 self.access_key.is_some() && self.secret_key.is_some()
57 }
58
59 pub fn generate_signature(&self, resource: &str) -> Option<SignatureInfo> {
61 let access_key = self.access_key.as_ref()?;
62 let secret_key = self.secret_key.as_ref()?;
63
64 let timestamp = current_time_millis().to_string();
65 let sign_str = format!("{}+{}", resource, timestamp);
66
67 let mut mac = Hmac::<Sha1>::new_from_slice(secret_key.as_bytes()).ok()?;
69 mac.update(sign_str.as_bytes());
70 let result = mac.finalize();
71 let signature = base64::engine::general_purpose::STANDARD.encode(result.into_bytes());
72
73 Some(SignatureInfo {
74 access_key: access_key.clone(),
75 signature,
76 timestamp,
77 })
78 }
79}
80
81#[derive(Clone, Debug)]
83pub struct SignatureInfo {
84 pub access_key: String,
85 pub signature: String,
86 pub timestamp: String,
87}
88
89#[derive(Clone, Debug, Default)]
91pub struct AccessToken {
92 pub token: String,
94 pub expire_time: i64,
96 pub global_admin: bool,
98}
99
100impl AccessToken {
101 pub fn is_expired(&self) -> bool {
103 if self.token.is_empty() {
104 return true;
105 }
106 current_time_millis() >= self.expire_time - 30000
108 }
109
110 pub fn is_valid(&self) -> bool {
112 !self.token.is_empty() && !self.is_expired()
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_credentials_basic_auth() {
122 let creds = Credentials::with_username_password("admin", "password");
123 assert!(creds.has_basic_auth());
124 assert!(!creds.has_ak_sk_auth());
125 assert!(creds.is_configured());
126 }
127
128 #[test]
129 fn test_credentials_ak_sk_auth() {
130 let creds = Credentials::with_access_key("ak123", "sk456");
131 assert!(!creds.has_basic_auth());
132 assert!(creds.has_ak_sk_auth());
133 assert!(creds.is_configured());
134 }
135
136 #[test]
137 fn test_generate_signature() {
138 let creds = Credentials::with_access_key("test-ak", "test-sk");
139 let sig = creds.generate_signature("test-resource");
140 assert!(sig.is_some());
141 let sig = sig.unwrap();
142 assert_eq!(sig.access_key, "test-ak");
143 assert!(!sig.signature.is_empty());
144 assert!(!sig.timestamp.is_empty());
145 }
146
147 #[test]
148 fn test_access_token_expired() {
149 let token = AccessToken {
150 token: "test-token".to_string(),
151 expire_time: 0,
152 global_admin: false,
153 };
154 assert!(token.is_expired());
155 assert!(!token.is_valid());
156 }
157
158 #[test]
159 fn test_access_token_valid() {
160 let token = AccessToken {
161 token: "test-token".to_string(),
162 expire_time: current_time_millis() + 60000,
163 global_admin: false,
164 };
165 assert!(!token.is_expired());
166 assert!(token.is_valid());
167 }
168}