Add Audit Dialog

This commit is contained in:
Reckless_Satoshi 2022-05-24 05:16:50 -07:00
parent b5b129fb4e
commit af001d31d2
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
8 changed files with 286 additions and 26 deletions

View File

@ -0,0 +1,200 @@
import React from "react";
import { useTranslation } from "react-i18next";
import {
Dialog,
DialogTitle,
Tooltip,
IconButton,
TextField,
DialogActions,
DialogContent,
DialogContentText,
Button,
Grid,
Link,
} from "@mui/material"
import { saveAsJson, saveAsTxt } from "../../utils/saveFile";
// Icons
import KeyIcon from '@mui/icons-material/Key';
import ContentCopy from "@mui/icons-material/ContentCopy";
import ForumIcon from '@mui/icons-material/Forum';
import { ExportIcon } from '../Icons';
type Props = {
open: boolean;
onClose: () => void;
orderId: number;
messages: array;
own_pub_key: string;
own_enc_priv_key: string;
peer_pub_key: string;
passphrase: string;
onClickBack: () => void;
}
const AuditPGPDialog = ({
open,
onClose,
orderId,
messages,
own_pub_key,
own_enc_priv_key,
peer_pub_key,
passphrase,
onClickBack,
}: Props): JSX.Element => {
const { t } = useTranslation();
return (
<Dialog
open={open}
onClose={onClose}
>
<DialogTitle >
{t("This chat is PGP Encrypted")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{t("Your communication is end-to-end encrypted with OpenPGP. You can verify the privacy of this chat using any third party tool based on the OpenPGP standard.")}
</DialogContentText>
<Grid container spacing={1} align="center">
<Grid item align="center" xs={12}>
<Button component={Link} target="_blank" href="https://learn.robosats.com/docs/pgp-encryption">{t("Learn how to audit")}</Button>
</Grid>
<Grid item align="center" xs={12}>
<TextField
sx={{width:"100%", maxWidth:"550px"}}
disabled
label={<b>{t("Your public key")}</b>}
value={own_pub_key}
variant='filled'
size='small'
InputProps={{
endAdornment:
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<IconButton onClick={()=> navigator.clipboard.writeText(own_pub_key)}>
<ContentCopy/>
</IconButton>
</Tooltip>,
}}
/>
</Grid>
<Grid item align="center" xs={12}>
<TextField
sx={{width:"100%", maxWidth:"550px"}}
disabled
label={<b>{t("Peer public key")}</b>}
value={peer_pub_key}
variant='filled'
size='small'
InputProps={{
endAdornment:
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<IconButton onClick={()=> navigator.clipboard.writeText(peer_pub_key)}>
<ContentCopy/>
</IconButton>
</Tooltip>,
}}
/>
</Grid>
<Grid item align="center" xs={12}>
<TextField
sx={{width:"100%", maxWidth:"550px"}}
disabled
label={<b>{t("Your encrypted private key")}</b>}
value={own_enc_priv_key}
variant='filled'
size='small'
InputProps={{
endAdornment:
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<IconButton onClick={()=> navigator.clipboard.writeText(own_enc_priv_key)}>
<ContentCopy/>
</IconButton>
</Tooltip>,
}}
/>
</Grid>
<Grid item align="center" xs={12}>
<TextField
sx={{width:"100%", maxWidth:"550px"}}
disabled
label={<b>{t("Your private key passphrase (keep secure!)")}</b>}
value={passphrase}
variant='filled'
size='small'
InputProps={{
endAdornment:
<Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
<IconButton onClick={()=> navigator.clipboard.writeText(passphrase)}>
<ContentCopy/>
</IconButton>
</Tooltip>,
}}
/>
</Grid>
<br/>
<Grid item xs={6}>
<Button
size="small"
color="primary"
variant="contained"
onClick={()=>saveAsJson(
'keys_'+orderId+'.json',
{"own_public_key": own_pub_key,
"peer_public_key":peer_pub_key,
"encrypted_private_key":own_enc_priv_key,
"passphrase":passphrase
})}>
<div style={{width:26,height:18}}>
<ExportIcon sx={{width:18,height:18}}/>
</div>
{t("Export Keys")}
<div style={{width:26,height:20}}>
<KeyIcon sx={{width:20,height:20}}/>
</div>
</Button>
</Grid>
<Grid item xs={6}>
{/* <ToolTip placement="top" enterTouchDelay={0} enterDelay={1000} enterNextDelay={2000} title={t("Save local messages and credentials as a JSON file")}> */}
<Button
size="small"
color="primary"
variant="contained"
onClick={()=>saveAsJson(
'messages_'+orderId+'.json',
messages)}>
<div style={{width:28,height:20}}>
<ExportIcon sx={{width:18,height:18}}/>
</div>
{t("Export Chat")}
<div style={{width:26,height:20}}>
<ForumIcon sx={{width:20,height:20}}/>
</div>
</Button>
{/* </ToolTip> */}
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={onClickBack} autoFocus>{t("Go back")}</Button>
</DialogActions>
</Dialog>
)
}
export default AuditPGPDialog;

View File

@ -1,3 +1,4 @@
export { default as AuditPGPDialog } from "./AuditPGP";
export { default as CommunityDialog } from "./Community";
export { default as InfoDialog } from "./Info";
export { default as LearnDialog } from "./Learn";

View File

@ -1,10 +1,19 @@
import React, { Component } from 'react';
import { withTranslation } from "react-i18next";
import {Button, Badge, TextField, Grid, Container, Card, CardHeader, Paper, Avatar, Typography} from "@mui/material";
import {Button, Badge, ToolTip, TextField, Grid, Container, Card, CardHeader, Paper, Avatar, Typography} from "@mui/material";
import ReconnectingWebSocket from 'reconnecting-websocket';
import { encryptMessage , decryptMessage} from "../utils/pgp";
import { getCookie } from "../utils/cookies";
import { saveAsTxt } from "../utils/saveFile";
import { saveAsJson, saveAsTxt } from "../utils/saveFile";
import { AuditPGPDialog } from "./Dialogs"
// Icons
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import FileDownloadIcon from '@mui/icons-material/FileDownload';
import VisibilityIcon from '@mui/icons-material/Visibility';
import KeyIcon from '@mui/icons-material/Key';
import { ExportIcon } from './Icons';
class Chat extends Component {
constructor(props) {
@ -65,7 +74,12 @@ class Chat extends Component {
// If we receive a public key other than ours (our peer key!)
if (dataFromServer.message.substring(0,36) == `-----BEGIN PGP PUBLIC KEY BLOCK-----` & dataFromServer.message != this.state.own_pub_key) {
console.log("PEER KEY RECEIVED!!")
if (dataFromServer.message == this.state.peer_pub_key){
console.log("PEER HAS RECONNECTED USING HIS PREVIOUSLY KNOWN PUBKEY")
} else if (dataFromServer.message != this.state.peer_pub_key & this.state.peer_pub_key != null){
console.log("PEER PUBKEY HAS CHANGED")
}
console.log("PEER KEY PUBKEY RECEIVED!!")
this.setState({peer_pub_key:dataFromServer.message})
} else
@ -85,6 +99,7 @@ class Chat extends Component {
plainTextMessage: decryptedData.decryptedMessage,
validSignature: decryptedData.validSignature,
userNick: dataFromServer.user_nick,
showPGP: false,
time: dataFromServer.time
}],
})
@ -102,18 +117,6 @@ class Chat extends Component {
this.rws.addEventListener('error', () => {
console.error('Socket encountered error: Closing socket');
});
// Encryption/Decryption Example
// console.log(encryptMessage('Example text to encrypt!',
// getCookie('pub_key').split('\\').join('\n'),
// getCookie('enc_priv_key').split('\\').join('\n'),
// getCookie('robot_token'))
// .then((encryptedMessage)=> decryptMessage(
// encryptedMessage,
// getCookie('pub_key').split('\\').join('\n'),
// getCookie('enc_priv_key').split('\\').join('\n'),
// getCookie('robot_token'))
// ))
}
componentDidUpdate() {
@ -140,6 +143,17 @@ class Chat extends Component {
e.preventDefault();
}
createJsonFile = () => {
return ({
"credentials": {
"own_public_key": this.state.own_pub_key,
"peer_public_key":this.state.peer_pub_key,
"encrypted_private_key":this.state.own_enc_priv_key,
"passphrase":this.state.token},
"messages": this.state.messages,
})
}
render() {
const { t } = this.props;
return (
@ -195,7 +209,7 @@ class Chat extends Component {
}
style={{backgroundColor: '#fafafa'}}
title={message.userNick}
subheader={this.state.audit ? message.plaintTextEncrypted : message.plainTextMessage}
subheader={this.state.audit ? message.encryptedMessage : message.plainTextMessage}
subheaderTypographyProps={{sx: {wordWrap: "break-word", width: '200px', color: '#444444'}}}
/>}
</Card>
@ -223,15 +237,30 @@ class Chat extends Component {
</Grid>
</Grid>
</form>
<Grid>
<Button color="info" variant="contained" onClick={()=>this.setState({audit:!this.state.audit})}>{t("Audit")} </Button>
</Grid>
<Grid>
<Button size="small" color="inherit" variant="contained" onClick={()=>saveAsTxt('messages.txt', this.state.messages)}>{t("Save Messages")} </Button>
</Grid>
<Grid>
<Button size="small" color="inherit" variant="contained" onClick={()=>saveAsTxt('keys.txt', {"own_public_key":this.state.own_pub_key,"peer_public_key":this.state.peer_pub_key,"encrypted_private_key":this.state.own_enc_priv_key,"passphrase":this.state.token})}>{t("Save Keys")} </Button>
<div style={{height:4}}/>
<Grid container spacing={0}>
<AuditPGPDialog
open={this.state.audit}
onClose={() => this.setState({audit:false})}
orderId={Number(this.props.orderId)}
messages={this.state.messages}
own_pub_key={this.state.own_pub_key}
own_enc_priv_key={this.state.own_enc_priv_key}
peer_pub_key={this.state.peer_pub_key ? this.state.peer_pub_key : "Not received yet"}
passphrase={this.state.token}
onClickBack={() => this.setState({audit:false})}
/>
<Grid item xs={6}>
<Button size="small" color="primary" variant="outlined" onClick={()=>this.setState({audit:!this.state.audit})}><KeyIcon/>{t("Audit PGP")} </Button>
</Grid>
<Grid item xs={6}>
{/* <ToolTip placement="top" enterTouchDelay={0} enterDelay={1000} enterNextDelay={2000} title={t("Save local messages and credentials as a JSON file")}> */}
<Button size="small" color="primary" variant="outlined" onClick={()=>saveAsJson('chat_'+this.props.orderId+'.json', this.createJsonFile())}><div style={{width:28,height:20}}><ExportIcon sx={{width:20,height:20}}/></div> {t("Export All")} </Button>
{/* </ToolTip> */}
</Grid>
</Grid>
</Container>
)
}

View File

@ -0,0 +1,10 @@
import React, { Component } from "react";
import { SvgIcon } from "@mui/material"
export default function ExportIcon(props) {
return (
<SvgIcon sx={props.sx} color={props.color} viewBox="0 0 576 512">
<path d="M192 312C192 298.8 202.8 288 216 288H384V160H256c-17.67 0-32-14.33-32-32L224 0H48C21.49 0 0 21.49 0 48v416C0 490.5 21.49 512 48 512h288c26.51 0 48-21.49 48-48v-128H216C202.8 336 192 325.3 192 312zM256 0v128h128L256 0zM568.1 295l-80-80c-9.375-9.375-24.56-9.375-33.94 0s-9.375 24.56 0 33.94L494.1 288H384v48h110.1l-39.03 39.03C450.3 379.7 448 385.8 448 392s2.344 12.28 7.031 16.97c9.375 9.375 24.56 9.375 33.94 0l80-80C578.3 319.6 578.3 304.4 568.1 295z"/>
</SvgIcon>
);
}

View File

@ -12,3 +12,5 @@ export { default as RoboSatsTextIcon } from "./RoboSatsText";
export { default as SellSatsCheckedIcon } from "./SellSatsChecked";
export { default as SellSatsIcon } from "./SellSats";
export { default as SendReceiveIcon } from "./SendReceive";
export { default as ExportIcon } from "./Export";

View File

@ -577,7 +577,7 @@ class MakerPage extends Component {
</Grid>
<Grid item xs={12} align="center">
<Accordion elevation={0} sx={{width:'280px', position:'relative', left:'-8px'}}>
<Accordion defaultExpanded={true} elevation={0} sx={{width:'280px', position:'relative', left:'-8px'}}>
<AccordionSummary expandIcon={<ExpandMoreIcon color="primary"/>}>
<Typography sx={{flexGrow: 1, textAlign: "center"}} color="text.secondary">{t("Expiry Timers")}</Typography>
</AccordionSummary>

View File

@ -6,7 +6,7 @@ export async function genKey(highEntropyToken) {
const keyPair = await openpgp.generateKey({
type: 'ecc', // Type of the key, defaults to ECC
curve: 'curve25519', // ECC curve name, defaults to curve25519
userIDs: [{name: 'RoboSats Avatar'}],
userIDs: [{name: 'RoboSats Avatar ID'+ parseInt(Math.random() * 1000000)}], //Just for identification. Ideally it would be the avatar nickname, but the nickname is generated only after submission
passphrase: highEntropyToken,
format: 'armored'
})

View File

@ -20,3 +20,21 @@ export const saveAsTxt = (filename, dataObjToWrite) => {
link.dispatchEvent(evt);
link.remove()
};
export const saveAsJson = (filename, dataObjToWrite) => {
const blob = new Blob([JSON.stringify(dataObjToWrite, null, 2)], { type: "text/json" });
const link = document.createElement("a");
link.download = filename;
link.href = window.URL.createObjectURL(blob);
link.dataset.downloadurl = ["text/json", link.download, link.href].join(":");
const evt = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
});
link.dispatchEvent(evt);
link.remove()
};