oauth2_broker/
flows.rs

1//! High-level flow orchestrators powered by the broker facade.
2
3pub mod auth_code_pkce;
4pub mod common;
5pub mod refresh;
6
7mod client_credentials;
8
9pub use auth_code_pkce::*;
10pub use common::*;
11pub use refresh::*;
12
13// self
14use crate::{
15	_prelude::*,
16	http::{ReqwestHttpClient, TokenHttpClient},
17	oauth::{ReqwestTransportErrorMapper, TransportErrorMapper},
18	provider::{ProviderDescriptor, ProviderStrategy},
19	store::{BrokerStore, StoreKey},
20};
21
22/// Coordinates OAuth 2.0 flows against a single provider descriptor.
23///
24/// The broker owns the HTTP client, token store, provider descriptor, and strategy
25/// references so individual flow implementations can focus on grant-specific logic
26/// (state + PKCE generation, code exchanges, refresh rotations, etc.). Client
27/// credentials are stored alongside the descriptor so client-auth methods can be
28/// applied consistently across token endpoints.
29
30#[derive(Clone)]
31pub struct Broker<C = ReqwestHttpClient, M = ReqwestTransportErrorMapper>
32where
33	C: ?Sized + TokenHttpClient,
34	M: ?Sized + TransportErrorMapper<C::TransportError>,
35{
36	/// HTTP client wrapper used for every outbound provider request.
37	pub http_client: Arc<C>,
38	/// Mapper applied to transport-layer errors before surfacing them to callers.
39	pub transport_mapper: Arc<M>,
40	/// Token store implementation that persists issued secrets.
41	pub store: Arc<dyn BrokerStore>,
42	/// Provider descriptor that defines OAuth endpoints and quirks.
43	pub descriptor: ProviderDescriptor,
44	/// Strategy responsible for provider-specific token request adjustments.
45	pub strategy: Arc<dyn ProviderStrategy>,
46	/// OAuth 2.0 client identifier used in every grant.
47	pub client_id: String,
48	/// Optional client secret for confidential authentication methods.
49	pub client_secret: Option<String>,
50	/// Shared metrics recorder for refresh flow outcomes.
51	pub refresh_metrics: Arc<RefreshMetrics>,
52	flow_guards: Arc<Mutex<HashMap<StoreKey, Arc<AsyncMutex<()>>>>>,
53}
54impl Broker {
55	/// Creates a new broker for the provided descriptor and client identifier.
56	///
57	/// The broker provisions its own reqwest-backed transport so callers do not need
58	/// to pass HTTP handles explicitly. Use [`Broker::with_client_secret`] to attach a confidential
59	/// client secret when the descriptor prefers `client_secret_basic` or
60	/// `client_secret_post`.
61	pub fn new(
62		store: Arc<dyn BrokerStore>,
63		descriptor: ProviderDescriptor,
64		strategy: Arc<dyn ProviderStrategy>,
65		client_id: impl Into<String>,
66	) -> Self {
67		Self::with_http_client(
68			store,
69			descriptor,
70			strategy,
71			client_id,
72			ReqwestHttpClient::default(),
73			Arc::new(ReqwestTransportErrorMapper),
74		)
75	}
76}
77impl<C, M> Broker<C, M>
78where
79	C: ?Sized + TokenHttpClient,
80	M: ?Sized + TransportErrorMapper<C::TransportError>,
81{
82	/// Creates a broker that reuses the caller-provided transport + mapper pair.
83	pub fn with_http_client(
84		store: Arc<dyn BrokerStore>,
85		descriptor: ProviderDescriptor,
86		strategy: Arc<dyn ProviderStrategy>,
87		client_id: impl Into<String>,
88		http_client: impl Into<Arc<C>>,
89		mapper: impl Into<Arc<M>>,
90	) -> Self {
91		Self {
92			http_client: http_client.into(),
93			transport_mapper: mapper.into(),
94			store,
95			descriptor,
96			strategy,
97			client_id: client_id.into(),
98			client_secret: None,
99			flow_guards: Arc::new(Mutex::new(HashMap::new())),
100			refresh_metrics: Arc::new(RefreshMetrics::default()),
101		}
102	}
103
104	/// Sets or replaces the client secret used for confidential client auth modes.
105	pub fn with_client_secret(mut self, secret: impl Into<String>) -> Self {
106		self.client_secret = Some(secret.into());
107
108		self
109	}
110}
111
112impl<C, M> Debug for Broker<C, M>
113where
114	C: ?Sized + TokenHttpClient,
115	M: ?Sized + TransportErrorMapper<C::TransportError>,
116{
117	fn fmt(&self, f: &mut Formatter) -> FmtResult {
118		f.debug_struct("Broker")
119			.field("descriptor", &self.descriptor)
120			.field("client_id", &self.client_id)
121			.field("client_secret_set", &self.client_secret.is_some())
122			.finish()
123	}
124}