diff --git a/package-lock.json b/package-lock.json index dd6ccb8..ae2c6b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 8697c9e..9a090d2 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/App.js b/src/app/App.js index 63f8207..e44af1b 100644 --- a/src/app/App.js +++ b/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 ( + { + setPassword(password); + if (state.connectKey) ConnectKeyUtils.setConnectData(state.connectKey, state.publicKey, password); + setLoggedIn(true); + setSessionLogIn(true); + }} + /> + ); + } else { + return ( + { + 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 (
@@ -234,56 +289,27 @@ function App() { onConfirm={handleConfirm} /> {loggedIn &&
} - {/*
*/} - {/*
- - - - -
*/} - {appConnected ? ( - loggedIn - ? - ( -
- - - - -
- ) - : - ( - creatingPassword ? - { - setPassword(password); - if (state.connectKey) ConnectKeyUtils.setConnectKey(state.connectKey, password); - setLoggedIn(true); - setSessionLogIn(true); - }} - /> : - { - if (comparePasswords(password)) { - setLoggedIn(true); - setSessionLogIn(true); - } else { - setIncorrectPassword(true); - } - }} - /> - ) - ) : - + loggedIn + ? + ( +
+ + + + +
+ ) + : + + ) + + : + + } )} diff --git a/src/app/components/ConnectPage/ConnectPage.jsx b/src/app/components/ConnectPage/ConnectPage.jsx index 970dd65..fa7456d 100644 --- a/src/app/components/ConnectPage/ConnectPage.jsx +++ b/src/app/components/ConnectPage/ConnectPage.jsx @@ -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 ( diff --git a/src/app/store/actions.js b/src/app/store/actions.js index cf92db8..b78d818 100644 --- a/src/app/store/actions.js +++ b/src/app/store/actions.js @@ -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 }); }; diff --git a/src/app/store/store-reducer.js b/src/app/store/store-reducer.js index 1225360..b960420 100644 --- a/src/app/store/store-reducer.js +++ b/src/app/store/store-reducer.js @@ -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; } diff --git a/src/app/utils/ConnectKeyUtils.js b/src/app/utils/ConnectKeyUtils.js index 103808f..71db27b 100644 --- a/src/app/utils/ConnectKeyUtils.js +++ b/src/app/utils/ConnectKeyUtils.js @@ -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; + } } \ No newline at end of file diff --git a/src/background/background.js b/src/background/background.js index 35c3ba1..a84e60a 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -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; } diff --git a/src/background/wallet.js b/src/background/wallet.js index 4cfc0aa..495e791 100644 --- a/src/background/wallet.js +++ b/src/background/wallet.js @@ -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()); } \ No newline at end of file