mirror of
https://github.com/modrinth/code.git
synced 2026-09-05 06:19:11 +00:00
feat: bring back email domain blacklist
This commit is contained in:
@@ -314,7 +314,7 @@ vars! {
|
||||
SENDY_LIST_ID: String = "none";
|
||||
SENDY_API_KEY: String = "none";
|
||||
|
||||
SKIP_EMAIL_CHECK_DOMAINS: String = "";
|
||||
SKIP_EMAIL_CHECK_DOMAINS: StringCsv = StringCsv(vec![]);
|
||||
|
||||
NEVERBOUNCE_API_KEY: String = "";
|
||||
NEVERBOUNCE_BASE_URL: String = neverbounce::DEFAULT_API_URL;
|
||||
@@ -323,6 +323,8 @@ vars! {
|
||||
USERCHECK_GATE_ID: String = "";
|
||||
USERCHECK_BASE_URL: String = crate::util::usercheck::DEFAULT_API_URL;
|
||||
|
||||
EMAIL_DOMAIN_BLACKLIST: StringCsv = StringCsv(vec![]);
|
||||
|
||||
CLICKHOUSE_REPLICATED: bool = false;
|
||||
CLICKHOUSE_URL: String = "http://localhost:8123";
|
||||
CLICKHOUSE_USER: String = "default";
|
||||
|
||||
@@ -1901,17 +1901,54 @@ impl From<NewAccount> for AccountRegisterFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/// Domains listed in `SKIP_EMAIL_CHECK_DOMAINS` bypass every email check.
|
||||
/// List entries are matched literally, unless they begin with `*.`, in which
|
||||
/// case they match any subdomain of the remaining suffix.
|
||||
fn matches_domain_entry(domain: &str, entry: &str) -> bool {
|
||||
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 domain_of(email: &str) -> Option<String> {
|
||||
email
|
||||
.rsplit_once('@')
|
||||
.map(|(_, domain)| domain.to_ascii_lowercase())
|
||||
}
|
||||
|
||||
fn domain_matches_list(domain: &str, list: &[String]) -> bool {
|
||||
list.iter().any(|entry| matches_domain_entry(domain, entry))
|
||||
}
|
||||
|
||||
/// Domains listed in `SKIP_EMAIL_CHECK_DOMAINS` bypass every email check,
|
||||
/// including the blacklist.
|
||||
fn email_checks_skipped(email: &str) -> bool {
|
||||
let Some((_, domain)) = email.rsplit_once('@') else {
|
||||
return false;
|
||||
domain_of(email).is_some_and(|domain| {
|
||||
domain_matches_list(&domain, &ENV.SKIP_EMAIL_CHECK_DOMAINS)
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_email_domain_is_not_blacklisted(email: &str) -> Result<(), ApiError> {
|
||||
let Some(domain) = domain_of(email) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
ENV.SKIP_EMAIL_CHECK_DOMAINS
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|skipped| !skipped.is_empty())
|
||||
.any(|skipped| skipped.eq_ignore_ascii_case(domain))
|
||||
if !domain_matches_list(&domain, &ENV.EMAIL_DOMAIN_BLACKLIST) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!(
|
||||
email.domain = domain.as_str(),
|
||||
"blacklisted email domain, denying",
|
||||
);
|
||||
|
||||
Err(ApiError::Request(eyre!(
|
||||
"This domain name may not be used ({domain})!"
|
||||
)))
|
||||
}
|
||||
|
||||
/// Runs the UserCheck gate, which covers both password and OAuth signups.
|
||||
@@ -1923,6 +1960,8 @@ async fn ensure_email_passes_gate(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ensure_email_domain_is_not_blacklisted(email)?;
|
||||
|
||||
let action = check_email_gate(redis, email)
|
||||
.await
|
||||
.wrap_request_err("checking email address")?;
|
||||
|
||||
Reference in New Issue
Block a user