1use std::collections::{BTreeMap, HashSet};
2use std::fmt;
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use rustauth_core::context::AuthContext;
8use rustauth_core::error::RustAuthError;
9use rustauth_core::options::SecondaryStorage;
10use rustauth_core::plugin::PluginRequest;
11use serde::{Deserialize, Serialize};
12use time::Duration;
13
14mod duration_serde {
15 use serde::{Deserialize, Deserializer, Serialize, Serializer};
16 use time::Duration;
17
18 pub mod as_millis {
19 use super::*;
20
21 pub fn serialize<S: Serializer>(
22 duration: &Duration,
23 serializer: S,
24 ) -> Result<S::Ok, S::Error> {
25 duration.whole_milliseconds().serialize(serializer)
26 }
27
28 pub fn deserialize<'de, D: Deserializer<'de>>(
29 deserializer: D,
30 ) -> Result<Duration, D::Error> {
31 let millis = i64::deserialize(deserializer)?;
32 Ok(Duration::milliseconds(millis))
33 }
34 }
35
36 pub mod as_secs_optional {
37 use super::*;
38
39 pub fn serialize<S: Serializer>(
40 duration: &Option<Duration>,
41 serializer: S,
42 ) -> Result<S::Ok, S::Error> {
43 match duration {
44 Some(value) => value.whole_seconds().serialize(serializer),
45 None => serializer.serialize_none(),
46 }
47 }
48
49 pub fn deserialize<'de, D: Deserializer<'de>>(
50 deserializer: D,
51 ) -> Result<Option<Duration>, D::Error> {
52 let seconds = Option::<i64>::deserialize(deserializer)?;
53 Ok(seconds.map(Duration::seconds))
54 }
55 }
56}
57
58pub type ApiKeyPermissions = BTreeMap<String, Vec<String>>;
59pub type ApiKeyGeneratorFuture =
60 Pin<Box<dyn Future<Output = Result<String, RustAuthError>> + Send + 'static>>;
61pub type ApiKeyGenerator = Arc<dyn Fn(ApiKeyGeneratorInput) -> ApiKeyGeneratorFuture + Send + Sync>;
62pub type ApiKeyGetterFuture<'a> =
63 Pin<Box<dyn Future<Output = Result<Option<String>, RustAuthError>> + Send + 'a>>;
64pub type ApiKeyGetter =
65 Arc<dyn for<'a> Fn(&'a AuthContext, &'a PluginRequest) -> ApiKeyGetterFuture<'a> + Send + Sync>;
66pub type ApiKeyValidatorFuture<'a> =
67 Pin<Box<dyn Future<Output = Result<bool, RustAuthError>> + Send + 'a>>;
68pub type ApiKeyValidator =
69 Arc<dyn for<'a> Fn(&'a AuthContext, &'a str) -> ApiKeyValidatorFuture<'a> + Send + Sync>;
70pub type DefaultPermissionsFuture<'a> =
71 Pin<Box<dyn Future<Output = Result<Option<ApiKeyPermissions>, RustAuthError>> + Send + 'a>>;
72pub type DefaultPermissionsResolver =
73 Arc<dyn for<'a> Fn(&'a AuthContext, &'a str) -> DefaultPermissionsFuture<'a> + Send + Sync>;
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct ApiKeyGeneratorInput {
77 pub length: usize,
78 pub prefix: Option<String>,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub enum ApiKeyStorageMode {
84 Database,
85 SecondaryStorage,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub enum ApiKeyReference {
91 User,
92 Organization,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct ApiKeyRateLimitOptions {
98 pub enabled: bool,
99 #[serde(with = "duration_serde::as_millis")]
100 pub time_window: Duration,
101 pub max_requests: i64,
102}
103
104impl Default for ApiKeyRateLimitOptions {
105 fn default() -> Self {
106 Self {
107 enabled: true,
108 time_window: Duration::days(1),
109 max_requests: 10,
110 }
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct ApiKeyExpirationOptions {
117 #[serde(with = "duration_serde::as_secs_optional")]
118 pub default_expires_in: Option<Duration>,
119 pub disable_custom_expires_time: bool,
120 pub min_expires_in_days: i64,
121 pub max_expires_in_days: i64,
122}
123
124impl Default for ApiKeyExpirationOptions {
125 fn default() -> Self {
126 Self {
127 default_expires_in: None,
128 disable_custom_expires_time: false,
129 min_expires_in_days: 1,
130 max_expires_in_days: 365,
131 }
132 }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136#[serde(rename_all = "camelCase")]
137pub struct StartingCharactersConfig {
138 pub should_store: bool,
139 pub characters_length: usize,
140}
141
142impl Default for StartingCharactersConfig {
143 fn default() -> Self {
144 Self {
145 should_store: true,
146 characters_length: 6,
147 }
148 }
149}
150
151#[derive(Clone, Serialize, Deserialize)]
152#[serde(default, rename_all = "camelCase")]
153pub struct ApiKeyConfiguration {
154 pub config_id: Option<String>,
155 pub api_key_headers: Vec<String>,
156 pub disable_key_hashing: bool,
157 pub default_key_length: usize,
158 pub default_prefix: Option<String>,
159 pub maximum_prefix_length: usize,
160 pub minimum_prefix_length: usize,
161 pub require_name: bool,
162 pub maximum_name_length: usize,
163 pub minimum_name_length: usize,
164 pub enable_metadata: bool,
165 pub key_expiration: ApiKeyExpirationOptions,
166 pub rate_limit: ApiKeyRateLimitOptions,
167 pub enable_session_for_api_keys: bool,
168 pub default_permissions: Option<ApiKeyPermissions>,
169 #[serde(skip)]
170 pub default_permissions_resolver: Option<DefaultPermissionsResolver>,
171 #[serde(skip)]
172 pub custom_key_generator: Option<ApiKeyGenerator>,
173 #[serde(skip)]
174 pub custom_api_key_getter: Option<ApiKeyGetter>,
175 #[serde(skip)]
176 pub custom_api_key_validator: Option<ApiKeyValidator>,
177 pub storage: ApiKeyStorageMode,
178 pub fallback_to_database: bool,
179 pub revalidate_secondary_against_database: bool,
187 #[serde(skip)]
188 pub custom_storage: Option<Arc<dyn SecondaryStorage>>,
189 pub defer_updates: bool,
190 pub reference: ApiKeyReference,
191 pub starting_characters: StartingCharactersConfig,
192}
193
194impl Default for ApiKeyConfiguration {
195 fn default() -> Self {
196 Self {
197 config_id: None,
198 api_key_headers: vec!["x-api-key".to_owned()],
199 disable_key_hashing: false,
200 default_key_length: 64,
201 default_prefix: None,
202 maximum_prefix_length: 32,
203 minimum_prefix_length: 1,
204 require_name: false,
205 maximum_name_length: 32,
206 minimum_name_length: 1,
207 enable_metadata: false,
208 key_expiration: ApiKeyExpirationOptions::default(),
209 rate_limit: ApiKeyRateLimitOptions::default(),
210 enable_session_for_api_keys: false,
211 default_permissions: None,
212 default_permissions_resolver: None,
213 custom_key_generator: None,
214 custom_api_key_getter: None,
215 custom_api_key_validator: None,
216 storage: ApiKeyStorageMode::Database,
217 fallback_to_database: false,
218 revalidate_secondary_against_database: false,
219 custom_storage: None,
220 defer_updates: false,
221 reference: ApiKeyReference::User,
222 starting_characters: StartingCharactersConfig::default(),
223 }
224 }
225}
226
227impl fmt::Debug for ApiKeyConfiguration {
228 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
229 formatter
230 .debug_struct("ApiKeyConfiguration")
231 .field("config_id", &self.config_id)
232 .field("api_key_headers", &self.api_key_headers)
233 .field("disable_key_hashing", &self.disable_key_hashing)
234 .field("default_key_length", &self.default_key_length)
235 .field("default_prefix", &self.default_prefix)
236 .field("maximum_prefix_length", &self.maximum_prefix_length)
237 .field("minimum_prefix_length", &self.minimum_prefix_length)
238 .field("require_name", &self.require_name)
239 .field("maximum_name_length", &self.maximum_name_length)
240 .field("minimum_name_length", &self.minimum_name_length)
241 .field("enable_metadata", &self.enable_metadata)
242 .field("key_expiration", &self.key_expiration)
243 .field("rate_limit", &self.rate_limit)
244 .field(
245 "enable_session_for_api_keys",
246 &self.enable_session_for_api_keys,
247 )
248 .field("default_permissions", &self.default_permissions)
249 .field(
250 "custom_key_generator",
251 &self
252 .custom_key_generator
253 .as_ref()
254 .map(|_| "<custom-key-generator>"),
255 )
256 .field(
257 "custom_api_key_getter",
258 &self
259 .custom_api_key_getter
260 .as_ref()
261 .map(|_| "<custom-api-key-getter>"),
262 )
263 .field(
264 "custom_api_key_validator",
265 &self
266 .custom_api_key_validator
267 .as_ref()
268 .map(|_| "<custom-api-key-validator>"),
269 )
270 .field("storage", &self.storage)
271 .field("fallback_to_database", &self.fallback_to_database)
272 .field(
273 "revalidate_secondary_against_database",
274 &self.revalidate_secondary_against_database,
275 )
276 .field(
277 "custom_storage",
278 &self.custom_storage.as_ref().map(|_| "<custom-storage>"),
279 )
280 .field("defer_updates", &self.defer_updates)
281 .field("reference", &self.reference)
282 .field("starting_characters", &self.starting_characters)
283 .finish()
284 }
285}
286
287#[derive(Debug, Clone, Default, Serialize, Deserialize)]
288#[serde(default, rename_all = "camelCase")]
289pub struct ApiKeyOptions {
290 pub configurations: Vec<ApiKeyConfiguration>,
291 pub schema: crate::api_key::schema::ApiKeySchemaOptions,
292}
293
294impl ApiKeyOptions {
295 #[must_use]
296 pub fn builder() -> ApiKeyOptionsBuilder {
297 ApiKeyOptionsBuilder::default()
298 }
299
300 pub(crate) fn resolve(self) -> Result<ResolvedConfigurations, RustAuthError> {
301 if self.configurations.is_empty() {
302 return Ok(ResolvedConfigurations::with_schema(
303 ApiKeyConfiguration::default(),
304 self.schema,
305 ));
306 }
307 if self.configurations.len() == 1 {
308 return Ok(ResolvedConfigurations::with_schema(
309 self.configurations[0].clone(),
310 self.schema,
311 ));
312 }
313 ResolvedConfigurations::multiple(self.configurations, self.schema).map_err(Into::into)
314 }
315
316 pub fn validate(&self) -> Result<(), RustAuthError> {
317 if self.configurations.len() <= 1 {
318 return Ok(());
319 }
320 ResolvedConfigurations::multiple(self.configurations.clone(), self.schema.clone())?;
321 Ok(())
322 }
323}
324
325#[derive(Debug, Clone, Default)]
326pub struct ApiKeyOptionsBuilder {
327 configurations: Vec<ApiKeyConfiguration>,
328 schema: crate::api_key::schema::ApiKeySchemaOptions,
329}
330
331impl ApiKeyOptionsBuilder {
332 #[must_use]
333 pub fn configuration(mut self, configuration: ApiKeyConfiguration) -> Self {
334 self.configurations = vec![configuration];
335 self
336 }
337
338 #[must_use]
339 pub fn configurations(mut self, configurations: Vec<ApiKeyConfiguration>) -> Self {
340 self.configurations = configurations;
341 self
342 }
343
344 #[must_use]
345 pub fn schema(mut self, schema: crate::api_key::schema::ApiKeySchemaOptions) -> Self {
346 self.schema = schema;
347 self
348 }
349
350 pub fn build(self) -> Result<ApiKeyOptions, RustAuthError> {
351 let options = ApiKeyOptions {
352 configurations: self.configurations,
353 schema: self.schema,
354 };
355 options.validate()?;
356 Ok(options)
357 }
358}
359
360#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
361pub enum ApiKeyOptionsError {
362 #[error("config_id is required for each API key configuration in the api-key plugin")]
363 MissingConfigId,
364 #[error("config_id must be unique for each API key configuration in the api-key plugin")]
365 DuplicateConfigId,
366}
367
368impl From<ApiKeyOptionsError> for RustAuthError {
369 fn from(error: ApiKeyOptionsError) -> Self {
370 Self::InvalidConfig(error.to_string())
371 }
372}
373
374#[derive(Debug, Clone)]
375pub(crate) struct ResolvedConfigurations {
376 configurations: Vec<ApiKeyConfiguration>,
377 schema: crate::api_key::schema::ApiKeySchemaOptions,
378}
379
380impl ResolvedConfigurations {
381 pub fn with_schema(
382 configuration: ApiKeyConfiguration,
383 schema: crate::api_key::schema::ApiKeySchemaOptions,
384 ) -> Self {
385 Self {
386 configurations: vec![configuration],
387 schema,
388 }
389 }
390
391 pub fn multiple(
392 configurations: Vec<ApiKeyConfiguration>,
393 schema: crate::api_key::schema::ApiKeySchemaOptions,
394 ) -> Result<Self, ApiKeyOptionsError> {
395 let mut seen = HashSet::new();
396 for configuration in &configurations {
397 let Some(config_id) = configuration.config_id.as_deref() else {
398 return Err(ApiKeyOptionsError::MissingConfigId);
399 };
400 if !seen.insert(config_id.to_owned()) {
401 return Err(ApiKeyOptionsError::DuplicateConfigId);
402 }
403 }
404 Ok(Self {
405 configurations,
406 schema,
407 })
408 }
409
410 pub fn schema(&self) -> &crate::api_key::schema::ApiKeySchemaOptions {
411 &self.schema
412 }
413
414 pub fn all(&self) -> &[ApiKeyConfiguration] {
415 &self.configurations
416 }
417
418 pub fn resolve(&self, config_id: Option<&str>) -> Result<ApiKeyConfiguration, RustAuthError> {
419 if let Some(config_id) = config_id {
420 if let Some(configuration) = self
421 .configurations
422 .iter()
423 .find(|configuration| configuration.config_id.as_deref() == Some(config_id))
424 {
425 return Ok(with_default_config_id(configuration.clone()));
426 }
427 }
428
429 self.configurations
430 .iter()
431 .find(|configuration| {
432 configuration.config_id.is_none()
433 || configuration.config_id.as_deref() == Some("default")
434 })
435 .cloned()
436 .map(with_default_config_id)
437 .ok_or_else(|| {
438 RustAuthError::Api(
439 crate::api_key::errors::message(
440 crate::api_key::errors::NO_DEFAULT_API_KEY_CONFIGURATION_FOUND,
441 )
442 .to_owned(),
443 )
444 })
445 }
446}
447
448fn with_default_config_id(mut configuration: ApiKeyConfiguration) -> ApiKeyConfiguration {
449 if configuration.config_id.is_none() {
450 configuration.config_id = Some("default".to_owned());
451 }
452 configuration
453}