From 9594ba9a986e4612ba125f34d6cc453a78151a06 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 1 Jul 2026 22:39:49 +1000 Subject: [PATCH] Display login errors to user (#12288) * Improved error message extraction * Updated playwright test --- src/frontend/playwright.config.ts | 2 +- src/frontend/src/functions/auth.tsx | 19 ++++++++-- src/frontend/tests/pui_login.spec.ts | 55 +++++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/frontend/playwright.config.ts b/src/frontend/playwright.config.ts index d9e09d3dd1..775f3c744c 100644 --- a/src/frontend/playwright.config.ts +++ b/src/frontend/playwright.config.ts @@ -90,7 +90,7 @@ export default defineConfig({ INVENTREE_FRONTEND_API_HOST: 'http://localhost:8000', INVENTREE_CORS_ORIGIN_ALLOW_ALL: 'True', INVENTREE_COOKIE_SAMESITE: 'False', - INVENTREE_LOGIN_ATTEMPTS: '100', + INVENTREE_LOGIN_ATTEMPTS: '3', INVENTREE_PLUGINS_MANDATORY: 'samplelocate', INVENTREE_CUSTOM_SPLASH: 'img/playwright_custom_splash.png', INVENTREE_CUSTOM_LOGO: 'img/playwright_custom_logo.png' diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 2c940df733..b076fde75c 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -129,11 +129,24 @@ export async function doBasicLogin( }); break; default: + const data = err.response?.data ?? {}; + + let msg: string = t`Check your input and try again.`; + + // Extract error message from response data + if (data?.detail) { + msg = data.detail; + } else if (data?.message) { + msg = data.message; + } else if (data?.error) { + msg = data.error; + } else if (data?.errors && Array.isArray(data.errors)) { + msg = data.errors[0]?.message ?? msg; + } + notifications.show({ title: `${t`Login failed`} (${err.response.status})`, - message: - err.response?.data?.detail ?? - t`Check your input and try again.`, + message: msg, id: 'auth-login-error', color: 'red' }); diff --git a/src/frontend/tests/pui_login.spec.ts b/src/frontend/tests/pui_login.spec.ts index fe1e69cce5..7da891d380 100644 --- a/src/frontend/tests/pui_login.spec.ts +++ b/src/frontend/tests/pui_login.spec.ts @@ -9,12 +9,24 @@ import { TOTP } from 'otpauth'; * Test various types of login failure */ test('Login - Failures', async ({ page }) => { - const loginWithError = async () => { + const loginWithError = async ({ + msg, + reload = true + }: { + msg?: string; + reload?: boolean; + }) => { await page.getByRole('button', { name: 'Log In' }).click(); await page.getByText('Login failed', { exact: true }).waitFor(); await page.getByText('Check your input and try again').first().waitFor(); - await page.reload(); + if (msg) { + await page.getByText(msg).waitFor(); + } + + if (reload) { + await page.reload(); + } }; // Navigate to the 'login' page @@ -26,25 +38,56 @@ test('Login - Failures', async ({ page }) => { await page.getByLabel('login-username').fill('invalid user'); await page.getByLabel('login-password').fill('invalid password'); - await loginWithError(); + await loginWithError({ + msg: 'The username and/or password you specified are not correct' + }); // Attempt login with valid (but disabled) user await page.getByLabel('login-username').fill('ian'); await page.getByLabel('login-password').fill('inactive'); - await loginWithError(); + await loginWithError({}); // Attempt login with no username await page.getByLabel('login-username').fill(''); await page.getByLabel('login-password').fill('hunter2'); - await loginWithError(); + await loginWithError({}); // Attempt login with no password await page.getByLabel('login-username').fill('ian'); await page.getByLabel('login-password').fill(''); - await loginWithError(); + await loginWithError({}); + + let tooManyAttempts = false; + + // Attempt login with incorrect password, multiple attempts + for (let i = 0; i < 10; i++) { + await page.getByLabel('login-username').fill('reader'); + await page.getByLabel('login-password').fill('readonlyx'); + await loginWithError({ reload: false }); + + const text = await page.getByText('Too many failed login attempts', { + exact: false + }); + + if ( + await expect(text) + .toBeVisible({ timeout: 100 }) + .then(() => true) + .catch(() => false) + ) { + tooManyAttempts = true; + break; + } + + await page.reload(); + } + + if (!tooManyAttempts) { + await expect(tooManyAttempts).toEqual(true); + } }); test('Login - Change Password', async ({ page }) => {