mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
feat: email whitelist
This commit is contained in:
@@ -318,6 +318,7 @@ vars! {
|
|||||||
NEVERBOUNCE_BASE_URL: String = neverbounce::DEFAULT_API_URL;
|
NEVERBOUNCE_BASE_URL: String = neverbounce::DEFAULT_API_URL;
|
||||||
|
|
||||||
EMAIL_DOMAIN_BLACKLIST: StringCsv = StringCsv(vec![]);
|
EMAIL_DOMAIN_BLACKLIST: StringCsv = StringCsv(vec![]);
|
||||||
|
EMAIL_DOMAIN_WHITELIST: StringCsv = StringCsv(vec![]);
|
||||||
|
|
||||||
CLICKHOUSE_REPLICATED: bool = false;
|
CLICKHOUSE_REPLICATED: bool = false;
|
||||||
CLICKHOUSE_URL: String = "http://localhost:8123";
|
CLICKHOUSE_URL: String = "http://localhost:8123";
|
||||||
|
|||||||
@@ -1847,9 +1847,35 @@ impl From<NewAccount> for AccountRegisterFlow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The bundled disposable domain list is checked first. Environment blacklist
|
#[derive(PartialEq, Eq)]
|
||||||
/// entries are matched literally, unless they begin with `*.`, in which case
|
enum EmailDomainStatus {
|
||||||
/// they match any subdomain of the remaining suffix.
|
Whitelisted,
|
||||||
|
Neutral,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Environment 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 is_whitelisted_domain(domain: &str) -> bool {
|
||||||
|
let domain = domain.to_ascii_lowercase();
|
||||||
|
|
||||||
|
ENV.EMAIL_DOMAIN_WHITELIST
|
||||||
|
.iter()
|
||||||
|
.any(|entry| matches_domain_entry(&domain, entry))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The bundled disposable domain list is checked first, then the environment
|
||||||
|
/// blacklist.
|
||||||
fn is_blacklisted_domain(domain: &str) -> bool {
|
fn is_blacklisted_domain(domain: &str) -> bool {
|
||||||
let domain = domain.to_ascii_lowercase();
|
let domain = domain.to_ascii_lowercase();
|
||||||
|
|
||||||
@@ -1863,35 +1889,39 @@ fn is_blacklisted_domain(domain: &str) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ENV.EMAIL_DOMAIN_BLACKLIST.iter().any(|entry| {
|
ENV.EMAIL_DOMAIN_BLACKLIST
|
||||||
let entry = entry.trim().to_ascii_lowercase();
|
.iter()
|
||||||
|
.any(|entry| matches_domain_entry(&domain, entry))
|
||||||
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> {
|
fn ensure_email_domain_is_allowed(
|
||||||
|
email: &str,
|
||||||
|
) -> Result<EmailDomainStatus, ApiError> {
|
||||||
let Some((_, domain)) = email.rsplit_once('@') else {
|
let Some((_, domain)) = email.rsplit_once('@') else {
|
||||||
return Err(ApiError::Request(email_check_error_generic()));
|
return Err(ApiError::Request(email_check_error_generic()));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if is_whitelisted_domain(domain) {
|
||||||
|
info!(email.domain = domain, "whitelisted email domain, allowing");
|
||||||
|
return Ok(EmailDomainStatus::Whitelisted);
|
||||||
|
}
|
||||||
|
|
||||||
if is_blacklisted_domain(domain) {
|
if is_blacklisted_domain(domain) {
|
||||||
info!(email.domain = domain, "blacklisted email domain, denying");
|
info!(email.domain = domain, "blacklisted email domain, denying");
|
||||||
return Err(ApiError::Request(email_check_error_generic()));
|
return Err(ApiError::Request(email_check_error_generic()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(EmailDomainStatus::Neutral)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ensure_email_is_usable(email: &str) -> Result<(), ApiError> {
|
async fn ensure_email_is_usable(email: &str) -> Result<(), ApiError> {
|
||||||
ensure_email_domain_is_allowed(email)
|
let status = ensure_email_domain_is_allowed(email)
|
||||||
.wrap_api_err("validating email domain is allowed")?;
|
.wrap_api_err("validating email domain is allowed")?;
|
||||||
|
|
||||||
|
if status == EmailDomainStatus::Whitelisted {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let result = check_email(email)
|
let result = check_email(email)
|
||||||
.await
|
.await
|
||||||
.wrap_request_err("checking email address")?;
|
.wrap_request_err("checking email address")?;
|
||||||
|
|||||||
Reference in New Issue
Block a user