diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts
index 116d048d2e8..c134860f1a8 100644
--- a/apps/browser/src/background/nativeMessaging.background.ts
+++ b/apps/browser/src/background/nativeMessaging.background.ts
@@ -257,7 +257,7 @@ export class NativeMessagingBackground {
message.command == BiometricsCommands.Unlock ||
message.command == BiometricsCommands.IsAvailable
) {
- // TODO remove after 2025.01
+ // TODO remove after 2025.3
// wait until there is no other callbacks, or timeout
const call = await firstValueFrom(
race(
diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts
index 77c0fcaf50a..711ae4e378f 100644
--- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts
+++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts
@@ -54,7 +54,15 @@ export class ExtensionLockComponentService implements LockComponentService {
if (!(await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$))) {
return BiometricsStatus.NotEnabledLocally;
} else {
- return await this.biometricsService.getBiometricsStatusForUser(userId);
+ // TODO remove after 2025.3
+ // remove after backward compatibility code for old biometrics ipc protocol is removed
+ const result: BiometricsStatus = (await Promise.race([
+ this.biometricsService.getBiometricsStatusForUser(userId),
+ new Promise((resolve) =>
+ setTimeout(() => resolve(BiometricsStatus.DesktopDisconnected), 1000),
+ ),
+ ])) as BiometricsStatus;
+ return result;
}
}),
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
diff --git a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml
index 02f2474927e..fea28052f8d 100644
--- a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml
+++ b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml
@@ -8,7 +8,6 @@ command: bitwarden.sh
finish-args:
- --share=ipc
- --share=network
- - --socket=wayland
- --socket=x11
- --device=dri
- --env=XDG_CURRENT_DESKTOP=Unity
@@ -43,5 +42,4 @@ modules:
commands:
- ulimit -c 0
- export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID"
- - exec zypak-wrapper /app/bin/bitwarden-app --ozone-platform-hint=auto
- --enable-features=WaylandWindowDecorations "$@"
+ - exec zypak-wrapper /app/bin/bitwarden-app "$@"
diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts
index 5fefbf9ddff..a05b09e139e 100644
--- a/apps/desktop/src/app/app.component.ts
+++ b/apps/desktop/src/app/app.component.ts
@@ -330,6 +330,20 @@ export class AppComponent implements OnInit, OnDestroy {
}
break;
}
+ case "upgradeOrganization": {
+ const upgradeConfirmed = await this.dialogService.openSimpleDialog({
+ title: { key: "upgradeOrganization" },
+ content: { key: "upgradeOrganizationDesc" },
+ acceptButtonText: { key: "learnMore" },
+ type: "info",
+ });
+ if (upgradeConfirmed) {
+ this.platformUtilsService.launchUri(
+ "https://bitwarden.com/help/upgrade-from-individual-to-org/",
+ );
+ }
+ break;
+ }
case "emailVerificationRequired": {
const emailVerificationConfirmed = await this.dialogService.openSimpleDialog({
title: { key: "emailVerificationRequired" },
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 1a02e5db4e7..bca12f16a7d 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -3474,5 +3474,14 @@
},
"changeAcctEmail": {
"message": "Change account email"
+ },
+ "organizationUpgradeRequired": {
+ "message": "Organization upgrade required"
+ },
+ "upgradeOrganization": {
+ "message": "Upgrade organization"
+ },
+ "upgradeOrganizationDesc": {
+ "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features."
}
}
diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts
index ea1e7e76c56..72d00988e88 100644
--- a/apps/desktop/src/services/biometric-message-handler.service.ts
+++ b/apps/desktop/src/services/biometric-message-handler.service.ts
@@ -188,7 +188,7 @@ export class BiometricMessageHandlerService {
appId,
);
}
- // TODO: legacy, remove after 2025.01
+ // TODO: legacy, remove after 2025.3
case BiometricsCommands.IsAvailable: {
const available =
(await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available;
@@ -200,7 +200,7 @@ export class BiometricMessageHandlerService {
appId,
);
}
- // TODO: legacy, remove after 2025.01
+ // TODO: legacy, remove after 2025.3
case BiometricsCommands.Unlock: {
const isTemporarilyDisabled =
(await this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId)) &&
diff --git a/apps/desktop/src/vault/app/vault/view.component.html b/apps/desktop/src/vault/app/vault/view.component.html
index 59e609312d7..f589ba53046 100644
--- a/apps/desktop/src/vault/app/vault/view.component.html
+++ b/apps/desktop/src/vault/app/vault/view.component.html
@@ -186,6 +186,16 @@
+
diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts
index 23b85eceda2..d3e8fff3495 100644
--- a/apps/desktop/src/vault/app/vault/view.component.ts
+++ b/apps/desktop/src/vault/app/vault/view.component.ts
@@ -157,4 +157,10 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
this.messagingService.send("premiumRequired");
}
}
+
+ upgradeOrganization() {
+ this.messagingService.send("upgradeOrganization", {
+ organizationId: this.cipher.organizationId,
+ });
+ }
}
diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts
index 56db7dc88da..916c845e9d3 100644
--- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts
+++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts
@@ -66,7 +66,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
protected messagingService: MessagingService,
eventCollectionService: EventCollectionService,
protected policyService: PolicyService,
- organizationService: OrganizationService,
+ protected organizationService: OrganizationService,
logService: LogService,
passwordRepromptService: PasswordRepromptService,
dialogService: DialogService,
@@ -307,7 +307,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
this.cipher.type === CipherType.Login &&
this.cipher.login.totp &&
this.organization?.productTierType != ProductTierType.Free &&
- (this.cipher.organizationUseTotp || this.canAccessPremium)
+ ((this.canAccessPremium && this.cipher.organizationId == null) ||
+ this.cipher.organizationUseTotp)
);
}
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts
index f773db6c11c..ecf649b8f31 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts
@@ -23,8 +23,7 @@ import { KeyService } from "@bitwarden/key-management";
templateUrl: "setup.component.html",
})
export class SetupComponent implements OnInit, OnDestroy {
- @ViewChild(ManageTaxInformationComponent)
- manageTaxInformationComponent: ManageTaxInformationComponent;
+ @ViewChild(ManageTaxInformationComponent) taxInformationComponent: ManageTaxInformationComponent;
loading = true;
providerId: string;
@@ -111,7 +110,7 @@ export class SetupComponent implements OnInit, OnDestroy {
try {
this.formGroup.markAllAsTouched();
- if (!this.manageTaxInformationComponent.validate() || !this.formGroup.valid) {
+ if (!this.taxInformationComponent.validate() || !this.formGroup.valid) {
return;
}
@@ -125,7 +124,7 @@ export class SetupComponent implements OnInit, OnDestroy {
request.key = key;
request.taxInfo = new ExpandedTaxInfoUpdateRequest();
- const taxInformation = this.manageTaxInformationComponent.getTaxInformation();
+ const taxInformation = this.taxInformationComponent.getTaxInformation();
request.taxInfo.country = taxInformation.country;
request.taxInfo.postalCode = taxInformation.postalCode;
@@ -147,6 +146,7 @@ export class SetupComponent implements OnInit, OnDestroy {
await this.router.navigate(["/providers", provider.id]);
} catch (e) {
+ e.message = this.i18nService.translate(e.message) || e.message;
this.validationService.showError(e);
}
};
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts
index b410bbec21e..94d9ab2d8ee 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts
@@ -1,10 +1,8 @@
import { NgModule } from "@angular/core";
import { BannerModule } from "@bitwarden/components";
+import { OnboardingModule } from "@bitwarden/web-vault/app/shared/components/onboarding/onboarding.module";
-// FIXME: remove `src` and fix import
-// eslint-disable-next-line no-restricted-imports
-import { OnboardingModule } from "../../../../../../apps/web/src/app/shared/components/onboarding/onboarding.module";
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
import { OverviewRoutingModule } from "./overview-routing.module";
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts
index 8447127fc91..78d367776d4 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts
@@ -8,10 +8,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ToastService } from "@bitwarden/components";
+import { RouterService } from "@bitwarden/web-vault/app/core";
-// FIXME: remove `src` and fix import
-// eslint-disable-next-line no-restricted-imports
-import { RouterService } from "../../../../../../../apps/web/src/app/core/router.service";
import { ProjectView } from "../../models/view/project.view";
import { ProjectService } from "../project.service";
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts
index ef29f6be7ea..a4079a6def3 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts
@@ -8,10 +8,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ToastService } from "@bitwarden/components";
+import { RouterService } from "@bitwarden/web-vault/app/core";
-// FIXME: remove `src` and fix import
-// eslint-disable-next-line no-restricted-imports
-import { RouterService } from "../../../../../../../../clients/apps/web/src/app/core/router.service";
import { ServiceAccountView } from "../../models/view/service-account.view";
import { ServiceAccountService } from "../service-account.service";
diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts
index 8d286e0a3f9..bf2e68b71cd 100644
--- a/libs/angular/src/vault/components/add-edit.component.ts
+++ b/libs/angular/src/vault/components/add-edit.component.ts
@@ -128,7 +128,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
protected policyService: PolicyService,
protected logService: LogService,
protected passwordRepromptService: PasswordRepromptService,
- private organizationService: OrganizationService,
+ protected organizationService: OrganizationService,
protected dialogService: DialogService,
protected win: Window,
protected datePipe: DatePipe,
diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts
index fc12aeff2f2..724a1507be1 100644
--- a/libs/angular/src/vault/components/view.component.ts
+++ b/libs/angular/src/vault/components/view.component.ts
@@ -65,6 +65,7 @@ export class ViewComponent implements OnDestroy, OnInit {
showPrivateKey: boolean;
canAccessPremium: boolean;
showPremiumRequiredTotp: boolean;
+ showUpgradeRequiredTotp: boolean;
totpCode: string;
totpCodeFormatted: string;
totpDash: number;
@@ -151,22 +152,25 @@ export class ViewComponent implements OnDestroy, OnInit {
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId),
);
this.showPremiumRequiredTotp =
- this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp;
+ this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationId;
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [
this.collectionId as CollectionId,
]);
+ this.showUpgradeRequiredTotp =
+ this.cipher.login.totp && this.cipher.organizationId && !this.cipher.organizationUseTotp;
+
if (this.cipher.folderId) {
this.folder = await (
await firstValueFrom(this.folderService.folderViews$(activeUserId))
).find((f) => f.id == this.cipher.folderId);
}
- if (
- this.cipher.type === CipherType.Login &&
- this.cipher.login.totp &&
- (cipher.organizationUseTotp || this.canAccessPremium)
- ) {
+ const canGenerateTotp = this.cipher.organizationId
+ ? this.cipher.organizationUseTotp
+ : this.canAccessPremium;
+
+ if (this.cipher.type === CipherType.Login && this.cipher.login.totp && canGenerateTotp) {
await this.totpUpdateCode();
const interval = this.totpService.getTimeInterval(this.cipher.login.totp);
await this.totpTick(interval);
diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html
index 0da8127369e..25172f850b7 100644
--- a/libs/importer/src/components/import.component.html
+++ b/libs/importer/src/components/import.component.html
@@ -395,6 +395,11 @@
Open the FullClient, go to the Main Menu and select Export. Start the export passwords
wizard and follow the instructions to export a CSV file.
+
+ Log in to NordPass and open Settings → Scroll down to the Import and Export section
+ and select Export items → Enter your Master Password and select Continue. → Save
+ the CSV file on your device.
+
{{ "verificationCodeTotp" | i18n }}
diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts
index 7533ac26471..281e187f78b 100644
--- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts
+++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts
@@ -2,7 +2,15 @@
// @ts-strict-ignore
import { CommonModule, DatePipe } from "@angular/common";
import { Component, inject, Input } from "@angular/core";
-import { Observable, switchMap } from "rxjs";
+import {
+ BehaviorSubject,
+ combineLatest,
+ filter,
+ map,
+ Observable,
+ shareReplay,
+ switchMap,
+} from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
@@ -12,13 +20,13 @@ import { EventType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
+ BadgeModule,
+ ColorPasswordModule,
FormFieldModule,
+ IconButtonModule,
SectionComponent,
SectionHeaderComponent,
TypographyModule,
- IconButtonModule,
- BadgeModule,
- ColorPasswordModule,
} from "@bitwarden/components";
// FIXME: remove `src` and fix import
@@ -51,13 +59,31 @@ type TotpCodeValues = {
],
})
export class LoginCredentialsViewComponent {
- @Input() cipher: CipherView;
+ @Input()
+ get cipher(): CipherView {
+ return this._cipher$.value;
+ }
+ set cipher(value: CipherView) {
+ this._cipher$.next(value);
+ }
+ private _cipher$ = new BehaviorSubject(null);
- isPremium$: Observable = this.accountService.activeAccount$.pipe(
+ private _userHasPremium$: Observable = this.accountService.activeAccount$.pipe(
switchMap((account) =>
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
),
);
+
+ allowTotpGeneration$: Observable = combineLatest([
+ this._userHasPremium$,
+ this._cipher$.pipe(filter((c) => c != null)),
+ ]).pipe(
+ map(([userHasPremium, cipher]) => {
+ // User premium status only applies to personal ciphers, organizationUseTotp applies to organization ciphers
+ return (userHasPremium && cipher.organizationId == null) || cipher.organizationUseTotp;
+ }),
+ shareReplay({ refCount: true, bufferSize: 1 }),
+ );
showPasswordCount: boolean = false;
passwordRevealed: boolean = false;
totpCodeCopyObj: TotpCodeValues;
diff --git a/package-lock.json b/package-lock.json
index 591478d3516..73e7c41ebd6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -83,7 +83,6 @@
"@angular-eslint/template-parser": "18.4.3",
"@angular/cli": "18.2.12",
"@angular/compiler-cli": "18.2.13",
- "@angular/elements": "18.2.13",
"@babel/core": "7.24.9",
"@babel/preset-env": "7.24.8",
"@compodoc/compodoc": "1.1.26",
@@ -2287,22 +2286,6 @@
"zone.js": "~0.14.10"
}
},
- "node_modules/@angular/elements": {
- "version": "18.2.13",
- "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-18.2.13.tgz",
- "integrity": "sha512-yahRkXWgFolpWMeVsTIlWYwoq4Bsz6wrfS4b+TKHIZbTCyRUlJ5zBFecpYMwgmVuQDJZp+WkUWZV2Qg7kLJR5w==",
- "dev": true,
- "dependencies": {
- "tslib": "^2.3.0"
- },
- "engines": {
- "node": "^18.19.1 || ^20.11.1 || >=22.0.0"
- },
- "peerDependencies": {
- "@angular/core": "18.2.13",
- "rxjs": "^6.5.3 || ^7.4.0"
- }
- },
"node_modules/@angular/forms": {
"version": "18.2.13",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.13.tgz",
diff --git a/package.json b/package.json
index 3dcb2c142ed..ef55a938831 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,6 @@
"@angular-eslint/template-parser": "18.4.3",
"@angular/cli": "18.2.12",
"@angular/compiler-cli": "18.2.13",
- "@angular/elements": "18.2.13",
"@babel/core": "7.24.9",
"@babel/preset-env": "7.24.8",
"@compodoc/compodoc": "1.1.26",