Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
826450c
update dependencies
wojcik91 Jul 14, 2025
c51363e
add new location MFA config to DB model
wojcik91 Jul 14, 2025
7d1628c
update location struct to include new mfa field
wojcik91 Jul 15, 2025
0da6a46
update API to expect MFA type
wojcik91 Jul 15, 2025
81a78c4
fix translation for network device setup
wojcik91 Jul 15, 2025
d499bb8
add temporary frontend for setting location MFA type
wojcik91 Jul 15, 2025
7510f22
update protos
wojcik91 Jul 15, 2025
a076062
handle updated protos in code
wojcik91 Jul 16, 2025
1a1fbe3
remove openid mfa setting from frontend
wojcik91 Jul 16, 2025
fe2ad22
Merge branch 'dev' into add_location_mfa_settings
wojcik91 Jul 16, 2025
74173ad
update query data
wojcik91 Jul 16, 2025
5cae679
update protos
wojcik91 Jul 16, 2025
48eac8c
handle restored field
wojcik91 Jul 16, 2025
d561778
handle updated field naming
wojcik91 Jul 17, 2025
b37e0b9
handle removal of openid provider
wojcik91 Jul 17, 2025
8ae8dfc
update query data
wojcik91 Jul 17, 2025
de5d3cb
bump version to 1.5.0
wojcik91 Jul 17, 2025
8878c9e
validate correct MFA method is selected
wojcik91 Jul 18, 2025
1c9bcd4
remove unused transaction
wojcik91 Jul 18, 2025
9088ed6
update test network fixtures
wojcik91 Jul 18, 2025
1d8e452
remove remaining references to mfa_enabled
wojcik91 Jul 18, 2025
285ed43
skip e2e tests until final UI is implemented
wojcik91 Jul 18, 2025
6d86f15
add styled MFA mode select
wojcik91 Jul 18, 2025
734711d
add message box and section header
wojcik91 Jul 21, 2025
b8d4ef8
Merge branch 'dev' into add_location_mfa_settings
wojcik91 Jul 21, 2025
a224bfa
reenable e2e openid tests
wojcik91 Jul 21, 2025
53ac122
fix mfa e2e test
wojcik91 Jul 21, 2025
bda7708
formatting
wojcik91 Jul 21, 2025
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: 2 additions & 2 deletions e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "commonjs",
"scripts": {
"lint": "pnpm prettier --check './tests/**.ts' './utils/**/*.ts' && pnpm eslint './tests/**.ts' './utils/**/*.ts'",
"fix": "pnpm prettier -w ./tests/**/*.ts ./utils/**/*.ts && pnpm eslint --fix ./tests/**/*.ts ./utils/**/*.ts",
"fix": "pnpm prettier -w './tests/**.ts' './utils/**/*.ts' && pnpm eslint --fix ./tests/**/*.ts ./utils/**/*.ts",
"test": "pnpm playwright test"
},
"keywords": [],
Expand Down Expand Up @@ -42,4 +42,4 @@
"volta": {
"node": "19.9.0"
}
}
}
2 changes: 1 addition & 1 deletion e2e/tests/externalopenid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ test.describe('External OIDC.', () => {
dockerDown();
});

