summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-11-15 11:49:22 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-03-14 13:58:44 +0100
commit9916f3444c1d15a6d9ad5caba3555e1c0e1a704c (patch)
tree74ae736686f5e86409fe9a46a02030c569d9ada2 /gui/src
parentdd8f146536a4bea945006cfe61f92e3ee4b8e584 (diff)
downloadmullvadvpn-9916f3444c1d15a6d9ad5caba3555e1c0e1a704c.tar.xz
mullvadvpn-9916f3444c1d15a6d9ad5caba3555e1c0e1a704c.zip
Show revoked view when account has been revoked
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/main/daemon-rpc.ts10
-rw-r--r--gui/src/main/index.ts15
-rw-r--r--gui/src/renderer/app.tsx65
-rw-r--r--gui/src/renderer/components/MainView.tsx33
-rw-r--r--gui/src/renderer/redux/account/actions.ts12
-rw-r--r--gui/src/renderer/redux/account/reducers.ts13
-rw-r--r--gui/src/shared/daemon-rpc-types.ts7
-rw-r--r--gui/src/shared/ipc-schema.ts3
8 files changed, 100 insertions, 58 deletions
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 8eace70f8b..bb90d77d52 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -49,6 +49,7 @@ import {
DeviceConfig,
IDevice,
IDeviceRemoval,
+ IDeviceEvent,
} from '../shared/daemon-rpc-types';
import log from '../shared/logging';
@@ -1152,7 +1153,7 @@ function convertFromDaemonEvent(data: grpcTypes.DaemonEvent): DaemonEvent {
const deviceConfig = data.getDevice();
if (deviceConfig !== undefined) {
- return { deviceConfig: convertFromDeviceEvent(deviceConfig) };
+ return { device: convertFromDeviceEvent(deviceConfig) };
}
const deviceRemoval = data.getRemoveDevice();
@@ -1367,8 +1368,11 @@ function convertToTransportProtocol(protocol: RelayProtocol): grpcTypes.Transpor
}
}
-function convertFromDeviceEvent(deviceEvent: grpcTypes.DeviceEvent): DeviceConfig {
- return convertFromDeviceConfig(deviceEvent.getDevice());
+function convertFromDeviceEvent(deviceEvent: grpcTypes.DeviceEvent): IDeviceEvent {
+ return {
+ deviceConfig: convertFromDeviceConfig(deviceEvent.getDevice()),
+ remote: deviceEvent.getRemote(),
+ };
}
function convertFromDeviceConfig(deviceConfig?: grpcTypes.DeviceConfig): DeviceConfig {
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index d2264242cc..9bb988a199 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -36,6 +36,7 @@ import {
RelaySettings,
RelaySettingsUpdate,
TunnelState,
+ IDeviceEvent,
} from '../shared/daemon-rpc-types';
import { messages, relayLocations } from '../shared/gettext';
import { SYSTEM_PREFERRED_LOCALE_KEY } from '../shared/gui-settings-state';
@@ -643,7 +644,7 @@ class ApplicationMain {
// fetch device
try {
- this.setDeviceConfig(await this.daemonRpc.getDevice());
+ this.setDeviceConfig({ deviceConfig: await this.daemonRpc.getDevice() });
} catch (e) {
const error = e as Error;
log.error(`Failed to fetch device: ${error.message}`);
@@ -779,8 +780,8 @@ class ApplicationMain {
);
} else if ('appVersionInfo' in daemonEvent) {
this.setLatestVersion(daemonEvent.appVersionInfo);
- } else if ('deviceConfig' in daemonEvent) {
- this.setDeviceConfig(daemonEvent.deviceConfig);
+ } else if ('device' in daemonEvent) {
+ this.setDeviceConfig(daemonEvent.device);
} else if ('deviceRemoval' in daemonEvent) {
if (this.windowController) {
IpcMainEventChannel.account.notifyDevices(
@@ -1112,20 +1113,20 @@ class ApplicationMain {
}
}
- private setDeviceConfig(deviceConfig: DeviceConfig) {
+ private setDeviceConfig(deviceEvent: IDeviceEvent) {
const oldDeviceConfig = this.deviceConfig;
- this.deviceConfig = deviceConfig;
+ this.deviceConfig = deviceEvent.deviceConfig;
// make sure to invalidate the account data cache when account tokens change
this.updateAccountDataOnAccountChange(
oldDeviceConfig?.accountToken,
- deviceConfig?.accountToken,
+ deviceEvent.deviceConfig?.accountToken,
);
void this.updateAccountHistory();
if (this.windowController) {
- IpcMainEventChannel.account.notifyDevice(this.windowController.webContents, deviceConfig);
+ IpcMainEventChannel.account.notifyDevice(this.windowController.webContents, deviceEvent);
}
}
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 586e120868..47a58fc4dd 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -42,6 +42,7 @@ import {
RelaySettingsUpdate,
TunnelState,
VoucherResponse,
+ IDeviceEvent,
} from '../shared/daemon-rpc-types';
import { LogLevel } from '../shared/logging-types';
import IpcOutput from './lib/logging';
@@ -124,9 +125,9 @@ export default class AppRenderer {
this.setAccountExpiry(newAccountData?.expiry);
});
- IpcRendererEventChannel.account.listenDevice((deviceConfig: DeviceConfig) => {
+ IpcRendererEventChannel.account.listenDevice((deviceEvent) => {
const oldDeviceConfig = this.deviceConfig;
- this.handleAccountChange(deviceConfig, oldDeviceConfig?.accountToken);
+ this.handleAccountChange(deviceEvent, oldDeviceConfig?.accountToken);
});
IpcRendererEventChannel.account.listenDevices((devices) => {
@@ -200,7 +201,7 @@ export default class AppRenderer {
this.setAccountExpiry(initialState.accountData?.expiry);
this.setSettings(initialState.settings);
- this.handleAccountChange(initialState.deviceConfig, undefined);
+ this.handleAccountChange({ deviceConfig: initialState.deviceConfig }, undefined);
this.setAccountHistory(initialState.accountHistory);
this.setTunnelState(initialState.tunnelState);
this.updateBlockedState(initialState.tunnelState, initialState.settings.blockWhenDisconnected);
@@ -235,10 +236,7 @@ export default class AppRenderer {
void this.updateLocation();
- const navigationBase = this.getNavigationBase(
- initialState.isConnected,
- initialState.deviceConfig?.accountToken,
- );
+ const navigationBase = this.getNavigationBase();
this.history = new History(navigationBase);
}
@@ -305,6 +303,13 @@ export default class AppRenderer {
}
}
+ public leaveRevokedDevice = async () => {
+ const reduxAccount = this.reduxActions.account;
+ reduxAccount.loggedOut();
+ this.resetNavigation();
+ await this.disconnectTunnel();
+ };
+
public async createNewAccount() {
log.info('Creating account');
@@ -645,13 +650,10 @@ export default class AppRenderer {
private resetNavigation() {
if (this.history) {
const pathname = this.history.location.pathname as RoutePath;
- const nextPath = this.getNavigationBase(
- this.connectedToDaemon,
- this.deviceConfig?.accountToken,
- ) as RoutePath;
+ const nextPath = this.getNavigationBase() as RoutePath;
- // First level contains the possible next locations and the second level contains the possible
- // current locations.
+ // First level contains the possible next locations and the second level contains the
+ // possible current locations.
const navigationTransitions: Partial<
Record<RoutePath, Partial<Record<RoutePath | '*', ITransitionSpecification>>>
> = {
@@ -679,9 +681,11 @@ export default class AppRenderer {
}
}
- private getNavigationBase(connectedToDaemon: boolean, accountToken?: string): RoutePath {
- if (connectedToDaemon) {
- return accountToken ? RoutePath.main : RoutePath.login;
+ private getNavigationBase(): RoutePath {
+ if (this.connectedToDaemon) {
+ const loginState = this.reduxStore.getState().account.status;
+ const deviceRevoked = loginState.type === 'none' && loginState.deviceRevoked;
+ return this.deviceConfig?.accountToken || deviceRevoked ? RoutePath.main : RoutePath.login;
} else {
return RoutePath.launch;
}
@@ -770,36 +774,31 @@ export default class AppRenderer {
}
}
- private handleAccountChange(newDeviceConfig: DeviceConfig, oldAccount?: string) {
+ private handleAccountChange(newDeviceEvent: IDeviceEvent, oldAccount?: string) {
const reduxAccount = this.reduxActions.account;
- this.deviceConfig = newDeviceConfig;
- const newAccount = newDeviceConfig?.accountToken;
+ this.deviceConfig = newDeviceEvent.deviceConfig;
+ const newAccount = newDeviceEvent.deviceConfig?.accountToken;
+ const newDevice = newDeviceEvent.deviceConfig?.device;
if (oldAccount && !newAccount) {
this.loginScheduler.cancel();
- reduxAccount.loggedOut();
+ if (newDeviceEvent.remote) {
+ reduxAccount.deviceRevoked();
+ } else {
+ reduxAccount.loggedOut();
+ }
this.resetNavigation();
- } else if (
- newDeviceConfig?.accountToken !== undefined &&
- newDeviceConfig?.device !== undefined &&
- oldAccount !== newAccount
- ) {
+ } else if (newAccount !== undefined && newDevice !== undefined && oldAccount !== newAccount) {
switch (this.loginState) {
case 'none':
case 'logging in':
- reduxAccount.loggedIn({
- accountToken: newDeviceConfig.accountToken,
- device: newDeviceConfig.device,
- });
+ reduxAccount.loggedIn({ accountToken: newAccount, device: newDevice });
break;
case 'creating account':
reduxAccount.accountCreated(
- {
- accountToken: newDeviceConfig.accountToken,
- device: newDeviceConfig.device,
- },
+ { accountToken: newAccount, device: newDevice },
new Date().toISOString(),
);
break;
diff --git a/gui/src/renderer/components/MainView.tsx b/gui/src/renderer/components/MainView.tsx
index c7a8851a90..9ced798cf9 100644
--- a/gui/src/renderer/components/MainView.tsx
+++ b/gui/src/renderer/components/MainView.tsx
@@ -1,29 +1,44 @@
import { useEffect, useState } from 'react';
-import { useSelector } from 'react-redux';
import { hasExpired } from '../../shared/account-expiry';
-import { IReduxState } from '../redux/store';
+import { useSelector } from '../redux/store';
import ConnectPage from '../containers/ConnectPage';
import ExpiredAccountErrorViewContainer from '../containers/ExpiredAccountErrorViewContainer';
import { useHistory } from '../lib/history';
import { RoutePath } from '../lib/routes';
+import { DeviceRevokedView } from './DeviceRevokedView';
export default function MainView() {
const history = useHistory();
- const accountExpiry = useSelector((state: IReduxState) => state.account.expiry);
- const accountHasExpired = accountExpiry && hasExpired(accountExpiry);
+ const accountExpiry = useSelector((state) => state.account.expiry);
+ const accountHasExpired = accountExpiry !== undefined && hasExpired(accountExpiry);
const isNewAccount = useSelector(
- (state: IReduxState) =>
- state.account.status.type === 'ok' && state.account.status.method === 'new_account',
+ (state) => state.account.status.type === 'ok' && state.account.status.method === 'new_account',
+ );
+ const showDeviceRevoked = useSelector(
+ (state) =>
+ (state.connection.status.state === 'error' &&
+ state.connection.status.details.cause.reason === 'tunnel_parameter_error' &&
+ state.connection.status.details.cause.details === 'no_wireguard_key') ||
+ (state.account.status.type === 'none' && state.account.status.deviceRevoked),
+ );
+
+ const [showAccountExpired, setShowAccountExpired] = useState<boolean>(
+ (isNewAccount || accountHasExpired) && !showDeviceRevoked,
);
- const [showAccountExpired, setShowAccountExpired] = useState(isNewAccount || accountHasExpired);
useEffect(() => {
- if (accountHasExpired) {
+ if (accountHasExpired && !showDeviceRevoked) {
setShowAccountExpired(true);
} else if (showAccountExpired && !accountHasExpired) {
history.push(RoutePath.timeAdded);
}
}, [showAccountExpired, accountHasExpired]);
- return showAccountExpired ? <ExpiredAccountErrorViewContainer /> : <ConnectPage />;
+ if (showDeviceRevoked) {
+ return <DeviceRevokedView />;
+ } else if (showAccountExpired) {
+ return <ExpiredAccountErrorViewContainer />;
+ } else {
+ return <ConnectPage />;
+ }
}
diff --git a/gui/src/renderer/redux/account/actions.ts b/gui/src/renderer/redux/account/actions.ts
index e53cb11545..5bc3e84aa9 100644
--- a/gui/src/renderer/redux/account/actions.ts
+++ b/gui/src/renderer/redux/account/actions.ts
@@ -29,6 +29,10 @@ interface IResetLoginErrorAction {
type: 'RESET_LOGIN_ERROR';
}
+interface IDeviceRevokedAction {
+ type: 'DEVICE_REVOKED';
+}
+
interface IStartCreateAccount {
type: 'START_CREATE_ACCOUNT';
}
@@ -76,6 +80,7 @@ export type AccountAction =
| ILoginTooManyDevicesAction
| ILoggedOutAction
| IResetLoginErrorAction
+ | IDeviceRevokedAction
| IStartCreateAccount
| ICreateAccountFailed
| IAccountCreated
@@ -126,6 +131,12 @@ function resetLoginError(): IResetLoginErrorAction {
};
}
+function deviceRevoked(): IDeviceRevokedAction {
+ return {
+ type: 'DEVICE_REVOKED',
+ };
+}
+
function startCreateAccount(): IStartCreateAccount {
return {
type: 'START_CREATE_ACCOUNT',
@@ -187,6 +198,7 @@ export default {
loginTooManyDevices,
loggedOut,
resetLoginError,
+ deviceRevoked,
startCreateAccount,
createAccountFailed,
accountCreated,
diff --git a/gui/src/renderer/redux/account/reducers.ts b/gui/src/renderer/redux/account/reducers.ts
index b336ea665a..5be640e98b 100644
--- a/gui/src/renderer/redux/account/reducers.ts
+++ b/gui/src/renderer/redux/account/reducers.ts
@@ -3,7 +3,7 @@ import { ReduxAction } from '../store';
type LoginMethod = 'existing_account' | 'new_account';
export type LoginState =
- | { type: 'none' }
+ | { type: 'none'; deviceRevoked: boolean }
| { type: 'logging in' | 'ok'; method: LoginMethod }
| { type: 'failed' | 'too many devices'; method: LoginMethod; error: Error };
export interface IAccountReduxState {
@@ -21,7 +21,7 @@ const initialState: IAccountReduxState = {
devices: [],
accountHistory: undefined,
expiry: undefined,
- status: { type: 'none' },
+ status: { type: 'none', deviceRevoked: false },
};
export default function (
@@ -56,14 +56,19 @@ export default function (
case 'LOGGED_OUT':
return {
...state,
- status: { type: 'none' },
+ status: { type: 'none', deviceRevoked: false },
accountToken: undefined,
expiry: undefined,
};
case 'RESET_LOGIN_ERROR':
return {
...state,
- status: { type: 'none' },
+ status: { type: 'none', deviceRevoked: false },
+ };
+ case 'DEVICE_REVOKED':
+ return {
+ ...state,
+ status: { type: 'none', deviceRevoked: true },
};
case 'START_CREATE_ACCOUNT':
return {
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 6fe598c7a0..7840a68d26 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -105,7 +105,7 @@ export type DaemonEvent =
| { settings: ISettings }
| { relayList: IRelayList }
| { appVersionInfo: IAppVersionInfo }
- | { deviceConfig: DeviceConfig }
+ | { device: IDeviceEvent }
| { deviceRemoval: Array<IDevice> };
export interface ITunnelStateRelayInfo {
@@ -322,6 +322,11 @@ export interface IAppVersionInfo {
suggestedIsBeta?: boolean;
}
+export interface IDeviceEvent {
+ deviceConfig: DeviceConfig;
+ remote?: boolean;
+}
+
export type DeviceConfig =
| undefined
| {
diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts
index 93947bee62..62dd206c4c 100644
--- a/gui/src/shared/ipc-schema.ts
+++ b/gui/src/shared/ipc-schema.ts
@@ -16,6 +16,7 @@ import {
RelaySettingsUpdate,
TunnelState,
VoucherResponse,
+ IDeviceEvent,
} from './daemon-rpc-types';
import { IGuiSettingsState } from './gui-settings-state';
import { LogLevel } from './logging-types';
@@ -167,7 +168,7 @@ export const ipcSchema = {
},
account: {
'': notifyRenderer<IAccountData | undefined>(),
- device: notifyRenderer<DeviceConfig>(),
+ device: notifyRenderer<IDeviceEvent>(),
devices: notifyRenderer<Array<IDevice>>(),
create: invoke<void, string>(),
login: invoke<AccountToken, void>(),