feat: honeypot signup field (#6776)

feat: account consent
This commit is contained in:
François-Xavier Talbot
2026-07-19 21:54:23 +00:00
committed by GitHub
parent 6b569e4ee4
commit c579925f03
5 changed files with 41 additions and 5 deletions
@@ -72,6 +72,18 @@
/> />
</div> </div>
<div class="hidden" aria-hidden="true">
<input
id="create-account-consent"
v-model="accountConsent"
name="account_consent"
type="checkbox"
tabindex="-1"
autocomplete="off"
/>
<label for="create-account-consent">I agree to receive account updates by email</label>
</div>
<ButtonStyled color="brand"> <ButtonStyled color="brand">
<button <button
class="!w-full font-bold" class="!w-full font-bold"
@@ -97,7 +109,7 @@ import {
StyledInput, StyledInput,
useVIntl, useVIntl,
} from '@modrinth/ui' } from '@modrinth/ui'
import { computed } from 'vue' import { computed, ref } from 'vue'
import HCaptcha from '@/components/ui/auth/HCaptcha.vue' import HCaptcha from '@/components/ui/auth/HCaptcha.vue'
@@ -109,7 +121,7 @@ interface AuthGlobals {
interface Props { interface Props {
globals?: AuthGlobals | null globals?: AuthGlobals | null
requiresDob?: boolean requiresDob?: boolean
onCompleteSignUp?: () => void onCompleteSignUp?: (accountConsent: boolean) => void
onSetCaptchaRef?: ((captchaRef: unknown) => void) | undefined onSetCaptchaRef?: ((captchaRef: unknown) => void) | undefined
} }
@@ -127,6 +139,7 @@ const dateOfBirthModel = defineModel<string | null>('dateOfBirth', { default: ''
const usernameModel = defineModel<string>('username', { default: '' }) const usernameModel = defineModel<string>('username', { default: '' })
const tokenModel = defineModel<string>('token', { default: '' }) const tokenModel = defineModel<string>('token', { default: '' })
const subscribeModel = defineModel<boolean>('subscribe', { default: false }) const subscribeModel = defineModel<boolean>('subscribe', { default: false })
const accountConsent = ref(false)
const maxInputDate = computed(() => `${new Date().getFullYear()}-12-31`) const maxInputDate = computed(() => `${new Date().getFullYear()}-12-31`)
@@ -198,7 +211,7 @@ function onCompleteSignUpClick() {
return return
} }
onCompleteSignUp() onCompleteSignUp(accountConsent.value)
} }
const messages = defineMessages({ const messages = defineMessages({
@@ -128,7 +128,7 @@ const { data: globals } = useQuery<AuthGlobalsResponse>({
}, },
}) })
async function completeOAuthSignUp() { async function completeOAuthSignUp(accountConsent: boolean) {
startLoading() startLoading()
try { try {
if (!oauthFlowState.value) { if (!oauthFlowState.value) {
@@ -140,6 +140,7 @@ async function completeOAuthSignUp() {
state: oauthFlowState.value, state: oauthFlowState.value,
challenge: token.value, challenge: token.value,
sign_up_newsletter: subscribe.value, sign_up_newsletter: subscribe.value,
account_consent: accountConsent,
}) })
await finishSignIn(res.session) await finishSignIn(res.session)
+2 -1
View File
@@ -250,7 +250,7 @@ function isUsernameTakenValidationError(error: unknown): boolean {
return errorCode === 'username_taken' return errorCode === 'username_taken'
} }
async function createAccount() { async function createAccount(accountConsent: boolean) {
startLoading() startLoading()
try { try {
const res = await client.labrinth.auth_v2.createAccount({ const res = await client.labrinth.auth_v2.createAccount({
@@ -259,6 +259,7 @@ async function createAccount() {
email: email.value, email: email.value,
challenge: token.value, challenge: token.value,
sign_up_newsletter: subscribe.value, sign_up_newsletter: subscribe.value,
account_consent: accountConsent,
}) })
await useAuth(res.session) await useAuth(res.session)
@@ -1438,6 +1438,19 @@ struct NewOAuthAccount {
pub state: String, pub state: String,
pub challenge: String, pub challenge: String,
pub sign_up_newsletter: bool, pub sign_up_newsletter: bool,
#[serde(default)]
pub account_consent: bool,
}
fn validate_account_consent(account_consent: bool) -> Result<(), ApiError> {
if account_consent {
info!("account consent trigger, denying");
return Err(ApiError::Request(eyre!(
"Sorry, something went wrong. Please try again"
)));
}
Ok(())
} }
/// Create account with OAuth. /// Create account with OAuth.
@@ -1463,6 +1476,8 @@ pub async fn create_oauth_account(
ApiError::InvalidInput(validation_errors_to_string(err, None)) ApiError::InvalidInput(validation_errors_to_string(err, None))
})?; })?;
validate_account_consent(new_account.account_consent)?;
if !check_hcaptcha(&req, &new_account.challenge).await? { if !check_hcaptcha(&req, &new_account.challenge).await? {
return Err(ApiError::Turnstile); return Err(ApiError::Turnstile);
} }
@@ -1709,6 +1724,8 @@ pub struct NewAccount {
pub email: String, pub email: String,
pub challenge: Option<String>, pub challenge: Option<String>,
pub sign_up_newsletter: Option<bool>, pub sign_up_newsletter: Option<bool>,
#[serde(default)]
pub account_consent: bool,
} }
#[derive(Debug, Validate)] #[derive(Debug, Validate)]
@@ -1996,6 +2013,8 @@ pub async fn create_account_with_password(
) -> Result<HttpResponse, ApiError> { ) -> Result<HttpResponse, ApiError> {
let new_account = new_account.into_inner(); let new_account = new_account.into_inner();
validate_account_consent(new_account.account_consent)?;
if !check_hcaptcha(&req, new_account.challenge.as_deref().unwrap_or("")) if !check_hcaptcha(&req, new_account.challenge.as_deref().unwrap_or(""))
.await? .await?
{ {
@@ -756,6 +756,7 @@ export namespace Labrinth {
email: string email: string
challenge: string challenge: string
sign_up_newsletter?: boolean sign_up_newsletter?: boolean
account_consent?: boolean
} }
export type CreateAccountResponse = { export type CreateAccountResponse = {
@@ -773,6 +774,7 @@ export namespace Labrinth {
state: string state: string
challenge: string challenge: string
sign_up_newsletter: boolean sign_up_newsletter: boolean
account_consent?: boolean
} }
export type CreateOAuthAccountResponse = { export type CreateOAuthAccountResponse = {