use base64::Engine;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, bon::Builder)]
pub struct Crv1Challenge {
#[builder(into)]
pub flags: String,
#[builder(into)]
pub state_id: String,
#[builder(into)]
pub username: String,
#[builder(into)]
pub challenge_text: String,
}
impl fmt::Display for Crv1Challenge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let engine = base64::engine::general_purpose::STANDARD;
write!(
f,
"CRV1:{}:{}:{}:{}",
self.flags,
engine.encode(&self.state_id),
engine.encode(&self.username),
self.challenge_text,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_produces_valid_crv1_string() {
let challenge = Crv1Challenge::builder()
.flags("R,E")
.state_id("session-123")
.username("jdoe")
.challenge_text("Enter OTP")
.build();
let wire = challenge.to_string();
assert!(wire.starts_with("CRV1:R,E:"));
assert!(wire.ends_with(":Enter OTP"));
let parts: Vec<&str> = wire.splitn(5, ':').collect();
assert_eq!(parts.len(), 5);
assert_eq!(parts[0], "CRV1");
assert_eq!(parts[1], "R,E");
let engine = base64::engine::general_purpose::STANDARD;
let state_id = engine.decode(parts[2]).expect("valid base64");
assert_eq!(state_id, b"session-123");
let username = engine.decode(parts[3]).expect("valid base64");
assert_eq!(username, b"jdoe");
assert_eq!(parts[4], "Enter OTP");
}
#[test]
fn empty_fields() {
let challenge = Crv1Challenge::builder()
.flags("")
.state_id("")
.username("")
.challenge_text("")
.build();
let wire = challenge.to_string();
assert_eq!(wire, "CRV1::::");
}
#[test]
fn roundtrip_with_special_characters() {
let challenge = Crv1Challenge::builder()
.flags("R")
.state_id("id with spaces & symbols!")
.username("user@example.com")
.challenge_text("Enter your PIN:")
.build();
let wire = challenge.to_string();
let parts: Vec<&str> = wire.splitn(5, ':').collect();
let engine = base64::engine::general_purpose::STANDARD;
let state_id = String::from_utf8(engine.decode(parts[2]).unwrap()).unwrap();
assert_eq!(state_id, "id with spaces & symbols!");
let username = String::from_utf8(engine.decode(parts[3]).unwrap()).unwrap();
assert_eq!(username, "user@example.com");
}
}