Add native swift SQLite implementation (#771)

This commit is contained in:
Leendert de Borst
2025-04-11 18:09:55 +02:00
parent 1c75782e46
commit 55648e95b4
8 changed files with 379 additions and 286 deletions

View File

@@ -140,7 +140,7 @@ class CredentialProviderViewModel: ObservableObject {
isLoading = true
do {
credentials = try SharedCredentialStore.shared.getAllCredentials(createKeyIfNeeded: false)
credentials = try SharedCredentialStore.shared.getAllCredentials()
Task {
do {
@@ -170,7 +170,7 @@ class CredentialProviderViewModel: ObservableObject {
service: newService)
do {
try SharedCredentialStore.shared.addCredential(credential, createKeyIfNeeded: false)
try SharedCredentialStore.shared.addCredential(credential)
Task {
try await CredentialIdentityStore.shared.saveCredentialIdentities([credential])
}

View File

@@ -42,7 +42,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
// Get credentials and return the first one that matches the identity
do {
let credentials = try SharedCredentialStore.shared.getAllCredentials(createKeyIfNeeded: false)
let credentials = try SharedCredentialStore.shared.getAllCredentials()
if let matchingCredential = credentials.first(where: { $0.service == credentialIdentity.serviceIdentifier.identifier }) {
let passwordCredential = ASPasswordCredential(
user: matchingCredential.username,

View File

@@ -7,4 +7,23 @@ RCT_EXTERN_METHOD(clearCredentials)
RCT_EXTERN_METHOD(getCredentials)
RCT_EXTERN_METHOD(requiresMainQueueSetup)
// New methods for SQLite database operations
RCT_EXTERN_METHOD(storeDatabase:(NSString *)base64EncryptedDb
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(storeEncryptionKey:(NSString *)base64EncryptionKey
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(executeQuery:(NSString *)query
params:(NSArray *)params
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(executeUpdate:(NSString *)query
params:(NSArray *)params
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end

View File

@@ -1,58 +1,133 @@
import Foundation
import React
import CryptoKit
import SQLite
import LocalAuthentication
@objc(CredentialManager)
class CredentialManager: NSObject {
@objc
func addCredential(_ username: String, password: String, service: String) {
do {
let credential = Credential(username: username, password: password, service: service)
try SharedCredentialStore.shared.addCredential(credential)
} catch let error as CryptoKitError {
print("Encryption error: \(error)")
// Handle encryption errors
} catch {
print("Failed to add credential: \(error)")
// Handle other errors
private let credentialStore = SharedCredentialStore.shared
override init() {
super.init()
}
}
@objc
func clearCredentials() {
SharedCredentialStore.shared.clearAllCredentials()
}
@objc
func getCredentials() -> [[String: String]] {
do {
let credentials = try SharedCredentialStore.shared.getAllCredentials()
let credentialDicts = credentials.map { credential in
return [
"username": credential.username,
"password": credential.password,
"service": credential.service
]
}
return credentialDicts
} catch let error as CryptoKitError {
print("Decryption error: \(error)")
// Handle decryption errors
return []
} catch {
print("Failed to get credentials: \(error)")
// Handle other errors
return []
@objc
func requiresMainQueueSetup() -> Bool {
return false
}
@objc
func storeDatabase(_ base64EncryptedDb: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
do {
try credentialStore.storeEncryptedDatabase(base64EncryptedDb)
resolve(nil)
} catch {
reject("DB_ERROR", "Failed to store database: \(error.localizedDescription)", error)
}
}
@objc
func storeEncryptionKey(_ base64EncryptionKey: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
do {
// Store the encryption key in the keychain with biometric protection
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
reject("BIOMETRICS_UNAVAILABLE", "Biometrics not available: \(error?.localizedDescription ?? "Unknown error")", error)
return
}
// Store the key in the keychain with biometric protection
try credentialStore.storeEncryptionKey(base64EncryptionKey)
resolve(nil)
} catch {
reject("KEYCHAIN_ERROR", "Failed to store encryption key: \(error.localizedDescription)", error)
}
}
@objc
func executeQuery(_ query: String,
params: [Any],
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
do {
// Ensure database is initialized
try credentialStore.initializeDatabase()
// Convert all params to strings
let bindingParams = params.map { param -> Binding? in
return String(describing: param)
}
// Execute the query through the credential store
let results = try credentialStore.executeQuery(query, params: bindingParams)
resolve(results)
} catch {
reject("QUERY_ERROR", "Failed to execute query: \(error.localizedDescription)", error)
}
}
@objc
func executeUpdate(_ query: String,
params: [Any],
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
do {
// Ensure database is initialized
try credentialStore.initializeDatabase()
// Convert all params to strings
let bindingParams = params.map { param -> Binding? in
return String(describing: param)
}
// Execute the update through the credential store
let changes = try credentialStore.executeUpdate(query, params: bindingParams)
resolve(changes)
} catch {
reject("UPDATE_ERROR", "Failed to execute update: \(error.localizedDescription)", error)
}
}
@objc
func addCredential(_ username: String, password: String, service: String) {
do {
let credential = Credential(username: username, password: password, service: service)
try credentialStore.addCredential(credential)
} catch {
print("Failed to add credential: \(error)")
}
}
@objc
func clearCredentials() {
credentialStore.clearAllCredentials()
}
@objc
func getCredentials() -> [String: Any] {
do {
let credentials = try credentialStore.getAllCredentials()
let credentialDicts = credentials.map { credential in
return [
"username": credential.username,
"password": credential.password,
"service": credential.service
]
}
return ["credentials": credentialDicts]
} catch {
print("Failed to get credentials: \(error)")
return [:]
}
}
@objc
static func moduleName() -> String! {
return "CredentialManager"
}
}
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
@objc
static func moduleName() -> String! {
return "CredentialManager"
}
}

View File

@@ -1,133 +1,225 @@
import Foundation
import KeychainAccess
import SQLite
import LocalAuthentication
import CryptoKit
import CommonCrypto
public class SharedCredentialStore {
public static let shared = SharedCredentialStore()
private let userDefaults: UserDefaults
private let encryptionKeyKey = "encryptionKey"
private let credentialsKey = "storedCredentials"
private var cachedEncryptionKey: SymmetricKey?
class SharedCredentialStore {
static let shared = SharedCredentialStore()
private let keychain = Keychain(service: "net.aliasvault.autofill", accessGroup: "group.net.aliasvault.autofill")
.accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: [.biometryAny])
private let encryptionKeyKey = "aliasvault_encryption_key"
private let encryptedDbFileName = "encrypted_db.sqlite"
private var db: Connection?
private init() {
// Use the app group identifier
userDefaults = UserDefaults(suiteName: "group.net.aliasvault.autofill")!
}
private init() {}
private func getEncryptionKey(createKeyIfNeeded: Bool = true) throws -> SymmetricKey {
print("Getting encryption key")
if let cached = cachedEncryptionKey {
// Verify the cached key is valid by checking its bit length
if cached.bitCount == 256 {
print("Using cached encryption key")
return cached
} else {
print("Cached key is invalid, clearing cache")
cachedEncryptionKey = nil
}
// MARK: - Encryption Key Management
func storeEncryptionKey(_ base64Key: String) throws {
print("Storing encryption key")
guard let keyData = Data(base64Encoded: base64Key) else {
throw NSError(domain: "SharedCredentialStore", code: 6, userInfo: [NSLocalizedDescriptionKey: "Invalid base64 key"])
}
print("No cached key, accessing keychain")
// Create a new keychain instance with authentication required for this specific access
let authKeychain = Keychain(service: "net.aliasvault.autofill", accessGroup: "group.net.aliasvault.autofill")
.accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: [.biometryAny])
if let keyData = try? authKeychain
.authenticationPrompt("Authenticate to unlock your vault")
.getData(encryptionKeyKey) {
print("Found existing key in keychain")
let key = SymmetricKey(data: keyData)
cachedEncryptionKey = key
print("Returning existing key")
return key
}
if createKeyIfNeeded {
print("Creating new encryption key")
// Create new key if none exists
let key = SymmetricKey(size: .bits256)
do {
try authKeychain
.authenticationPrompt("Authenticate to unlock your vault")
.set(key.withUnsafeBytes { Data($0) }, key: encryptionKeyKey)
print("New key saved in keychain")
} catch {
print("Failed to save key to keychain: \(error)")
}
cachedEncryptionKey = key
return key
} else {
print("No encryption key found in keychain")
throw NSError(domain: "SharedCredentialStore", code: -1, userInfo: [NSLocalizedDescriptionKey: "No encryption key found in keychain"])
}
}
private func encrypt(_ data: Data, createKeyIfNeeded: Bool = true) throws -> Data {
print("Encrypting data")
let key = try getEncryptionKey(createKeyIfNeeded: createKeyIfNeeded)
print("Using key with bit length: \(key.bitCount)")
let sealedBox = try AES.GCM.seal(data, using: key)
guard let combined = sealedBox.combined else {
print("Failed to get combined data from sealed box")
throw NSError(domain: "SharedCredentialStore", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get combined data from sealed box"])
}
print("Successfully encrypted data, combined length: \(combined.count)")
return combined
}
private func decrypt(_ data: Data, createKeyIfNeeded: Bool = true) throws -> Data {
print("Decrypting data with length: \(data.count)")
let key = try getEncryptionKey(createKeyIfNeeded: createKeyIfNeeded)
print("Using key with bit length: \(key.bitCount)")
do {
let sealedBox = try AES.GCM.SealedBox(combined: data)
print("Successfully created sealed box")
let decryptedData = try AES.GCM.open(sealedBox, using: key)
print("Successfully decrypted data, length: \(decryptedData.count)")
return decryptedData
} catch let error as CryptoKitError {
print("CryptoKit error during decryption: \(error)")
throw error
try keychain
.authenticationPrompt("Authenticate to unlock your vault")
.set(keyData, key: encryptionKeyKey)
print("Key saved in keychain")
} catch {
print("Unexpected error during decryption: \(error)")
print("Failed to save key to keychain: \(error)")
throw error
}
}
public func getAllCredentials(createKeyIfNeeded: Bool = true) throws -> [Credential] {
print("Getting all credentials")
guard let encryptedData = userDefaults.data(forKey: credentialsKey) else {
print("No encrypted data found in UserDefaults")
return []
// MARK: - Database Management
private func getEncryptedDbPath() -> URL {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return documentsDirectory.appendingPathComponent(encryptedDbFileName)
}
func storeEncryptedDatabase(_ base64EncryptedDb: String) throws {
guard let encryptedData = Data(base64Encoded: base64EncryptedDb) else {
throw NSError(domain: "SharedCredentialStore", code: 3, userInfo: [NSLocalizedDescriptionKey: "Invalid base64 data"])
}
let decryptedData = try decrypt(encryptedData, createKeyIfNeeded: createKeyIfNeeded)
return try JSONDecoder().decode([Credential].self, from: decryptedData)
// Store the encrypted database in the app's documents directory
try encryptedData.write(to: getEncryptedDbPath())
}
public func addCredential(_ credential: Credential, createKeyIfNeeded: Bool = true) throws {
print("Adding new credential")
var credentials = try getAllCredentials(createKeyIfNeeded: createKeyIfNeeded)
credentials.append(credential)
let data = try JSONEncoder().encode(credentials)
let encryptedData = try encrypt(data, createKeyIfNeeded: createKeyIfNeeded)
userDefaults.set(encryptedData, forKey: credentialsKey)
func getEncryptedDatabase() -> String? {
do {
let encryptedData = try Data(contentsOf: getEncryptedDbPath())
return encryptedData.base64EncodedString()
} catch {
return nil
}
}
public func clearAllCredentials() {
print("Clearing all credentials")
userDefaults.removeObject(forKey: credentialsKey)
let authKeychain = Keychain(service: "net.aliasvault.autofill", accessGroup: "group.net.aliasvault.autofill")
.accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: [.biometryAny])
try? authKeychain.authenticationPrompt("Authenticate to unlock your vault").removeAll()
func initializeDatabase() throws {
// Get the encrypted database
guard let base64EncryptedDb = getEncryptedDatabase() else {
throw NSError(domain: "SharedCredentialStore", code: 1, userInfo: [NSLocalizedDescriptionKey: "No encrypted database found"])
}
// Get the encryption key
guard let keyData = try? keychain
.authenticationPrompt("Authenticate to unlock your vault")
.getData(encryptionKeyKey) else {
throw NSError(domain: "SharedCredentialStore", code: 2, userInfo: [NSLocalizedDescriptionKey: "No encryption key found"])
}
// Convert base64 strings to data
guard let encryptedData = Data(base64Encoded: base64EncryptedDb) else {
throw NSError(domain: "SharedCredentialStore", code: 3, userInfo: [NSLocalizedDescriptionKey: "Invalid base64 data"])
}
// Decrypt the database
let decryptedData = try decrypt(data: encryptedData, key: keyData)
// Create an in-memory database
db = try Connection(":memory:")
// Import the decrypted database into memory
try db?.execute("ATTACH DATABASE '\(decryptedData)' AS source")
try db?.execute("BEGIN TRANSACTION")
// Copy all tables from source to memory
let tables = try db?.prepare("SELECT name FROM source.sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
for table in tables! {
let tableName = table[0] as! String
try db?.execute("CREATE TABLE \(tableName) AS SELECT * FROM source.\(tableName)")
}
try db?.execute("COMMIT")
try db?.execute("DETACH DATABASE source")
// Setup database pragmas
try db?.execute("PRAGMA journal_mode = WAL")
try db?.execute("PRAGMA synchronous = NORMAL")
try db?.execute("PRAGMA foreign_keys = ON")
}
public func clearCache() {
print("Clearing encryption key cache")
cachedEncryptionKey = nil
// MARK: - Encryption/Decryption
private func encrypt(data: Data, key: Data) throws -> Data {
let key = SymmetricKey(data: key)
let sealedBox = try AES.GCM.seal(data, using: key)
return sealedBox.combined!
}
private func decrypt(data: Data, key: Data) throws -> Data {
let key = SymmetricKey(data: key)
let sealedBox = try AES.GCM.SealedBox(combined: data)
return try AES.GCM.open(sealedBox, using: key)
}
// MARK: - Credential Operations
func addCredential(_ credential: Credential) throws {
guard let db = db else {
throw NSError(domain: "SharedCredentialStore", code: 4, userInfo: [NSLocalizedDescriptionKey: "Database not initialized"])
}
let credentials = Table("credentials")
let id = Expression<String>("id")
let username = Expression<String>("username")
let password = Expression<String>("password")
let service = Expression<String>("service")
let createdAt = Expression<Date>("created_at")
let updatedAt = Expression<Date>("updated_at")
try db.run(credentials.insert(
id <- UUID().uuidString,
username <- credential.username,
password <- credential.password,
service <- credential.service,
createdAt <- Date(),
updatedAt <- Date()
))
}
func getAllCredentials() throws -> [Credential] {
guard let db = db else {
throw NSError(domain: "SharedCredentialStore", code: 4, userInfo: [NSLocalizedDescriptionKey: "Database not initialized"])
}
let credentials = Table("credentials")
let username = Expression<String>("username")
let password = Expression<String>("password")
let service = Expression<String>("service")
var result: [Credential] = []
for row in try db.prepare(credentials) {
result.append(Credential(
username: row[username],
password: row[password],
service: row[service]
))
}
return result
}
func clearAllCredentials() {
guard let db = db else { return }
let credentials = Table("credentials")
try? db.run(credentials.delete())
}
// MARK: - Query Execution
func executeQuery(_ query: String, params: [Binding?]) throws -> [[String: Any]] {
guard let db = db else {
throw NSError(domain: "SharedCredentialStore", code: 4, userInfo: [NSLocalizedDescriptionKey: "Database not initialized"])
}
let statement = try db.prepare(query)
var results: [[String: Any]] = []
for row in statement {
var rowDict: [String: Any] = [:]
for (index, column) in statement.columnNames.enumerated() {
rowDict[column] = row[index]
}
results.append(rowDict)
}
return results
}
func executeUpdate(_ query: String, params: [Binding?]) throws -> Int {
guard let db = db else {
throw NSError(domain: "SharedCredentialStore", code: 4, userInfo: [NSLocalizedDescriptionKey: "Database not initialized"])
}
let statement = try db.prepare(query)
try statement.run(params)
return db.changes
}
// MARK: - Biometric Authentication
func authenticateWithBiometrics() async throws -> Bool {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
throw error ?? NSError(domain: "SharedCredentialStore", code: 5, userInfo: [NSLocalizedDescriptionKey: "Biometrics not available"])
}
return try await withCheckedThrowingContinuation { continuation in
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Authenticate to access your credentials") { success, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: success)
}
}
}
}
}

View File

@@ -16,6 +16,7 @@ prepare_react_native_project!
target 'AliasVault' do
use_expo_modules!
pod 'KeychainAccess', '~> 4.2.2'
pod 'SQLite.swift', '~> 0.14.0'
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
@@ -68,4 +69,5 @@ end
target 'Autofill' do
pod 'KeychainAccess', '~> 4.2.2'
pod 'SQLite.swift', '~> 0.14.0'
end

View File

@@ -2094,6 +2094,9 @@ PODS:
- ReactCommon/turbomodule/core
- Yoga
- SocketRocket (0.7.1)
- SQLite.swift (0.14.1):
- SQLite.swift/standard (= 0.14.1)
- SQLite.swift/standard (0.14.1)
- Yoga (0.0.0)
DEPENDENCIES:
@@ -2195,6 +2198,7 @@ DEPENDENCIES:
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- SQLite.swift (~> 0.14.0)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
@@ -2202,6 +2206,7 @@ SPEC REPOS:
- CatCrypto
- KeychainAccess
- SocketRocket
- SQLite.swift
EXTERNAL SOURCES:
boost:
@@ -2497,8 +2502,9 @@ SPEC CHECKSUMS:
RNReanimated: 2e5069649cbab2c946652d3b97589b2ae0526220
RNScreens: 362f4c861dd155f898908d5035d97b07a3f1a9d1
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
SQLite.swift: 2992550ebf3c5b268bf4352603e3df87d2a4ed72
Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a
PODFILE CHECKSUM: 8289d60f8e8c7cee8b314289b8ec81b6b239f13f
PODFILE CHECKSUM: 71411f71c96e4b6a3db40174f3834328e5a380b9
COCOAPODS: 1.16.2

View File

@@ -1,5 +1,4 @@
import * as SQLite from 'expo-sqlite';
import * as FileSystem from 'expo-file-system';
import { NativeModules } from 'react-native';
import { Credential } from './types/Credential';
import { EncryptionKey } from './types/EncryptionKey';
import { TotpCode } from './types/TotpCode';
@@ -12,83 +11,18 @@ const placeholderBase64 = 'UklGRjoEAABXRUJQVlA4IC4EAAAwFwCdASqAAIAAPpFCm0olo6Ihp
type SQLiteBindValue = string | number | null | Uint8Array;
interface SQLiteResult {
rows: {
length: number;
item: (index: number) => any;
};
rowsAffected: number;
}
/**
* Client for interacting with the SQLite database.
* Client for interacting with the SQLite database through native code.
*/
class SqliteClient {
private db: SQLite.SQLiteDatabase | null = null;
private credentialManager = NativeModules.CredentialManager;
/**
* Initialize the SQLite database from a base64 string
*/
public async initializeFromBase64(base64String: string): Promise<void> {
try {
// Ensure SQLite directory exists
const sqliteDir = `${FileSystem.documentDirectory}SQLite`;
const dirInfo = await FileSystem.getInfoAsync(sqliteDir);
if (!dirInfo.exists) {
await FileSystem.makeDirectoryAsync(sqliteDir, { intermediates: true });
}
// For in-memory database, we need to create a temporary file first
const tempFileUri = `${sqliteDir}/temp.db`;
console.log('Writing database to temporary file');
// Delete existing file if it exists
const fileInfo = await FileSystem.getInfoAsync(tempFileUri);
if (fileInfo.exists) {
await FileSystem.deleteAsync(tempFileUri);
}
await FileSystem.writeAsStringAsync(tempFileUri, base64String, {
encoding: FileSystem.EncodingType.Base64,
});
console.log('Database written to temporary file');
console.log('tempFileUri', tempFileUri);
// Open the database in memory
console.log('Opening database from file');
this.db = SQLite.openDatabaseSync('temp.db');
console.log('Database opened from file');
// TODO: Finish implementation of in-memory database as we don't want to persist the database to the file system.
// Attach in-memory db
/*await this.executeUpdate(`ATTACH DATABASE ':memory:' AS target`);
await this.executeUpdate('BEGIN TRANSACTION');
console.log('Executing query to get tables');
// Copy all tables from source to memory
const tables = await this.executeQuery<{ name: string }>(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
);
console.log('Tables copied');
for (const table of tables) {
await this.executeUpdate(`CREATE TABLE ${table.name} AS SELECT * FROM target.${table.name}`);
}
await this.executeUpdate('COMMIT');
await this.executeUpdate('DETACH DATABASE source');
// Clean up the temporary file
await FileSystem.deleteAsync(tempFileUri);*/
// Setup database pragmas to configure the database.
await this.executeUpdate('PRAGMA journal_mode = WAL');
await this.executeUpdate('PRAGMA synchronous = NORMAL');
await this.executeUpdate('PRAGMA foreign_keys = ON');
await this.credentialManager.storeDatabase(base64String);
} catch (error) {
console.error('Error initializing SQLite database:', error);
throw error;
@@ -96,21 +30,13 @@ class SqliteClient {
}
/**
* Export the SQLite database to a base64 string
* @returns Base64 encoded string of the database
* Store the encryption key in the native keychain
*/
public async exportToBase64(): Promise<string> {
if (!this.db) {
throw new Error('Database not initialized');
}
public async storeEncryptionKey(base64EncryptionKey: string): Promise<void> {
try {
// TODO: Implement database export to base64
// This would require reading the database file and converting it to base64
console.warn('Database export to base64 not yet implemented');
return '';
await this.credentialManager.storeEncryptionKey(base64EncryptionKey);
} catch (error) {
console.error('Error exporting SQLite database:', error);
console.error('Error storing encryption key:', error);
throw error;
}
}
@@ -119,25 +45,8 @@ class SqliteClient {
* Execute a SELECT query
*/
public async executeQuery<T>(query: string, params: SQLiteBindValue[] = []): Promise<T[]> {
if (!this.db) {
throw new Error('Database not initialized');
}
try {
console.log('Executing query:', query);
// First do query to get all tables
const tables = await this.db.getAllAsync(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
);
console.log('Tables:', tables);
const results = await this.db.getAllAsync(
query,
...params
);
console.log('Results:', results);
const results = await this.credentialManager.executeQuery(query, params);
return results as T[];
} catch (error) {
console.error('Error executing query:', error);
@@ -149,16 +58,9 @@ class SqliteClient {
* Execute an INSERT, UPDATE, or DELETE query
*/
public async executeUpdate(query: string, params: SQLiteBindValue[] = []): Promise<number> {
if (!this.db) {
throw new Error('Database not initialized');
}
try {
const result = await this.db.runAsync(
query,
...params
);
return result.changes;
const result = await this.credentialManager.executeUpdate(query, params);
return result as number;
} catch (error) {
console.error('Error executing update:', error);
throw error;
@@ -169,10 +71,7 @@ class SqliteClient {
* Close the database connection and free resources.
*/
public close(): void {
if (this.db) {
this.db.closeAsync();
this.db = null;
}
// No-op since the native code handles connection lifecycle
}
/**
@@ -369,8 +268,8 @@ class SqliteClient {
* @returns The number of rows modified
*/
public async createCredential(credential: Credential): Promise<number> {
if (!this.db) {
throw new Error('Database not initialized');
if (!this.credentialManager) {
throw new Error('CredentialManager not initialized');
}
try {
@@ -474,8 +373,8 @@ class SqliteClient {
* Returns null if no migrations are found.
*/
public async getDatabaseVersion(): Promise<string | null> {
if (!this.db) {
throw new Error('Database not initialized');
if (!this.credentialManager) {
throw new Error('CredentialManager not initialized');
}
try {
@@ -512,8 +411,8 @@ class SqliteClient {
* @returns Array of TotpCode objects
*/
public async getTotpCodesForCredential(credentialId: string): Promise<TotpCode[]> {
if (!this.db) {
throw new Error('Database not initialized');
if (!this.credentialManager) {
throw new Error('CredentialManager not initialized');
}
try {
@@ -643,8 +542,8 @@ class SqliteClient {
* @returns True if the table exists, false otherwise
*/
private async tableExists(tableName: string): Promise<boolean> {
if (!this.db) {
throw new Error('Database not initialized');
if (!this.credentialManager) {
throw new Error('CredentialManager not initialized');
}
try {