Fix MaxMind (#4590)

This commit is contained in:
aecsocket
2025-10-20 08:58:31 -07:00
committed by GitHub
parent 63d8f70e20
commit 24bec6baba
7 changed files with 26 additions and 33 deletions

View File

@@ -315,7 +315,7 @@ pub fn app_config(
.app_data(web::Data::new(labrinth_config.ip_salt.clone())) .app_data(web::Data::new(labrinth_config.ip_salt.clone()))
.app_data(web::Data::new(labrinth_config.analytics_queue.clone())) .app_data(web::Data::new(labrinth_config.analytics_queue.clone()))
.app_data(web::Data::new(labrinth_config.clickhouse.clone())) .app_data(web::Data::new(labrinth_config.clickhouse.clone()))
.app_data(web::Data::new(labrinth_config.maxmind.clone())) .app_data(labrinth_config.maxmind.clone())
.app_data(labrinth_config.active_sockets.clone()) .app_data(labrinth_config.active_sockets.clone())
.app_data(labrinth_config.automated_moderation_queue.clone()) .app_data(labrinth_config.automated_moderation_queue.clone())
.app_data(web::Data::new(labrinth_config.stripe_client.clone())) .app_data(web::Data::new(labrinth_config.stripe_client.clone()))
@@ -478,6 +478,7 @@ pub fn check_env_vars() -> bool {
failed |= check_var::<String>("CLICKHOUSE_PASSWORD"); failed |= check_var::<String>("CLICKHOUSE_PASSWORD");
failed |= check_var::<String>("CLICKHOUSE_DATABASE"); failed |= check_var::<String>("CLICKHOUSE_DATABASE");
failed |= check_var::<String>("MAXMIND_ACCOUNT_ID");
failed |= check_var::<String>("MAXMIND_LICENSE_KEY"); failed |= check_var::<String>("MAXMIND_LICENSE_KEY");
failed |= check_var::<String>("FLAME_ANVIL_URL"); failed |= check_var::<String>("FLAME_ANVIL_URL");

View File

@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use std::hash::Hash; use std::hash::Hash;
use std::net::Ipv6Addr; use std::net::Ipv6Addr;
#[derive(Row, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Row, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
pub struct Download { pub struct Download {
pub recorded: i64, pub recorded: i64,
pub domain: String, pub domain: String,
@@ -24,7 +24,7 @@ pub struct Download {
pub headers: Vec<(String, String)>, pub headers: Vec<(String, String)>,
} }
#[derive(Row, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Row, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
pub struct PageView { pub struct PageView {
pub recorded: i64, pub recorded: i64,
pub domain: String, pub domain: String,

View File

@@ -1,23 +0,0 @@
use modrinth_maxmind::{MaxMind, geoip2};
use std::net::Ipv6Addr;
pub struct MaxMindIndexer {
pub maxmind: MaxMind,
}
impl MaxMindIndexer {
pub async fn new() -> Self {
Self {
maxmind: MaxMind::new().await,
}
}
pub async fn query(&self, ip: Ipv6Addr) -> Option<String> {
let reader = self.maxmind.reader.as_ref()?;
reader
.lookup::<geoip2::Country>(ip.into())
.ok()?
.and_then(|c| c.country)
.and_then(|c| c.iso_code.map(|s| s.to_string()))
}
}

View File

@@ -1,7 +1,6 @@
pub mod analytics; pub mod analytics;
pub mod billing; pub mod billing;
pub mod email; pub mod email;
pub mod maxmind;
pub mod moderation; pub mod moderation;
pub mod payouts; pub mod payouts;
pub mod session; pub mod session;

View File

@@ -3,18 +3,19 @@ use crate::database::redis::RedisPool;
use crate::models::analytics::{PageView, Playtime}; use crate::models::analytics::{PageView, Playtime};
use crate::models::pats::Scopes; use crate::models::pats::Scopes;
use crate::queue::analytics::AnalyticsQueue; use crate::queue::analytics::AnalyticsQueue;
use crate::queue::maxmind::MaxMindIndexer;
use crate::queue::session::AuthQueue; use crate::queue::session::AuthQueue;
use crate::routes::ApiError; use crate::routes::ApiError;
use crate::util::date::get_current_tenths_of_ms; use crate::util::date::get_current_tenths_of_ms;
use crate::util::env::parse_strings_from_var; use crate::util::env::parse_strings_from_var;
use actix_web::{HttpRequest, HttpResponse}; use actix_web::{HttpRequest, HttpResponse};
use actix_web::{post, web}; use actix_web::{post, web};
use modrinth_maxmind::MaxMind;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::sync::Arc; use std::sync::Arc;
use tracing::trace;
use url::Url; use url::Url;
pub const FILTERED_HEADERS: &[&str] = &[ pub const FILTERED_HEADERS: &[&str] = &[
@@ -48,7 +49,7 @@ pub struct UrlInput {
#[post("view")] #[post("view")]
pub async fn page_view_ingest( pub async fn page_view_ingest(
req: HttpRequest, req: HttpRequest,
maxmind: web::Data<Arc<MaxMindIndexer>>, maxmind: web::Data<MaxMind>,
analytics_queue: web::Data<Arc<AnalyticsQueue>>, analytics_queue: web::Data<Arc<AnalyticsQueue>>,
session_queue: web::Data<AuthQueue>, session_queue: web::Data<AuthQueue>,
url_input: web::Json<UrlInput>, url_input: web::Json<UrlInput>,
@@ -113,7 +114,7 @@ pub async fn page_view_ingest(
user_id: 0, user_id: 0,
project_id: 0, project_id: 0,
ip, ip,
country: maxmind.query(ip).await.unwrap_or_default(), country: maxmind.query_country(ip).await.unwrap_or_default(),
user_agent: headers.get("user-agent").cloned().unwrap_or_default(), user_agent: headers.get("user-agent").cloned().unwrap_or_default(),
headers: headers headers: headers
.into_iter() .into_iter()
@@ -154,6 +155,7 @@ pub async fn page_view_ingest(
view.user_id = user.id.0; view.user_id = user.id.0;
} }
trace!("Ingested page view {view:?}");
analytics_queue.add_view(view); analytics_queue.add_view(view);
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))

View File

@@ -6,7 +6,6 @@ use crate::models::ids::ProjectId;
use crate::models::pats::Scopes; use crate::models::pats::Scopes;
use crate::models::threads::MessageBody; use crate::models::threads::MessageBody;
use crate::queue::analytics::AnalyticsQueue; use crate::queue::analytics::AnalyticsQueue;
use crate::queue::maxmind::MaxMindIndexer;
use crate::queue::moderation::AUTOMOD_ID; use crate::queue::moderation::AUTOMOD_ID;
use crate::queue::session::AuthQueue; use crate::queue::session::AuthQueue;
use crate::routes::ApiError; use crate::routes::ApiError;
@@ -14,6 +13,7 @@ use crate::search::SearchConfig;
use crate::util::date::get_current_tenths_of_ms; use crate::util::date::get_current_tenths_of_ms;
use crate::util::guards::admin_key_guard; use crate::util::guards::admin_key_guard;
use actix_web::{HttpRequest, HttpResponse, patch, post, web}; use actix_web::{HttpRequest, HttpResponse, patch, post, web};
use modrinth_maxmind::MaxMind;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use std::collections::HashMap; use std::collections::HashMap;
@@ -48,7 +48,7 @@ pub async fn count_download(
req: HttpRequest, req: HttpRequest,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
redis: web::Data<RedisPool>, redis: web::Data<RedisPool>,
maxmind: web::Data<Arc<MaxMindIndexer>>, maxmind: web::Data<MaxMind>,
analytics_queue: web::Data<Arc<AnalyticsQueue>>, analytics_queue: web::Data<Arc<AnalyticsQueue>>,
session_queue: web::Data<AuthQueue>, session_queue: web::Data<AuthQueue>,
download_body: web::Json<DownloadBody>, download_body: web::Json<DownloadBody>,
@@ -132,7 +132,7 @@ pub async fn count_download(
project_id: project_id as u64, project_id: project_id as u64,
version_id: version_id as u64, version_id: version_id as u64,
ip, ip,
country: maxmind.query(ip).await.unwrap_or_default(), country: maxmind.query_country(ip).await.unwrap_or_default(),
user_agent: download_body user_agent: download_body
.headers .headers
.get("user-agent") .get("user-agent")

View File

@@ -2,6 +2,7 @@
use std::{ use std::{
io::{Cursor, Read}, io::{Cursor, Read},
net::IpAddr,
path::Path, path::Path,
sync::Arc, sync::Arc,
}; };
@@ -49,6 +50,19 @@ impl MaxMind {
.ok(), .ok(),
} }
} }
/// Queries the MaxMind database for the ISO country code of an IP address.
///
/// If MaxMind is not configured or the database could not be read, returns
/// [`None`].
pub async fn query_country(&self, ip: impl Into<IpAddr>) -> Option<String> {
let reader = self.reader.as_ref()?;
reader
.lookup::<geoip2::Country>(ip.into())
.ok()?
.and_then(|c| c.country)
.and_then(|c| c.iso_code.map(|s| s.to_string()))
}
} }
/// Creates a [`maxminddb::Reader`] for use in [`MaxMind::reader`]. /// Creates a [`maxminddb::Reader`] for use in [`MaxMind::reader`].