diff --git a/browser-extensions/chrome/background.ts b/browser-extensions/chrome/background.ts index 2e1cf09b6..a959cec2d 100644 --- a/browser-extensions/chrome/background.ts +++ b/browser-extensions/chrome/background.ts @@ -25,7 +25,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { handleClearVault(vaultState, sendResponse); break; - case 'GET_CREDENTIALS_FOR_URL': + case 'GET_CREDENTIALS': handleGetCredentials(vaultState, sendResponse); break; diff --git a/browser-extensions/chrome/src/app/pages/CredentialDetails.tsx b/browser-extensions/chrome/src/app/pages/CredentialDetails.tsx index 16a435deb..fc87b601f 100644 --- a/browser-extensions/chrome/src/app/pages/CredentialDetails.tsx +++ b/browser-extensions/chrome/src/app/pages/CredentialDetails.tsx @@ -52,12 +52,11 @@ const CredentialDetails: React.FC = () => { if (!dbContext?.sqliteClient || !id) return; try { - const result = dbContext.sqliteClient.getAllCredentials(); - // TODO: create a SQLite function to get a credential by id. - const credential = result.find(cred => cred.Id === id); - if (credential) { - setCredential(credential); + const result = dbContext.sqliteClient.getCredentialById(id); + if (result) { + setCredential(result); } else { + console.error('Credential not found'); navigate('/credentials'); } } catch (err) { diff --git a/browser-extensions/chrome/src/app/pages/EmailsList.tsx b/browser-extensions/chrome/src/app/pages/EmailsList.tsx index 00f67c3ca..742fbeb13 100644 --- a/browser-extensions/chrome/src/app/pages/EmailsList.tsx +++ b/browser-extensions/chrome/src/app/pages/EmailsList.tsx @@ -38,6 +38,7 @@ const EmailsList: React.FC = () => { return; } + // TODO: create separate query to only get email addresses to avoid loading all credentials. const credentials = dbContext.sqliteClient.getAllCredentials(); // Get unique email addresses from all credentials. diff --git a/browser-extensions/chrome/src/app/pages/Login.tsx b/browser-extensions/chrome/src/app/pages/Login.tsx index 4956c8d57..2380f2f6f 100644 --- a/browser-extensions/chrome/src/app/pages/Login.tsx +++ b/browser-extensions/chrome/src/app/pages/Login.tsx @@ -73,20 +73,11 @@ const Login: React.FC = () => { // Initialize the SQLite context with the new vault data. await dbContext.initializeDatabase(vaultResponseJson, passwordHashBase64); - // 3. Handle 2FA if required - /* - * if (validationResponse.requiresTwoFactor) { - * // TODO: Implement 2FA flow - * console.log('2FA required'); - * return; - * } - */ + // 3. TODO: Handle 2FA if required - // 5. Redirect to home page - /* - * window.location.href = '/'; - */ + // 4. TODO: handle recovery code if required (or link to main client instead) + // Show app. hideLoading(); } catch (err) { setError('Login failed. Please check your credentials and try again.'); diff --git a/browser-extensions/chrome/src/background/VaultMessageHandler.ts b/browser-extensions/chrome/src/background/VaultMessageHandler.ts index a72310b5e..2e7752231 100644 --- a/browser-extensions/chrome/src/background/VaultMessageHandler.ts +++ b/browser-extensions/chrome/src/background/VaultMessageHandler.ts @@ -104,7 +104,7 @@ export function handleClearVault( } /** - * Get the credentials for a URL. + * Get all credentials. */ export async function handleGetCredentials( vaultState: VaultState, @@ -191,6 +191,7 @@ export function getEmailAddressesForVault( sqliteClient: SqliteClient, vaultState: VaultState ): string[] { + // TODO: create separate query to only get email addresses to avoid loading all credentials. const credentials = sqliteClient.getAllCredentials(); const emailAddresses = credentials diff --git a/browser-extensions/chrome/src/contentscript/Popup.ts b/browser-extensions/chrome/src/contentscript/Popup.ts index 29f003e20..ec4372e86 100644 --- a/browser-extensions/chrome/src/contentscript/Popup.ts +++ b/browser-extensions/chrome/src/contentscript/Popup.ts @@ -280,7 +280,7 @@ export function createAutofillPopup(input: HTMLInputElement, credentials: Creden const searchTerm = searchInput.value.toLowerCase(); // Request credentials from background script - chrome.runtime.sendMessage({ type: 'GET_CREDENTIALS_FOR_URL', url: window.location.href }, (response: CredentialResponse) => { + chrome.runtime.sendMessage({ type: 'GET_CREDENTIALS' }, (response: CredentialResponse) => { if (response.status === 'OK' && response.credentials) { let filteredCredentials; @@ -862,7 +862,7 @@ export function openAutofillPopup(input: HTMLInputElement) : void { document.addEventListener('keydown', handleEnterKey); // Request credentials from background script - chrome.runtime.sendMessage({ type: 'GET_CREDENTIALS_FOR_URL', url: window.location.href }, (response: CredentialResponse) => { + chrome.runtime.sendMessage({ type: 'GET_CREDENTIALS' }, (response: CredentialResponse) => { switch (response.status) { case 'OK': if (response.credentials?.length) { diff --git a/browser-extensions/chrome/src/shared/SqliteClient.tsx b/browser-extensions/chrome/src/shared/SqliteClient.tsx index 1ddbb0dd0..0f32a8389 100644 --- a/browser-extensions/chrome/src/shared/SqliteClient.tsx +++ b/browser-extensions/chrome/src/shared/SqliteClient.tsx @@ -77,7 +77,8 @@ class SqliteClient { const results: T[] = []; while (stmt.step()) { - results.push(stmt.getAsObject()); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + results.push(stmt.getAsObject() as any); } stmt.free(); @@ -110,7 +111,7 @@ class SqliteClient { } /** - * Close the database connection and free resources + * Close the database connection and free resources. */ public close(): void { if (this.db) { @@ -120,8 +121,66 @@ class SqliteClient { } /** - * Fetch all credentials with their associated service information - * @returns Array of Credential objects with service details + * Fetch a single credential with its associated service information. + * @param credentialId - The ID of the credential to fetch. + * @returns Credential object with service details or null if not found. + */ + public getCredentialById(credentialId: string): Credential | null { + const query = ` + SELECT DISTINCT + c.Id, + c.Username, + c.Notes, + c.ServiceId, + s.Name as ServiceName, + s.Url as ServiceUrl, + s.Logo as Logo, + a.FirstName, + a.LastName, + a.NickName, + a.BirthDate, + a.Gender, + a.Email, + p.Value as Password + FROM Credentials c + LEFT JOIN Services s ON c.ServiceId = s.Id + LEFT JOIN Aliases a ON c.AliasId = a.Id + LEFT JOIN Passwords p ON p.CredentialId = c.Id + WHERE c.IsDeleted = 0 + AND c.Id = ?`; + + const results = this.executeQuery(query, [credentialId]); + + if (results.length === 0) { + return null; + } + + // Convert the first row to a Credential object + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const row = results[0] as any; + return { + Id: row.Id, + Username: row.Username, + Password: row.Password, + Email: row.Email, + ServiceName: row.ServiceName, + ServiceUrl: row.ServiceUrl, + Logo: row.Logo, + Notes: row.Notes, + Alias: { + FirstName: row.FirstName, + LastName: row.LastName, + NickName: row.NickName, + BirthDate: row.BirthDate, + Gender: row.Gender, + Email: row.Email + } + }; + } + + /** + * Fetch all credentials with their associated service information. + * @returns Array of Credential objects with service details. */ public getAllCredentials(): Credential[] { const query = `