From 4c347d09ac6415e31eba5564565a799a86cf6481 Mon Sep 17 00:00:00 2001
From: neuronull <9162534+neuronull@users.noreply.github.com>
Date: Tue, 21 Oct 2025 07:22:09 -0700
Subject: [PATCH 01/16] Fix biometric v2 windows unit test clippy lint (#16961)
---
apps/desktop/desktop_native/core/src/biometric_v2/windows.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs b/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs
index 479f96312ce..043c2453cd0 100644
--- a/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs
+++ b/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs
@@ -376,6 +376,7 @@ unsafe fn as_mut_bytes(buffer: &IBuffer) -> Result<&mut [u8]> {
}
#[cfg(test)]
+#[allow(clippy::print_stdout)]
mod tests {
use crate::biometric_v2::{
biometric_v2::{
From f1340c67da7fe069be9e848b6d7188b829571c4c Mon Sep 17 00:00:00 2001
From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Date: Tue, 21 Oct 2025 15:46:15 +0100
Subject: [PATCH 02/16] [PM-27050] Fix : Cannot upload self-hosted license on
new Premium page (#16919)
* Fix the isselfhost bug
* Resolve the self-host issue
---
.../individual-billing-routing.module.ts | 26 +++++++++++++------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/apps/web/src/app/billing/individual/individual-billing-routing.module.ts b/apps/web/src/app/billing/individual/individual-billing-routing.module.ts
index 0bc6b1effbb..26d0c43ff8f 100644
--- a/apps/web/src/app/billing/individual/individual-billing-routing.module.ts
+++ b/apps/web/src/app/billing/individual/individual-billing-routing.module.ts
@@ -1,8 +1,11 @@
-import { NgModule } from "@angular/core";
+import { inject, NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
+import { map } from "rxjs";
-import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
+import { componentRouteSwap } from "@bitwarden/angular/utils/component-route-swap";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
+import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { AccountPaymentDetailsComponent } from "@bitwarden/web-vault/app/billing/individual/payment-details/account-payment-details.component";
import { BillingHistoryViewComponent } from "./billing-history-view.component";
@@ -23,15 +26,22 @@ const routes: Routes = [
component: UserSubscriptionComponent,
data: { titleId: "premiumMembership" },
},
- ...featureFlaggedRoute({
- defaultComponent: PremiumComponent,
- flaggedComponent: PremiumVNextComponent,
- featureFlag: FeatureFlag.PM24033PremiumUpgradeNewDesign,
- routeOptions: {
+ ...componentRouteSwap(
+ PremiumComponent,
+ PremiumVNextComponent,
+ () => {
+ const configService = inject(ConfigService);
+ const platformUtilsService = inject(PlatformUtilsService);
+
+ return configService
+ .getFeatureFlag$(FeatureFlag.PM24033PremiumUpgradeNewDesign)
+ .pipe(map((flagValue) => flagValue === true && !platformUtilsService.isSelfHost()));
+ },
+ {
data: { titleId: "goPremium" },
path: "premium",
},
- }),
+ ),
{
path: "payment-details",
component: AccountPaymentDetailsComponent,
From a5dd42396cdf5b8beda4aa242c103157d5aa3bc6 Mon Sep 17 00:00:00 2001
From: Alex <55413326+AlexRubik@users.noreply.github.com>
Date: Tue, 21 Oct 2025 11:02:44 -0400
Subject: [PATCH 03/16] [PM-27024] password progress card at risk detection
(#16955)
* [PM-27024] Fix password change progress card to track only critical apps and detect new at-risk passwords
- Filter at-risk password count to critical applications only
- Update state logic to transition back to assign tasks when new at-risk passwords detected
- Only create security tasks for critical applications with at-risk passwords
- Show 'X new passwords at-risk' message when tasks exist and new at-risk passwords appear
* spec
---
apps/web/src/locales/en/messages.json | 9 +++
.../services/all-activities.service.ts | 4 +-
.../password-change-metric.component.html | 6 +-
.../password-change-metric.component.ts | 68 +++++++++++++------
.../shared/security-tasks.service.spec.ts | 20 ++++++
.../shared/security-tasks.service.ts | 3 +-
6 files changed, 87 insertions(+), 23 deletions(-)
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 5eab55e2301..1f8c4ec55b2 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -154,6 +154,15 @@
}
}
},
+ "newPasswordsAtRisk": {
+ "message": "$COUNT$ new passwords at-risk",
+ "placeholders": {
+ "count": {
+ "content": "$1",
+ "example": "5"
+ }
+ }
+ },
"notifiedMembersWithCount": {
"message": "Notified members ($COUNT$)",
"placeholders": {
diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts
index ee7f0fae56a..4044a01926c 100644
--- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts
+++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts
@@ -76,7 +76,9 @@ export class AllActivitiesService {
}
setAllAppsReportDetails(applications: ApplicationHealthReportDetailEnriched[]) {
- const totalAtRiskPasswords = applications.reduce(
+ // Only count at-risk passwords for CRITICAL applications
+ const criticalApps = applications.filter((app) => app.isMarkedAsCritical);
+ const totalAtRiskPasswords = criticalApps.reduce(
(sum, app) => sum + app.atRiskPasswordCount,
0,
);
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.html
index 718efc4c67e..674bc0b5c62 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.html
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.html
@@ -21,7 +21,11 @@
- {{ "countOfAtRiskPasswords" | i18n: atRiskPasswordsCount }}
+ {{
+ hasExistingTasks
+ ? ("newPasswordsAtRisk" | i18n: newAtRiskPasswordsCount)
+ : ("countOfAtRiskPasswords" | i18n: atRiskPasswordsCount)
+ }}
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.ts
index 3b8475ed5cf..4d2085d7c3a 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.ts
@@ -9,6 +9,7 @@ import {
LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
SecurityTasksApiService,
TaskMetrics,
+ OrganizationReportSummary,
} from "@bitwarden/bit-common/dirt/reports/risk-insights";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { ButtonModule, ProgressModule, TypographyModule } from "@bitwarden/components";
@@ -73,25 +74,8 @@ export class PasswordChangeMetricComponent implements OnInit {
this.totalTasks = taskMetrics.totalTasks;
this.allApplicationsDetails = allApplicationsDetails;
- // No critical apps setup
- this.renderMode =
- summary.totalCriticalApplicationCount === 0 ? RenderMode.noCriticalApps : this.renderMode;
-
- // Critical apps setup with at-risk apps but no tasks
- this.renderMode =
- summary.totalCriticalApplicationCount > 0 &&
- summary.totalCriticalAtRiskApplicationCount >= 0 &&
- taskMetrics.totalTasks === 0
- ? RenderMode.criticalAppsWithAtRiskAppsAndNoTasks
- : this.renderMode;
-
- // Critical apps setup with at-risk apps and tasks
- this.renderMode =
- summary.totalAtRiskApplicationCount > 0 &&
- summary.totalCriticalAtRiskApplicationCount >= 0 &&
- taskMetrics.totalTasks > 0
- ? RenderMode.criticalAppsWithAtRiskAppsAndTasks
- : this.renderMode;
+ // Determine render mode based on state
+ this.renderMode = this.determineRenderMode(summary, taskMetrics, atRiskPasswordsCount);
this.allActivitiesService.setPasswordChangeProgressMetricHasProgressBar(
this.renderMode === RenderMode.criticalAppsWithAtRiskAppsAndTasks,
@@ -106,6 +90,38 @@ export class PasswordChangeMetricComponent implements OnInit {
protected accessIntelligenceSecurityTasksService: AccessIntelligenceSecurityTasksService,
) {}
+ private determineRenderMode(
+ summary: OrganizationReportSummary,
+ taskMetrics: TaskMetrics,
+ atRiskPasswordsCount: number,
+ ): RenderMode {
+ // State 1: No critical apps setup
+ if (summary.totalCriticalApplicationCount === 0) {
+ return RenderMode.noCriticalApps;
+ }
+
+ // State 2: Critical apps with at-risk passwords but no tasks assigned yet
+ // OR tasks exist but NEW at-risk passwords detected (more at-risk passwords than tasks)
+ if (
+ summary.totalCriticalApplicationCount > 0 &&
+ (taskMetrics.totalTasks === 0 || atRiskPasswordsCount > taskMetrics.totalTasks)
+ ) {
+ return RenderMode.criticalAppsWithAtRiskAppsAndNoTasks;
+ }
+
+ // State 3: Critical apps with at-risk apps and tasks (progress tracking)
+ if (
+ summary.totalCriticalApplicationCount > 0 &&
+ taskMetrics.totalTasks > 0 &&
+ atRiskPasswordsCount <= taskMetrics.totalTasks
+ ) {
+ return RenderMode.criticalAppsWithAtRiskAppsAndTasks;
+ }
+
+ // Default to no critical apps
+ return RenderMode.noCriticalApps;
+ }
+
get completedPercent(): number {
if (this.totalTasks === 0) {
return 0;
@@ -144,7 +160,19 @@ export class PasswordChangeMetricComponent implements OnInit {
}
get canAssignTasks(): boolean {
- return this.atRiskAppsCount > this.totalTasks ? true : false;
+ return this.atRiskPasswordsCount > this.totalTasks;
+ }
+
+ get hasExistingTasks(): boolean {
+ return this.totalTasks > 0;
+ }
+
+ get newAtRiskPasswordsCount(): number {
+ // Calculate new at-risk passwords as the difference between current count and tasks created
+ if (this.atRiskPasswordsCount > this.totalTasks) {
+ return this.atRiskPasswordsCount - this.totalTasks;
+ }
+ return 0;
}
get renderModes() {
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.spec.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.spec.ts
index 520164c80e7..a57bdfc279c 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.spec.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.spec.ts
@@ -40,6 +40,7 @@ describe("AccessIntelligenceSecurityTasksService", () => {
const organizationId = "org-1" as OrganizationId;
const apps = [
{
+ isMarkedAsCritical: true,
atRiskPasswordCount: 1,
atRiskCipherIds: ["cid1"],
} as LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
@@ -56,10 +57,12 @@ describe("AccessIntelligenceSecurityTasksService", () => {
const organizationId = "org-2" as OrganizationId;
const apps = [
{
+ isMarkedAsCritical: true,
atRiskPasswordCount: 2,
atRiskCipherIds: ["cid1", "cid2"],
} as LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
{
+ isMarkedAsCritical: true,
atRiskPasswordCount: 1,
atRiskCipherIds: ["cid2"],
} as LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
@@ -85,6 +88,7 @@ describe("AccessIntelligenceSecurityTasksService", () => {
const organizationId = "org-3" as OrganizationId;
const apps = [
{
+ isMarkedAsCritical: true,
atRiskPasswordCount: 1,
atRiskCipherIds: ["cid3"],
} as LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
@@ -106,6 +110,7 @@ describe("AccessIntelligenceSecurityTasksService", () => {
const organizationId = "org-4" as OrganizationId;
const apps = [
{
+ isMarkedAsCritical: true,
atRiskPasswordCount: 0,
atRiskCipherIds: ["cid4"],
} as LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
@@ -115,5 +120,20 @@ describe("AccessIntelligenceSecurityTasksService", () => {
expect(defaultAdminTaskServiceSpy.bulkCreateTasks).toHaveBeenCalledWith(organizationId, []);
expect(result).toBe(0);
});
+
+ it("should not create any tasks for non-critical apps", async () => {
+ const organizationId = "org-5" as OrganizationId;
+ const apps = [
+ {
+ isMarkedAsCritical: false,
+ atRiskPasswordCount: 2,
+ atRiskCipherIds: ["cid5", "cid6"],
+ } as LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
+ ];
+ const result = await service.requestPasswordChange(organizationId, apps);
+
+ expect(defaultAdminTaskServiceSpy.bulkCreateTasks).toHaveBeenCalledWith(organizationId, []);
+ expect(result).toBe(0);
+ });
});
});
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts
index c5610c638b0..34fd6daa2b0 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts
@@ -33,8 +33,9 @@ export class AccessIntelligenceSecurityTasksService {
organizationId: OrganizationId,
apps: LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher[],
): Promise
{
+ // Only create tasks for CRITICAL applications with at-risk passwords
const cipherIds = apps
- .filter((_) => _.atRiskPasswordCount > 0)
+ .filter((_) => _.isMarkedAsCritical && _.atRiskPasswordCount > 0)
.flatMap((app) => app.atRiskCipherIds);
const distinctCipherIds = Array.from(new Set(cipherIds));
From 5a307633bb8d1d7347d67bc3249d0393ca8ec23c Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann
Date: Tue, 21 Oct 2025 17:24:52 +0200
Subject: [PATCH 04/16] [PM-26778] Make VaultTimeoutService use LogoutService
(#16820)
* Make vaulttimeoutservice use logoutservice
* Fix browser build
* Fix mv3 build
* Fix lint
---
apps/browser/src/background/idle.background.ts | 5 ++++-
apps/browser/src/background/main.background.ts | 5 ++++-
.../foreground-vault-timeout.service.ts | 4 ----
.../key-rotation/user-key-rotation.service.ts | 7 +++----
.../src/services/jslib-services.module.ts | 2 +-
.../abstractions/vault-timeout.service.ts | 1 -
.../services/vault-timeout.service.spec.ts | 14 +++++++-------
.../services/vault-timeout.service.ts | 17 ++++-------------
8 files changed, 23 insertions(+), 32 deletions(-)
diff --git a/apps/browser/src/background/idle.background.ts b/apps/browser/src/background/idle.background.ts
index 2de4b48a9c0..0f89aa4792a 100644
--- a/apps/browser/src/background/idle.background.ts
+++ b/apps/browser/src/background/idle.background.ts
@@ -1,5 +1,6 @@
import { firstValueFrom } from "rxjs";
+import { LogoutService } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
VaultTimeoutAction,
@@ -8,6 +9,7 @@ import {
VaultTimeoutStringType,
} from "@bitwarden/common/key-management/vault-timeout";
import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications";
+import { UserId } from "@bitwarden/user-core";
const IdleInterval = 60 * 5; // 5 minutes
@@ -21,6 +23,7 @@ export default class IdleBackground {
private serverNotificationsService: ServerNotificationsService,
private accountService: AccountService,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
+ private logoutService: LogoutService,
) {
this.idle = chrome.idle || (browser != null ? browser.idle : null);
}
@@ -61,7 +64,7 @@ export default class IdleBackground {
this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(userId),
);
if (action === VaultTimeoutAction.LogOut) {
- await this.vaultTimeoutService.logOut(userId);
+ await this.logoutService.logout(userId as UserId, "vaultTimeout");
} else {
await this.vaultTimeoutService.lock(userId);
}
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index 7ba55a45892..c4c412732c9 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -21,6 +21,7 @@ import {
AuthRequestServiceAbstraction,
DefaultAuthRequestApiService,
DefaultLockService,
+ DefaultLogoutService,
InternalUserDecryptionOptionsServiceAbstraction,
LoginEmailServiceAbstraction,
LogoutReason,
@@ -976,6 +977,7 @@ export default class MainBackground {
this.restrictedItemTypesService,
);
+ const logoutService = new DefaultLogoutService(this.messagingService);
this.vaultTimeoutService = new VaultTimeoutService(
this.accountService,
this.masterPasswordService,
@@ -994,7 +996,7 @@ export default class MainBackground {
this.logService,
this.biometricsService,
lockedCallback,
- logoutCallback,
+ logoutService,
);
this.containerService = new ContainerService(this.keyService, this.encryptService);
@@ -1386,6 +1388,7 @@ export default class MainBackground {
this.serverNotificationsService,
this.accountService,
this.vaultTimeoutSettingsService,
+ logoutService,
);
this.usernameGenerationService = legacyUsernameGenerationServiceFactory(
diff --git a/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts b/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts
index 5003dfd5b29..4081ab03359 100644
--- a/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts
+++ b/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts
@@ -13,8 +13,4 @@ export class ForegroundVaultTimeoutService implements BaseVaultTimeoutService {
async lock(userId?: UserId): Promise {
this.messagingService.send("lockVault", { userId });
}
-
- async logOut(userId?: string): Promise {
- this.messagingService.send("logout", { userId });
- }
}
diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts
index 0980beddd09..168dbe7442e 100644
--- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts
+++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts
@@ -1,6 +1,7 @@
import { Injectable } from "@angular/core";
import { firstValueFrom, Observable } from "rxjs";
+import { LogoutService } from "@bitwarden/auth/common";
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
@@ -14,7 +15,6 @@ import {
WrappedPrivateKey,
WrappedSigningKey,
} from "@bitwarden/common/key-management/types";
-import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -89,7 +89,7 @@ export class UserKeyRotationService {
private syncService: SyncService,
private webauthnLoginAdminService: WebauthnLoginAdminService,
private logService: LogService,
- private vaultTimeoutService: VaultTimeoutService,
+ private logoutService: LogoutService,
private toastService: ToastService,
private i18nService: I18nService,
private dialogService: DialogService,
@@ -189,8 +189,7 @@ export class UserKeyRotationService {
timeout: 15000,
});
- // temporary until userkey can be better verified
- await this.vaultTimeoutService.logOut();
+ await this.logoutService.logout(user.id);
}
protected async ensureIsAllowedToRotateUserKey(): Promise {
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index f5642f45b2e..5d2a23444f0 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -891,7 +891,7 @@ const safeProviders: SafeProvider[] = [
LogService,
BiometricsService,
LOCKED_CALLBACK,
- LOGOUT_CALLBACK,
+ LogoutService,
],
}),
safeProvider({
diff --git a/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout.service.ts b/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout.service.ts
index 1c88a5c51ea..401fb8b107b 100644
--- a/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout.service.ts
+++ b/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout.service.ts
@@ -1,5 +1,4 @@
export abstract class VaultTimeoutService {
abstract checkVaultTimeout(): Promise;
abstract lock(userId?: string): Promise;
- abstract logOut(userId?: string): Promise;
}
diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts
index da815f76f79..5ba434f7188 100644
--- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts
+++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts
@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
-import { MockProxy, any, mock } from "jest-mock-extended";
+import { MockProxy, mock } from "jest-mock-extended";
import { BehaviorSubject, from, of } from "rxjs";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
@@ -8,7 +8,7 @@ import { BehaviorSubject, from, of } from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
-import { LogoutReason } from "@bitwarden/auth/common";
+import { LogoutService } from "@bitwarden/auth/common";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { BiometricsService } from "@bitwarden/key-management";
@@ -53,8 +53,8 @@ describe("VaultTimeoutService", () => {
let taskSchedulerService: MockProxy;
let logService: MockProxy;
let biometricsService: MockProxy;
+ let logoutService: MockProxy;
let lockedCallback: jest.Mock, [userId: string]>;
- let loggedOutCallback: jest.Mock, [logoutReason: LogoutReason, userId?: string]>;
let vaultTimeoutActionSubject: BehaviorSubject;
let availableVaultTimeoutActionsSubject: BehaviorSubject;
@@ -80,9 +80,9 @@ describe("VaultTimeoutService", () => {
taskSchedulerService = mock();
logService = mock();
biometricsService = mock();
+ logoutService = mock();
lockedCallback = jest.fn();
- loggedOutCallback = jest.fn();
vaultTimeoutActionSubject = new BehaviorSubject(VaultTimeoutAction.Lock);
@@ -110,7 +110,7 @@ describe("VaultTimeoutService", () => {
logService,
biometricsService,
lockedCallback,
- loggedOutCallback,
+ logoutService,
);
});
@@ -213,12 +213,12 @@ describe("VaultTimeoutService", () => {
};
const expectUserToHaveLoggedOut = (userId: string) => {
- expect(loggedOutCallback).toHaveBeenCalledWith("vaultTimeout", userId);
+ expect(logoutService.logout).toHaveBeenCalledWith(userId, "vaultTimeout");
};
const expectNoAction = (userId: string) => {
expect(lockedCallback).not.toHaveBeenCalledWith(userId);
- expect(loggedOutCallback).not.toHaveBeenCalledWith(any(), userId);
+ expect(logoutService.logout).not.toHaveBeenCalledWith(userId, "vaultTimeout");
};
describe("checkVaultTimeout", () => {
diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts
index 8b523498c31..c0fa0423694 100644
--- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts
+++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts
@@ -7,7 +7,7 @@ import { combineLatest, concatMap, filter, firstValueFrom, map, timeout } from "
import { CollectionService } from "@bitwarden/admin-console/common";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
-import { LogoutReason } from "@bitwarden/auth/common";
+import { LogoutService } from "@bitwarden/auth/common";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { BiometricsService } from "@bitwarden/key-management";
@@ -52,10 +52,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
protected logService: LogService,
private biometricService: BiometricsService,
private lockedCallback: (userId: UserId) => Promise = null,
- private loggedOutCallback: (
- logoutReason: LogoutReason,
- userId?: string,
- ) => Promise = null,
+ private logoutService: LogoutService,
) {
this.taskSchedulerService.registerTaskHandler(
ScheduledTaskNames.vaultTimeoutCheckInterval,
@@ -123,7 +120,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
);
const supportsLock = availableActions.includes(VaultTimeoutAction.Lock);
if (!supportsLock) {
- await this.logOut(userId);
+ await this.logoutService.logout(userId, "vaultTimeout");
}
// HACK: Start listening for the transition of the locking user from something to the locked state.
@@ -165,12 +162,6 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
}
}
- async logOut(userId?: string): Promise {
- if (this.loggedOutCallback != null) {
- await this.loggedOutCallback("vaultTimeout", userId);
- }
- }
-
private async shouldLock(
userId: string,
lastActive: Date,
@@ -214,7 +205,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(userId),
);
timeoutAction === VaultTimeoutAction.LogOut
- ? await this.logOut(userId)
+ ? await this.logoutService.logout(userId, "vaultTimeout")
: await this.lock(userId);
}
}
From 93ab65cab9f66a8cadfb975fc09dc49a9425728f Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann
Date: Tue, 21 Oct 2025 18:12:27 +0200
Subject: [PATCH 05/16] Fix codeowners for biometrics v2 (#16962)
---
.github/CODEOWNERS | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 255ddc08c80..ae5d62a90c2 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -180,6 +180,8 @@ libs/common/src/key-management @bitwarden/team-key-management-dev
libs/node @bitwarden/team-key-management-dev
apps/desktop/desktop_native/core/src/biometric/ @bitwarden/team-key-management-dev
+apps/desktop/desktop_native/core/src/biometric_v2/ @bitwarden/team-key-management-dev
+apps/desktop/desktop_native/core/src/secure_memory/ @bitwarden/team-key-management-dev
apps/desktop/src/services/native-messaging.service.ts @bitwarden/team-key-management-dev
apps/browser/src/background/nativeMessaging.background.ts @bitwarden/team-key-management-dev
apps/desktop/src/services/biometric-message-handler.service.ts @bitwarden/team-key-management-dev
From 1794803debcb87bfb8b447729aeda4c3c044c931 Mon Sep 17 00:00:00 2001
From: Kyle Denney <4227399+kdenney@users.noreply.github.com>
Date: Tue, 21 Oct 2025 11:18:15 -0500
Subject: [PATCH 06/16] [PM-24032] upgrade nav button updates (#16933)
* [PM-24032] upgrade nav button post-upgrade action
* fixing broken stories
* added component tests
* new behavior for the nav button when self hosted
* fix stories
---
.../upgrade-nav-button.component.html | 2 +-
.../upgrade-nav-button.component.spec.ts | 162 ++++++++++++++++++
.../upgrade-nav-button.component.ts | 40 ++++-
.../upgrade-nav-button.stories.ts | 21 +++
4 files changed, 220 insertions(+), 5 deletions(-)
create mode 100644 apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.spec.ts
diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html b/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html
index 115c0be86a2..2ffcd14fab0 100644
--- a/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html
+++ b/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html
@@ -5,7 +5,7 @@