Skip to main content

alibi_core/store/contracts/
api_key.rs

1use crate::{ApiKey, AuthError, AuthResult, CreateApiKey, UpdateApiKey};
2use async_trait::async_trait;
3#[async_trait]
4pub trait ApiKeyStore: Send + Sync {
5    async fn create_api_key(&self, input: CreateApiKey) -> AuthResult<ApiKey>;
6    async fn get_api_key_by_id(&self, id: &str) -> AuthResult<Option<ApiKey>>;
7    async fn get_api_key_by_hash(&self, hash: &str) -> AuthResult<Option<ApiKey>>;
8    async fn list_api_keys_by_reference(&self, reference_id: &str) -> AuthResult<Vec<ApiKey>>;
9    async fn update_api_key(&self, id: &str, update: UpdateApiKey) -> AuthResult<ApiKey>;
10    async fn delete_api_key(&self, id: &str) -> AuthResult<()>;
11    async fn delete_expired_api_keys(&self) -> AuthResult<usize>;
12
13    /// Atomically consume one use of an API key: decrement remaining
14    /// (with refill), increment rate-limit counter, and update timestamps.
15    ///
16    /// All counter mutations are derived from the locked row inside a
17    /// transaction, preventing concurrent requests from corrupting counters.
18    /// Read-only checks (enabled, expired, permissions) happen before this
19    /// call in the plugin layer.
20    ///
21    /// `global_rate_limit_enabled`: whether the plugin-level rate limiting
22    /// is turned on. Per-key settings are read from the locked row.
23    async fn consume_api_key_usage(
24        &self,
25        id: &str,
26        global_rate_limit_enabled: bool,
27    ) -> AuthResult<ConsumeApiKeyResult>;
28
29    /// Consume usage from the validated database snapshot with Source write phases.
30    /// Quota/refill and rate claims are independently guarded atomic writes;
31    /// successful earlier writes survive a later storage failure. Finally touch
32    /// `updated_at` and return the current row. This is distinct from the combined
33    /// transactional operation above and cannot be supplied by delegating to it.
34    async fn consume_api_key_usage_from_snapshot(
35        &self,
36        _observed: &ApiKey,
37        _global_rate_limit_enabled: bool,
38    ) -> AuthResult<ConsumeApiKeyResult> {
39        Err(AuthError::internal(
40            "snapshot-aware API key consumption is unsupported by this store",
41        ))
42    }
43}
44
45/// Outcome of an atomic API key usage consumption.
46pub enum ConsumeApiKeyResult {
47    /// The key was valid and counters were updated. Contains the updated key.
48    Allowed(Box<ApiKey>),
49    /// The rate limit was exceeded after consuming the request's usage quota.
50    RateLimited {
51        /// Milliseconds until the current rate-limit window ends.
52        try_again_in: f64,
53    },
54    /// The quota was exhausted. Non-refillable keys at zero quota are deleted.
55    UsageExhausted,
56}
57
58impl std::fmt::Debug for ConsumeApiKeyResult {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        match self {
61            Self::Allowed(..) => f.write_str("ConsumeApiKeyResult::Allowed"),
62            Self::RateLimited { .. } => f.write_str("ConsumeApiKeyResult::RateLimited"),
63            Self::UsageExhausted => f.write_str("ConsumeApiKeyResult::UsageExhausted"),
64        }
65    }
66}