summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2023-11-15 09:55:48 +0100
committerOskar Nyberg <oskar@mullvad.net>2024-01-29 09:33:49 +0100
commitdb9f5abf496019d3e40b62833c334b550e2d759a (patch)
tree3d7c1f5dec3f9c28fdb9df604bfead229217a7b1 /gui
parent4d5a0533149b9af4541c002591ebc32255c5d433 (diff)
downloadmullvadvpn-db9f5abf496019d3e40b62833c334b550e2d759a.tar.xz
mullvadvpn-db9f5abf496019d3e40b62833c334b550e2d759a.zip
Add api acces methods to daemon-rpc
Diffstat (limited to 'gui')
-rw-r--r--gui/src/main/daemon-rpc.ts237
-rw-r--r--gui/src/renderer/components/select-location/custom-list-helpers.ts5
-rw-r--r--gui/src/shared/daemon-rpc-types.ts57
-rw-r--r--gui/src/shared/utils.ts3
4 files changed, 297 insertions, 5 deletions
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index d5d29ad709..f93258a589 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -8,10 +8,13 @@ import {
import { promisify } from 'util';
import {
+ AccessMethod,
+ AccessMethodSetting,
AccountDataError,
AccountDataResponse,
AccountToken,
AfterDisconnect,
+ ApiAccessMethodSettings,
AuthFailedError,
BridgeSettings,
BridgeState,
@@ -20,6 +23,7 @@ import {
Constraint,
CustomListError,
CustomLists,
+ CustomProxy,
DaemonEvent,
DeviceEvent,
DeviceState,
@@ -49,6 +53,7 @@ import {
IWireguardEndpointData,
LoggedInDeviceState,
LoggedOutDeviceState,
+ NewAccessMethodSetting,
ObfuscationSettings,
ObfuscationType,
Ownership,
@@ -58,6 +63,7 @@ import {
RelayLocationGeographical,
RelayProtocol,
RelaySettings,
+ SocksAuth,
TunnelParameterError,
TunnelProtocol,
TunnelState,
@@ -642,6 +648,55 @@ export class DaemonRpc {
}
}
+ public async addApiAccessMethod(method: NewAccessMethodSetting): Promise<string> {
+ const result = await this.call<grpcTypes.NewAccessMethodSetting, grpcTypes.UUID>(
+ this.client.addApiAccessMethod,
+ convertToNewApiAccessMethodSetting(method),
+ );
+ return result.getValue();
+ }
+
+ public async updateApiAccessMethod(method: AccessMethodSetting) {
+ await this.call(this.client.updateApiAccessMethod, convertToApiAccessMethodSetting(method));
+ }
+
+ public async getCurrentApiAccessMethod() {
+ const response = await this.callEmpty<grpcTypes.AccessMethodSetting>(
+ this.client.getCurrentApiAccessMethod,
+ );
+ return convertFromApiAccessMethodSetting(response);
+ }
+
+ public async removeApiAccessMethod(id: string) {
+ const uuid = new grpcTypes.UUID();
+ uuid.setValue(id);
+ await this.call(this.client.removeApiAccessMethod, uuid);
+ }
+
+ public async setApiAccessMethod(id: string) {
+ const uuid = new grpcTypes.UUID();
+ uuid.setValue(id);
+ await this.call(this.client.setApiAccessMethod, uuid);
+ }
+
+ public async testApiAccessMethodById(id: string): Promise<boolean> {
+ const uuid = new grpcTypes.UUID();
+ uuid.setValue(id);
+ const result = await this.call<grpcTypes.UUID, BoolValue>(
+ this.client.testApiAccessMethodById,
+ uuid,
+ );
+ return result.getValue();
+ }
+
+ public async testCustomApiAccessMethod(method: CustomProxy): Promise<boolean> {
+ const result = await this.call<grpcTypes.CustomProxy, BoolValue>(
+ this.client.testCustomApiAccessMethod,
+ convertToCustomProxy(method),
+ );
+ return result.getValue();
+ }
+
private subscriptionId(): number {
const current = this.nextSubscriptionId;
this.nextSubscriptionId += 1;
@@ -1102,6 +1157,7 @@ function convertFromSettings(settings: grpcTypes.Settings): ISettings | undefine
const splitTunnel = settingsObject.splitTunnel ?? { enableExclusions: false, appsList: [] };
const obfuscationSettings = convertFromObfuscationSettings(settingsObject.obfuscationSettings);
const customLists = convertFromCustomListSettings(settings.getCustomLists());
+ const apiAccessMethods = convertFromApiAccessMethodSettings(settings.getApiAccessMethods());
return {
...settings.toObject(),
bridgeState,
@@ -1111,6 +1167,7 @@ function convertFromSettings(settings: grpcTypes.Settings): ISettings | undefine
splitTunnel,
obfuscationSettings,
customLists,
+ apiAccessMethods,
};
}
@@ -1411,6 +1468,11 @@ function convertFromDaemonEvent(data: grpcTypes.DaemonEvent): DaemonEvent {
return { appVersionInfo: versionInfo.toObject() };
}
+ const newAccessMethod = data.getNewAccessMethod();
+ if (newAccessMethod !== undefined) {
+ return { accessMethodSetting: convertFromApiAccessMethodSetting(newAccessMethod) };
+ }
+
// Handle unknown daemon events
const keys = Object.entries(data.toObject())
.filter(([, value]) => value !== undefined)
@@ -1734,6 +1796,181 @@ function convertToCustomList(customList: ICustomList): grpcTypes.CustomList {
return grpcCustomList;
}
+function convertToApiAccessMethodSetting(
+ method: AccessMethodSetting,
+): grpcTypes.AccessMethodSetting {
+ const updatedMethod = new grpcTypes.AccessMethodSetting();
+ const uuid = new grpcTypes.UUID();
+ uuid.setValue(method.id);
+ updatedMethod.setId(uuid);
+ return fillApiAccessMethodSetting(updatedMethod, method);
+}
+
+function convertToNewApiAccessMethodSetting(
+ method: NewAccessMethodSetting,
+): grpcTypes.NewAccessMethodSetting {
+ const newMethod = new grpcTypes.NewAccessMethodSetting();
+ return fillApiAccessMethodSetting(newMethod, method);
+}
+
+function fillApiAccessMethodSetting<T extends grpcTypes.NewAccessMethodSetting>(
+ newMethod: T,
+ method: NewAccessMethodSetting,
+): T {
+ newMethod.setName(method.name);
+ newMethod.setEnabled(method.enabled);
+
+ const accessMethod = new grpcTypes.AccessMethod();
+ switch (method.type) {
+ case 'direct': {
+ const direct = new grpcTypes.AccessMethod.Direct();
+ accessMethod.setDirect(direct);
+ break;
+ }
+ case 'bridges': {
+ const bridges = new grpcTypes.AccessMethod.Bridges();
+ accessMethod.setBridges(bridges);
+ break;
+ }
+ default:
+ accessMethod.setCustom(convertToCustomProxy(method));
+ }
+
+ newMethod.setAccessMethod(accessMethod);
+ return newMethod;
+}
+
+function convertToCustomProxy(proxy: CustomProxy): grpcTypes.CustomProxy {
+ const customProxy = new grpcTypes.CustomProxy();
+
+ switch (proxy.type) {
+ case 'socks5-local': {
+ const socks5Local = new grpcTypes.Socks5Local();
+ socks5Local.setRemoteIp(proxy.remoteIp);
+ socks5Local.setRemotePort(proxy.remotePort);
+ socks5Local.setRemoteTransportProtocol(
+ convertToTransportProtocol(proxy.remoteTransportProtocol),
+ );
+ socks5Local.setLocalPort(proxy.localPort);
+ customProxy.setSocks5local(socks5Local);
+ break;
+ }
+ case 'socks5-remote': {
+ const socks5Remote = new grpcTypes.Socks5Remote();
+ socks5Remote.setIp(proxy.ip);
+ socks5Remote.setPort(proxy.port);
+ if (proxy.authentication !== undefined) {
+ socks5Remote.setAuth(convertToSocksAuth(proxy.authentication));
+ }
+ customProxy.setSocks5remote(socks5Remote);
+ break;
+ }
+ case 'shadowsocks': {
+ const shadowsocks = new grpcTypes.Shadowsocks();
+ shadowsocks.setIp(proxy.ip);
+ shadowsocks.setPort(proxy.port);
+ shadowsocks.setPassword(proxy.password);
+ shadowsocks.setCipher(proxy.cipher);
+ customProxy.setShadowsocks(shadowsocks);
+ break;
+ }
+ }
+
+ return customProxy;
+}
+
+function convertToSocksAuth(authentication: SocksAuth): grpcTypes.SocksAuth {
+ const auth = new grpcTypes.SocksAuth();
+ auth.setUsername(authentication.username);
+ auth.setPassword(authentication.password);
+ return auth;
+}
+
+function convertFromApiAccessMethodSettings(
+ accessMethods?: grpcTypes.ApiAccessMethodSettings,
+): ApiAccessMethodSettings {
+ return (
+ accessMethods
+ ?.getAccessMethodSettingsList()
+ .filter((setting) => setting.hasId() && setting.hasAccessMethod())
+ .map(convertFromApiAccessMethodSetting) ?? []
+ );
+}
+
+function convertFromApiAccessMethodSetting(
+ setting: grpcTypes.AccessMethodSetting,
+): AccessMethodSetting {
+ const id = setting.getId()!;
+ const accessMethod = setting.getAccessMethod()!;
+
+ return {
+ id: id.getValue(),
+ name: setting.getName(),
+ enabled: setting.getEnabled(),
+ ...convertFromAccessMethod(accessMethod),
+ };
+}
+
+function convertFromAccessMethod(method: grpcTypes.AccessMethod): AccessMethod {
+ switch (method.getAccessMethodCase()) {
+ case grpcTypes.AccessMethod.AccessMethodCase.DIRECT:
+ return { type: 'direct' };
+ case grpcTypes.AccessMethod.AccessMethodCase.BRIDGES:
+ return { type: 'bridges' };
+ case grpcTypes.AccessMethod.AccessMethodCase.CUSTOM: {
+ const proxy = method.getCustom()!;
+ switch (proxy.getProxyMethodCase()) {
+ case grpcTypes.CustomProxy.ProxyMethodCase.SOCKS5LOCAL: {
+ const socks5Local = proxy.getSocks5local()!;
+ return {
+ type: 'socks5-local',
+ remoteIp: socks5Local.getRemoteIp(),
+ remotePort: socks5Local.getRemotePort(),
+ remoteTransportProtocol: convertFromTransportProtocol(
+ socks5Local.getRemoteTransportProtocol(),
+ ),
+ localPort: socks5Local.getLocalPort(),
+ };
+ }
+ case grpcTypes.CustomProxy.ProxyMethodCase.SOCKS5REMOTE: {
+ const socks5Remote = proxy.getSocks5remote()!;
+ const auth = socks5Remote.getAuth();
+ return {
+ type: 'socks5-remote',
+ ip: socks5Remote.getIp(),
+ port: socks5Remote.getPort(),
+ authentication: auth === undefined ? undefined : convertFromSocksAuth(auth),
+ };
+ }
+ case grpcTypes.CustomProxy.ProxyMethodCase.SHADOWSOCKS: {
+ const shadowsocks = proxy.getShadowsocks()!;
+ return {
+ type: 'shadowsocks',
+ ip: shadowsocks.getIp(),
+ port: shadowsocks.getPort(),
+ password: shadowsocks.getPassword(),
+ cipher: shadowsocks.getCipher(),
+ };
+ }
+ case grpcTypes.CustomProxy.ProxyMethodCase.PROXY_METHOD_NOT_SET:
+ throw new Error('Custom method not set, which should always be set');
+ }
+ // This break is required to prevent eslint from complainting about fallthrough, even though
+ // all cases are covered above.
+ break;
+ }
+ case grpcTypes.AccessMethod.AccessMethodCase.ACCESS_METHOD_NOT_SET:
+ throw new Error('Access method not set, which should always be set');
+ }
+}
+
+function convertFromSocksAuth(auth: grpcTypes.SocksAuth): SocksAuth {
+ return {
+ username: auth.getUsername(),
+ password: auth.getPassword(),
+ };
+}
+
function ensureExists<T>(value: T | undefined, errorMessage: string): T {
if (value) {
return value;
diff --git a/gui/src/renderer/components/select-location/custom-list-helpers.ts b/gui/src/renderer/components/select-location/custom-list-helpers.ts
index 40d77ba33d..799deb8ed3 100644
--- a/gui/src/renderer/components/select-location/custom-list-helpers.ts
+++ b/gui/src/renderer/components/select-location/custom-list-helpers.ts
@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { ICustomList, RelayLocation } from '../../../shared/daemon-rpc-types';
+import { hasValue } from '../../../shared/utils';
import { searchMatch } from '../../lib/filter-locations';
import { useSelector } from '../../redux/store';
import { useDisabledLocation, useSelectedLocation } from './RelayListContext';
@@ -169,7 +170,3 @@ function updateRelay(relay: RelaySpecification, customList: string): RelaySpecif
visible: true,
};
}
-
-function hasValue<T>(value: T): value is NonNullable<T> {
- return value !== undefined && value !== null;
-}
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 48a4110e13..b409a6e835 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -173,7 +173,8 @@ export type DaemonEvent =
| { relayList: IRelayListWithEndpointData }
| { appVersionInfo: IAppVersionInfo }
| { device: DeviceEvent }
- | { deviceRemoval: Array<IDevice> };
+ | { deviceRemoval: Array<IDevice> }
+ | { accessMethodSetting: AccessMethodSetting };
export interface ITunnelStateRelayInfo {
endpoint: ITunnelEndpoint;
@@ -427,6 +428,7 @@ export interface ISettings {
splitTunnel: SplitTunnelSettings;
obfuscationSettings: ObfuscationSettings;
customLists: CustomLists;
+ apiAccessMethods: ApiAccessMethodSettings;
}
export type BridgeState = 'auto' | 'on' | 'off';
@@ -474,6 +476,59 @@ export type VoucherResponse =
| { type: 'success'; newExpiry: string; secondsAdded: number }
| { type: 'invalid' | 'already_used' | 'error' };
+export interface SocksAuth {
+ username: string;
+ password: string;
+}
+
+export type Socks5LocalAccessMethod = {
+ type: 'socks5-local';
+ remoteIp: string;
+ remotePort: number;
+ remoteTransportProtocol: RelayProtocol;
+ localPort: number;
+};
+
+export type Socks5RemoteAccessMethod = {
+ type: 'socks5-remote';
+ ip: string;
+ port: number;
+ authentication?: SocksAuth;
+};
+
+export type ShadowsocksAccessMethod = {
+ type: 'shadowsocks';
+ ip: string;
+ port: number;
+ password: string;
+ cipher: string;
+};
+
+export type CustomProxy =
+ | Socks5LocalAccessMethod
+ | Socks5RemoteAccessMethod
+ | ShadowsocksAccessMethod;
+
+export type AccessMethod =
+ | {
+ type: 'direct';
+ }
+ | {
+ type: 'bridges';
+ }
+ | CustomProxy;
+
+export type NewAccessMethodSetting = AccessMethod & {
+ name: string;
+ enabled: boolean;
+};
+
+export type AccessMethodSetting = NewAccessMethodSetting & {
+ id: string;
+};
+
+export type ApiAccessMethodSettings = Array<AccessMethodSetting>;
+
export function parseSocketAddress(socketAddrStr: string): ISocketAddress {
const re = new RegExp(/(.+):(\d+)$/);
const matches = socketAddrStr.match(re);
diff --git a/gui/src/shared/utils.ts b/gui/src/shared/utils.ts
new file mode 100644
index 0000000000..24984e4412
--- /dev/null
+++ b/gui/src/shared/utils.ts
@@ -0,0 +1,3 @@
+export function hasValue<T>(value: T): value is NonNullable<T> {
+ return value !== undefined && value !== null;
+}