diff --git a/components/Myvpncard.tsx b/components/Myvpncard.tsx index da89acc..48a205b 100644 --- a/components/Myvpncard.tsx +++ b/components/Myvpncard.tsx @@ -8,6 +8,9 @@ import QrCode from "./qrCode"; import dlt from "../public/dlt.png"; import Image from "next/image"; import Link from "next/link"; +import CryptoJS from 'crypto-js'; + +import DownloadFromWalrusButton from "./walrus/DownloadFromWalrusButton"; const REACT_APP_GATEWAY_URL = process.env.NEXT_PUBLIC_GATEWAY_URL; const EREBRUS_GATEWAY_URL = process.env.NEXT_PUBLIC_EREBRUS_BASE_URL; @@ -82,6 +85,8 @@ const MyVpnCard: React.FC = ({ const [delvpn, setdelvpn] = useState(false); const [qr, setqr] = useState(false); const [formattedDate, setFormattedDate] = useState(''); + const [showDownloadModal, setShowDownloadModal] = useState(false); + const [downloadPin, setDownloadPin] = useState(''); useEffect(() => { if (metaData) { @@ -117,6 +122,60 @@ const MyVpnCard: React.FC = ({ onReviewDeleted(); // Call the callback function when a review is deleted } }; + const handleDownloadClick = () => { + setShowDownloadModal(true); + }; + + const downloadConfig = async () => { + if (downloadPin.length !== 6) { + alert('Please enter a valid 6-digit PIN'); + return; + } + + try { + const auth = Cookies.get("erebrus_token"); + const walletAddress = Cookies.get('erebrus_wallet') || ''; + + if (!walletAddress) { + throw new Error('Wallet address not found'); + } + + // Fetch the encrypted blobId from Erebrus + const response = await axios.get( + `${EREBRUS_GATEWAY_URL}api/v1.0/erebrus/client/${metaData.UUID}/blobId`, + { + headers: { + Authorization: `Bearer ${auth}`, + }, + } + ); + + const encryptedBlobId = response.data.payload.blobId; + const decryptedBlobId = decryptBlobId(encryptedBlobId, walletAddress, downloadPin); + + // Fetch the config from Walrus + const walrusResponse = await axios.get( + `https://aggregator-devnet.walrus.space/v1/${decryptedBlobId}`, + { responseType: 'blob' } + ); + + // Download the config file + const blob = new Blob([walrusResponse.data], { type: 'text/plain' }); + saveAs(blob, `${metaData.name}.conf`); + + setShowDownloadModal(false); + setDownloadPin(''); + } catch (error) { + console.error('Error downloading config:', error); + alert('Failed to download config. Please check your PIN and try again.'); + } + }; + + const decryptBlobId = (encryptedBlobId: string, walletAddress: string, pin: string) => { + const key = `${walletAddress}-${pin}`; // Reconstruct the key + const bytes = CryptoJS.AES.decrypt(encryptedBlobId, key); + return bytes.toString(CryptoJS.enc.Utf8); + }; const deletevpn = async (id: string, region: string) => { setLoading(true); @@ -188,60 +247,24 @@ const MyVpnCard: React.FC = ({ - {/*
- -
*/} +
- {/* - */} - -
- - {/*
-
- -
{ - - }}> - -
-
- -
- -
-
- -
-
*/} + + + + {qr && ( @@ -343,6 +366,55 @@ const MyVpnCard: React.FC = ({ > Delete + + + + + + )} + {showDownloadModal && ( +
+
+
+
+

+ Enter PIN to Download +

+
+
+ setDownloadPin(e.target.value)} + maxLength={6} + placeholder="Enter 6-digit PIN" + className="w-full p-2 rounded-md text-black" + /> +
+
+ +
diff --git a/components/walrus/DownloadFromWalrusButton.tsx b/components/walrus/DownloadFromWalrusButton.tsx new file mode 100644 index 0000000..2a1ceee --- /dev/null +++ b/components/walrus/DownloadFromWalrusButton.tsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import axios from 'axios'; + +interface DownloadFromWalrusButtonProps { + blobId: string; + clientName: string; +} + +const DownloadFromWalrusButton: React.FC = ({ blobId, clientName }) => { + const [isDownloading, setIsDownloading] = useState(false); + + const downloadFromWalrus = async () => { + setIsDownloading(true); + try { + const response = await axios.get(`https://aggregator-devnet.walrus.space/v1/${blobId}`, { + responseType: 'blob' + }); + + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', `${clientName}.conf`); + document.body.appendChild(link); + link.click(); + link.remove(); + } catch (error) { + console.error('Error downloading from Walrus:', error); + alert('Failed to download the configuration file.'); + } finally { + setIsDownloading(false); + } + }; + + return ( + + ); +}; + +export default DownloadFromWalrusButton; \ No newline at end of file diff --git a/components/walrus/SaveToWalrusButton.tsx b/components/walrus/SaveToWalrusButton.tsx new file mode 100644 index 0000000..90cb4ce --- /dev/null +++ b/components/walrus/SaveToWalrusButton.tsx @@ -0,0 +1,145 @@ +import React, { useState } from 'react'; +import axios from 'axios'; +import Cookies from 'js-cookie'; +import CryptoJS from 'crypto-js'; // For encryption + +interface SaveToWalrusButtonProps { + configFile: string; + vpnName: string; + clientUUID: string; +} + +const SaveToWalrusButton: React.FC = ({ configFile, vpnName, clientUUID }) => { + const [isSaving, setIsSaving] = useState(false); + const [saveStatus, setSaveStatus] = useState<'idle' | 'success' | 'error'>('idle'); + const [isPinModalOpen, setIsPinModalOpen] = useState(false); + const [pin, setPin] = useState(''); + const [confirmPin, setConfirmPin] = useState(''); + + // Encryption function using wallet address and PIN + const encryptBlobId = (blobId: string, walletAddress: string, pin: string) => { + const key = `${walletAddress}-${pin}`; // Combine wallet address and PIN for encryption key + return CryptoJS.AES.encrypt(blobId, key).toString(); // Encrypt the blobId + }; + + // Open modal to enter the PIN + const handleSave = async () => { + setIsPinModalOpen(true); // Open the modal + }; + + // Function to save to Walrus and update Erebrus with encrypted blobId + const saveToWalrus = async () => { + if (pin.length !== 6) { + alert('Please enter a valid 6-digit PIN'); + return; + } + + if (pin !== confirmPin) { + alert('PIN and confirmation PIN do not match'); + return; + } + + setIsSaving(true); + setSaveStatus('idle'); + + try { + // Save to Walrus + const walrusResponse = await axios.put( + 'https://publisher-devnet.walrus.space/v1/store', + configFile, + { + params: { epochs: 5 }, + headers: { 'Content-Type': 'text/plain' }, + } + ); + + const blobId = walrusResponse.data.newlyCreated.blobObject.blobId; + + // Retrieve user's wallet address from cookies + const walletAddress = Cookies.get('erebrus_wallet') || ''; + if (!walletAddress) { + throw new Error('Wallet address not found'); + } + + // Encrypt blobId with wallet address and PIN + const encryptedBlobId = encryptBlobId(blobId, walletAddress, pin); + + // Update encrypted blobId in Erebrus Gateway + const erebrusGatewayUrl = process.env.NEXT_PUBLIC_EREBRUS_BASE_URL || ''; + await axios.put(`${erebrusGatewayUrl}api/v1.0/erebrus/client/${clientUUID}/blobId`, + { blobId: encryptedBlobId }, + { + headers: { + 'Authorization': `Bearer ${Cookies.get('erebrus_token')}`, + 'Content-Type': 'application/json', + }, + } + ); + + setSaveStatus('success'); + } catch (error) { + console.error('Error saving to Walrus or updating blobId:', error); + setSaveStatus('error'); + } finally { + setIsSaving(false); + setIsPinModalOpen(false); // Close the modal after saving + } + }; + + return ( + <> + + + {/* Modal for PIN and Confirm PIN entry */} + {isPinModalOpen && ( +
+
+

Enter PIN and Confirm PIN

+ setPin(e.target.value)} + maxLength={6} + placeholder="Enter 6-digit PIN" + className="border border-gray-300 p-2 rounded-md w-full mb-4" + /> + setConfirmPin(e.target.value)} + maxLength={6} + placeholder="Confirm PIN" + className="border border-gray-300 p-2 rounded-md w-full" + /> +
+ + +
+
+
+ )} + + ); +}; + +export default SaveToWalrusButton; diff --git a/package.json b/package.json index 1cb6b22..bdd0beb 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "react-intersection-observer": "^9.13.0", "react-leaflet": "^4.2.1", "react-mapbox-gl": "^5.1.1", + "react-pin-input": "^1.3.1", "react-responsive-carousel": "^3.2.23", "react-toastify": "^9.1.1", "react-world-countries-map": "^1.1.0", diff --git a/pages/subscription.tsx b/pages/subscription.tsx index ad6d94a..f056539 100644 --- a/pages/subscription.tsx +++ b/pages/subscription.tsx @@ -14,6 +14,8 @@ import Button from "../components/Button"; import { useRouter } from "next/router"; import Image from "next/image"; import SingleSignerTransaction from "../components/transactionFlow/SingleSigner"; +import SaveToWalrusButton from "../components/walrus/SaveToWalrusButton"; +import DownloadFromWalrusButton from "../components/walrus/DownloadFromWalrusButton"; const REACT_APP_GATEWAY_URL = process.env.NEXT_PUBLIC_GATEWAY_URL; const EREBRUS_GATEWAY_URL = process.env.NEXT_PUBLIC_EREBRUS_BASE_URL; const mynetwork = process.env.NEXT_PUBLIC_NETWORK; @@ -73,6 +75,7 @@ const Subscription = () => { const [selectedIndex, setSelectedIndex] = useState(null); const { account, connected, network, signMessage } = useWallet(); const router = useRouter(); + const [clientUUID, setClientUUID] = useState(''); let sendable = isSendableNetwork(connected, network?.name); const bg = { @@ -199,6 +202,7 @@ const Subscription = () => { if (response.status === 200) { const responseData = await response.json(); setVpnName(responseData.payload.client.Name); + setClientUUID(responseData.payload.client.UUID); setFormData(initialFormData); console.log("vpn data", responseData); @@ -1251,6 +1255,11 @@ const Subscription = () => {
+
@@ -1273,7 +1282,7 @@ const Subscription = () => {
)} - + {loading && (