CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(common-oauth): RpFactory::build takes client_id and kind per call

Add DefaultRpFactory and DeterministicRpFactory per Phase 8 plan.
RpFactory::build now accepts (client_id: Url, kind: ClientKind) rather
than operating with no per-call parameters. DefaultRpFactory increments
a counter for per-flow seed diversity; DeterministicRpFactory reuses
a fixed seed for reproducible tests. Update interactive.rs to pass
client_id=http://localhost:3000 and kind=Public when building RPs.
Update test implementations to accept the new signature.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

+67 -4
+7 -1
src/commands/test/oauth/client/pipeline/interactive.rs
··· 292 292 } 293 293 InteractiveDriveMode::DriveRpInProcess { rp_factory } => { 294 294 // Drive the RP through the happy path. 295 - let rp = rp_factory.build(); 295 + let client_id = "http://localhost:3000" 296 + .parse() 297 + .expect("statically known-good URL"); 298 + let rp = rp_factory.build( 299 + client_id, 300 + crate::common::oauth::relying_party::ClientKind::Public, 301 + ); 296 302 297 303 // Discover AS. 298 304 let as_desc = match rp.discover_as(&server.active_base).await {
+55 -2
src/common/oauth/relying_party.rs
··· 127 127 128 128 /// Factory trait for building RelyingParty instances. 129 129 pub trait RpFactory: Send + Sync { 130 - /// Build a fresh RelyingParty instance. 131 - fn build(&self) -> RelyingParty; 130 + /// Build a fresh RelyingParty instance with the given client_id and kind. 131 + /// Implementations own the clock and RNG-seed state — callers only supply 132 + /// the target client's identity and kind. 133 + fn build(&self, client_id: Url, kind: ClientKind) -> RelyingParty; 134 + } 135 + 136 + /// Production factory: wraps a clock and derives per-flow seeds from an internal 137 + /// counter so successive builds yield distinct keys without needing OS randomness. 138 + pub struct DefaultRpFactory { 139 + clock: Arc<dyn Clock>, 140 + counter: Mutex<u64>, 141 + } 142 + 143 + impl DefaultRpFactory { 144 + /// Create a new DefaultRpFactory with the given clock. 145 + pub fn new(clock: Arc<dyn Clock>) -> Self { 146 + Self { 147 + clock, 148 + counter: Mutex::new(0), 149 + } 150 + } 151 + } 152 + 153 + impl RpFactory for DefaultRpFactory { 154 + fn build(&self, client_id: Url, kind: ClientKind) -> RelyingParty { 155 + let mut counter = self.counter.lock().unwrap(); 156 + let seed_base = *counter; 157 + *counter = counter.wrapping_add(1); 158 + drop(counter); 159 + 160 + let mut seed = [0u8; 32]; 161 + seed[0..8].copy_from_slice(&seed_base.to_le_bytes()); 162 + RelyingParty::new(client_id, kind, self.clock.clone(), seed) 163 + } 164 + } 165 + 166 + /// Deterministic factory used by tests: holds a fixed clock and a fixed base seed; 167 + /// each build yields a fresh RP with the same material, so repeated test runs 168 + /// produce identical requests, identical DPoP proofs, and identical snapshots. 169 + pub struct DeterministicRpFactory { 170 + clock: Arc<dyn Clock>, 171 + base_seed: [u8; 32], 172 + } 173 + 174 + impl DeterministicRpFactory { 175 + /// Create a new DeterministicRpFactory with the given clock and base seed. 176 + pub fn new(clock: Arc<dyn Clock>, base_seed: [u8; 32]) -> Self { 177 + Self { clock, base_seed } 178 + } 179 + } 180 + 181 + impl RpFactory for DeterministicRpFactory { 182 + fn build(&self, client_id: Url, kind: ClientKind) -> RelyingParty { 183 + RelyingParty::new(client_id, kind, self.clock.clone(), self.base_seed) 184 + } 132 185 } 133 186 134 187 impl RelyingParty {
+5 -1
tests/oauth_client_interactive.rs
··· 235 235 struct TestRpFactory; 236 236 237 237 impl atproto_devtool::common::oauth::relying_party::RpFactory for TestRpFactory { 238 - fn build(&self) -> atproto_devtool::common::oauth::relying_party::RelyingParty { 238 + fn build( 239 + &self, 240 + _client_id: Url, 241 + _kind: atproto_devtool::common::oauth::relying_party::ClientKind, 242 + ) -> atproto_devtool::common::oauth::relying_party::RelyingParty { 239 243 let clock = Arc::new(FakeClock::new(1_700_000_000)); 240 244 let client_id: Url = "http://localhost:3000".parse().unwrap(); 241 245 atproto_devtool::common::oauth::relying_party::RelyingParty::new(