This is an automated email from the ASF dual-hosted git repository.
apratim pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/incubator-resilientdb-resvault.git
The following commit(s) were added to refs/heads/main by this push:
new 6c6d3f6 Added multi-key support to ResVault
6c6d3f6 is described below
commit 6c6d3f64cbceea36717b169712a51dd61f295317
Author: Apratim Shukla <[email protected]>
AuthorDate: Wed Oct 9 19:20:11 2024 -0700
Added multi-key support to ResVault
- Now supports adding multiple keys to the same ResVault profile.
- Modified GlobalContext to handle state management.
- Selected keys can now be exported using the download button.
---
src/App.js | 35 +-
src/context/GlobalContext.js | 182 +++++++-
src/css/App.css | 24 ++
src/index.js | 5 +-
src/pages/Dashboard.jsx | 978 +++++++++++++++++++++++--------------------
src/pages/Home.jsx | 36 +-
src/pages/Login.jsx | 368 +++++++++-------
src/pages/SignUp.jsx | 81 ++--
8 files changed, 988 insertions(+), 721 deletions(-)
diff --git a/src/App.js b/src/App.js
index 489056e..1223c1f 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,27 +1,34 @@
/*global chrome*/
import './css/App.css';
-import React from 'react';
+import React, { useContext } from 'react';
import Home from "./pages/Home";
import SignUp from "./pages/SignUp";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
import Logs from "./pages/Logs";
-import { Navigate, Route, Routes } from "react-router-dom";
-import { GlobalProvider } from './context/GlobalContext';
+import { Routes, Route, Navigate } from 'react-router-dom';
+import { GlobalContext } from './context/GlobalContext';
function App() {
+ const { isAuthenticated } = useContext(GlobalContext);
+
return (
- <GlobalProvider>
- <Routes>
- <Route path='/' element={<Home />} />
- <Route path='/signup' element={<SignUp />} />
- <Route path='/login' element={<Login />} />
- <Route path='/dashboard' element={<Dashboard />} />
- <Route path='/logs' element={<Logs />} />
- <Route path='*' element={<Navigate to='/' />} />
- </Routes>
- </GlobalProvider>
+ <Routes>
+ {!isAuthenticated ? (
+ <>
+ <Route path="/" element={<Home />} />
+ <Route path="/signup" element={<SignUp />} />
+ <Route path="/login" element={<Login />} />
+ <Route path="*" element={<Navigate to="/" replace />} />
+ </>
+ ) : (
+ <>
+ <Route path="/dashboard" element={<Dashboard />} />
+ <Route path="*" element={<Navigate to="/dashboard" replace />} />
+ </>
+ )}
+ </Routes>
);
}
-export default App;
+export default App;
\ No newline at end of file
diff --git a/src/context/GlobalContext.js b/src/context/GlobalContext.js
index 4c85e55..7a88a7a 100644
--- a/src/context/GlobalContext.js
+++ b/src/context/GlobalContext.js
@@ -1,57 +1,193 @@
/*global chrome*/
import React, { createContext, useState, useEffect } from 'react';
import CryptoJS from 'crypto-js';
+import nacl from 'tweetnacl';
+import Base58 from 'bs58';
export const GlobalContext = createContext();
export const GlobalProvider = ({ children }) => {
const [values, setValues] = useState({ password: '', showPassword: false });
- const [loginValues, setLoginValues] = useState({ password: '', showPassword:
false });
const [confirmValues, setConfirmValues] = useState({ password: '',
showPassword: false });
- const [transactionData, setTransactionData] = useState({});
+ const [loginValues, setLoginValues] = useState({ password: '', showPassword:
false });
const [publicKey, setPublicKey] = useState('');
const [privateKey, setPrivateKey] = useState('');
- const [networks, setNetworks] = useState([]);
+ const [keyPairs, setKeyPairs] = useState([]);
+ const [selectedKeyPairIndex, setSelectedKeyPairIndex] = useState(0);
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const [storedPassword, setStoredPassword] = useState('');
+
+ // Function to encrypt and store key pairs
+ const saveKeyPairsToStorage = (keyPairs, password) => {
+ const encryptedKeyPairs = CryptoJS.AES.encrypt(
+ JSON.stringify(keyPairs),
+ password
+ ).toString();
+ chrome.storage.sync.set({ encryptedKeyPairs }, () => {
+ console.log('Encrypted key pairs saved to storage.');
+ });
+ };
+
+ // Function to load and decrypt key pairs
+ const loadKeyPairsFromStorage = (password, callback) => {
+ chrome.storage.sync.get(['encryptedKeyPairs'], (result) => {
+ if (result.encryptedKeyPairs) {
+ try {
+ const bytes = CryptoJS.AES.decrypt(result.encryptedKeyPairs,
password);
+ const decryptedData = bytes.toString(CryptoJS.enc.Utf8);
+ const decryptedKeyPairs = JSON.parse(decryptedData);
+ callback(decryptedKeyPairs);
+ } catch (err) {
+ console.error('Error decrypting key pairs:', err);
+ callback([]);
+ }
+ } else {
+ callback([]);
+ }
+ });
+ };
+
+ // Function to save selected key pair index
+ const saveSelectedKeyPairIndex = (index) => {
+ chrome.storage.local.set({ selectedKeyPairIndex: index }, () => {});
+ };
+
+ // Function to load selected key pair index
+ const loadSelectedKeyPairIndex = (callback) => {
+ chrome.storage.local.get(['selectedKeyPairIndex'], (result) => {
+ const index = result.selectedKeyPairIndex !== undefined ?
result.selectedKeyPairIndex : 0;
+ callback(index);
+ });
+ };
+
+ // Function to generate new key pair and store it
+ const generateKeyPair = (callback) => {
+ const password = storedPassword;
+ if (!password) {
+ console.error('Password is not available');
+ return;
+ }
+ const keyPair = nacl.sign.keyPair();
+ const newPublicKey = Base58.encode(keyPair.publicKey);
+ const newPrivateKey = Base58.encode(keyPair.secretKey.slice(0, 32));
+ const newKeyPair = { publicKey: newPublicKey, privateKey: newPrivateKey };
+
+ // Load existing key pairs
+ loadKeyPairsFromStorage(password, (existingKeyPairs) => {
+ const updatedKeyPairs = [...existingKeyPairs, newKeyPair];
+ // Save updated key pairs
+ saveKeyPairsToStorage(updatedKeyPairs, password);
+ setKeyPairs(updatedKeyPairs);
+ setPublicKey(newPublicKey);
+ setPrivateKey(newPrivateKey);
+ const newIndex = updatedKeyPairs.length - 1;
+ setSelectedKeyPairIndex(newIndex);
+ saveSelectedKeyPairIndex(newIndex);
+ setIsAuthenticated(true);
+
+ // Encrypt the private key and save in 'store'
+ const encryptedPrivateKey = CryptoJS.AES.encrypt(newPrivateKey,
password).toString();
+ const hash = CryptoJS.SHA256(password).toString(CryptoJS.enc.Hex);
+ const store = {
+ hash,
+ publicKey: newPublicKey,
+ encryptedPrivateKey: encryptedPrivateKey,
+ history: [],
+ };
+ chrome.storage.sync.set({ store }, () => {
+ console.log('Store updated with new key pair');
+ if (callback) {
+ callback();
+ }
+ });
+ });
+ };
+
+ // Load key pairs from storage when context is initialized
useEffect(() => {
- // Retrieve publicKey and encryptedPrivateKey from storage on
initialization
- chrome.storage.sync.get('store', (data) => {
- if (data.store && data.store.publicKey) {
- setPublicKey(data.store.publicKey);
- chrome.storage.local.get('password', (passwordData) => {
- if (passwordData.password) {
- const decryptedPrivateKey = CryptoJS.AES.decrypt(
- data.store.encryptedPrivateKey,
- passwordData.password
- ).toString(CryptoJS.enc.Utf8);
- setPrivateKey(JSON.parse(decryptedPrivateKey));
+ // Retrieve password from storage
+ chrome.storage.local.get(['password'], (result) => {
+ const password = result.password;
+ if (password) {
+ setStoredPassword(password);
+ loadKeyPairsFromStorage(password, (loadedKeyPairs) => {
+ if (loadedKeyPairs.length > 0) {
+ setKeyPairs(loadedKeyPairs);
+ // Load selected key pair index
+ loadSelectedKeyPairIndex((index) => {
+ if (loadedKeyPairs[index]) {
+ setPublicKey(loadedKeyPairs[index].publicKey);
+ setPrivateKey(loadedKeyPairs[index].privateKey);
+ setSelectedKeyPairIndex(index);
+ } else if (loadedKeyPairs.length > 0) {
+ setPublicKey(loadedKeyPairs[0].publicKey);
+ setPrivateKey(loadedKeyPairs[0].privateKey);
+ setSelectedKeyPairIndex(0);
+ }
+ setIsAuthenticated(true);
+ });
+ } else {
+ setIsAuthenticated(false);
}
});
+ } else {
+ setIsAuthenticated(false);
}
});
}, []);
- const updateTransaction = (data) => setTransactionData(data);
- const clearData = () => setTransactionData({});
+ // Function to set selected key pair
+ const setSelectedKeyPair = (index) => {
+ if (keyPairs[index]) {
+ setPublicKey(keyPairs[index].publicKey);
+ setPrivateKey(keyPairs[index].privateKey);
+ setSelectedKeyPairIndex(index);
+ saveSelectedKeyPairIndex(index);
+
+ const password = storedPassword;
+ if (!password) {
+ console.error('Password is not available');
+ return;
+ }
+ // Encrypt the private key and save in 'store'
+ const encryptedPrivateKey =
CryptoJS.AES.encrypt(keyPairs[index].privateKey, password).toString();
+ const hash = CryptoJS.SHA256(password).toString(CryptoJS.enc.Hex);
+ const store = {
+ hash,
+ publicKey: keyPairs[index].publicKey,
+ encryptedPrivateKey: encryptedPrivateKey,
+ history: [],
+ };
+ chrome.storage.sync.set({ store }, () => {
+ console.log('Store updated with selected key pair');
+ });
+ }
+ };
return (
<GlobalContext.Provider
value={{
values,
setValues,
- loginValues,
- setLoginValues,
confirmValues,
setConfirmValues,
- transactionData,
- updateTransaction,
- clearData,
+ loginValues,
+ setLoginValues,
publicKey,
setPublicKey,
privateKey,
setPrivateKey,
- networks,
- setNetworks,
+ keyPairs,
+ setKeyPairs,
+ generateKeyPair,
+ selectedKeyPairIndex,
+ setSelectedKeyPairIndex,
+ setSelectedKeyPair,
+ isAuthenticated,
+ setIsAuthenticated,
+ storedPassword,
+ setStoredPassword,
}}
>
{children}
diff --git a/src/css/App.css b/src/css/App.css
index ca2f49b..1ab679e 100644
--- a/src/css/App.css
+++ b/src/css/App.css
@@ -3683,3 +3683,27 @@ tr:hover {
justify-content: flex-end;
}
+.keypair-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.keypair-icons {
+ display: flex;
+ align-items: center;
+}
+
+.icon-button {
+ background: none;
+ border: none;
+ outline: none;
+ cursor: pointer;
+ padding: 0 5px;
+}
+
+.icon-button:hover {
+ opacity: 0.7;
+}
+
+
diff --git a/src/index.js b/src/index.js
index 9f69574..efcfd5b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -25,6 +25,7 @@ import reportWebVitals from './reportWebVitals';
import { transitions, positions, Provider as AlertProvider } from
'react-alert';
import AlertTemplate from 'react-alert-template-basic';
import { MemoryRouter } from "react-router-dom";
+import { GlobalProvider } from './context/GlobalContext';
// optional configuration
const options = {
@@ -41,7 +42,9 @@ root.render(
<React.StrictMode>
<MemoryRouter>
<AlertProvider template={AlertTemplate} {...options}>
- <App />
+ <GlobalProvider>
+ <App />
+ </GlobalProvider>
</AlertProvider>
</MemoryRouter>
</React.StrictMode>
diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx
index ce52a3c..10cafc2 100644
--- a/src/pages/Dashboard.jsx
+++ b/src/pages/Dashboard.jsx
@@ -1,25 +1,28 @@
/**
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements. See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership. The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
-* OF ANY KIND, either express or implied. See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ * OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
/*global chrome*/
import '../css/App.css';
import ExitToAppIcon from '@mui/icons-material/ExitToApp';
+import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import DownloadIcon from '@mui/icons-material/Download';
import React, { useRef, useState, useEffect, useContext } from 'react';
import Lottie from 'react-lottie';
import versionData from '../data/version.json';
@@ -30,7 +33,19 @@ import { GlobalContext } from '../context/GlobalContext';
import { useNavigate } from 'react-router-dom';
function Dashboard() {
- const { publicKey, privateKey } = useContext(GlobalContext);
+ const {
+ publicKey,
+ privateKey,
+ keyPairs,
+ generateKeyPair,
+ setPublicKey,
+ setPrivateKey,
+ selectedKeyPairIndex,
+ setSelectedKeyPairIndex,
+ setSelectedKeyPair,
+ setIsAuthenticated,
+ } = useContext(GlobalContext);
+
const [tabId, setTabId] = useState(null);
const [domain, setDomain] = useState('');
const [selectedNet, setSelectedNet] = useState('');
@@ -46,7 +61,6 @@ function Dashboard() {
const [error, setError] = useState('');
const [jsonFileName, setJsonFileName] = useState('');
const fileInputRef = useRef(null);
- const inputRef = useRef(null);
const navigate = useNavigate();
// New state variables for transaction data and handling
@@ -58,18 +72,13 @@ function Dashboard() {
// New state for copying transaction ID
const [isIdCopied, setIsIdCopied] = useState(false);
- useEffect(() => {
- console.log('publicKey in Dashboard:', publicKey);
- console.log('privateKey in Dashboard:', privateKey);
- }, [publicKey, privateKey]);
-
const defaultOptions = {
loop: true,
autoplay: true,
animationData: require('../images/bg.json'),
rendererSettings: {
- preserveAspectRatio: 'xMidYMid slice',
- renderer: 'svg'
+ preserveAspectRatio: 'xMidYMid slice',
+ renderer: 'svg'
}
};
@@ -78,83 +87,83 @@ function Dashboard() {
autoplay: true,
animationData: require('../images/modal.json'),
rendererSettings: {
- preserveAspectRatio: 'xMidYMid slice',
- renderer: 'svg'
+ preserveAspectRatio: 'xMidYMid slice',
+ renderer: 'svg'
}
};
// Function to get full hostname from URL
function getBaseDomain(url) {
try {
- const urlObj = new URL(url);
- return urlObj.hostname;
+ const urlObj = new URL(url);
+ return urlObj.hostname;
} catch (error) {
- console.error('Invalid URL:', url);
- return '';
+ console.error('Invalid URL:', url);
+ return '';
}
}
// Function to set connection status per domain and net
const setDomainConnectionStatus = (domain, net, isConnected) => {
chrome.storage.local.get(['connections'], (result) => {
- let connections = result.connections || {};
- if (!connections[domain]) {
- connections[domain] = {};
- }
- connections[domain][net] = isConnected;
- chrome.storage.local.set({ connections });
+ let connections = result.connections || {};
+ if (!connections[domain]) {
+ connections[domain] = {};
+ }
+ connections[domain][net] = isConnected;
+ chrome.storage.local.set({ connections });
});
};
// Function to get connection status per domain and net
const getDomainConnectionStatus = (domain, net, callback) => {
chrome.storage.local.get(['connections'], (result) => {
- const connections = result.connections || {};
- const domainConnections = connections[domain] || {};
- const isConnected = domainConnections[net] || false;
- callback(isConnected);
+ const connections = result.connections || {};
+ const domainConnections = connections[domain] || {};
+ const isConnected = domainConnections[net] || false;
+ callback(isConnected);
});
};
useEffect(() => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
- if (tabs.length > 0 && tabs[0].url) {
- const currentTab = tabs[0];
- setTabId(currentTab.id);
-
- const currentDomain = getBaseDomain(currentTab.url);
- setDomain(currentDomain);
-
- // Ensure selectedNet is set before checking connection status
- if (selectedNet) {
- getDomainConnectionStatus(currentDomain, selectedNet,
(connected) => {
- setIsConnected(connected);
- });
- }
- } else {
- // If no active tab or no URL, set domain to 'extension'
- setDomain('extension');
-
- // Ensure selectedNet is set before checking connection status
- if (selectedNet) {
- getDomainConnectionStatus('extension', selectedNet,
(connected) => {
- setIsConnected(connected);
- });
- }
+ if (tabs.length > 0 && tabs[0].url) {
+ const currentTab = tabs[0];
+ setTabId(currentTab.id);
+
+ const currentDomain = getBaseDomain(currentTab.url);
+ setDomain(currentDomain);
+
+ // Ensure selectedNet is set before checking connection status
+ if (selectedNet) {
+ getDomainConnectionStatus(currentDomain, selectedNet, (connected)
=> {
+ setIsConnected(connected);
+ });
+ }
+ } else {
+ // If no active tab or no URL, set domain to 'extension'
+ setDomain('extension');
+
+ // Ensure selectedNet is set before checking connection status
+ if (selectedNet) {
+ getDomainConnectionStatus('extension', selectedNet, (connected) =>
{
+ setIsConnected(connected);
+ });
}
+ }
});
}, [selectedNet]);
useEffect(() => {
if (domain && selectedNet) {
- chrome.runtime.sendMessage({ action: "requestData", domain:
domain, net: selectedNet }, function(response) {
- if (response && response.faviconUrl) {
- setFaviconUrl(response.faviconUrl);
- } else {
- setFaviconUrl('');
- }
- // Handle other response data if needed
- });
+ chrome.runtime.sendMessage({ action: "requestData", domain: domain,
net: selectedNet }, function(response) {
+ if (response && response.faviconUrl) {
+ setFaviconUrl(response.faviconUrl);
+ } else {
+ setFaviconUrl('');
+ }
+ // Handle other response data if needed
+ });
}
}, [domain, selectedNet]);
@@ -166,38 +175,38 @@ function Dashboard() {
useEffect(() => {
// Fetch active net URL from storage
chrome.storage.local.get(['activeNetUrl'], (result) => {
- if (result.activeNetUrl) {
- setCompleteUrl(result.activeNetUrl); // Use the full URL with
protocol
-
- // Check if it's one of the known networks
- if (result.activeNetUrl ===
'https://cloud.resilientdb.com/graphql') {
- setSelectedNet('ResilientDB Mainnet');
- } else if (result.activeNetUrl ===
'http://localhost:8000/graphql') {
- setSelectedNet('ResilientDB Localnet');
- } else {
- // Custom URL case
- const customNet = nets.find(net => net.url ===
result.activeNetUrl);
- if (customNet) {
- setSelectedNet(customNet.name); // Set the name of the
custom network
- } else {
- setSelectedNet('Custom URL');
- }
- // Set customUrl by stripping the protocol and /graphql
part
-
setCustomUrl(result.activeNetUrl.replace(/^https?:\/\/|\/graphql$/g, ''));
- }
+ if (result.activeNetUrl) {
+ setCompleteUrl(result.activeNetUrl); // Use the full URL with
protocol
+
+ // Check if it's one of the known networks
+ if (result.activeNetUrl ===
'https://cloud.resilientdb.com/graphql') {
+ setSelectedNet('ResilientDB Mainnet');
+ } else if (result.activeNetUrl ===
'http://localhost:8000/graphql') {
+ setSelectedNet('ResilientDB Localnet');
+ } else {
+ // Custom URL case
+ const customNet = nets.find(net => net.url ===
result.activeNetUrl);
+ if (customNet) {
+ setSelectedNet(customNet.name); // Set the name of the custom
network
} else {
- // No active net URL, default to ResilientDB Mainnet
- setCompleteUrl('https://cloud.resilientdb.com/graphql');
- setSelectedNet('ResilientDB Mainnet'); // Ensure default
network is selected
+ setSelectedNet('Custom URL');
}
+ // Set customUrl by stripping the protocol and /graphql part
+
setCustomUrl(result.activeNetUrl.replace(/^https?:\/\/|\/graphql$/g, ''));
+ }
+ } else {
+ // No active net URL, default to ResilientDB Mainnet
+ setCompleteUrl('https://cloud.resilientdb.com/graphql');
+ setSelectedNet('ResilientDB Mainnet'); // Ensure default network
is selected
+ }
});
}, [nets]);
useEffect(() => {
if (domain && selectedNet) {
- getDomainConnectionStatus(domain, selectedNet, (connected) => {
- setIsConnected(connected);
- });
+ getDomainConnectionStatus(domain, selectedNet, (connected) => {
+ setIsConnected(connected);
+ });
}
}, [domain, selectedNet]);
@@ -205,14 +214,14 @@ function Dashboard() {
const newProtocol = protocol === 'http' ? 'https' : 'http';
setProtocol(newProtocol);
if (selectedNet === 'Custom URL' && customUrl) {
- setCompleteUrl(`${newProtocol}://${customUrl}/graphql`);
+ setCompleteUrl(`${newProtocol}://${customUrl}/graphql`);
}
};
const addNet = () => {
if (!newNetName.trim() || !customUrl.trim()) {
- setError('Both fields are required.');
- return;
+ setError('Both fields are required.');
+ return;
}
setError('');
const fullUrl = `${protocol}://${customUrl}/graphql`;
@@ -232,77 +241,77 @@ function Dashboard() {
const toggleConnection = () => {
if (!publicKey || !privateKey) {
- console.error('Public or Private key is missing');
- return;
+ console.error('Public or Private key is missing');
+ return;
}
-
+
const newConnectionStatus = !isConnected;
setIsConnected(newConnectionStatus);
-
+
if (newConnectionStatus) {
- console.log('Connecting to net:', selectedNet, 'on domain:', domain);
- chrome.runtime.sendMessage(
+ console.log('Connecting to net:', selectedNet, 'on domain:', domain);
+ chrome.runtime.sendMessage(
{
- action: 'storeKeys',
- publicKey,
- privateKey,
- url: completeUrl,
- domain: domain,
- net: selectedNet,
+ action: 'storeKeys',
+ publicKey,
+ privateKey,
+ url: completeUrl,
+ domain: domain,
+ net: selectedNet,
},
() => {
- setDomainConnectionStatus(domain, selectedNet,
newConnectionStatus);
+ setDomainConnectionStatus(domain, selectedNet,
newConnectionStatus);
}
- );
+ );
} else {
- console.log('Disconnecting from net:', selectedNet, 'on domain:',
domain);
- chrome.runtime.sendMessage(
+ console.log('Disconnecting from net:', selectedNet, 'on domain:',
domain);
+ chrome.runtime.sendMessage(
{
- action: 'disconnectKeys',
- domain: domain,
- net: selectedNet,
+ action: 'disconnectKeys',
+ domain: domain,
+ net: selectedNet,
},
() => {
- setDomainConnectionStatus(domain, selectedNet, false);
+ setDomainConnectionStatus(domain, selectedNet, false);
}
- );
+ );
}
};
const switchNetwork = (value) => {
if (value === 'Manage Nets') {
- setShowModal(true);
+ setShowModal(true);
} else {
- let newCompleteUrl = '';
- switch (value) {
- case 'ResilientDB Mainnet':
- newCompleteUrl = 'https://cloud.resilientdb.com/graphql';
- break;
- case 'ResilientDB Localnet':
- newCompleteUrl = 'http://localhost:8000/graphql';
- break;
- case 'Custom URL':
- if (customUrl) {
- newCompleteUrl = `${protocol}://${customUrl}/graphql`;
- } else {
- newCompleteUrl = `${protocol}://`;
- }
- break;
- default:
- const selectedNetwork = nets.find(net => net.name ===
value);
- if (selectedNetwork) {
- newCompleteUrl = selectedNetwork.url;
-
setCustomUrl(selectedNetwork.url.split('://')[1].replace('/graphql', ''));
- }
- break;
+ let newCompleteUrl = '';
+ switch (value) {
+ case 'ResilientDB Mainnet':
+ newCompleteUrl = 'https://cloud.resilientdb.com/graphql';
+ break;
+ case 'ResilientDB Localnet':
+ newCompleteUrl = 'http://localhost:8000/graphql';
+ break;
+ case 'Custom URL':
+ if (customUrl) {
+ newCompleteUrl = `${protocol}://${customUrl}/graphql`;
+ } else {
+ newCompleteUrl = `${protocol}://`;
+ }
+ break;
+ default:
+ const selectedNetwork = nets.find(net => net.name === value);
+ if (selectedNetwork) {
+ newCompleteUrl = selectedNetwork.url;
+
setCustomUrl(selectedNetwork.url.split('://')[1].replace('/graphql', ''));
}
- setCompleteUrl(newCompleteUrl);
- setSelectedNet(value); // Set this at the end to avoid premature
updates
+ break;
+ }
+ setCompleteUrl(newCompleteUrl);
+ setSelectedNet(value); // Set this at the end to avoid premature
updates
- // Update activeNetUrl in storage using the new URL
- chrome.storage.local.set({ activeNetUrl: newCompleteUrl }, () => {
- console.log('Active net URL updated to', newCompleteUrl);
- });
+ // Update activeNetUrl in storage using the new URL
+ chrome.storage.local.set({ activeNetUrl: newCompleteUrl }, () => {
+ console.log('Active net URL updated to', newCompleteUrl);
+ });
}
};
@@ -311,21 +320,21 @@ function Dashboard() {
if (value === selectedNet) return; // Ignore if the value hasn't
changed
if (isConnected) {
- // Disconnect from the current network and clear domain connection
status
- chrome.runtime.sendMessage({
- action: 'disconnectKeys',
- domain: domain,
- net: selectedNet
- }, () => {
- console.log('Disconnected from previous network');
- setIsConnected(false);
- setDomainConnectionStatus(domain, selectedNet, false);
- // Proceed with switching networks
- switchNetwork(value);
- });
- } else {
- // If not connected, just switch networks
+ // Disconnect from the current network and clear domain connection
status
+ chrome.runtime.sendMessage({
+ action: 'disconnectKeys',
+ domain: domain,
+ net: selectedNet
+ }, () => {
+ console.log('Disconnected from previous network');
+ setIsConnected(false);
+ setDomainConnectionStatus(domain, selectedNet, false);
+ // Proceed with switching networks
switchNetwork(value);
+ });
+ } else {
+ // If not connected, just switch networks
+ switchNetwork(value);
}
};
@@ -337,61 +346,73 @@ function Dashboard() {
};
const back = () => {
- chrome.storage.local.clear(function() {
- navigate("/login");
+ chrome.storage.local.remove(['password'], function() {
+ setIsAuthenticated(false);
+ navigate("/login");
});
};
- const originalAccountId = publicKey || '';
- const shortenedId = originalAccountId
- ? `${originalAccountId.slice(0, 5)}...${originalAccountId.slice(-5)}`
- : '';
-
- const handleInputClick = () => {
+ // Function to copy public key
+ const handleCopyPublicKey = () => {
try {
- const tempInput = document.createElement('input');
- tempInput.value = originalAccountId;
- document.body.appendChild(tempInput);
- tempInput.select();
- document.execCommand('copy');
- document.body.removeChild(tempInput);
- setIsCopied(true);
- setTimeout(() => {
- setIsCopied(false);
- }, 1500);
+ const tempInput = document.createElement('input');
+ tempInput.value = publicKey;
+ document.body.appendChild(tempInput);
+ tempInput.select();
+ document.execCommand('copy');
+ document.body.removeChild(tempInput);
+ setIsCopied(true);
+ setTimeout(() => {
+ setIsCopied(false);
+ }, 1500);
} catch (err) {
- console.error('Unable to copy text: ', err);
+ console.error('Unable to copy text: ', err);
}
};
+ // Function to download key pair as JSON
+ const handleDownloadKeyPair = () => {
+ const keyPair = {
+ publicKey: publicKey,
+ privateKey: privateKey,
+ };
+ const dataStr = "data:text/json;charset=utf-8," +
encodeURIComponent(JSON.stringify(keyPair));
+ const downloadAnchorNode = document.createElement('a');
+ downloadAnchorNode.setAttribute("href", dataStr);
+ downloadAnchorNode.setAttribute("download", "keypair.json");
+ document.body.appendChild(downloadAnchorNode); // required for firefox
+ downloadAnchorNode.click();
+ downloadAnchorNode.remove();
+ };
+
const handleFileUpload = (e) => {
const file = e.target.files[0];
if (file && file.type === 'application/json') {
- setJsonFileName(file.name); // Show file name once uploaded
-
- const reader = new FileReader();
- reader.onload = (event) => {
- try {
- const json = JSON.parse(event.target.result);
- // Validate JSON data
- if (json.asset && json.recipientAddress && json.amount) {
- setTransactionData(json);
- setTransactionError(''); // Clear any previous error
- } else {
- setTransactionData(null);
- setTransactionError('Invalid JSON format: Missing
required fields.');
- }
- } catch (err) {
- console.error('Error parsing JSON:', err);
- setTransactionData(null);
- setTransactionError('Invalid JSON format.');
- }
- };
- reader.readAsText(file);
- } else {
- setJsonFileName(''); // Clear if the file is not JSON
+ setJsonFileName(file.name); // Show file name once uploaded
+
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ try {
+ const json = JSON.parse(event.target.result);
+ // Validate JSON data
+ if (json.asset && json.recipientAddress && json.amount) {
+ setTransactionData(json);
+ setTransactionError(''); // Clear any previous error
+ } else {
+ setTransactionData(null);
+ setTransactionError('Invalid JSON format: Missing required
fields.');
+ }
+ } catch (err) {
+ console.error('Error parsing JSON:', err);
setTransactionData(null);
- setTransactionError('Please upload a JSON file.');
+ setTransactionError('Invalid JSON format.');
+ }
+ };
+ reader.readAsText(file);
+ } else {
+ setJsonFileName(''); // Clear if the file is not JSON
+ setTransactionData(null);
+ setTransactionError('Please upload a JSON file.');
}
};
@@ -410,31 +431,31 @@ function Dashboard() {
e.stopPropagation();
const file = e.dataTransfer.files[0];
if (file && file.type === 'application/json') {
- setJsonFileName(file.name);
-
- const reader = new FileReader();
- reader.onload = (event) => {
- try {
- const json = JSON.parse(event.target.result);
- // Validate JSON data
- if (json.asset && json.recipientAddress && json.amount) {
- setTransactionData(json);
- setTransactionError(''); // Clear any previous error
- } else {
- setTransactionData(null);
- setTransactionError('Invalid JSON format: Missing
required fields.');
- }
- } catch (err) {
- console.error('Error parsing JSON:', err);
- setTransactionData(null);
- setTransactionError('Invalid JSON format.');
- }
- };
- reader.readAsText(file);
- } else {
- setJsonFileName('');
+ setJsonFileName(file.name);
+
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ try {
+ const json = JSON.parse(event.target.result);
+ // Validate JSON data
+ if (json.asset && json.recipientAddress && json.amount) {
+ setTransactionData(json);
+ setTransactionError(''); // Clear any previous error
+ } else {
+ setTransactionData(null);
+ setTransactionError('Invalid JSON format: Missing required
fields.');
+ }
+ } catch (err) {
+ console.error('Error parsing JSON:', err);
setTransactionData(null);
- setTransactionError('Please upload a JSON file.');
+ setTransactionError('Invalid JSON format.');
+ }
+ };
+ reader.readAsText(file);
+ } else {
+ setJsonFileName('');
+ setTransactionData(null);
+ setTransactionError('Please upload a JSON file.');
}
};
@@ -444,49 +465,49 @@ function Dashboard() {
const handleSubmit = () => {
if (!transactionData) {
- setTransactionError('No valid transaction data found.');
- return;
+ setTransactionError('No valid transaction data found.');
+ return;
}
if (!isConnected) {
- setTransactionError('Please connect to a net before submitting a
transaction.');
- return;
+ setTransactionError('Please connect to a net before submitting a
transaction.');
+ return;
}
// Send transaction data to background script
chrome.runtime.sendMessage({
- action: 'submitTransactionFromDashboard',
- transactionData: transactionData,
- domain: domain,
- net: selectedNet,
+ action: 'submitTransactionFromDashboard',
+ transactionData: transactionData,
+ domain: domain,
+ net: selectedNet,
}, (response) => {
- if (response.success) {
- setSuccessResponse(response.data);
- setShowSuccessModal(true);
- setTransactionError('');
- setJsonFileName(''); // Clear the file name after successful
submission
- setTransactionData(null);
- } else {
- setTransactionError(response.error || 'Transaction submission
failed.');
- }
+ if (response.success) {
+ setSuccessResponse(response.data);
+ setShowSuccessModal(true);
+ setTransactionError('');
+ setJsonFileName(''); // Clear the file name after successful
submission
+ setTransactionData(null);
+ } else {
+ setTransactionError(response.error || 'Transaction submission
failed.');
+ }
});
};
// New function to handle transaction ID click
const handleIdClick = () => {
try {
- const transactionId = (successResponse &&
successResponse.postTransaction && successResponse.postTransaction.id) || '';
- const tempInput = document.createElement('input');
- tempInput.value = transactionId;
- document.body.appendChild(tempInput);
- tempInput.select();
- document.execCommand('copy');
- document.body.removeChild(tempInput);
- setIsIdCopied(true);
- setTimeout(() => {
- setIsIdCopied(false);
- }, 1500);
+ const transactionId = (successResponse &&
successResponse.postTransaction && successResponse.postTransaction.id) || '';
+ const tempInput = document.createElement('input');
+ tempInput.value = transactionId;
+ document.body.appendChild(tempInput);
+ tempInput.select();
+ document.execCommand('copy');
+ document.body.removeChild(tempInput);
+ setIsIdCopied(true);
+ setTimeout(() => {
+ setIsIdCopied(false);
+ }, 1500);
} catch (err) {
- console.error('Unable to copy text: ', err);
+ console.error('Unable to copy text: ', err);
}
};
@@ -495,214 +516,259 @@ function Dashboard() {
setFaviconUrl(''); // This will trigger the globe icon to display
};
+ const disconnectDueToKeysChange = () => {
+ if (isConnected) {
+ // Disconnect from the current network and clear domain connection
status
+ chrome.runtime.sendMessage({
+ action: 'disconnectKeys',
+ domain: domain,
+ net: selectedNet
+ }, () => {
+ console.log('Disconnected from previous network');
+ setIsConnected(false);
+ setDomainConnectionStatus(domain, selectedNet, false);
+ });
+ }
+ }
+
+ const switchKeyPair = (index) => {
+ setSelectedKeyPair(index);
+ disconnectDueToKeysChange();
+ };
+
+ const handleGenerateKeyPair = () => {
+ generateKeyPair(() => {
+ setSelectedKeyPairIndex(keyPairs.length); // Select the newly
generated key pair
+ disconnectDueToKeysChange();
+ });
+ };
+
+
return (
<>
- <div className="lottie-background">
- <Lottie options={defaultOptions} height="100%" width="100%" />
+ <div className="lottie-background">
+ <Lottie options={defaultOptions} height="100%" width="100%" />
+ </div>
+ <div className="page page--main" data-page="buy">
+ <header className="header header--fixed">
+ <div className="header__inner header-container">
+ <div className="header__logo header__logo--text">
+ Res<strong>Vault</strong>
+ </div>
+ <div className="badge-container">
+ <span className="badge">KV Service</span>
+ </div>
+ <div className="header__icon open-panel">
+ <button
+ style={{ background: 'none', color: 'white', fontWeight:
'bolder', outline: 'none', borderStyle: 'none', cursor: 'pointer' }}
+ onClick={back}
+ >
+ <ExitToAppIcon />
+ </button>
+ </div>
</div>
- <div className="page page--main" data-page="buy">
- <header className="header header--fixed">
- <div className="header__inner header-container">
- <div className="header__logo header__logo--text">
- Res<strong>Vault</strong>
- </div>
- <div className="badge-container">
- <span className="badge">KV Service</span>
- </div>
- <div className="header__icon open-panel">
- <button
- style={{ background: 'none', color: 'white',
fontWeight: 'bolder', outline: 'none', borderStyle: 'none', cursor: 'pointer' }}
- onClick={back}
- >
- <ExitToAppIcon />
- </button>
- </div>
- </div>
- </header>
-
- {showModal && (
- <div className="overlay">
- <div className="modal">
- <div className="lottie-modal-background">
- <Lottie options={modalOptions} height="100%"
width="100%" />
- </div>
- <div className="modal-content">
- <h2>Manage Nets</h2>
- {nets.length > 0 && (
- <div className="table-container">
- <table>
- <thead>
- <tr>
- <th>Net Name</th>
- <th>Action</th>
- </tr>
- </thead>
- <tbody>
- {nets.map(net => (
- <tr key={net.name}>
- <td>{net.name}</td>
- <td>
- <button
className="icon-button" onClick={() => removeNet(net.name)}>
- <i
className="fas fa-trash"></i>
- </button>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- )}
- <input className="modal-net-input" type="text"
placeholder="Net Name" value={newNetName} onChange={e =>
setNewNetName(e.target.value)} />
- <div className="modal-url-input-container">
- <div className="modal-url-toggle"
onClick={toggleProtocol}>
- <FontAwesomeIcon icon={protocol ===
'https' ? faLock : faUnlock} className={`icon ${protocol === 'https' ?
'icon-green' : 'icon-red'}`} />
- </div>
- <input type="text" placeholder="GraphQL
URL" value={customUrl} onChange={handleCustomUrlChange}
className="modal-url-input" />
- <div className="modal-url-fixed">
- <img src={graphql}
className="graphql-icon" alt="GraphQL"></img>
- </div>
- </div>
- <div className="save-container">
- <button onClick={addNet}
className="button-save">
- Save
- </button>
- <button onClick={() =>
setShowModal(false)} className="button-close">
- Close
- </button>
- </div>
- {error && <p
className="error-message">{error}</p>}
- </div>
- </div>
- </div>
- )}
+ </header>
- {showSuccessModal && (
- <div className="overlay">
- <div className="modal">
- <div className="modal-content">
- <h2>Transaction Submitted Successfully!</h2>
- {/* Extract transaction ID */}
- {successResponse &&
successResponse.postTransaction && successResponse.postTransaction.id ? (
- <div className="fieldset">
- <div className="radio-option
radio-option--full">
- <input
- type="radio"
- name="transactionId"
- id="txId"
-
value={successResponse.postTransaction.id}
- checked
- readOnly
- onClick={handleIdClick}
- />
- <label htmlFor="txId">
- <span>{isIdCopied ? 'Copied' :
`${successResponse.postTransaction.id.slice(0,
5)}...${successResponse.postTransaction.id.slice(-5)}`}</span>
- </label>
- </div>
- </div>
- ) : (
- <p>No transaction ID found.</p>
- )}
- <button onClick={() =>
setShowSuccessModal(false)} className="button-close">
- Close
+ {showModal && (
+ <div className="overlay">
+ <div className="modal">
+ <div className="lottie-modal-background">
+ <Lottie options={modalOptions} height="100%" width="100%"
/>
+ </div>
+ <div className="modal-content">
+ <h2>Manage Nets</h2>
+ {nets.length > 0 && (
+ <div className="table-container">
+ <table>
+ <thead>
+ <tr>
+ <th>Net Name</th>
+ <th>Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ {nets.map(net => (
+ <tr key={net.name}>
+ <td>{net.name}</td>
+ <td>
+ <button className="icon-button" onClick={() =>
removeNet(net.name)}>
+ <i className="fas fa-trash"></i>
</button>
- </div>
- </div>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ )}
+ <input className="modal-net-input" type="text"
placeholder="Net Name" value={newNetName} onChange={e =>
setNewNetName(e.target.value)} />
+ <div className="modal-url-input-container">
+ <div className="modal-url-toggle" onClick={toggleProtocol}>
+ <FontAwesomeIcon icon={protocol === 'https' ? faLock :
faUnlock} className={`icon ${protocol === 'https' ? 'icon-green' :
'icon-red'}`} />
+ </div>
+ <input type="text" placeholder="GraphQL URL"
value={customUrl} onChange={handleCustomUrlChange} className="modal-url-input"
/>
+ <div className="modal-url-fixed">
+ <img src={graphql} className="graphql-icon"
alt="GraphQL"></img>
+ </div>
+ </div>
+ <div className="save-container">
+ <button onClick={addNet} className="button-save">
+ Save
+ </button>
+ <button onClick={() => setShowModal(false)}
className="button-close">
+ Close
+ </button>
+ </div>
+ {error && <p className="error-message">{error}</p>}
+ </div>
+ </div>
+ </div>
+ )}
+
+ {showSuccessModal && (
+ <div className="overlay">
+ <div className="modal">
+ <div className="modal-content">
+ <h2>Transaction Submitted Successfully!</h2>
+ {/* Extract transaction ID */}
+ {successResponse && successResponse.postTransaction &&
successResponse.postTransaction.id ? (
+ <div className="fieldset">
+ <div className="radio-option radio-option--full">
+ <input
+ type="radio"
+ name="transactionId"
+ id="txId"
+ value={successResponse.postTransaction.id}
+ checked
+ readOnly
+ onClick={handleIdClick}
+ />
+ <label htmlFor="txId">
+ <span>{isIdCopied ? 'Copied' :
`${successResponse.postTransaction.id.slice(0,
5)}...${successResponse.postTransaction.id.slice(-5)}`}</span>
+ </label>
</div>
+ </div>
+ ) : (
+ <p>No transaction ID found.</p>
+ )}
+ <button onClick={() => setShowSuccessModal(false)}
className="button-close">
+ Close
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
+
+ <div className="page__content page__content--with-header
page__content--with-bottom-nav">
+ <h2 className="page__title">Dashboard</h2>
+
+ <div className="net">
+ <div className="net-header">
+ <div className="select-wrapper">
+ <select value={selectedNet} onChange={handleNetworkChange}
className="select">
+ <option value="ResilientDB Mainnet">ResilientDB
Mainnet</option>
+ <option value="ResilientDB Localnet">ResilientDB
Localnet</option>
+ {nets.map(net => (
+ <option key={net.name}
value={net.name}>{net.name}</option>
+ ))}
+ <option value="Manage Nets">Manage Nets</option>
+ </select>
+ <i className="fas fa-chevron-down"></i>
+ </div>
+
+ <div className="icon-container" onClick={toggleConnection}>
+ {faviconUrl ? (
+ <img
+ src={faviconUrl}
+ alt="Favicon"
+ className={`icon ${isConnected ? 'connected' : ''}`}
+ onError={handleFaviconError}
+ />
+ ) : (
+ <i className={`fa fa-globe icon ${isConnected ?
'connected' : ''}`} aria-hidden="true"></i>
+ )}
+ <span className="status-dot"></span>
+ <span className="tooltip">{isConnected ? 'Connected' :
'Disconnected'}</span>
+ </div>
+ {selectedNet === 'Custom Net' && (
+ <input
+ type="text"
+ value={customUrl}
+ onChange={handleCustomUrlChange}
+ placeholder="Enter custom URL"
+ className="input"
+ />
)}
+ </div>
- <div className="page__content page__content--with-header
page__content--with-bottom-nav">
- <h2 className="page__title">Dashboard</h2>
-
- <div className="net">
- <div className="net-header">
- <div className="select-wrapper">
- <select value={selectedNet}
onChange={handleNetworkChange} className="select">
- <option value="ResilientDB
Mainnet">ResilientDB Mainnet</option>
- <option value="ResilientDB
Localnet">ResilientDB Localnet</option>
- {nets.map(net => (
- <option key={net.name}
value={net.name}>{net.name}</option>
- ))}
- <option value="Manage Nets">Manage
Nets</option>
- </select>
- <i className="fas fa-chevron-down"></i>
- </div>
-
- <div className="icon-container"
onClick={toggleConnection}>
- {faviconUrl ? (
- <img
- src={faviconUrl}
- alt="Favicon"
- className={`icon ${isConnected ?
'connected' : ''}`}
- onError={handleFaviconError} // Add
onError handler
- />
- ) : (
- <i className={`fa fa-globe icon
${isConnected ? 'connected' : ''}`} aria-hidden="true"></i>
- )}
- <span className="status-dot"></span>
- <span className="tooltip">{isConnected ?
'Connected' : 'Disconnected'}</span>
- </div>
- {selectedNet === 'Custom Net' && (
- <input
- type="text"
- value={customUrl}
- onChange={handleCustomUrlChange}
- placeholder="Enter custom URL"
- className="input"
- />
- )}
- </div>
- <div className="file-upload">
- <div
- className={`drag_box_outline ${jsonFileName ?
'file-uploaded' : ''}`}
- onDragEnter={handleDragEnter}
- onDragOver={handleDragOver}
- onDrop={handleDrop}
- onClick={handleFileClick}
- >
- <input
- type="file"
- ref={fileInputRef}
- style={{ display: 'none' }}
- accept="application/json"
- onChange={handleFileUpload}
- />
- {jsonFileName ? (
- <span className="filename">{jsonFileName}
uploaded</span>
- ) : (
- <span className="filename">Click to Upload
JSON File</span>
- )}
- </div>
- {transactionError && <p
className="error-message">{transactionError}</p>}
- </div>
+ <div className="file-upload">
+ <div
+ className={`drag_box_outline ${jsonFileName ?
'file-uploaded' : ''}`}
+ onDragEnter={handleDragEnter}
+ onDragOver={handleDragOver}
+ onDrop={handleDrop}
+ onClick={handleFileClick}
+ >
+ <input
+ type="file"
+ ref={fileInputRef}
+ style={{ display: 'none' }}
+ accept="application/json"
+ onChange={handleFileUpload}
+ />
+ {jsonFileName ? (
+ <span className="filename">{jsonFileName}
uploaded</span>
+ ) : (
+ <span className="filename">Click to Upload JSON
File</span>
+ )}
</div>
+ {transactionError && <p
className="error-message">{transactionError}</p>}
+ </div>
+ </div>
- <h2 className="page__title">Select Account</h2>
- <div className="fieldset">
- <div className="radio-option radio-option--full">
- <input
- type="radio"
- name="wallet"
- id="w3"
- value={originalAccountId}
- checked
- readOnly
- onClick={handleInputClick}
- ref={inputRef}
- />
- <label htmlFor="w3">
- <span>{isCopied ? 'Copied' :
shortenedId}</span>
- </label>
- </div>
+
+
+ <h2 className="page__title">Select Account</h2>
+ <div className="net">
+ <div className="keypair">
+ <div className="keypair-header">
+ <div className="select-wrapper">
+ <select
+ value={selectedKeyPairIndex}
+ onChange={(e) => switchKeyPair(e.target.value)}
+ className="select"
+ >
+ {keyPairs.map((keyPair, index) => (
+ <option key={index} value={index}>
+ {`${keyPair.publicKey.slice(0,
4)}...${keyPair.publicKey.slice(-4)}`}
+ </option>
+ ))}
+ </select>
+ <i className="fas fa-chevron-down"></i>
+ </div>
+ <div className="keypair-icons">
+ <button onClick={handleGenerateKeyPair}
className="icon-button">
+ <AddCircleOutlineIcon style={{ color: 'white' }} />
+ </button>
+ <button onClick={handleCopyPublicKey}
className="icon-button">
+ <ContentCopyIcon style={{ color: isCopied ?
'green' : 'white' }} />
+ </button>
+ <button onClick={handleDownloadKeyPair}
className="icon-button">
+ <DownloadIcon style={{ color: 'white' }} />
+ </button>
+ </div>
</div>
-
- <button className="button button--full button--main
open-popup" onClick={handleSubmit}>
- Submit
- </button>
- <p className="bottom-navigation" style={{ backgroundColor:
'transparent', display: 'flex', justifyContent: 'center', textShadow: '1px 1px
1px rgba(0, 0, 0, 0.3)', color: 'rgb(255, 255, 255, 0.5)', fontSize: '9px' }}>
- ResVault v{versionData.version}
- </p>
</div>
</div>
+
+ <button className="button button--full button--main open-popup"
onClick={handleSubmit}>
+ Submit
+ </button>
+ <p className="bottom-navigation" style={{ backgroundColor:
'transparent', display: 'flex', justifyContent: 'center', textShadow: '1px 1px
1px rgba(0, 0, 0, 0.3)', color: 'rgb(255, 255, 255, 0.5)', fontSize: '9px' }}>
+ ResVault v{versionData.version}
+ </p>
+ </div>
+ </div>
</>
);
}
diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx
index 0347311..951d31b 100644
--- a/src/pages/Home.jsx
+++ b/src/pages/Home.jsx
@@ -1,21 +1,21 @@
/**
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements. See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership. The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied. See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
/*global chrome*/
@@ -52,7 +52,7 @@ function Home() {
navigate("/login", { state: res.store });
} else {
// Decrypt private key and store both public and private keys
- const bytes = CryptoJS.AES.decrypt(res.store.encryptedPrivateKey,
result.password.password);
+ const bytes = CryptoJS.AES.decrypt(res.store.encryptedPrivateKey,
result.password);
const privateKey = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
setPublicKey(res.store.publicKey);
setPrivateKey(privateKey);
diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx
index 90b1cf7..a2d5bea 100644
--- a/src/pages/Login.jsx
+++ b/src/pages/Login.jsx
@@ -1,25 +1,6 @@
-/**
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements. See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership. The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied. See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
/*global chrome*/
import '../css/App.css';
-import CryptoJS from "crypto-js";
+import CryptoJS from "crypto-js"; // Changed to default import
import IconButton from "@material-ui/core/IconButton";
import Visibility from "@material-ui/icons/Visibility";
import InputAdornment from "@material-ui/core/InputAdornment";
@@ -32,147 +13,222 @@ import versionData from '../data/version.json';
import { GlobalContext } from '../context/GlobalContext';
function Login() {
- const { loginValues, setLoginValues, setPublicKey, setPrivateKey } =
useContext(GlobalContext);
- const navigate = useNavigate();
- const [showPasswordErrorModal, setShowPasswordErrorModal] =
useState(false);
-
- const defaultOptions = {
- loop: true,
- autoplay: true,
- animationData: require('../images/signup.json'),
- rendererSettings: {
- preserveAspectRatio: 'xMidYMid slice',
- renderer: 'svg'
+ const {
+ loginValues,
+ setLoginValues,
+ setPublicKey,
+ setPrivateKey,
+ setIsAuthenticated,
+ setKeyPairs,
+ setSelectedKeyPairIndex,
+ setStoredPassword
+ } = useContext(GlobalContext);
+ const navigate = useNavigate();
+ const [showPasswordErrorModal, setShowPasswordErrorModal] = useState(false);
+
+ const defaultOptions = {
+ loop: true,
+ autoplay: true,
+ animationData: require('../images/signup.json'),
+ rendererSettings: {
+ preserveAspectRatio: 'xMidYMid slice',
+ renderer: 'svg'
+ }
+ };
+
+ const removeAccount = async () => {
+ chrome.storage.sync.clear(function() {});
+ chrome.storage.local.clear(function() {});
+ localStorage.removeItem('nets');
+ setIsAuthenticated(false);
+ navigate("/");
+ };
+
+ const loginAccount = async (e) => {
+ e.preventDefault();
+ chrome.storage.sync.get(['store', 'encryptedKeyPairs'], (res) => {
+ if (res.store &&
CryptoJS.SHA256(loginValues.password).toString(CryptoJS.enc.Hex) ===
res.store.hash) {
+ try {
+ // Decrypt private key from store
+ const privateKeyBytes =
CryptoJS.AES.decrypt(res.store.encryptedPrivateKey, loginValues.password);
+ const decryptedPrivateKey =
privateKeyBytes.toString(CryptoJS.enc.Utf8);
+ const publicKey = res.store.publicKey;
+
+ // Check if encryptedKeyPairs exist
+ if (!res.encryptedKeyPairs) {
+ console.error('No encryptedKeyPairs found in storage.');
+ setShowPasswordErrorModal(true);
+ return;
+ }
+
+ // Load key pairs
+ const encryptedKeyPairs = res.encryptedKeyPairs;
+ const decryptedKeyPairsBytes =
CryptoJS.AES.decrypt(encryptedKeyPairs, loginValues.password);
+ const decryptedKeyPairsString =
decryptedKeyPairsBytes.toString(CryptoJS.enc.Utf8);
+
+ let decryptedKeyPairs;
+ try {
+ decryptedKeyPairs = JSON.parse(decryptedKeyPairsString);
+ } catch (parseError) {
+ console.error('Error parsing decrypted key pairs:', parseError,
'decryptedKeyPairsString:', decryptedKeyPairsString);
+ setShowPasswordErrorModal(true);
+ return;
+ }
+
+ // Ensure decryptedKeyPairs is an array
+ if (!Array.isArray(decryptedKeyPairs)) {
+ console.error('Decrypted key pairs is not an array:',
decryptedKeyPairs);
+ setShowPasswordErrorModal(true);
+ return;
+ }
+
+ // Update keyPairs and set keys
+ setKeyPairs(decryptedKeyPairs);
+ // Find the index of the key pair matching the publicKey
+ const keyPairIndex = decryptedKeyPairs.findIndex(kp => kp.publicKey
=== publicKey);
+ if (keyPairIndex !== -1) {
+ setSelectedKeyPairIndex(keyPairIndex);
+ setPublicKey(decryptedKeyPairs[keyPairIndex].publicKey);
+ setPrivateKey(decryptedKeyPairs[keyPairIndex].privateKey);
+ } else {
+ // If not found, default to first key pair
+ setSelectedKeyPairIndex(0);
+ setPublicKey(decryptedKeyPairs[0].publicKey);
+ setPrivateKey(decryptedKeyPairs[0].privateKey);
+ }
+
+ // Store password in chrome.storage.local and set storedPassword in
context
+ chrome.storage.local.set({ password: loginValues.password }, () => {
+ setStoredPassword(loginValues.password);
+ setIsAuthenticated(true);
+ navigate("/dashboard");
+ });
+ } catch (err) {
+ console.error('Error during login:', err);
+ setShowPasswordErrorModal(true);
}
- };
-
- const removeAccount = async () => {
- chrome.storage.sync.clear(function() {});
- localStorage.removeItem('nets');
- navigate("/");
- };
-
- useEffect(() => {
- chrome.storage.local.get(['password'], (result) => {
- if (result.password) {
- chrome.storage.sync.get(['store'], (res) => {
- if (res.store) {
- setPublicKey(res.store.publicKey);
- setPrivateKey(res.store.encryptedPrivateKey);
- navigate("/dashboard");
+ } else {
+ setShowPasswordErrorModal(true);
+ }
+ });
+ };
+
+ const handleClickShowPassword = () => {
+ setLoginValues({ ...loginValues, showPassword: !loginValues.showPassword
});
+ };
+
+ const handleMouseDownPassword = (event) => {
+ event.preventDefault();
+ };
+
+ const handlePasswordChange = (prop) => (event) => {
+ setLoginValues({ ...loginValues, [prop]: event.target.value });
+ };
+
+ const closeModal = () => {
+ setShowPasswordErrorModal(false);
+ };
+
+ // Reset loginValues when the component mounts
+ useEffect(() => {
+ setLoginValues({ password: '', showPassword: false });
+ }, [setLoginValues]);
+
+ return (
+ <>
+ <div className="lottie-background">
+ <Lottie options={defaultOptions} height="100%" width="100%" />
+ </div>
+ <div className="page page--login" data-page="login">
+ <div className="login">
+ <div className="login__content">
+ <h2 className="login__title">Login</h2>
+ <div className="login-form">
+ <form id="LoginForm" onSubmit={loginAccount}>
+ <div className="login-form__row">
+ <label className="login-form__label">Password</label>
+ <Input
+ type={loginValues.showPassword ? "text" : "password"}
+ onChange={handlePasswordChange("password")}
+ placeholder="Password"
+ className="login-form__input"
+ value={loginValues.password}
+ style={{ color: 'white', width: '100%' }}
+ disableUnderline
+ required
+ endAdornment={
+ <InputAdornment position="end">
+ <IconButton
+ onClick={handleClickShowPassword}
+ onMouseDown={handleMouseDownPassword}
+ style={{ color: 'white' }}
+ >
+ {loginValues.showPassword ? <Visibility /> :
<VisibilityOff />}
+ </IconButton>
+ </InputAdornment>
}
- });
- }
- });
- }, [navigate, setPublicKey, setPrivateKey]);
-
- const loginAccount = async (e) => {
- e.preventDefault();
- chrome.storage.sync.get(['store'], (res) => {
- if (res.store &&
CryptoJS.SHA256(loginValues.password).toString(CryptoJS.enc.Hex) ===
res.store.hash) {
- try {
- const bytes =
CryptoJS.AES.decrypt(res.store.encryptedPrivateKey, loginValues.password);
- const decryptedPrivateKey =
JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
- setPublicKey(res.store.publicKey);
- setPrivateKey(decryptedPrivateKey);
-
- chrome.storage.local.set({ password: { password:
loginValues.password } }, () => {});
- navigate("/dashboard");
- } catch (err) {
- setShowPasswordErrorModal(true); // Show modal if
decryption fails
- }
- } else {
- setShowPasswordErrorModal(true); // Show modal if password is
incorrect
- }
- });
- };
-
- const handleClickShowPassword = () => {
- setLoginValues({ ...loginValues, showPassword:
!loginValues.showPassword });
- };
-
- const handleMouseDownPassword = (event) => {
- event.preventDefault();
- };
-
- const handlePasswordChange = (prop) => (event) => {
- setLoginValues({ ...loginValues, [prop]: event.target.value });
- };
-
- const closeModal = () => {
- setShowPasswordErrorModal(false);
- };
-
- return (
- <>
- <div className="lottie-background">
- <Lottie options={defaultOptions} height="100%" width="100%" />
- </div>
- <div className="page page--login" data-page="login">
- <div className="login">
- <div className="login__content">
- <h2 className="login__title">Login</h2>
- <div className="login-form">
- <form id="LoginForm" onSubmit={loginAccount}>
- <div className="login-form__row">
- <label
className="login-form__label">Password</label>
- <Input
- type={loginValues.showPassword ?
"text" : "password"}
-
onChange={handlePasswordChange("password")}
- placeholder="Password"
- className="login-form__input"
- value={loginValues.password}
- style={{color: 'white', width: '100%'}}
- disableUnderline
- required
- endAdornment={
- <InputAdornment position="end">
- <IconButton
-
onClick={handleClickShowPassword}
-
onMouseDown={handleMouseDownPassword}
- style={{color: 'white'}}
- >
- {loginValues.showPassword
? <Visibility /> : <VisibilityOff />}
- </IconButton>
- </InputAdornment>
- }
- />
- </div>
- <div className="login-form__row">
- <button disabled={!loginValues.password}
className="login-form__submit button button--main button--full" type="submit">
- Login
- </button>
- </div>
- </form>
- <div className="login-form__bottom">
- <p>
- Delete current account? <br />
- <button style={{color: '#47e7ce',
fontWeight: '600', fontSize: '1.2rem', background: 'transparent', border:
'none', outline: 'none', cursor: 'pointer'}} onClick={removeAccount}>
- Remove Account
- </button>
- </p>
- </div>
- <p className="bottom-navigation"
style={{backgroundColor: 'transparent', display: 'flex', justifyContent:
'center', textShadow: '1px 1px 1px rgba(0, 0, 0, 0.3)', color: 'rgb(255, 255,
255, 0.5)', fontSize: '9px'}}>
- ResVault v{versionData.version}
- </p>
- </div>
- </div>
+ />
</div>
+ <div className="login-form__row">
+ <button
+ disabled={!loginValues.password}
+ className="login-form__submit button button--main
button--full"
+ type="submit"
+ >
+ Login
+ </button>
+ </div>
+ </form>
+ <div className="login-form__bottom">
+ <p>
+ Delete current account? <br />
+ <button
+ style={{
+ color: '#47e7ce',
+ fontWeight: '600',
+ fontSize: '1.2rem',
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ cursor: 'pointer',
+ }}
+ onClick={removeAccount}
+ >
+ Remove Account
+ </button>
+ </p>
+ </div>
+ <p
+ className="bottom-navigation"
+ style={{
+ backgroundColor: 'transparent',
+ display: 'flex',
+ justifyContent: 'center',
+ textShadow: '1px 1px 1px rgba(0, 0, 0, 0.3)',
+ color: 'rgb(255, 255, 255, 0.5)',
+ fontSize: '9px',
+ }}
+ >
+ ResVault v{versionData.version}
+ </p>
+ </div>
+ </div>
+ </div>
- {showPasswordErrorModal && (
- <div className="modal-overlay">
- <div className="modal">
- <h2>Password Incorrect!</h2>
- <p>Please check your password and try again.</p>
- <button className="button button--main
button--full" onClick={closeModal}>
- OK
- </button>
- </div>
- </div>
- )}
+ {showPasswordErrorModal && (
+ <div className="modal-overlay">
+ <div className="modal">
+ <h2>Password Incorrect!</h2>
+ <p>Please check your password and try again.</p>
+ <button className="button button--main button--full"
onClick={closeModal}>
+ OK
+ </button>
</div>
- </>
- );
+ </div>
+ )}
+ </div>
+ </>
+ );
}
export default Login;
\ No newline at end of file
diff --git a/src/pages/SignUp.jsx b/src/pages/SignUp.jsx
index b2ca816..1301621 100644
--- a/src/pages/SignUp.jsx
+++ b/src/pages/SignUp.jsx
@@ -1,23 +1,3 @@
-/**
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements. See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership. The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied. See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-
/*global chrome*/
import '../css/App.css';
import CryptoJS from "crypto-js";
@@ -26,20 +6,28 @@ import Visibility from "@material-ui/icons/Visibility";
import InputAdornment from "@material-ui/core/InputAdornment";
import VisibilityOff from "@material-ui/icons/VisibilityOff";
import Input from "@material-ui/core/Input";
-import Base58 from 'bs58';
-import nacl from 'tweetnacl';
import PasswordStrengthBar from 'react-password-strength-bar';
import backicon from "../images/icons/arrow-back.svg";
-import React, { useContext, useState } from 'react';
+import React, { useContext, useState, useEffect } from 'react'; // Note the
import of useEffect
import Lottie from 'react-lottie';
import versionData from '../data/version.json';
import { useNavigate } from 'react-router-dom';
import { GlobalContext } from '../context/GlobalContext';
function SignUp() {
- const { values, setValues, confirmValues, setConfirmValues, setPublicKey,
setPrivateKey } = useContext(GlobalContext);
+ const {
+ values,
+ setValues,
+ confirmValues,
+ setConfirmValues,
+ generateKeyPair,
+ setIsAuthenticated,
+ setStoredPassword,
+ storedPassword,
+ } = useContext(GlobalContext);
const navigate = useNavigate();
const [showPasswordMismatchModal, setShowPasswordMismatchModal] =
useState(false);
+ const [shouldGenerateKeyPair, setShouldGenerateKeyPair] = useState(false);
const defaultOptions = {
loop: true,
@@ -47,7 +35,7 @@ function SignUp() {
animationData: require('../images/signup.json'),
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
- renderer: 'svg'
+ renderer: 'svg'
}
};
@@ -63,43 +51,30 @@ function SignUp() {
outline: 'none',
};
- function generateKeyPair() {
- const keyPair = nacl.sign.keyPair();
- const pk = Base58.encode(keyPair.publicKey);
- const sk = Base58.encode(keyPair.secretKey.slice(0, 32));
- return { publicKey: pk, privateKey: sk };
- };
-
const createAccount = async (e) => {
e.preventDefault();
if (values.password === confirmValues.password) {
- chrome.storage.sync.clear(async function () {
- const keys = generateKeyPair();
- const publicKey = keys.publicKey;
- const privateKey = keys.privateKey;
- const encryptedPrivateKey = CryptoJS.AES.encrypt(
- JSON.stringify(privateKey),
- values.password
- ).toString();
-
- const hash =
CryptoJS.SHA256(values.password).toString(CryptoJS.enc.Hex);
- const store = { publicKey, encryptedPrivateKey, hash };
- const password = { password: values.password };
-
- chrome.storage.local.set({ password }, () => {});
-
- store.history = [];
- chrome.storage.sync.set({ store }, () => {
- setPublicKey(publicKey);
- setPrivateKey(privateKey);
- navigate("/dashboard");
- });
+ const password = values.password;
+ // Store password
+ chrome.storage.local.set({ password }, () => {
+ setStoredPassword(password);
+ setShouldGenerateKeyPair(true);
});
} else {
setShowPasswordMismatchModal(true);
}
};
+ useEffect(() => {
+ if (shouldGenerateKeyPair && storedPassword) {
+ generateKeyPair(() => {
+ setIsAuthenticated(true);
+ navigate("/dashboard");
+ });
+ setShouldGenerateKeyPair(false);
+ }
+ }, [shouldGenerateKeyPair, storedPassword, generateKeyPair,
setIsAuthenticated, navigate]);
+
const handleClickShowPassword = () => {
setValues({ ...values, showPassword: !values.showPassword });
};