Skip to main content

tuwunel_service/threepid/
mod.rs

1//! Email third-party identifier bindings and verification sessions.
2//!
3//! The service stores bidirectional email bindings, durable pending proofs,
4//! and UIAA ownership claims. Process-local token buckets separately limit
5//! verification requests by caller address and canonical email.
6
7mod binding;
8mod canonical;
9mod pending;
10mod ratelimit;
11
12use std::{
13	collections::HashMap,
14	net::IpAddr,
15	sync::{Arc, Mutex},
16	time::Instant,
17};
18
19use ruma::{MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, thirdparty::Medium};
20use serde::{Deserialize, Serialize};
21use tuwunel_core::{Result, smallstr::SmallString, utils::MutexMap};
22use tuwunel_database::{Database, Map};
23
24/// Public helpers and result types exposed by the threepid service.
25///
26/// Email canonicalization produces storage keys, while pending outcomes tell
27/// callers whether a verification message must be sent.
28/// Both APIs preserve the service's canonical addressing rules.
29pub use self::{canonical::canonicalize_email, pending::PendingOutcome};
30
31/// Token-bucket table keyed on a throttle axis: last-refill instant and
32/// remaining tokens per key.
33type Ratelimiter<K> = Mutex<HashMap<K, (Instant, f64)>>;
34
35/// Stack-string key for the per-address throttle bucket; the modal email
36/// canonical address fits inline.
37type EmailKey = SmallString<[u8; 48]>;
38
39/// Manages email threepid bindings, verification sessions, and request limits.
40///
41/// Persistent maps provide lookups from user to email and email to user.
42/// In-memory token buckets limit `requestToken` calls by caller IP and
43/// canonical address.
44pub struct Service {
45	db: Data,
46	pending_mutex: MutexMap<String, ()>,
47	claim_mutex: MutexMap<UiaaKey, ()>,
48	ip_ratelimiter: Ratelimiter<IpAddr>,
49	address_ratelimiter: Ratelimiter<EmailKey>,
50}
51
52struct Data {
53	database: Arc<Database>,
54	userid_email: Arc<Map>,
55	email_userid: Arc<Map>,
56	threepidsid_pending: Arc<Map>,
57	userdevicesessionid_threepid: Arc<Map>,
58}
59
60/// Stores a UIAA session identifier inline in the common case.
61///
62/// The 32-byte budget matches identifiers minted by the UIAA service.
63/// Longer identifiers spill to the backing allocation without changing their
64/// value semantics.
65pub type UiaaSessionId = SmallString<[u8; 32]>;
66
67/// Identifies the exact UIAA session that owns a validated threepid.
68///
69/// Owned components let the durable claim key outlive an individual request.
70/// The tuple scopes a claim by user, device, and UIAA session identifier.
71pub type UiaaKey = (OwnedUserId, OwnedDeviceId, UiaaSessionId);
72
73/// CBOR value of a `userid_email` row: the per-binding metadata, with the
74/// address carried in the composite key.
75#[derive(Clone, Debug, Deserialize, Serialize)]
76struct Binding {
77	medium: Medium,
78	validated_at: MilliSecondsSinceUnixEpoch,
79	added_at: MilliSecondsSinceUnixEpoch,
80}
81
82/// Validated third-party identifier consumed from a pending proof.
83///
84/// Redemption returns the original medium and address stored with the
85/// verification session. Owning both values lets the result outlive the
86/// pending-row read.
87#[derive(Clone, Debug)]
88pub struct Association {
89	/// Third-party identifier medium that was verified.
90	pub medium: Medium,
91
92	/// Address exactly as stored by the verification session.
93	pub address: String,
94}
95
96impl crate::Service for Service {
97	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
98		Ok(Arc::new(Self {
99			db: Data {
100				database: args.db.clone(),
101				userid_email: args.db["userid_email"].clone(),
102				email_userid: args.db["email_userid"].clone(),
103				threepidsid_pending: args.db["threepidsid_pending"].clone(),
104				userdevicesessionid_threepid: args.db["userdevicesessionid_threepid"].clone(),
105			},
106			pending_mutex: MutexMap::new(),
107			claim_mutex: MutexMap::new(),
108			ip_ratelimiter: Mutex::new(HashMap::new()),
109			address_ratelimiter: Mutex::new(HashMap::new()),
110		}))
111	}
112
113	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
114}