From be62003b312dc9df7013e7171fc0770566f6cc2a Mon Sep 17 00:00:00 2001 From: zapomnij Date: Tue, 27 Feb 2024 18:42:40 +0100 Subject: [PATCH] Offline deletion is now possible, added button to lock unlocked vault, set notes fields of ciphers as multiline, clearing vault is possible only when device is online, manual syncing vault is prevented when offline --- LibrePass/API/LibrePassAPI.swift | 16 ++++++++++++---- LibrePass/API/Vault.swift | 6 ++++-- LibrePass/LibrePassApp.swift | 3 ++- LibrePass/LibrePassCipherView.swift | 8 ++++---- LibrePass/LibrePassLocalLogin.swift | 22 +++++++++++----------- LibrePass/LibrePassLoginWindow.swift | 2 ++ LibrePass/LibrePassManagerWindow.swift | 23 ++++++++++++----------- 7 files changed, 47 insertions(+), 33 deletions(-) diff --git a/LibrePass/API/LibrePassAPI.swift b/LibrePass/API/LibrePassAPI.swift index ddfd304..ce93a62 100644 --- a/LibrePass/API/LibrePassAPI.swift +++ b/LibrePass/API/LibrePassAPI.swift @@ -153,7 +153,7 @@ struct LibrePassClient { self.loginData = nil self.sharedKey = nil self.credentialsDatabase = nil - self.vault.vault = [] + self.vault = LibrePassDecryptedVault(lastSync: 0) } mutating func fetchCiphers() throws { @@ -215,6 +215,14 @@ struct LibrePassClient { newVaultToSync.append(false) } + for index in stride(from: self.vault.idstoDelete.count - 1, through: 0, by: -1) { + if let _ = try? self.delete(id: self.vault.idstoDelete[index]) { + if let _ = try? self.vault.remove(id: self.vault.idstoDelete[index], save: true) { + self.vault.idstoDelete.remove(at: index) + } + } + } + for encCipher in updated.ciphers { if self.vault.vault.firstIndex(where: { cipher in encCipher.id == cipher.id }) == nil { newVault.append(try LibrePassCipher(encCipher: encCipher, key: self.sharedKey!)) @@ -251,11 +259,11 @@ struct LibrePassClient { mutating func delete(id: String) throws { if networkMonitor.isConnected { _ = try self.client.request(path: "/api/cipher/" + id, body: nil, method: "DELETE") - - try self.vault.remove(id: id, save: true) } else { - throw LibrePassApiErrors.WithMessage(message: "Offline deletion is unsupported") + self.vault.idstoDelete.append(id) } + + try self.vault.remove(id: id, save: true) } func generateId() -> String { diff --git a/LibrePass/API/Vault.swift b/LibrePass/API/Vault.swift index 0b89353..d89d8f0 100644 --- a/LibrePass/API/Vault.swift +++ b/LibrePass/API/Vault.swift @@ -11,6 +11,7 @@ import CryptoKit struct LibrePassEncryptedVault: Codable { var vault: [LibrePassEncryptedCipher] = [] var toSync: [Bool] = [] + var idsToDelete: [String] = [] var lastSync: Int64 static func loadVault() throws -> Self { @@ -26,7 +27,7 @@ struct LibrePassEncryptedVault: Codable { } func decryptVault(key: SymmetricKey) throws -> LibrePassDecryptedVault { - var ciphers: LibrePassDecryptedVault = LibrePassDecryptedVault(toSync: self.toSync, lastSync: self.lastSync, key: key) + var ciphers: LibrePassDecryptedVault = LibrePassDecryptedVault(toSync: self.toSync, idstoDelete: self.idsToDelete, lastSync: self.lastSync, key: key) for (i, encCipher) in self.vault.enumerated() { try ciphers.addOrReplace(cipher: try LibrePassCipher(encCipher: encCipher, key: key), toSync: self.toSync[i], save: false) } @@ -42,6 +43,7 @@ struct LibrePassEncryptedVault: Codable { struct LibrePassDecryptedVault { var vault: [LibrePassCipher] = [] var toSync: [Bool] = [] + var idstoDelete: [String] = [] var lastSync: Int64 var key: SymmetricKey? @@ -77,7 +79,7 @@ struct LibrePassDecryptedVault { } func encryptVault() throws -> LibrePassEncryptedVault { - var encCiphers: LibrePassEncryptedVault = LibrePassEncryptedVault(toSync: self.toSync, lastSync: self.lastSync) + var encCiphers: LibrePassEncryptedVault = LibrePassEncryptedVault(toSync: self.toSync, idsToDelete: self.idstoDelete, lastSync: self.lastSync) for cipher in self.vault { encCiphers.vault.append(try LibrePassEncryptedCipher(cipher: cipher, key: self.key!)) } diff --git a/LibrePass/LibrePassApp.swift b/LibrePass/LibrePassApp.swift index 9f52755..b7601bc 100644 --- a/LibrePass/LibrePassApp.swift +++ b/LibrePass/LibrePassApp.swift @@ -38,7 +38,7 @@ struct MainWindow: View { } else { NavigationView { List { - NavigationLink(destination: LibrePassLoginWindow(lClient: $lClient, loggedIn: $loggedIn)) { + NavigationLink(destination: LibrePassLoginWindow(lClient: $lClient, loggedIn: $loggedIn, localLogIn: $localLogIn)) { Text("Log in") } NavigationLink(destination: LibrePassRegistrationWindow(lClient: $lClient)) { @@ -78,6 +78,7 @@ struct MainWindow: View { Text("Copyright © 2024 LibrePass Team") Text("LibrePass server: Medzik (Oskar) and contributors") Text("LibrePass app for iOS: Zapomnij (Jacek)") + Text("App is licensed under GPL v3 license") Link("See on Github", destination: URL(string: "https://github.com/LibrePass")!) .padding() diff --git a/LibrePass/LibrePassCipherView.swift b/LibrePass/LibrePassCipherView.swift index 085458d..b340b40 100644 --- a/LibrePass/LibrePassCipherView.swift +++ b/LibrePass/LibrePassCipherView.swift @@ -70,7 +70,7 @@ struct CipherLoginDataView: View { } Section(header: Text("Notes")) { - TextField("Notes", text: self.$notes) + TextField("Notes", text: self.$notes, axis: .vertical) } Section { @@ -107,8 +107,8 @@ struct CipherSecureNoteView: View { var body: some View { List { - TextFieldWithCopyButton(text: "Title", textBind: self.$title) - TextFieldWithCopyButton(text: "Note", textBind: self.$note) + TextField("Title", text: self.$title) + TextField("Note", text: self.$note, axis: .vertical) ButtonWithSpinningWheel(text: "Save", task: self.saveCipher) } @@ -151,7 +151,7 @@ struct CipherCardDataView: View { } Section(header: Text("Notes")) { - TextFieldWithCopyButton(text: "Notes", textBind: self.$notes) + TextField("Note", text: self.$notes, axis: .vertical) } Section { diff --git a/LibrePass/LibrePassLocalLogin.swift b/LibrePass/LibrePassLocalLogin.swift index ef4e168..c140452 100644 --- a/LibrePass/LibrePassLocalLogin.swift +++ b/LibrePass/LibrePassLocalLogin.swift @@ -24,26 +24,26 @@ struct LibrePassLocalLogin: View { ButtonWithSpinningWheel(text: "Unlock vault", task: self.login) } - Section(header: Text("If you're encountering crashes after update, try clearing local saved Vault. WARNING! THIS WILL DELETE VAULT SAVED ON THE DISK")) { + Section(header: Text("WARNING! THIS WILL DELETE VAULT SAVED ON THE DISK, but can fix crashes")) { ButtonWithSpinningWheel(text: "Clear vault", task: self.clearVault, color: Color.red) } } } func clearVault() throws { - self.errorString = " " - - let credentials = try LibrePassCredentialsDatabase.load() - self.lClient = try LibrePassClient(credentials: credentials, password: self.password) - - try self.lClient.fetchCiphers() - - self.lClient.unAuth() + if networkMonitor.isConnected { + let credentials = try LibrePassCredentialsDatabase.load() + self.lClient = try LibrePassClient(credentials: credentials, password: self.password) + + try self.lClient.fetchCiphers() + + self.lClient.unAuth() + } else { + throw LibrePassApiErrors.WithMessage(message: "Offline clearing vault can't be done") + } } func login() throws { - self.errorString = " " - let credentials = try LibrePassCredentialsDatabase.load() self.lClient = try LibrePassClient(credentials: credentials, password: self.password) try self.lClient.syncVault() diff --git a/LibrePass/LibrePassLoginWindow.swift b/LibrePass/LibrePassLoginWindow.swift index 06efc95..07e1433 100644 --- a/LibrePass/LibrePassLoginWindow.swift +++ b/LibrePass/LibrePassLoginWindow.swift @@ -15,6 +15,7 @@ struct LibrePassLoginWindow: View { @State private var apiServer = "https://api.librepass.org" @Binding var loggedIn: Bool + @Binding var localLogIn: Bool var body: some View { List { @@ -42,5 +43,6 @@ struct LibrePassLoginWindow: View { try lClient.login(email: self.email, password: self.password) try self.lClient.fetchCiphers() self.loggedIn = true + self.localLogIn = true } } diff --git a/LibrePass/LibrePassManagerWindow.swift b/LibrePass/LibrePassManagerWindow.swift index 21fbebb..15f2f0b 100644 --- a/LibrePass/LibrePassManagerWindow.swift +++ b/LibrePass/LibrePassManagerWindow.swift @@ -57,6 +57,15 @@ struct LibrePassManagerWindow: View { .foregroundStyle(Color.red) } + + Button(action: { + self.lClient.unAuth() + self.loggedIn = false + }) { + Image(systemName: "lock") + .foregroundColor(Color.yellow) + } + Button(action: { self.refreshIndicator = true }) { @@ -73,16 +82,6 @@ struct LibrePassManagerWindow: View { } } - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now()) { - while !self.loggedIn { - - } - - self.refreshIndicator = true - } - } - .alert(self.errorString, isPresented: self.$showAlert) { Button("OK", role: .cancel) { lClient.unAuth() @@ -119,7 +118,9 @@ struct LibrePassManagerWindow: View { } func syncVault() throws { - try self.lClient.syncVault() + if networkMonitor.isConnected { + try self.lClient.syncVault() + } } func deleteCiphers() throws {