Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>;