test.fixme('Login through external oidc.', async ({ page }) => {
test('Login through external oidc.', async ({ page }) => {
expect(client.clientID).toBeDefined();
expect(client.clientSecret).toBeDefined();
await waitForBase(page);
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/externalopenidmfa.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ test.describe('External OIDC.', () => {
dockerDown();
});

test.fixme('Complete client MFA through external OpenID', async ({ page, browser }) => {
test('Complete client MFA through external OpenID', async ({ page, browser }) => {
await waitForBase(page);
const mfaStartUrl = `${testsConfig.ENROLLMENT_URL}/api/v1/client-mfa/start`;
await createDevice(browser, testUser, {
Expand Down
2 changes: 1 addition & 1 deletion e2e/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export type NetworkForm = {
port: string;
allowed_ips?: string;
dns?: string;
location_mfa_mode?:string;
location_mfa_mode?: string;
};

export type DeviceForm = {
Expand Down
11 changes: 10 additions & 1 deletion e2e/utils/controllers/vpn/createNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,20 @@ export const createNetwork = async (browser: Browser, network: NetworkForm) => {
const navNext = page.getByTestId('wizard-next');
await page.getByTestId('setup-option-manual').click();
await navNext.click();
for (const key of Object.keys(network)) {

// fill form
for (const key of Object.keys(network).filter((key) => key !== 'location_mfa_mode')) {
const field = page.getByTestId(`field-${key}`);
await field.clear();
await field.type(network[key]);
}
// select location MFA mode
if (network.location_mfa_mode) {
const mfaModeSelect = page.locator('div.location-mfa-mode-select');
const mfaMode = mfaModeSelect.locator(`div.${network.location_mfa_mode}`);
await mfaMode.click();
}

const responseCreateNetworkPromise = page.waitForResponse('**/network');
await navNext.click();
const response = await responseCreateNetworkPromise;
Expand Down
8 changes: 8 additions & 0 deletions web/src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1991,6 +1991,11 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do
'By default, all users will be allowed to connect to this location. If you want to restrict access to this location to a specific group, please select it below.',
aclFeatureDisabled:
"ACL functionality is an enterprise feature and you've exceeded the user, device or network limits to use it. In order to use this feature, purchase an enterprise license or upgrade your existing one.",
locationMfaMode: {
description: 'Choose how MFA is enforced when connecting to this location:',
internal: "Internal MFA - MFA is enforced using Defguard's built-in MFA (e.g. TOTP, WebAuthn) with internal identity",
external: 'External MFA - If configured (see [OpenID settings](settings)) this option uses external identity provider for MFA',
},
},
messages: {
networkModified: 'Location modified.',
Expand Down Expand Up @@ -2031,6 +2036,9 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do
acl_default_allow: {
label: 'Default ACL policy',
},
location_mfa_mode: {
label: 'MFA requirement',
}
},
controls: {
submit: 'Save changes',
Expand Down
40 changes: 40 additions & 0 deletions web/src/i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4788,6 +4788,20 @@ type RootTranslation = {
* A​C​L​ ​f​u​n​c​t​i​o​n​a​l​i​t​y​ ​i​s​ ​a​n​ ​e​n​t​e​r​p​r​i​s​e​ ​f​e​a​t​u​r​e​ ​a​n​d​ ​y​o​u​'​v​e​ ​e​x​c​e​e​d​e​d​ ​t​h​e​ ​u​s​e​r​,​ ​d​e​v​i​c​e​ ​o​r​ ​n​e​t​w​o​r​k​ ​l​i​m​i​t​s​ ​t​o​ ​u​s​e​ ​i​t​.​ ​I​n​ ​o​r​d​e​r​ ​t​o​ ​u​s​e​ ​t​h​i​s​ ​f​e​a​t​u​r​e​,​ ​p​u​r​c​h​a​s​e​ ​a​n​ ​e​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e​ ​o​r​ ​u​p​g​r​a​d​e​ ​y​o​u​r​ ​e​x​i​s​t​i​n​g​ ​o​n​e​.
*/
aclFeatureDisabled: string
locationMfaMode: {
/**
* C​h​o​o​s​e​ ​h​o​w​ ​M​F​A​ ​i​s​ ​e​n​f​o​r​c​e​d​ ​w​h​e​n​ ​c​o​n​n​e​c​t​i​n​g​ ​t​o​ ​t​h​i​s​ ​l​o​c​a​t​i​o​n​:
*/
description: string
/**
* I​n​t​e​r​n​a​l​ ​M​F​A​ ​-​ ​M​F​A​ ​i​s​ ​e​n​f​o​r​c​e​d​ ​u​s​i​n​g​ ​D​e​f​g​u​a​r​d​'​s​ ​b​u​i​l​t​-​i​n​ ​M​F​A​ ​(​e​.​g​.​ ​T​O​T​P​,​ ​W​e​b​A​u​t​h​n​)​ ​w​i​t​h​ ​i​n​t​e​r​n​a​l​ ​i​d​e​n​t​i​t​y
*/
internal: string
/**
* E​x​t​e​r​n​a​l​ ​M​F​A​ ​-​ ​I​f​ ​c​o​n​f​i​g​u​r​e​d​ ​(​s​e​e​ ​[​O​p​e​n​I​D​ ​s​e​t​t​i​n​g​s​]​(​s​e​t​t​i​n​g​s​)​)​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​u​s​e​s​ ​e​x​t​e​r​n​a​l​ ​i​d​e​n​t​i​t​y​ ​p​r​o​v​i​d​e​r​ ​f​o​r​ ​M​F​A
*/
external: string
}
}
messages: {
/**
Expand Down Expand Up @@ -4870,6 +4884,12 @@ type RootTranslation = {
*/
label: string
}
location_mfa_mode: {
/**
* M​F​A​ ​r​e​q​u​i​r​e​m​e​n​t
*/
label: string
}
}
controls: {
/**
Expand Down Expand Up @@ -11339,6 +11359,20 @@ export type TranslationFunctions = {
* ACL functionality is an enterprise feature and you've exceeded the user, device or network limits to use it. In order to use this feature, purchase an enterprise license or upgrade your existing one.
*/
aclFeatureDisabled: () => LocalizedString
locationMfaMode: {
/**
* Choose how MFA is enforced when connecting to this location:
*/
description: () => LocalizedString
/**
* Internal MFA - MFA is enforced using Defguard's built-in MFA (e.g. TOTP, WebAuthn) with internal identity
*/
internal: () => LocalizedString
/**
* External MFA - If configured (see [OpenID settings](settings)) this option uses external identity provider for MFA
*/
external: () => LocalizedString
}
}
messages: {
/**
Expand Down Expand Up @@ -11421,6 +11455,12 @@ export type TranslationFunctions = {
*/
label: () => LocalizedString
}
location_mfa_mode: {
/**
* MFA requirement
*/
label: () => LocalizedString
}
}
controls: {
/**
Expand Down
18 changes: 18 additions & 0 deletions web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { shallow } from 'zustand/shallow';
import { useI18nContext } from '../../../i18n/i18n-react';
import { FormAclDefaultPolicy } from '../../../shared/components/Form/FormAclDefaultPolicySelect/FormAclDefaultPolicy.tsx';
import { FormLocationMfaModeSelect } from '../../../shared/components/Form/FormLocationMfaModeSelect/FormLocationMfaModeSelect.tsx';
import { RenderMarkdown } from '../../../shared/components/Layout/RenderMarkdown/RenderMarkdown.tsx';
import { FormCheckBox } from '../../../shared/defguard-ui/components/Form/FormCheckBox/FormCheckBox.tsx';
import { FormInput } from '../../../shared/defguard-ui/components/Form/FormInput/FormInput';
import { FormSelect } from '../../../shared/defguard-ui/components/Form/FormSelect/FormSelect';
Expand All @@ -31,6 +32,7 @@ import {
validateIpOrDomainList,
} from '../../../shared/validators';
import { useNetworkPageStore } from '../hooks/useNetworkPageStore';
import { DividerHeader } from './components/DividerHeader.tsx';

export const NetworkEditForm = () => {
const toaster = useToaster();
Expand Down Expand Up @@ -341,6 +343,22 @@ export const NetworkEditForm = () => {
label={LL.networkConfiguration.form.fields.peer_disconnect_threshold.label()}
type="number"
/>
<DividerHeader
text={LL.networkConfiguration.form.fields.location_mfa_mode.label()}
/>
<MessageBox id="location-mfa-mode-explain-message-box">
<p>{LL.networkConfiguration.form.helpers.locationMfaMode.description()}</p>
<ul>
<li>
<p>{LL.networkConfiguration.form.helpers.locationMfaMode.internal()}</p>
</li>
<li>
<RenderMarkdown
content={LL.networkConfiguration.form.helpers.locationMfaMode.external()}
/>
</li>
</ul>
</MessageBox>
<FormLocationMfaModeSelect controller={{ control, name: 'location_mfa_mode' }} />
<button type="submit" className="hidden" ref={submitRef}></button>
</form>
Expand Down
16 changes: 16 additions & 0 deletions web/src/pages/network/NetworkEditForm/components/DividerHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { PropsWithChildren } from 'react';

type DividerHeaderProps = {
text: string;
} & PropsWithChildren;

export const DividerHeader = ({ text, children }: DividerHeaderProps) => {
return (
<div className="divider-header spacer">
<div className="inner">
<p className="header">{text}</p>
{children}
</div>
</div>
);
};
29 changes: 29 additions & 0 deletions web/src/pages/network/NetworkEditForm/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,33 @@
}
}
}

#location-mfa-mode-explain-message-box {
ul {
list-style-position: inside;
margin-top: 8px;

li {
p {
display: inline;
}
}
}
}

.divider-header {
padding-bottom: var(--spacing-s);

.inner {
display: flex;
flex-flow: row;
align-items: center;
justify-content: flex-start;
border-bottom: 1px solid var(--border-primary);
}

.header {
@include typography(app-side-bar);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import './style.scss';
import clsx from 'clsx';
import { useMemo } from 'react';
import type { FieldValues, UseControllerProps } from 'react-hook-form';

import {
type FieldValues,
type UseControllerProps,
useController,
} from 'react-hook-form';
import { useI18nContext } from '../../../../i18n/i18n-react';
import { FormSelect } from '../../../defguard-ui/components/Form/FormSelect/FormSelect';
import { RadioButton } from '../../../defguard-ui/components/Layout/RadioButton/Radiobutton';
import type { SelectOption } from '../../../defguard-ui/components/Layout/Select/types';
import { LocationMfaMode } from '../../../types';

type Props<T extends FieldValues> = {
controller: UseControllerProps<T>;
disabled?: boolean;
};

export const FormLocationMfaModeSelect = <T extends FieldValues>({
controller,
disabled = false,
}: Props<T>) => {
const { LL } = useI18nContext();
const {
field: { onChange, value: fieldValue },
} = useController(controller);

const options = useMemo(
(): SelectOption<LocationMfaMode>[] => [
Expand All @@ -37,12 +43,26 @@ export const FormLocationMfaModeSelect = <T extends FieldValues>({
],
[LL.components.aclDefaultPolicySelect.options],
);

return (
<FormSelect
controller={controller}
options={options}
label={LL.components.locationMfaModeSelect.label()}
disabled={disabled}
/>
<div className="location-mfa-mode-select">
{options.map(({ key, value, label }) => {
const active = fieldValue === value;
return (
<div
className={clsx(`location-mfa-mode ${value}`, {
active,
})}
key={key}
onClick={() => {
onChange(value);
}}
>
<p className="label">{label}</p>
<RadioButton active={active} />
</div>
);
})}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.location-mfa-mode-select {
display: flex;
flex-flow: column;
row-gap: var(--spacing-s);

.location-mfa-mode {
display: flex;
align-items: center;
justify-content: space-between;
column-gap: var(--spacing-xs);
min-height: 30px;
border: 1px solid var(--border-primary);
padding: var(--spacing-xs) var(--spacing-s);
border-radius: 10px;
cursor: pointer;
user-select: none;
transition-property: border-color;

@include animate-standard;

&:not(.active) {
&:hover {
border-color: var(--border-separator);
}
}

&.active {
border-color: var(--surface-main-primary);
}

&.active,
&:hover {
.label {
color: var(--text-body-primary);
}
}

.label {
color: var(--text-body-secondary);
transition-property: color;
@include typography(app-modal-1);
@include animate-standard;
}
}
}