Skip to content
This repository has been archived by the owner on Feb 8, 2024. It is now read-only.

Commit

Permalink
Move RecoveryCode component from enterprise (#789)
Browse files Browse the repository at this point in the history
* Make tweaks based on move
* Update e-ref
  • Loading branch information
kimlisa authored Apr 30, 2022
1 parent c7c61fb commit 7bb9cf5
Show file tree
Hide file tree
Showing 20 changed files with 1,323 additions and 899 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2019 Gravitational, Inc.
Copyright 2019-2022 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -18,15 +18,19 @@ import React from 'react';
import styled from 'styled-components';
import { Card, Text } from 'design';

export default function Expired() {
export default function Expired({ resetMode = false }) {
const titleCodeTxt = resetMode ? 'Reset' : 'Invitation';
const paraCodeTxt = resetMode ? 'reset' : 'invite';

return (
<Card width="540px" color="text.onLight" p={6} bg="light" mt={6} mx="auto">
<Text typography="h1" textAlign="center" fontSize={8} color="text" mb={3}>
Invitation Code Expired
{titleCodeTxt} Code Expired
</Text>
<Text typography="paragraph" mb="2">
It appears that your invite code isn't valid any more. Please contact
your account administrator and request another invite.
It appears that your {paraCodeTxt} code isn't valid any more. Please
contact your account administrator and request another {paraCodeTxt}{' '}
link.
</Text>
<Text typography="paragraph">
If you believe this is an issue with the product, please create a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import React from 'react';
import FormNewCredentials, { Props } from './FormNewCredentials';

export default {
title: 'Teleport/FormNewCredentials',
title: 'Teleport/Welcome/FormNewCredentials',
component: FormNewCredentials,
};

Expand Down
41 changes: 35 additions & 6 deletions packages/teleport/src/Welcome/NewCredentials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,58 @@ limitations under the License.
*/

import React from 'react';
import Form, { Expired } from 'teleport/components/FormNewCredentials';
import RecoveryCodes from 'teleport/components/RecoveryCodes';
import Form from './FormNewCredentials';
import Expired from './FormNewCredentials/Expired';
import useToken, { State } from './useToken';

export default function Container({ tokenId = '', title, submitBtnText }) {
export default function Container({
tokenId = '',
title = '',
submitBtnText = '',
resetMode = false,
}) {
const state = useToken(tokenId);
return (
<NewCredentials {...state} title={title} submitBtnText={submitBtnText} />
<NewCredentials
{...state}
title={title}
submitBtnText={submitBtnText}
resetMode={resetMode}
/>
);
}

export function NewCredentials(props: Props) {
const { submitAttempt, fetchAttempt, passwordToken, ...rest } = props;
const {
submitAttempt,
fetchAttempt,
passwordToken,
recoveryCodes,
resetMode,
redirect,
...rest
} = props;

if (fetchAttempt.status === 'failed') {
return <Expired />;
return <Expired resetMode={resetMode} />;
}

if (fetchAttempt.status !== 'success') {
return null;
}

const { user, qrCode } = passwordToken;
if (recoveryCodes) {
return (
<RecoveryCodes
recoveryCodes={recoveryCodes}
redirect={redirect}
isNewCodes={resetMode}
/>
);
}

const { user, qrCode } = passwordToken;
return <Form user={user} qr={qrCode} attempt={submitAttempt} {...rest} />;
}

Expand Down
136 changes: 95 additions & 41 deletions packages/teleport/src/Welcome/Welcome.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,63 +18,117 @@ import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import Welcome from './Welcome';
import { NewCredentials, Props } from './NewCredentials';
import { mockedProps } from './fixtures/fixtures';
import CardWelcome from './CardWelcome';

export default { title: 'Teleport/Welcome' };

export const GetStarted = () => <MockedWelcome url="/web/invite/1234" />;

export const Continue = () => (
<MockedWelcome url="/web/invite/1234/continue" Form={Otp} />
export const WelcomeCustom = () => (
<CardWelcome
title="Some Title"
subTitle="some small subtitle"
btnText="Button Text"
onClick={() => null}
/>
);

export const GetStartedReset = () => <MockedWelcome url="/web/reset/1234" />;

export const ContinueReset = () => (
<MockedWelcome url="/web/reset/1234/continue" Form={Otp} />
export const WelcomeInvite = () => (
<MemoryRouter initialEntries={['/web/invite/1234']}>
<Welcome />
</MemoryRouter>
);

export const AuthMfaOn = () => (
<MockedWelcome url="/web/invite/1234/continue" Form={MfaOn} />
export const WelcomeReset = () => (
<MemoryRouter initialEntries={['/web/reset/1234']}>
<Welcome />
</MemoryRouter>
);

export const AuthMfaOptional = () => (
<MockedWelcome url="/web/invite/1234/continue" Form={MfaOptional} />
export const ExpiredInvite = () => (
<NewCredentials {...props} fetchAttempt={{ status: 'failed' }} />
);

export const AuthMfaOnOtp = () => (
<MockedWelcome url="/web/invite/1234/continue" Form={Otp} />
export const ExpiredReset = () => (
<NewCredentials
{...props}
fetchAttempt={{ status: 'failed' }}
resetMode={true}
/>
);

const MfaOn = (props: Props) => (
<NewCredentials {...props} {...mockedProps} auth2faType="on" />
export const RecoveryCodesInvite = () => (
<NewCredentials {...props} recoveryCodes={recoveryCodes} />
);

const Otp = (props: Props) => (
<NewCredentials {...props} {...mockedProps} auth2faType="otp" />
export const RecoveryCodesReset = () => (
<NewCredentials {...props} recoveryCodes={recoveryCodes} resetMode={true} />
);

const MfaOptional = (props: Props) => (
<NewCredentials {...props} {...mockedProps} auth2faType="optional" />
);
export const BasicForm = () => <NewCredentials {...props} />;

type MockedWelcomeProps = {
url: string;
Form?: React.FC<Props>;
const recoveryCodes = {
codes: [
'tele-testword-testword-testword-testword-testword-testword-testword',
'tele-testword-testword-testword-testword-testword-testword-testword-testword',
'tele-testword-testword-testword-testword-testword-testword-testword',
],
createdDate: new Date('2019-08-30T11:00:00.00Z'),
};

function MockedWelcome({ url, Form }: MockedWelcomeProps) {
return (
<MemoryRouter initialEntries={[url]}>
<Welcome CustomForm={Form} />
</MemoryRouter>
);
}

GetStarted.storyName = 'GetStarted';
GetStartedReset.storyName = 'GetStartedReset';
ContinueReset.storyName = 'ContinueReset';
Continue.storyName = 'Continue';
AuthMfaOnOtp.storyName = 'MfaOnOtp';
AuthMfaOn.storyName = 'MfaOn';
AuthMfaOptional.storyName = 'MfaOptional';
const props: Props = {
submitBtnText: 'Some Button Text',
title: 'Some Title',
auth2faType: 'off',
preferredMfaType: 'webauthn',
submitAttempt: { status: '' },
clearSubmitAttempt: () => null,
fetchAttempt: { status: 'success' },
onSubmitWithWebauthn: () => null,
onSubmit: () => null,
redirect: () => null,
recoveryCodes: null,
passwordToken: {
user: '[email protected]',
tokenId: 'test123',
qrCode:
'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',
},
};
19 changes: 0 additions & 19 deletions packages/teleport/src/Welcome/Welcome.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import cfg from 'teleport/config';
import history from 'teleport/services/history';
import auth from 'teleport/services/auth';
import Welcome from './Welcome';
import { AuthMfaOn, AuthMfaOptional } from './Welcome.story';

const invitePath = '/web/invite/5182';
const inviteContinuePath = '/web/invite/5182/continue';
Expand Down Expand Up @@ -202,24 +201,6 @@ describe('teleport/components/Welcome', () => {

expect(screen.getByText('server_error')).toBeDefined();
});

it('auth type "on" should render form with hardware key as first option in dropdown', () => {
const { container } = render(
<MemoryRouter initialEntries={[inviteContinuePath]}>
<AuthMfaOn />
</MemoryRouter>
);
expect(container).toMatchSnapshot();
});

it('auth type "optional" should render form with hardware key as first option in dropdown', () => {
const { container } = render(
<MemoryRouter initialEntries={[inviteContinuePath]}>
<AuthMfaOptional />
</MemoryRouter>
);
expect(container).toMatchSnapshot();
});
});

function renderInvite(url = inviteContinuePath) {
Expand Down
13 changes: 4 additions & 9 deletions packages/teleport/src/Welcome/Welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ import { Route, Switch, useParams } from 'teleport/components/Router';
import history from 'teleport/services/history';
import LogoHero from 'teleport/components/LogoHero';
import cfg from 'teleport/config';
import NewCredentials, { Props as NewCredentialsProps } from './NewCredentials';
import NewCredentials from './NewCredentials';
import CardWelcome from './CardWelcome';

export default function Welcome(props: Props) {
export default function Welcome() {
const { tokenId } = useParams<{ tokenId: string }>();
const Form = props.CustomForm ? props.CustomForm : NewCredentials;

const handleOnInviteContinue = () => {
history.push(cfg.getUserInviteTokenContinueRoute(tokenId));
Expand Down Expand Up @@ -55,14 +54,14 @@ export default function Welcome(props: Props) {
/>
</Route>
<Route path={cfg.routes.userInviteContinue}>
<Form
<NewCredentials
tokenId={tokenId}
title="Welcome to Teleport"
submitBtnText="Create Account"
/>
</Route>
<Route path={cfg.routes.userResetContinue}>
<Form
<NewCredentials
resetMode={true}
tokenId={tokenId}
title="Reset Password"
Expand All @@ -73,7 +72,3 @@ export default function Welcome(props: Props) {
</>
);
}

type Props = {
CustomForm?: React.FC<Partial<NewCredentialsProps>>;
};
Loading

0 comments on commit 7bb9cf5

Please sign in to comment.