Skip to main content

alibi_plugins/api_key/
config.rs

1use super::callbacks::{
2    ApiKeyDefaultPermissions, ApiKeyGenerator, ApiKeyGetter, ApiKeyPermissions, ApiKeyValidator,
3};
4use super::storage::{ApiKeyStorage, ApiKeyStorageMode};
5use std::sync::Arc;
6
7/// Which kind of entity a configuration's keys belong to.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum ApiKeyReferences {
10    /// Keys are owned by the signed-in user.
11    #[default]
12    User,
13    /// Keys are owned by an organization; callers pass `organizationId` and
14    /// must hold the matching `apiKey` permission in that organization.
15    Organization,
16}
17
18/// Configuration for the API Key plugin, aligned with the TypeScript `ApiKeyOptions`.
19#[derive(Clone)]
20#[expect(
21    clippy::struct_excessive_bools,
22    reason = "Independent configuration switches model distinct upstream behavior, rather than mutually exclusive states"
23)]
24pub struct ApiKeyConfig {
25    /// Name of this configuration, stored on every key it creates.
26    /// Upstream defaults it to `"default"`.
27    pub config_id: String,
28    /// Whether keys from this configuration belong to a user or to an
29    /// organization.
30    pub references: ApiKeyReferences,
31
32    /// Database persistence (default) or application-owned secondary storage.
33    pub storage: ApiKeyStorageMode,
34    /// Use database rows for durable writes and admission in secondary mode.
35    pub fallback_to_database: bool,
36    /// Shared secondary cache for this plugin, independent of session persistence.
37    pub secondary_storage: Option<Arc<dyn alibi_core::store::CacheAdapter>>,
38    /// Application storage overriding the plugin's secondary cache.
39    pub custom_storage: Option<Arc<dyn ApiKeyStorage>>,
40
41    /// Raw length excluding the prefix. Zero and NaN use 64; the built-in
42    /// generator admits safely terminating fractions from 0.5 upward.
43    /// A custom generator receives the number unchanged after that fallback.
44    pub key_length: f64,
45    pub prefix: Option<String>,
46    /// Permissions applied when creation does not supply explicit permissions.
47    pub default_permissions: Option<ApiKeyPermissions>,
48    /// Optional application generator, receiving only length and effective prefix.
49    pub custom_key_generator: Option<Arc<dyn ApiKeyGenerator>>,
50    /// Dynamic defaults replace static `default_permissions` when configured.
51    pub default_permissions_callback: Option<Arc<dyn ApiKeyDefaultPermissions>>,
52
53    pub api_key_headers: Vec<String>,
54    /// Trusted application lookup, replacing `api_key_headers` when configured.
55    pub custom_api_key_getter: Option<Arc<dyn ApiKeyGetter>>,
56    /// Trusted acceptance predicate, checked before quota and rate-limit writes.
57    pub custom_api_key_validator: Option<Arc<dyn ApiKeyValidator>>,
58
59    pub disable_key_hashing: bool,
60
61    /// UTF-16 substring end: finite fractions truncate, negative/NaN select an
62    /// empty prefix, and positive infinity selects the whole credential.
63    /// SQLite retains a cut through a surrogate pair as actual WTF-8 TEXT.
64    pub starting_characters_length: f64,
65    pub store_starting_characters: bool,
66
67    /// Compare the actual UTF-16 prefix length against this raw number.
68    pub max_prefix_length: f64,
69    /// Compare the actual UTF-16 prefix length against this raw number.
70    pub min_prefix_length: f64,
71
72    /// Compare the actual UTF-16 name length against this raw number.
73    pub max_name_length: f64,
74    /// Compare the actual UTF-16 name length against this raw number.
75    pub min_name_length: f64,
76    pub require_name: bool,
77
78    pub enable_metadata: bool,
79
80    pub key_expiration: KeyExpirationConfig,
81
82    pub rate_limit: RateLimitDefaults,
83
84    pub enable_session_for_api_keys: bool,
85    /// Start secondary-only usage merges and automatic cleanup in background work.
86    /// The application background handler receives completion observations.
87    /// Successful trusted verification launches cleanup only when enabled.
88    /// Database quota and rate-limit admission remain atomic and awaited.
89    pub defer_updates: bool,
90}
91
92impl ApiKeyConfig {
93    pub(in crate::api_key) const fn normalized(mut self) -> Self {
94        // Upstream resolves defaultKeyLength using JavaScript's `|| 64`.
95        if self.key_length == 0.0 || self.key_length.is_nan() {
96            self.key_length = 64.0;
97        }
98        self
99    }
100}
101
102impl std::fmt::Debug for ApiKeyConfig {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        f.debug_struct("ApiKeyConfig")
105            .field("config_id", &self.config_id)
106            .field("references", &self.references)
107            .field("key_length", &self.key_length)
108            .field("prefix", &self.prefix)
109            .field("custom_key_generator", &self.custom_key_generator.is_some())
110            .field(
111                "default_permissions_callback",
112                &self.default_permissions_callback.is_some(),
113            )
114            .field("api_key_headers", &self.api_key_headers)
115            .field(
116                "custom_api_key_getter",
117                &self.custom_api_key_getter.is_some(),
118            )
119            .field(
120                "custom_api_key_validator",
121                &self.custom_api_key_validator.is_some(),
122            )
123            .field(
124                "enable_session_for_api_keys",
125                &self.enable_session_for_api_keys,
126            )
127            .field("storage", &self.storage)
128            .field("fallback_to_database", &self.fallback_to_database)
129            .field("secondary_storage", &self.secondary_storage.is_some())
130            .field("custom_storage", &self.custom_storage.is_some())
131            .field("defer_updates", &self.defer_updates)
132            .finish_non_exhaustive()
133    }
134}
135
136/// Key expiration constraints.
137#[derive(Debug, Clone)]
138pub struct KeyExpirationConfig {
139    /// Raw default `expiresIn` in seconds when none is provided. Zero/NaN omit
140    /// expiry; finite fractions retain milliseconds, and invalid dates reject
141    /// creation after generation and before the database write.
142    pub default_expires_in: Option<f64>,
143    /// If true, clients cannot set a custom `expiresIn`.
144    pub disable_custom_expires_time: bool,
145    /// Maximum `expiresIn` in **days**.
146    pub max_expires_in: f64,
147    /// Minimum `expiresIn` in **days**.
148    pub min_expires_in: f64,
149}
150
151impl Default for KeyExpirationConfig {
152    fn default() -> Self {
153        Self {
154            default_expires_in: None,
155            disable_custom_expires_time: false,
156            max_expires_in: 365.0,
157            min_expires_in: 1.0,
158        }
159    }
160}
161
162/// Global rate-limit defaults applied to newly-created keys.
163#[derive(Debug, Clone)]
164pub struct RateLimitDefaults {
165    pub enabled: bool,
166    /// Default time window in milliseconds.
167    pub time_window: f64,
168    /// Default max requests per window.
169    pub max_requests: f64,
170}
171
172impl Default for RateLimitDefaults {
173    fn default() -> Self {
174        Self {
175            enabled: true,
176            time_window: 86_400_000.0, // 24 hours
177            max_requests: 10.0,
178        }
179    }
180}
181
182impl Default for ApiKeyConfig {
183    fn default() -> Self {
184        Self {
185            storage: ApiKeyStorageMode::Database,
186            fallback_to_database: false,
187            custom_storage: None,
188            secondary_storage: None,
189            config_id: "default".to_owned(),
190            references: ApiKeyReferences::default(),
191            key_length: 64.0,
192            prefix: None,
193            default_permissions: None,
194            custom_key_generator: None,
195            default_permissions_callback: None,
196            api_key_headers: vec!["x-api-key".to_owned()],
197            custom_api_key_getter: None,
198            custom_api_key_validator: None,
199            disable_key_hashing: false,
200            starting_characters_length: 6.0,
201            store_starting_characters: true,
202            max_prefix_length: 32.0,
203            min_prefix_length: 1.0,
204            max_name_length: 32.0,
205            min_name_length: 1.0,
206            require_name: false,
207            enable_metadata: false,
208            key_expiration: KeyExpirationConfig::default(),
209            rate_limit: RateLimitDefaults::default(),
210            enable_session_for_api_keys: false,
211            defer_updates: false,
212        }
213    }
214}