1use crate::crypto::hashing::Salt;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ApiKey {
11 pub id: String,
13 pub name: String,
15 pub key: String,
17 pub secret_hash: Option<String>,
19 pub salt: Option<Salt>,
21 pub role: Role,
23 pub created_at: DateTime<Utc>,
25 pub expires_at: Option<DateTime<Utc>>,
27 pub last_used: Option<DateTime<Utc>>,
29 #[serde(default)]
31 pub ip_whitelist: Vec<String>,
32 pub active: bool,
34 #[serde(default)]
36 pub usage_count: u64,
37}
38
39impl ApiKey {
40 pub fn new(
42 name: String,
43 role: Role,
44 expires_at: Option<DateTime<Utc>>,
45 ip_whitelist: Vec<String>,
46 ) -> Self {
47 use crate::crypto::hashing::{generate_salt, hash_api_key};
48 use crate::crypto::keys::{generate_key_id, generate_secure_key};
49
50 let role_str = match &role {
51 Role::Admin => "admin",
52 Role::Operator => "op",
53 Role::Monitor => "mon",
54 Role::Device { .. } => "dev",
55 Role::Custom { .. } => "custom",
56 };
57
58 let id = generate_key_id(role_str);
59 let secret = generate_secure_key();
60
61 let salt = generate_salt();
63 let secret_hash = hash_api_key(&secret, &salt);
64
65 Self {
66 id,
67 name,
68 key: secret,
69 secret_hash: Some(secret_hash),
70 salt: Some(salt),
71 role,
72 created_at: Utc::now(),
73 expires_at,
74 last_used: None,
75 ip_whitelist,
76 active: true,
77 usage_count: 0,
78 }
79 }
80
81 pub fn is_expired(&self) -> bool {
83 if let Some(expires_at) = self.expires_at {
84 Utc::now() > expires_at
85 } else {
86 false
87 }
88 }
89
90 pub fn is_valid(&self) -> bool {
92 self.active && !self.is_expired()
93 }
94
95 pub fn mark_used(&mut self) {
97 self.last_used = Some(Utc::now());
98 self.usage_count += 1;
99 }
100
101 pub fn verify_key(
103 &self,
104 provided_key: &str,
105 ) -> Result<bool, crate::crypto::hashing::HashingError> {
106 use crate::crypto::hashing::verify_api_key;
107
108 if let (Some(ref hash), Some(ref salt)) = (&self.secret_hash, &self.salt) {
109 verify_api_key(provided_key, hash, salt)
110 } else {
111 Ok(provided_key == self.key)
113 }
114 }
115
116 pub fn to_secure_storage(&self) -> SecureApiKey {
118 SecureApiKey {
119 id: self.id.clone(),
120 name: self.name.clone(),
121 secret_hash: self.secret_hash.clone(),
122 salt: self.salt.clone(),
123 role: self.role.clone(),
124 created_at: self.created_at,
125 expires_at: self.expires_at,
126 last_used: self.last_used,
127 ip_whitelist: self.ip_whitelist.clone(),
128 active: self.active,
129 usage_count: self.usage_count,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct SecureApiKey {
137 pub id: String,
139 pub name: String,
141 pub secret_hash: Option<String>,
143 pub salt: Option<Salt>,
145 pub role: Role,
147 pub created_at: DateTime<Utc>,
149 pub expires_at: Option<DateTime<Utc>>,
151 pub last_used: Option<DateTime<Utc>>,
153 #[serde(default)]
155 pub ip_whitelist: Vec<String>,
156 pub active: bool,
158 #[serde(default)]
160 pub usage_count: u64,
161}
162
163impl SecureApiKey {
164 pub fn to_api_key(&self) -> ApiKey {
166 ApiKey {
167 id: self.id.clone(),
168 name: self.name.clone(),
169 key: "***redacted***".to_string(), secret_hash: self.secret_hash.clone(),
171 salt: self.salt.clone(),
172 role: self.role.clone(),
173 created_at: self.created_at,
174 expires_at: self.expires_at,
175 last_used: self.last_used,
176 ip_whitelist: self.ip_whitelist.clone(),
177 active: self.active,
178 usage_count: self.usage_count,
179 }
180 }
181
182 pub fn is_expired(&self) -> bool {
184 if let Some(expires_at) = self.expires_at {
185 Utc::now() > expires_at
186 } else {
187 false
188 }
189 }
190
191 pub fn is_valid(&self) -> bool {
193 self.active && !self.is_expired()
194 }
195
196 pub fn verify_key(
198 &self,
199 provided_key: &str,
200 ) -> Result<bool, crate::crypto::hashing::HashingError> {
201 use crate::crypto::hashing::verify_api_key;
202
203 if let (Some(ref hash), Some(ref salt)) = (&self.secret_hash, &self.salt) {
204 verify_api_key(provided_key, hash, salt)
205 } else {
206 Ok(false)
208 }
209 }
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
214#[serde(rename_all = "lowercase")]
215pub enum Role {
216 Admin,
218 Operator,
220 Monitor,
222 Device {
224 allowed_devices: Vec<String>,
226 },
227 Custom {
229 permissions: Vec<String>,
231 },
232}
233
234impl Role {
235 pub fn has_permission(&self, permission: &str) -> bool {
237 match self {
238 Role::Admin => true, Role::Operator => !permission.starts_with("admin."), Role::Monitor => permission.starts_with("read.") || permission == "health.check",
241 Role::Device { allowed_devices } => {
242 if let Some(device_uuid) = permission.strip_prefix("device.") {
244 allowed_devices.contains(&device_uuid.to_string())
245 } else {
246 false
247 }
248 }
249 Role::Custom { permissions } => permissions.contains(&permission.to_string()),
250 }
251 }
252
253 pub fn description(&self) -> String {
255 match self {
256 Role::Admin => "Full administrative access".to_string(),
257 Role::Operator => "Device control and monitoring".to_string(),
258 Role::Monitor => "Read-only system monitoring".to_string(),
259 Role::Device { allowed_devices } => {
260 format!("Device control for {} devices", allowed_devices.len())
261 }
262 Role::Custom { permissions } => {
263 format!("Custom role with {} permissions", permissions.len())
264 }
265 }
266 }
267}
268
269impl fmt::Display for Role {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 match self {
272 Role::Admin => write!(f, "admin"),
273 Role::Operator => write!(f, "operator"),
274 Role::Monitor => write!(f, "monitor"),
275 Role::Device { .. } => write!(f, "device"),
276 Role::Custom { .. } => write!(f, "custom"),
277 }
278 }
279}
280
281#[derive(Debug)]
283pub struct AuthResult {
284 pub success: bool,
285 pub user_id: Option<String>,
286 pub roles: Vec<Role>,
287 pub message: Option<String>,
288 pub rate_limited: bool,
290 pub client_ip: Option<String>,
292}
293
294impl AuthResult {
295 pub fn success(user_id: String, roles: Vec<Role>) -> Self {
297 Self {
298 success: true,
299 user_id: Some(user_id),
300 roles,
301 message: None,
302 rate_limited: false,
303 client_ip: None,
304 }
305 }
306
307 pub fn failure(message: String) -> Self {
309 Self {
310 success: false,
311 user_id: None,
312 roles: vec![],
313 message: Some(message),
314 rate_limited: false,
315 client_ip: None,
316 }
317 }
318
319 pub fn rate_limited(client_ip: String) -> Self {
321 Self {
322 success: false,
323 user_id: None,
324 roles: vec![],
325 message: Some("Too many failed attempts".to_string()),
326 rate_limited: true,
327 client_ip: Some(client_ip),
328 }
329 }
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct AuthContext {
335 pub user_id: Option<String>,
336 pub roles: Vec<Role>,
337 pub api_key_id: Option<String>,
338 pub permissions: Vec<String>,
340}
341
342impl AuthContext {
343 pub fn has_permission(&self, permission: &str) -> bool {
345 self.roles
346 .iter()
347 .any(|role| role.has_permission(permission))
348 }
349
350 pub fn get_all_permissions(&self) -> Vec<String> {
352 self.permissions.clone()
353 }
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct KeyCreationRequest {
359 pub name: String,
361 pub role: Role,
363 pub expires_at: Option<DateTime<Utc>>,
365 pub ip_whitelist: Option<Vec<String>>,
367}
368
369#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
371pub struct KeyUsageStats {
372 pub total_keys: u32,
374 pub active_keys: u32,
376 pub disabled_keys: u32,
378 pub expired_keys: u32,
380 pub total_usage_count: u64,
382 pub admin_keys: u32,
384 pub operator_keys: u32,
386 pub monitor_keys: u32,
388 pub device_keys: u32,
390 pub custom_keys: u32,
392}
393
394#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
396pub struct ApiCompletenessCheck {
397 pub has_create_key: bool,
399 pub has_validate_key: bool,
401 pub has_list_keys: bool,
403 pub has_revoke_key: bool,
405 pub has_update_key: bool,
407 pub has_bulk_operations: bool,
409 pub has_role_based_access: bool,
411 pub has_rate_limiting: bool,
413 pub has_ip_whitelisting: bool,
415 pub has_expiration_support: bool,
417 pub has_usage_tracking: bool,
419 pub framework_version: String,
421 pub production_ready: bool,
423}