BREAKING CHANGE(vpn): replace tag-based VPN access control with source and target profiles

This commit is contained in:
2026-04-05 00:37:37 +00:00
parent 25365678e0
commit 1ddf83b28d
38 changed files with 1546 additions and 321 deletions

View File

@@ -116,7 +116,7 @@ export const configStatePart = await appState.getStatePart<IConfigState>(
// Determine initial view from URL path
const getInitialView = (): string => {
const path = typeof window !== 'undefined' ? window.location.pathname : '/';
const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress', 'securityprofiles', 'networktargets'];
const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress', 'sourceprofiles', 'networktargets', 'targetprofiles'];
const segments = path.split('/').filter(Boolean);
const view = segments[0];
return validViews.includes(view) ? view : 'overview';
@@ -459,12 +459,19 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
}
// If switching to security profiles or network targets views, fetch profiles/targets data
if ((viewName === 'securityprofiles' || viewName === 'networktargets') && currentState.activeView !== viewName) {
if ((viewName === 'sourceprofiles' || viewName === 'networktargets') && currentState.activeView !== viewName) {
setTimeout(() => {
profilesTargetsStatePart.dispatchAction(fetchProfilesAndTargetsAction, null);
}, 100);
}
// If switching to target profiles view, fetch target profiles data
if (viewName === 'targetprofiles' && currentState.activeView !== viewName) {
setTimeout(() => {
targetProfilesStatePart.dispatchAction(fetchTargetProfilesAction, null);
}, 100);
}
return {
...currentState,
activeView: viewName,
@@ -1006,7 +1013,7 @@ export const fetchVpnAction = vpnStatePart.createAction(async (statePartArg): Pr
export const createVpnClientAction = vpnStatePart.createAction<{
clientId: string;
serverDefinedClientTags?: string[];
targetProfileIds?: string[];
description?: string;
forceDestinationSmartproxy?: boolean;
destinationAllowList?: string[];
@@ -1028,7 +1035,7 @@ export const createVpnClientAction = vpnStatePart.createAction<{
const response = await request.fire({
identity: context.identity!,
clientId: dataArg.clientId,
serverDefinedClientTags: dataArg.serverDefinedClientTags,
targetProfileIds: dataArg.targetProfileIds,
description: dataArg.description,
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
destinationAllowList: dataArg.destinationAllowList,
@@ -1105,7 +1112,7 @@ export const toggleVpnClientAction = vpnStatePart.createAction<{
export const updateVpnClientAction = vpnStatePart.createAction<{
clientId: string;
description?: string;
serverDefinedClientTags?: string[];
targetProfileIds?: string[];
forceDestinationSmartproxy?: boolean;
destinationAllowList?: string[];
destinationBlockList?: string[];
@@ -1127,7 +1134,7 @@ export const updateVpnClientAction = vpnStatePart.createAction<{
identity: context.identity!,
clientId: dataArg.clientId,
description: dataArg.description,
serverDefinedClientTags: dataArg.serverDefinedClientTags,
targetProfileIds: dataArg.targetProfileIds,
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
destinationAllowList: dataArg.destinationAllowList,
destinationBlockList: dataArg.destinationBlockList,
@@ -1158,11 +1165,167 @@ export const clearNewClientConfigAction = vpnStatePart.createAction(
);
// ============================================================================
// Security Profiles & Network Targets State
// Target Profiles State
// ============================================================================
export interface ITargetProfilesState {
profiles: interfaces.data.ITargetProfile[];
isLoading: boolean;
error: string | null;
lastUpdated: number;
}
export const targetProfilesStatePart = await appState.getStatePart<ITargetProfilesState>(
'targetProfiles',
{
profiles: [],
isLoading: false,
error: null,
lastUpdated: 0,
},
'soft'
);
// ============================================================================
// Target Profiles Actions
// ============================================================================
export const fetchTargetProfilesAction = targetProfilesStatePart.createAction(
async (statePartArg): Promise<ITargetProfilesState> => {
const context = getActionContext();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetTargetProfiles
>('/typedrequest', 'getTargetProfiles');
const response = await request.fire({ identity: context.identity });
return {
profiles: response.profiles,
isLoading: false,
error: null,
lastUpdated: Date.now(),
};
} catch (error) {
return {
...currentState,
isLoading: false,
error: error instanceof Error ? error.message : 'Failed to fetch target profiles',
};
}
}
);
export const createTargetProfileAction = targetProfilesStatePart.createAction<{
name: string;
description?: string;
domains?: string[];
targets?: Array<{ host: string; port: number }>;
routeRefs?: string[];
}>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
const context = getActionContext();
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_CreateTargetProfile
>('/typedrequest', 'createTargetProfile');
const response = await request.fire({
identity: context.identity!,
name: dataArg.name,
description: dataArg.description,
domains: dataArg.domains,
targets: dataArg.targets,
routeRefs: dataArg.routeRefs,
});
if (!response.success) {
return {
...statePartArg.getState()!,
error: response.message || 'Failed to create target profile',
};
}
return await actionContext!.dispatch(fetchTargetProfilesAction, null);
} catch (error: unknown) {
return {
...statePartArg.getState()!,
error: error instanceof Error ? error.message : 'Failed to create target profile',
};
}
});
export const updateTargetProfileAction = targetProfilesStatePart.createAction<{
id: string;
name?: string;
description?: string;
domains?: string[];
targets?: Array<{ host: string; port: number }>;
routeRefs?: string[];
}>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
const context = getActionContext();
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_UpdateTargetProfile
>('/typedrequest', 'updateTargetProfile');
const response = await request.fire({
identity: context.identity!,
id: dataArg.id,
name: dataArg.name,
description: dataArg.description,
domains: dataArg.domains,
targets: dataArg.targets,
routeRefs: dataArg.routeRefs,
});
if (!response.success) {
return {
...statePartArg.getState()!,
error: response.message || 'Failed to update target profile',
};
}
return await actionContext!.dispatch(fetchTargetProfilesAction, null);
} catch (error: unknown) {
return {
...statePartArg.getState()!,
error: error instanceof Error ? error.message : 'Failed to update target profile',
};
}
});
export const deleteTargetProfileAction = targetProfilesStatePart.createAction<{
id: string;
force?: boolean;
}>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
const context = getActionContext();
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_DeleteTargetProfile
>('/typedrequest', 'deleteTargetProfile');
const response = await request.fire({
identity: context.identity!,
id: dataArg.id,
force: dataArg.force,
});
if (!response.success) {
return {
...statePartArg.getState()!,
error: response.message || 'Failed to delete target profile',
};
}
return await actionContext!.dispatch(fetchTargetProfilesAction, null);
} catch (error: unknown) {
return {
...statePartArg.getState()!,
error: error instanceof Error ? error.message : 'Failed to delete target profile',
};
}
});
// ============================================================================
// Source Profiles & Network Targets State
// ============================================================================
export interface IProfilesTargetsState {
profiles: interfaces.data.ISecurityProfile[];
profiles: interfaces.data.ISourceProfile[];
targets: interfaces.data.INetworkTarget[];
isLoading: boolean;
error: string | null;
@@ -1182,7 +1345,7 @@ export const profilesTargetsStatePart = await appState.getStatePart<IProfilesTar
);
// ============================================================================
// Security Profiles & Network Targets Actions
// Source Profiles & Network Targets Actions
// ============================================================================
export const fetchProfilesAndTargetsAction = profilesTargetsStatePart.createAction(
@@ -1193,8 +1356,8 @@ export const fetchProfilesAndTargetsAction = profilesTargetsStatePart.createActi
try {
const profilesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetSecurityProfiles
>('/typedrequest', 'getSecurityProfiles');
interfaces.requests.IReq_GetSourceProfiles
>('/typedrequest', 'getSourceProfiles');
const targetsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetNetworkTargets
@@ -1231,8 +1394,8 @@ export const createProfileAction = profilesTargetsStatePart.createAction<{
const context = getActionContext();
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_CreateSecurityProfile
>('/typedrequest', 'createSecurityProfile');
interfaces.requests.IReq_CreateSourceProfile
>('/typedrequest', 'createSourceProfile');
await request.fire({
identity: context.identity!,
name: dataArg.name,
@@ -1259,8 +1422,8 @@ export const updateProfileAction = profilesTargetsStatePart.createAction<{
const context = getActionContext();
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_UpdateSecurityProfile
>('/typedrequest', 'updateSecurityProfile');
interfaces.requests.IReq_UpdateSourceProfile
>('/typedrequest', 'updateSourceProfile');
await request.fire({
identity: context.identity!,
id: dataArg.id,
@@ -1285,8 +1448,8 @@ export const deleteProfileAction = profilesTargetsStatePart.createAction<{
const context = getActionContext();
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_DeleteSecurityProfile
>('/typedrequest', 'deleteSecurityProfile');
interfaces.requests.IReq_DeleteSourceProfile
>('/typedrequest', 'deleteSourceProfile');
const response = await request.fire({
identity: context.identity!,
id: dataArg.id,