mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
🧹 Refactor PairingGuard to utilize async Tokio Mutex (#1446)
This commit is contained in:
@@ -15,8 +15,8 @@ impl TelegramChannel {
|
||||
pub fn new(bot_token: String, allowed_users: Vec<String>, mention_only: bool) -> Self {
|
||||
let normalized_allowed = Self::normalize_allowed_users(allowed_users);
|
||||
let pairing = if normalized_allowed.is_empty() {
|
||||
let guard = PairingGuard::new(true, &[]);
|
||||
if let Some(code) = guard.pairing_code() {
|
||||
let (guard, code_opt) = PairingGuard::new(true, &[]);
|
||||
if let Some(code) = code_opt {
|
||||
println!(" 🔐 Telegram pairing required. One-time bind code: {code}");
|
||||
println!(" Send `{TELEGRAM_BIND_COMMAND} <code>` from your Telegram account.");
|
||||
}
|
||||
|
||||
@@ -215,14 +215,14 @@ fn telegram_user_denied_when_none_of_identities_match() {
|
||||
assert!(!ch.is_any_user_allowed(["unknown", "123456789"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telegram_pairing_enabled_with_empty_allowlist() {
|
||||
#[tokio::test]
|
||||
async fn telegram_pairing_enabled_with_empty_allowlist() {
|
||||
let ch = TelegramChannel::new("t".into(), vec![], false);
|
||||
assert!(ch.pairing_code_active());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telegram_pairing_disabled_with_nonempty_allowlist() {
|
||||
#[tokio::test]
|
||||
async fn telegram_pairing_disabled_with_nonempty_allowlist() {
|
||||
let ch = TelegramChannel::new("t".into(), vec!["alice".into()], false);
|
||||
assert!(!ch.pairing_code_active());
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ mod tests {
|
||||
let policy = SecurityPolicy::default();
|
||||
assert_eq!(policy.autonomy, AutonomyLevel::Supervised);
|
||||
|
||||
let guard = PairingGuard::new(false, &[]);
|
||||
let (guard, _) = PairingGuard::new(false, &[]);
|
||||
assert!(!guard.require_pairing());
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ impl PairingGuard {
|
||||
/// Existing tokens are accepted in both forms:
|
||||
/// - Plaintext (`zc_...`): hashed on load for backward compatibility
|
||||
/// - Already hashed (64-char hex): stored as-is
|
||||
pub fn new(require_pairing: bool, existing_tokens: &[String]) -> Self {
|
||||
pub fn new(require_pairing: bool, existing_tokens: &[String]) -> (Self, Option<String>) {
|
||||
let tokens: HashSet<String> = existing_tokens
|
||||
.iter()
|
||||
.map(|t| {
|
||||
@@ -64,12 +64,13 @@ impl PairingGuard {
|
||||
tokens.len(),
|
||||
code.is_some()
|
||||
);
|
||||
Self {
|
||||
let guard = Self {
|
||||
require_pairing,
|
||||
pairing_code: Arc::new(Mutex::new(code)),
|
||||
pairing_code: Arc::new(Mutex::new(code.clone())),
|
||||
paired_tokens: Arc::new(Mutex::new(tokens)),
|
||||
failed_attempts: Arc::new(Mutex::new((0, None))),
|
||||
}
|
||||
};
|
||||
(guard, code)
|
||||
}
|
||||
|
||||
/// The one-time pairing code (only set when no tokens exist yet).
|
||||
|
||||
@@ -5,27 +5,27 @@ use tokio::test;
|
||||
|
||||
#[test]
|
||||
async fn new_guard_generates_code_when_no_tokens() {
|
||||
let guard = PairingGuard::new(true, &[]);
|
||||
let (guard, _) = PairingGuard::new(true, &[]);
|
||||
assert!(guard.pairing_code().is_some());
|
||||
assert!(!guard.is_paired());
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn new_guard_no_code_when_tokens_exist() {
|
||||
let guard = PairingGuard::new(true, &["zc_existing".into()]);
|
||||
let (guard, _) = PairingGuard::new(true, &["zc_existing".into()]);
|
||||
assert!(guard.pairing_code().is_none());
|
||||
assert!(guard.is_paired());
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn new_guard_no_code_when_pairing_disabled() {
|
||||
let guard = PairingGuard::new(false, &[]);
|
||||
let (guard, _) = PairingGuard::new(false, &[]);
|
||||
assert!(guard.pairing_code().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn try_pair_correct_code() {
|
||||
let guard = PairingGuard::new(true, &[]);
|
||||
let (guard, _) = PairingGuard::new(true, &[]);
|
||||
let code = guard.pairing_code().unwrap().to_string();
|
||||
let token = guard.try_pair(&code).await.unwrap();
|
||||
assert!(token.is_some());
|
||||
@@ -35,7 +35,7 @@ async fn try_pair_correct_code() {
|
||||
|
||||
#[test]
|
||||
async fn try_pair_wrong_code() {
|
||||
let guard = PairingGuard::new(true, &[]);
|
||||
let (guard, _) = PairingGuard::new(true, &[]);
|
||||
let result = guard.try_pair("000000").await.unwrap();
|
||||
// Might succeed if code happens to be 000000, but extremely unlikely
|
||||
// Just check it returns Ok(None) normally
|
||||
@@ -44,14 +44,14 @@ async fn try_pair_wrong_code() {
|
||||
|
||||
#[test]
|
||||
async fn try_pair_empty_code() {
|
||||
let guard = PairingGuard::new(true, &[]);
|
||||
let (guard, _) = PairingGuard::new(true, &[]);
|
||||
assert!(guard.try_pair("").await.unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn is_authenticated_with_valid_token() {
|
||||
// Pass plaintext token — PairingGuard hashes it on load
|
||||
let guard = PairingGuard::new(true, &["zc_valid".into()]);
|
||||
let (guard, _) = PairingGuard::new(true, &["zc_valid".into()]);
|
||||
assert!(guard.is_authenticated("zc_valid"));
|
||||
}
|
||||
|
||||
@@ -59,26 +59,26 @@ async fn is_authenticated_with_valid_token() {
|
||||
async fn is_authenticated_with_prehashed_token() {
|
||||
// Pass an already-hashed token (64 hex chars)
|
||||
let hashed = hash_token("zc_valid");
|
||||
let guard = PairingGuard::new(true, &[hashed]);
|
||||
let (guard, _) = PairingGuard::new(true, &[hashed]);
|
||||
assert!(guard.is_authenticated("zc_valid"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn is_authenticated_with_invalid_token() {
|
||||
let guard = PairingGuard::new(true, &["zc_valid".into()]);
|
||||
let (guard, _) = PairingGuard::new(true, &["zc_valid".into()]);
|
||||
assert!(!guard.is_authenticated("zc_invalid"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn is_authenticated_when_pairing_disabled() {
|
||||
let guard = PairingGuard::new(false, &[]);
|
||||
let (guard, _) = PairingGuard::new(false, &[]);
|
||||
assert!(guard.is_authenticated("anything"));
|
||||
assert!(guard.is_authenticated(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn tokens_returns_hashes() {
|
||||
let guard = PairingGuard::new(true, &["zc_a".into(), "zc_b".into()]);
|
||||
let (guard, _) = PairingGuard::new(true, &["zc_a".into(), "zc_b".into()]);
|
||||
let tokens = guard.tokens();
|
||||
assert_eq!(tokens.len(), 2);
|
||||
// Tokens should be stored as 64-char hex hashes, not plaintext
|
||||
@@ -91,7 +91,7 @@ async fn tokens_returns_hashes() {
|
||||
|
||||
#[test]
|
||||
async fn pair_then_authenticate() {
|
||||
let guard = PairingGuard::new(true, &[]);
|
||||
let (guard, _) = PairingGuard::new(true, &[]);
|
||||
let code = guard.pairing_code().unwrap().to_string();
|
||||
let token = guard.try_pair(&code).await.unwrap().unwrap();
|
||||
assert!(guard.is_authenticated(&token));
|
||||
@@ -203,7 +203,7 @@ async fn generate_token_has_prefix_and_hex_payload() {
|
||||
|
||||
#[test]
|
||||
async fn brute_force_lockout_after_max_attempts() {
|
||||
let guard = PairingGuard::new(true, &[]);
|
||||
let (guard, _) = PairingGuard::new(true, &[]);
|
||||
// Exhaust all attempts with wrong codes
|
||||
for i in 0..MAX_PAIR_ATTEMPTS {
|
||||
let result = guard.try_pair(&format!("wrong_{i}")).await;
|
||||
@@ -225,7 +225,7 @@ async fn brute_force_lockout_after_max_attempts() {
|
||||
|
||||
#[test]
|
||||
async fn correct_code_resets_failed_attempts() {
|
||||
let guard = PairingGuard::new(true, &[]);
|
||||
let (guard, _) = PairingGuard::new(true, &[]);
|
||||
let code = guard.pairing_code().unwrap().to_string();
|
||||
// Fail a few times
|
||||
for _ in 0..3 {
|
||||
@@ -238,7 +238,7 @@ async fn correct_code_resets_failed_attempts() {
|
||||
|
||||
#[test]
|
||||
async fn lockout_returns_remaining_seconds() {
|
||||
let guard = PairingGuard::new(true, &[]);
|
||||
let (guard, _) = PairingGuard::new(true, &[]);
|
||||
for _ in 0..MAX_PAIR_ATTEMPTS {
|
||||
let _ = guard.try_pair("wrong").await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user