Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ members = [
"packages/modrinth-log",
"packages/modrinth-maxmind",
"packages/modrinth-util",
"packages/neverbounce",
"packages/path-util",
]

Expand Down Expand Up @@ -132,6 +133,7 @@ modrinth-util = { path = "packages/modrinth-util" }
muralpay = { path = "packages/muralpay" }
murmur2 = "0.1.0"
native-dialog = "0.9.2"
neverbounce = { path = "packages/neverbounce" }
notify = { version = "8.2.0", default-features = false }
notify-debouncer-mini = { version = "0.7.0", default-features = false }
objc2-app-kit = { version = "0.3.2", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions apps/labrinth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ modrinth-content-management = { workspace = true }
modrinth-util = { workspace = true, features = ["decimal", "sentry", "utoipa"] }
muralpay = { workspace = true, features = ["client", "mock", "utoipa"] }
murmur2 = { workspace = true }
neverbounce = { workspace = true }
paste = { workspace = true }
path-util = { workspace = true }
postcard = { workspace = true }
Expand Down
3 changes: 3 additions & 0 deletions apps/labrinth/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ vars! {
SENDY_LIST_ID: String = "none";
SENDY_API_KEY: String = "none";

NEVERBOUNCE_API_KEY: String = "";
NEVERBOUNCE_BASE_URL: String = neverbounce::DEFAULT_API_URL;

CLICKHOUSE_REPLICATED: bool = false;
CLICKHOUSE_URL: String = "http://localhost:8123";
CLICKHOUSE_USER: String = "default";
Expand Down
71 changes: 47 additions & 24 deletions apps/labrinth/src/routes/internal/flows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::util::captcha::check_hcaptcha;
use crate::util::error::Context;
use crate::util::ext::get_image_ext;
use crate::util::img::upload_image_optimized;
use crate::util::neverbounce::{check_email, email_check_error_generic};
use crate::util::validate::validation_errors_to_string;
use actix_http::header::LOCATION;
use actix_web::http::StatusCode;
Expand Down Expand Up @@ -1440,7 +1441,7 @@ struct NewOAuthAccount {
pub sign_up_newsletter: bool,
}

/// Create account with OAuth.
/// Create account with OAuth.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -1481,6 +1482,10 @@ pub async fn create_oauth_account(
return Err(ApiError::Internal(eyre!("invalid flow kind")));
};

if let Some(email) = &user.email {
ensure_email_is_usable(email).await?;
}

let mut txn = db
.begin()
.await
Expand Down Expand Up @@ -1527,7 +1532,7 @@ struct DiscordCommunityHandoffPayload {
nonce: String,
}

/// Link Discord community.
/// Link Discord community.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -1604,7 +1609,7 @@ pub async fn discord_community_link(
Ok(web::Json(DiscordCommunityLinkResponse { url }))
}

/// Remove an auth provider.
/// Remove an auth provider.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -1794,6 +1799,20 @@ impl From<NewAccount> for AccountRegisterFlow {
}
}

async fn ensure_email_is_usable(email: &str) -> Result<(), ApiError> {
let result = check_email(email).await.map_err(ApiError::Request)?;

if matches!(
result,
neverbounce::VerificationResult::Invalid
| neverbounce::VerificationResult::Disposable
) {
return Err(ApiError::Request(email_check_error_generic()));
}

Ok(())
}

impl AccountRegisterFlow {
async fn validate(
self,
Expand Down Expand Up @@ -1947,7 +1966,7 @@ impl ReadyAccountRegisterFlow {
}
}

/// Validate password account creation.
/// Validate password account creation.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -1975,7 +1994,7 @@ pub async fn validate_create_account_with_password(
Ok(())
}

/// Create account with a password.
/// Create account with a password.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand All @@ -2002,6 +2021,8 @@ pub async fn create_account_with_password(
return Err(ApiError::Turnstile);
}

ensure_email_is_usable(&new_account.email).await?;

let mut transaction = pool.begin().await?;

let ready_flow = AccountRegisterFlow::from(new_account)
Expand All @@ -2024,7 +2045,7 @@ pub struct Login {
pub challenge: String,
}

/// Log in with a password.
/// Log in with a password.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -2185,7 +2206,7 @@ async fn validate_2fa_code(
}
}

/// Complete login with 2FA.
/// Complete login with 2FA.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -2245,7 +2266,7 @@ pub async fn login_2fa(
}
}

/// Start 2FA setup.
/// Start 2FA setup.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -2296,7 +2317,7 @@ pub async fn begin_2fa_flow(
}
}

/// Finish 2FA setup.
/// Finish 2FA setup.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -2431,7 +2452,7 @@ pub struct Remove2FA {
pub code: String,
}

/// Remove 2FA.
/// Remove 2FA.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -2531,7 +2552,7 @@ pub struct ResetPassword {
pub challenge: String,
}

/// Start password reset.
/// Start password reset.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -2637,7 +2658,7 @@ pub struct ChangePassword {
pub new_password: Option<String>,
}

/// Change password.
/// Change password.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -2803,7 +2824,7 @@ pub struct SetEmail {
pub email: String,
}

/// Set email address.
/// Set email address.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -2856,6 +2877,8 @@ pub async fn set_email(
));
}

ensure_email_is_usable(&email_address.email).await?;

let mut transaction = pool.begin().await?;

sqlx::query!(
Expand Down Expand Up @@ -2925,7 +2948,7 @@ pub async fn set_email(
Ok(HttpResponse::Ok().finish())
}

/// Resend verification email.
/// Resend verification email.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3000,7 +3023,7 @@ pub struct VerifyEmail {
pub flow: String,
}

/// Verify email address.
/// Verify email address.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3066,7 +3089,7 @@ pub async fn verify_email(
}
}

/// Subscribe to the newsletter.
/// Subscribe to the newsletter.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3115,7 +3138,7 @@ pub async fn subscribe_newsletter(
Ok(HttpResponse::NoContent().finish())
}

/// Get newsletter subscription status.
/// Get newsletter subscription status.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3165,7 +3188,7 @@ pub struct RegisterPasskeyResponse {
pub flow: String,
}

/// Start passkey registration.
/// Start passkey registration.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3265,7 +3288,7 @@ pub struct PasskeyResponse {
pub last_used: Option<chrono::DateTime<Utc>>,
}

/// Finish passkey registration.
/// Finish passkey registration.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3374,7 +3397,7 @@ pub struct AuthenticatePasskeyResponse {
pub flow: String,
}

/// Start passkey authentication.
/// Start passkey authentication.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3417,7 +3440,7 @@ pub struct AuthenticatePasskeyFinish {
pub credential: PublicKeyCredential,
}

/// Finish passkey authentication.
/// Finish passkey authentication.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3534,7 +3557,7 @@ pub async fn authenticate_passkey_finish(
}
}

/// List passkeys.
/// List passkeys.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3584,7 +3607,7 @@ pub struct RenamePasskey {
pub name: String,
}

/// Rename a passkey.
/// Rename a passkey.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down Expand Up @@ -3639,7 +3662,7 @@ pub async fn rename_passkey(
Ok(HttpResponse::NoContent().finish())
}

/// Delete a passkey.
/// Delete a passkey.
#[utoipa::path(
context_path = "/auth",
tag = "auth",
Expand Down
1 change: 1 addition & 0 deletions apps/labrinth/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod http;
pub mod img;
pub mod ip;
pub mod kafka;
pub mod neverbounce;
pub mod ratelimit;
pub mod redis;
pub mod routes;
Expand Down
Loading
Loading