1use super::entities::api_key::Model;
2use super::entities::api_key_start::ApiKeyStart;
3use super::{SqlxStore, lock_exclusive};
4use crate::error::{record_not_inserted, record_not_updated};
5use crate::model::{self, ActiveRow, SqlxModel};
6use crate::pool::Exec;
7use crate::schema::AuthSchema;
8use crate::sql::Sql;
9use crate::value::SqlxValue;
10use alibi_core::error::{AuthError, AuthResult};
11use alibi_core::store::adapter::parse_optional_rfc3339;
12use alibi_core::store::{ApiKeyStore, ConsumeApiKeyResult};
13use alibi_core::types::{ApiKey, CreateApiKey, UpdateApiKey};
14use async_trait::async_trait;
15use chrono::Utc;
16use uuid::Uuid;
17
18#[async_trait]
19impl<S> ApiKeyStore for SqlxStore<S>
20where
21 S: AuthSchema + Send + Sync,
22{
23 async fn consume_api_key_usage_from_snapshot(
24 &self,
25 observed: &ApiKey,
26 global_rate_limit_enabled: bool,
27 ) -> AuthResult<ConsumeApiKeyResult> {
28 self.consume_usage_phases(observed, global_rate_limit_enabled)
29 .await
30 }
31
32 async fn create_api_key(&self, input: CreateApiKey) -> AuthResult<ApiKey> {
33 let now = Utc::now();
34 let start = input
35 .start
36 .map(|start| ApiKeyStart::prepare(&start, self.exec().engine()))
37 .transpose()?;
38 let sqlite_cast = start
39 .as_ref()
40 .is_some_and(ApiKeyStart::requires_sqlite_cast);
41 let mut active = ActiveRow::new();
42 active.set(
43 "id",
44 self.generated_id(self.exec(), "apikey", "apikey", "id")
45 .await?
46 .unwrap_or_else(|| Uuid::new_v4().to_string()),
47 );
48 active.set("name", input.name);
49 active.set("start", start.into_sql_value());
50 active.set("prefix", input.prefix);
51 active.set("key", input.key_hash);
52 active.set("referenceId", input.reference_id);
53 active.set("configId", input.config_id);
54 active.set("refillInterval", input.refill_interval);
55 active.set("refillAmount", input.refill_amount);
56 active.set("lastRefillAt", None::<chrono::DateTime<Utc>>);
57 active.set("enabled", input.enabled);
58 active.set("rateLimitEnabled", input.rate_limit_enabled);
59 active.set("rateLimitTimeWindow", input.rate_limit_time_window);
60 active.set("rateLimitMax", input.rate_limit_max);
61 active.set("requestCount", Some(0.0_f64));
62 active.set("remaining", input.remaining);
63 active.set("lastRequest", None::<chrono::DateTime<Utc>>);
64 active.set(
65 "expiresAt",
66 parse_optional_rfc3339(input.expires_at.as_deref(), "expiresAt")?,
67 );
68 active.set("createdAt", now);
69 active.set("updatedAt", now);
70 active.set("permissions", input.permissions);
71 active.set("metadata", input.metadata);
72 let inserted = if sqlite_cast {
73 let mut sql = Sql::with(self.exec().engine(), "INSERT INTO ");
75 sql.ident(Model::TABLE);
76 sql.push(" (");
77 let present = active.present().collect::<Vec<_>>();
78 sql.column_list(&present.iter().map(|(name, _)| *name).collect::<Vec<_>>());
79 sql.push(") SELECT ");
80 for (index, (name, value)) in present.into_iter().enumerate() {
81 if index > 0 {
82 sql.push(", ");
83 }
84 if name == "start" {
85 sql.push("CAST(");
86 sql.bind(value.clone());
87 sql.push(" AS text)");
88 } else {
89 sql.bind(value.clone());
90 }
91 }
92 model::returning::<Model>(&mut sql);
93 self.exec()
94 .fetch_optional::<Model>(sql)
95 .await?
96 .ok_or_else(record_not_inserted)?
97 } else {
98 model::insert::<Model>(self.exec(), &active).await?
99 };
100 Ok(ApiKey::from(&inserted))
101 }
102
103 async fn get_api_key_by_id(&self, id: &str) -> AuthResult<Option<ApiKey>> {
104 let mut sql = model::by_id::<Model>(self.exec(), id);
105 model::limit_one(&mut sql);
106 Ok(self
107 .exec()
108 .fetch_optional::<Model>(sql)
109 .await?
110 .map(|model| ApiKey::from(&model)))
111 }
112
113 async fn get_api_key_by_hash(&self, hash: &str) -> AuthResult<Option<ApiKey>> {
114 let mut sql = model::select_model::<Model>(self.exec());
115 sql.push(" WHERE ");
116 sql.compare(Model::TABLE, "key", " = ", hash);
117 model::limit_one(&mut sql);
118 Ok(self
119 .exec()
120 .fetch_optional::<Model>(sql)
121 .await?
122 .map(|model| ApiKey::from(&model)))
123 }
124
125 async fn list_api_keys_by_reference(&self, reference_id: &str) -> AuthResult<Vec<ApiKey>> {
126 let mut sql = model::select_model::<Model>(self.exec());
128 sql.push(" WHERE ");
129 sql.compare(Model::TABLE, "referenceId", " = ", reference_id);
130 sql.push(" ORDER BY ");
131 sql.column(Model::TABLE, "createdAt");
132 sql.push(" ASC");
133 Ok(self
134 .exec()
135 .fetch_all::<Model>(sql)
136 .await?
137 .iter()
138 .map(ApiKey::from)
139 .collect())
140 }
141
142 async fn update_api_key(&self, id: &str, update: UpdateApiKey) -> AuthResult<ApiKey> {
143 let mut sql = model::by_id::<Model>(self.exec(), id);
144 model::limit_one(&mut sql);
145 let Some(model) = self.exec().fetch_optional::<Model>(sql).await? else {
146 return Err(AuthError::not_found("API Key not found"));
147 };
148
149 let active = apply_update_fields(model.into_active(), update)?;
150 model::update::<Model>(self.exec(), &active)
151 .await?
152 .map(|model| ApiKey::from(&model))
153 .ok_or_else(record_not_updated)
154 }
155
156 #[expect(
157 clippy::as_conversions,
158 clippy::cast_precision_loss,
159 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
160 )]
161 async fn consume_api_key_usage(
162 &self,
163 id: &str,
164 global_rate_limit_enabled: bool,
165 ) -> AuthResult<ConsumeApiKeyResult> {
166 let transaction = self.begin(true).await?;
168 let exec = Exec::tx(&transaction);
169 let id = id.to_owned();
170 let result = async {
171 let mut select = model::by_id::<Model>(exec, id.as_str());
172 model::limit_one(&mut select);
173 lock_exclusive(&mut select);
174 let Some(model) = exec.fetch_optional::<Model>(select).await? else {
175 return Err(AuthError::not_found("API Key not found"));
176 };
177
178 let now = Utc::now();
179 let mut update = UpdateApiKey::default();
180
181 if let Some(remaining) = model.remaining.map(|value| value.0) {
182 if let (Some(interval), Some(amount)) = (
183 model.refill_interval.map(|value| value.0),
184 model.refill_amount.map(|value| value.0),
185 ) && interval != 0.0
186 && amount != 0.0
187 && (now.timestamp_millis()
188 - model
189 .last_refill_at
190 .unwrap_or(model.created_at)
191 .timestamp_millis()) as f64
192 > interval
193 {
194 update.remaining = Some(amount - 1.0);
195 update.last_refill_at = Some(Some(now.to_rfc3339()));
196 } else if remaining > 0.0 {
197 update.remaining = Some(remaining - 1.0);
198 } else {
199 return Ok(ConsumeApiKeyResult::UsageExhausted);
200 }
201 }
202
203 if global_rate_limit_enabled && model.rate_limit_enabled.unwrap_or(false) {
204 if let (Some(window), Some(max)) = (
205 model.rate_limit_time_window.map(|value| value.0),
206 model.rate_limit_max.map(|value| value.0),
207 ) {
208 let elapsed = model
209 .last_request
210 .map(|last| (now.timestamp_millis() - last.timestamp_millis()) as f64);
211 if let Some(elapsed) = elapsed
212 && elapsed <= window
213 && model.request_count.map_or(0.0, |value| value.0) >= max
214 {
215 if update.remaining.is_some() {
218 let updated_at = model.updated_at;
219 let mut active = apply_update_fields(model.into_active(), update)?;
220 active.set("updatedAt", updated_at);
221 _ = model::update::<Model>(exec, &active)
222 .await?
223 .ok_or_else(record_not_updated)?;
224 }
225 return Ok(ConsumeApiKeyResult::RateLimited {
226 try_again_in: (window - elapsed).ceil(),
227 });
228 }
229
230 update.request_count =
231 Some(if elapsed.is_none_or(|elapsed| elapsed > window) {
232 1.0
233 } else {
234 model.request_count.map_or(0.0, |value| value.0) + 1.0
235 });
236 update.last_request = Some(Some(now.to_rfc3339()));
237 }
238 } else {
239 update.last_request = Some(Some(now.to_rfc3339()));
240 }
241
242 let active = apply_update_fields(model.into_active(), update)?;
243 let updated = model::update::<Model>(exec, &active)
244 .await?
245 .ok_or_else(record_not_updated)?;
246 Ok(ConsumeApiKeyResult::Allowed(Box::new(ApiKey::from(
247 &updated,
248 ))))
249 }
250 .await;
251 if result.is_ok() {
252 transaction.commit().await?;
253 } else {
254 transaction.rollback().await?;
255 }
256 result
257 }
258
259 async fn delete_api_key(&self, id: &str) -> AuthResult<()> {
260 self.exec()
261 .execute(model::delete_by_id::<Model>(self.exec(), id))
262 .await
263 .map(|_| ())
264 }
265
266 async fn delete_expired_api_keys(&self) -> AuthResult<usize> {
267 let mut sql = Sql::with(self.exec().engine(), "DELETE FROM ");
268 sql.ident(Model::TABLE);
269 sql.push(" WHERE ");
270 sql.column(Model::TABLE, "expiresAt");
271 sql.push(" IS NOT NULL AND ");
272 sql.compare(Model::TABLE, "expiresAt", " < ", Utc::now());
273 let deleted = self.exec().execute(sql).await?;
274 usize::try_from(deleted)
275 .map_err(|_error| AuthError::internal("Affected row count exceeds usize"))
276 }
277}
278
279fn apply_update_fields(mut active: ActiveRow, update: UpdateApiKey) -> AuthResult<ActiveRow> {
281 if let Some(name) = update.name {
282 active.set("name", Some(name));
283 }
284 if let Some(enabled) = update.enabled {
285 active.set("enabled", enabled);
286 }
287 if let Some(remaining) = update.remaining {
288 active.set("remaining", Some(remaining));
289 }
290 if let Some(rate_limit_enabled) = update.rate_limit_enabled {
291 active.set("rateLimitEnabled", rate_limit_enabled);
292 }
293 if let Some(rate_limit_time_window) = update.rate_limit_time_window {
294 active.set("rateLimitTimeWindow", Some(rate_limit_time_window));
295 }
296 if let Some(rate_limit_max) = update.rate_limit_max {
297 active.set("rateLimitMax", Some(rate_limit_max));
298 }
299 if let Some(refill_interval) = update.refill_interval {
300 active.set("refillInterval", Some(refill_interval));
301 }
302 if let Some(refill_amount) = update.refill_amount {
303 active.set("refillAmount", Some(refill_amount));
304 }
305 if let Some(permissions) = update.permissions {
306 active.set("permissions", Some(permissions));
307 }
308 if let Some(metadata) = update.metadata {
309 active.set("metadata", Some(metadata));
310 }
311 if let Some(expires_at) = update.expires_at {
312 active.set(
313 "expiresAt",
314 parse_optional_rfc3339(expires_at.as_deref(), "expiresAt")?,
315 );
316 }
317 if let Some(last_request) = update.last_request {
318 active.set(
319 "lastRequest",
320 parse_optional_rfc3339(last_request.as_deref(), "lastRequest")?,
321 );
322 }
323 if let Some(request_count) = update.request_count {
324 active.set("requestCount", Some(request_count));
325 }
326 if let Some(last_refill_at) = update.last_refill_at {
327 active.set(
328 "lastRefillAt",
329 parse_optional_rfc3339(last_refill_at.as_deref(), "lastRefillAt")?,
330 );
331 }
332 active.set("updatedAt", Utc::now());
333 Ok(active)
334}