Ratelimit acct creation (#2933)
This commit is contained in:
parent
648b40a8f5
commit
3fa07f64d3
@ -6,7 +6,6 @@ use dashmap::{DashMap, DashSet};
|
|||||||
use redis::cmd;
|
use redis::cmd;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::Ipv6Addr;
|
|
||||||
|
|
||||||
const DOWNLOADS_NAMESPACE: &str = "downloads";
|
const DOWNLOADS_NAMESPACE: &str = "downloads";
|
||||||
const VIEWS_NAMESPACE: &str = "views";
|
const VIEWS_NAMESPACE: &str = "views";
|
||||||
@ -33,23 +32,8 @@ impl AnalyticsQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_ip(ip: Ipv6Addr) -> u64 {
|
|
||||||
if let Some(ip) = ip.to_ipv4_mapped() {
|
|
||||||
let octets = ip.octets();
|
|
||||||
u64::from_be_bytes([
|
|
||||||
octets[0], octets[1], octets[2], octets[3], 0, 0, 0, 0,
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
let octets = ip.octets();
|
|
||||||
u64::from_be_bytes([
|
|
||||||
octets[0], octets[1], octets[2], octets[3], octets[4],
|
|
||||||
octets[5], octets[6], octets[7],
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_view(&self, page_view: PageView) {
|
pub fn add_view(&self, page_view: PageView) {
|
||||||
let ip_stripped = Self::strip_ip(page_view.ip);
|
let ip_stripped = crate::util::ip::strip_ip(page_view.ip);
|
||||||
|
|
||||||
self.views_queue
|
self.views_queue
|
||||||
.entry((ip_stripped, page_view.project_id))
|
.entry((ip_stripped, page_view.project_id))
|
||||||
@ -57,7 +41,7 @@ impl AnalyticsQueue {
|
|||||||
.push(page_view);
|
.push(page_view);
|
||||||
}
|
}
|
||||||
pub fn add_download(&self, download: Download) {
|
pub fn add_download(&self, download: Download) {
|
||||||
let ip_stripped = Self::strip_ip(download.ip);
|
let ip_stripped = crate::util::ip::strip_ip(download.ip);
|
||||||
self.downloads_queue
|
self.downloads_queue
|
||||||
.insert((ip_stripped, download.project_id), download);
|
.insert((ip_stripped, download.project_id), download);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ use actix_web::{HttpRequest, HttpResponse};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr};
|
use std::net::Ipv4Addr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@ -39,16 +39,6 @@ pub const FILTERED_HEADERS: &[&str] = &[
|
|||||||
"x-vercel-ip-latitude",
|
"x-vercel-ip-latitude",
|
||||||
"x-vercel-ip-country",
|
"x-vercel-ip-country",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn convert_to_ip_v6(src: &str) -> Result<Ipv6Addr, AddrParseError> {
|
|
||||||
let ip_addr: IpAddr = src.parse()?;
|
|
||||||
|
|
||||||
Ok(match ip_addr {
|
|
||||||
IpAddr::V4(x) => x.to_ipv6_mapped(),
|
|
||||||
IpAddr::V6(x) => x,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UrlInput {
|
pub struct UrlInput {
|
||||||
url: String,
|
url: String,
|
||||||
@ -101,7 +91,7 @@ pub async fn page_view_ingest(
|
|||||||
})
|
})
|
||||||
.collect::<HashMap<String, String>>();
|
.collect::<HashMap<String, String>>();
|
||||||
|
|
||||||
let ip = convert_to_ip_v6(
|
let ip = crate::util::ip::convert_to_ip_v6(
|
||||||
if let Some(header) = headers.get("cf-connecting-ip") {
|
if let Some(header) = headers.get("cf-connecting-ip") {
|
||||||
header
|
header
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -108,7 +108,7 @@ pub async fn count_download(
|
|||||||
ApiError::InvalidInput("invalid download URL specified!".to_string())
|
ApiError::InvalidInput("invalid download URL specified!".to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let ip = crate::routes::analytics::convert_to_ip_v6(&download_body.ip)
|
let ip = crate::util::ip::convert_to_ip_v6(&download_body.ip)
|
||||||
.unwrap_or_else(|_| Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped());
|
.unwrap_or_else(|_| Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped());
|
||||||
|
|
||||||
analytics_queue.add_download(Download {
|
analytics_queue.add_download(Download {
|
||||||
|
|||||||
@ -1468,6 +1468,8 @@ pub struct NewAccount {
|
|||||||
pub sign_up_newsletter: Option<bool>,
|
pub sign_up_newsletter: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NEW_ACCOUNT_LIMITER_NAMESPACE: &str = "new_account_ips";
|
||||||
|
|
||||||
#[post("create")]
|
#[post("create")]
|
||||||
pub async fn create_account_with_password(
|
pub async fn create_account_with_password(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
@ -1533,19 +1535,6 @@ pub async fn create_account_with_password(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let flow = Flow::ConfirmEmail {
|
|
||||||
user_id,
|
|
||||||
confirm_email: new_account.email.clone(),
|
|
||||||
}
|
|
||||||
.insert(Duration::hours(24), &redis)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
send_email_verify(
|
|
||||||
new_account.email.clone(),
|
|
||||||
flow,
|
|
||||||
&format!("Welcome to Modrinth, {}!", new_account.username),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
crate::database::models::User {
|
crate::database::models::User {
|
||||||
id: user_id,
|
id: user_id,
|
||||||
github_id: None,
|
github_id: None,
|
||||||
@ -1577,6 +1566,49 @@ pub async fn create_account_with_password(
|
|||||||
let session = issue_session(req, user_id, &mut transaction, &redis).await?;
|
let session = issue_session(req, user_id, &mut transaction, &redis).await?;
|
||||||
let res = crate::models::sessions::Session::from(session, true, None);
|
let res = crate::models::sessions::Session::from(session, true, None);
|
||||||
|
|
||||||
|
// We limit each ip to creating 5 accounts in a six hour period
|
||||||
|
let ip = crate::util::ip::convert_to_ip_v6(&res.ip).map_err(|_| {
|
||||||
|
ApiError::InvalidInput("unable to parse user ip!".to_string())
|
||||||
|
})?;
|
||||||
|
let stripped_ip = crate::util::ip::strip_ip(ip).to_string();
|
||||||
|
|
||||||
|
let mut conn = redis.connect().await?;
|
||||||
|
let uses = if let Some(res) = conn
|
||||||
|
.get(NEW_ACCOUNT_LIMITER_NAMESPACE, &stripped_ip)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
res.parse::<u64>().unwrap_or(0)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if uses >= 5 {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"IP has been rate-limited.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.set(
|
||||||
|
NEW_ACCOUNT_LIMITER_NAMESPACE,
|
||||||
|
&stripped_ip,
|
||||||
|
&(uses + 1).to_string(),
|
||||||
|
Some(60 * 60 * 6),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let flow = Flow::ConfirmEmail {
|
||||||
|
user_id,
|
||||||
|
confirm_email: new_account.email.clone(),
|
||||||
|
}
|
||||||
|
.insert(Duration::hours(24), &redis)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
send_email_verify(
|
||||||
|
new_account.email.clone(),
|
||||||
|
flow,
|
||||||
|
&format!("Welcome to Modrinth, {}!", new_account.username),
|
||||||
|
)?;
|
||||||
|
|
||||||
if new_account.sign_up_newsletter.unwrap_or(false) {
|
if new_account.sign_up_newsletter.unwrap_or(false) {
|
||||||
sign_up_beehiiv(&new_account.email).await?;
|
sign_up_beehiiv(&new_account.email).await?;
|
||||||
}
|
}
|
||||||
|
|||||||
25
apps/labrinth/src/util/ip.rs
Normal file
25
apps/labrinth/src/util/ip.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use std::net::{AddrParseError, IpAddr, Ipv6Addr};
|
||||||
|
|
||||||
|
pub fn convert_to_ip_v6(src: &str) -> Result<Ipv6Addr, AddrParseError> {
|
||||||
|
let ip_addr: IpAddr = src.parse()?;
|
||||||
|
|
||||||
|
Ok(match ip_addr {
|
||||||
|
IpAddr::V4(x) => x.to_ipv6_mapped(),
|
||||||
|
IpAddr::V6(x) => x,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn strip_ip(ip: Ipv6Addr) -> u64 {
|
||||||
|
if let Some(ip) = ip.to_ipv4_mapped() {
|
||||||
|
let octets = ip.octets();
|
||||||
|
u64::from_be_bytes([
|
||||||
|
octets[0], octets[1], octets[2], octets[3], 0, 0, 0, 0,
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
let octets = ip.octets();
|
||||||
|
u64::from_be_bytes([
|
||||||
|
octets[0], octets[1], octets[2], octets[3], octets[4], octets[5],
|
||||||
|
octets[6], octets[7],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ pub mod env;
|
|||||||
pub mod ext;
|
pub mod ext;
|
||||||
pub mod guards;
|
pub mod guards;
|
||||||
pub mod img;
|
pub mod img;
|
||||||
|
pub mod ip;
|
||||||
pub mod ratelimit;
|
pub mod ratelimit;
|
||||||
pub mod redis;
|
pub mod redis;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user