Skip to main content

ironflow_store/entities/
api_key.rs

1//! API key entity for machine-to-machine authentication.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::entities::api_key_scope::ApiKeyScope;
8
9/// A stored API key (hashed, never contains the raw secret).
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ApiKey {
12    /// Unique API key ID (UUID v7).
13    pub id: Uuid,
14    /// Owner user ID.
15    pub user_id: Uuid,
16    /// Human-readable name for this key.
17    pub name: String,
18    /// Argon2id hash of the raw key (never exposed).
19    #[serde(skip_serializing)]
20    pub key_hash: String,
21    /// First 8 characters of the raw key for identification.
22    pub key_prefix: String,
23    /// Scopes granted to this key.
24    pub scopes: Vec<ApiKeyScope>,
25    /// Whether this key is active.
26    pub is_active: bool,
27    /// Optional expiration date.
28    pub expires_at: Option<DateTime<Utc>>,
29    /// Last time this key was used.
30    pub last_used_at: Option<DateTime<Utc>>,
31    /// When the key was created.
32    pub created_at: DateTime<Utc>,
33    /// When the key was last updated.
34    pub updated_at: DateTime<Utc>,
35    /// Optional per-key rate limit override (requests per minute).
36    ///
37    /// When set, the API uses this value instead of the global rate limit
38    /// for requests authenticated with this key. `None` means use the
39    /// server default. `Some(0)` disables rate limiting for this key.
40    pub rate_limit_override: Option<u32>,
41}
42
43/// Parameters for creating a new API key.
44#[derive(Debug, Clone)]
45pub struct NewApiKey {
46    /// Owner user ID.
47    pub user_id: Uuid,
48    /// Human-readable name.
49    pub name: String,
50    /// Argon2id hash of the raw key.
51    pub key_hash: String,
52    /// First 8 characters of the raw key.
53    pub key_prefix: String,
54    /// Granted scopes.
55    pub scopes: Vec<ApiKeyScope>,
56    /// Optional expiration date.
57    pub expires_at: Option<DateTime<Utc>>,
58    /// Optional per-key rate limit override (requests per minute).
59    /// `None` uses the server default. `Some(0)` disables rate limiting.
60    pub rate_limit_override: Option<u32>,
61}
62
63/// Parameters for updating an API key.
64#[derive(Debug, Clone, Default)]
65pub struct ApiKeyUpdate {
66    /// New name.
67    pub name: Option<String>,
68    /// New scopes.
69    pub scopes: Option<Vec<ApiKeyScope>>,
70    /// New active status.
71    pub is_active: Option<bool>,
72    /// New expiration date. `Some(None)` removes expiration.
73    pub expires_at: Option<Option<DateTime<Utc>>>,
74    /// New rate limit override. `Some(None)` removes the override.
75    pub rate_limit_override: Option<Option<u32>>,
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn api_key_serde_excludes_hash() {
84        let key = ApiKey {
85            id: Uuid::now_v7(),
86            user_id: Uuid::now_v7(),
87            name: "test-key".to_string(),
88            key_hash: "secret_hash".to_string(),
89            key_prefix: "irfl_abc".to_string(),
90            scopes: vec![ApiKeyScope::RunsRead],
91            is_active: true,
92            expires_at: None,
93            last_used_at: None,
94            created_at: Utc::now(),
95            updated_at: Utc::now(),
96            rate_limit_override: None,
97        };
98
99        let json = serde_json::to_string(&key).expect("serialize");
100        assert!(!json.contains("secret_hash"));
101        assert!(json.contains("test-key"));
102        assert!(json.contains("irfl_abc"));
103    }
104
105    #[test]
106    fn api_key_update_default_is_empty() {
107        let update = ApiKeyUpdate::default();
108        assert!(update.name.is_none());
109        assert!(update.scopes.is_none());
110        assert!(update.is_active.is_none());
111        assert!(update.expires_at.is_none());
112        assert!(update.rate_limit_override.is_none());
113    }
114}