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/messages/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
"misc_secret": "Secret",
"misc_active": "Active",
"misc_disabled": "Disabled",
"license_business_required": "Available in Business plan.",
"license_upgrade_business_tooltip": "This feature is part of a paid plan.\nUpgrade to Business to activate it.",
"license_enterprise_required": "Available in Enterprise plan.",
"license_upgrade_to_unlock": "Upgrade to unlock.",
"controls_connect": "Connect",
"controls_search": "Search",
"controls_accept": "Accept",
Expand Down
7 changes: 6 additions & 1 deletion web/messages/en/location.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"location_delete_success": "Location deleted",
"add_location_firewall_no_license_subtitle": "Firewall is available only in Business and Enterprise plans. You can still create location now and activate Firewall later by upgrading your plan.",
"add_location_internal_vpn_allowed_ips_from_firewall_rules": "Allowed IPs are generated automatically from firewall rules", "add_location_mfa_disabled_title": "Do not enforce MFA",
"add_location_mfa_internal_title": "Internal Defguard Multi-Factor Authentication",
"add_location_mfa_internal_content": "Uses the MFA methods configured in your Defguard profile.",
"add_location_mfa_external_title": "External Identity Provider Authentication",
"add_location_mfa_external_content": "Requires configuring an external identity provider in the settings, such as Google, Microsoft Entra ID, Okta, or JumpCloud.", "location_delete_success": "Location deleted",
"location_delete_failed": "Failed to delete location",
"location_edit_failed": "Failed to update location",
"location_edit_failed_has_devices": "Gateway VPN IP address and netmask can’t be changed while devices are active on this network. To proceed, remove all devices from the current network first."
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 35 additions & 2 deletions web/src/pages/AddLocationPage/steps/AddLocationFirewallStep.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import { cloneDeep, omit } from 'lodash-es';
import { useCallback, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { m } from '../../../paraglide/messages';
import api from '../../../shared/api/api';
import { ActionCard } from '../../../shared/components/ActionCard/ActionCard';
import { WizardCard } from '../../../shared/components/wizard/WizardCard/WizardCard';
import { externalLink } from '../../../shared/constants';
import { Button } from '../../../shared/defguard-ui/components/Button/Button';
import { Checkbox } from '../../../shared/defguard-ui/components/Checkbox/Checkbox';
import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider';
import { ModalControls } from '../../../shared/defguard-ui/components/ModalControls/ModalControls';
import { Radio } from '../../../shared/defguard-ui/components/Radio/Radio';
import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox';
import { ThemeSpacing } from '../../../shared/defguard-ui/types';
import { isPresent } from '../../../shared/defguard-ui/utils/isPresent';
import { getLicenseInfoQueryOptions } from '../../../shared/query';
import { canUseBusinessFeature } from '../../../shared/utils/license';
import { useGatewayWizardStore } from '../../GatewaySetupPage/useGatewayWizardStore';
import businessFeatureCardImage from '../assets/business-feature-icon.png';
import actionCardImage from '../assets/gateway-setup-action-card.png';
import { AddLocationPageStep } from '../types';
import { useAddLocationStore } from '../useAddLocationStore';
Expand All @@ -25,6 +30,13 @@ export const AddLocationFirewallStep = () => {
const [state, setState] = useState<Choice>('disable');
const navigate = useNavigate();

const { data: licenseInfo } = useQuery(getLicenseInfoQueryOptions);
const canUseFeature = useMemo(() => {
if (licenseInfo === undefined) return undefined;
return canUseBusinessFeature(licenseInfo).result;
}, [licenseInfo]);
const firewallLocked = isPresent(canUseFeature) && !canUseFeature;

const { mutate, isPending } = useMutation({
mutationFn: api.location.addLocation,
meta: {
Expand Down Expand Up @@ -75,12 +87,31 @@ export const AddLocationFirewallStep = () => {

return (
<WizardCard>
{firewallLocked && (
<>
<ActionCard
imageSrc={businessFeatureCardImage}
title=""
subtitle={m.add_location_firewall_no_license_subtitle()}
>
<a href={externalLink.defguard.pricing} target="_blank" rel="noreferrer">
<Button
variant="outlined"
text="See other plans"
iconRight="open-in-new-window"
/>
</a>
</ActionCard>
<Divider spacing={ThemeSpacing.Xl2} />
</>
)}
<Radio
active={state === 'disable'}
onClick={() => {
setState('disable');
}}
text="Disable firewall option"
disabled={firewallLocked}
/>
<SizedBox height={ThemeSpacing.Md} />
<Radio
Expand All @@ -89,6 +120,7 @@ export const AddLocationFirewallStep = () => {
setState('enabled-allowed');
}}
text="Users/devices can access all resources unless limited by ACL rules."
disabled={firewallLocked}
/>
<SizedBox height={ThemeSpacing.Md} />
<Radio
Expand All @@ -97,6 +129,7 @@ export const AddLocationFirewallStep = () => {
setState('enabled-denied');
}}
text="All traffic not explicitly allowed by an ACL rule will be blocked."
disabled={firewallLocked}
/>
<Divider spacing={ThemeSpacing.Xl2} />
<ActionCard
Expand Down
31 changes: 31 additions & 0 deletions web/src/pages/AddLocationPage/steps/AddLocationInternalVpnStep.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react';
import z from 'zod';
import { useShallow } from 'zustand/react/shallow';
import { m } from '../../../paraglide/messages';
Expand All @@ -6,9 +8,16 @@ import { WizardCard } from '../../../shared/components/wizard/WizardCard/WizardC
import { Button } from '../../../shared/defguard-ui/components/Button/Button';
import { ModalControls } from '../../../shared/defguard-ui/components/ModalControls/ModalControls';
import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox';
import { Toggle } from '../../../shared/defguard-ui/components/Toggle/Toggle';
import { TooltipContent } from '../../../shared/defguard-ui/providers/tooltip/TooltipContent';
import { TooltipProvider } from '../../../shared/defguard-ui/providers/tooltip/TooltipContext';
import { TooltipTrigger } from '../../../shared/defguard-ui/providers/tooltip/TooltipTrigger';
import { ThemeSpacing } from '../../../shared/defguard-ui/types';
import { isPresent } from '../../../shared/defguard-ui/utils/isPresent';
import { useAppForm } from '../../../shared/form';
import { formChangeLogic } from '../../../shared/formLogic';
import { getLicenseInfoQueryOptions } from '../../../shared/query';
import { canUseBusinessFeature } from '../../../shared/utils/license';
import { Validate } from '../../../shared/validate';
import { AddLocationPageStep } from '../types';
import { useAddLocationStore } from '../useAddLocationStore';
Expand All @@ -29,6 +38,13 @@ const formSchema = z.object({
type FormFields = z.infer<typeof formSchema>;

export const AddLocationInternalVpnStep = () => {
const { data: licenseInfo } = useQuery(getLicenseInfoQueryOptions);
const canUseBusiness = useMemo(() => {
if (licenseInfo === undefined) return undefined;
return canUseBusinessFeature(licenseInfo).result;
}, [licenseInfo]);
const firewallRulesToggleLocked = isPresent(canUseBusiness) && !canUseBusiness;

const defaultValues = useAddLocationStore(
useShallow(
(s): FormFields => ({
Expand Down Expand Up @@ -84,6 +100,21 @@ export const AddLocationInternalVpnStep = () => {
<form.AppField name="allowed_ips">
{(field) => <field.FormInput required label={'Allowed IPs'} />}
</form.AppField>
<SizedBox height={ThemeSpacing.Lg} />
<TooltipProvider disabled={!firewallRulesToggleLocked} placement="top-start">
<TooltipTrigger>
<div>
<Toggle // Does nothing now #TODO: implement generating allowed ips based on firewall rules
active={false}
disabled={firewallRulesToggleLocked}
label={m.add_location_internal_vpn_allowed_ips_from_firewall_rules()}
/>
</div>
</TooltipTrigger>
<TooltipContent>
<p>{m.license_upgrade_business_tooltip()}</p>
</TooltipContent>
</TooltipProvider>
<SizedBox height={ThemeSpacing.Xl} />
<form.AppField name="dns">
{(field) => <field.FormInput label={'DNS'} />}
Expand Down
47 changes: 31 additions & 16 deletions web/src/pages/AddLocationPage/steps/AddLocationMfaStep.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { useEffect, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useEffect, useMemo, useState } from 'react';
import z from 'zod';
import { m } from '../../../paraglide/messages';
import { LocationMfaMode, type NetworkLocation } from '../../../shared/api/types';
import { businessBadgeProps } from '../../../shared/components/badges/BusinessBadge';
import { WizardCard } from '../../../shared/components/wizard/WizardCard/WizardCard';
import { Button } from '../../../shared/defguard-ui/components/Button/Button';
import { Input } from '../../../shared/defguard-ui/components/Input/Input';
import { InteractiveBlock } from '../../../shared/defguard-ui/components/InteractiveBlock/InteractiveBlock';
import { ModalControls } from '../../../shared/defguard-ui/components/ModalControls/ModalControls';
import { Radio } from '../../../shared/defguard-ui/components/Radio/Radio';
import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox';
import { ThemeSpacing } from '../../../shared/defguard-ui/types';
import { isPresent } from '../../../shared/defguard-ui/utils/isPresent';
import { getLicenseInfoQueryOptions } from '../../../shared/query';
import { canUseBusinessFeature } from '../../../shared/utils/license';
import { AddLocationPageStep } from '../types';
import { useAddLocationStore } from '../useAddLocationStore';

Expand All @@ -20,6 +24,11 @@ const schema = z
export const AddLocationMfaStep = () => {
const [error, setError] = useState<string | null>(null);
const [disconnect, setDisconnect] = useState<number | null>(300);
const { data: licenseInfo } = useQuery(getLicenseInfoQueryOptions);
const canUseFeature = useMemo(() => {
if (licenseInfo === undefined) return undefined;
return canUseBusinessFeature(licenseInfo).result;
}, [licenseInfo]);

const [choice, setChoice] = useState<NetworkLocation['location_mfa_mode']>(
LocationMfaMode.Disabled,
Expand Down Expand Up @@ -49,25 +58,31 @@ export const AddLocationMfaStep = () => {

return (
<WizardCard>
<Radio
active={choice === LocationMfaMode.Disabled}
<InteractiveBlock
value={choice === LocationMfaMode.Disabled}
onClick={() => setChoice(LocationMfaMode.Disabled)}
text="Do not enforce MFA"
testId="do-not-enforce-mfa"
title={m.add_location_mfa_disabled_title()}
data-testid="do-not-enforce-mfa"
/>
<SizedBox height={ThemeSpacing.Md} />
<Radio
active={choice === LocationMfaMode.Internal}
<SizedBox height={ThemeSpacing.Xl} />
<InteractiveBlock
value={choice === LocationMfaMode.Internal}
onClick={() => setChoice(LocationMfaMode.Internal)}
text="Internal MFA"
testId="enforce-internal-mfa"
title={m.add_location_mfa_internal_title()}
content={m.add_location_mfa_internal_content()}
data-testid="enforce-internal-mfa"
/>
<SizedBox height={ThemeSpacing.Md} />
<Radio
active={choice === LocationMfaMode.External}
<SizedBox height={ThemeSpacing.Xl} />
<InteractiveBlock
value={choice === LocationMfaMode.External}
onClick={() => setChoice(LocationMfaMode.External)}
text="External MFA"
testId="enforce-external-mfa"
title={m.add_location_mfa_external_title()}
content={m.add_location_mfa_external_content()}
disabled={isPresent(canUseFeature) && !canUseFeature}
badge={
isPresent(canUseFeature) && !canUseFeature ? businessBadgeProps : undefined
}
data-testid="enforce-external-mfa"
/>
{choice !== LocationMfaMode.Disabled && (
<>
Expand Down
Loading
Loading