Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: tup-549 improve mfa unpair modal - use pages, not modal #293

Closed
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
8 changes: 7 additions & 1 deletion apps/tup-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
Sidebar,
Tickets,
MfaPairingview,
MfaUnpairingview,
MfaSuccessview,
MfaSelection,
ManageAccount,
RequireAuth,
Expand Down Expand Up @@ -50,11 +52,15 @@ function App() {
</Route>
<Route path="system-status" element={<Systems />}></Route>
<Route path="system-status/:tas_name" element={<Systems />} />
<Route path="mfa" element={<Mfa />}>
<Route path="mfa" element={<Mfa task="pair" />}>
<Route path="" element={<MfaSelection />} />
<Route path="totp" element={<MfaPairingview method="totp" />} />
<Route path="sms" element={<MfaPairingview method="sms" />} />
</Route>
<Route path="mfa/unpair" element={<Mfa task="unpair" />}>
<Route path="" element={<MfaUnpairingview />} />
<Route path="success" element={<MfaSuccessview task="unpair" />} />
</Route>
<Route
path="account"
element={
Expand Down
10 changes: 7 additions & 3 deletions apps/tup-ui/src/pages/Mfa/Mfa.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ import { MfaHeader, MfaWrapper, RequireAuth } from '@tacc/tup-components';
import styles from './Mfa.module.css';
import { Outlet } from 'react-router-dom';

const Mfa: React.FC = () => {
const Mfa: React.FC<{ task: 'pair' | 'unpair' }> = ({ task }) => {
return (
<RequireAuth>
<section className={styles['mfa-layout']}>
<MfaHeader />
{/* Default to a "success" view if user has a verified token */}
<MfaWrapper>
{task === 'pair' ? (
<MfaWrapper>
<Outlet />
</MfaWrapper>
) : (
<Outlet />
</MfaWrapper>
)}
</section>
</RequireAuth>
);
Expand Down
26 changes: 0 additions & 26 deletions libs/tup-components/src/accounts/ManageAccount.module.css
Original file line number Diff line number Diff line change
@@ -1,29 +1,3 @@
@import url('@tacc/core-styles/src/lib/_imports/tools/media-queries.css');

/* Layout styles */
.unpairing-container {
composes: c-step-panels c-step-panels--has-scroll-parent from '../../styles/c-step-panels.css';
}
.unpairing-separator {
composes: c-step-panels__separator from '../../styles/c-step-panels.css';
}
.has-sms {
width: 100%;
max-width: 375px;
grid-template-columns: 1fr auto 1fr;
}
@media screen and (--medium-and-above) {
.unpairing-container {
composes: c-step-panels--horizontal from '../../styles/c-step-panels.css';
}
.mfa-message {
composes: c-step-panels__message from '../../styles/c-step-panels.css';
}
}
.mfa-fieldwrap {
composes: c-step-panels__content from '../../styles/c-step-panels.css';
}

.account-layout{
padding: var(--global-space--section);
padding-right: 30px;
Expand Down
150 changes: 14 additions & 136 deletions libs/tup-components/src/accounts/ManageAccountMfa.tsx
Original file line number Diff line number Diff line change
@@ -1,141 +1,10 @@
import {
Button,
InlineMessage,
LoadingSpinner,
SectionMessage,
} from '@tacc/core-components';
import {
MfaTokenResponse,
useMfa,
useMfaDelete,
useMfaChallenge,
useMfaEmailUnpair,
} from '@tacc/tup-hooks';
import { Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import React from 'react';
import { Button, LoadingSpinner, SectionMessage } from '@tacc/core-components';
import { useMfa } from '@tacc/tup-hooks';
import { TicketCreateModal } from '../tickets';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import styles from './ManageAccount.module.css';

const MfaUnpair: React.FC<{ pairing: MfaTokenResponse }> = ({ pairing }) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => {
setIsOpen(!isOpen);
};

const closeBtn = (
<button className="close" onClick={toggle} type="button">
&times;
</button>
);
const [currentToken, setCurrentToken] = useState('');
const { mutate: unpairWithCode, isError } = useMfaDelete();
const { mutate: unpairWithEmail, isSuccess: emailSentSuccess } =
useMfaEmailUnpair();
const { mutate: sendChallenge } = useMfaChallenge();

const submit = (e: React.FormEvent) => {
e.preventDefault();
e.stopPropagation();
setCurrentToken('');

unpairWithCode({
otpcode: currentToken,
});
};

const useSms = pairing.token?.tokentype === 'sms';

return (
<>
<Button type="secondary" onClick={() => toggle()}>
Unpair
</Button>
<Modal
isOpen={isOpen}
toggle={toggle}
size="md"
className="modal-dialog-centered"
>
<ModalHeader
toggle={toggle}
close={closeBtn}
className={styles['modal-header']}
>
<span>Unpair Multifactor Authentication</span>
</ModalHeader>
<form onSubmit={(e) => submit(e)}>
<ModalBody>
<SectionMessage type="warning">
You are about to remove multifactor authentication from your
account.
</SectionMessage>
<br />
<ol
className={`
${styles['unpairing-container']}
${useSms ? styles['has-sms'] : ''}
`}
>
{useSms && (
<>
<li>
<label className={`${styles['mfa-fieldwrap']}`}>
<Button type="link" onClick={() => sendChallenge(null)}>
Send SMS token.
</Button>
</label>
</li>
<li aria-hidden className={styles['unpairing-separator']} />
</>
)}
<li value={useSms ? '2' : undefined}>
<div className={`${styles['mfa-fieldwrap']}`}>
<label htmlFor="current-mfa-token">Enter MFA Token:</label>
<input
value={currentToken}
autoComplete="off"
onChange={(e) => setCurrentToken(e.target.value)}
id="current-mfa-token"
/>
</div>

<p className={styles['mfa-message']}>
Alternatively,{' '}
<Button type="link" onClick={() => unpairWithEmail(null)}>
unpair via email
</Button>
.
</p>

{isError && (
<InlineMessage type="error">
There was an error verifying your MFA token.
</InlineMessage>
)}
</li>
</ol>
{emailSentSuccess && (
<SectionMessage type="info">
An email has been sent to the address listed on your account.
Follow its instructions to continue the unpairing.
</SectionMessage>
)}
</ModalBody>
<ModalFooter>
<Button type="primary" attr="submit">
Confirm Unpairing
</Button>
<Button onClick={() => toggle()} type="secondary" attr="submit">
Cancel
</Button>
</ModalFooter>
</form>
</Modal>
</>
);
};

const MfaSectionHeader: React.FC = () => (
<div className={styles['tap-header']}>
<strong>MFA Pairing</strong>
Expand All @@ -161,7 +30,14 @@ export const AccountMfa: React.FC = () => {
</>
);
}
if (isLoading || !data) return <LoadingSpinner />;
if (isLoading || !data) {
return (
<>
<MfaSectionHeader />
<LoadingSpinner />
</>
);
}
const hasPairing = data?.token?.rollout_state === 'enrolled';
return (
<>
Expand All @@ -179,7 +55,9 @@ export const AccountMfa: React.FC = () => {
<p>
{TOKEN_TYPE[data.token.tokentype]} ({data.token.serial})
</p>
<MfaUnpair pairing={data} />
<Link to="/mfa/unpair" className={styles['tap-href']}>
<Button type="secondary">Unpair</Button>
</Link>
</div>
)}
</>
Expand Down
5 changes: 3 additions & 2 deletions libs/tup-components/src/mfa/MfaSuccessView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { Button, SectionMessage } from '@tacc/core-components';
import { useNavigate } from 'react-router-dom';
import styles from './Mfa.module.css';

const MfaSuccessView: React.FC = () => {
const MfaSuccessView: React.FC<{ task: 'pair' | 'unpair' }> = ({ task }) => {
const navigate = useNavigate();
return (
<div className={styles['mfa-success-container']}>
<SectionMessage type="success" scope="section">
Pairing Successful
{task === 'pair' && 'Pairing successful'}
{task === 'unpair' && 'Unpairing successful'}
</SectionMessage>
<Button type="primary" onClick={() => navigate('/')}>
Return to Dashboard
Expand Down
124 changes: 124 additions & 0 deletions libs/tup-components/src/mfa/MfaUnpairingView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { useState } from 'react';
import { Navigate } from 'react-router-dom';
import { useMfa } from '@tacc/tup-hooks';
import {
Button,
InlineMessage,
LoadingSpinner,
SectionMessage,
} from '@tacc/core-components';
import {
useMfaDelete,
useMfaChallenge,
useMfaEmailUnpair,
} from '@tacc/tup-hooks';
import TicketCreateModal from '../tickets/TicketCreateModal';
import styles from './Mfa.module.css';

const MfaUnpairingView: React.FC = () => {
const [currentToken, setCurrentToken] = useState('');
const { data, isLoading } = useMfa();
const { mutate: unpairWithCode, isError, isSuccess } = useMfaDelete();
const { mutate: unpairWithEmail, isSuccess: emailSentSuccess } =
useMfaEmailUnpair();
const {
mutate: sendChallenge,
isSuccess: sendChallengeSuccess,
isLoading: isChallengeLoading,
} = useMfaChallenge();

const method = data?.token?.tokentype;

const submit = (e: React.FormEvent) => {
e.preventDefault();
e.stopPropagation();
setCurrentToken('');

unpairWithCode({
otpcode: currentToken,
});
};

if (isLoading || !data) {
return <LoadingSpinner />;
}

return (
<>
<SectionMessage type="warning">
You are about to remove multifactor authentication from your account.
</SectionMessage>
<br />
<ol className={styles['pairing-container']}>
<li className={`${styles['mfa-form']}`}>
{method === 'sms' && (
<>
<label>Send SMS token to your phone</label>
<Button
type="secondary"
onClick={() => sendChallenge(null)}
isLoading={isChallengeLoading}
>
Send Token
</Button>
</>
)}
{method === 'totp' && <label>Open the MFA token app.</label>}
</li>
<li aria-hidden className={styles['pairing-separator']} />
<li value="2">
<form
onSubmit={(e) => submit(e)}
className={`${styles['mfa-form']} s-form`}
>
<div>
<label htmlFor="current-mfa-token">Enter MFA Token:</label>
<input
value={currentToken}
autoComplete="off"
onChange={(e) => setCurrentToken(e.target.value)}
id="current-mfa-token"
/>
</div>
<Button type="primary" attr="submit" isLoading={isLoading}>
Confirm Unpairing
</Button>
</form>

{!sendChallengeSuccess && (
<p className={styles['mfa-message']}>
Alternatively,{' '}
<Button type="link" onClick={() => unpairWithEmail(null)}>
unpair via email
</Button>
.
{emailSentSuccess && (
<SectionMessage type="info">
An email has been sent to the address listed on your account.
Follow its instructions to continue the unpairing.
</SectionMessage>
)}
</p>
)}

{method === 'sms' && sendChallengeSuccess && (
<p className={styles['mfa-message']}>
Didn't receive a message within 5 minutes?{' '}
<TicketCreateModal display="link">Get Help</TicketCreateModal>
</p>
)}

{isError && (
<InlineMessage type="error">
There was an error verifying your MFA token.
</InlineMessage>
)}

{isSuccess && <Navigate to="/mfa/unpair/success" />}
</li>
</ol>
</>
);
};

export default MfaUnpairingView;
Loading
Loading