summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2019-03-06 11:16:13 +0100
committerAndrej Mihajlov <and@mullvad.net>2019-03-07 13:48:30 +0100
commit8657e73fcc11edb3d0e22e2008c28436fffa1cb3 (patch)
treeb8fcf283e8bdecb626bd8cf7af26c771030f0ed2 /gui
parent07c3a6f1c07d776686fbb8842b677424bdec8fca (diff)
downloadmullvadvpn-8657e73fcc11edb3d0e22e2008c28436fffa1cb3.tar.xz
mullvadvpn-8657e73fcc11edb3d0e22e2008c28436fffa1cb3.zip
Add heuristics around account expiry to predict when it becomes stale
Diffstat (limited to 'gui')
-rw-r--r--gui/src/renderer/app.tsx24
-rw-r--r--gui/src/renderer/components/Connect.tsx55
2 files changed, 68 insertions, 11 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 51b2983bcd..770f148c77 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -25,6 +25,7 @@ import { IWindowShapeParameters } from '../main/window-controller';
import { loadTranslations } from '../shared/gettext';
import { IGuiSettingsState } from '../shared/gui-settings-state';
import { IpcRendererEventChannel } from '../shared/ipc-event-channel';
+import AccountExpiry from './lib/account-expiry';
import {
AccountToken,
@@ -59,13 +60,14 @@ export default class AppRenderer {
return IpcRendererEventChannel.account.getData(accountToken);
},
(accountData) => {
- this.reduxActions.account.updateAccountExpiry(accountData && accountData.expiry);
+ this.setAccountExpiry(accountData && accountData.expiry);
},
);
private tunnelState: TunnelStateTransition;
private settings: ISettings;
private guiSettings: IGuiSettingsState;
+ private accountExpiry?: AccountExpiry;
private connectedToDaemon = false;
private autoConnected = false;
private doingLogin = false;
@@ -102,6 +104,10 @@ export default class AppRenderer {
IpcRendererEventChannel.tunnel.listen((newState: TunnelStateTransition) => {
this.setTunnelState(newState);
this.updateBlockedState(newState, this.settings.blockWhenDisconnected);
+
+ if (this.accountExpiry) {
+ this.detectStaleAccountExpiry(newState, this.accountExpiry);
+ }
});
IpcRendererEventChannel.settings.listen((newSettings: ISettings) => {
@@ -563,6 +569,22 @@ export default class AppRenderer {
this.reduxActions.settings.updateGuiSettings(guiSettings);
}
+ private setAccountExpiry(expiry?: string) {
+ this.accountExpiry = expiry ? new AccountExpiry(expiry, remote.app.getLocale()) : undefined;
+ this.reduxActions.account.updateAccountExpiry(expiry);
+ }
+
+ private detectStaleAccountExpiry(
+ tunnelState: TunnelStateTransition,
+ accountExpiry: AccountExpiry,
+ ) {
+ // It's likely that the account expiry is stale if the daemon managed to establish the tunnel.
+ if (tunnelState.state === 'connected' && accountExpiry.hasExpired()) {
+ log.info('Detected the stale account expiry.');
+ this.accountDataCache.invalidate();
+ }
+ }
+
private storeAutoStart(autoStart: boolean) {
this.reduxActions.settings.updateAutoStart(autoStart);
}
diff --git a/gui/src/renderer/components/Connect.tsx b/gui/src/renderer/components/Connect.tsx
index 657d030c6b..6d10400a30 100644
--- a/gui/src/renderer/components/Connect.tsx
+++ b/gui/src/renderer/components/Connect.tsx
@@ -74,7 +74,23 @@ const styles = {
}),
};
-export default class Connect extends Component<IProps> {
+interface IState {
+ isAccountExpired: boolean;
+}
+
+export default class Connect extends Component<IProps, IState> {
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ isAccountExpired: this.checkAccountExpired(props, false),
+ };
+ }
+
+ public componentDidUpdate() {
+ this.updateAccountExpired();
+ }
+
public render() {
return (
<Layout>
@@ -83,23 +99,42 @@ export default class Connect extends Component<IProps> {
<SettingsBarButton onPress={this.props.onSettings} />
</Header>
<Container>
- {this.shouldShowExpiredAccountView() ? this.renderExpiredAccountView() : this.renderMap()}
+ {this.state.isAccountExpired ? this.renderExpiredAccountView() : this.renderMap()}
</Container>
</Layout>
);
}
- private shouldShowExpiredAccountView(): boolean {
- const tunnelState = this.props.connection.status;
+ private updateAccountExpired() {
+ const nextAccountExpired = this.checkAccountExpired(this.props, this.state.isAccountExpired);
+
+ if (nextAccountExpired !== this.state.isAccountExpired) {
+ this.setState({
+ isAccountExpired: nextAccountExpired,
+ });
+ }
+ }
+
+ private checkAccountExpired(props: IProps, prevAccountExpired: boolean): boolean {
+ const tunnelState = props.connection.status;
+
+ // Blocked with auth failure / expired account
+ if (
+ tunnelState.state === 'blocked' &&
+ tunnelState.details.reason === 'auth_failed' &&
+ parseAuthFailure(tunnelState.details.details).kind === AuthFailureKind.expiredAccount
+ ) {
+ return true;
+ }
- if (tunnelState.state === 'blocked' && tunnelState.details.reason === 'auth_failed') {
- const authError = new AuthFailureError(tunnelState.details.details);
- if (authError.kind === AuthFailureKind.expiredAccount) {
- return true;
- }
+ // Use the account expiry to deduce the account state
+ if (this.props.accountExpiry) {
+ return this.props.accountExpiry.hasExpired();
}
- return this.props.accountExpiry ? this.props.accountExpiry.hasExpired() : false;
+ // Do not assume that the account hasn't expired if the expiry is not available at the moment
+ // instead return the last known state.
+ return prevAccountExpired;
}
private renderExpiredAccountView() {