Skip to content

Commit

Permalink
Supporting changes for new email-based password reset UI for Cloud us…
Browse files Browse the repository at this point in the history
…ers (#34639) (#34869)

* WIP new password reset UX

* Add unit tests for password reset UI overrides
  • Loading branch information
timothyb89 committed Nov 29, 2023
1 parent 26f005f commit eb077a7
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 2 deletions.
4 changes: 4 additions & 0 deletions web/packages/teleport/src/Users/Users.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,20 @@ const sample = {
user: null,
} as any,
inviteCollaboratorsOpen: false,
emailPasswordResetOpen: false,
onStartCreate: () => null,
onStartDelete: () => null,
onStartEdit: () => null,
onStartReset: () => null,
onStartInviteCollaborators: () => null,
onStartEmailResetPassword: () => null,
onClose: () => null,
onCreate: () => null,
onDelete: () => null,
onUpdate: () => null,
onReset: () => null,
onInviteCollaboratorsClose: () => null,
InviteCollaborators: null,
onEmailPasswordResetClose: () => null,
EmailPasswordReset: null,
};
77 changes: 77 additions & 0 deletions web/packages/teleport/src/Users/Users.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ describe('invite collaborators integration', () => {
onInviteCollaboratorsClose: () => undefined,
InviteCollaborators: null,
inviteCollaboratorsOpen: false,
onEmailPasswordResetClose: () => undefined,
EmailPasswordReset: null,
};
});

Expand Down Expand Up @@ -100,3 +102,78 @@ describe('invite collaborators integration', () => {
expect(screen.getByTestId('invite-collaborators')).toBeInTheDocument();
});
});

describe('email password reset integration', () => {
const ctx = createTeleportContext();

let props: State;
beforeEach(() => {
props = {
attempt: {
message: 'success',
isSuccess: true,
isProcessing: false,
isFailed: false,
},
users: [],
roles: [],
operation: {
type: 'reset',
user: { name: 'alice@example.com', roles: ['foo'] },
},

onStartCreate: () => undefined,
onStartDelete: () => undefined,
onStartEdit: () => undefined,
onStartReset: () => undefined,
onStartInviteCollaborators: () => undefined,
onClose: () => undefined,
onDelete: () => undefined,
onCreate: () => undefined,
onUpdate: () => undefined,
onReset: () => undefined,
onInviteCollaboratorsClose: () => undefined,
InviteCollaborators: null,
inviteCollaboratorsOpen: false,
onEmailPasswordResetClose: () => undefined,
EmailPasswordReset: null,
};
});

test('displays the traditional reset UI when not configured', async () => {
render(
<MemoryRouter>
<ContextProvider ctx={ctx}>
<Users {...props} />
</ContextProvider>
</MemoryRouter>
);

expect(screen.getByText('Reset User Authentication?')).toBeInTheDocument();
expect(screen.queryByText('New Reset UI')).not.toBeInTheDocument();
});

test('displays the email-based UI when configured', async () => {
props = {
...props,
InviteCollaborators: () => (
<div data-testid="new-reset-ui">New Reset UI</div>
),
};

render(
<MemoryRouter>
<ContextProvider ctx={ctx}>
<Users {...props} />
</ContextProvider>
</MemoryRouter>
);

expect(screen.getByText('New Reset UI')).toBeInTheDocument();

// This will display regardless since the dialog display is managed by the
// dialog itself, and our mock above is trivial, but we can make sure it
// renders.
expect(screen.getByTestId('new-reset-ui')).toBeInTheDocument();
});
});
10 changes: 9 additions & 1 deletion web/packages/teleport/src/Users/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export function Users(props: State) {
onInviteCollaboratorsClose,
inviteCollaboratorsOpen,
InviteCollaborators,
EmailPasswordReset,
onEmailPasswordResetClose,
} = props;
return (
<FeatureBox>
Expand Down Expand Up @@ -108,13 +110,19 @@ export function Users(props: State) {
username={operation.user.name}
/>
)}
{operation.type === 'reset' && (
{operation.type === 'reset' && !EmailPasswordReset && (
<UserReset
onClose={onClose}
onReset={onReset}
username={operation.user.name}
/>
)}
{operation.type === 'reset' && EmailPasswordReset && (
<EmailPasswordReset
onClose={onEmailPasswordResetClose}
username={operation.user.name}
/>
)}
{InviteCollaborators && (
<InviteCollaborators
open={inviteCollaboratorsOpen}
Expand Down
17 changes: 16 additions & 1 deletion web/packages/teleport/src/Users/useUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import { useAttempt } from 'shared/hooks';
import { User } from 'teleport/services/user';
import useTeleport from 'teleport/useTeleport';

export default function useUsers({ InviteCollaborators }: UsersContainerProps) {
export default function useUsers({
InviteCollaborators,
EmailPasswordReset,
}: UsersContainerProps) {
const ctx = useTeleport();
const [attempt, attemptActions] = useAttempt({ isProcessing: true });
const [users, setUsers] = useState([] as User[]);
Expand Down Expand Up @@ -93,6 +96,10 @@ export default function useUsers({ InviteCollaborators }: UsersContainerProps) {
setOperation({ type: 'none' });
}

function onEmailPasswordResetClose() {
setOperation({ type: 'none' });
}

useEffect(() => {
function fetchRoles() {
if (ctx.getFeatureFlags().roles) {
Expand Down Expand Up @@ -130,6 +137,8 @@ export default function useUsers({ InviteCollaborators }: UsersContainerProps) {
onInviteCollaboratorsClose,
InviteCollaborators,
inviteCollaboratorsOpen,
onEmailPasswordResetClose,
EmailPasswordReset,
};
}

Expand All @@ -149,8 +158,14 @@ export interface InviteCollaboratorsDialogProps {
open: boolean;
}

export interface EmailPasswordResetDialogProps {
username: string;
onClose: () => void;
}

export type UsersContainerProps = {
InviteCollaborators?: (props: InviteCollaboratorsDialogProps) => ReactElement;
EmailPasswordReset?: (props: EmailPasswordResetDialogProps) => ReactElement;
};

export type State = ReturnType<typeof useUsers>;

0 comments on commit eb077a7

Please sign in to comment.