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
2 changes: 1 addition & 1 deletion .trivyignore.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
vulnerabilities:
- id: GHSA-wrw7-89jp-8q8g
expired_at: 2026-09-30
expired_at: 2026-10-30
statement: "glib is a transitive dependency of Tauri which we cannot update ourselves. Waiting for tauri to finish migration to gtk4-rs: https://github.com/tauri-apps/tauri/issues/12563"
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ export const ConnectModal = () => {
useShallow((s) => [s.view, s.visible, s.location, s.stepIndex, s.stepPlan]),
);

const { instances } = useAppData();
const instance = instances.find((entry) => entry.id === location?.instance_id);

const stepCount = isPresent(location) ? mfaStepCount(location) : 0;
const stepMethod = stepPlan[stepIndex];
const isOnMfaStepView =
isPresent(stepMethod) && view === mfaMethodToConnectModalView(stepMethod);
const stepLabel =
stepCount > 1 && isOnMfaStepView
? `Step ${stepIndex + 1}/${stepCount}: ${mfaToText(stepMethod)}`
? `Step ${stepIndex + 1}/${stepCount}: ${mfaToText(stepMethod, instance)}`
: null;

const isOpen = isPresent(view) && isPresent(location) && visible;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import { useShallow } from 'zustand/shallow';
import { Button } from '../../../../../../../shared/components/Button/Button';
import { ButtonVariant } from '../../../../../../../shared/components/Button/types';
import { Checkbox } from '../../../../../../../shared/components/Checkbox/Checkbox';
import { ExternalProviderButton } from '../../../../../../../shared/components/ExternalProviderButton/ExternalProviderButton';
import { SizedBox } from '../../../../../../../shared/components/SizedBox/SizedBox';
import { useAppData } from '../../../../../../../shared/providers/AppDataContext';
import { api } from '../../../../../../../shared/rust-api/api';
import { getAppConfigQueryOptions } from '../../../../../../../shared/rust-api/query';
import { ThemeSpacing } from '../../../../../../../shared/types';
import { isPresent } from '../../../../../../../shared/utils/isPresent';
import {
findOpenIdProvider,
openIdProviderName,
} from '../../../../../../../shared/utils/mfa';
import { ConnectModalPostureCheckLoading } from '../../components/ConnectModalPostureCheckLoading/ConnectModalPostureCheckLoading';
import { ConnectModalView } from '../../hooks/types';
import { useConnectModal } from '../../hooks/useConnectModal';
Expand All @@ -22,6 +28,9 @@ export const ConnectModalMfaOidc = () => {
const [perviousView, location, initAutoStart] = useConnectModal(
useShallow((s) => [s.perviousView, s.location, s.autoStartOpenId]),
);
const { instances } = useAppData();
const instance = instances.find((entry) => entry.id === location?.instance_id);
const providerName = openIdProviderName(instance);

const { start, isStarting, startError, isPolling, pollError } = useConnectModalMfaOidc({
autoStart: initAutoStart,
Expand Down Expand Up @@ -60,8 +69,7 @@ export const ConnectModalMfaOidc = () => {
<div id="mfa-oidc-view">
{screen === 'idle' && (
<p className="view-description">
To connect to the VPN, authenticate via your OpenID provider. A browser window
will open for you to sign in.
{`To connect to the VPN, sign in with ${providerName} by clicking button below. A browser window will open and redirect you to ${providerName} for authentication.`}
</p>
)}
{screen === 'polling' && (
Expand All @@ -72,9 +80,9 @@ export const ConnectModalMfaOidc = () => {
{screen === 'error' && <p className="view-description">{errorMessage}</p>}
<div className="actions">
{screen !== 'error' && (
<Button
text="Auth with OpenID"
variant={ButtonVariant.Primary}
<ExternalProviderButton
text={`Authenticate with ${providerName}`}
provider={findOpenIdProvider(instance) ?? 'custom'}
loading={screen === 'polling' || isStarting}
onClick={handleStart}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#mfa-oidc-view {
.actions {
.btn {
.btn,
.external-provider-button {
width: 100%;
}
}
Expand Down
2 changes: 2 additions & 0 deletions new-ui/src/pages/playground/PlaygroundIndex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PlaygroundTestMenu } from './components/PlaygroundTestMenu/PlaygroundTe
import { PlaygroundTestMfaSelector } from './components/PlaygroundTestMfaSelector/PlaygroundTestMfaSelector';
import { PlaygroundTestMfaSettingsSection } from './components/PlaygroundTestMfaSettingsSection/PlaygroundTestMfaSettingsSection';
import { PlaygroundTestMfaVerificatorFactorSelector } from './components/PlaygroundTestMfaVerificatorFactorSelector/PlaygroundTestMfaVerificatorFactorSelector';
import { PlaygroundTestOpenIdProviders } from './components/PlaygroundTestOpenIdProviders/PlaygroundTestOpenIdProviders';
import { PlaygroundTestSelect } from './components/PlaygroundTestSelect';

export const PlaygroundIndex = () => {
Expand All @@ -18,6 +19,7 @@ export const PlaygroundIndex = () => {
<div className="main-track">
<PlaygroundTestConnectButton />
<PlaygroundTestMfaSelector />
<PlaygroundTestOpenIdProviders />
<PlaygroundTestMfaVerificatorFactorSelector />
<PlaygroundTestInstanceSelector />
<PlaygroundTestMfaSettingsSection />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const singleStepLocation: MfaSettingsLocation = {

const instance: MfaSettingsInstance = {
mfa_configured_methods: [MfaMethod.Totp, MfaMethod.Oidc, MfaMethod.Fido2],
openid_display_name: null,
};

const Demo = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import './style.scss';
import { ExternalProviderButton } from '../../../../shared/components/ExternalProviderButton/ExternalProviderButton';
import { MfaSelector } from '../../../../shared/components/LocationCard/components/MfaSelector/MfaSelector';
import { type InstanceInfo, MfaMethod } from '../../../../shared/rust-api/types';
import { findOpenIdProvider, openIdProviderName } from '../../../../shared/utils/mfa';
import { PlaygroundCard } from '../PlaygroundCard/PlaygroundCard';

const noop = () => {};

const instances: Pick<InstanceInfo, 'openid_display_name'>[] = [
{ openid_display_name: 'Microsoft' },
{ openid_display_name: 'Google' },
{ openid_display_name: 'Okta' },
{ openid_display_name: 'JumpCloud' },
{ openid_display_name: 'Keycloak' },
{ openid_display_name: 'Contoso Enterprise Single Sign-On Identity Provider' },
];

export const PlaygroundTestOpenIdProviders = () => {
return (
<PlaygroundCard>
<div className="playground-test-openid-providers">
<h3>Authenticate (full view)</h3>
<div className="track full">
{instances.map((instance) => (
<ExternalProviderButton
key={instance.openid_display_name}
text={`Authenticate with ${openIdProviderName(instance)}`}
provider={findOpenIdProvider(instance) ?? 'custom'}
onClick={noop}
/>
))}
</div>
<h3>Authenticate (tray view)</h3>
<div className="track tray">
{instances.map((instance) => (
<ExternalProviderButton
key={instance.openid_display_name}
text={`Authenticate with ${openIdProviderName(instance)}`}
provider={findOpenIdProvider(instance) ?? 'custom'}
onClick={noop}
/>
))}
</div>
<h3>Authenticate (loading)</h3>
<div className="track full">
<ExternalProviderButton
text="Authenticate with Google"
provider="google"
loading
onClick={noop}
/>
</div>
<h3>MFA method list</h3>
<div className="track">
{instances.map((instance) => (
<MfaSelector
key={instance.openid_display_name}
factor={MfaMethod.Oidc}
instance={instance}
isSelectable
/>
))}
</div>
<h3>MFA method list (selected)</h3>
<div className="track">
{instances.map((instance) => (
<MfaSelector
key={instance.openid_display_name}
factor={MfaMethod.Oidc}
instance={instance}
isSelectable
selected
/>
))}
</div>
<h3>MFA method list (not configured)</h3>
<div className="track">
{instances.map((instance) => (
<MfaSelector
key={instance.openid_display_name}
factor={MfaMethod.Oidc}
instance={instance}
configured={false}
isSelectable={false}
/>
))}
</div>
</div>
</PlaygroundCard>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.playground-card:has(> .playground-test-openid-providers) {
background-color: var(--bg-dark-blue-40);
}

.playground-test-openid-providers {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
align-items: center;

.track {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
max-width: 296px;

&.full {
max-width: 328px;
}

&.tray {
max-width: 248px;
}

.external-provider-button {
width: 100%;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import './style.scss';
import { clsx } from 'clsx';
import { AnimatePresence, motion } from 'motion/react';
import { useMemo } from 'react';
import { motionTransitionStandard } from '../../consts';
import { LoaderSpinner } from '../LoaderSpinner/LoaderSpinner';
import customImage from './assets/custom.svg';
import googleImage from './assets/google.svg';
import jumpcloudImage from './assets/jumpcloud.svg';
import microsoftImage from './assets/microsoft.svg';
import oktaImage from './assets/okta.svg';
import type { ExternalProviderButtonProps } from './types';

export const ExternalProviderButton = ({
text,
testId,
provider,
onClick,
ref,
size = 'primary',
variant = 'primary',
type = 'button',
disabled = false,
loading = false,
className,
...props
}: ExternalProviderButtonProps) => {
const providerImage = useMemo(() => {
switch (provider) {
case 'microsoft':
return microsoftImage;
case 'google':
return googleImage;
case 'okta':
return oktaImage;
case 'jumpcloud':
return jumpcloudImage;
case 'custom':
return customImage;
}
}, [provider]);

return (
<button
{...props}
data-variant={variant}
data-testid={testId}
ref={ref}
type={type}
disabled={disabled || loading}
onClick={(e) => {
if (!disabled && !loading) {
onClick?.(e);
}
}}
className={clsx(
'external-provider-button',
`variant-${variant}`,
`size-${size}`,
className,
{
disabled,
loading: !disabled && loading,
},
)}
>
<img src={providerImage} width={16} height={16} loading="eager" />
<span className="text">{text}</span>
<AnimatePresence mode="wait">
{loading && !disabled && (
<motion.div
className="loader-overlay"
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
exit={{ opacity: 0 }}
transition={motionTransitionStandard}
>
<LoaderSpinner />
</motion.div>
)}
</AnimatePresence>
</button>
);
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading