pub mod memory; use std::future::Future; use std::net::IpAddr; use uuid::Uuid; use crate::error::CaptchaError; use crate::puzzle::types::Challenge; use crate::site::types::{Site, SitePolicy}; pub trait Store: Send + Sync + 'static { fn store_challenge( &self, challenge: &Challenge, ) -> impl Future> + Send; fn get_challenge( &self, id: &Uuid, ) -> impl Future, CaptchaError>> + Send; fn delete_challenge(&self, id: &Uuid) -> impl Future> + Send; /// Atomically consume a correctly-solved challenge. Exactly one caller /// can consume a challenge; later callers receive `ChallengeNotFound` /// because the challenge has been removed from the active set. fn consume_challenge( &self, challenge_id: &Uuid, ) -> impl Future> + Send; /// Record a failed PoW attempt against a challenge and evict it once the /// count reaches `max_attempts`. Returns `true` when the challenge was /// evicted (cap reached), `false` while it stays live for retry. A /// `max_attempts` of 0 disables the cap and is always a no-op. Bounds how /// many (potentially memory-hard) verify attempts one challenge can absorb. fn register_failed_attempt( &self, challenge_id: &Uuid, max_attempts: u32, ) -> impl Future> + Send; fn store_site(&self, site: &Site) -> impl Future> + Send; fn get_site_by_key( &self, site_key: &Uuid, ) -> impl Future, CaptchaError>> + Send; fn get_site_by_secret( &self, secret: &str, ) -> impl Future, CaptchaError>> + Send; /// Replace the site's secret_key. Returns the new secret on success. /// Returns `NotFound` if the site doesn't exist. The old secret is /// invalidated immediately — any in-flight `/v1/verify` calls using /// the old secret will fail. fn rotate_site_secret( &self, site_key: &Uuid, new_secret: String, ) -> impl Future> + Send; /// Replace the site's allowed-origins list (already validated/normalized /// by the caller). Returns `NotFound` if the site doesn't exist. Lets an /// operator change a tenant's origin allowlist without rotating its secret. fn update_site_origins( &self, site_key: &Uuid, origins: Vec, ) -> impl Future> + Send; /// Replace the site's display name (already validated by the caller). /// Returns `NotFound` if the site doesn't exist. Purely a label: nothing /// in the scoring pipeline reads it, so this is the one site edit that /// cannot change how a visitor is treated. fn update_site_name( &self, site_key: &Uuid, name: String, ) -> impl Future> + Send; /// Replace the site's scoring policy (already validated by the caller /// against the process config). Returns `NotFound` if the site doesn't /// exist. Takes effect on the next request — challenges already issued /// keep the difficulty they were stamped with. fn update_site_policy( &self, site_key: &Uuid, policy: SitePolicy, ) -> impl Future> + Send; /// Delete a site. Returns `NotFound` if the site doesn't exist. /// Existing challenges issued to this site are not retroactively /// invalidated — they'll fail at `/v1/verify` time on the secret /// lookup, which is sufficient. fn delete_site(&self, site_key: &Uuid) -> impl Future> + Send; fn increment_ip_count( &self, ip: &IpAddr, ) -> impl Future> + Send; /// Same counter key as [`Store::increment_ip_count`], over the sustained /// window ([`crate::risk::signals::RATE_SUSTAINED_WINDOW_SECS`]). The /// handler increments both per request and the rate signal takes the /// worse band of the two. fn increment_ip_count_sustained( &self, ip: &IpAddr, ) -> impl Future> + Send; fn increment_site_count( &self, site_key: &Uuid, ) -> impl Future> + Send; /// Count how many times an identical verify-time behaviour blob has been /// submitted for this site inside the dedup window, returning the new /// count (this submission included). `fingerprint` is /// [`crate::risk::behavior::behavior_fingerprint`] — a hash of the blob's /// parsed fields, never the raw JSON. /// /// Deliberately in-memory and short-lived wherever it's implemented: the /// window exists to spot one script inside one site's traffic, and a hash /// of event counters is not something to persist. fn increment_behavior_blob_count( &self, site_key: &Uuid, fingerprint: u64, ) -> impl Future> + Send; fn cleanup_expired(&self) -> impl Future> + Send; }