diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2019-09-02 15:27:57 +0100 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2019-09-05 10:46:54 +0100 |
| commit | 8f47cab67f7f92b1b246d5b993f3d2d846dd4219 (patch) | |
| tree | b40fc803aa34b99a8b8769f14a4c384a064e3fb3 /gui/src/renderer | |
| parent | 96d52dfc0a69928c274531d65f9ddf1965ad5026 (diff) | |
| download | mullvadvpn-8f47cab67f7f92b1b246d5b993f3d2d846dd4219.tar.xz mullvadvpn-8f47cab67f7f92b1b246d5b993f3d2d846dd4219.zip | |
Add button to regenerate key
Diffstat (limited to 'gui/src/renderer')
| -rw-r--r-- | gui/src/renderer/app.tsx | 24 | ||||
| -rw-r--r-- | gui/src/renderer/components/WireguardKeys.tsx | 156 | ||||
| -rw-r--r-- | gui/src/renderer/components/WireguardKeysStyles.tsx | 1 | ||||
| -rw-r--r-- | gui/src/renderer/containers/WireguardKeysPage.tsx | 9 | ||||
| -rw-r--r-- | gui/src/renderer/redux/settings/actions.ts | 41 | ||||
| -rw-r--r-- | gui/src/renderer/redux/settings/reducers.ts | 72 |
6 files changed, 232 insertions, 71 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index 094b4f376d..b08743dab4 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -16,6 +16,7 @@ import AppRoutes from './routes'; import accountActions from './redux/account/actions'; import connectionActions from './redux/connection/actions'; import settingsActions from './redux/settings/actions'; +import { IWgKey } from './redux/settings/reducers'; import configureStore from './redux/store'; import userInterfaceActions from './redux/userinterface/actions'; import versionActions from './redux/version/actions'; @@ -33,6 +34,7 @@ import { ILocation, IRelayList, ISettings, + IWireguardPublicKey, KeygenEvent, liftConstraint, RelaySettings, @@ -129,7 +131,7 @@ export default class AppRenderer { this.storeAutoStart(autoStart); }); - IpcRendererEventChannel.wireguardKeys.listen((publicKey?: string) => { + IpcRendererEventChannel.wireguardKeys.listen((publicKey?: IWireguardPublicKey) => { this.setWireguardPublicKey(publicKey); }); @@ -303,11 +305,16 @@ export default class AppRenderer { IpcRendererEventChannel.guiSettings.setMonochromaticIcon(monochromaticIcon); } - public async verifyWireguardKey(publicKey: string) { + public async verifyWireguardKey(publicKey: IWgKey) { const actions = this.reduxActions; actions.settings.verifyWireguardKey(publicKey); - const valid = await IpcRendererEventChannel.wireguardKeys.verifyKey(); - actions.settings.completeWireguardKeyVerification(valid); + try { + const valid = await IpcRendererEventChannel.wireguardKeys.verifyKey(); + actions.settings.completeWireguardKeyVerification(valid); + } catch (error) { + log.error(`Failed to verify WireGuard key - ${error.message}`); + actions.settings.completeWireguardKeyVerification(undefined); + } } public async generateWireguardKey() { @@ -317,6 +324,13 @@ export default class AppRenderer { actions.settings.setWireguardKeygenEvent(keygenEvent); } + public async replaceWireguardKey(oldKey: IWgKey) { + const actions = this.reduxActions; + actions.settings.replaceWireguardKey(oldKey); + const keygenEvent = await IpcRendererEventChannel.wireguardKeys.generateKey(); + actions.settings.setWireguardKeygenEvent(keygenEvent); + } + private setRelaySettings(relaySettings: RelaySettings) { const actions = this.reduxActions; @@ -554,7 +568,7 @@ export default class AppRenderer { this.reduxActions.settings.updateAutoStart(autoStart); } - private setWireguardPublicKey(publicKey?: string) { + private setWireguardPublicKey(publicKey?: IWireguardPublicKey) { this.reduxActions.settings.setWireguardKey(publicKey); } } diff --git a/gui/src/renderer/components/WireguardKeys.tsx b/gui/src/renderer/components/WireguardKeys.tsx index d66ef68cf2..9ea386ec91 100644 --- a/gui/src/renderer/components/WireguardKeys.tsx +++ b/gui/src/renderer/components/WireguardKeys.tsx @@ -1,7 +1,10 @@ +import log from 'electron-log'; +import moment from 'moment'; import * as React from 'react'; import { Component, Text, View } from 'reactxp'; +import { sprintf } from 'sprintf-js'; import { messages } from '../../shared/gettext'; -import { WgKeyState } from '../redux/settings/reducers'; +import { IWgKey, WgKeyState } from '../redux/settings/reducers'; import * as AppButton from './AppButton'; import ImageView from './ImageView'; import { Container, Layout } from './Layout'; @@ -12,11 +15,13 @@ import styles from './WireguardKeysStyles'; export interface IProps { keyState: WgKeyState; isOffline: boolean; + locale: string; + onClose: () => void; onGenerateKey: () => void; - onVerifyKey: (publicKey: string) => void; + onReplaceKey: (old: IWgKey) => void; + onVerifyKey: (publicKey: IWgKey) => void; onVisitWebsiteKey: () => void; - onClose: () => void; } export default class WireguardKeys extends Component<IProps> { @@ -46,11 +51,24 @@ export default class WireguardKeys extends Component<IProps> { <Text style={styles.wgkeys__row_label}> {messages.pgettext('wireguard-keys', 'Public key')} </Text> - <View style={styles.wgkeys__row_value}>{this.getKeyRow()}</View> + <View style={styles.wgkeys__row_value}>{this.getKeyText()}</View> + <Text style={styles.wgkeys__row_label}> + {messages.pgettext('wireguard-keys', 'Key generated')} + </Text> + <Text style={styles.wgkeys__row_value}>{this.ageOfKeyString()}</Text> <View style={styles.wgkeys__validity_row}>{this.keyValidityLabel()}</View> </View> - <View style={styles.wgkeys__row}>{this.getActionButton()}</View> + <View style={styles.wgkeys__row}>{this.getGenerateButton()}</View> + <View style={styles.wgkeys__row}> + <AppButton.GreenButton + disabled={this.isVerifyButtonDisabled()} + onPress={this.getOnVerifyKeyCb()}> + <AppButton.Label> + {messages.pgettext('wireguard-key-view', 'Verify key')} + </AppButton.Label> + </AppButton.GreenButton> + </View> <View style={styles.wgkeys__row}> <AppButton.GreenButton disabled={this.props.isOffline} @@ -68,34 +86,50 @@ export default class WireguardKeys extends Component<IProps> { ); } - /// Action button can either generate or verify a key - private getActionButton() { + private isVerifyButtonDisabled(): boolean { switch (this.props.keyState.type) { case 'key-set': - const publicKey = this.props.keyState.publicKey; - // if the key is known to be invalid, allow the user to generate a new one - if (this.props.keyState.valid === false) { - break; - } + return false || this.props.isOffline; + default: + return true; + } + } - const verificationCallback = () => this.props.onVerifyKey(publicKey); + private getOnVerifyKeyCb() { + return () => { + switch (this.props.keyState.type) { + case 'key-set': + const key = this.props.keyState.key; + this.props.onVerifyKey(key); + break; + default: + log.error(`onVerifyKey called from invalid state - ${this.props.keyState.type}`); + } + }; + } - return ( - <AppButton.GreenButton disabled={this.props.isOffline} onPress={verificationCallback}> - <AppButton.Label> - {messages.pgettext('wireguard-key-view', 'Verify key')} - </AppButton.Label> - </AppButton.GreenButton> - ); + /// Action button can either generate or verify a key + private getGenerateButton() { + const generateText = messages.pgettext('wireguard-key-view', 'Generate key'); + const regenerateText = messages.pgettext('wireguard-key-view', 'Regenerate key'); + let buttonText = generateText; + let generateKey = this.props.onGenerateKey; + switch (this.props.keyState.type) { + case 'key-set': + buttonText = regenerateText; + const key = this.props.keyState.key; + generateKey = () => this.props.onReplaceKey(key); + break; case 'being-verified': - return this.busyButton(messages.pgettext('wireguard-key-view', 'Verifying key')); + return this.busyButton(regenerateText); + case 'being-replaced': case 'being-generated': return this.busyButton(messages.pgettext('wireguard-key-view', 'Generating key')); } return ( - <AppButton.GreenButton disabled={this.props.isOffline} onPress={this.props.onGenerateKey}> - <AppButton.Label>{messages.pgettext('wireguard-key-view', 'Generate key')}</AppButton.Label> + <AppButton.GreenButton disabled={this.props.isOffline} onPress={generateKey}> + <AppButton.Label>{buttonText}</AppButton.Label> </AppButton.GreenButton> ); } @@ -109,30 +143,26 @@ export default class WireguardKeys extends Component<IProps> { ); } - private getKeyRow() { + private getKeyText() { switch (this.props.keyState.type) { case 'being-verified': case 'key-set': // mimicking the truncating of the key from website return ( - <View title={this.props.keyState.publicKey}> + <View title={this.props.keyState.key.publicKey}> <Text style={styles.wgkeys__row_value}> - {this.props.keyState.publicKey.substring(0, 20) + '...'} + {this.props.keyState.key.publicKey.substring(0, 20) + '...'} </Text> </View> ); + case 'being-replaced': case 'being-generated': - return <ImageView source="icon-spinner" height={25} width={25} />; + return <ImageView source="icon-spinner" height={19} width={19} />; case 'too-many-keys': - return ( - <Text style={styles.wgkeys__invalid_key}> - {messages.pgettext('wireguard-key-view', 'Account has too many keys already')} - </Text> - ); case 'generation-failure': return ( <Text style={styles.wgkeys__invalid_key}> - {messages.pgettext('wireguard-key-view', 'Failed to generate key')} + {this.formatKeygenFailure(this.props.keyState.type)} </Text> ); default: @@ -147,23 +177,75 @@ export default class WireguardKeys extends Component<IProps> { private keyValidityLabel() { switch (this.props.keyState.type) { case 'being-verified': - return <ImageView source="icon-spinner" height={25} width={25} />; + return <ImageView source="icon-spinner" height={20} width={20} />; case 'key-set': - if (this.props.keyState.valid === true) { + const key = this.props.keyState.key; + if (key.valid === true) { return ( <Text style={styles.wgkeys__valid_key}> {messages.pgettext('account-view', 'Key is valid')} </Text> ); - } else if (this.props.keyState.valid === false) { + } else if (key.valid === false) { return ( <Text style={styles.wgkeys__invalid_key}> {messages.pgettext('wireguard-key-view', 'Key is invalid')} </Text> ); + } else if (key.replacementFailure) { + let failure = ''; + switch (key.replacementFailure) { + case 'too_many_keys': + failure = this.formatKeygenFailure('too-many-keys'); + break; + case 'generation_failure': + failure = this.formatKeygenFailure('generation-failure'); + break; + } + + const failureMessage = sprintf( + messages.pgettext('wireguard-key-view', 'Failed to replace key - %(failure)s'), + { failure }, + ); + return <Text style={styles.wgkeys__invalid_key}>{failureMessage}</Text>; + } else if (key.verificationFailed) { + return ( + <Text style={styles.wgkeys__invalid_key}> + {messages.pgettext('wireguard-key-view', 'Key verification failed')} + </Text> + ); } + + default: + return ( + // Placeholder to take up the same amount of space as the validity text/spinner + <View style={{ marginBottom: 20 }} /> + ); + } + } + + private ageOfKeyString(): string { + let keyCreatedSince = '-'; + switch (this.props.keyState.type) { + case 'key-set': + case 'being-verified': + keyCreatedSince = moment(this.props.keyState.key.created) + .locale(this.props.locale) + .fromNow(); + break; + } + + return keyCreatedSince; + } + + private formatKeygenFailure(failure: 'too-many-keys' | 'generation-failure'): string { + switch (failure) { + case 'too-many-keys': + return messages.pgettext('wireguard-key-view', 'Account has too many keys already'); + case 'generation-failure': + return messages.pgettext('wireguard-key-view', 'Failed to generate a key'); default: - return ''; + return failure; } } diff --git a/gui/src/renderer/components/WireguardKeysStyles.tsx b/gui/src/renderer/components/WireguardKeysStyles.tsx index 2172b79dae..ec3fd6cf2f 100644 --- a/gui/src/renderer/components/WireguardKeysStyles.tsx +++ b/gui/src/renderer/components/WireguardKeysStyles.tsx @@ -24,7 +24,6 @@ export default { lineHeight: 20, letterSpacing: -0.2, color: colors.white60, - marginBottom: 9, }), wgkeys__validity_row: Styles.createViewStyle({ paddingTop: 5, diff --git a/gui/src/renderer/containers/WireguardKeysPage.tsx b/gui/src/renderer/containers/WireguardKeysPage.tsx index 2fb185cb1d..813526ff29 100644 --- a/gui/src/renderer/containers/WireguardKeysPage.tsx +++ b/gui/src/renderer/containers/WireguardKeysPage.tsx @@ -4,20 +4,23 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { links } from '../../config.json'; import WireguardKeys from '../components/WireguardKeys'; +import { IWgKey } from '../redux/settings/reducers'; import { IReduxState, ReduxDispatch } from '../redux/store'; import { ISharedRouteProps } from '../routes'; -const mapStateToProps = (state: IReduxState, _props: ISharedRouteProps) => ({ +const mapStateToProps = (state: IReduxState, props: ISharedRouteProps) => ({ keyState: state.settings.wireguardKeyState, isOffline: state.connection.isBlocked, + locale: props.locale, }); const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) => { const history = bindActionCreators({ push, goBack }, dispatch); return { + onClose: () => history.goBack(), onGenerateKey: () => props.app.generateWireguardKey(), - onVerifyKey: (publicKey: string) => props.app.verifyWireguardKey(publicKey), + onReplaceKey: (oldKey: IWgKey) => props.app.replaceWireguardKey(oldKey), + onVerifyKey: (publicKey: IWgKey) => props.app.verifyWireguardKey(publicKey), onVisitWebsiteKey: () => shell.openExternal(links.manageKeys), - onClose: () => history.goBack(), }; }; diff --git a/gui/src/renderer/redux/settings/actions.ts b/gui/src/renderer/redux/settings/actions.ts index 6c87c18fa9..a7c766e7fd 100644 --- a/gui/src/renderer/redux/settings/actions.ts +++ b/gui/src/renderer/redux/settings/actions.ts @@ -1,6 +1,6 @@ -import { BridgeState, KeygenEvent } from '../../../shared/daemon-rpc-types'; +import { BridgeState, IWireguardPublicKey, KeygenEvent } from '../../../shared/daemon-rpc-types'; import { IGuiSettingsState } from '../../../shared/gui-settings-state'; -import { IRelayLocationRedux, RelaySettingsRedux } from './reducers'; +import { IRelayLocationRedux, IWgKey, RelaySettingsRedux } from './reducers'; export interface IUpdateGuiSettingsAction { type: 'UPDATE_GUI_SETTINGS'; @@ -50,16 +50,21 @@ export interface IUpdateAutoStartAction { // Used to set wireguard key when accounts are changed. export interface IWireguardSetKey { type: 'SET_WIREGUARD_KEY'; - publicKey?: string; + key?: IWgKey; } export interface IWireguardGenerateKey { type: 'GENERATE_WIREGUARD_KEY'; } +export interface IWireguardReplaceKey { + type: 'REPLACE_WIREGUARD_KEY'; + oldKey: IWgKey; +} + export interface IWireguardVerifyKey { type: 'VERIFY_WIREGUARD_KEY'; - publicKey: string; + key: IWgKey; } export interface IWireguardKeygenEvent { @@ -69,7 +74,7 @@ export interface IWireguardKeygenEvent { export interface IWireguardKeyVerifiedAction { type: 'WIREGUARD_KEY_VERIFICATION_COMPLETE'; - verified: boolean; + verified?: boolean; } export type SettingsAction = @@ -85,6 +90,7 @@ export type SettingsAction = | IWireguardSetKey | IWireguardVerifyKey | IWireguardGenerateKey + | IWireguardReplaceKey | IWireguardKeygenEvent | IWireguardKeyVerifiedAction; @@ -153,10 +159,17 @@ function updateAutoStart(autoStart: boolean): IUpdateAutoStartAction { }; } -function setWireguardKey(publicKey?: string): IWireguardSetKey { +function setWireguardKey(publicKey?: IWireguardPublicKey): IWireguardSetKey { + const key = publicKey + ? { + publicKey: publicKey.key, + created: publicKey.created, + valid: undefined, + } + : undefined; return { type: 'SET_WIREGUARD_KEY', - publicKey, + key, }; } @@ -173,14 +186,21 @@ function generateWireguardKey(): IWireguardGenerateKey { }; } -function verifyWireguardKey(publicKey: string): IWireguardVerifyKey { +function replaceWireguardKey(oldKey: IWgKey): IWireguardReplaceKey { + return { + type: 'REPLACE_WIREGUARD_KEY', + oldKey, + }; +} + +function verifyWireguardKey(key: IWgKey): IWireguardVerifyKey { return { type: 'VERIFY_WIREGUARD_KEY', - publicKey, + key, }; } -function completeWireguardKeyVerification(verified: boolean): IWireguardKeyVerifiedAction { +function completeWireguardKeyVerification(verified?: boolean): IWireguardKeyVerifiedAction { return { type: 'WIREGUARD_KEY_VERIFICATION_COMPLETE', verified, @@ -200,6 +220,7 @@ export default { setWireguardKey, setWireguardKeygenEvent, generateWireguardKey, + replaceWireguardKey, verifyWireguardKey, completeWireguardKeyVerification, }; diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts index e90022062c..5d7f37fb50 100644 --- a/gui/src/renderer/redux/settings/reducers.ts +++ b/gui/src/renderer/redux/settings/reducers.ts @@ -54,10 +54,17 @@ export interface IRelayLocationRedux { cities: IRelayLocationCityRedux[]; } -interface IWgKeySet { - type: 'key-set'; +export interface IWgKey { publicKey: string; + created: string; valid?: boolean; + replacementFailure?: KeygenEvent; + verificationFailed?: boolean; +} + +interface IWgKeySet { + type: 'key-set'; + key: IWgKey; } interface IWgKeyNotSet { @@ -76,9 +83,14 @@ interface IWgKeyBeingGenerated { type: 'being-generated'; } +interface IWgKeyBeingReplaced { + type: 'being-replaced'; + oldKey: IWgKey; +} + interface IWgKeyBeingVerified { type: 'being-verified'; - publicKey: string; + key: IWgKey; } export type WgKeyState = @@ -87,6 +99,7 @@ export type WgKeyState = | IWgKeyGenerationFailure | IWgTooManyKeys | IWgKeyBeingVerified + | IWgKeyBeingReplaced | IWgKeyBeingGenerated; export interface ISettingsReduxState { @@ -199,12 +212,12 @@ export default function( case 'SET_WIREGUARD_KEY': return { ...state, - wireguardKeyState: setWireguardKey(action.publicKey), + wireguardKeyState: setWireguardKey(action.key), }; case 'WIREGUARD_KEYGEN_EVENT': return { ...state, - wireguardKeyState: setWireguardKeygenEvent(action.event), + wireguardKeyState: setWireguardKeygenEvent(state, action.event), }; case 'WIREGUARD_KEY_VERIFICATION_COMPLETE': return { @@ -214,7 +227,7 @@ export default function( case 'VERIFY_WIREGUARD_KEY': return { ...state, - wireguardKeyState: { type: 'being-verified', publicKey: action.publicKey }, + wireguardKeyState: { type: 'being-verified', key: action.key }, }; case 'GENERATE_WIREGUARD_KEY': @@ -223,16 +236,22 @@ export default function( wireguardKeyState: { type: 'being-generated' }, }; + case 'REPLACE_WIREGUARD_KEY': + return { + ...state, + wireguardKeyState: { type: 'being-replaced', oldKey: action.oldKey }, + }; + default: return state; } } -function setWireguardKey(publicKey?: string): WgKeyState { - if (publicKey) { +function setWireguardKey(key?: IWgKey): WgKeyState { + if (key) { return { type: 'key-set', - publicKey, + key, }; } else { return { @@ -241,7 +260,23 @@ function setWireguardKey(publicKey?: string): WgKeyState { } } -function setWireguardKeygenEvent(keygenEvent: KeygenEvent): WgKeyState { +function setWireguardKeygenEvent(state: ISettingsReduxState, keygenEvent: KeygenEvent): WgKeyState { + const oldKeyState = state.wireguardKeyState; + if (oldKeyState.type === 'being-replaced') { + switch (keygenEvent) { + case 'too_many_keys': + case 'generation_failure': + return { + type: 'key-set', + key: { + ...oldKeyState.oldKey, + replacementFailure: keygenEvent, + }, + }; + default: + break; + } + } switch (keygenEvent) { case 'too_many_keys': return { type: 'too-many-keys' }; @@ -250,19 +285,26 @@ function setWireguardKeygenEvent(keygenEvent: KeygenEvent): WgKeyState { default: return { type: 'key-set', - publicKey: keygenEvent.newKey, - valid: true, + key: { + publicKey: keygenEvent.newKey.key, + created: keygenEvent.newKey.created, + valid: undefined, + }, }; } } -function applyKeyVerification(state: WgKeyState, verified: boolean): WgKeyState { +function applyKeyVerification(state: WgKeyState, verified?: boolean): WgKeyState { + const verificationFailed = verified === undefined ? true : undefined; switch (state.type) { case 'being-verified': return { - ...state, type: 'key-set', - valid: verified, + key: { + ...state.key, + valid: verified, + verificationFailed, + }, }; // drop the verification event if the key wasn't being verified. default: |
