use crate::api::schemas::crypto::PublicKey;
use crate::api::schemas::keys::{OneTimePreKey, SignedPreKey};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
static USERNAME_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9_]{3,50}$").expect("Hardcoded username validation regex should compile"));
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegistrationRequest {
pub username: String,
pub password: String,
pub identity_key: PublicKey,
pub registration_id: i32,
pub signed_pre_key: SignedPreKey,
pub one_time_pre_keys: Vec<OneTimePreKey>,
}
impl RegistrationRequest {
pub fn validate(&self) -> Result<(), String> {
if self.username.trim().is_empty() {
return Err("Username cannot be empty".into());
}
if !USERNAME_REGEX.is_match(&self.username) {
return Err(
"Username must be between 3 and 50 characters and can only contain letters, numbers, and underscores"
.into(),
);
}
if self.password.len() < 12 {
return Err("Password must be at least 12 characters long".into());
}
let mut unique_ids = std::collections::HashSet::with_capacity(self.one_time_pre_keys.len());
for pk in &self.one_time_pre_keys {
if !unique_ids.insert(pk.key_id) {
return Err(format!("Duplicate prekey ID: {}", pk.key_id));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::schemas::crypto::{PublicKey as SchemaPublicKey, Signature};
use crate::api::schemas::keys::{OneTimePreKey, SignedPreKey};
fn mock_registration(password: &str) -> RegistrationRequest {
RegistrationRequest {
username: "testuser".into(),
password: password.into(),
identity_key: SchemaPublicKey("A".repeat(44)), registration_id: 123,
signed_pre_key: SignedPreKey {
key_id: 1,
public_key: SchemaPublicKey("B".repeat(44)),
signature: Signature("C".repeat(88)),
},
one_time_pre_keys: vec![],
}
}
#[test]
fn test_registration_validation_valid() {
let reg = mock_registration("password12345");
assert!(reg.validate().is_ok());
}
#[test]
fn test_registration_validation_too_short() {
let reg = mock_registration("short");
let res = reg.validate();
assert!(res.is_err());
assert_eq!(res.expect_err("Password too short should fail"), "Password must be at least 12 characters long");
}
#[test]
fn test_registration_validation_duplicate_keys() {
let mut reg = mock_registration("password12345");
reg.one_time_pre_keys = vec![
OneTimePreKey { key_id: 1, public_key: SchemaPublicKey("A".repeat(44)) },
OneTimePreKey { key_id: 1, public_key: SchemaPublicKey("B".repeat(44)) },
];
let res = reg.validate();
assert!(res.is_err());
assert_eq!(res.expect_err("Duplicate prekey IDs should fail"), "Duplicate prekey ID: 1");
}
#[test]
fn test_registration_validation_username_empty() {
let mut reg = mock_registration("password12345");
reg.username = String::new();
let res = reg.validate();
assert!(res.is_err());
assert_eq!(res.expect_err("Empty username should fail"), "Username cannot be empty");
}
#[test]
fn test_registration_validation_username_whitespace() {
let mut reg = mock_registration("password12345");
reg.username = " ".into();
let res = reg.validate();
assert!(res.is_err());
assert_eq!(res.expect_err("Whitespace username should fail"), "Username cannot be empty");
}
#[test]
fn test_registration_validation_username_too_long() {
let mut reg = mock_registration("password12345");
reg.username = "a".repeat(51);
let res = reg.validate();
assert!(res.is_err());
assert_eq!(
res.expect_err("Username too long should fail"),
"Username must be between 3 and 50 characters and can only contain letters, numbers, and underscores"
);
}
}
#[derive(Debug, Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RefreshRequest {
pub refresh_token: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LogoutRequest {
pub refresh_token: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthResponse {
pub token: String,
pub refresh_token: String,
pub expires_at: i64,
}