diff --git a/browser-extensions/chrome/src/App.tsx b/browser-extensions/chrome/src/App.tsx index 8ce812892..0540b1f03 100644 --- a/browser-extensions/chrome/src/App.tsx +++ b/browser-extensions/chrome/src/App.tsx @@ -8,6 +8,10 @@ import CredentialsList from './pages/CredentialsList'; import { useMinDurationLoading } from './hooks/useMinDurationLoading'; import LoadingSpinner from './components/LoadingSpinner'; import './styles/app.css'; +import EncryptionUtility from './utils/EncryptionUtility'; +import { VaultResponse } from './types/webapi/VaultResponse'; +import { useWebApi } from './context/WebApiContext'; +import SrpUtility from './utils/SrpUtility'; /** * Main application component @@ -15,6 +19,7 @@ import './styles/app.css'; const App: React.FC = () => { const authContext = useAuth(); const dbContext = useDb(); + const webApi = useWebApi(); const [needsUnlock, setNeedsUnlock] = useState(false); const [showSettings, setShowSettings] = useState(false); const [isUserMenuOpen, setIsUserMenuOpen] = useState(false); @@ -79,6 +84,33 @@ const App: React.FC = () => { } }; + /** + * Refresh the vault. + */ + const handleRefresh = async (): Promise => { + setIsLoading(true); + try { + // Make API call to get latest vault + const vaultResponseJson = await webApi.get('Vault') as VaultResponse; + + // Get derived key from background worker + const passwordHashBase64 = await chrome.runtime.sendMessage({ type: 'GET_DERIVED_KEY' }); + + // Attempt to decrypt the blob + const decryptedBlob = await EncryptionUtility.symmetricDecrypt( + vaultResponseJson.vault.blob, + passwordHashBase64 + ); + + // Initialize the SQLite context again with the newly retrieved decrypted blob + await dbContext.initializeDatabase(passwordHashBase64, decryptedBlob); + } catch (err) { + console.error('Refresh error:', err); + } finally { + setIsLoading(false); + } + }; + /** * Toggle settings. */ @@ -95,9 +127,27 @@ const App: React.FC = () => { /** * User menu. + * + * Only shown if the user is logged in and the vault is not locked. */ - const userMenu = authContext.isLoggedIn ? ( -
+ const userMenu = authContext.isLoggedIn && !needsUnlock ? ( +
+
+
+ + +
+ + Loading...
+ +
)} +
) : null; @@ -176,6 +227,7 @@ const App: React.FC = () => {

AliasVault

{!authContext.isLoggedIn ? ( + <> + ) : ( userMenu )} diff --git a/browser-extensions/chrome/src/background.ts b/browser-extensions/chrome/src/background.ts index fbea2e126..e99268941 100644 --- a/browser-extensions/chrome/src/background.ts +++ b/browser-extensions/chrome/src/background.ts @@ -1,27 +1,25 @@ -import { Buffer } from 'buffer'; import EncryptionUtility from './utils/EncryptionUtility'; import SqliteClient from './utils/SqliteClient'; let vaultState: { - sessionKey: string | null; + derivedKey: string | null; } = { - sessionKey: null + derivedKey: null }; // Listen for messages from popup chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { switch (message.type) { case 'STORE_VAULT': { - // Generate random session key - const sessionKey = crypto.getRandomValues(new Uint8Array(32)); - vaultState.sessionKey = Buffer.from(sessionKey).toString('base64'); + // Store derived key in memory for future vault syncs + vaultState.derivedKey = message.derivedKey; // Re-encrypt vault with session key (async () : Promise => { try { const encryptedVault = await EncryptionUtility.symmetricEncrypt( message.vault, - vaultState.sessionKey! + vaultState.derivedKey! ); // Store in chrome.storage.session and wait for completion @@ -41,8 +39,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { break; } case 'GET_VAULT': { - if (!vaultState.sessionKey) { - console.error('No session key available'); + if (!vaultState.derivedKey) { sendResponse({ vault: null }); return; } @@ -55,10 +52,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { return; } - // Decrypt vault with session key + // Decrypt vault with derived key const decryptedVault = await EncryptionUtility.symmetricDecrypt( result.encryptedVault, - vaultState.sessionKey! + vaultState.derivedKey! ); // Parse the decrypted vault and send response @@ -76,14 +73,14 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { break; } case 'CLEAR_VAULT': { - vaultState.sessionKey = null; + vaultState.derivedKey = null; chrome.storage.session.remove(['encryptedVault']); sendResponse({ success: true }); break; } case 'GET_CREDENTIALS_FOR_URL': { - if (!vaultState.sessionKey) { + if (!vaultState.derivedKey) { sendResponse({ credentials: [] }); return; } @@ -97,7 +94,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { const decryptedVault = await EncryptionUtility.symmetricDecrypt( result.encryptedVault, - vaultState.sessionKey! + vaultState.derivedKey! ); // Initialize SQLite client @@ -131,6 +128,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { }); break; } + + case 'GET_DERIVED_KEY': { + sendResponse(vaultState.derivedKey ? vaultState.derivedKey : null); + break; + } } return true; }); diff --git a/browser-extensions/chrome/src/context/DbContext.tsx b/browser-extensions/chrome/src/context/DbContext.tsx index 87da5f06c..57eefaf81 100644 --- a/browser-extensions/chrome/src/context/DbContext.tsx +++ b/browser-extensions/chrome/src/context/DbContext.tsx @@ -5,7 +5,7 @@ type DbContextType = { sqliteClient: SqliteClient | null; dbInitialized: boolean; dbAvailable: boolean; - initializeDatabase: (blob: string) => Promise; + initializeDatabase: (derivedKey: string, vault: string) => Promise; clearDatabase: () => void; } @@ -30,9 +30,9 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children } */ const [dbAvailable, setDbAvailable] = useState(false); - const initializeDatabase = useCallback(async (blob: string) => { + const initializeDatabase = useCallback(async (derivedKey: string, vault: string) => { const client = new SqliteClient(); - await client.initializeFromBase64(blob); + await client.initializeFromBase64(vault); setSqliteClient(client); setDbInitialized(true); setDbAvailable(true); @@ -40,7 +40,8 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children } // Store in background worker chrome.runtime.sendMessage({ type: 'STORE_VAULT', - vault: blob + derivedKey: derivedKey, + vault: vault, }); }, []); @@ -53,12 +54,6 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children } setSqliteClient(client); setDbInitialized(true); setDbAvailable(true); - - // Store in background worker - chrome.runtime.sendMessage({ - type: 'STORE_VAULT', - vault: response.vault - }); } else { setDbInitialized(true); diff --git a/browser-extensions/chrome/src/pages/Login.tsx b/browser-extensions/chrome/src/pages/Login.tsx index 7730fbcba..f0ee25a3d 100644 --- a/browser-extensions/chrome/src/pages/Login.tsx +++ b/browser-extensions/chrome/src/pages/Login.tsx @@ -72,7 +72,7 @@ const Login: React.FC = () => { const decryptedBlob = await EncryptionUtility.symmetricDecrypt(vaultResponseJson.vault.blob, passwordHashBase64); // Initialize the SQLite context with decrypted data - await dbContext.initializeDatabase(decryptedBlob); + await dbContext.initializeDatabase(passwordHashBase64, decryptedBlob); // 3. Handle 2FA if required /* diff --git a/browser-extensions/chrome/src/pages/Unlock.tsx b/browser-extensions/chrome/src/pages/Unlock.tsx index 0d77d35f0..653b29679 100644 --- a/browser-extensions/chrome/src/pages/Unlock.tsx +++ b/browser-extensions/chrome/src/pages/Unlock.tsx @@ -52,7 +52,7 @@ const Unlock: React.FC = () => { ); // Initialize the SQLite context with decrypted data - await dbContext.initializeDatabase(decryptedBlob); + await dbContext.initializeDatabase(passwordHashBase64, decryptedBlob); } catch (err) { setError('Failed to unlock vault. Please check your password and try again.'); console.error('Unlock error:', err);