Skip to content

Commit 6bdf6c7

Browse files
feat: [UIE-8819] - IAM RBAC: User Email validation (linode#12395)
* feat: [UIE-8819] - IAM RBAC: User Email validation * Added changeset: IAM RBAC: email validation
1 parent 8369944 commit 6bdf6c7

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

packages/manager/src/features/IAM/Users/UserDetails/UserEmailPanel.test.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
import { profileFactory } from '@linode/utilities';
2+
import { screen } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
24
import React from 'react';
35

46
import { accountUserFactory } from 'src/factories';
57
import { http, HttpResponse, server } from 'src/mocks/testServer';
6-
import { renderWithTheme } from 'src/utilities/testHelpers';
8+
import {
9+
renderWithTheme,
10+
renderWithThemeAndRouter,
11+
} from 'src/utilities/testHelpers';
712

813
import { UserEmailPanel } from './UserEmailPanel';
914

15+
const queryMocks = vi.hoisted(() => ({
16+
useProfile: vi.fn().mockReturnValue({}),
17+
}));
18+
19+
// Mock useProfile
20+
vi.mock('@linode/queries', async () => {
21+
const actual = await vi.importActual('@linode/queries');
22+
return {
23+
...actual,
24+
useProfile: queryMocks.useProfile,
25+
};
26+
});
1027
describe('UserEmailPanel', () => {
1128
it("initializes the form with the user's email", async () => {
1229
const user = accountUserFactory.build();
@@ -69,4 +86,26 @@ describe('UserEmailPanel', () => {
6986
// Verify save button is disabled
7087
expect(getByText('Save').closest('button')).toBeDisabled();
7188
});
89+
90+
it('shows validation error for invalid email address', async () => {
91+
queryMocks.useProfile.mockReturnValue({
92+
data: profileFactory.build({ username: 'user-1' }),
93+
});
94+
const user = accountUserFactory.build({
95+
username: 'user-1',
96+
});
97+
98+
await renderWithThemeAndRouter(<UserEmailPanel user={user} />);
99+
100+
const emailInput = screen.getByLabelText('Email');
101+
102+
await userEvent.clear(emailInput);
103+
await userEvent.type(emailInput, 'user#@example.com');
104+
105+
const saveButton = screen.getByText('Save').closest('button');
106+
await userEvent.click(saveButton!);
107+
108+
const errorText = screen.getByText(/invalid email address/i);
109+
expect(errorText).toBeInTheDocument();
110+
});
72111
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/validation": Added
3+
---
4+
5+
IAM RBAC: email validation ([#12395](https://github.com/linode/manager/pull/12395))

packages/validation/src/profile.schema.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { isPossiblePhoneNumber } from 'libphonenumber-js';
22
import { array, boolean, number, object, string } from 'yup';
33

4+
// Using the same regex as the API to ensure consistent email validation across UI and backend.
5+
const EMAIL_VALIDATION_REGEX = new RegExp(
6+
// eslint-disable-next-line sonarjs/regex-complexity
7+
/^(?!-.*|.*(\.{2}|@-))[a-zA-Z0-9_.+"-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]*[a-zA-Z0-9]$/,
8+
);
9+
410
export const createPersonalAccessTokenSchema = object({
511
scopes: string(),
612
expiry: string(),
@@ -27,7 +33,9 @@ export const updateSSHKeySchema = object({
2733
});
2834

2935
export const updateProfileSchema = object({
30-
email: string().email(),
36+
email: string()
37+
.email()
38+
.matches(EMAIL_VALIDATION_REGEX, `Invalid email address. `),
3139
timezone: string(),
3240
email_notifications: boolean(),
3341
authorized_keys: array().of(string()),

0 commit comments

Comments
 (0)