use serde::Deserialize;
use serde_json::from_slice;
use super::RateLimitKey;
pub(super) const ACCOUNT_BODY_LIMIT: usize = 64 * 1024;
const MAX_EMAIL_LEN: usize = 320;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum AccountLimit {
#[default]
Off,
ByEmail,
}
#[derive(Deserialize)]
struct TargetAccount {
email: String,
}
pub(super) fn account_key(route: &str, body: &[u8]) -> Option<RateLimitKey> {
let TargetAccount { email } = from_slice(body).ok()?;
let email = email.trim().to_lowercase();
let usable = !email.is_empty() && email.len() <= MAX_EMAIL_LEN;
usable.then(|| RateLimitKey::Account {
route: route.to_string(),
email,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn sign_in(email: &str) -> Option<RateLimitKey> {
Some(RateLimitKey::Account {
route: "/sign-in".to_string(),
email: email.to_string(),
})
}
#[test]
fn email_is_trimmed_and_lowercased() {
assert_eq!(
account_key(
"/sign-in",
br#"{"email":" Alice@IronFlow.dev ","password":"x"}"#
),
sign_in("alice@ironflow.dev")
);
}
#[test]
fn body_without_email_has_no_account() {
assert_eq!(account_key("/sign-in", br#"{"password":"x"}"#), None);
assert_eq!(account_key("/sign-in", br#"{"email":" "}"#), None);
assert_eq!(account_key("/sign-in", br#"{"email":42}"#), None);
}
#[test]
fn non_json_body_has_no_account() {
assert_eq!(account_key("/sign-in", b"email=alice@ironflow.dev"), None);
assert_eq!(account_key("/sign-in", b""), None);
}
#[test]
fn oversized_email_has_no_account() {
let long = format!(r#"{{"email":"{}@x.dev"}}"#, "a".repeat(MAX_EMAIL_LEN));
assert_eq!(account_key("/sign-in", long.as_bytes()), None);
}
#[test]
fn unicode_email_is_lowercased() {
assert_eq!(
account_key("/sign-in", r#"{"email":"ÉLODIE@x.dev"}"#.as_bytes()),
sign_in("élodie@x.dev")
);
}
#[test]
fn routes_get_distinct_account_keys() {
let body = br#"{"email":"alice@ironflow.dev"}"#;
let sign_up = account_key("/sign-up", body);
assert_eq!(account_key("/sign-in", body), sign_in("alice@ironflow.dev"));
assert_eq!(
sign_up,
Some(RateLimitKey::Account {
route: "/sign-up".to_string(),
email: "alice@ironflow.dev".to_string(),
})
);
assert_ne!(sign_up, account_key("/sign-in", body));
}
}