mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-11 08:48:33 -04:00
Add logout event emitter (#1274)
This commit is contained in:
committed by
Leendert de Borst
parent
624296da0d
commit
5215a0bdb8
@@ -1,8 +1,11 @@
|
||||
import React, { createContext, useContext, useMemo, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAuth } from '@/entrypoints/popup/context/AuthContext';
|
||||
import { useWebApi } from '@/entrypoints/popup/context/WebApiContext';
|
||||
|
||||
import { logoutEventEmitter } from '@/events/LogoutEventEmitter';
|
||||
|
||||
type AppContextType = {
|
||||
isLoggedIn: boolean;
|
||||
isInitialized: boolean;
|
||||
@@ -23,6 +26,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const auth = useAuth();
|
||||
const webApi = useWebApi();
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
/**
|
||||
* Logout the user by revoking tokens and clearing the auth tokens from storage.
|
||||
@@ -38,13 +42,22 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
* @returns boolean indicating whether the user is logged in.
|
||||
*/
|
||||
const initializeAuth = useCallback(async () : Promise<boolean> => {
|
||||
console.log('initializeAuth');
|
||||
const isLoggedIn = await auth.initializeAuth();
|
||||
console.log('isLoggedIn', isLoggedIn);
|
||||
setIsLoggedIn(isLoggedIn);
|
||||
return isLoggedIn;
|
||||
}, [auth]);
|
||||
|
||||
/**
|
||||
* Subscribe to logout events from WebApiService.
|
||||
*/
|
||||
useEffect(() => {
|
||||
const unsubscribe = logoutEventEmitter.subscribe(async (errorKey: string) => {
|
||||
await logout(t(errorKey));
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [logout, t]);
|
||||
|
||||
/**
|
||||
* Check for tokens in browser local storage on initial load when this context is mounted.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { createContext, useContext, useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import React, { createContext, useContext, useState, useMemo, useCallback } from 'react';
|
||||
import { sendMessage } from 'webext-bridge/popup';
|
||||
|
||||
import { useDb } from '@/entrypoints/popup/context/DbContext';
|
||||
|
||||
@@ -85,10 +85,6 @@ export const NavigationProvider: React.FC<{ children: React.ReactNode }> = ({ ch
|
||||
|
||||
// Listen on isloggedin state to redirect to login page if not logged in
|
||||
useEffect(() => {
|
||||
console.log('authInitialized', authInitialized);
|
||||
console.log('dbInitialized', dbInitialized);
|
||||
console.log('isFullyInitialized', isFullyInitialized);
|
||||
console.log('isLoggedIn', isLoggedIn);
|
||||
if (isFullyInitialized && !isLoggedIn) {
|
||||
navigate('/login', { replace: true });
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ const Login: React.FC = () => {
|
||||
showLoading();
|
||||
|
||||
if (!passwordHashString || !passwordHashBase64 || !loginResponse) {
|
||||
throw new Error(t('auth.errors.loginDataMissing'));
|
||||
throw new Error(t('common.errors.unknownError'));
|
||||
}
|
||||
|
||||
// Validate that 2FA code is a 6-digit number
|
||||
|
||||
38
apps/browser-extension/src/events/LogoutEventEmitter.ts
Normal file
38
apps/browser-extension/src/events/LogoutEventEmitter.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
type LogoutListener = (errorMessage: string) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Simple event emitter for logout events to avoid circular dependencies
|
||||
* between WebApiService and Auth contexts.
|
||||
*/
|
||||
class LogoutEventEmitter {
|
||||
private listeners: Set<LogoutListener> = new Set();
|
||||
|
||||
/**
|
||||
* Subscribe to logout events.
|
||||
* Returns an unsubscribe function.
|
||||
*/
|
||||
public subscribe(listener: LogoutListener): () => void {
|
||||
this.listeners.add(listener);
|
||||
return () => {
|
||||
this.listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a logout event to all listeners.
|
||||
*
|
||||
* @param errorKey - The translation key of the error message to emit.
|
||||
*/
|
||||
public emit(errorTranslationKey: string): void {
|
||||
this.listeners.forEach(listener => {
|
||||
try {
|
||||
listener(errorTranslationKey);
|
||||
} catch (error) {
|
||||
console.error('Error in logout listener:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const logoutEventEmitter = new LogoutEventEmitter();
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "An error occurred while checking for pending migrations.",
|
||||
"wrongPassword": "Incorrect password. Please try again.",
|
||||
"accountLocked": "Account temporarily locked due to too many failed attempts.",
|
||||
"networkError": "Network error. Please check your connection and try again.",
|
||||
"loginDataMissing": "Login session expired. Please try again."
|
||||
"networkError": "Network error. Please check your connection and try again."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "Beim Prüfen auf ausstehende Migrationen ist ein Fehler aufgetreten.",
|
||||
"wrongPassword": "Falsches Passwort. Bitte versuche es erneut.",
|
||||
"accountLocked": "Das Konto wurde wegen zu vieler fehlgeschlagener Anmeldeversuche vorübergehend gesperrt.",
|
||||
"networkError": "Netzwerkfehler. Bitte überprüfe Deine Verbindung und versuche es erneut.",
|
||||
"loginDataMissing": "Deine Anmelde-Sitzung ist abgelaufen. Bitte versuche es erneut."
|
||||
"networkError": "Netzwerkfehler. Bitte überprüfe Deine Verbindung und versuche es erneut."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"wrongPassword": "Incorrect password. Please try again.",
|
||||
"accountLocked": "Account temporarily locked due to too many failed attempts.",
|
||||
"networkError": "Network error. Please check your connection and try again.",
|
||||
"loginDataMissing": "Login session expired. Please try again."
|
||||
"sessionExpired": "Your session has expired. Please log in again."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "An error occurred while checking for pending migrations.",
|
||||
"wrongPassword": "Incorrect password. Please try again.",
|
||||
"accountLocked": "Account temporarily locked due to too many failed attempts.",
|
||||
"networkError": "Network error. Please check your connection and try again.",
|
||||
"loginDataMissing": "Login session expired. Please try again."
|
||||
"networkError": "Network error. Please check your connection and try again."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "Tapahtui virhe tarkistettaessa odottavia siirtoja.",
|
||||
"wrongPassword": "Virheellinen salasana. Yritä uudelleen.",
|
||||
"accountLocked": "Tili on tilapäisesti lukittu liian monen epäonnistuneen yrityksen vuoksi. Yritä myöhemmin uudelleen.",
|
||||
"networkError": "Verkkovirhe: tarkista yhteytesi ja yritä uudelleen.",
|
||||
"loginDataMissing": "Kirjautumisistunto on vanhentunut. Yritä uudelleen."
|
||||
"networkError": "Verkkovirhe: tarkista yhteytesi ja yritä uudelleen."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "Une erreur s'est produite lors de la vérification des migrations en attente.",
|
||||
"wrongPassword": "Mot de passe incorrect, veuillez réessayer.",
|
||||
"accountLocked": "Compte temporairement verrouillé en raison d'un trop grand nombre de tentatives échouées.",
|
||||
"networkError": "Erreur réseau. Vérifiez votre connexion et réessayez.",
|
||||
"loginDataMissing": "La session a expiré. Veuillez réessayer."
|
||||
"networkError": "Erreur réseau. Vérifiez votre connexion et réessayez."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "אירעה שגיאה בעת בדיקה לאיתור הסבות ממתינות.",
|
||||
"wrongPassword": "סיסמה שגויה. נא לנסות שוב.",
|
||||
"accountLocked": "החשבון נעול זמנית עקב ריבוי ניסיונות כושלים.",
|
||||
"networkError": "שגיאת רשת. נא לבדוק את החיבור ולנסות שוב.",
|
||||
"loginDataMissing": "תוקף ההפעלה שלך פג. נא לנסות שוב."
|
||||
"networkError": "שגיאת רשת. נא לבדוק את החיבור ולנסות שוב."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "Si è verificato un errore nel controllo delle migrazioni pendenti.",
|
||||
"wrongPassword": "Password non corretta. Riprova nuovamente.",
|
||||
"accountLocked": "Account temporaneamente bloccato a causa di troppi tentativi falliti.",
|
||||
"networkError": "Errore di rete: Controlla la tua connessione e riprova.",
|
||||
"loginDataMissing": "Sessione di accesso scaduta. Effettua nuovamente l'accesso."
|
||||
"networkError": "Errore di rete: Controlla la tua connessione e riprova."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "Er is een fout opgetreden bij het controleren op updates.",
|
||||
"wrongPassword": "Onjuist wachtwoord. Probeer het opnieuw.",
|
||||
"accountLocked": "Account tijdelijk vergrendeld vanwege te veel mislukte pogingen.",
|
||||
"networkError": "Netwerkfout. Controleer de verbinding en probeer het opnieuw.",
|
||||
"loginDataMissing": "Sessie verlopen. Probeer het opnieuw."
|
||||
"networkError": "Netwerkfout. Controleer de verbinding en probeer het opnieuw."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "An error occurred while checking for pending migrations.",
|
||||
"wrongPassword": "Incorrect password. Please try again.",
|
||||
"accountLocked": "Account temporarily locked due to too many failed attempts.",
|
||||
"networkError": "Network error. Please check your connection and try again.",
|
||||
"loginDataMissing": "Login session expired. Please try again."
|
||||
"networkError": "Network error. Please check your connection and try again."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "Возникла ошибка при проверке ожидающих перемещений.",
|
||||
"wrongPassword": "Неверный пароль. Пожалуйста, повторите попытку.",
|
||||
"accountLocked": "Аккаунт временно заблокирован из-за слишком большого числа неудачных попыток.",
|
||||
"networkError": "Ошибка сети. Пожалуйста, проверьте соединение и повторите еще раз.",
|
||||
"loginDataMissing": "Время входа истекло. Пожалуйста, повторите попытку."
|
||||
"networkError": "Ошибка сети. Пожалуйста, проверьте соединение и повторите еще раз."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "An error occurred while checking for pending migrations.",
|
||||
"wrongPassword": "Incorrect password. Please try again.",
|
||||
"accountLocked": "Account temporarily locked due to too many failed attempts.",
|
||||
"networkError": "Network error. Please check your connection and try again.",
|
||||
"loginDataMissing": "Login session expired. Please try again."
|
||||
"networkError": "Network error. Please check your connection and try again."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "An error occurred while checking for pending migrations.",
|
||||
"wrongPassword": "Incorrect password. Please try again.",
|
||||
"accountLocked": "Account temporarily locked due to too many failed attempts.",
|
||||
"networkError": "Network error. Please check your connection and try again.",
|
||||
"loginDataMissing": "Login session expired. Please try again."
|
||||
"networkError": "Network error. Please check your connection and try again."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "Під час перевірки незавершених перенесень сталася помилка.",
|
||||
"wrongPassword": "Невірний пароль. Будь ласка, спробуйте ще раз.",
|
||||
"accountLocked": "Обліковий запис тимчасово заблоковано через занадто багато невдалих спроб.",
|
||||
"networkError": "Помилка мережі. Будь ласка, перевірте з’єднання та спробуйте ще раз.",
|
||||
"loginDataMissing": "Термін дії сеансу закінчився. Будь ласка, спробуйте ще раз."
|
||||
"networkError": "Помилка мережі. Будь ласка, перевірте з’єднання та спробуйте ще раз."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
"migrationError": "检查待处理迁移时发生错误。",
|
||||
"wrongPassword": "密码不正确。请重试。",
|
||||
"accountLocked": "由于多次尝试失败,账户已暂时锁定。",
|
||||
"networkError": "网络错误。请检查你的连接后重试。",
|
||||
"loginDataMissing": "登录会话已过期。请重试。"
|
||||
"networkError": "网络错误。请检查你的连接后重试。"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { StatusResponse } from '@/utils/dist/shared/models/webapi';
|
||||
|
||||
import { logoutEventEmitter } from '@/events/LogoutEventEmitter';
|
||||
|
||||
import { AppInfo } from "./AppInfo";
|
||||
|
||||
import { storage } from '#imports';
|
||||
@@ -74,7 +76,7 @@ export class WebApiService {
|
||||
|
||||
return parseJson ? retryResponse.json() : retryResponse as unknown as T;
|
||||
} else {
|
||||
this.logout('Your session has expired. Please login again.');
|
||||
logoutEventEmitter.emit('auth.errors.sessionExpired');
|
||||
throw new Error('Session expired');
|
||||
}
|
||||
}
|
||||
@@ -118,41 +120,6 @@ export class WebApiService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the access token.
|
||||
*/
|
||||
private async refreshAccessToken(): Promise<string | null> {
|
||||
const refreshToken = await this.getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.rawFetch('Auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Ignore-Failure': 'true',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: await this.getAccessToken(),
|
||||
refreshToken: refreshToken,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to refresh token');
|
||||
}
|
||||
|
||||
const tokenResponse: TokenResponse = await response.json();
|
||||
this.updateTokens(tokenResponse.token, tokenResponse.refreshToken);
|
||||
return tokenResponse.token;
|
||||
} catch {
|
||||
this.logout('Your session has expired. Please login again.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue GET request to the API.
|
||||
*/
|
||||
@@ -271,6 +238,41 @@ export class WebApiService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the access token.
|
||||
*/
|
||||
private async refreshAccessToken(): Promise<string | null> {
|
||||
const refreshToken = await this.getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.rawFetch('Auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Ignore-Failure': 'true',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: await this.getAccessToken(),
|
||||
refreshToken: refreshToken,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to refresh token');
|
||||
}
|
||||
|
||||
const tokenResponse: TokenResponse = await response.json();
|
||||
this.updateTokens(tokenResponse.token, tokenResponse.refreshToken);
|
||||
return tokenResponse.token;
|
||||
} catch {
|
||||
logoutEventEmitter.emit('auth.errors.sessionExpired');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current access token from storage.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user