implement secure connection
This commit is contained in:
parent
94d0685aec
commit
0ff8814561
9 changed files with 221 additions and 153 deletions
1
package-lock.json
generated
1
package-lock.json
generated
|
|
@ -14,6 +14,7 @@
|
|||
"big.js": "^6.2.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-chrome-extension-router": "^1.4.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"big.js": "^6.2.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-chrome-extension-router": "^1.4.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
|||
124
src/app/App.js
124
src/app/App.js
|
|
@ -17,6 +17,7 @@ import {
|
|||
updateLoading,
|
||||
updateConfirmationModal,
|
||||
updateTransactionStatus,
|
||||
setConnectData,
|
||||
} from "./store/actions";
|
||||
import { Store } from "./store/store-reducer";
|
||||
import { getZanoPrice } from "./api/coingecko";
|
||||
|
|
@ -96,10 +97,11 @@ function App() {
|
|||
const checkConnection = async () => {
|
||||
if (!chrome?.runtime?.sendMessage) return;
|
||||
|
||||
const balanceData = await fetchBackground({
|
||||
method: "GET_WALLET_BALANCE",
|
||||
const walletActive = await fetchBackground({
|
||||
method: "PING_WALLET",
|
||||
});
|
||||
updateWalletConnected(dispatch, !!balanceData.data);
|
||||
updateWalletConnected(dispatch, walletActive.data);
|
||||
updateLoading(dispatch, false);
|
||||
};
|
||||
|
||||
const getWalletData = async () => {
|
||||
|
|
@ -223,6 +225,59 @@ function App() {
|
|||
|
||||
const appConnected = !!(state.connectKey || ConnectKeyUtils.getConnectKeyEncrypted());
|
||||
|
||||
useEffect(() => {
|
||||
if (state.connectKey && state.publicKey) {
|
||||
fetchBackground({
|
||||
method: "SET_API_CREDENTIALS",
|
||||
credentials: {
|
||||
token: state.connectKey,
|
||||
publicKey: state.publicKey,
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [state.connectKey, state.publicKey]);
|
||||
|
||||
|
||||
function PasswordPages() {
|
||||
|
||||
if (creatingPassword) {
|
||||
return (
|
||||
<PasswordCreatePage
|
||||
incorrectPassword={incorrectPassword}
|
||||
setIncorrectPassword={setIncorrectPassword}
|
||||
onConfirm={(password) => {
|
||||
setPassword(password);
|
||||
if (state.connectKey) ConnectKeyUtils.setConnectData(state.connectKey, state.publicKey, password);
|
||||
setLoggedIn(true);
|
||||
setSessionLogIn(true);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<PasswordPage
|
||||
incorrectPassword={incorrectPassword}
|
||||
setIncorrectPassword={setIncorrectPassword}
|
||||
onConfirm={(password) => {
|
||||
if (comparePasswords(password)) {
|
||||
setLoggedIn(true);
|
||||
setSessionLogIn(true);
|
||||
const connectData = ConnectKeyUtils.getConnectData(password);
|
||||
if (connectData?.token) {
|
||||
setConnectData(dispatch, {
|
||||
token: connectData.token,
|
||||
publicKey: connectData.publicKey
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setIncorrectPassword(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<AppPlug />
|
||||
|
|
@ -234,56 +289,27 @@ function App() {
|
|||
onConfirm={handleConfirm}
|
||||
/>
|
||||
{loggedIn && <Header />}
|
||||
{/* <Header/> */}
|
||||
<AppLoader />
|
||||
|
||||
{/* <div className="container">
|
||||
<Router>
|
||||
<Wallet />
|
||||
<TokensTabs />
|
||||
</Router>
|
||||
</div> */}
|
||||
|
||||
{appConnected ?
|
||||
(
|
||||
loggedIn
|
||||
?
|
||||
(
|
||||
<div className="container">
|
||||
<Router>
|
||||
<Wallet />
|
||||
<TokensTabs />
|
||||
</Router>
|
||||
</div>
|
||||
)
|
||||
:
|
||||
(
|
||||
creatingPassword ?
|
||||
<PasswordCreatePage
|
||||
incorrectPassword={incorrectPassword}
|
||||
setIncorrectPassword={setIncorrectPassword}
|
||||
onConfirm={(password) => {
|
||||
setPassword(password);
|
||||
if (state.connectKey) ConnectKeyUtils.setConnectKey(state.connectKey, password);
|
||||
setLoggedIn(true);
|
||||
setSessionLogIn(true);
|
||||
}}
|
||||
/> :
|
||||
<PasswordPage
|
||||
incorrectPassword={incorrectPassword}
|
||||
setIncorrectPassword={setIncorrectPassword}
|
||||
onConfirm={(password) => {
|
||||
if (comparePasswords(password)) {
|
||||
setLoggedIn(true);
|
||||
setSessionLogIn(true);
|
||||
} else {
|
||||
setIncorrectPassword(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
) :
|
||||
<ConnectPage />
|
||||
loggedIn
|
||||
?
|
||||
(
|
||||
<div className="container">
|
||||
<Router>
|
||||
<Wallet />
|
||||
<TokensTabs />
|
||||
</Router>
|
||||
</div>
|
||||
)
|
||||
:
|
||||
<PasswordPages />
|
||||
)
|
||||
|
||||
:
|
||||
|
||||
<ConnectPage />
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ import logo from "../../assets/svg/logo.svg";
|
|||
import { useContext, useState } from "react";
|
||||
import MyInput from "../UI/MyInput/MyInput";
|
||||
import { fetchBackground } from "../../utils/utils";
|
||||
import { setConnectKey } from "../../store/actions";
|
||||
import { setConnectData } from "../../store/actions";
|
||||
import { Store } from "../../store/store-reducer";
|
||||
import forge from "node-forge";
|
||||
|
||||
export default function ConnectPage() {
|
||||
const { dispatch } = useContext(Store);
|
||||
|
|
@ -14,15 +15,25 @@ export default function ConnectPage() {
|
|||
const [connectState, setConnectState] = useState("start");
|
||||
|
||||
const [keyValue, setKeyValue] = useState("");
|
||||
const [receivedPublicKey, setReceivedPublicKey] = useState("");
|
||||
|
||||
async function connectClick() {
|
||||
const response = await fetchBackground({ method: "CREATE_CONNECT_KEY" });
|
||||
setReceivedPublicKey(response.publicKey);
|
||||
if (response.success) setConnectState("code");
|
||||
}
|
||||
|
||||
async function continueClick() {
|
||||
const response = await fetchBackground({ method: "VALIDATE_CONNECT_KEY" });
|
||||
if (response.success) setConnectKey(dispatch, keyValue);
|
||||
const publicKey = forge.pki.publicKeyFromPem(receivedPublicKey);
|
||||
const encrypted = publicKey.encrypt(keyValue);
|
||||
const response = await fetchBackground({ method: "VALIDATE_CONNECT_KEY", key: encrypted });
|
||||
console.log(response);
|
||||
if (response.success) {
|
||||
setConnectData(dispatch, {
|
||||
token: keyValue,
|
||||
publicKey: receivedPublicKey
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ export const updateTransactionStatus = (dispatch, state) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const setConnectKey = (dispatch, state) => {
|
||||
export const setConnectData = (dispatch, state) => {
|
||||
return dispatch({
|
||||
type: "SET_CONNECT_KEY",
|
||||
type: "SET_CONNECT_DATA",
|
||||
payload: state
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ const reducer = (state, action) => {
|
|||
return { ...state, confirmationModal: action.payload };
|
||||
case "TRANSACTION_STATUS_UPDATED":
|
||||
return { ...state, transactionStatus: action.payload };
|
||||
case "SET_CONNECT_KEY":
|
||||
return { ...state, connectKey: action.payload }
|
||||
case "SET_CONNECT_DATA":
|
||||
return { ...state, connectKey: action.payload.token, publicKey: action.payload.publicKey }
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
import CryptoJS from "crypto-js";
|
||||
|
||||
export default class ConnectKeyUtils {
|
||||
static setConnectKey(key, extPass) {
|
||||
localStorage.setItem("connectKey", CryptoJS.AES.encrypt(key, extPass).toString());
|
||||
static setConnectData(key, publicKey, extPass) {
|
||||
const data = JSON.stringify({ token: key, publicKey: publicKey });
|
||||
localStorage.setItem("connectKey", CryptoJS.AES.encrypt(data, extPass).toString());
|
||||
}
|
||||
|
||||
static getConnectKeyEncrypted() {
|
||||
return localStorage.getItem("connectKey");
|
||||
}
|
||||
|
||||
static getConnectData(password) {
|
||||
const encrypted = localStorage.getItem("connectKey");
|
||||
const decrypted = CryptoJS.AES.decrypt(encrypted, password).toString(CryptoJS.enc.Utf8);
|
||||
const data = JSON.parse(decrypted);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,8 @@ chrome.runtime.onStartup.addListener(() => {
|
|||
console.log("Background script loaded on startup");
|
||||
});
|
||||
|
||||
export let apiCredentials = null;
|
||||
|
||||
let pendingTx = null;
|
||||
|
||||
const userData = { login: false };
|
||||
|
|
@ -29,6 +31,18 @@ chrome.storage.local.get("pendingTx", (result) => {
|
|||
// eslint-disable-next-line no-undef
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
switch (request.method) {
|
||||
case "SET_API_CREDENTIALS":
|
||||
apiCredentials = request.credentials;
|
||||
break;
|
||||
|
||||
case "PING_WALLET":
|
||||
fetch('http://localhost:12111/ping')
|
||||
.then(res => res.json())
|
||||
.then(res => sendResponse({ data: true }))
|
||||
.catch(err => sendResponse({ data: false }));
|
||||
break;
|
||||
|
||||
|
||||
case "SET_ACTIVE_WALLET":
|
||||
fetchData("mw_select_wallet", { wallet_id: request.id })
|
||||
.then((response) => response.json())
|
||||
|
|
@ -166,14 +180,14 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|||
|
||||
case "CREATE_CONNECT_KEY": {
|
||||
createConnectKey()
|
||||
.then(() => sendResponse({ success: true }))
|
||||
.then((res) => sendResponse({ success: true, publicKey: res?.publicKey }))
|
||||
.catch(() => sendResponse({ error: "Internal error" }));
|
||||
break;
|
||||
}
|
||||
|
||||
case "VALIDATE_CONNECT_KEY": {
|
||||
validateConnectKey()
|
||||
.then(() => sendResponse({ success: true }))
|
||||
validateConnectKey(request.key)
|
||||
.then((res) => sendResponse(res))
|
||||
.catch(() => sendResponse({ error: "Internal error" }));
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,55 @@
|
|||
import { addZeros, removeZeros } from "../app/utils/utils";
|
||||
import { apiCredentials } from "./background";
|
||||
import forge from "node-forge";
|
||||
|
||||
function generateAccessToken() {
|
||||
if (!apiCredentials?.publicKey || !apiCredentials?.token) {
|
||||
throw new Error("No API credentials found, extension is not connected");
|
||||
}
|
||||
|
||||
const publicKey = forge.pki.publicKeyFromPem(apiCredentials.publicKey);
|
||||
|
||||
function generateRandomString(length) {
|
||||
const bytes = forge.random.getBytesSync(Math.ceil(length / 2));
|
||||
const hexString = forge.util.bytesToHex(bytes);
|
||||
return hexString.substring(0, length);
|
||||
}
|
||||
|
||||
const dataToEncrypt = JSON.stringify({
|
||||
accessToken: apiCredentials.token,
|
||||
timestamp: +Date.now(),
|
||||
salt: generateRandomString(64)
|
||||
})
|
||||
|
||||
const encrypted = publicKey.encrypt(dataToEncrypt);
|
||||
|
||||
return btoa(encrypted);
|
||||
}
|
||||
|
||||
export const fetchData = async (method, params = {}) =>
|
||||
fetch("http://localhost:12111/json_rpc", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Zano-Access-Token": generateAccessToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: "0",
|
||||
method,
|
||||
params,
|
||||
}),
|
||||
});
|
||||
|
||||
const fetchTxData = async () => {
|
||||
try {
|
||||
const response = await fetch("http://localhost:12111/json_rpc", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: "0",
|
||||
method: "get_recent_txs_and_info",
|
||||
params: {
|
||||
offset: 0,
|
||||
update_provision_info: true,
|
||||
exclude_mining_txs: true,
|
||||
count: 20,
|
||||
order: "FROM_END_TO_BEGIN",
|
||||
exclude_unconfirmed: false,
|
||||
},
|
||||
}),
|
||||
const response = await fetchData("get_recent_txs_and_info", {
|
||||
offset: 0,
|
||||
update_provision_info: true,
|
||||
exclude_mining_txs: true,
|
||||
count: 20,
|
||||
order: "FROM_END_TO_BEGIN",
|
||||
exclude_unconfirmed: false,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
|
|
@ -32,20 +62,6 @@ const fetchTxData = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
export const fetchData = async (method, params = {}) =>
|
||||
fetch("http://localhost:12111/json_rpc", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: "0",
|
||||
method,
|
||||
params,
|
||||
}),
|
||||
});
|
||||
|
||||
export const getAlias = async (address) => {
|
||||
const response = await fetchData("get_alias_by_address", address);
|
||||
const data = await response.json();
|
||||
|
|
@ -192,12 +208,25 @@ export const ionicSwap = async (swapParams) => {
|
|||
|
||||
console.log('send swap request:', swapRequest);
|
||||
|
||||
const response = await fetch("http://localhost:12111/json_rpc", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
const response = await fetchData("ionic_swap_generate_proposal", {
|
||||
proposal: {
|
||||
to_initiator: [
|
||||
{
|
||||
asset_id: swapParams.destinationAssetID,
|
||||
amount: swapParams.destinationAssetAmount * 1e12,
|
||||
},
|
||||
],
|
||||
to_finalizer: [
|
||||
{
|
||||
asset_id: swapParams.currentAssetID,
|
||||
amount: swapParams.currentAssetAmount * 1e12,
|
||||
},
|
||||
],
|
||||
mixins: 10,
|
||||
fee_paid_by_a: 10000000000,
|
||||
expiration_time: swapParams.expirationTimestamp,
|
||||
},
|
||||
body: JSON.stringify(swapRequest)
|
||||
destination_address: swapParams.destinationAddress,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
@ -209,22 +238,11 @@ export const ionicSwap = async (swapParams) => {
|
|||
};
|
||||
|
||||
export const ionicSwapAccept = async (swapParams) => {
|
||||
|
||||
|
||||
console.log(swapParams.hex_raw_proposal);
|
||||
|
||||
const response = await fetch("http://localhost:12111/json_rpc", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: "0",
|
||||
method: "ionic_swap_accept_proposal",
|
||||
params: {
|
||||
hex_raw_proposal: swapParams.hex_raw_proposal,
|
||||
},
|
||||
}),
|
||||
const response = await fetchData("ionic_swap_accept_proposal", {
|
||||
hex_raw_proposal: swapParams.hex_raw_proposal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
@ -248,21 +266,10 @@ export const transfer = async (
|
|||
},
|
||||
];
|
||||
|
||||
const response = await fetch("http://localhost:12111/json_rpc", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: "0",
|
||||
method: "transfer",
|
||||
params: {
|
||||
destinations,
|
||||
fee: 10000000000,
|
||||
mixin: 10,
|
||||
},
|
||||
}),
|
||||
const response = await fetchData("transfer", {
|
||||
destinations,
|
||||
fee: 10000000000,
|
||||
mixin: 10,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
@ -302,30 +309,19 @@ export const transferBridge = async (
|
|||
byte.toString(16).padStart(2, "0")
|
||||
).join("");
|
||||
|
||||
const response = await fetch("http://localhost:12111/json_rpc", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: "0",
|
||||
method: "transfer",
|
||||
params: {
|
||||
destinations,
|
||||
fee: 10000000000,
|
||||
mixin: 10,
|
||||
service_entries_permanent: true,
|
||||
service_entries: [
|
||||
{
|
||||
service_id: "X",
|
||||
instruction: "",
|
||||
body: bodyHex,
|
||||
flags: 5,
|
||||
},
|
||||
],
|
||||
const response = await fetchData("transfer", {
|
||||
destinations,
|
||||
fee: 10000000000,
|
||||
mixin: 10,
|
||||
service_entries_permanent: true,
|
||||
service_entries: [
|
||||
{
|
||||
service_id: "X",
|
||||
instruction: "",
|
||||
body: bodyHex,
|
||||
flags: 5,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// console.log(response);
|
||||
|
|
@ -339,9 +335,20 @@ export const transferBridge = async (
|
|||
};
|
||||
|
||||
export const createConnectKey = async () => {
|
||||
await fetch("http://localhost:12111/connect-api-consumer");
|
||||
return await fetch("http://localhost:12111/connect-api-consumer", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(r => r.json());
|
||||
}
|
||||
|
||||
export const validateConnectKey = async () => {
|
||||
await fetch("http://localhost:12111/validate-connect-key");
|
||||
export const validateConnectKey = async (key) => {
|
||||
return await fetch("http://localhost:12111/validate-connection-key", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ key })
|
||||
}).then(r => r.json());
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue