mirror of
https://github.com/vxcontrol/pentagi.git
synced 2026-09-26 12:15:38 +00:00
fix(server): link OAuth logins to existing accounts by email; harden email change
- authLoginCallback matches users by email alone so an OAuth login links into an existing (incl. local) account instead of 500-ing on users_mail_unique - relink on a create-branch unique-violation race instead of returning 500 - issue the session with the linked account's actual role privileges - clear the stale OAuth provider link when a user changes their email - map the email-change unique-violation race to 409 instead of 500 - isUniqueViolation matches Postgres and SQLite case-insensitively - tests for link/create/blocked/role-inheritance/race, email 409, provider reset - update auth form test selectors after the form refactor Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
901753e8d1
commit
06178bf868
@@ -15,7 +15,7 @@ vi.mock('@/lib/axios', async (importOriginal) => {
|
||||
});
|
||||
|
||||
vi.mock('@/providers/user-provider', () => ({
|
||||
useUser: () => ({ authInfo: { user: { mail: 'old@example.com' } }, refreshAuthInfo }),
|
||||
useUser: () => ({ refreshAuthInfo }),
|
||||
}));
|
||||
|
||||
vi.mock('sonner', () => ({ toast: { error: vi.fn(), success: vi.fn() } }));
|
||||
@@ -30,21 +30,13 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe('EmailChangeForm', () => {
|
||||
it('shows the current email in a label-associated disabled field', () => {
|
||||
render(<EmailChangeForm />);
|
||||
|
||||
const current = screen.getByLabelText('Current Email') as HTMLInputElement;
|
||||
expect(current).toBeDisabled();
|
||||
expect(current.value).toBe('old@example.com');
|
||||
});
|
||||
|
||||
it('submits the new email and refreshes auth before closing', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSuccess = vi.fn();
|
||||
render(<EmailChangeForm onSuccess={onSuccess} />);
|
||||
|
||||
await user.type(screen.getByPlaceholderText('Enter new email address'), 'new@example.com');
|
||||
await user.type(screen.getByPlaceholderText('Enter your current password to confirm'), 'Oldpass0!');
|
||||
await user.type(screen.getByPlaceholderText('Enter your new email address'), 'new@example.com');
|
||||
await user.type(screen.getByPlaceholderText('Enter your current password'), 'Oldpass0!');
|
||||
await user.click(screen.getByRole('button', { name: 'Update Email' }));
|
||||
|
||||
await waitFor(() => expect(onSuccess).toHaveBeenCalledOnce());
|
||||
@@ -57,8 +49,8 @@ describe('EmailChangeForm', () => {
|
||||
put.mockRejectedValueOnce(apiError('Users.ChangeEmailCurrentUser.EmailAlreadyExists', 'email already exists'));
|
||||
render(<EmailChangeForm />);
|
||||
|
||||
await user.type(screen.getByPlaceholderText('Enter new email address'), 'taken@example.com');
|
||||
await user.type(screen.getByPlaceholderText('Enter your current password to confirm'), 'Oldpass0!');
|
||||
await user.type(screen.getByPlaceholderText('Enter your new email address'), 'taken@example.com');
|
||||
await user.type(screen.getByPlaceholderText('Enter your current password'), 'Oldpass0!');
|
||||
await user.click(screen.getByRole('button', { name: 'Update Email' }));
|
||||
|
||||
expect(await screen.findByText('Email address is already in use')).toBeInTheDocument();
|
||||
|
||||
@@ -40,8 +40,8 @@ describe('PasswordChangeForm', () => {
|
||||
render(<PasswordChangeForm onSuccess={onSuccess} />);
|
||||
|
||||
await user.type(screen.getByPlaceholderText('Enter your current password'), 'Oldpass0!');
|
||||
await user.type(screen.getByPlaceholderText('Enter new password'), 'Abcdef1!gh');
|
||||
await user.type(screen.getByPlaceholderText('Confirm new password'), 'Abcdef1!gh');
|
||||
await user.type(screen.getByPlaceholderText('Enter your new password'), 'Abcdef1!gh');
|
||||
await user.type(screen.getByPlaceholderText('Confirm your new password'), 'Abcdef1!gh');
|
||||
await user.click(screen.getByRole('button', { name: 'Update Password' }));
|
||||
|
||||
await waitFor(() => expect(onSuccess).toHaveBeenCalledOnce());
|
||||
@@ -60,8 +60,8 @@ describe('PasswordChangeForm', () => {
|
||||
render(<PasswordChangeForm />);
|
||||
|
||||
await user.type(screen.getByPlaceholderText('Enter your current password'), 'Oldpass0!');
|
||||
await user.type(screen.getByPlaceholderText('Enter new password'), 'Abcdef1!gh');
|
||||
await user.type(screen.getByPlaceholderText('Confirm new password'), 'Abcdef1!gh');
|
||||
await user.type(screen.getByPlaceholderText('Enter your new password'), 'Abcdef1!gh');
|
||||
await user.type(screen.getByPlaceholderText('Confirm your new password'), 'Abcdef1!gh');
|
||||
await user.click(screen.getByRole('button', { name: 'Update Password' }));
|
||||
|
||||
expect(await screen.findByText('Current password is incorrect')).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user