rust-auth-utils 1.0.0

A rust port of @better-auth/utils.
Documentation
use crate::otp::OTP;
use std::time::{SystemTime, UNIX_EPOCH};

#[tokio::test]
async fn test_hotp_generation() {
    let key = "1234567890";
    let counter = 1;
    let digits = 6;
    let otp = OTP::new(key, Some(digits), None)
        .hotp(counter)
        .await
        .unwrap();
    assert_eq!(otp.len(), digits as usize);
    assert!(otp.chars().all(|c| c.is_ascii_digit()));
}

#[tokio::test]
async fn test_hotp_invalid_digits() {
    let key = "1234567890";
    let counter = 1;
    let result = OTP::new(key, Some(9), None).hotp(counter).await;
    assert!(result.is_err());
    assert_eq!(
        result.unwrap_err().to_string(),
        "Digits must be between 1 and 8"
    );

    let result = OTP::new(key, Some(0), None).hotp(counter).await;
    assert!(result.is_err());
    assert_eq!(
        result.unwrap_err().to_string(),
        "Digits must be between 1 and 8"
    );
}

#[tokio::test]
async fn test_totp_generation() {
    let secret = "1234567890";
    let digits = 6;
    let otp = OTP::new(secret, Some(digits), None).totp().await.unwrap();
    assert_eq!(otp.len(), digits as usize);
    assert!(otp.chars().all(|c| c.is_ascii_digit()));
}

#[tokio::test]
async fn test_totp_different_time_windows() {
    let secret = "1234567890";
    let seconds = 30;
    let digits = 6;

    let otp1 = OTP::new(secret, Some(digits), Some(seconds))
        .totp()
        .await
        .unwrap();

    // Simulate time passing by manually calculating a new counter
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();
    let counter1 = now / seconds as u64;
    let counter2 = counter1 + 1;

    let otp2 = OTP::new(secret, Some(digits), Some(seconds))
        .hotp(counter2)
        .await
        .unwrap();

    assert_ne!(otp1, otp2);
}

#[tokio::test]
async fn test_totp_verification() {
    let secret = "1234567890";
    let otp = OTP::new(secret, None, None);
    let totp = otp.totp().await.unwrap();
    let is_valid = otp.verify(&totp, None).await.unwrap();
    assert!(is_valid);
}

#[tokio::test]
async fn test_totp_invalid_verification() {
    let secret = "1234567890";
    let invalid_totp = "000000";
    let is_valid = OTP::new(secret, None, None)
        .verify(invalid_totp, None)
        .await
        .unwrap();
    assert!(!is_valid);
}

#[tokio::test]
async fn test_totp_window_verification() {
    let secret = "1234567890";
    let otp = OTP::new(secret, None, None);
    let totp = otp.totp().await.unwrap();
    let is_valid = otp.verify(&totp, Some(1)).await.unwrap();
    assert!(is_valid);
}

#[tokio::test]
async fn test_qr_code_generation() {
    let secret = "1234567890";
    let issuer = "my-site.com";
    let account = "account";
    let url = OTP::new(secret, None, None).url(issuer, account).unwrap();
    assert!(url.contains("otpauth://totp"));
    assert!(url.contains(issuer));
    assert!(url.contains(account));
}