You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			256 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			256 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			TypeScript
		
	
| import React from 'react';
 | |
| import * as _ from 'lodash';
 | |
| 
 | |
| import {
 | |
|   SessionButton,
 | |
|   SessionButtonColor,
 | |
|   SessionButtonType,
 | |
| } from './SessionButton';
 | |
| import { UserUtil } from '../../util';
 | |
| import { MultiDeviceProtocol } from '../../session/protocols';
 | |
| import { PubKey } from '../../session/types';
 | |
| import { ConversationModel } from '../../../js/models/conversations';
 | |
| import { SessionSpinner } from './SessionSpinner';
 | |
| import classNames from 'classnames';
 | |
| import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
 | |
| import { Constants } from '../../session';
 | |
| import { DefaultTheme, withTheme } from 'styled-components';
 | |
| 
 | |
| interface Props {
 | |
|   conversation: ConversationModel;
 | |
|   theme: DefaultTheme;
 | |
| }
 | |
| 
 | |
| interface State {
 | |
|   loading: boolean;
 | |
|   error?: 'verificationKeysLoadFail';
 | |
|   securityNumber?: string;
 | |
|   isVerified?: boolean;
 | |
| }
 | |
| 
 | |
| class SessionKeyVerificationInner extends React.Component<Props, State> {
 | |
|   constructor(props: any) {
 | |
|     super(props);
 | |
| 
 | |
|     this.state = {
 | |
|       loading: true,
 | |
|       error: undefined,
 | |
|       securityNumber: undefined,
 | |
|       isVerified: this.props.conversation.isVerified(),
 | |
|     };
 | |
| 
 | |
|     this.toggleVerification = this.toggleVerification.bind(this);
 | |
|     this.onSafetyNumberChanged = this.onSafetyNumberChanged.bind(this);
 | |
|   }
 | |
| 
 | |
|   public async componentWillMount() {
 | |
|     const securityNumber = await this.generateSecurityNumber();
 | |
| 
 | |
|     if (!securityNumber) {
 | |
|       this.setState({
 | |
|         error: 'verificationKeysLoadFail',
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Finished loading
 | |
|     this.setState({
 | |
|       loading: false,
 | |
|       securityNumber,
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   public render() {
 | |
|     const theirName = this.props.conversation.attributes.profileName;
 | |
|     const theirPubkey = this.props.conversation.id;
 | |
|     const isVerified = this.props.conversation.isVerified();
 | |
| 
 | |
|     if (this.state.loading) {
 | |
|       return (
 | |
|         <div className="key-verification">
 | |
|           <SessionSpinner loading={this.state.loading} />
 | |
|         </div>
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const verificationIconColor = isVerified
 | |
|       ? Constants.UI.COLORS.GREEN
 | |
|       : Constants.UI.COLORS.DANGER;
 | |
|     const verificationButtonColor = isVerified
 | |
|       ? SessionButtonColor.Warning
 | |
|       : SessionButtonColor.Success;
 | |
|     const verificationButton = (
 | |
|       <SessionButton
 | |
|         buttonType={SessionButtonType.DefaultOutline}
 | |
|         buttonColor={verificationButtonColor}
 | |
|         onClick={this.toggleVerification}
 | |
|       >
 | |
|         {window.i18n(isVerified ? 'unverify' : 'verify')}
 | |
|       </SessionButton>
 | |
|     );
 | |
| 
 | |
|     return (
 | |
|       <div className="key-verification">
 | |
|         {this.state.error ? (
 | |
|           <h3>{window.i18n(this.state.error)}</h3>
 | |
|         ) : (
 | |
|           <>
 | |
|             <div className={classNames('key-verification__header')}>
 | |
|               <h2>{window.i18n('safetyNumber')}</h2>
 | |
|               <small>{theirPubkey}</small>
 | |
|             </div>
 | |
| 
 | |
|             <div
 | |
|               className={classNames(
 | |
|                 'key-verification__key',
 | |
|                 'session-info-box'
 | |
|               )}
 | |
|             >
 | |
|               {this.renderSecurityNumber()}
 | |
|             </div>
 | |
| 
 | |
|             <div className="key-verification__help">
 | |
|               {window.i18n('verifyHelp', theirName)}
 | |
|             </div>
 | |
| 
 | |
|             <div className="key-verification__is-verified">
 | |
|               <span>
 | |
|                 <SessionIcon
 | |
|                   iconType={SessionIconType.Lock}
 | |
|                   iconSize={SessionIconSize.Huge}
 | |
|                   iconColor={verificationIconColor}
 | |
|                   theme={this.props.theme}
 | |
|                 />
 | |
|                 {window.i18n(
 | |
|                   isVerified ? 'isVerified' : 'isNotVerified',
 | |
|                   theirName
 | |
|                 )}
 | |
|               </span>
 | |
| 
 | |
|               {verificationButton}
 | |
|             </div>
 | |
|           </>
 | |
|         )}
 | |
|       </div>
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   public async onSafetyNumberChanged() {
 | |
|     const conversationModel = this.props.conversation;
 | |
|     await conversationModel.getProfiles();
 | |
| 
 | |
|     const securityNumber = await this.generateSecurityNumber();
 | |
|     this.setState({ securityNumber });
 | |
| 
 | |
|     window.confirmationDialog({
 | |
|       title: window.i18n('changedSinceVerifiedTitle'),
 | |
|       message: window.i18n('changedRightAfterVerify', [
 | |
|         conversationModel.attributes.profileName,
 | |
|         conversationModel.attributes.profileName,
 | |
|       ]),
 | |
|       hideCancel: true,
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   private async generateSecurityNumber(): Promise<string | undefined> {
 | |
|     const ourDeviceKey = await UserUtil.getCurrentDevicePubKey();
 | |
| 
 | |
|     if (!ourDeviceKey) {
 | |
|       this.setState({
 | |
|         error: 'verificationKeysLoadFail',
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const conversationId = this.props.conversation.id;
 | |
|     const ourPrimaryKey = (
 | |
|       await MultiDeviceProtocol.getPrimaryDevice(PubKey.cast(ourDeviceKey))
 | |
|     ).key;
 | |
| 
 | |
|     // Grab identity keys
 | |
|     const ourIdentityKey = await window.textsecure.storage.protocol.loadIdentityKey(
 | |
|       ourPrimaryKey
 | |
|     );
 | |
|     const theirIdentityKey = await window.textsecure.storage.protocol.loadIdentityKey(
 | |
|       this.props.conversation.id
 | |
|     );
 | |
| 
 | |
|     if (!ourIdentityKey || !theirIdentityKey) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Generate security number
 | |
|     const fingerprintGenerator = new window.libsignal.FingerprintGenerator(
 | |
|       5200
 | |
|     );
 | |
|     return fingerprintGenerator.createFor(
 | |
|       ourPrimaryKey,
 | |
|       ourIdentityKey,
 | |
|       conversationId,
 | |
|       theirIdentityKey
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   private async toggleVerification() {
 | |
|     const conversationModel = this.props.conversation;
 | |
| 
 | |
|     try {
 | |
|       await conversationModel.toggleVerified();
 | |
|       this.setState({ isVerified: !this.state.isVerified });
 | |
| 
 | |
|       await conversationModel.getProfiles();
 | |
|     } catch (e) {
 | |
|       if (e instanceof Error) {
 | |
|         if (e.name === 'OutgoingIdentityKeyError') {
 | |
|           await this.onSafetyNumberChanged();
 | |
|         } else {
 | |
|           window.log.error(
 | |
|             'failed to toggle verified:',
 | |
|             e && e.stack ? e.stack : e
 | |
|           );
 | |
|         }
 | |
|       } else {
 | |
|         const keyError = _.some(
 | |
|           e.errors,
 | |
|           error => error.name === 'OutgoingIdentityKeyError'
 | |
|         );
 | |
|         if (keyError) {
 | |
|           await this.onSafetyNumberChanged();
 | |
|         } else {
 | |
|           _.forEach(e.errors, error => {
 | |
|             window.log.error(
 | |
|               'failed to toggle verified:',
 | |
|               error && error.stack ? error.stack : error
 | |
|             );
 | |
|           });
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private renderSecurityNumber(): Array<JSX.Element> | undefined {
 | |
|     // Turns  32813902154726601686003948952478 ...
 | |
|     // into   32813 90215 47266 ...
 | |
|     const { loading, securityNumber } = this.state;
 | |
| 
 | |
|     if (loading) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const securityNumberChunks = _.chunk(
 | |
|       Array.from(securityNumber ?? []),
 | |
|       5
 | |
|     ).map(chunk => chunk.join(''));
 | |
|     const securityNumberLines = _.chunk(securityNumberChunks, 4).map(chunk =>
 | |
|       chunk.join(' ')
 | |
|     );
 | |
| 
 | |
|     const securityNumberElement = securityNumberLines.map(line => (
 | |
|       <div key={line}>{line}</div>
 | |
|     ));
 | |
|     return securityNumberElement;
 | |
|   }
 | |
| }
 | |
| 
 | |
| export const SessionKeyVerification = withTheme(SessionKeyVerificationInner);
 |