From 70bb8ef3e4915680be677eea01fe926b346046d7 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Jun 2025 11:41:21 +0200 Subject: [PATCH] Add vault outdated status flag (#957) --- .../entrypoints/popup/hooks/useVaultMutate.ts | 5 ++++- .../entrypoints/popup/hooks/useVaultSync.ts | 2 +- .../src/utils/WebApiService.ts | 7 ++++++- apps/mobile-app/app/upgrade.tsx | 2 +- apps/mobile-app/hooks/useVaultMutate.ts | 5 ++++- apps/mobile-app/hooks/useVaultSync.ts | 2 +- apps/mobile-app/utils/WebApiService.ts | 8 +++++++- .../Controllers/VaultController.cs | 18 +++++++++++++----- .../Services/Database/DbService.cs | 5 +++++ .../Models/Enums/VaultStatus.cs | 9 +++++++-- 10 files changed, 49 insertions(+), 14 deletions(-) diff --git a/apps/browser-extension/src/entrypoints/popup/hooks/useVaultMutate.ts b/apps/browser-extension/src/entrypoints/popup/hooks/useVaultMutate.ts index e0bad5a1a..7346c2e8d 100644 --- a/apps/browser-extension/src/entrypoints/popup/hooks/useVaultMutate.ts +++ b/apps/browser-extension/src/entrypoints/popup/hooks/useVaultMutate.ts @@ -70,9 +70,12 @@ export function useVaultMutate() : { await dbContext.setCurrentVaultRevisionNumber(response.newRevisionNumber); options.onSuccess?.(); } else if (response.status === 1) { + // Note: vault merge is no longer allowed by the API as of 0.20.0, updates with the same revision number are rejected. So this check can be removed later. throw new Error('Vault merge required. Please login via the web app to merge the multiple pending updates to your vault.'); + } else if (response.status === 2) { + throw new Error('Your vault is outdated. Please login on the AliasVault website and follow the steps.'); } else { - throw new Error('Failed to upload vault to server'); + throw new Error('Failed to upload vault to server. Please try again by re-opening the app.'); } } catch (error) { // Check if it's a network error diff --git a/apps/browser-extension/src/entrypoints/popup/hooks/useVaultSync.ts b/apps/browser-extension/src/entrypoints/popup/hooks/useVaultSync.ts index 248478707..3a57b47e1 100644 --- a/apps/browser-extension/src/entrypoints/popup/hooks/useVaultSync.ts +++ b/apps/browser-extension/src/entrypoints/popup/hooks/useVaultSync.ts @@ -132,7 +132,7 @@ export const useVaultSync = () : { return false; } // Vault could not be decrypted, throw an error - throw new Error('Vault could not be decrypted, if problem persists please logout and login again.'); + throw new Error('Vault could not be decrypted, if the problem persists please logout and login again.'); } } diff --git a/apps/browser-extension/src/utils/WebApiService.ts b/apps/browser-extension/src/utils/WebApiService.ts index 8c39a4733..a0d873e16 100644 --- a/apps/browser-extension/src/utils/WebApiService.ts +++ b/apps/browser-extension/src/utils/WebApiService.ts @@ -292,10 +292,15 @@ export class WebApiService { * Status 0 = OK, vault is ready. * Status 1 = Merge required, which only the web client supports. */ - if (vaultResponseJson.status !== 0) { + if (vaultResponseJson.status === 1) { + // Note: vault merge is no longer allowed by the API as of 0.20.0, updates with the same revision number are rejected. So this check can be removed later. return 'Your vault needs to be updated. Please login on the AliasVault website and follow the steps.'; } + if (vaultResponseJson.status === 2) { + return 'Your vault is outdated. Please login on the AliasVault website and follow the steps.'; + } + if (!vaultResponseJson.vault?.blob) { return 'Your account does not have a vault yet. Please complete the tutorial in the AliasVault web client before using the browser extension.'; } diff --git a/apps/mobile-app/app/upgrade.tsx b/apps/mobile-app/app/upgrade.tsx index 48d77d2ad..343ea00e5 100644 --- a/apps/mobile-app/app/upgrade.tsx +++ b/apps/mobile-app/app/upgrade.tsx @@ -135,7 +135,7 @@ export default function UpgradeScreen() : React.ReactNode { } catch (error) { console.error(`Error executing SQL command ${i + 1}:`, sqlCommand, error); await NativeVaultManager.rollbackTransaction(); - throw new Error(`Failed to apply migration ${i + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`); + throw new Error(`Failed to apply migration (${i + 1} of ${upgradeResult.sqlCommands.length})`); } } diff --git a/apps/mobile-app/hooks/useVaultMutate.ts b/apps/mobile-app/hooks/useVaultMutate.ts index ff1abac64..310b0f7c3 100644 --- a/apps/mobile-app/hooks/useVaultMutate.ts +++ b/apps/mobile-app/hooks/useVaultMutate.ts @@ -116,9 +116,12 @@ export function useVaultMutate() : { await NativeVaultManager.setCurrentVaultRevisionNumber(response.newRevisionNumber); options.onSuccess?.(); } else if (response.status === 1) { + // Note: vault merge is no longer allowed by the API as of 0.20.0, updates with the same revision number are rejected. So this check can be removed later. throw new Error('Vault merge required. Please login via the web app to merge the multiple pending updates to your vault.'); + } else if (response.status === 2) { + throw new Error('Your vault is outdated. Please login on the AliasVault website and follow the steps.'); } else { - throw new Error('Failed to upload vault to server'); + throw new Error('Failed to upload vault to server. Please try again by re-opening the app.'); } } catch (error) { // Check if it's a network error diff --git a/apps/mobile-app/hooks/useVaultSync.ts b/apps/mobile-app/hooks/useVaultSync.ts index 419f521fe..60131a966 100644 --- a/apps/mobile-app/hooks/useVaultSync.ts +++ b/apps/mobile-app/hooks/useVaultSync.ts @@ -125,7 +125,7 @@ export const useVaultSync = () : { return true; } catch { // Vault could not be decrypted, throw an error - throw new Error('Vault could not be decrypted, if problem persists please logout and login again.'); + throw new Error('Vault could not be decrypted, if the problem persists please logout and login again.'); } } diff --git a/apps/mobile-app/utils/WebApiService.ts b/apps/mobile-app/utils/WebApiService.ts index 479d7e0cb..ac2d20dd6 100644 --- a/apps/mobile-app/utils/WebApiService.ts +++ b/apps/mobile-app/utils/WebApiService.ts @@ -306,11 +306,17 @@ export class WebApiService { /** * Status 0 = OK, vault is ready. * Status 1 = Merge required, which only the web client supports. + * Status 2 = Outdated, which means the local vault is outdated and the client should fetch the latest vault from the server before saving can continue. */ - if (vaultResponseJson.status !== 0) { + if (vaultResponseJson.status === 1) { + // Note: vault merge is no longer allowed by the API as of 0.20.0, updates with the same revision number are rejected. So this check can be removed later. return 'Your vault needs to be updated. Please login on the AliasVault website and follow the steps.'; } + if (vaultResponseJson.status === 2) { + return 'Your vault is outdated. Please login on the AliasVault website and follow the steps.'; + } + if (!vaultResponseJson.vault?.blob) { return 'Your account does not have a vault yet. Please complete the tutorial in the AliasVault web client before using the browser extension.'; } diff --git a/apps/server/AliasVault.Api/Controllers/VaultController.cs b/apps/server/AliasVault.Api/Controllers/VaultController.cs index b5ed41c5c..721a7fd2f 100644 --- a/apps/server/AliasVault.Api/Controllers/VaultController.cs +++ b/apps/server/AliasVault.Api/Controllers/VaultController.cs @@ -117,6 +117,10 @@ public class VaultController(ILogger logger, IAliasServerDbCont // Check if there are no other vaults with the same revision number. // If there are, return a merge required status. + // NOTE: a vault merge is no longer allowed by the API as of 0.20.0, updates with the same revision number are now rejected. + // So the logic below can be removed later, together with the local merge logic in the WASM client. + // We do probably want to still keep this until the datamodel has been updated to accomodate improved offline mode which might warrant + // a new and improved merge logic. So we keep this here for reference purposes for now. var duplicateRevisionCount = await context.Vaults .Where(x => x.UserId == user.Id && x.RevisionNumber == vault.RevisionNumber) .CountAsync(); @@ -237,10 +241,10 @@ public class VaultController(ILogger logger, IAliasServerDbCont var newRevisionNumber = model.CurrentRevisionNumber + 1; // Check if the latest vault revision number is equal to or higher than the new revision number. - // If so, reject update and return a merge required status. + // If so it means the client's vault is outdated and the client should fetch the latest vault from the server before saving can continue. if (latestVault.RevisionNumber >= newRevisionNumber) { - return Ok(new VaultUpdateResponse { Status = VaultStatus.MergeRequired, NewRevisionNumber = latestVault.RevisionNumber }); + return Ok(new VaultUpdateResponse { Status = VaultStatus.Outdated, NewRevisionNumber = latestVault.RevisionNumber }); } // Create new vault entry with salt and verifier of current vault. @@ -332,11 +336,15 @@ public class VaultController(ILogger logger, IAliasServerDbCont } // Calculate the new revision number for the vault. - // Note: it is possible multiple clients are updating the vault at the same time which would cause - // multiple vaults with the same revision number. This is expected and will trigger a vault - // synchronize/merge process on the client side. var newRevisionNumber = model.CurrentRevisionNumber + 1; + // Check if the latest vault revision number is equal to or higher than the new revision number. + // If so it means the client's vault is outdated and the client should fetch the latest vault from the server before saving can continue. + if (latestVault.RevisionNumber >= newRevisionNumber) + { + return Ok(new VaultUpdateResponse { Status = VaultStatus.Outdated, NewRevisionNumber = latestVault.RevisionNumber }); + } + // Create new vault entry with salt and verifier of current vault. var newVault = new AliasServerDb.Vault { diff --git a/apps/server/AliasVault.Client/Services/Database/DbService.cs b/apps/server/AliasVault.Client/Services/Database/DbService.cs index 8d615bbbf..8d80b2b5d 100644 --- a/apps/server/AliasVault.Client/Services/Database/DbService.cs +++ b/apps/server/AliasVault.Client/Services/Database/DbService.cs @@ -724,6 +724,11 @@ public sealed class DbService : IDisposable _state.UpdateState(DbServiceState.DatabaseStatus.MergeRequired); return await MergeDatabasesAsync(); } + else if (vaultUpdateResponse.Status == VaultStatus.Outdated) + { + // If the server responds with an outdated status, we need to fetch the latest vault from the server before we can continue. + return false; + } _vaultRevisionNumber = vaultUpdateResponse.NewRevisionNumber; return true; diff --git a/apps/server/Shared/AliasVault.Shared/Models/Enums/VaultStatus.cs b/apps/server/Shared/AliasVault.Shared/Models/Enums/VaultStatus.cs index 332e2a23e..39bdb33d6 100644 --- a/apps/server/Shared/AliasVault.Shared/Models/Enums/VaultStatus.cs +++ b/apps/server/Shared/AliasVault.Shared/Models/Enums/VaultStatus.cs @@ -15,10 +15,15 @@ public enum VaultStatus /// /// The vault was retrieved or updated successfully. /// - Ok, + Ok = 0, /// /// A client-side merge is required before the vault can be retrieved or updated. /// - MergeRequired, + MergeRequired = 1, + + /// + /// The local vault is outdated and the client should fetch the latest vault from the server before saving can continue. + /// + Outdated = 2, }