implement secure connection

This commit is contained in:
jejolare 2024-02-16 22:42:45 +07:00
parent 94d0685aec
commit 0ff8814561
9 changed files with 221 additions and 153 deletions

1
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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 />
}
</>
)}

View file

@ -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 (

View file

@ -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
});
};

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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());
}