mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-14 11:26:24 +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 CommunityDialog } from "./Community";
|
||||||
export { default as InfoDialog } from "./Info";
|
export { default as InfoDialog } from "./Info";
|
||||||
export { default as LearnDialog } from "./Learn";
|
export { default as LearnDialog } from "./Learn";
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { withTranslation } from "react-i18next";
|
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 ReconnectingWebSocket from 'reconnecting-websocket';
|
||||||
import { encryptMessage , decryptMessage} from "../utils/pgp";
|
import { encryptMessage , decryptMessage} from "../utils/pgp";
|
||||||
import { getCookie } from "../utils/cookies";
|
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 {
|
class Chat extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -65,7 +74,12 @@ class Chat extends Component {
|
|||||||
|
|
||||||
// If we receive a public key other than ours (our peer key!)
|
// 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) {
|
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})
|
this.setState({peer_pub_key:dataFromServer.message})
|
||||||
} else
|
} else
|
||||||
|
|
||||||
@ -85,6 +99,7 @@ class Chat extends Component {
|
|||||||
plainTextMessage: decryptedData.decryptedMessage,
|
plainTextMessage: decryptedData.decryptedMessage,
|
||||||
validSignature: decryptedData.validSignature,
|
validSignature: decryptedData.validSignature,
|
||||||
userNick: dataFromServer.user_nick,
|
userNick: dataFromServer.user_nick,
|
||||||
|
showPGP: false,
|
||||||
time: dataFromServer.time
|
time: dataFromServer.time
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
@ -102,18 +117,6 @@ class Chat extends Component {
|
|||||||
this.rws.addEventListener('error', () => {
|
this.rws.addEventListener('error', () => {
|
||||||
console.error('Socket encountered error: Closing socket');
|
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() {
|
componentDidUpdate() {
|
||||||
@ -140,6 +143,17 @@ class Chat extends Component {
|
|||||||
e.preventDefault();
|
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() {
|
render() {
|
||||||
const { t } = this.props;
|
const { t } = this.props;
|
||||||
return (
|
return (
|
||||||
@ -195,7 +209,7 @@ class Chat extends Component {
|
|||||||
}
|
}
|
||||||
style={{backgroundColor: '#fafafa'}}
|
style={{backgroundColor: '#fafafa'}}
|
||||||
title={message.userNick}
|
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'}}}
|
subheaderTypographyProps={{sx: {wordWrap: "break-word", width: '200px', color: '#444444'}}}
|
||||||
/>}
|
/>}
|
||||||
</Card>
|
</Card>
|
||||||
@ -223,15 +237,30 @@ class Chat extends Component {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</form>
|
</form>
|
||||||
<Grid>
|
<div style={{height:4}}/>
|
||||||
<Button color="info" variant="contained" onClick={()=>this.setState({audit:!this.state.audit})}>{t("Audit")} </Button>
|
<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>
|
||||||
<Grid>
|
|
||||||
<Button size="small" color="inherit" variant="contained" onClick={()=>saveAsTxt('messages.txt', this.state.messages)}>{t("Save Messages")} </Button>
|
<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>
|
||||||
<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>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</Container>
|
</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 SellSatsCheckedIcon } from "./SellSatsChecked";
|
||||||
export { default as SellSatsIcon } from "./SellSats";
|
export { default as SellSatsIcon } from "./SellSats";
|
||||||
export { default as SendReceiveIcon } from "./SendReceive";
|
export { default as SendReceiveIcon } from "./SendReceive";
|
||||||
|
export { default as ExportIcon } from "./Export";
|
||||||
|
|
||||||
|
@ -577,7 +577,7 @@ class MakerPage extends Component {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12} align="center">
|
<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"/>}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon color="primary"/>}>
|
||||||
<Typography sx={{flexGrow: 1, textAlign: "center"}} color="text.secondary">{t("Expiry Timers")}</Typography>
|
<Typography sx={{flexGrow: 1, textAlign: "center"}} color="text.secondary">{t("Expiry Timers")}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
|
@ -6,7 +6,7 @@ export async function genKey(highEntropyToken) {
|
|||||||
const keyPair = await openpgp.generateKey({
|
const keyPair = await openpgp.generateKey({
|
||||||
type: 'ecc', // Type of the key, defaults to ECC
|
type: 'ecc', // Type of the key, defaults to ECC
|
||||||
curve: 'curve25519', // ECC curve name, defaults to curve25519
|
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,
|
passphrase: highEntropyToken,
|
||||||
format: 'armored'
|
format: 'armored'
|
||||||
})
|
})
|
||||||
|
@ -20,3 +20,21 @@ export const saveAsTxt = (filename, dataObjToWrite) => {
|
|||||||
link.dispatchEvent(evt);
|
link.dispatchEvent(evt);
|
||||||
link.remove()
|
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