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}