⎇ 6aee9254d410696bd376e339fef8a51afe60a7ff
crates/ente-zero/src/graph/capabilities.rs
Historia
Quién tocó qué
crudo
Tests audit replay, métricas chain, TTL por cap, brain snapshot
1//! Mediación de capabilities: emisión, renovación, revocación de tokens.
2//!
3//! Los grants tienen TTL (`DEFAULT_GRANT_TTL`). El cliente debe renovarlos
4//! periódicamente con `renew_grant(token)`; en caso contrario, el background
5//! task `purge_expired_grants` los revoca al vencimiento.
6
7use super::{ttl_for_capability, EnteGraph, GrantedCapability};
8use crate::events::CapabilityGrant;
9use ente_card::Capability;
10use std::time::Instant;
11use tokio::sync::oneshot;
12use tracing::debug;
13use ulid::Ulid;
14
15impl EnteGraph {
16 pub async fn mediate_capability(
17 &mut self,
18 from: Ulid,
19 cap: Capability,
20 reply: oneshot::Sender<CapabilityGrant>,
21 ) {
22 let grant = match self.providers.get(&cap).and_then(|s| s.iter().next().copied()) {
23 None => CapabilityGrant::NoProvider,
24 Some(provider) => {
25 let token = self.next_token;
26 self.next_token += 1;
27 // TTL específico por variante de capability — caps escaladas
28 // (Spawn, FilesystemRoot) viven menos.
29 let ttl = ttl_for_capability(&cap);
30 let expires_at = Instant::now() + ttl;
31 self.grants.insert(token, GrantedCapability {
32 cap: cap.clone(),
33 provider,
34 holder: from,
35 expires_at,
36 });
37 CapabilityGrant::Granted { token }
38 }
39 };
40 let _ = reply.send(grant);
41 }
42
43 /// Extiende un grant existente. Devuelve `true` si renovó. Si el token
44 /// no existe o ya expiró, `false` (el cliente debe re-acquire).
45 /// Usa el TTL específico de la cap del grant.
46 pub fn renew_grant(&mut self, token: u64) -> bool {
47 let now = Instant::now();
48 if let Some(g) = self.grants.get_mut(&token) {
49 if g.expires_at > now {
50 g.expires_at = now + ttl_for_capability(&g.cap);
51 return true;
52 }
53 // Expired — purgamos aquí mismo.
54 self.grants.remove(&token);
55 }
56 false
57 }
58
59 /// GC: elimina grants vencidos. Devuelve cuántos fueron purgados.
60 pub fn purge_expired_grants(&mut self) -> usize {
61 let now = Instant::now();
62 let expired: Vec<u64> = self.grants.iter()
63 .filter(|(_, g)| g.expires_at <= now)
64 .map(|(t, _)| *t)
65 .collect();
66 for t in &expired {
67 self.grants.remove(t);
68 }
69 if !expired.is_empty() {
70 debug!(count = expired.len(), "grants expirados purgados");
71 }
72 expired.len()
73 }
74
75 /// Cuenta de grants vivos (no expirados). Usado por métricas.
76 pub fn active_grants_count(&self) -> usize {
77 let now = Instant::now();
78 self.grants.values().filter(|g| g.expires_at > now).count()
79 }
80}
81