test: add comprehensive pairing code consumption tests

Add comprehensive tests for pairing code consumption feature
This commit is contained in:
Edvard Schøyen 2026-02-15 07:36:54 -05:00 committed by GitHub
parent dc654f6835
commit bd02d73ecc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -28,7 +28,7 @@ pub struct PairingGuard {
/// Whether pairing is required at all. /// Whether pairing is required at all.
require_pairing: bool, require_pairing: bool,
/// One-time pairing code (generated on startup, consumed on first pair). /// One-time pairing code (generated on startup, consumed on first pair).
pairing_code: Option<String>, pairing_code: Mutex<Option<String>>,
/// Set of SHA-256 hashed bearer tokens (persisted across restarts). /// Set of SHA-256 hashed bearer tokens (persisted across restarts).
paired_tokens: Mutex<HashSet<String>>, paired_tokens: Mutex<HashSet<String>>,
/// Brute-force protection: failed attempt counter + lockout time. /// Brute-force protection: failed attempt counter + lockout time.
@ -62,15 +62,18 @@ impl PairingGuard {
}; };
Self { Self {
require_pairing, require_pairing,
pairing_code: code, pairing_code: Mutex::new(code),
paired_tokens: Mutex::new(tokens), paired_tokens: Mutex::new(tokens),
failed_attempts: Mutex::new((0, None)), failed_attempts: Mutex::new((0, None)),
} }
} }
/// The one-time pairing code (only set when no tokens exist yet). /// The one-time pairing code (only set when no tokens exist yet).
pub fn pairing_code(&self) -> Option<&str> { pub fn pairing_code(&self) -> Option<String> {
self.pairing_code.as_deref() self.pairing_code
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
} }
/// Whether pairing is required at all. /// Whether pairing is required at all.
@ -97,7 +100,12 @@ impl PairingGuard {
} }
} }
if let Some(ref expected) = self.pairing_code { {
let mut pairing_code = self
.pairing_code
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(ref expected) = *pairing_code {
if constant_time_eq(code.trim(), expected.trim()) { if constant_time_eq(code.trim(), expected.trim()) {
// Reset failed attempts on success // Reset failed attempts on success
{ {
@ -113,9 +121,14 @@ impl PairingGuard {
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner); .unwrap_or_else(std::sync::PoisonError::into_inner);
tokens.insert(hash_token(&token)); tokens.insert(hash_token(&token));
// Consume the pairing code so it cannot be reused
*pairing_code = None;
return Ok(Some(token)); return Ok(Some(token));
} }
} }
}
// Increment failed attempts // Increment failed attempts
{ {