fix(auth): prevent FormData crash on successful login (#503)

## What

Removes `resetOnSuccess={['password']}` from the login form.

## Sentry

- Fixes PHP-LARAVEL-2Y — `TypeError: Failed to construct 'FormData':
parameter 1 is not of type 'HTMLFormElement'.` (6 users, on `/login`)

## Root cause

On a successful login the server redirects to the dashboard, which
**unmounts the login form**. `resetOnSuccess` then fires a form reset,
and Inertia's reset handler constructs `new FormData(ref)` from the form
ref — now stale/null — which throws.

The reset serves no purpose on login (the user navigates away
regardless), so removing it eliminates the crash.

## Tests

- Added `resources/js/pages/auth/login.test.tsx`: renders the login page
and asserts the `<Form>` is not configured to reset on success.
This commit is contained in:
Víctor Falcón 2026-06-08 09:03:15 +02:00 committed by GitHub
parent a69c602366
commit f4bbbfd767
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 66 additions and 1 deletions

View File

@ -0,0 +1,66 @@
import { render, screen } from '@testing-library/react';
import { type ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import Login from './login';
globalThis.ResizeObserver ??= class {
observe() {}
unobserve() {}
disconnect() {}
};
const capturedFormProps: Record<string, unknown> = {};
vi.mock('@inertiajs/react', () => ({
Form: ({
children,
...props
}: {
children: (renderProps: {
processing: boolean;
errors: Record<string, string>;
}) => ReactNode;
} & Record<string, unknown>) => {
Object.assign(capturedFormProps, props);
return <form>{children({ processing: false, errors: {} })}</form>;
},
Head: () => null,
Link: ({ children }: { children: ReactNode }) => <a>{children}</a>,
usePage: () => ({ props: { demoCredentials: undefined } }),
}));
vi.mock('@/layouts/auth-layout', () => ({
default: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}));
vi.mock('@/lib/key-storage', () => ({
clearKey: vi.fn(),
}));
vi.mock('@/utils/i18n', () => ({
__: (key: string) => key,
}));
vi.mock('@/routes', () => ({
register: () => ({ url: '/register' }),
}));
vi.mock('@/routes/login', () => ({
store: { form: () => ({ action: '/login', method: 'post' }) },
}));
vi.mock('@/routes/password', () => ({
request: () => ({ url: '/forgot-password' }),
}));
describe('Login', () => {
it('does not reset the form on success', () => {
// The success redirect unmounts the form; resetting it afterwards makes
// Inertia construct a FormData from a stale ref and crash (PHP-LARAVEL-2Y).
render(<Login canResetPassword canRegister />);
expect(screen.getByRole('button', { name: /log in/i })).toBeTruthy();
expect(capturedFormProps.resetOnSuccess).toBeUndefined();
});
});

View File

@ -54,7 +54,6 @@ export default function Login({
<Form
{...store.form()}
resetOnSuccess={['password']}
onSuccess={clearKey}
className="flex flex-col gap-6"
>