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 { TunnelState } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import { IWgKey, WgKeyState } from '../redux/settings/reducers'; import * as AppButton from './AppButton'; import ClipboardLabel from './ClipboardLabel'; import ImageView from './ImageView'; import { Container, Layout } from './Layout'; import { BackBarItem, NavigationBar, NavigationContainer, NavigationItems, NavigationScrollbars, TitleBarItem, } from './NavigationBar'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; import styles from './WireguardKeysStyles'; export interface IProps { keyState: WgKeyState; isOffline: boolean; locale: string; tunnelState: TunnelState; onClose: () => void; onGenerateKey: () => void; onReplaceKey: (old: IWgKey) => void; onVerifyKey: (publicKey: IWgKey) => void; onVisitWebsiteKey: () => Promise; } export interface IState { recentlyGeneratedKey: boolean; userHasInitiatedVerification: boolean; } export default class WireguardKeys extends Component { public state = { recentlyGeneratedKey: false, userHasInitiatedVerification: false, }; public componentDidMount() { this.verifyKey(); } public componentDidUpdate(prevProps: IProps) { const prevKey = prevProps.keyState.type === 'key-set' ? prevProps.keyState.key.publicKey : undefined; const key = this.props.keyState.type === 'key-set' ? this.props.keyState.key.publicKey : undefined; if (this.props.tunnelState.state === 'connected' && key !== undefined && key != prevKey) { this.setState({ recentlyGeneratedKey: true }); } if ( this.state.recentlyGeneratedKey && prevProps.tunnelState.state !== 'connected' && this.props.tunnelState.state === 'connected' ) { this.setState({ recentlyGeneratedKey: false }); } } public render() { return ( { // TRANSLATORS: Back button in navigation bar messages.pgettext('wireguard-keys-nav', 'Advanced') } { // TRANSLATORS: Title label in navigation bar messages.pgettext('wireguard-keys-nav', 'WireGuard key') } {messages.pgettext('wireguard-keys-nav', 'WireGuard key')} {messages.pgettext('wireguard-key-view', 'Public key')} {this.getKeyText()} {messages.pgettext('wireguard-key-view', 'Key generated')} {this.ageOfKeyString()} {this.props.isOffline ? ( this.offlineLabel() ) : ( {this.keyValidityLabel()} )} {this.getGenerateButton()} {messages.pgettext('wireguard-key-view', 'Verify key')} {messages.pgettext('wireguard-key-view', 'Manage keys')} ); } private offlineLabel() { return this.state.recentlyGeneratedKey ? ( {messages.pgettext('wireguard-key-view', 'Reconnecting with new WireGuard key...')} ) : ( {messages.pgettext('wireguard-key-view', 'Unable to manage keys while in a blocked state')} ); } private isVerifyButtonDisabled(): boolean { switch (this.props.keyState.type) { case 'key-set': return false || this.props.isOffline; default: return true; } } private handleVerifyKeyPress = () => { this.setState({ userHasInitiatedVerification: true }); this.verifyKey(); }; private verifyKey() { 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}`); } } /// 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(regenerateText); case 'being-replaced': case 'being-generated': return this.busyButton(messages.pgettext('wireguard-key-view', 'Generating key')); } return ( {buttonText} ); } private busyButton(message: string) { return ( {message} ); } private getKeyText() { switch (this.props.keyState.type) { case 'being-verified': case 'key-set': { // mimicking the truncating of the key from website const publicKey = this.props.keyState.key.publicKey; return ( ); } case 'being-replaced': case 'being-generated': return ; case 'too-many-keys': case 'generation-failure': return ( {this.formatKeygenFailure(this.props.keyState.type)} ); default: return ( {messages.pgettext('wireguard-key-view', 'No key set')} ); } } private keyValidityLabel() { switch (this.props.keyState.type) { case 'being-verified': return this.state.userHasInitiatedVerification ? ( ) : null; case 'key-set': { const key = this.props.keyState.key; if (key.valid === true && this.state.userHasInitiatedVerification) { return ( {messages.pgettext('wireguard-key-view', 'Key is valid')} ); } else if (key.valid === false) { return ( {messages.pgettext('wireguard-key-view', 'Key is invalid')} ); } else if (key.replacementFailure) { let failureMessage = ''; switch (key.replacementFailure) { case 'too_many_keys': failureMessage = this.formatKeygenFailure('too-many-keys'); break; case 'generation_failure': failureMessage = this.formatKeygenFailure('generation-failure'); break; } return {failureMessage}; } else if (key.verificationFailed) { return ( {messages.pgettext('wireguard-key-view', 'Key verification failed')} ); } else { return null; } } default: return null; } } 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': // TRANSLATORS: "%(manage)" is replaced with the text in the "Manage keys" button. return sprintf( messages.pgettext( 'wireguard-key-view', 'Unable to regenerate key: you already have the maximum number of keys. To generate a new key, you first need to revoke one under “Manage keys.”', ), { manage: messages.pgettext('wireguard-key-view', 'Manage keys') }, ); case 'generation-failure': return messages.pgettext('wireguard-key-view', 'Failed to generate a key'); default: return failure; } } }