fix(login): respect password complexity settings in UI (#11528)

<!--
Please inform yourself about the contribution guidelines on submitting a
PR here:
https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr.
Take note of how PR/commit titles should be written and replace the
template texts in the sections below. Don't remove any of the sections.
It is important that the commit history clearly shows what is changed
and why.
Important: By submitting a contribution you agree to the terms from our
Licensing Policy as described here:
https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions.
-->

# Which Problems Are Solved

- Login V2 UI displays all password complexity requirements (symbol,
number, uppercase, lowercase) regardless of the actual
instance/organization configuration
- Users see requirements they don't need to meet, causing confusion

# How the Problems Are Solved

- Updated the PasswordComplexity component to conditionally render each
requirement based on the corresponding `requires*` flag from the
password complexity settings
- Only enabled requirements are now displayed to users

# Additional Changes

None.

# Additional Context

- Closes #11511
This commit is contained in:
Liam Neville
2026-02-06 15:23:36 +01:00
committed by GitHub
parent 1f0d54a978
commit 4712d99856
2 changed files with 166 additions and 27 deletions
@@ -119,7 +119,7 @@ describe("<PasswordComplexity/>", () => {
expect(svg).toHaveClass("text-warn-light-500");
});
test("should render all complexity checks", () => {
test("should render all complexity checks when all requirements are enabled", () => {
render(
<NextIntlClientProvider locale="en" messages={messages}>
<PasswordComplexity
@@ -144,5 +144,138 @@ describe("<PasswordComplexity/>", () => {
expect(screen.getByTestId("number-check")).toBeInTheDocument();
expect(screen.getByTestId("uppercase-check")).toBeInTheDocument();
expect(screen.getByTestId("lowercase-check")).toBeInTheDocument();
expect(screen.getByTestId("equal-check")).toBeInTheDocument();
});
test("should not render symbol check when requiresSymbol is false", () => {
render(
<NextIntlClientProvider locale="en" messages={messages}>
<PasswordComplexity
password="Password1!"
equals
passwordComplexitySettings={
{
minLength: BigInt(8),
requiresLowercase: true,
requiresUppercase: true,
requiresNumber: true,
requiresSymbol: false,
resourceOwnerType: 0,
} as any
}
/>
</NextIntlClientProvider>,
);
expect(screen.queryByTestId("symbol-check")).not.toBeInTheDocument();
expect(screen.getByTestId("number-check")).toBeInTheDocument();
expect(screen.getByTestId("uppercase-check")).toBeInTheDocument();
expect(screen.getByTestId("lowercase-check")).toBeInTheDocument();
});
test("should not render number check when requiresNumber is false", () => {
render(
<NextIntlClientProvider locale="en" messages={messages}>
<PasswordComplexity
password="Password1!"
equals
passwordComplexitySettings={
{
minLength: BigInt(8),
requiresLowercase: true,
requiresUppercase: true,
requiresNumber: false,
requiresSymbol: true,
resourceOwnerType: 0,
} as any
}
/>
</NextIntlClientProvider>,
);
expect(screen.getByTestId("symbol-check")).toBeInTheDocument();
expect(screen.queryByTestId("number-check")).not.toBeInTheDocument();
expect(screen.getByTestId("uppercase-check")).toBeInTheDocument();
expect(screen.getByTestId("lowercase-check")).toBeInTheDocument();
});
test("should not render uppercase check when requiresUppercase is false", () => {
render(
<NextIntlClientProvider locale="en" messages={messages}>
<PasswordComplexity
password="Password1!"
equals
passwordComplexitySettings={
{
minLength: BigInt(8),
requiresLowercase: true,
requiresUppercase: false,
requiresNumber: true,
requiresSymbol: true,
resourceOwnerType: 0,
} as any
}
/>
</NextIntlClientProvider>,
);
expect(screen.getByTestId("symbol-check")).toBeInTheDocument();
expect(screen.getByTestId("number-check")).toBeInTheDocument();
expect(screen.queryByTestId("uppercase-check")).not.toBeInTheDocument();
expect(screen.getByTestId("lowercase-check")).toBeInTheDocument();
});
test("should not render lowercase check when requiresLowercase is false", () => {
render(
<NextIntlClientProvider locale="en" messages={messages}>
<PasswordComplexity
password="Password1!"
equals
passwordComplexitySettings={
{
minLength: BigInt(8),
requiresLowercase: false,
requiresUppercase: true,
requiresNumber: true,
requiresSymbol: true,
resourceOwnerType: 0,
} as any
}
/>
</NextIntlClientProvider>,
);
expect(screen.getByTestId("symbol-check")).toBeInTheDocument();
expect(screen.getByTestId("number-check")).toBeInTheDocument();
expect(screen.getByTestId("uppercase-check")).toBeInTheDocument();
expect(screen.queryByTestId("lowercase-check")).not.toBeInTheDocument();
});
test("should only render length and equals checks when all other requirements are disabled", () => {
render(
<NextIntlClientProvider locale="en" messages={messages}>
<PasswordComplexity
password="password"
equals
passwordComplexitySettings={
{
minLength: BigInt(8),
requiresLowercase: false,
requiresUppercase: false,
requiresNumber: false,
requiresSymbol: false,
resourceOwnerType: 0,
} as any
}
/>
</NextIntlClientProvider>,
);
expect(screen.getByTestId("length-check")).toBeInTheDocument();
expect(screen.getByTestId("equal-check")).toBeInTheDocument();
expect(screen.queryByTestId("symbol-check")).not.toBeInTheDocument();
expect(screen.queryByTestId("number-check")).not.toBeInTheDocument();
expect(screen.queryByTestId("uppercase-check")).not.toBeInTheDocument();
expect(screen.queryByTestId("lowercase-check")).not.toBeInTheDocument();
});
});
@@ -91,33 +91,39 @@ export function PasswordComplexity({
/>
</span>
</div>
) : (
<span />
) : null}
{passwordComplexitySettings.requiresSymbol && (
<div className="flex flex-row items-center" data-testid="symbol-check">
{renderIcon(hasSymbol, t)}
<span className={desc}>
<Translated i18nKey="complexity.hasSymbol" namespace="password" />
</span>
</div>
)}
{passwordComplexitySettings.requiresNumber && (
<div className="flex flex-row items-center" data-testid="number-check">
{renderIcon(hasNumber, t)}
<span className={desc}>
<Translated i18nKey="complexity.hasNumber" namespace="password" />
</span>
</div>
)}
{passwordComplexitySettings.requiresUppercase && (
<div className="flex flex-row items-center" data-testid="uppercase-check">
{renderIcon(hasUppercase, t)}
<span className={desc}>
<Translated i18nKey="complexity.hasUppercase" namespace="password" />
</span>
</div>
)}
{passwordComplexitySettings.requiresLowercase && (
<div className="flex flex-row items-center" data-testid="lowercase-check">
{renderIcon(hasLowercase, t)}
<span className={desc}>
<Translated i18nKey="complexity.hasLowercase" namespace="password" />
</span>
</div>
)}
<div className="flex flex-row items-center" data-testid="symbol-check">
{renderIcon(hasSymbol, t)}
<span className={desc}>
<Translated i18nKey="complexity.hasSymbol" namespace="password" />
</span>
</div>
<div className="flex flex-row items-center" data-testid="number-check">
{renderIcon(hasNumber, t)}
<span className={desc}>
<Translated i18nKey="complexity.hasNumber" namespace="password" />
</span>
</div>
<div className="flex flex-row items-center" data-testid="uppercase-check">
{renderIcon(hasUppercase, t)}
<span className={desc}>
<Translated i18nKey="complexity.hasUppercase" namespace="password" />
</span>
</div>
<div className="flex flex-row items-center" data-testid="lowercase-check">
{renderIcon(hasLowercase, t)}
<span className={desc}>
<Translated i18nKey="complexity.hasLowercase" namespace="password" />
</span>
</div>
<div className="flex flex-row items-center" data-testid="equal-check">
{renderIcon(equals, t)}
<span className={desc}>