Skip to main content

alibi_api/plugins/api_key/
config.rs

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