Skip to main content

micromegas_auth/
db_audience_grants.rs

1//! DB-backed audience grant store: a whole-table snapshot cache over the `audience_grants` table
2//! (migration v7, `rust/ingestion/src/sql_migration.rs`), checked alongside the static grant map
3//! by [`crate::policy::AudienceReadPolicy`]/[`crate::policy::AudienceMintPolicy`] -- a selector
4//! present in either source grants access, without either side being deep-cloned or merged into
5//! a combined map (`current()` hands back the cached grants behind an `Arc`). This is what makes
6//! a grant creatable without a service restart -- the static map stays the bootstrap/test-only
7//! layer.
8//!
9//! The cache mechanics (cold-start throttling, last-good serving, `ProviderUnavailable`
10//! wrapping) live in [`crate::db_snapshot`], shared with [`crate::groups::DbGroupsSource`].
11
12use crate::db_api_key::resolve_u64;
13use crate::db_snapshot::{SnapshotLoader, SnapshotSource};
14use crate::policy::{AudienceGrants, GrantAxis};
15use anyhow::{Context, Result, anyhow};
16use async_trait::async_trait;
17use sqlx::{PgPool, Row};
18
19/// Cache-TTL knob for [`DbAudienceGrantsSource`], read from env with a default.
20#[derive(Clone, Copy, Debug)]
21pub struct DbAudienceGrantsConfig {
22    /// `MICROMEGAS_AUTH_CACHE_TTL_SECONDS`, default 60 -- a single flat, unprefixed knob (no
23    /// `{prefix}_` role variant) shared with `DbApiKeyConfig`/`DbGroupsConfig`'s own
24    /// positive-cache TTL: one value governs the API-key, audience-grant, and group snapshot
25    /// caches process-wide, across every role.
26    pub cache_ttl_secs: u64,
27}
28
29impl DbAudienceGrantsConfig {
30    /// Resolves the flat, unprefixed `MICROMEGAS_AUTH_CACHE_TTL_SECONDS` knob directly -- there
31    /// is exactly one value for this knob process-wide, so `prefix` is accepted (for call-site
32    /// symmetry with every other `from_env_with_prefix`) but not consulted here.
33    pub fn from_env_with_prefix(_prefix: &str) -> Self {
34        Self {
35            cache_ttl_secs: resolve_u64("", "AUTH_CACHE_TTL_SECONDS", 60),
36        }
37    }
38}
39
40/// [`SnapshotLoader`] for the `audience_grants` table.
41#[derive(Debug)]
42pub struct AudienceGrantsLoader;
43
44#[async_trait]
45impl SnapshotLoader for AudienceGrantsLoader {
46    type Snapshot = AudienceGrants;
47    const NAME: &'static str = "audience grant store";
48
49    /// Queries the whole table and builds an `AudienceGrants` via
50    /// [`AudienceGrants::from_rows`]. The one place a malformed row (one that slipped past the
51    /// table's own `CHECK` constraints, e.g. via a direct `psql` session) surfaces as a load
52    /// failure rather than a silently-inert or silently-unreadable grant.
53    async fn fetch(pool: &PgPool) -> Result<AudienceGrants> {
54        let rows = sqlx::query("SELECT audience, axis, selector FROM audience_grants")
55            .fetch_all(pool)
56            .await
57            .context("querying audience_grants")?;
58        let mut triples = Vec::with_capacity(rows.len());
59        for row in rows {
60            let audience: String = row.try_get("audience").context("reading audience")?;
61            let axis: String = row.try_get("axis").context("reading axis")?;
62            let selector: String = row.try_get("selector").context("reading selector")?;
63            let axis = match axis.as_str() {
64                "read" => GrantAxis::Read,
65                "mint" => GrantAxis::Mint,
66                other => {
67                    return Err(anyhow!(
68                        "audience_grants row for {audience:?}/{selector:?} has unrecognized \
69                         axis {other:?}"
70                    ));
71                }
72            };
73            triples.push((audience, axis, selector));
74        }
75        AudienceGrants::from_rows(triples)
76    }
77
78    fn count_refresh_error() {
79        micromegas_tracing::imetric!("audience_grant_refresh_error_count", "count", 1_u64);
80    }
81}
82
83/// The whole-table snapshot cache described in the module doc comment.
84pub type DbAudienceGrantsSource = SnapshotSource<AudienceGrantsLoader>;