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 fe26861 Added authentication with ResVault
fe26861 is described below
commit fe2686171079f4703f673f5570e87b0926ea4520
Author: Apratim Shukla <[email protected]>
AuthorDate: Tue Oct 1 22:45:46 2024 -0700
Added authentication with ResVault
---
public/background.js | 124 ++++++++++++++++-
public/content.js | 345 ++++++++++++++++++++++++++++++++++--------------
src/pages/Dashboard.jsx | 12 +-
3 files changed, 375 insertions(+), 106 deletions(-)
diff --git a/public/background.js b/public/background.js
index 631d374..0f09cf3 100644
--- a/public/background.js
+++ b/public/background.js
@@ -1,5 +1,3 @@
-// background.js
-
let faviconUrls = {};
// Helper functions
@@ -103,6 +101,14 @@ function updateFaviconUrl(tabId, changeInfo, tab) {
// Add the listener for tab updates
chrome.tabs.onUpdated.addListener(updateFaviconUrl);
+// Function to generate UUID v4
+function generateUUID() {
+ // Public Domain/MIT
+ return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
+ (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c /
4).toString(16)
+ );
+}
+
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.action === "storeKeys") {
(async function() {
@@ -405,9 +411,6 @@ chrome.runtime.onMessage.addListener(function (request,
sender, sendResponse) {
}
`;
- // Log the mutation for debugging
- console.log('Mutation:', mutation);
-
const response = await fetch(decryptedUrl, {
method: 'POST',
headers: {
@@ -440,6 +443,117 @@ chrome.runtime.onMessage.addListener(function (request,
sender, sendResponse) {
});
})();
+ return true; // Keep the message channel open for async sendResponse
+ } else if (request.action === 'submitLoginTransaction') {
+ (async function() {
+ console.log('Handling submitLoginTransaction action');
+ console.log('Sender:', sender);
+ let senderUrl = null;
+ if (sender.tab && sender.tab.url) {
+ senderUrl = sender.tab.url;
+ } else if (sender.url) {
+ senderUrl = sender.url;
+ } else if (sender.origin) {
+ senderUrl = sender.origin;
+ } else {
+ console.error('Sender URL is undefined');
+ sendResponse({ success: false, error: 'Cannot determine sender
URL' });
+ return;
+ }
+ console.log('Sender URL:', senderUrl);
+
+ const domain = getBaseDomain(senderUrl);
+ console.log('Domain:', domain);
+
+ chrome.storage.local.get(['keys', 'connectedNets'], async function
(result) {
+ const keys = result.keys || {};
+ const connectedNets = result.connectedNets || {};
+ console.log('ConnectedNets:', connectedNets);
+ const net = connectedNets[domain];
+ console.log('Net for domain:', domain, 'is', net);
+
+ if (keys[domain] && keys[domain][net]) {
+ const { publicKey, privateKey, url, exportedKey } =
keys[domain][net];
+
+ try {
+ // Import the key material from JWK format
+ const keyMaterial = await crypto.subtle.importKey(
+ 'jwk',
+ exportedKey,
+ { name: 'AES-GCM' },
+ true,
+ ['encrypt', 'decrypt']
+ );
+
+ const decryptedPublicKey = await
decryptData(publicKey.ciphertext, publicKey.iv, keyMaterial);
+ const decryptedPrivateKey = await
decryptData(privateKey.ciphertext, privateKey.iv, keyMaterial);
+ const decryptedUrl = await decryptData(url.ciphertext,
url.iv, keyMaterial);
+
+ // Prepare asset data with current timestamp and
unique login_transaction_id
+ const currentTimestamp = Math.floor(Date.now() / 1000);
+
+ let loginTransactionId = '';
+ if (crypto.randomUUID) {
+ loginTransactionId =
crypto.randomUUID().replace(/[^a-zA-Z0-9]/g, '');
+ } else {
+ loginTransactionId =
generateUUID().replace(/[^a-zA-Z0-9]/g, '');
+ }
+
+ const assetData = JSON.stringify({
+ data: {
+ login_timestamp: currentTimestamp,
+ login_transaction_id: loginTransactionId
+ }
+ });
+
+ // Construct the GraphQL mutation
+ const mutation = `
+ mutation {
+ postTransaction(data: {
+ operation: "CREATE"
+ amount: 1
+ signerPublicKey:
"${escapeGraphQLString(decryptedPublicKey)}"
+ signerPrivateKey:
"${escapeGraphQLString(decryptedPrivateKey)}"
+ recipientPublicKey:
"${escapeGraphQLString(decryptedPublicKey)}"
+ asset: """${assetData}"""
+ }) {
+ id
+ }
+ }
+ `;
+
+ const response = await fetch(decryptedUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ query: mutation }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Network response was not ok:
${response.statusText}`);
+ }
+
+ const resultData = await response.json();
+ if (resultData.errors) {
+ console.error('GraphQL errors:',
resultData.errors);
+ sendResponse({ success: false, errors:
resultData.errors });
+ } else {
+ console.log('Login transaction submitted
successfully:', resultData.data);
+ sendResponse({ success: true, data:
resultData.data });
+ }
+ } catch (error) {
+ console.error('Error submitting login transaction:',
error);
+ sendResponse({ success: false, error: error.message });
+ }
+ } else {
+ console.error('No keys found for domain:', domain, 'and
net:', net);
+ console.log('Available keys:', keys);
+ sendResponse({ error: "No keys found for domain and net"
});
+ }
+ });
+ })();
+
return true; // Keep the message channel open for async sendResponse
}
});
\ No newline at end of file
diff --git a/public/content.js b/public/content.js
index 6345305..bc38506 100644
--- a/public/content.js
+++ b/public/content.js
@@ -3,11 +3,10 @@
// 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 '';
+ return '';
}
}
@@ -22,10 +21,12 @@ function sendMessageToPage(request) {
}
// Add event listener to listen for messages from the web page
-window.addEventListener("message", (event) => {
+window.addEventListener('message', (event) => {
if (event.source === window) {
- if (event.data.direction === "commit-page-script") {
+ if (event.data.direction === 'commit') {
handleCommitOperation(event);
+ } else if (event.data.direction === 'login') {
+ handleLoginOperation(event);
}
}
});
@@ -34,19 +35,168 @@ window.addEventListener("message", (event) => {
function handleCommitOperation(event) {
const { amount, data, recipient } = event.data;
- console.log('Received in handleCommitOperation:', { amount, data, recipient
});
-
// Ensure the amount is present before proceeding
if (amount.trim() !== '') {
- const modal = createOrUpdateModal(amount);
- setupModalEventListeners(modal, { amount, data, recipient });
+ const modal = createOrUpdateModal(amount, { amount, data, recipient });
+ // Event delegation handles event listeners
}
}
+// Handle login operation and display the login modal
+function handleLoginOperation(event) {
+ // Create or update the login modal
+ const modal = createOrUpdateLoginModal();
+ // Event delegation handles event listeners
+}
+
+// Create or update the login modal
+function createOrUpdateLoginModal() {
+ let modal = document.getElementById('resVaultLoginModal');
+ const modalContent = generateLoginModalContent();
+
+ if (!modal) {
+ // Create the modal if it doesn't exist
+ modal = document.createElement('div');
+ modal.id = 'resVaultLoginModal';
+ modal.style.cssText = `
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ display: block;
+ `;
+ modal.innerHTML = modalContent;
+ document.body.appendChild(modal);
+
+ // Attach a single event listener to the modal for event delegation
+ modal.addEventListener('click', async (event) => {
+ if (event.target.id === 'resVaultLoginModalClose') {
+ modal.style.display = 'none';
+ } else if (event.target.id === 'resVaultLoginModalAuthenticate') {
+ const isConnected = await checkConnectionStatus();
+ if (isConnected) {
+ handleLoginTransactionSubmit();
+ } else {
+ alert('Please connect to the website and net in ResVault
extension.');
+ }
+ modal.style.display = 'none';
+ }
+ });
+ } else {
+ // Update the modal content if it already exists
+ modal.innerHTML = modalContent;
+ modal.style.display = 'block'; // Ensure the modal is visible again
+ }
+
+ return modal;
+}
+
+// Generate the HTML content for the login modal
+function generateLoginModalContent() {
+ return `
+ <div id="resVaultLoginModalContent" style="
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: #0f0638;
+ padding: 20px;
+ border-radius: 10px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ z-index: 1001;
+ font-family: Poppins, sans-serif;
+ width: 300px;
+ ">
+ <div style="width: calc(100%);
+ padding: 20px;
+ background-color: #291f57;
+ border-radius: 15px;
+ margin-bottom: 20px;
+ box-shadow: 5px 5px 35px -14px rgba(0,0,0,.17);
+ text-align: center;">
+ <p style="
+ color: #fff;
+ font-size: 14px;
+ font-weight: bold;
+ margin: 0;
+ ">
+ Authenticate with Res<span style="color: #47e7ce;">Vault</span>
+ </p>
+ </div>
+
+ <div style="display: flex; justify-content: space-between;">
+ <button id="resVaultLoginModalClose" style="
+ background-color: #291f57;
+ border: 1px #47e7ce solid;
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 5px;
+ cursor: pointer;
+ width: 50%;
+ box-sizing: border-box;
+ margin-right: 5px;">
+ Close
+ </button>
+ <button id="resVaultLoginModalAuthenticate" style="
+ background: linear-gradient(60deg, #47e7ce, #4fa8c4);
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 5px;
+ cursor: pointer;
+ width: 50%;
+ box-sizing: border-box;">
+ Authenticate
+ </button>
+ </div>
+ </div>
+ `;
+}
+
+// Check if the user is connected to the website and net
+function checkConnectionStatus() {
+ return new Promise((resolve) => {
+ const domain = window.location.hostname;
+
+ chrome.storage.local.get(['keys', 'connectedNets'], (result) => {
+ const keys = result.keys || {};
+ const connectedNets = result.connectedNets || {};
+
+ const net = connectedNets[domain];
+ if (net && keys[domain] && keys[domain][net]) {
+ resolve(true);
+ } else {
+ resolve(false);
+ }
+ });
+ });
+}
+
+// Handle login transaction submission and send data to background script
+function handleLoginTransactionSubmit() {
+ chrome.runtime.sendMessage(
+ {
+ action: 'submitLoginTransaction',
+ },
+ (response) => {
+ if (response) {
+ // Send the response to the page script
+ window.postMessage({ type: 'FROM_CONTENT_SCRIPT', data: response },
'*');
+ }
+ }
+ );
+}
+
+// Existing functions for commit operation remain unchanged
+
// Create or update the modal with the necessary transaction details
-function createOrUpdateModal(amount) {
+function createOrUpdateModal(amount, transactionData) {
let modal = document.getElementById('resVaultModal');
- const modalContent = generateModalContent("COMMIT", amount);
+ const modalContent = generateModalContent('COMMIT', amount);
if (!modal) {
// Create the modal if it doesn't exist
@@ -64,90 +214,98 @@ function createOrUpdateModal(amount) {
`;
modal.innerHTML = modalContent;
document.body.appendChild(modal);
+
+ // Attach a single event listener to the modal for event delegation
+ modal.addEventListener('click', (event) => {
+ if (event.target.id === 'resVaultModalClose') {
+ modal.style.display = 'none';
+ } else if (event.target.id === 'resVaultModalSubmit') {
+ handleTransactionSubmit(modal.transactionData); // Use
modal.transactionData
+ modal.style.display = 'none';
+ }
+ });
} else {
// Update the modal content if it already exists
- const amountDisplay = modal.querySelector("#amountDisplay");
- if (amountDisplay) {
- amountDisplay.textContent = amount;
- } else {
- // If the amount display element is not found, recreate the modal content
- modal.innerHTML = modalContent;
- }
+ modal.innerHTML = modalContent;
modal.style.display = 'block'; // Ensure the modal is visible again
}
+ // Update transactionData on the modal element
+ modal.transactionData = transactionData;
+
return modal;
}
-// Generate the HTML content for the modal
+// Generate the HTML content for the commit modal
function generateModalContent(operation, amount) {
return `
<div id="resVaultModalContent" style="
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background-color: #0f0638;
- padding: 20px;
- border-radius: 10px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
- z-index: 1001;
- font-family: Poppins, sans-serif;
- width: 300px;
-">
-<div style="width: calc(100%);
- padding: 20px;
- background-color: #291f57;
- border-radius: 15px;
- margin-bottom: 20px;
- box-shadow: 5px 5px 35px -14px rgba(0,0,0,.17);">
- <form id="Form" style="margin: auto;">
- <div style="width: 100%; margin-bottom: 10px;">
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: #0f0638;
+ padding: 20px;
+ border-radius: 10px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ z-index: 1001;
+ font-family: Poppins, sans-serif;
+ width: 300px;
+ ">
+ <div style="width: calc(100%);
+ padding: 20px;
+ background-color: #291f57;
+ border-radius: 15px;
+ margin-bottom: 20px;
+ box-shadow: 5px 5px 35px -14px rgba(0,0,0,.17);">
+ <form id="Form" style="margin: auto;">
+ <div style="width: 100%; margin-bottom: 10px;">
<div style="padding: 0;
- width: 100%;
- margin: 0;
- overflow: hidden;
- border-radius: 0px;
- color: #fff;
- border-bottom: 1px rgba(255, 255, 255, 0.3) solid;">
- <p>
+ width: 100%;
+ margin: 0;
+ overflow: hidden;
+ border-radius: 0px;
+ color: #fff;
+ border-bottom: 1px rgba(255, 255, 255, 0.3) solid;">
+ <p>
Operation: ${operation}
- </p>
+ </p>
</div>
- </div>
- <div style="width: 100%;
- margin-bottom: 10px; display: flex; align-items: center;
justify-content: space-between;">
- <p style="width: calc(((100% / 3) * 2) - 35px);
- border: none;
- background-color: transparent;
- padding: 10px 0px;
- border-radius: 0px;
- border-bottom: 1px rgba(255, 255, 255, 0.3) solid;
- color: #fff;
- text-align: center;
- ">
- ${amount}
+ </div>
+ <div style="width: 100%;
+ margin-bottom: 10px; display: flex; align-items: center;
justify-content: space-between;">
+ <p id="amountDisplay" style="width: calc(((100% / 3) * 2) - 35px);
+ border: none;
+ background-color: transparent;
+ padding: 10px 0px;
+ border-radius: 0px;
+ border-bottom: 1px rgba(255, 255, 255, 0.3) solid;
+ color: #fff;
+ text-align: center;
+ ">
+ ${amount}
</p>
<div style="display: flex;
- align-items: center;"><div style="display: block; font-family:
Arial, Helvetica, sans-serif; font-size: 20px; font-weight: bold; color:
#f0f0f0; background-color: #808080; border-radius: 50%; width: 30px; height:
30px; display: flex; align-items: center; justify-content: center; margin:
10px; transform: rotate(20deg);">R</div><span style="color: #fff; font-weight:
600; padding-left: 5px;
- ">RoK</span></div>
- </div>
- <p style="font-size: small; color: #fff">Powered by Res<strong
style="color: #47e7ce">Vault</strong></p>
- </div>
- </form>
- <span style="display: flex;">
- <button id="resVaultModalClose" style="
+ align-items: center;">
+ <div style="display: block; font-family: Arial, Helvetica,
sans-serif; font-size: 20px; font-weight: bold; color: #f0f0f0;
background-color: #808080; border-radius: 50%; width: 30px; height: 30px;
display: flex; align-items: center; justify-content: center; margin: 10px;
transform: rotate(20deg);">R</div>
+ <span style="color: #fff; font-weight: 600; padding-left:
5px;">RoK</span>
+ </div>
+ </div>
+ <p style="font-size: small; color: #fff">Powered by Res<strong
style="color: #47e7ce">Vault</strong></p>
+ </form>
+ </div>
+ <span style="display: flex;">
+ <button id="resVaultModalClose" style="
background-color: #291f57;
- border: 1px #47e7ce solid;
+ border: none;
color: white;
- border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
width: 50%;
box-sizing: border-box;
margin-right: 5px;">Cancel</button>
- <button id="resVaultModalSubmit" style="
+ <button id="resVaultModalSubmit" style="
background: linear-gradient(60deg, #47e7ce, #4fa8c4);
color: white;
border: none;
@@ -156,37 +314,24 @@ function generateModalContent(operation, amount) {
cursor: pointer;
width: 50%;
box-sizing: border-box;">Submit</button>
- </span>
-</div>
-</div>`;
-}
-
-// Setup event listeners for modal interactions
-function setupModalEventListeners(modal, transactionData) {
- const closeModalButton = modal.querySelector('#resVaultModalClose');
- const submitButton = modal.querySelector('#resVaultModalSubmit');
-
- closeModalButton.addEventListener('click', () => {
- modal.style.display = 'none';
- });
-
- submitButton.addEventListener('click', () => {
- handleTransactionSubmit(transactionData);
- modal.style.display = 'none';
- });
+ </span>
+ </div>`;
}
// Handle transaction submission and send data to background script
function handleTransactionSubmit({ amount, data, recipient }) {
- chrome.runtime.sendMessage({
- action: 'submitTransaction',
- amount,
- data,
- recipient
- }, (response) => {
- if (response) {
- // Send the response to the page script
- window.postMessage({ type: 'FROM_CONTENT_SCRIPT', data: response }, '*');
+ chrome.runtime.sendMessage(
+ {
+ action: 'submitTransaction',
+ amount,
+ data,
+ recipient,
+ },
+ (response) => {
+ if (response) {
+ // Send the response to the page script
+ window.postMessage({ type: 'FROM_CONTENT_SCRIPT', data: response },
'*');
+ }
}
- });
+ );
}
\ No newline at end of file
diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx
index caf28f1..ce52a3c 100644
--- a/src/pages/Dashboard.jsx
+++ b/src/pages/Dashboard.jsx
@@ -490,6 +490,11 @@ function Dashboard() {
}
};
+ // **New function to handle favicon load error**
+ const handleFaviconError = () => {
+ setFaviconUrl(''); // This will trigger the globe icon to display
+ };
+
return (
<>
<div className="lottie-background">
@@ -624,7 +629,12 @@ function Dashboard() {
<div className="icon-container"
onClick={toggleConnection}>
{faviconUrl ? (
- <img src={faviconUrl} alt="Favicon"
className={`icon ${isConnected ? 'connected' : ''}`} />
+ <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>
)}