From 36b8023846f26b89931c1a1fc3217cf068e59661 Mon Sep 17 00:00:00 2001 From: Rubin Aga Date: Fri, 24 Apr 2026 16:36:15 +0200 Subject: [PATCH 1/2] fix: update login form to support identity-based authentication --- .changeset/warm-corners-wink.md | 5 +++ src/features/auth/api/types.ts | 7 ++-- .../components/LoginForm/LoginForm.test.tsx | 37 ++++++++++++++++++- .../auth/components/LoginForm/LoginForm.tsx | 23 ++++++------ 4 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 .changeset/warm-corners-wink.md diff --git a/.changeset/warm-corners-wink.md b/.changeset/warm-corners-wink.md new file mode 100644 index 000000000..53ce58191 --- /dev/null +++ b/.changeset/warm-corners-wink.md @@ -0,0 +1,5 @@ +--- +"landscape-ui": patch +--- + +Fix PAM users being unable to log in to the UI diff --git a/src/features/auth/api/types.ts b/src/features/auth/api/types.ts index 40812536f..f7e61a4c8 100644 --- a/src/features/auth/api/types.ts +++ b/src/features/auth/api/types.ts @@ -36,10 +36,9 @@ export type AuthStateResponse = invitation_id: string | null; }); -export interface LoginRequestParams { - email: string; - password: string; -} +export type LoginRequestParams = + | { email: string; password: string } + | { identity: string; password: string }; export interface GetUbuntuOneUrlParams { external?: boolean; diff --git a/src/features/auth/components/LoginForm/LoginForm.test.tsx b/src/features/auth/components/LoginForm/LoginForm.test.tsx index 21eec21cd..2cf80250d 100644 --- a/src/features/auth/components/LoginForm/LoginForm.test.tsx +++ b/src/features/auth/components/LoginForm/LoginForm.test.tsx @@ -66,7 +66,7 @@ describe("LoginForm", () => { renderWithProviders(); await userEvent.type( - screen.getByTestId("email"), + screen.getByTestId("identifier"), taskId > 0 ? user.email : user.email.slice(1), ); @@ -95,6 +95,39 @@ describe("LoginForm", () => { }); }); + describe("with PAM auth (isIdentityAvailable)", () => { + beforeEach(async () => { + vi.doUnmock("react-router"); + vi.doUnmock("@/hooks/useAuth"); + vi.resetModules(); + vi.clearAllMocks(); + + loginSpy.mockResolvedValue({ data: authUser }); + + mockTestParams(); + + const { default: Component } = await import("./LoginForm"); + + renderWithProviders(); + + await userEvent.type(screen.getByTestId("identifier"), "john"); + await userEvent.type(screen.getByTestId("password"), user.password); + await userEvent.click(screen.getByRole("button", { name: /sign in/i })); + }); + + it("should sign in with identity field instead of email", async () => { + expect(loginSpy).toHaveBeenCalledWith({ + identity: "john", + password: user.password, + }); + expect(setUser).toHaveBeenCalledWith(authUser); + expect(safeRedirect).toHaveBeenCalledWith(HOMEPAGE_PATH, { + external: false, + replace: true, + }); + }); + }); + describe("with additional test params", () => { const testSearchParams = [ { "redirect-to": "/dashboard" }, @@ -121,7 +154,7 @@ describe("LoginForm", () => { renderWithProviders(); - await userEvent.type(screen.getByTestId("email"), user.email); + await userEvent.type(screen.getByTestId("identifier"), user.email); await userEvent.type(screen.getByTestId("password"), user.password); diff --git a/src/features/auth/components/LoginForm/LoginForm.tsx b/src/features/auth/components/LoginForm/LoginForm.tsx index 2eccb4cc4..1f4b92263 100644 --- a/src/features/auth/components/LoginForm/LoginForm.tsx +++ b/src/features/auth/components/LoginForm/LoginForm.tsx @@ -16,7 +16,7 @@ import classes from "./LoginForm.module.scss"; import { getFormikError } from "@/utils/formikErrors"; interface FormProps { - email: string; + identifier: string; password: string; } @@ -28,7 +28,7 @@ const LoginForm: FC = ({ isIdentityAvailable }) => { const [searchParams] = useSearchParams(); const debug = useDebug(); - const { login: signInWithEmailAndPassword, isLoggingIn } = useLogin(); + const { login, isLoggingIn } = useLogin(); const { safeRedirect, setUser } = useAuth(); @@ -37,11 +37,11 @@ const LoginForm: FC = ({ isIdentityAvailable }) => { const formik = useFormik({ initialValues: { - email: "", + identifier: "", password: "", }, validationSchema: Yup.object().shape({ - email: isIdentityAvailable + identifier: isIdentityAvailable ? Yup.string().required("This field is required") : Yup.string() .required("This field is required") @@ -83,10 +83,11 @@ const LoginForm: FC = ({ isIdentityAvailable }) => { }), onSubmit: async (values) => { try { - const { data } = await signInWithEmailAndPassword({ - email: values.email, - password: values.password, - }); + const { identifier, password } = values; + const credentials = isIdentityAvailable + ? { identity: identifier, password } + : { email: identifier, password }; + const { data } = await login(credentials); if ("current_account" in data) { setUser(data); @@ -107,9 +108,9 @@ const LoginForm: FC = ({ isIdentityAvailable }) => { Date: Tue, 28 Apr 2026 09:59:08 +0200 Subject: [PATCH 2/2] fix tests --- e2e/features/auth/login.page.ts | 8 ++++---- e2e/support/helpers/auth.ts | 4 ++-- .../LoginMethodsLayout/LoginMethodsLayout.test.tsx | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/e2e/features/auth/login.page.ts b/e2e/features/auth/login.page.ts index 87591de55..40de771d1 100644 --- a/e2e/features/auth/login.page.ts +++ b/e2e/features/auth/login.page.ts @@ -3,20 +3,20 @@ import { basePage } from "../../support/pages/basePage"; export class LoginPage extends basePage { readonly page: Page; - readonly emailInput: Locator; + readonly identifierInput: Locator; readonly passwordInput: Locator; readonly loginButton: Locator; constructor(page: Page) { super(page); this.page = page; - this.emailInput = page.locator('input[name="email"]'); + this.identifierInput = page.locator('input[name="identifier"]'); this.passwordInput = page.locator('input[name="password"]'); this.loginButton = page.locator("[type=submit]", { hasText: "Sign in" }); } - async login(email: string, password: string): Promise { - await this.emailInput.fill(email); + async login(identifier: string, password: string): Promise { + await this.identifierInput.fill(identifier); await this.passwordInput.fill(password); await this.loginButton.click(); } diff --git a/e2e/support/helpers/auth.ts b/e2e/support/helpers/auth.ts index 141372e48..e05687bf4 100644 --- a/e2e/support/helpers/auth.ts +++ b/e2e/support/helpers/auth.ts @@ -3,13 +3,13 @@ import { navigateTo } from "./navigation"; export async function login( page: Page, - email: string, + identifier: string, password: string, params?: Record, ): Promise { await navigateTo(page, "/login", params); - await page.fill('input[name="email"]', email); + await page.fill('input[name="identifier"]', identifier); await page.fill('input[name="password"]', password); await page.click('button[type="submit"]'); } diff --git a/src/features/auth/components/LoginMethodsLayout/LoginMethodsLayout.test.tsx b/src/features/auth/components/LoginMethodsLayout/LoginMethodsLayout.test.tsx index a27af897d..0eba92fcd 100644 --- a/src/features/auth/components/LoginMethodsLayout/LoginMethodsLayout.test.tsx +++ b/src/features/auth/components/LoginMethodsLayout/LoginMethodsLayout.test.tsx @@ -119,15 +119,15 @@ describe("LoginMethodsLayout", () => { renderWithProviders(); const inputLabel = screen.getByText(/identity/i); - const emailInput = screen.getByRole("textbox", { name: /identity/i }); + const identityInput = screen.getByRole("textbox", { name: /identity/i }); expect(inputLabel).toBeInTheDocument(); - expect(emailInput).toBeInTheDocument(); + expect(identityInput).toBeInTheDocument(); - await userEvent.type(emailInput, "testinputvalue"); + await userEvent.type(identityInput, "testinputvalue"); await waitFor(() => { - emailInput.blur(); + identityInput.blur(); }); expect(