From 47201b5433d56a2431a8a447df65b1603ad1450c Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 12 Feb 2025 11:27:03 +0100 Subject: [PATCH] Add status call to unlock page, fix redirect bug (#541) --- .../chrome/src/app/pages/CredentialsList.tsx | 2 +- .../chrome/src/app/pages/Home.tsx | 20 ++++++++++---- .../chrome/src/app/pages/Unlock.tsx | 15 +++++++++-- .../src/background/VaultMessageHandler.ts | 4 +-- .../chrome/src/shared/WebApiService.ts | 26 ++++++++++++------- 5 files changed, 47 insertions(+), 20 deletions(-) diff --git a/browser-extensions/chrome/src/app/pages/CredentialsList.tsx b/browser-extensions/chrome/src/app/pages/CredentialsList.tsx index 9280167d..8c0b0064 100644 --- a/browser-extensions/chrome/src/app/pages/CredentialsList.tsx +++ b/browser-extensions/chrome/src/app/pages/CredentialsList.tsx @@ -72,7 +72,7 @@ const CredentialsList: React.FC = () => { const checkStatus = async (): Promise => { if (!dbContext?.sqliteClient) return; - const statusResponse = await webApi.get('Auth/status') as StatusResponse; + const statusResponse = await webApi.getStatus(); if (!statusResponse.supported) { authContext.logout('This version of the AliasVault browser extension is outdated. Please update to the latest version.'); return; diff --git a/browser-extensions/chrome/src/app/pages/Home.tsx b/browser-extensions/chrome/src/app/pages/Home.tsx index aef4fddc..d09e16c6 100644 --- a/browser-extensions/chrome/src/app/pages/Home.tsx +++ b/browser-extensions/chrome/src/app/pages/Home.tsx @@ -6,18 +6,20 @@ import UnlockSuccess from './UnlockSuccess'; import { useNavigate } from 'react-router-dom'; import { useDb } from '../context/DbContext'; import { useLoading } from '../context/LoadingContext'; +import { useWebApi } from '../context/WebApiContext'; /** * Home page that shows the correct page based on the user's authentication state. */ const Home: React.FC = () => { - const { isLoggedIn } = useAuth(); const authContext = useAuth(); const dbContext = useDb(); const navigate = useNavigate(); + const webApi = useWebApi(); const { setIsInitialLoading } = useLoading(); const [isInlineUnlockMode, setIsInlineUnlockMode] = useState(false); - const needsUnlock = (!authContext.isLoggedIn && authContext.isInitialized) || (!dbContext.dbAvailable && dbContext.dbInitialized); + const initialized = authContext.isInitialized && dbContext.dbInitialized; + const needsUnlock = initialized && (!authContext.isLoggedIn || !dbContext.dbAvailable); useEffect(() => { // Detect if the user is coming from the unlock page with mode=inline_unlock @@ -25,15 +27,23 @@ const Home: React.FC = () => { const isInlineUnlockMode = urlParams.get('mode') === 'inline_unlock'; setIsInlineUnlockMode(isInlineUnlockMode); - if (isLoggedIn && !needsUnlock && !isInlineUnlockMode) { + // Do a status check to see if the auth tokens are still valid, if not, redirect to the login page. + const checkStatus = async () => { + const status = await webApi.get('Status'); + if (status.status !== 0) { + authContext.logout(); + } + }; + + if (initialized && !needsUnlock) { navigate('/credentials', { replace: true }); } - }, [isLoggedIn, needsUnlock, isInlineUnlockMode, navigate]); + }, [initialized,needsUnlock, isInlineUnlockMode, navigate]); // Set initial loading state to false once the page is loaded until here. setIsInitialLoading(false); - if (!isLoggedIn) { + if (!authContext.isLoggedIn) { return ; } diff --git a/browser-extensions/chrome/src/app/pages/Unlock.tsx b/browser-extensions/chrome/src/app/pages/Unlock.tsx index 3917cf78..55b5c5e2 100644 --- a/browser-extensions/chrome/src/app/pages/Unlock.tsx +++ b/browser-extensions/chrome/src/app/pages/Unlock.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useDb } from '../context/DbContext'; import { useAuth } from '../context/AuthContext'; import { useWebApi } from '../context/WebApiContext'; @@ -23,6 +23,17 @@ const Unlock: React.FC = () => { const [error, setError] = useState(null); const { showLoading, hideLoading } = useLoading(); + useEffect(() => { + const checkStatus = async () => { + const status = await webApi.getStatus(); + if (!status.supported) { + authContext.logout('The browser extension is outdated. Please update to the latest version.'); + } + }; + + checkStatus(); + }, [webApi, authContext]); + /** * Handle submit */ @@ -30,6 +41,7 @@ const Unlock: React.FC = () => { e.preventDefault(); setError(null); showLoading(); + try { // 1. Initiate login to get salt and server ephemeral const loginResponse = await srpUtil.initiateLogin(authContext.username!); @@ -84,7 +96,6 @@ const Unlock: React.FC = () => { finally { hideLoading(); } - }; return ( diff --git a/browser-extensions/chrome/src/background/VaultMessageHandler.ts b/browser-extensions/chrome/src/background/VaultMessageHandler.ts index f5d3aaff..705512db 100644 --- a/browser-extensions/chrome/src/background/VaultMessageHandler.ts +++ b/browser-extensions/chrome/src/background/VaultMessageHandler.ts @@ -47,10 +47,10 @@ export async function handleSyncVault( ) : Promise { const webApi = new WebApiService(() => {}); await webApi.initializeBaseUrl(); - const response = await webApi.get('Auth/status') as StatusResponse; + const response = await webApi.getStatus(); if (!response.supported) { - sendResponse({ success: false, error: 'Browser extension is not supported. Please update to the latest version.' }); + sendResponse({ success: false, error: 'The browser extension is outdated. Please update to the latest version.' }); return; } diff --git a/browser-extensions/chrome/src/shared/WebApiService.ts b/browser-extensions/chrome/src/shared/WebApiService.ts index e800a0ce..c3d6e03a 100644 --- a/browser-extensions/chrome/src/shared/WebApiService.ts +++ b/browser-extensions/chrome/src/shared/WebApiService.ts @@ -1,4 +1,5 @@ import { AppInfo } from "./AppInfo"; +import { StatusResponse } from "./types/webapi/StatusResponse"; import { VaultResponse } from "./types/webapi/VaultResponse"; type RequestInit = globalThis.RequestInit; @@ -15,8 +16,6 @@ type TokenResponse = { * Service class for interacting with the web API. */ export class WebApiService { - private baseUrl: string = ''; - /** * Constructor for the WebApiService class. * @@ -25,17 +24,15 @@ export class WebApiService { public constructor( private handleLogout: () => void ) { - // Load the API URL from storage when service is initialized - this.initializeBaseUrl(); + // Remove initialization of baseUrl } /** - * Initialize the base URL for the API from settings. + * Get the base URL for the API from settings. */ - public async initializeBaseUrl() : Promise { + private async getBaseUrl(): Promise { const result = await chrome.storage.local.get(['apiUrl']); - // Trim trailing slash if present - this.baseUrl = (result.apiUrl || 'https://app.aliasvault.net/api').replace(/\/$/, '') + '/v1/'; + return (result.apiUrl || 'https://app.aliasvault.net/api').replace(/\/$/, '') + '/v1/'; } /** @@ -46,7 +43,8 @@ export class WebApiService { options: RequestInit = {}, parseJson: boolean = true ): Promise { - const url = this.baseUrl + endpoint; + const baseUrl = await this.getBaseUrl(); + const url = baseUrl + endpoint; const headers = new Headers(options.headers || {}); // Add authorization header if we have an access token @@ -107,7 +105,8 @@ export class WebApiService { } try { - const response = await fetch(`${this.baseUrl}Auth/refresh`, { + const baseUrl = await this.getBaseUrl(); + const response = await fetch(`${baseUrl}Auth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -191,6 +190,13 @@ export class WebApiService { }, false); } + /** + * Calls the status endpoint to check if the auth tokens are still valid, app is supported and the vault is up to date. + */ + public async getStatus(): Promise { + return await this.get('Auth/status') as StatusResponse; + } + /** * Validates the vault response and returns an error message if validation fails */