Compare commits

...
Author SHA1 Message Date
Michael H. 96f06e9617 feat: wildcard support 2026-07-20 11:54:06 +02:00
Michael H. 32109ad737 feat: email blacklist 2026-07-20 11:33:18 +02:00
2 changed files with 34 additions and 0 deletions
+2
View File
@@ -238,6 +238,8 @@ vars! {
NEVERBOUNCE_API_KEY: String = "";
NEVERBOUNCE_BASE_URL: String = neverbounce::DEFAULT_API_URL;
EMAIL_DOMAIN_BLACKLIST: StringCsv = StringCsv(vec![]);
CLICKHOUSE_REPLICATED: bool = false;
CLICKHOUSE_URL: String = "http://localhost:8123";
CLICKHOUSE_USER: String = "default";
@@ -1816,7 +1816,39 @@ impl From<NewAccount> for AccountRegisterFlow {
}
}
/// Blacklist entries are matched literally, unless they begin with `*.`, in
/// which case they match any subdomain of the remaining suffix.
fn is_blacklisted_domain(domain: &str) -> bool {
let domain = domain.to_ascii_lowercase();
ENV.EMAIL_DOMAIN_BLACKLIST.iter().any(|entry| {
let entry = entry.trim().to_ascii_lowercase();
match entry.strip_prefix("*.") {
Some(suffix) => domain
.strip_suffix(suffix)
.is_some_and(|subdomain| subdomain.ends_with('.')),
None => entry == domain,
}
})
}
fn ensure_email_domain_is_allowed(email: &str) -> Result<(), ApiError> {
let Some((_, domain)) = email.rsplit_once('@') else {
return Err(ApiError::Request(email_check_error_generic()));
};
if is_blacklisted_domain(domain) {
info!(email.domain = domain, "blacklisted email domain, denying");
return Err(ApiError::Request(email_check_error_generic()));
}
Ok(())
}
async fn ensure_email_is_usable(email: &str) -> Result<(), ApiError> {
ensure_email_domain_is_allowed(email)?;
let result = check_email(email).await.map_err(ApiError::Request)?;
if matches!(