From a3d702f2e56577e4ea4f5f4e11ad3dc1ee8b0bce Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Jun 2025 16:01:45 +0200 Subject: [PATCH] Update database version retrieval to use VaultVersion objects (#957) --- .../background/VaultMessageHandler.ts | 2 +- .../src/utils/SqliteClient.ts | 78 ++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/apps/browser-extension/src/entrypoints/background/VaultMessageHandler.ts b/apps/browser-extension/src/entrypoints/background/VaultMessageHandler.ts index d78fe0114..9424898f8 100644 --- a/apps/browser-extension/src/entrypoints/background/VaultMessageHandler.ts +++ b/apps/browser-extension/src/entrypoints/background/VaultMessageHandler.ts @@ -391,7 +391,7 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise {}); diff --git a/apps/browser-extension/src/utils/SqliteClient.ts b/apps/browser-extension/src/utils/SqliteClient.ts index 8c9742fea..5fe7d7a91 100644 --- a/apps/browser-extension/src/utils/SqliteClient.ts +++ b/apps/browser-extension/src/utils/SqliteClient.ts @@ -1,6 +1,8 @@ import initSqlJs, { Database } from 'sql.js'; import type { Credential, EncryptionKey, PasswordSettings, TotpCode } from '@/utils/dist/shared/models/vault'; +import type { VaultVersion } from '@/utils/dist/shared/vault-sql'; +import { VaultSqlGenerator } from '@/utils/dist/shared/vault-sql'; /** * Placeholder base64 image for credentials without a logo. @@ -526,7 +528,7 @@ export class SqliteClient { * Returns the semantic version (e.g., "1.4.1") from the latest migration. * Returns null if no migrations are found. */ - public getDatabaseVersion(): string | null { + public getDatabaseVersion(): VaultVersion { if (!this.db) { throw new Error('Database not initialized'); } @@ -540,7 +542,7 @@ export class SqliteClient { LIMIT 1`); if (results.length === 0) { - return null; + throw new Error('No migrations found in the database.'); } // Extract version using regex - matches patterns like "20240917191243_1.4.1-RenameAttachmentsPlural" @@ -548,17 +550,53 @@ export class SqliteClient { const versionRegex = /_(\d+\.\d+\.\d+)-/; const versionMatch = versionRegex.exec(migrationId); + let currentVersion = null; if (versionMatch?.[1]) { - return versionMatch[1]; + currentVersion = versionMatch[1]; } - return null; + // Get all available vault versions to get the revision number of the current version. + const vaultSqlGenerator = new VaultSqlGenerator(); + const allVersions = vaultSqlGenerator.getAllVersions(); + const currentVersionRevision = allVersions.find(v => v.version === currentVersion); + + if (!currentVersionRevision) { + throw new Error('This browser extension is outdated and cannot be used to access this vault. Please update this extension to continue.'); + } + + return currentVersionRevision; } catch (error) { console.error('Error getting database version:', error); throw error; } } + /** + * Get the latest available database version + * @returns The latest VaultVersion + */ + public async getLatestDatabaseVersion(): Promise { + const vaultSqlGenerator = new VaultSqlGenerator(); + const allVersions = vaultSqlGenerator.getAllVersions(); + return allVersions[allVersions.length - 1]; + } + + /** + * Check if there are pending migrations + * @returns True if there are pending migrations, false otherwise + */ + public async hasPendingMigrations(): Promise { + try { + const currentVersion = this.getDatabaseVersion(); + const latestVersion = await this.getLatestDatabaseVersion(); + + return currentVersion.revision < latestVersion.revision; + } catch (error) { + console.error('Error checking pending migrations:', error); + throw error; + } + } + /** * Get TOTP codes for a credential * @param credentialId - The ID of the credential to get TOTP codes for @@ -931,6 +969,38 @@ export class SqliteClient { return false; } } + + /** + * Execute raw SQL command + * @param query - The SQL command to execute + */ + public executeRaw(query: string): void { + if (!this.db) { + throw new Error('Database not initialized'); + } + + try { + // Split the query by semicolons to handle multiple statements + const statements = query.split(';'); + + for (const statement of statements) { + const trimmedStatement = statement.trim(); + + // Skip empty statements and transaction control statements (handled externally) + if (trimmedStatement.length === 0 || + trimmedStatement.toUpperCase().startsWith('BEGIN TRANSACTION') || + trimmedStatement.toUpperCase().startsWith('COMMIT') || + trimmedStatement.toUpperCase().startsWith('ROLLBACK')) { + continue; + } + + this.db.run(trimmedStatement); + } + } catch (error) { + console.error('Error executing raw SQL:', error); + throw error; + } + } } export default SqliteClient; \ No newline at end of file