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

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { useMutation } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import { useState } from 'react';
import { Button } from '../../../../../shared/components/Button/Button';
import { ButtonVariant } from '../../../../../shared/components/Button/types';
import { Controls } from '../../../../../shared/components/Controls/Controls';
import { ExternalProviderButton } from '../../../../../shared/components/ExternalProviderButton/ExternalProviderButton';
import { FullPageTitle } from '../../../../../shared/components/FullPageTitle/FullPageTitle';
import { Icon, IconKind } from '../../../../../shared/components/Icon';
import { MfaSelector } from '../../../../../shared/components/LocationCard/components/MfaSelector/MfaSelector';
import { SizedBox } from '../../../../../shared/components/SizedBox/SizedBox';
import { FullPage } from '../../../../../shared/layouts/FullPage/FullPage';
import { ConfigureFactorsSource, MfaMethod } from '../../../../../shared/rust-api/types';
import { ConfigureFactorsSource } from '../../../../../shared/rust-api/types';
import { ThemeSpacing, ThemeVariable } from '../../../../../shared/types';
import { isPresent } from '../../../../../shared/utils/isPresent';
import {
type ClientAuthorizingMethod,
findOpenIdProvider,
openIdProviderName,
} from '../../../../../shared/utils/mfa';
import { ConfigureMfaVerificatorFactorSelector } from '../../components/ConfigureMfaVerificatorFactorSelector/ConfigureMfaVerificatorFactorSelector';
import {
discardMfaConfiguration,
useConfigureMfaStore,
Expand All @@ -36,10 +31,6 @@ export const ConfigureSelectVerificationStep = ({ onCancel }: Props) => {
);
const instance = useConfigureMfaStore((s) => s.instance) ?? undefined;

const [selected, setSelected] = useState<ClientAuthorizingMethod | undefined>(
methods[0],
);

const { mutate: cancel, isPending: isCancelling } = useMutation({
// the source is read before the discard resets the store
mutationFn: async () => {
Expand All @@ -63,26 +54,34 @@ export const ConfigureSelectVerificationStep = ({ onCancel }: Props) => {
hideScrollContainer
withControls
>
<FullPageTitle title="Verify your identity to add a new MFA method" />
<p className="description">
<span>{`For security, verify your identity using one of your existing MFA methods. You'll be able to add a new method after verification.`}</span>
</p>
{isPresent(error) && <p className="error">{error}</p>}
<div className="label">
<p>Select an existing method</p>
</div>
<div className="methods">
{methods.map((method) => (
<ConfigureMfaVerificatorFactorSelector
key={method}
mfaMethod={method}
instance={instance}
selected={selected === method}
onClick={() => {
setSelected(method);
}}
/>
))}
<div className="content">
<Icon
icon={IconKind.UserShield}
size={28}
staticColor={ThemeVariable.FgWhite50}
/>
<SizedBox height={ThemeSpacing.Xl2} />
<p className="title">Verify your identity</p>
<SizedBox height={ThemeSpacing.Sm} />
<p className="subtitle">
To add a new MFA method, please select one of the available options to verify
your identity.
</p>
<SizedBox height={ThemeSpacing.Xl2} />
{isPresent(error) && <p className="error">{error}</p>}
<div className="methods">
{methods.map((method) => (
<MfaSelector
key={method}
factor={method}
instance={instance}
isSelectable={!isCancelling}
onClick={() => {
useConfigureMfaStore.getState().selectVerificationMethod(method);
}}
/>
))}
</div>
</div>
<Controls>
<Button
Expand All @@ -93,29 +92,6 @@ export const ConfigureSelectVerificationStep = ({ onCancel }: Props) => {
cancel();
}}
/>
<div className="right">
{selected === MfaMethod.Oidc && (
<ExternalProviderButton
text={`Authenticate with ${openIdProviderName(instance)}`}
provider={findOpenIdProvider(instance) ?? 'custom'}
disabled={isCancelling}
onClick={() => {
useConfigureMfaStore.getState().selectVerificationMethod(MfaMethod.Oidc);
}}
/>
)}
{selected !== MfaMethod.Oidc && (
<Button
text="Verify identity"
variant={ButtonVariant.Primary}
disabled={selected === undefined || isCancelling}
onClick={() => {
if (selected === undefined) return;
useConfigureMfaStore.getState().selectVerificationMethod(selected);
}}
/>
)}
</div>
</Controls>
</FullPage>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
#configure-select-verification-step {
.label {
padding-bottom: var(--spacing-md);
& > .content {
display: flex;
flex: 1 1 auto;
flex-flow: column;
align-items: center;
justify-content: center;

& > p {
font: var(--t-body-xs-500);
& > .title {
text-align: center;
font: var(--t-h4);
color: var(--fg-white-100);
}
}

.methods {
display: flex;
flex-flow: column;
row-gap: var(--spacing-md);
width: 100%;
padding-bottom: var(--spacing-xl);
& > .subtitle {
max-width: 380px;
text-align: center;
font: var(--t-body-sm-400);
color: var(--fg-white-70);
}

& > .error {
padding-bottom: var(--spacing-xl);
text-align: center;
font: var(--t-body-sm-400);
color: var(--fg-critical);
}

& > .methods {
display: flex;
flex-flow: column;
row-gap: var(--spacing-md);
width: 100%;
max-width: 296px;
}
}
}
3 changes: 3 additions & 0 deletions new-ui/src/shared/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ import { IconUbuntu } from './icons/IconUbuntu';
import { IconUpload } from './icons/IconUpload';
import { IconUser } from './icons/IconUser';
import { IconUserActive } from './icons/IconUserActive';
import { IconUserShield } from './icons/IconUserShield';
import { IconUsers } from './icons/IconUsers';
import { IconWarningFilled } from './icons/IconWarningFilled';
import { IconWarningOutlined } from './icons/IconWarningOutlined';
Expand Down Expand Up @@ -430,6 +431,8 @@ export const Icon = <T extends IconKindValue>({
return IconUser;
case 'user-active':
return IconUserActive;
case 'user-shield':
return IconUserShield;
case 'users':
return IconUsers;
case 'webhooks':
Expand Down
1 change: 1 addition & 0 deletions new-ui/src/shared/components/Icon/icon-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const IconKind = {
EmptyPoint: 'empty-point',
DevicesActive: 'devices-active',
UserActive: 'user-active',
UserShield: 'user-shield',
Windows: 'windows',
AppStore: 'app-store',
Apple: 'apple',
Expand Down
31 changes: 31 additions & 0 deletions new-ui/src/shared/components/Icon/icons/IconUserShield.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { SVGProps } from 'react';

export const IconUserShield = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="28"
height="28"
viewBox="0 0 28 28"
fill="none"
{...props}
>
<path
d="M20.2586 13.536C20.4788 13.4404 20.7336 13.4528 20.9463 13.5723C21.5087 13.8884 22.2562 14.2458 23.1588 14.5171L23.1618 14.5182C23.9338 14.7532 24.6338 14.8686 25.2331 14.9235L25.7988 14.9598L25.9441 14.98C26.2733 15.0571 26.5214 15.3438 26.54 15.693C26.5888 16.6159 26.6584 19.3686 24.982 22.1156C23.6602 24.29 21.91 25.384 21.0331 25.8518C20.9048 25.944 20.7493 26 20.5823 26H20.5672C20.4077 26 20.2585 25.9487 20.1336 25.8639C19.2485 25.4076 17.4909 24.3099 16.1654 22.1136C14.4933 19.3415 14.543 16.6405 14.6094 15.6809L14.6336 15.5356C14.7203 15.2078 15.0153 14.9674 15.3657 14.9598C15.9671 14.9474 16.741 14.88 17.6216 14.6543L18.0058 14.5474C18.9079 14.2609 19.6334 13.9051 20.1668 13.5833L20.2586 13.536ZM20.5793 15.1303C20.0036 15.4444 19.2951 15.764 18.4616 16.0278L18.4515 16.0318C17.5898 16.2906 16.8035 16.4126 16.1372 16.4684C16.1457 17.5626 16.3503 19.422 17.4915 21.3139L17.7164 21.6668C18.6792 23.1048 19.8392 23.9232 20.5692 24.3391C21.3643 23.8784 22.6564 22.9611 23.659 21.3119L23.66 21.3098C24.7992 19.4435 24.9938 17.5615 25.0052 16.4563C24.3392 16.3905 23.5644 16.2586 22.7131 15.9995C21.8764 15.748 21.1602 15.4311 20.5793 15.1303Z"
fillRule="evenodd"
clipRule="evenodd"
fill="#7E8794"
/>
<path
d="M12.044 17.137H8.90481C5.66898 17.1374 3.04918 19.7522 3.04893 22.9707C3.04893 23.1229 3.16119 23.2386 3.32019 23.239H12.0591V24.7879H3.32019C2.31065 24.7875 1.5 23.9833 1.5 22.9707C1.50025 18.8918 4.81849 15.5885 8.90481 15.5881H12.044V17.137Z"
fill="#7E8794"
/>
<path
d="M10.4669 3C13.0132 3.00014 15.0801 5.062 15.0804 7.60746V8.86496C15.0803 11.4106 13.0134 13.4723 10.4669 13.4724C7.92043 13.4722 5.85438 11.4105 5.85435 8.86496V7.60746C5.85457 5.06207 7.92055 3.00024 10.4669 3ZM10.4669 4.54893C8.77397 4.54917 7.4035 5.91954 7.40328 7.60746V8.86496C7.40331 10.553 8.77385 11.9233 10.4669 11.9235C12.1599 11.9234 13.5314 10.5531 13.5314 8.86496V7.60746C13.5312 5.91948 12.1598 4.54907 10.4669 4.54893Z"
fillRule="evenodd"
clipRule="evenodd"
fill="#7E8794"
/>
</svg>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
type MfaMethodValue,
} from '../../../../rust-api/types';
import { isPresent } from '../../../../utils/isPresent';
import { findOpenIdProvider, mfaToText } from '../../../../utils/mfa';
import { findOpenIdProvider, mfaToText, openIdProviderName } from '../../../../utils/mfa';
import { Icon } from '../../../Icon';
import checkboxSrc from './assets/checkbox.svg';
import { openIdProviderIcon } from './consts';
Expand Down Expand Up @@ -69,7 +69,11 @@ export const MfaSelector = ({
</div>
)}
<div className="middle">
<p className="name">{mfaToText(factor, instance)}</p>
<p className="name">
{factor === MfaMethod.Oidc
? `Verify with ${openIdProviderName(instance)}`
: mfaToText(factor, instance)}
</p>
</div>
<div className="right">
{(isMobileOnly || !configured) && (
Expand Down
Loading