diff --git a/.gitignore b/.gitignore index 547186e..1e844bd 100644 --- a/.gitignore +++ b/.gitignore @@ -189,4 +189,13 @@ pnpm-lock.yaml packages/*/node_modules packages/*/dist packages/*/build -packages/*/.next \ No newline at end of file +packages/*/.next + +# Cursor +cursor.rules + +.cursor/ +.cursorrules +.cursorignore + + diff --git a/packages/Extenstions/.gitignore b/packages/Extenstions/.gitignore new file mode 100644 index 0000000..c03fbbf --- /dev/null +++ b/packages/Extenstions/.gitignore @@ -0,0 +1,27 @@ +# Environment files +.env +.env.local +.env.development +.env.test +.env.production + +# Node modules (if any build tools are added) +node_modules/ + +# Build outputs +dist/ +build/ + +# Temporary files +*.tmp +*.temp + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/packages/Extenstions/README.md b/packages/Extenstions/README.md index 1ed71d5..35d5503 100644 --- a/packages/Extenstions/README.md +++ b/packages/Extenstions/README.md @@ -11,14 +11,26 @@ This Chrome extension allows users to autofill card and email details from their ## Setup +### Environment Configuration + +⚠️ **IMPORTANT**: Before setting up the extension, you must configure the environment variables for security. + +**See [SETUP.md](./SETUP.md) for detailed environment configuration instructions.** + +Quick setup: +1. Create a `.env` file in this directory +2. Add: `INDEXED_DB_AES_KEY="your_key_here"` +3. Get the key from your team or the frontend-web `.env.local` file + ### Development Setup 1. Clone this repository -2. Open Chrome and navigate to `chrome://extensions/` -3. Enable "Developer mode" in the top-right corner -4. Click "Load unpacked" and select the `packages/Extenstions` directory -5. Note the Extension ID assigned by Chrome (you'll need it for the next step) -6. Update the `EXTENSION_ID` constant in `packages/frontend-web/app/extension-login/page.tsx` with your extension ID +2. **Configure environment variables** (see SETUP.md) +3. Open Chrome and navigate to `chrome://extensions/` +4. Enable "Developer mode" in the top-right corner +5. Click "Load unpacked" and select the `packages/Extenstions` directory +6. Note the Extension ID assigned by Chrome (you'll need it for the next step) +7. Update the `EXTENSION_ID` constant in `packages/frontend-web/app/extension-login/page.tsx` with your extension ID ### Frontend Setup diff --git a/packages/Extenstions/SETUP.md b/packages/Extenstions/SETUP.md new file mode 100644 index 0000000..2602ddd --- /dev/null +++ b/packages/Extenstions/SETUP.md @@ -0,0 +1,86 @@ +# Zecrypt Browser Extension Setup + +## Environment Configuration + +⚠️ **SECURITY NOTICE**: This extension requires proper environment configuration to work securely. Never commit sensitive keys to version control. + +### Development Setup + +1. **Create Environment File** + + Create a `.env` file in the `packages/Extenstions/` directory: + + ```bash + cd packages/Extenstions/ + cp .env.example .env # If example exists, or create manually + ``` + +2. **Configure Environment Variables** + + Add the following to your `.env` file: + + ``` + INDEXED_DB_AES_KEY="HxmfPmPwqQZ3gHKwfHXi6TmPwVDppr0oDKyPwCdopDI=" + ``` + + **Note**: The key should match the `NEXT_PUBLIC_INDEXED_DB_AES_KEY` from the frontend-web `.env.local` file. + +3. **Verify Setup** + + - The `.env` file should NOT be committed to git (it's already in `.gitignore`) + - Check that the extension loads without console errors + - Verify that crypto operations work correctly + +### Production Deployment + +For production environments, consider: + +1. **Build-time Injection**: Use a build script to inject environment variables +2. **Secure Key Management**: Use proper secrets management systems +3. **Key Rotation**: Implement regular key rotation procedures + +### File Structure + +``` +packages/Extenstions/ +├── .env # Your environment file (NOT committed) +├── .gitignore # Ensures .env is ignored +├── config.js # Configuration loader +├── crypto-utils.js # Crypto utilities (uses config) +├── background.js # Background script (initializes config) +└── ... other files +``` + +### Troubleshooting + +**Error: "Extension configuration not loaded"** +- Ensure `.env` file exists in the extensions directory +- Check that `INDEXED_DB_AES_KEY` is properly set in `.env` +- Verify the key format (should be base64 encoded) + +**Error: "Configuration key not found"** +- Check that the key in `.env` matches exactly: `INDEXED_DB_AES_KEY` +- Ensure there are no extra spaces or characters +- Verify the file is saved properly + +**Error: "Invalid encryption key format"** +- The key should be a valid base64 string +- Check that the key length is correct (32 bytes when decoded) +- Ensure the key matches the one used in the web application + +### Security Best Practices + +1. **Never hardcode keys** in source code +2. **Use different keys** for different environments (dev/staging/prod) +3. **Rotate keys regularly** and update all environments +4. **Restrict access** to environment files and key management systems +5. **Monitor usage** and implement key usage auditing + +### Getting the Key + +The `INDEXED_DB_AES_KEY` should be obtained from: +1. Your development team's secure key management system +2. The frontend-web application's environment configuration +3. Your organization's secrets management platform + +Contact your development team if you don't have access to the required keys. \ No newline at end of file diff --git a/packages/Extenstions/background.js b/packages/Extenstions/background.js index c356b08..47b4b19 100644 --- a/packages/Extenstions/background.js +++ b/packages/Extenstions/background.js @@ -1,9 +1,20 @@ // Base API URL const API_BASE_URL = 'https://preview.api.zecrypt.io/api/v1/web'; -// Import crypto utilities +// Import configuration and crypto utilities +importScripts('config.js'); importScripts('crypto-utils.js'); +// Initialize configuration when extension starts +(async function initializeExtension() { + try { + await ExtensionConfig.initConfig(); + } catch (error) { + console.error('Failed to load extension configuration:', error); + console.error('Please ensure you have created a .env file in the extensions directory with the required keys'); + } +})(); + // Helper function to check if URL is accessible for script injection function isValidUrl(url) { // Exclude chrome:// URLs, chrome-extension:// URLs, and other restricted schemes @@ -56,7 +67,6 @@ async function storeProjectAesKey(projectAesKey) { chrome.storage.local.set({ zecryptProjectAesKey: encryptedKey }); - console.log('Project AES key stored securely'); } catch (error) { console.error('Error storing project AES key:', error); } @@ -69,7 +79,6 @@ async function getDecryptedProjectAesKey() { if (result.zecryptProjectAesKey) { try { const decryptedKey = await CryptoUtils.decryptFromLocalStorage(result.zecryptProjectAesKey); - console.log('Project AES key retrieved and decrypted'); resolve(decryptedKey); } catch (error) { console.error('Error decrypting project AES key:', error); @@ -107,7 +116,6 @@ async function processCardData(cardRaw) { const decryptedData = await CryptoUtils.decryptDataField(cardRaw.data, projectAesKey); const parsedData = JSON.parse(decryptedData); - console.log('Card data decrypted successfully'); return { ...cardRaw, cardNumber: parsedData.number || 'undefined', @@ -174,8 +182,6 @@ async function processEmailData(emailRaw) { const decryptedData = await CryptoUtils.decryptDataField(emailRaw.data, projectAesKey); const parsedData = JSON.parse(decryptedData); - console.log('Email data decrypted successfully'); - console.log("[Zecrypt Debug] Decrypted Email Password:", parsedData.password); return { ...emailRaw, email: parsedData.email_address || 'undefined', @@ -192,7 +198,6 @@ async function processEmailData(emailRaw) { // Data might not be encrypted (legacy format) try { const parsedData = JSON.parse(emailRaw.data); - console.log("[Zecrypt Debug] Decrypted Email Password (legacy):", parsedData.password); return { ...emailRaw, email: parsedData.email_address || 'undefined', @@ -268,7 +273,6 @@ function getCards() { apiRequest(ENDPOINTS.cards) .then(async function(response) { const cards = response.data || []; - console.log(`Processing ${cards.length} cards for decryption`); const processedCards = await Promise.all(cards.map(processCardData)); resolve({ @@ -291,7 +295,6 @@ function getEmails() { apiRequest(ENDPOINTS.emails) .then(async function(response) { const emails = response.data || []; - console.log(`Processing ${emails.length} emails for decryption`); const processedEmails = await Promise.all(emails.map(processEmailData)); resolve({ @@ -311,7 +314,6 @@ function getEmails() { // Enhanced external message listener to handle project AES key chrome.runtime.onMessageExternal.addListener( (message, sender, sendResponse) => { - console.log('External message received:', message); if (message.type === 'LOGIN' && message.token) { // Store the token and workspace/project IDs securely in chrome.storage.local chrome.storage.local.set({ @@ -323,12 +325,9 @@ chrome.runtime.onMessageExternal.addListener( console.error('Error storing tokens:', chrome.runtime.lastError); sendResponse({ success: false, error: chrome.runtime.lastError.message }); } else { - console.log('Token and workspace data stored successfully'); - // Store project AES key securely if provided if (message.projectAesKey) { await storeProjectAesKey(message.projectAesKey); - console.log('Project AES key stored successfully'); } sendResponse({ success: true }); @@ -372,7 +371,6 @@ function checkLocalStorageAuth() { } }, (results) => { if (chrome.runtime.lastError) { - console.log('Script execution failed:', chrome.runtime.lastError.message); resolve({ success: false, error: chrome.runtime.lastError.message }); return; } @@ -389,12 +387,9 @@ function checkLocalStorageAuth() { console.error('Error storing auth data:', chrome.runtime.lastError); resolve({ success: false, error: chrome.runtime.lastError.message }); } else { - console.log('Auth data retrieved from localStorage and stored'); - // Store project AES key securely if provided if (authData.projectAesKey) { await storeProjectAesKey(authData.projectAesKey); - console.log('Project AES key from localStorage stored successfully'); } resolve({ success: true, authData }); @@ -404,7 +399,6 @@ function checkLocalStorageAuth() { resolve({ success: false, error: 'No auth data found' }); } }); } else { - console.log('No valid tab found for localStorage check. Current tab:', tabs[0]?.url || 'No tab'); resolve({ success: false, error: 'No accessible tab found' }); } }); @@ -432,7 +426,6 @@ function startAuthCheck() { // Stop checking after max attempts if (authCheckAttempts > MAX_AUTH_CHECK_ATTEMPTS) { - console.log('Auth check timeout after', MAX_AUTH_CHECK_ATTEMPTS, 'attempts'); stopAuthCheck(); return; } @@ -442,18 +435,15 @@ function startAuthCheck() { if (result.success) { clearInterval(authCheckInterval); authCheckInterval = null; - console.log('Authentication successful via localStorage polling'); // Notify popup if it's open try { chrome.runtime.sendMessage({ type: 'AUTH_SUCCESS' }); } catch (e) { // Popup might not be open, that's ok - console.log('Could not notify popup:', e.message); } } else if (result.error && result.error.includes('Cannot access')) { // If we get access errors, reduce polling frequency - console.log('Access error during auth check, slowing down polling'); } } catch (error) { console.error('Error during auth check:', error); @@ -491,10 +481,8 @@ function checkAuth() { // Listen for messages from popup or content scripts chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - console.log('Internal message received:', message); if (message.type === 'CHECK_AUTH') { checkAuth().then(result => { - console.log('Auth check result:', result); // If not authenticated, try checking localStorage as fallback if (!result.isAuthenticated) { @@ -509,8 +497,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { // Only start periodic checking if we don't have access errors if (!localResult.error || !localResult.error.includes('Cannot access')) { startAuthCheck(); - } else { - console.log('Skipping periodic auth check due to access restrictions'); } } }); @@ -541,10 +527,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { } // Handle logout request if (message.type === 'LOGOUT') { - chrome.storage.local.remove(['zecryptToken', 'zecryptWorkspaceId', 'zecryptProjectId', 'zecryptProjectAesKey'], () => { - console.log('Tokens and project key removed'); - sendResponse({ success: true }); - }); + chrome.storage.local.remove(['zecryptToken', 'zecryptWorkspaceId', 'zecryptProjectId', 'zecryptProjectAesKey'], () => { + sendResponse({ success: true }); + }); return true; } @@ -555,12 +540,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { zecryptWorkspaceId: message.workspaceId, zecryptProjectId: message.projectId }, async () => { - console.log('Token and workspace data stored successfully from popup'); - // Store project AES key securely if provided if (message.projectAesKey) { await storeProjectAesKey(message.projectAesKey); - console.log('Project AES key stored successfully from popup'); } sendResponse({ success: true }); @@ -630,5 +612,5 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { // Initialize when the extension is installed or updated chrome.runtime.onInstalled.addListener(() => { - console.log('Zecrypt extension installed'); + // Extension installed }); \ No newline at end of file diff --git a/packages/Extenstions/config.js b/packages/Extenstions/config.js new file mode 100644 index 0000000..b4fa945 --- /dev/null +++ b/packages/Extenstions/config.js @@ -0,0 +1,125 @@ +/** + * Configuration loader for browser extension + * Handles environment variables in browser context + * + * SECURITY NOTE: + * - Create a .env file in this directory with: INDEXED_DB_AES_KEY="your_key_here" + * - The .env file is ignored by git and should never be committed + * - In production, use build-time injection or secure configuration management + */ + +// This will hold our configuration +let CONFIG = null; + +/** + * Load configuration from environment file + * Note: This is a simplified approach for browser extensions + * In a real production setup, you'd want to use build-time injection + */ +async function loadConfigFromEnv() { + try { + // Try to fetch the .env file (this will only work in development) + const response = await fetch(chrome.runtime.getURL('.env')); + if (response.ok) { + const envText = await response.text(); + const config = {}; + + // Parse simple KEY="value" format + const lines = envText.split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith('#')) { + const [key, ...valueParts] = trimmed.split('='); + if (key && valueParts.length > 0) { + // Remove quotes from value + let value = valueParts.join('=').trim(); + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + config[key] = value; + } + } + } + + return config; + } + } catch (error) { + console.warn('Could not load .env file:', error.message); + } + + return null; +} + +/** + * Initialize configuration + * This should be called when the extension starts + */ +async function initConfig() { + // Try to load from .env file first + CONFIG = await loadConfigFromEnv(); + + // If no config loaded, check for injected config (build-time injection) + if (!CONFIG && typeof window !== 'undefined' && window.INJECTED_CONFIG) { + CONFIG = window.INJECTED_CONFIG; + } + + // If still no config, throw an error + if (!CONFIG || !CONFIG.INDEXED_DB_AES_KEY) { + throw new Error( + 'Configuration not found! Please create a .env file in the extensions directory with:\n' + + 'INDEXED_DB_AES_KEY="your_key_here"\n\n' + + 'See README.md for setup instructions.' + ); + } + + return CONFIG; +} + +/** + * Get a configuration value + */ +function getConfig(key) { + if (!CONFIG) { + throw new Error('Configuration not initialized. Call initConfig() first.'); + } + + const value = CONFIG[key]; + if (!value) { + throw new Error(`Configuration key '${key}' not found.`); + } + + return value; +} + +/** + * Get the AES encryption key for localStorage encryption + */ +function getIndexedDbAesKey() { + return getConfig('INDEXED_DB_AES_KEY'); +} + +/** + * Check if configuration is loaded + */ +function isConfigLoaded() { + return CONFIG !== null; +} + +// Export for browser extension context +const ExtensionConfig = { + initConfig, + getConfig, + getIndexedDbAesKey, + isConfigLoaded +}; + +// Make available globally +if (typeof globalThis !== 'undefined') { + globalThis.ExtensionConfig = ExtensionConfig; +} + +// Also support module exports for potential build tools +if (typeof module !== 'undefined' && module.exports) { + module.exports = ExtensionConfig; +} \ No newline at end of file diff --git a/packages/Extenstions/crypto-utils.js b/packages/Extenstions/crypto-utils.js index fc8545b..d867344 100644 --- a/packages/Extenstions/crypto-utils.js +++ b/packages/Extenstions/crypto-utils.js @@ -2,11 +2,11 @@ * Crypto utilities for the browser extension * Simplified version of the frontend-web crypto functions * Works in both service worker and regular browser contexts + * + * SECURITY NOTE: The AES key is now loaded from configuration instead of being hardcoded + * Make sure config.js is loaded and initialized before using these functions */ -// Environment variable equivalent - this should be the same as NEXT_PUBLIC_INDEXED_DB_AES_KEY -const INDEXED_DB_AES_KEY = "HxmfPmPwqQZ3gHKwfHXi6TmPwVDppr0oDKyPwCdopDI="; - // Get the appropriate global context (self for service workers, window for regular contexts) const globalContext = (function() { if (typeof self !== 'undefined') { @@ -23,6 +23,13 @@ const globalContext = (function() { */ function getStorageEncryptionKey() { try { + // Get the key from configuration instead of hardcoded value + if (!globalContext.ExtensionConfig || !globalContext.ExtensionConfig.isConfigLoaded()) { + throw new Error("Extension configuration not loaded. Please ensure config.js is loaded and initConfig() is called."); + } + + const INDEXED_DB_AES_KEY = globalContext.ExtensionConfig.getIndexedDbAesKey(); + const binaryString = atob(INDEXED_DB_AES_KEY); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { diff --git a/packages/Extenstions/manifest.json b/packages/Extenstions/manifest.json index f6cc526..2f24974 100644 --- a/packages/Extenstions/manifest.json +++ b/packages/Extenstions/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Zecrypt", - "version": "1.0", + "version": "1.1", "description": "Secure autofill for passwords, cards, and personal data from your Zecrypt vault", "permissions": [ "storage", @@ -26,17 +26,17 @@ "content_scripts": [ { "matches": [""], - "js": ["crypto-utils.js", "content.js"] + "js": ["config.js", "crypto-utils.js", "content.js"] } ], "web_accessible_resources": [ { - "resources": ["crypto-utils.js"], + "resources": ["config.js", "crypto-utils.js", ".env"], "matches": [""] } ], "externally_connectable": { - "matches": ["*://*.zecrypt.com/*", "*://localhost:*/*"] + "matches": ["*://*.zecrypt.io/*"] }, "icons": { "16": "icons/favicon-1.png", diff --git a/packages/Extenstions/popup.js b/packages/Extenstions/popup.js index 8ea5e72..ac77462 100644 --- a/packages/Extenstions/popup.js +++ b/packages/Extenstions/popup.js @@ -19,7 +19,6 @@ document.addEventListener('DOMContentLoaded', () => { projectId: event.data.projectId }, (response) => { if (response && response.success) { - console.log('Authentication successful via postMessage'); // Refresh the popup UI showAuthenticatedUI(); fetchData(); @@ -31,7 +30,6 @@ document.addEventListener('DOMContentLoaded', () => { // Listen for auth success from background script chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'AUTH_SUCCESS') { - console.log('Authentication successful via background polling'); // Refresh the popup UI checkAuthAndUpdateUI(); } @@ -59,11 +57,11 @@ document.addEventListener('DOMContentLoaded', () => { showUnauthenticatedUI(); } } - });// Configuration for development vs production - const isDevelopment = true; // Set to false for production + }); // Configuration for development vs production + const isDevelopment = false; // Set to false for production const BASE_URL = isDevelopment ? 'http://localhost:3000' - : 'https://app.zecrypt.com'; + : 'https://app.zecrypt.io'; // Login button click handler loginBtn.addEventListener('click', () => { // Start auth checking in background diff --git a/packages/backend-server/.dockerignore b/packages/backend-server/.dockerignore new file mode 100644 index 0000000..43da448 --- /dev/null +++ b/packages/backend-server/.dockerignore @@ -0,0 +1,97 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git/ +.gitignore + +# Documentation +README.md +*.md +docs/ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ +.nox/ +coverage.xml +*.cover +.hypothesis/ + +# Logs +*.log +logs/ + +# Temporary files +*.tmp +*.temp +.cache/ + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Development files +.env.local +.env.development +.env.test +.env.production + +# Build artifacts +*.slim +slim.report.json + +# Node modules (if any) +node_modules/ + +# Backup files +*.bak +*.backup \ No newline at end of file diff --git a/packages/backend-server/app/utils/jwt_utils.py b/packages/backend-server/app/utils/jwt_utils.py index b2d4a43..299eeea 100644 --- a/packages/backend-server/app/utils/jwt_utils.py +++ b/packages/backend-server/app/utils/jwt_utils.py @@ -16,7 +16,7 @@ def create_jwt_token( if expires_delta: expire = datetime.utcnow() + expires_delta else: - expire = datetime.utcnow() + timedelta(days=2) + expire = datetime.utcnow() + timedelta(days=30) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, jwt_secret, algorithm=jwt_algorithm) return encoded_jwt diff --git a/packages/backend-server/app/utils/utils.py b/packages/backend-server/app/utils/utils.py index 03bced9..916058d 100644 --- a/packages/backend-server/app/utils/utils.py +++ b/packages/backend-server/app/utils/utils.py @@ -41,7 +41,7 @@ def create_uuid(): def get_origins(env): data = { "production": ["https://app.zecrypt.io"], - "dev": ["http://localhost:3000", "https://preview.app.zecrypt.io"], + "dev": ["http://localhost:3000", "https://preview.app.zecrypt.io", "chrome-extension://*"], } return data.get(env, ["*"]) diff --git a/packages/frontend-web/app/[locale]/extension-login/page.tsx b/packages/frontend-web/app/[locale]/extension-login/page.tsx index 0a2d7d0..17773b6 100644 --- a/packages/frontend-web/app/[locale]/extension-login/page.tsx +++ b/packages/frontend-web/app/[locale]/extension-login/page.tsx @@ -401,8 +401,7 @@ export default function ExtensionLogin() { ✓ Authentication successful - Your Zecrypt account is now connected to the browser extension. - You can close this tab and start using the extension to autofill your cards and emails. + You may now close this tab. )} diff --git a/packages/frontend-web/app/layout.tsx b/packages/frontend-web/app/layout.tsx index 939cd45..c6bcca3 100644 --- a/packages/frontend-web/app/layout.tsx +++ b/packages/frontend-web/app/layout.tsx @@ -23,7 +23,31 @@ export default function RootLayout({ }>) { return ( + + {/* Google Tag Manager */} +