Skip to main content

openauth_plugins/api_key/
options.rs

1use std::collections::{BTreeMap, HashSet};
2use std::fmt;
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use openauth_core::context::AuthContext;
8use openauth_core::error::OpenAuthError;
9use openauth_core::options::SecondaryStorage;
10use openauth_core::plugin::PluginRequest;
11use serde::{Deserialize, Serialize};
12
13pub type ApiKeyPermissions = BTreeMap<String, Vec<String>>;
14pub type ApiKeyGeneratorFuture =
15    Pin<Box<dyn Future<Output = Result<String, OpenAuthError>> + Send + 'static>>;
16pub type ApiKeyGenerator = Arc<dyn Fn(ApiKeyGeneratorInput) -> ApiKeyGeneratorFuture + Send + Sync>;
17pub type ApiKeyGetterFuture<'a> =
18    Pin<Box<dyn Future<Output = Result<Option<String>, OpenAuthError>> + Send + 'a>>;
19pub type ApiKeyGetter =
20    Arc<dyn for<'a> Fn(&'a AuthContext, &'a PluginRequest) -> ApiKeyGetterFuture<'a> + Send + Sync>;
21pub type ApiKeyValidatorFuture<'a> =
22    Pin<Box<dyn Future<Output = Result<bool, OpenAuthError>> + Send + 'a>>;
23pub type ApiKeyValidator =
24    Arc<dyn for<'a> Fn(&'a AuthContext, &'a str) -> ApiKeyValidatorFuture<'a> + Send + Sync>;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct ApiKeyGeneratorInput {
28    pub length: usize,
29    pub prefix: Option<String>,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub enum ApiKeyStorageMode {
35    Database,
36    SecondaryStorage,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub enum ApiKeyReference {
42    User,
43    Organization,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct ApiKeyRateLimitOptions {
49    pub enabled: bool,
50    pub time_window: i64,
51    pub max_requests: i64,
52}
53
54impl Default for ApiKeyRateLimitOptions {
55    fn default() -> Self {
56        Self {
57            enabled: true,
58            time_window: 1000 * 60 * 60 * 24,
59            max_requests: 10,
60        }
61    }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct ApiKeyExpirationOptions {
67    pub default_expires_in: Option<i64>,
68    pub disable_custom_expires_time: bool,
69    pub min_expires_in_days: i64,
70    pub max_expires_in_days: i64,
71}
72
73impl Default for ApiKeyExpirationOptions {
74    fn default() -> Self {
75        Self {
76            default_expires_in: None,
77            disable_custom_expires_time: false,
78            min_expires_in_days: 1,
79            max_expires_in_days: 365,
80        }
81    }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct StartingCharactersConfig {
87    pub should_store: bool,
88    pub characters_length: usize,
89}
90
91impl Default for StartingCharactersConfig {
92    fn default() -> Self {
93        Self {
94            should_store: true,
95            characters_length: 6,
96        }
97    }
98}
99
100#[derive(Clone, Serialize, Deserialize)]
101#[serde(default, rename_all = "camelCase")]
102pub struct ApiKeyConfiguration {
103    pub config_id: Option<String>,
104    pub api_key_headers: Vec<String>,
105    pub disable_key_hashing: bool,
106    pub default_key_length: usize,
107    pub default_prefix: Option<String>,
108    pub maximum_prefix_length: usize,
109    pub minimum_prefix_length: usize,
110    pub require_name: bool,
111    pub maximum_name_length: usize,
112    pub minimum_name_length: usize,
113    pub enable_metadata: bool,
114    pub key_expiration: ApiKeyExpirationOptions,
115    pub rate_limit: ApiKeyRateLimitOptions,
116    pub enable_session_for_api_keys: bool,
117    pub default_permissions: Option<ApiKeyPermissions>,
118    #[serde(skip)]
119    pub custom_key_generator: Option<ApiKeyGenerator>,
120    #[serde(skip)]
121    pub custom_api_key_getter: Option<ApiKeyGetter>,
122    #[serde(skip)]
123    pub custom_api_key_validator: Option<ApiKeyValidator>,
124    pub storage: ApiKeyStorageMode,
125    pub fallback_to_database: bool,
126    #[serde(skip)]
127    pub custom_storage: Option<Arc<dyn SecondaryStorage>>,
128    pub defer_updates: bool,
129    pub reference: ApiKeyReference,
130    pub starting_characters: StartingCharactersConfig,
131}
132
133impl Default for ApiKeyConfiguration {
134    fn default() -> Self {
135        Self {
136            config_id: None,
137            api_key_headers: vec!["x-api-key".to_owned()],
138            disable_key_hashing: false,
139            default_key_length: 64,
140            default_prefix: None,
141            maximum_prefix_length: 32,
142            minimum_prefix_length: 1,
143            require_name: false,
144            maximum_name_length: 32,
145            minimum_name_length: 1,
146            enable_metadata: false,
147            key_expiration: ApiKeyExpirationOptions::default(),
148            rate_limit: ApiKeyRateLimitOptions::default(),
149            enable_session_for_api_keys: false,
150            default_permissions: None,
151            custom_key_generator: None,
152            custom_api_key_getter: None,
153            custom_api_key_validator: None,
154            storage: ApiKeyStorageMode::Database,
155            fallback_to_database: false,
156            custom_storage: None,
157            defer_updates: false,
158            reference: ApiKeyReference::User,
159            starting_characters: StartingCharactersConfig::default(),
160        }
161    }
162}
163
164impl fmt::Debug for ApiKeyConfiguration {
165    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
166        formatter
167            .debug_struct("ApiKeyConfiguration")
168            .field("config_id", &self.config_id)
169            .field("api_key_headers", &self.api_key_headers)
170            .field("disable_key_hashing", &self.disable_key_hashing)
171            .field("default_key_length", &self.default_key_length)
172            .field("default_prefix", &self.default_prefix)
173            .field("maximum_prefix_length", &self.maximum_prefix_length)
174            .field("minimum_prefix_length", &self.minimum_prefix_length)
175            .field("require_name", &self.require_name)
176            .field("maximum_name_length", &self.maximum_name_length)
177            .field("minimum_name_length", &self.minimum_name_length)
178            .field("enable_metadata", &self.enable_metadata)
179            .field("key_expiration", &self.key_expiration)
180            .field("rate_limit", &self.rate_limit)
181            .field(
182                "enable_session_for_api_keys",
183                &self.enable_session_for_api_keys,
184            )
185            .field("default_permissions", &self.default_permissions)
186            .field(
187                "custom_key_generator",
188                &self
189                    .custom_key_generator
190                    .as_ref()
191                    .map(|_| "<custom-key-generator>"),
192            )
193            .field(
194                "custom_api_key_getter",
195                &self
196                    .custom_api_key_getter
197                    .as_ref()
198                    .map(|_| "<custom-api-key-getter>"),
199            )
200            .field(
201                "custom_api_key_validator",
202                &self
203                    .custom_api_key_validator
204                    .as_ref()
205                    .map(|_| "<custom-api-key-validator>"),
206            )
207            .field("storage", &self.storage)
208            .field("fallback_to_database", &self.fallback_to_database)
209            .field(
210                "custom_storage",
211                &self.custom_storage.as_ref().map(|_| "<custom-storage>"),
212            )
213            .field("defer_updates", &self.defer_updates)
214            .field("reference", &self.reference)
215            .field("starting_characters", &self.starting_characters)
216            .finish()
217    }
218}
219
220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
221#[serde(default, rename_all = "camelCase")]
222pub struct ApiKeyOptions {
223    pub configuration: ApiKeyConfiguration,
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
227pub enum ApiKeyOptionsError {
228    #[error("config_id is required for each API key configuration in the api-key plugin")]
229    MissingConfigId,
230    #[error("config_id must be unique for each API key configuration in the api-key plugin")]
231    DuplicateConfigId,
232}
233
234impl From<ApiKeyOptionsError> for OpenAuthError {
235    fn from(error: ApiKeyOptionsError) -> Self {
236        Self::InvalidConfig(error.to_string())
237    }
238}
239
240#[derive(Debug, Clone)]
241pub(crate) struct ResolvedConfigurations {
242    configurations: Vec<ApiKeyConfiguration>,
243}
244
245impl ResolvedConfigurations {
246    pub fn single(configuration: ApiKeyConfiguration) -> Self {
247        Self {
248            configurations: vec![configuration],
249        }
250    }
251
252    pub fn multiple(configurations: Vec<ApiKeyConfiguration>) -> Result<Self, ApiKeyOptionsError> {
253        let mut seen = HashSet::new();
254        for configuration in &configurations {
255            let Some(config_id) = configuration.config_id.as_deref() else {
256                return Err(ApiKeyOptionsError::MissingConfigId);
257            };
258            if !seen.insert(config_id.to_owned()) {
259                return Err(ApiKeyOptionsError::DuplicateConfigId);
260            }
261        }
262        Ok(Self { configurations })
263    }
264
265    pub fn all(&self) -> &[ApiKeyConfiguration] {
266        &self.configurations
267    }
268
269    pub fn resolve(&self, config_id: Option<&str>) -> Result<ApiKeyConfiguration, OpenAuthError> {
270        if let Some(config_id) = config_id {
271            if let Some(configuration) = self
272                .configurations
273                .iter()
274                .find(|configuration| configuration.config_id.as_deref() == Some(config_id))
275            {
276                return Ok(with_default_config_id(configuration.clone()));
277            }
278        }
279
280        self.configurations
281            .iter()
282            .find(|configuration| {
283                configuration.config_id.is_none()
284                    || configuration.config_id.as_deref() == Some("default")
285            })
286            .cloned()
287            .map(with_default_config_id)
288            .ok_or_else(|| {
289                OpenAuthError::Api(
290                    crate::api_key::errors::message(
291                        crate::api_key::errors::NO_DEFAULT_API_KEY_CONFIGURATION_FOUND,
292                    )
293                    .to_owned(),
294                )
295            })
296    }
297}
298
299fn with_default_config_id(mut configuration: ApiKeyConfiguration) -> ApiKeyConfiguration {
300    if configuration.config_id.is_none() {
301        configuration.config_id = Some("default".to_owned());
302    }
303    configuration
304}