From 462e2666042a69c6ee9424ee9fbed5ba112f87e9 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 9 Sep 2025 09:37:32 +0200 Subject: [PATCH] fix: Registration Form Legal Checkbox Logic (#10597) Closes #10498 The registration form's legal checkboxes had incorrect validation logic that prevented users from completing registration when only one legal document (ToS or Privacy Policy) was configured, or when no legal documents were required. additionally removes a duplicate description for "or use Identity Provider" # Which Problems Are Solved Having only partial legal documents was blocking users to register. The logic now conditionally renders checkboxes and checks if all provided documents are accepted. # How the Problems Are Solved - Fixed checkbox validation: Now properly validates based on which legal documents are actually available - acceptance logic: Only requires acceptance of checkboxes that are shown - No legal docs support: Users can proceed when no legal documents are configured - Proper state management: Fixed checkbox state tracking and mixed-up test IDs --------- Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com> (cherry picked from commit b9b9baf67fdf5cb62189f789178ed2938687b0fd) --- apps/login/locales/de.json | 1 - apps/login/locales/en.json | 1 - apps/login/locales/es.json | 1 - apps/login/locales/it.json | 1 - apps/login/locales/pl.json | 1 - apps/login/locales/ru.json | 1 - apps/login/locales/zh.json | 1 - apps/login/src/app/(login)/register/page.tsx | 19 +- .../components/privacy-policy-checkboxes.tsx | 44 +- apps/login/src/components/register-form.tsx | 67 +- internal/query/v2-default.json | 5091 ++++++++--------- 11 files changed, 2597 insertions(+), 2631 deletions(-) diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 05183374b76..55a63551204 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -222,7 +222,6 @@ "termsOfService": "Nutzungsbedingungen", "privacyPolicy": "Datenschutzrichtlinie", "submit": "Weiter", - "orUseIDP": "oder verwenden Sie einen Identitätsanbieter", "password": { "title": "Passwort festlegen", "description": "Legen Sie das Passwort für Ihr Konto fest", diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 5c47e8e64fe..8d8c11ec3be 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -222,7 +222,6 @@ "termsOfService": "Terms of Service", "privacyPolicy": "Privacy Policy", "submit": "Continue", - "orUseIDP": "or use an Identity Provider", "password": { "title": "Set Password", "description": "Set the password for your account", diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 1d44cb53cee..82f1e9335d5 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -222,7 +222,6 @@ "termsOfService": "Términos de Servicio", "privacyPolicy": "Política de Privacidad", "submit": "Continuar", - "orUseIDP": "o usa un Proveedor de Identidad", "password": { "title": "Establecer Contraseña", "description": "Establece la contraseña para tu cuenta", diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index 40f59cd9e8e..815efb5a355 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -222,7 +222,6 @@ "termsOfService": "Termini di Servizio", "privacyPolicy": "Informativa sulla Privacy", "submit": "Continua", - "orUseIDP": "o usa un Identity Provider", "password": { "title": "Imposta Password", "description": "Imposta la password per il tuo account", diff --git a/apps/login/locales/pl.json b/apps/login/locales/pl.json index 5cb69ad2074..ae81ba7f39f 100644 --- a/apps/login/locales/pl.json +++ b/apps/login/locales/pl.json @@ -222,7 +222,6 @@ "termsOfService": "Regulamin", "privacyPolicy": "Polityka prywatności", "submit": "Kontynuuj", - "orUseIDP": "lub użyj dostawcy tożsamości", "password": { "title": "Ustaw hasło", "description": "Ustaw hasło dla swojego konta", diff --git a/apps/login/locales/ru.json b/apps/login/locales/ru.json index caf1366cf10..f5e91066dbd 100644 --- a/apps/login/locales/ru.json +++ b/apps/login/locales/ru.json @@ -222,7 +222,6 @@ "termsOfService": "Условия использования", "privacyPolicy": "Политика конфиденциальности", "submit": "Продолжить", - "orUseIDP": "или используйте Identity Provider", "password": { "title": "Установить пароль", "description": "Установите пароль для вашего аккаунта", diff --git a/apps/login/locales/zh.json b/apps/login/locales/zh.json index 5d87b5d8d70..3ec2ec31e90 100644 --- a/apps/login/locales/zh.json +++ b/apps/login/locales/zh.json @@ -222,7 +222,6 @@ "termsOfService": "服务条款", "privacyPolicy": "隐私政策", "submit": "继续", - "orUseIDP": "或使用身份提供者", "password": { "title": "设置密码", "description": "为您的账户设置密码", diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index 5a11ca1f895..2168813c40d 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -20,12 +20,10 @@ import { headers } from "next/headers"; export async function generateMetadata(): Promise { const t = await getTranslations("register"); - return { title: t('title')}; + return { title: t("title") }; } -export default async function Page(props: { - searchParams: Promise>; -}) { +export default async function Page(props: { searchParams: Promise> }) { const searchParams = await props.searchParams; let { firstname, lastname, email, organization, requestId } = searchParams; @@ -104,12 +102,9 @@ export default async function Page(props: { {legal && passwordComplexitySettings && organization && - (loginSettings.allowUsernamePassword || - loginSettings.passkeysType == PasskeysType.ALLOWED) && ( + (loginSettings.allowUsernamePassword || loginSettings.passkeysType == PasskeysType.ALLOWED) && ( -
-

- -

-
- { + const hasTosLink = !!legal?.tosLink; + const hasPrivacyLink = !!legal?.privacyPolicyLink; + + // Check that all required checkboxes are accepted + return ( + (!hasTosLink || newState.tosAccepted) && + (!hasPrivacyLink || newState.privacyPolicyAccepted) + ); + }; + return ( <>

@@ -50,16 +62,17 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {

{ - setAcceptanceState({ + const newState = { ...acceptanceState, tosAccepted: checked, - }); - onChange(checked && acceptanceState.privacyPolicyAccepted); + }; + setAcceptanceState(newState); + onChange(checkAllAccepted(newState)); }} - data-testid="privacy-policy-checkbox" + data-testid="tos-checkbox" />
@@ -75,25 +88,22 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
{ - setAcceptanceState({ + const newState = { ...acceptanceState, privacyPolicyAccepted: checked, - }); - onChange(checked && acceptanceState.tosAccepted); + }; + setAcceptanceState(newState); + onChange(checkAllAccepted(newState)); }} - data-testid="tos-checkbox" + data-testid="privacy-policy-checkbox" />

- +

diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index bf44ad2864e..a8f8b0962b3 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -2,20 +2,13 @@ import { registerUser } from "@/lib/server/register"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; -import { - LoginSettings, - PasskeysType, -} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { LoginSettings, PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { FieldValues, useForm } from "react-hook-form"; import { Alert, AlertType } from "./alert"; -import { - AuthenticationMethod, - AuthenticationMethodRadio, - methods, -} from "./authentication-method-radio"; +import { AuthenticationMethod, AuthenticationMethodRadio, methods } from "./authentication-method-radio"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; @@ -98,10 +91,7 @@ export function RegisterForm({ return response; } - async function submitAndContinue( - value: Inputs, - withPassword: boolean = false, - ) { + async function submitAndContinue(value: Inputs, withPassword: boolean = false) { const registerParams: any = value; if (organization) { @@ -114,9 +104,7 @@ export function RegisterForm({ // redirect user to /register/password if password is chosen if (withPassword) { - return router.push( - `/register/password?` + new URLSearchParams(registerParams), - ); + return router.push(`/register/password?` + new URLSearchParams(registerParams)); } else { return submitAndRegister(value); } @@ -125,6 +113,11 @@ export function RegisterForm({ const { errors } = formState; const [tosAndPolicyAccepted, setTosAndPolicyAccepted] = useState(false); + + // Check if legal acceptance is required + const isLegalAcceptanceRequired = !!(legal?.tosLink || legal?.privacyPolicyLink); + const canSubmit = formState.isValid && (!isLegalAcceptanceRequired || tosAndPolicyAccepted); + return (
@@ -162,38 +155,27 @@ export function RegisterForm({ />
- {legal && ( - + {(legal?.tosLink || legal?.privacyPolicyLink) && ( + )} {/* show chooser if both methods are allowed */} - {loginSettings && - loginSettings.allowUsernamePassword && - loginSettings.passkeysType == PasskeysType.ALLOWED && ( - <> -

- -

+ {loginSettings && loginSettings.allowUsernamePassword && loginSettings.passkeysType == PasskeysType.ALLOWED && ( + <> +

+ +

-
- -
- - )} +
+ +
+ + )} {!loginSettings?.allowUsernamePassword && loginSettings?.passkeysType !== PasskeysType.ALLOWED && (!loginSettings?.allowExternalIdp || !idpCount) && (
- +
)} @@ -209,11 +191,10 @@ export function RegisterForm({