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}
36
37/// Parameters for creating a new API key.
38#[derive(Debug, Clone)]
39pub struct NewApiKey {
40    /// Owner user ID.
41    pub user_id: Uuid,
42    /// Human-readable name.
43    pub name: String,
44    /// Argon2id hash of the raw key.
45    pub key_hash: String,
46    /// First 8 characters of the raw key.
47    pub key_prefix: String,
48    /// Granted scopes.
49    pub scopes: Vec<ApiKeyScope>,
50    /// Optional expiration date.
51    pub expires_at: Option<DateTime<Utc>>,
52}
53
54/// Parameters for updating an API key.
55#[derive(Debug, Clone, Default)]
56pub struct ApiKeyUpdate {
57    /// New name.
58    pub name: Option<String>,
59    /// New scopes.
60    pub scopes: Option<Vec<ApiKeyScope>>,
61    /// New active status.
62    pub is_active: Option<bool>,
63    /// New expiration date. `Some(None)` removes expiration.
64    pub expires_at: Option<Option<DateTime<Utc>>>,
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn api_key_serde_excludes_hash() {
73        let key = ApiKey {
74            id: Uuid::now_v7(),
75            user_id: Uuid::now_v7(),
76            name: "test-key".to_string(),
77            key_hash: "secret_hash".to_string(),
78            key_prefix: "irfl_abc".to_string(),
79            scopes: vec![ApiKeyScope::RunsRead],
80            is_active: true,
81            expires_at: None,
82            last_used_at: None,
83            created_at: Utc::now(),
84            updated_at: Utc::now(),
85        };
86
87        let json = serde_json::to_string(&key).expect("serialize");
88        assert!(!json.contains("secret_hash"));
89        assert!(json.contains("test-key"));
90        assert!(json.contains("irfl_abc"));
91    }
92
93    #[test]
94    fn api_key_update_default_is_empty() {
95        let update = ApiKeyUpdate::default();
96        assert!(update.name.is_none());
97        assert!(update.scopes.is_none());
98        assert!(update.is_active.is_none());
99        assert!(update.expires_at.is_none());
100    }
101}