Add server version check (#541)

This commit is contained in:
Leendert de Borst
2025-02-20 15:22:30 +01:00
parent 57673b5ee0
commit 45f748e247
10 changed files with 47 additions and 41 deletions

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
@@ -9,13 +9,15 @@ import { useAuth } from '../context/AuthContext';
const GlobalStateChangeHandler: React.FC = () => {
const authContext = useAuth();
const navigate = useNavigate();
const lastLoginState = useRef(authContext.isLoggedIn);
/**
* Listen for auth logged in changes and redirect to home page if logged in state changes to handle logins and logouts.
*/
useEffect(() => {
// Only navigate when auth state changes and we're not already on home page
if (window.location.pathname !== '/index.html' && window.location.pathname !== '/') {
// Only navigate when auth state is different from the last state we acted on.
if (lastLoginState.current !== authContext.isLoggedIn) {
lastLoginState.current = authContext.isLoggedIn;
navigate('/');
}
}, [authContext.isLoggedIn]); // eslint-disable-line react-hooks/exhaustive-deps

View File

@@ -10,7 +10,6 @@ import ReloadButton from '../components/ReloadButton';
import { useAuth } from '../context/AuthContext';
import LoadingSpinner from '../components/LoadingSpinner';
import { useMinDurationLoading } from '../hooks/useMinDurationLoading';
import { AppInfo } from '../../shared/AppInfo';
/**
* Credentials list page.
@@ -38,15 +37,9 @@ const CredentialsList: React.FC = () => {
// Do status check first to ensure the extension is (still) supported.
const statusResponse = await webApi.getStatus();
console.log('CredentialsList: statusResponse', statusResponse);
if (!statusResponse.clientVersionSupported) {
authContext.logout('This version of the AliasVault browser extension is outdated. Please update your browser extension to the latest version.');
return;
}
// Check if server version is supported by this client.
if (!AppInfo.isServerVersionSupported(statusResponse.serverVersion)) {
authContext.logout('The AliasVault server needs to be updated to a newer version in order to use this browser extension. Please contact support if you need help.');
const statusError = webApi.validateStatusResponse(statusResponse);
if (statusError !== null) {
authContext.logout(statusError);
return;
}

View File

@@ -38,29 +38,20 @@ const Home: React.FC = () => {
// Show loading state if not fully initialized or when about to redirect to credentials.
if (!isFullyInitialized || (isFullyInitialized && !requireLoginOrUnlock)) {
// Global loading spinner will be shown by the parent component.
console.log('Home: not fully initialized');
return null;
}
setIsInitialLoading(false);
if (!isAuthenticated) {
console.log('Home: not authenticated');
return <Login />;
}
if (!isDatabaseAvailable) {
console.log('isFullyInitialized', isFullyInitialized);
console.log('isAuthenticated', isAuthenticated);
console.log('isDatabaseAvailable', isDatabaseAvailable);
console.log('isInlineUnlockMode', isInlineUnlockMode);
console.log('requireLoginOrUnlock', requireLoginOrUnlock);
console.log('Home: not database available');
return <Unlock />;
}
if (isInlineUnlockMode) {
console.log('Home: inline unlock mode');
return <UnlockSuccess onClose={() => setIsInlineUnlockMode(false)} />;
}

View File

@@ -167,7 +167,6 @@ const Login: React.FC = () => {
return;
}
// All is good. Store auth info which is required to make requests to the web API.
await authContext.setAuthTokens(credentials.username, validationResponse.token.token, validationResponse.token.refreshToken);

View File

@@ -28,9 +28,11 @@ const Unlock: React.FC = () => {
* Make status call to API which acts as health check.
*/
const checkStatus = async () : Promise<void> => {
const status = await webApi.getStatus();
if (!status.clientVersionSupported) {
authContext.logout('The browser extension is outdated. Please update to the latest version.');
const statusResponse = await webApi.getStatus();
const statusError = webApi.validateStatusResponse(statusResponse);
if (statusError !== null) {
authContext.logout(statusError);
return;
}
};

View File

@@ -45,10 +45,10 @@ export async function handleSyncVault(
sendResponse: (response: any) => void
) : Promise<void> {
const webApi = new WebApiService(() => {});
const response = await webApi.getStatus();
if (!response.clientVersionSupported) {
sendResponse({ success: false, error: 'The browser extension is outdated. Please update to the latest version.' });
const statusResponse = await webApi.getStatus();
const statusError = webApi.validateStatusResponse(statusResponse);
if (statusError !== null) {
sendResponse({ success: false, error: statusError });
return;
}
@@ -56,7 +56,7 @@ export async function handleSyncVault(
'vaultRevisionNumber'
]);
if (response.vaultRevision > result.vaultRevisionNumber) {
if (statusResponse.vaultRevision > result.vaultRevisionNumber) {
// Retrieve the latest vault from the server.
const vaultResponse = await webApi.get<VaultResponse>('Vault');

View File

@@ -2,14 +2,20 @@
* AppInfo class which contains information about the application version.
*/
export class AppInfo {
// Current extension version - should be updated with each release.
/**
* The current extension version. This should be updated with each release of the extension.
*/
public static readonly VERSION = '0.12.0';
// Minimum supported AliasVault server (API) version. If the server version is below this, the
// client will throw an error stating that the server should be updated.
public static readonly MIN_SERVER_VERSION = '0.13.0';
/**
* The minimum supported AliasVault server (API) version. If the server version is below this, the
* client will throw an error stating that the server should be updated.
*/
public static readonly MIN_SERVER_VERSION = '0.12.0-dev';
// Minimum supported AliasVault client vault version.
/**
* The minimum supported AliasVault client vault version.
*/
public static readonly MIN_VAULT_VERSION = '1.4.1';
/*
@@ -65,8 +71,7 @@ export class AppInfo {
if (part1 < part2) return false;
}
// If core versions are equal, check pre-release versions
// No pre-release > pre-release
// If core versions are equal, check pre-release versions.
if (!preRelease1 && preRelease2) return true;
if (preRelease1 && !preRelease2) return false;
if (!preRelease1 && !preRelease2) return true;

View File

@@ -218,6 +218,21 @@ export class WebApiService {
return await this.get<StatusResponse>('Auth/status');
}
/**
* Validates the status response and returns an error message if validation fails.
*/
public validateStatusResponse(statusResponse: StatusResponse): string | null {
if (!statusResponse.clientVersionSupported) {
return 'This version of the AliasVault browser extension is outdated. Please update your browser extension to the latest version.';
}
if (!AppInfo.isServerVersionSupported(statusResponse.serverVersion)) {
return 'The AliasVault server needs to be updated to a newer version in order to use this browser extension. Please contact support if you need help.';
}
return null;
}
/**
* Validates the vault response and returns an error message if validation fails
*/

View File

@@ -1,7 +1,6 @@
import { AppInfo } from '../AppInfo';
import { describe, it, expect } from 'vitest';
describe('AppInfo', () => {
describe('isVersionSupported', () => {
it('should support exact version match', () => {

View File

@@ -25,12 +25,12 @@ public static class AppInfo
/// <summary>
/// Gets the minor version number.
/// </summary>
public const int VersionMinor = 11;
public const int VersionMinor = 12;
/// <summary>
/// Gets the patch version number.
/// </summary>
public const int VersionPatch = 1;
public const int VersionPatch = 0;
/// <summary>
/// Gets a dictionary of minimum supported client versions that the WebApi supports.