mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 23:33:31 +00:00
Auth - PM-7392 & PM-7436 - Token Service - Desktop - Add disk fallback for secure storage failures (#8913)
* PM-7392 - EncryptSvc - add new method for detecting if a simple string is an enc string. * PM-7392 - TokenSvc - add checks when setting and retrieving the access token to improve handling around the access token encryption. * PM-7392 - (1) Clean up token svc (2) export access token key type for use in tests. * PM-7392 - Get token svc tests passing; WIP more tests to come for new scenarios. * PM-7392 - Access token secure storage to disk fallback WIP but mostly functional besides weird logout behavior. * PM-7392 - Clean up unnecessary comment * PM-7392 - TokenSvc - refresh token disk storage fallback * PM-7392 - Fix token service tests in prep for adding tests for new scenarios. * PM-7392 - TokenSvc tests - Test new setRefreshToken scenarios * PM-7392 - TokenSvc - getRefreshToken should return null or a value - not undefined. * PM-7392 - Fix test name. * PM-7392 - TokenSvc tests - clean up test names that reference removed refresh token migrated flag. * PM-7392 - getRefreshToken tests done. * PM-7392 - Fix error quote * PM-7392 - TokenSvc tests - setAccessToken new scenarios tested. * PM-7392 - TokenSvc - getAccessToken - if secure storage errors add error to log. * PM-7392 - TokenSvc tests - getAccessToken - all new scenarios tested * PM-7392 - EncryptSvc - test new stringIsEncString method * PM-7392 - Main.ts - fix circ dep issue. * PM-7392 - Main.ts - remove comment. * PM-7392 - Don't re-invent the wheel and simply use existing isSerializedEncString static method. * PM-7392 - Enc String - (1) Add handling for Nan in parseEncryptedString (2) Added null handling to isSerializedEncString. (3) Plan to remove encrypt service implementation Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * PM-7392 - Remove encrypt service method Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * PM-7392 - Actually fix circ dep issues with Justin. Ty! Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * PM-7392 - TokenSvc - update to use EncString instead of EncryptSvc + fix tests. * PM-7392 - TokenSvc - (1) Remove test code (2) Refactor decryptAccessToken method to accept access token key and error on failure to pass required decryption key to method. * PM-7392 - Per PR feedback and discussion, do not log the user out if hte refresh token cannot be found. This will allow users to continue to use the app until their access token expires and we will error on trying to refresh it. The app will then still work on a fresh login for 55 min. * PM-7392 - API service - update doAuthRefresh error to clarify which token cannot be refreshed. * PM-7392 - Fix SetRefreshToken case where a null input would incorrectly trigger a fallback to disk. * PM-7392 - If the access token cannot be refreshed due to a missing refresh token or API keys, then surface an error to the user and log it so it isn't a silent failure + we get a log. * PM-7392 - Fix CLI build errors * PM-7392 - Per PR feedback, add missing tests (thank you Jake for writing these!) Co-authored-by: Jake Fink <jfink@bitwarden.com> * PM-7392 - Per PR feedback, update incorrect comment from 3 releases to 3 months. * PM-7392 - Per PR feedback, remove links. * PM-7392 - Per PR feedback, move tests to existing describe. * PM-7392 - Per PR feedback, adjust all test names to match naming convention. * PM-7392 - ApiService - refreshIdentityToken - log error before swallowing it so we have a record of it. * PM-7392 - Fix copy for errorRefreshingAccessToken * PM-7392 - Per PR feedback, move error handling toast responsibility to client specific app component logic reached via messaging. * PM-7392 - Swap logout reason from enum to type. * PM-7392 - ApiService - Stop using messaging to trigger toast to let user know about refresh access token errors; replace with client specific callback logic. Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * PM-7392 - Per PR feedback, adjust enc string changes and tests. * PM-7392 - Rename file to be type from enum * PM-7392 - ToastService - we need to await the activeToast.onHidden observable so return the activeToast from the showToast. * PM-7392 - Desktop AppComp - cleanup messaging * PM-7392 - Move Logout reason custom type to auth/common * PM-7392 - WIP - Enhancing logout callback to consider the logout reason + move show toast logic into logout callback * PM-7392 - Logout callback should simply pass along the LogoutReason instead of handling it - let each client's message listener handle it. * PM-7392 - More replacements of expired with logoutReason * PM-7392 - More expired to logoutReason replacements * PM-7392 - Build new handlers for displaying the logout reason for desktop & web. * PM-7392 - Revert ToastService changes * PM-7392 - TokenSvc - Replace messageSender with logout callback per PR feedback. * PM-7392 - Desktop App comp - replace toast usage with simple dialog to guarantee users will see the reason for them being logged out. * PM-7392 - Web app comp - fix issue * PM-7392 - Desktop App comp - don't show cancel btn on simple dialogs. * PM-7392 - Desktop App comp - Don't open n simple dialogs. * PM-7392 - Fix browser build * PM-7392 - Remove logout reason from CLI as each logout call handles messaging on its own. * PM-7392 - Previously, if a security stamp was invalid, the session was marked as expired. Restore that functionality. * PM-7392 - Update sync service logoutCallback to include optional user id. * PM-7392 - Clean up web app comp * PM-7392 - Web - app comp - only handle actually possible web logout scenarios. * PM-7392 - Browser Popup app comp - restore done logging out message functionality + add new default logout message * PM-7392 - Add optional user id to logout callbacks. * PM-7392 - Main.background.ts - add clarifying comment. * PM-7392 - Per feedback, use danger simple dialog type for error. * PM-7392 - Browser Popup - add comment clarifying expectation of seeing toasts. * PM-7392 - Consolidate invalidSecurityStamp error handling * PM-7392 - Per PR feedback, REFRESH_ACCESS_TOKEN_ERROR_CALLBACK can be completely sync. + Refactor to method in main.background. * PM-7392 - Per PR feedback, use a named callback for refreshAccessTokenErrorCallback in CLI * PM-7392 - Add TODO * PM-7392 - Re-apply bw.ts changes to new service-container. * PM-7392 - TokenSvc - tweak error message. * PM-7392 - Fix test * PM-7392 - Clean up merge conflict where I duplicated dependencies. * PM-7392 - Per discussion with product, change default logout toast to be info * PM-7392 - After merge, add new logout reason to sync service. * PM-7392 - Remove default logout message per discussion with product since it isn't really visible on desktop or browser. * PM-7392 - address PR feedback. --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Jake Fink <jfink@bitwarden.com> Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
This commit is contained in:
@@ -599,6 +599,9 @@
|
||||
"loggedOut": {
|
||||
"message": "Logged out"
|
||||
},
|
||||
"loggedOutDesc": {
|
||||
"message": "You have been logged out of your account."
|
||||
},
|
||||
"loginExpired": {
|
||||
"message": "Your login session has expired."
|
||||
},
|
||||
@@ -1744,6 +1747,12 @@
|
||||
"ok": {
|
||||
"message": "Ok"
|
||||
},
|
||||
"errorRefreshingAccessToken":{
|
||||
"message": "Access Token Refresh Error"
|
||||
},
|
||||
"errorRefreshingAccessTokenDesc":{
|
||||
"message": "No refresh token or API keys found. Please try logging out and logging back in."
|
||||
},
|
||||
"desktopSyncVerificationTitle": {
|
||||
"message": "Desktop sync verification"
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
AuthRequestService,
|
||||
LoginEmailServiceAbstraction,
|
||||
LoginEmailService,
|
||||
LogoutReason,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||
@@ -375,8 +376,17 @@ export default class MainBackground {
|
||||
}
|
||||
};
|
||||
|
||||
const logoutCallback = async (expired: boolean, userId?: UserId) =>
|
||||
await this.logout(expired, userId);
|
||||
const logoutCallback = async (logoutReason: LogoutReason, userId?: UserId) =>
|
||||
await this.logout(logoutReason, userId);
|
||||
|
||||
const refreshAccessTokenErrorCallback = () => {
|
||||
// Send toast to popup
|
||||
this.messagingService.send("showToast", {
|
||||
type: "error",
|
||||
title: this.i18nService.t("errorRefreshingAccessToken"),
|
||||
message: this.i18nService.t("errorRefreshingAccessTokenDesc"),
|
||||
});
|
||||
};
|
||||
|
||||
const isDev = process.env.ENV === "development";
|
||||
this.logService = new ConsoleLogService(isDev);
|
||||
@@ -523,6 +533,7 @@ export default class MainBackground {
|
||||
this.keyGenerationService,
|
||||
this.encryptService,
|
||||
this.logService,
|
||||
logoutCallback,
|
||||
);
|
||||
|
||||
const migrationRunner = new MigrationRunner(
|
||||
@@ -608,9 +619,12 @@ export default class MainBackground {
|
||||
this.platformUtilsService,
|
||||
this.environmentService,
|
||||
this.appIdService,
|
||||
refreshAccessTokenErrorCallback,
|
||||
this.logService,
|
||||
(logoutReason: LogoutReason, userId?: UserId) => this.logout(logoutReason, userId),
|
||||
this.vaultTimeoutSettingsService,
|
||||
(expired: boolean) => this.logout(expired),
|
||||
);
|
||||
|
||||
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
|
||||
this.fileUploadService = new FileUploadService(this.logService);
|
||||
this.cipherFileUploadService = new CipherFileUploadService(
|
||||
@@ -1283,7 +1297,7 @@ export default class MainBackground {
|
||||
}
|
||||
}
|
||||
|
||||
async logout(expired: boolean, userId?: UserId) {
|
||||
async logout(logoutReason: LogoutReason, userId?: UserId) {
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
map((a) => a?.id),
|
||||
@@ -1349,7 +1363,7 @@ export default class MainBackground {
|
||||
await logoutPromise;
|
||||
|
||||
this.messagingService.send("doneLoggingOut", {
|
||||
expired: expired,
|
||||
logoutReason: logoutReason,
|
||||
userId: userBeingLoggedOut,
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angula
|
||||
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
||||
import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap } from "rxjs";
|
||||
|
||||
import { LogoutReason } from "@bitwarden/auth/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
@@ -10,7 +11,12 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { MessageListener } from "@bitwarden/common/platform/messaging";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
|
||||
import {
|
||||
DialogService,
|
||||
SimpleDialogOptions,
|
||||
ToastOptions,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { BrowserApi } from "../platform/browser/browser-api";
|
||||
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
|
||||
@@ -83,13 +89,10 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
.pipe(
|
||||
tap((msg: any) => {
|
||||
if (msg.command === "doneLoggingOut") {
|
||||
// TODO: PM-8544 - why do we call logout in the popup after receiving the doneLoggingOut message? Hasn't this already completeted logout?
|
||||
this.authService.logOut(async () => {
|
||||
if (msg.expired) {
|
||||
this.toastService.showToast({
|
||||
variant: "warning",
|
||||
title: this.i18nService.t("loggedOut"),
|
||||
message: this.i18nService.t("loginExpired"),
|
||||
});
|
||||
if (msg.logoutReason) {
|
||||
await this.displayLogoutReason(msg.logoutReason);
|
||||
}
|
||||
});
|
||||
this.changeDetectorRef.detectChanges();
|
||||
@@ -233,4 +236,23 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
this.browserSendStateService.setBrowserSendTypeComponentState(null),
|
||||
]);
|
||||
}
|
||||
|
||||
// Displaying toasts isn't super useful on the popup due to the reloads we do.
|
||||
// However, it is visible for a moment on the FF sidebar logout.
|
||||
private async displayLogoutReason(logoutReason: LogoutReason) {
|
||||
let toastOptions: ToastOptions;
|
||||
switch (logoutReason) {
|
||||
case "invalidSecurityStamp":
|
||||
case "sessionExpired": {
|
||||
toastOptions = {
|
||||
variant: "warning",
|
||||
title: this.i18nService.t("loggedOut"),
|
||||
message: this.i18nService.t("loginExpired"),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.toastService.showToast(toastOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaul
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||
|
||||
@@ -21,8 +22,10 @@ export class NodeApiService extends ApiService {
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
appIdService: AppIdService,
|
||||
refreshAccessTokenErrorCallback: () => Promise<void>,
|
||||
logService: LogService,
|
||||
logoutCallback: () => Promise<void>,
|
||||
vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
logoutCallback: (expired: boolean) => Promise<void>,
|
||||
customUserAgent: string = null,
|
||||
) {
|
||||
super(
|
||||
@@ -30,8 +33,10 @@ export class NodeApiService extends ApiService {
|
||||
platformUtilsService,
|
||||
environmentService,
|
||||
appIdService,
|
||||
vaultTimeoutSettingsService,
|
||||
refreshAccessTokenErrorCallback,
|
||||
logService,
|
||||
logoutCallback,
|
||||
vaultTimeoutSettingsService,
|
||||
customUserAgent,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -255,6 +255,8 @@ export class ServiceContainer {
|
||||
p = path.join(process.env.HOME, ".config/Bitwarden CLI");
|
||||
}
|
||||
|
||||
const logoutCallback = async () => await this.logout();
|
||||
|
||||
this.platformUtilsService = new CliPlatformUtilsService(ClientType.Cli, packageJson);
|
||||
this.logService = new ConsoleLogService(
|
||||
this.platformUtilsService.isDev(),
|
||||
@@ -337,6 +339,7 @@ export class ServiceContainer {
|
||||
this.keyGenerationService,
|
||||
this.encryptService,
|
||||
this.logService,
|
||||
logoutCallback,
|
||||
);
|
||||
|
||||
const migrationRunner = new MigrationRunner(
|
||||
@@ -421,13 +424,19 @@ export class ServiceContainer {
|
||||
VaultTimeoutStringType.Never, // default vault timeout
|
||||
);
|
||||
|
||||
const refreshAccessTokenErrorCallback = () => {
|
||||
throw new Error("Refresh Access token error");
|
||||
};
|
||||
|
||||
this.apiService = new NodeApiService(
|
||||
this.tokenService,
|
||||
this.platformUtilsService,
|
||||
this.environmentService,
|
||||
this.appIdService,
|
||||
refreshAccessTokenErrorCallback,
|
||||
this.logService,
|
||||
logoutCallback,
|
||||
this.vaultTimeoutSettingsService,
|
||||
async (expired: boolean) => await this.logout(),
|
||||
customUserAgent,
|
||||
);
|
||||
|
||||
@@ -485,7 +494,7 @@ export class ServiceContainer {
|
||||
this.logService,
|
||||
this.organizationService,
|
||||
this.keyGenerationService,
|
||||
async (expired: boolean) => await this.logout(),
|
||||
logoutCallback,
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
@@ -660,7 +669,7 @@ export class ServiceContainer {
|
||||
this.sendApiService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.avatarService,
|
||||
async (expired: boolean) => await this.logout(),
|
||||
logoutCallback,
|
||||
this.billingAccountProfileStateService,
|
||||
this.tokenService,
|
||||
this.authService,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DialogRef } from "@angular/cdk/dialog";
|
||||
import {
|
||||
Component,
|
||||
NgZone,
|
||||
@@ -13,6 +14,7 @@ import { filter, firstValueFrom, map, Subject, takeUntil, timeout } from "rxjs";
|
||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
|
||||
import { LogoutReason } from "@bitwarden/auth/common";
|
||||
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
@@ -48,7 +50,7 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti
|
||||
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { DialogService, ToastOptions, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||
import { LoginApprovalComponent } from "../auth/login/login-approval.component";
|
||||
@@ -108,6 +110,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private idleTimer: number = null;
|
||||
private isIdle = false;
|
||||
private activeUserId: UserId = null;
|
||||
private activeSimpleDialog: DialogRef<boolean> = null;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@@ -207,7 +210,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
break;
|
||||
case "logout":
|
||||
this.loading = message.userId == null || message.userId === this.activeUserId;
|
||||
await this.logOut(!!message.expired, message.userId);
|
||||
await this.logOut(message.logoutReason, message.userId);
|
||||
this.loading = false;
|
||||
break;
|
||||
case "lockVault":
|
||||
@@ -545,9 +548,73 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
this.messagingService.send("updateAppMenu", { updateRequest: updateRequest });
|
||||
}
|
||||
|
||||
private async displayLogoutReason(logoutReason: LogoutReason) {
|
||||
let toastOptions: ToastOptions;
|
||||
|
||||
switch (logoutReason) {
|
||||
case "invalidSecurityStamp":
|
||||
case "sessionExpired": {
|
||||
toastOptions = {
|
||||
variant: "warning",
|
||||
title: this.i18nService.t("loggedOut"),
|
||||
message: this.i18nService.t("loginExpired"),
|
||||
};
|
||||
break;
|
||||
}
|
||||
// We don't expect these scenarios to be common, but we want the user to
|
||||
// understand why they are being logged out before a process reload.
|
||||
case "accessTokenUnableToBeDecrypted": {
|
||||
// Don't create multiple dialogs if this fires multiple times
|
||||
if (this.activeSimpleDialog) {
|
||||
// Let the caller of this function listen for the dialog to close
|
||||
return firstValueFrom(this.activeSimpleDialog.closed);
|
||||
}
|
||||
|
||||
this.activeSimpleDialog = this.dialogService.openSimpleDialogRef({
|
||||
title: { key: "loggedOut" },
|
||||
content: { key: "accessTokenUnableToBeDecrypted" },
|
||||
acceptButtonText: { key: "ok" },
|
||||
cancelButtonText: null,
|
||||
type: "danger",
|
||||
});
|
||||
|
||||
await firstValueFrom(this.activeSimpleDialog.closed);
|
||||
this.activeSimpleDialog = null;
|
||||
|
||||
break;
|
||||
}
|
||||
case "refreshTokenSecureStorageRetrievalFailure": {
|
||||
// Don't create multiple dialogs if this fires multiple times
|
||||
if (this.activeSimpleDialog) {
|
||||
// Let the caller of this function listen for the dialog to close
|
||||
return firstValueFrom(this.activeSimpleDialog.closed);
|
||||
}
|
||||
|
||||
this.activeSimpleDialog = this.dialogService.openSimpleDialogRef({
|
||||
title: { key: "loggedOut" },
|
||||
content: { key: "refreshTokenSecureStorageRetrievalFailure" },
|
||||
acceptButtonText: { key: "ok" },
|
||||
cancelButtonText: null,
|
||||
type: "danger",
|
||||
});
|
||||
|
||||
await firstValueFrom(this.activeSimpleDialog.closed);
|
||||
this.activeSimpleDialog = null;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toastOptions) {
|
||||
this.toastService.showToast(toastOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Even though the userId parameter is no longer optional doesn't mean a message couldn't be
|
||||
// passing null-ish values to us.
|
||||
private async logOut(expired: boolean, userId: UserId) {
|
||||
private async logOut(logoutReason: LogoutReason, userId: UserId) {
|
||||
await this.displayLogoutReason(logoutReason);
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
@@ -620,15 +687,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
// This must come last otherwise the logout will prematurely trigger
|
||||
// a process reload before all the state service user data can be cleaned up
|
||||
if (userBeingLoggedOut === activeUserId) {
|
||||
this.authService.logOut(async () => {
|
||||
if (expired) {
|
||||
this.platformUtilsService.showToast(
|
||||
"warning",
|
||||
this.i18nService.t("loggedOut"),
|
||||
this.i18nService.t("loginExpired"),
|
||||
);
|
||||
}
|
||||
});
|
||||
this.authService.logOut(async () => {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,7 +769,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
options[1] === "logOut"
|
||||
? this.logOut(false, userId as UserId)
|
||||
? this.logOut("vaultTimeout", userId as UserId)
|
||||
: await this.vaultTimeoutService.lock(userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"loggedOut": {
|
||||
"message": "Logged out"
|
||||
},
|
||||
"loggedOutDesc": {
|
||||
"message": "You have been logged out of your account."
|
||||
},
|
||||
"loginExpired": {
|
||||
"message": "Your login session has expired."
|
||||
},
|
||||
@@ -1212,6 +1215,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"errorRefreshingAccessToken":{
|
||||
"message": "Access Token Refresh Error"
|
||||
},
|
||||
"errorRefreshingAccessTokenDesc":{
|
||||
"message": "No refresh token or API keys found. Please try logging out and logging back in."
|
||||
},
|
||||
"help": {
|
||||
"message": "Help"
|
||||
},
|
||||
@@ -2474,6 +2483,12 @@
|
||||
"important": {
|
||||
"message": "Important:"
|
||||
},
|
||||
"accessTokenUnableToBeDecrypted": {
|
||||
"message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue."
|
||||
},
|
||||
"refreshTokenSecureStorageRetrievalFailure": {
|
||||
"message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue."
|
||||
},
|
||||
"masterPasswordHint": {
|
||||
"message": "Your master password cannot be recovered if you forget it!"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as path from "path";
|
||||
import { app } from "electron";
|
||||
import { Subject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { LogoutReason } from "@bitwarden/auth/common";
|
||||
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/services/token.service";
|
||||
@@ -31,6 +32,7 @@ import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state
|
||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
/* eslint-enable import/no-restricted-paths */
|
||||
|
||||
import { DesktopAutofillSettingsService } from "./autofill/services/desktop-autofill-settings.service";
|
||||
@@ -182,6 +184,7 @@ export class Main {
|
||||
this.keyGenerationService,
|
||||
this.encryptService,
|
||||
this.logService,
|
||||
async (logoutReason: LogoutReason, userId?: UserId) => {},
|
||||
);
|
||||
|
||||
this.migrationRunner = new MigrationRunner(
|
||||
@@ -207,11 +210,9 @@ export class Main {
|
||||
);
|
||||
|
||||
this.desktopSettingsService = new DesktopSettingsService(stateProvider);
|
||||
|
||||
const biometricStateService = new DefaultBiometricStateService(stateProvider);
|
||||
|
||||
this.windowMain = new WindowMain(
|
||||
this.stateService,
|
||||
biometricStateService,
|
||||
this.logService,
|
||||
this.storageService,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { app, BrowserWindow, ipcMain, nativeTheme, screen, session } from "elect
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
|
||||
@@ -38,7 +37,6 @@ export class WindowMain {
|
||||
readonly defaultHeight = 600;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
private logService: LogService,
|
||||
private storageService: AbstractStorageService,
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
timer,
|
||||
} from "rxjs";
|
||||
|
||||
import { LogoutReason } from "@bitwarden/auth/common";
|
||||
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
@@ -40,7 +41,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { DialogService, ToastOptions, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { PolicyListService } from "./admin-console/core/policy-list.service";
|
||||
import {
|
||||
@@ -148,7 +149,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.router.navigate(["/"]);
|
||||
break;
|
||||
case "logout":
|
||||
await this.logOut(!!message.expired, message.redirect);
|
||||
await this.logOut(message.logoutReason, message.redirect);
|
||||
break;
|
||||
case "lockVault":
|
||||
await this.vaultTimeoutService.lock();
|
||||
@@ -278,7 +279,34 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private async logOut(expired: boolean, redirect = true) {
|
||||
private async displayLogoutReason(logoutReason: LogoutReason) {
|
||||
let toastOptions: ToastOptions;
|
||||
switch (logoutReason) {
|
||||
case "invalidSecurityStamp":
|
||||
case "sessionExpired": {
|
||||
toastOptions = {
|
||||
variant: "warning",
|
||||
title: this.i18nService.t("loggedOut"),
|
||||
message: this.i18nService.t("loginExpired"),
|
||||
};
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
toastOptions = {
|
||||
variant: "info",
|
||||
title: this.i18nService.t("loggedOut"),
|
||||
message: this.i18nService.t("loggedOutDesc"),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.toastService.showToast(toastOptions);
|
||||
}
|
||||
|
||||
private async logOut(logoutReason: LogoutReason, redirect = true) {
|
||||
await this.displayLogoutReason(logoutReason);
|
||||
|
||||
await this.eventUploadService.uploadEvents();
|
||||
const userId = (await this.stateService.getUserId()) as UserId;
|
||||
|
||||
@@ -308,14 +336,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
|
||||
await this.searchService.clearIndex();
|
||||
this.authService.logOut(async () => {
|
||||
if (expired) {
|
||||
this.platformUtilsService.showToast(
|
||||
"warning",
|
||||
this.i18nService.t("loggedOut"),
|
||||
this.i18nService.t("loginExpired"),
|
||||
);
|
||||
}
|
||||
|
||||
await this.stateService.clean({ userId: userId });
|
||||
await this.accountService.clean(userId);
|
||||
|
||||
|
||||
@@ -587,6 +587,9 @@
|
||||
"loggedOut": {
|
||||
"message": "Logged out"
|
||||
},
|
||||
"loggedOutDesc": {
|
||||
"message": "You have been logged out of your account."
|
||||
},
|
||||
"loginExpired": {
|
||||
"message": "Your login session has expired."
|
||||
},
|
||||
@@ -1050,6 +1053,12 @@
|
||||
"copyUuid": {
|
||||
"message": "Copy UUID"
|
||||
},
|
||||
"errorRefreshingAccessToken":{
|
||||
"message": "Access Token Refresh Error"
|
||||
},
|
||||
"errorRefreshingAccessTokenDesc":{
|
||||
"message": "No refresh token or API keys found. Please try logging out and logging back in."
|
||||
},
|
||||
"warning": {
|
||||
"message": "Warning"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user