mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-13 19:06:26 +00:00
Add Audit Dialog
This commit is contained in:
parent
b5b129fb4e
commit
af001d31d2
200
frontend/src/components/Dialogs/AuditPGP.tsx
Normal file
200
frontend/src/components/Dialogs/AuditPGP.tsx
Normal 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;
|
@ -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";
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
10
frontend/src/components/Icons/Export.tsx
Normal file
10
frontend/src/components/Icons/Export.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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";
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
})
|
||||
|
@ -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()
|
||||
};
|
Loading…
Reference in New Issue
Block a user