From 4b0f3bc6373b60bb54426abe7f7cdf01f64d9e62 Mon Sep 17 00:00:00 2001 From: Jonathan Zernik Date: Wed, 20 Jan 2021 11:41:02 -0800 Subject: [PATCH] Add rpc method to get private key (#664) * Add rpc method to get private key * Add export private key to frontend --- .../ExportPrivateKeyDialog.js | 124 ++++++++++++++++++ .../ExportPrivateKeyDialog/package.json | 6 + .../ExportPrivateKeyDialog/styles.js | 43 ++++++ frontend/src/pages/profile/Profile.js | 43 ++++++ frontend/src/squeakclient/requests.js | 13 ++ itests/tests/test_squeak_node.py | 12 ++ proto/squeak_admin.proto | 14 ++ .../admin/squeak_admin_server_handler.py | 10 ++ .../admin/squeak_admin_server_servicer.py | 3 + squeaknode/admin/webapp/app.py | 8 ++ squeaknode/core/squeak_controller.py | 8 ++ 11 files changed, 284 insertions(+) create mode 100644 frontend/src/components/ExportPrivateKeyDialog/ExportPrivateKeyDialog.js create mode 100644 frontend/src/components/ExportPrivateKeyDialog/package.json create mode 100644 frontend/src/components/ExportPrivateKeyDialog/styles.js diff --git a/frontend/src/components/ExportPrivateKeyDialog/ExportPrivateKeyDialog.js b/frontend/src/components/ExportPrivateKeyDialog/ExportPrivateKeyDialog.js new file mode 100644 index 00000000..e79881b4 --- /dev/null +++ b/frontend/src/components/ExportPrivateKeyDialog/ExportPrivateKeyDialog.js @@ -0,0 +1,124 @@ +import React, {useState, useEffect} from 'react'; +import { + Paper, + IconButton, + Menu, + MenuItem, + Typography, + Grid, + Box, + Link, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + TextField, + DialogActions, + Button, + FormControl, + InputLabel, + Select, +} from "@material-ui/core"; +import { MoreVert as MoreIcon } from "@material-ui/icons"; +import {useHistory} from "react-router-dom"; +import classnames from "classnames"; + +// styles +import useStyles from "./styles"; + +import Widget from "../../components/Widget"; +import SqueakThreadItem from "../../components/SqueakThreadItem"; + +import { + getSqueakProfilePrivateKey, +} from "../../squeakclient/requests" + + + +export default function ExportPrivateKeyDialog({ + open, + handleClose, + profile, + ...props +}) { + var classes = useStyles(); + const history = useHistory(); + + var [privateKey, setPrivateKey] = useState(''); + + const resetFields = () => { + setPrivateKey(''); + }; + + const handleChangePrivateKey = (event) => { + setPrivateKey(event.target.value); + }; + + const getPrivateKey = () => { + var profileId = profile.getProfileId(); + getSqueakProfilePrivateKey(profileId, (response) => { + setPrivateKey(response.getPrivateKey()); + }); + }; + + function handleSubmit(event) { + event.preventDefault(); + getPrivateKey(); + } + + function DisplayPrivateKey() { + return ( + + ) + } + + function CancelButton() { + return ( + + ) + } + + function ShowPrivateKeyButton() { + return ( + + ) + } + + return ( + + Export Private Key +
+ + {DisplayPrivateKey()} + + + {CancelButton()} + {ShowPrivateKeyButton()} + +
+
+ ) +} diff --git a/frontend/src/components/ExportPrivateKeyDialog/package.json b/frontend/src/components/ExportPrivateKeyDialog/package.json new file mode 100644 index 00000000..3bc2270c --- /dev/null +++ b/frontend/src/components/ExportPrivateKeyDialog/package.json @@ -0,0 +1,6 @@ +{ + "name": "ExportPrivateKeyDialog", + "version": "0.0.0", + "private": true, + "main": "ExportPrivateKeyDialog.js" +} diff --git a/frontend/src/components/ExportPrivateKeyDialog/styles.js b/frontend/src/components/ExportPrivateKeyDialog/styles.js new file mode 100644 index 00000000..f2d64da1 --- /dev/null +++ b/frontend/src/components/ExportPrivateKeyDialog/styles.js @@ -0,0 +1,43 @@ +import { makeStyles } from "@material-ui/styles"; + +export default makeStyles(theme => ({ + widgetWrapper: { + display: "flex", + minHeight: "100%", + }, + widgetHeader: { + padding: theme.spacing(3), + paddingBottom: theme.spacing(1), + display: "flex", + justifyContent: "space-between", + alignItems: "center", + }, + widgetRoot: { + boxShadow: theme.customShadows.widget, + }, + widgetBody: { + paddingBottom: theme.spacing(3), + paddingRight: theme.spacing(3), + paddingLeft: theme.spacing(3), + }, + noPadding: { + padding: 0, + }, + paper: { + display: "flex", + flexDirection: "column", + flexGrow: 1, + overflow: "hidden", + }, + moreButton: { + margin: -theme.spacing(1), + padding: 0, + width: 40, + height: 40, + color: theme.palette.text.hint, + "&:hover": { + backgroundColor: theme.palette.primary.main, + color: "rgba(255, 255, 255, 0.35)", + }, + }, +})); diff --git a/frontend/src/pages/profile/Profile.js b/frontend/src/pages/profile/Profile.js index 27f653f2..298d74e8 100644 --- a/frontend/src/pages/profile/Profile.js +++ b/frontend/src/pages/profile/Profile.js @@ -19,6 +19,7 @@ import useStyles from "./styles"; import PageTitle from "../../components/PageTitle"; import Widget from "../../components/Widget"; import DeleteProfileDialog from "../../components/DeleteProfileDialog"; +import ExportPrivateKeyDialog from "../../components/ExportPrivateKeyDialog"; import { getSqueakProfileRequest, @@ -32,6 +33,7 @@ export default function ProfilePage() { const { id } = useParams(); const [squeakProfile, setSqueakProfile] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [exportPrivateKeyDialogOpen, setExportPrivateKeyDialogOpen] = useState(false); const history = useHistory(); const getSqueakProfile = (id) => { @@ -61,10 +63,18 @@ export default function ProfilePage() { setDeleteDialogOpen(true); }; + const handleClickOpenExportPrivateKeyDialog = () => { + setExportPrivateKeyDialogOpen(true); + }; + const handleCloseDeleteDialog = () => { setDeleteDialogOpen(false); }; + const handleCloseExportPrivateKeyDialog = () => { + setExportPrivateKeyDialogOpen(false); + }; + const handleSettingsFollowingChange = (event) => { console.log("Following changed for profile id: " + id); console.log("Following changed to: " + event.target.checked); @@ -94,6 +104,9 @@ export default function ProfilePage() { {ProfileSettingsForm()} {ViewSqueaksButton()} {DeleteProfileButton()} + {squeakProfile.getHasPrivateKey() && + ExportPrivateKeyButton() + } ) } @@ -133,6 +146,23 @@ export default function ProfilePage() { ) } + function ExportPrivateKeyButton() { + return ( + <> + +
+ +
+
+ + ) + } + function ViewSqueaksButton() { return ( <> @@ -162,6 +192,18 @@ export default function ProfilePage() { ) } + function ExportPrivateKeyDialogContent() { + return ( + <> + + + ) + } + return ( <> @@ -172,6 +214,7 @@ export default function ProfilePage() { } {DeleteProfileDialogContent()} + {ExportPrivateKeyDialogContent()} ); } diff --git a/frontend/src/squeakclient/requests.js b/frontend/src/squeakclient/requests.js index c5fca65d..9903e491 100644 --- a/frontend/src/squeakclient/requests.js +++ b/frontend/src/squeakclient/requests.js @@ -86,6 +86,8 @@ import { GetReceivedPaymentsReply, GetNetworkRequest, GetNetworkReply, + GetSqueakProfilePrivateKeyRequest, + GetSqueakProfilePrivateKeyReply, } from "../proto/squeak_admin_pb" console.log('The value of REACT_APP_SERVER_PORT is:', process.env.REACT_APP_SERVER_PORT); @@ -1007,3 +1009,14 @@ export function getNetworkRequest(handleResponse) { } ); }; + +export function getSqueakProfilePrivateKey(id, handleResponse) { + var request = new GetSqueakProfilePrivateKeyRequest(); + request.setProfileId(id); + makeRequest( + 'getsqueakprofileprivatekey', + request, + GetSqueakProfilePrivateKeyReply.deserializeBinary, + handleResponse, + ); +}; diff --git a/itests/tests/test_squeak_node.py b/itests/tests/test_squeak_node.py index dd686759..60f98211 100644 --- a/itests/tests/test_squeak_node.py +++ b/itests/tests/test_squeak_node.py @@ -500,6 +500,18 @@ def test_delete_profile(server_stub, admin_stub, contact_profile_id): assert "Profile not found." in str(excinfo.value) +def test_get_profile_private_key(server_stub, admin_stub, signing_profile_id): + # Get the private key + private_key_response = admin_stub.GetSqueakProfilePrivateKey( + squeak_admin_pb2.GetSqueakProfilePrivateKeyRequest( + profile_id=signing_profile_id, + ) + ) + + print(private_key_response.private_key) + assert len(private_key_response.private_key) > 0 + + def test_get_following_squeaks( server_stub, admin_stub, saved_squeak_hash, signing_profile_id ): diff --git a/proto/squeak_admin.proto b/proto/squeak_admin.proto index 50c28185..794bdc77 100644 --- a/proto/squeak_admin.proto +++ b/proto/squeak_admin.proto @@ -100,6 +100,10 @@ service SqueakAdmin { */ rpc SetSqueakProfileSharing (SetSqueakProfileSharingRequest) returns (SetSqueakProfileSharingReply) {} + /** sqkadmin: `getsqueakprofileprivatekey` + */ + rpc GetSqueakProfilePrivateKey (GetSqueakProfilePrivateKeyRequest) returns (GetSqueakProfilePrivateKeyReply) {} + /** sqkadmin: `deletesqueakprofile` */ rpc DeleteSqueakProfile (DeleteSqueakProfileRequest) returns (DeleteSqueakProfileReply) {} @@ -293,6 +297,16 @@ message SetSqueakProfileSharingRequest { message SetSqueakProfileSharingReply { } +message GetSqueakProfilePrivateKeyRequest { + /// The profile id + int32 profile_id = 1; +} + +message GetSqueakProfilePrivateKeyReply { + /// The private key + string private_key = 1; +} + message DeleteSqueakProfileRequest { /// The profile id int32 profile_id = 1; diff --git a/squeaknode/admin/squeak_admin_server_handler.py b/squeaknode/admin/squeak_admin_server_handler.py index afc637b0..b938b80c 100644 --- a/squeaknode/admin/squeak_admin_server_handler.py +++ b/squeaknode/admin/squeak_admin_server_handler.py @@ -190,6 +190,16 @@ class SqueakAdminServerHandler(object): self.squeak_controller.delete_squeak_profile(profile_id) return squeak_admin_pb2.DeleteSqueakProfileReply() + def handle_get_squeak_profile_private_key(self, request): + profile_id = request.profile_id + logger.info( + "Handle get squeak profile private key for id: {}".format(profile_id)) + private_key = self.squeak_controller.get_squeak_profile_private_key( + profile_id) + return squeak_admin_pb2.GetSqueakProfilePrivateKeyReply( + private_key=private_key + ) + def handle_make_squeak(self, request): profile_id = request.profile_id content_str = request.content diff --git a/squeaknode/admin/squeak_admin_server_servicer.py b/squeaknode/admin/squeak_admin_server_servicer.py index e029c38d..33ae18a8 100644 --- a/squeaknode/admin/squeak_admin_server_servicer.py +++ b/squeaknode/admin/squeak_admin_server_servicer.py @@ -91,6 +91,9 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer): def DeleteSqueakProfile(self, request, context): return self.handler.handle_delete_squeak_profile(request) + def GetSqueakProfilePrivateKey(self, request, context): + return self.handler.handle_get_squeak_profile_private_key(request) + def MakeSqueak(self, request, context): return self.handler.handle_make_squeak(request) diff --git a/squeaknode/admin/webapp/app.py b/squeaknode/admin/webapp/app.py index 49de2a3d..eb46f9d0 100644 --- a/squeaknode/admin/webapp/app.py +++ b/squeaknode/admin/webapp/app.py @@ -433,6 +433,14 @@ def create_app(handler, username, password): handler.handle_get_network, ) + @app.route("/getsqueakprofileprivatekey", methods=["POST"]) + @login_required + def getsqueakprofileprivatekey(): + return handle_request( + squeak_admin_pb2.GetSqueakProfilePrivateKeyRequest(), + handler.handle_get_squeak_profile_private_key, + ) + return app diff --git a/squeaknode/core/squeak_controller.py b/squeaknode/core/squeak_controller.py index ef481a5e..a7e48959 100644 --- a/squeaknode/core/squeak_controller.py +++ b/squeaknode/core/squeak_controller.py @@ -154,6 +154,14 @@ class SqueakController: def delete_squeak_profile(self, profile_id: int): self.squeak_db.delete_profile(profile_id) + def get_squeak_profile_private_key(self, profile_id: int): + profile = self.get_squeak_profile(profile_id) + if profile.private_key is None: + raise Exception("Profile with id: {} does not have a private key.".format( + profile_id + )) + return profile.private_key + def make_squeak(self, profile_id: int, content_str: str, replyto_hash: bytes): squeak_profile = self.squeak_db.get_profile(profile_id) squeak_entry = self.squeak_core.make_squeak(