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