1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 14:34:02 +00:00

Merge branch 'main' into PM-19741

This commit is contained in:
Miles Blackwood
2025-05-08 15:44:51 -04:00
68 changed files with 909 additions and 390 deletions

View File

@@ -29,7 +29,9 @@ export function CipherInfo({ cipher, theme }: CipherInfoProps) {
</span>
${login?.username
? html`<span class=${cipherInfoSecondaryTextStyles(theme)}>${login.username}</span>`
? html`<span title=${login.username} class=${cipherInfoSecondaryTextStyles(theme)}
>${login.username}</span
>`
: null}
</div>
`;

View File

@@ -3,7 +3,7 @@ import { html, nothing } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { themes } from "../../constants/styles";
import { spacing, themes } from "../../constants/styles";
import { Celebrate, Keyhole, Warning } from "../../illustrations";
import { NotificationConfirmationMessage } from "./message";
@@ -67,7 +67,7 @@ export const iconContainerStyles = (error?: string | boolean) => css`
}
`;
export const notificationConfirmationBodyStyles = ({ theme }: { theme: Theme }) => css`
gap: 16px;
gap: ${spacing[4]};
display: flex;
align-items: center;
justify-content: flex-start;

View File

@@ -43,7 +43,7 @@ export function NotificationConfirmationContainer({
type,
}: NotificationConfirmationContainerProps) {
const headerMessage = getHeaderMessage(i18n, type, error);
const confirmationMessage = getConfirmationMessage(i18n, itemName, type, error);
const confirmationMessage = getConfirmationMessage(i18n, type, error);
const buttonText = error ? i18n.newItem : i18n.view;
const buttonAria = chrome.i18n.getMessage("notificationViewAria", [itemName]);
@@ -109,19 +109,13 @@ export const notificationContainerStyles = (theme: Theme) => css`
}
`;
function getConfirmationMessage(
i18n: I18n,
itemName: string,
type?: NotificationType,
error?: string,
) {
const loginSaveConfirmation = chrome.i18n.getMessage("loginSaveConfirmation", [itemName]);
const loginUpdatedConfirmation = chrome.i18n.getMessage("loginUpdatedConfirmation", [itemName]);
function getConfirmationMessage(i18n: I18n, type?: NotificationType, error?: string) {
if (error) {
return i18n.saveFailureDetails;
}
return type === NotificationTypes.Add ? loginSaveConfirmation : loginUpdatedConfirmation;
return type === NotificationTypes.Add
? i18n.loginSaveConfirmation
: i18n.loginUpdatedConfirmation;
}
function getHeaderMessage(i18n: I18n, type?: NotificationType, error?: string) {

View File

@@ -3,7 +3,7 @@ import { html, nothing } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { themes, typography } from "../../constants/styles";
import { spacing, themes, typography } from "../../constants/styles";
export type NotificationConfirmationMessageProps = {
buttonAria?: string;
@@ -18,15 +18,17 @@ export type NotificationConfirmationMessageProps = {
export function NotificationConfirmationMessage({
buttonAria,
buttonText,
itemName,
message,
messageDetails,
handleClick,
theme,
}: NotificationConfirmationMessageProps) {
return html`
<div>
<div class=${containerStyles}>
${message || buttonText
? html`
<span class=${itemNameStyles(theme)} title=${itemName}> ${itemName} </span>
<span
title=${message || buttonText}
class=${notificationConfirmationMessageStyles(theme)}
@@ -57,6 +59,14 @@ export function NotificationConfirmationMessage({
`;
}
const containerStyles = css`
display: flex;
flex-wrap: wrap;
align-items: center;
gap: ${spacing[1]};
width: 100%;
`;
export const baseTextStyles = css`
flex-grow: 1;
overflow-x: hidden;
@@ -74,6 +84,15 @@ export const notificationConfirmationMessageStyles = (theme: Theme) => css`
font-weight: 400;
`;
const itemNameStyles = (theme: Theme) => css`
${baseTextStyles}
color: ${themes[theme].text.main};
font-weight: 400;
white-space: nowrap;
max-width: 300px;
`;
export const notificationConfirmationButtonTextStyles = (theme: Theme) => css`
${baseTextStyles}

View File

@@ -4,7 +4,7 @@ import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import { CloseButton } from "../buttons/close-button";
import { themes } from "../constants/styles";
import { spacing, themes } from "../constants/styles";
import { BrandIconContainer } from "../icons/brand-icon-container";
import { NotificationHeaderMessage } from "./header-message";
@@ -47,7 +47,7 @@ const notificationHeaderStyles = ({
standalone: boolean;
theme: Theme;
}) => css`
gap: 8px;
gap: ${spacing[2]};
display: flex;
align-items: center;
justify-content: flex-start;

View File

@@ -51,7 +51,7 @@ export function ButtonRow({ theme, primaryButton, selectButtons }: ButtonRowProp
}
const buttonRowStyles = css`
gap: 16px;
gap: ${spacing[4]};
display: flex;
align-items: center;
justify-content: space-between;

View File

@@ -39,7 +39,6 @@ import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
@@ -1514,9 +1513,6 @@ export default class MainBackground {
}
nextAccountStatus = await this.authService.getAuthStatus(userId);
const forcePasswordReset =
(await firstValueFrom(this.masterPasswordService.forceSetPasswordReason$(userId))) !=
ForceSetPasswordReason.None;
await this.systemService.clearPendingClipboard();
@@ -1524,8 +1520,6 @@ export default class MainBackground {
this.messagingService.send("goHome");
} else if (nextAccountStatus === AuthenticationStatus.Locked) {
this.messagingService.send("locked", { userId: userId });
} else if (forcePasswordReset) {
this.messagingService.send("update-temp-password", { userId: userId });
} else {
this.messagingService.send("unlocked", { userId: userId });
await this.refreshBadge();

View File

@@ -160,10 +160,6 @@ 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
this.router.navigate(["/remove-password"]);
} else if (msg.command == "update-temp-password") {
// 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
this.router.navigate(["/update-temp-password"]);
}
}),
takeUntil(this.destroy$),

View File

@@ -23,6 +23,12 @@
<i slot="end" class="bwi bwi-external-link" aria-hidden="true"></i>
</button>
</bit-item>
<bit-item *ngIf="!(isNudgeFeatureEnabled$ | async)">
<a bit-item-content routerLink="/more-from-bitwarden">
{{ "moreFromBitwarden" | i18n }}
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</bit-item>
<bit-item>
<button type="button" bit-item-content (click)="rate()">
{{ "rateExtension" | i18n }}

View File

@@ -5,6 +5,8 @@ import { firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { DeviceType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService, ItemModule } from "@bitwarden/components";
@@ -47,12 +49,17 @@ export class AboutPageV2Component {
private dialogService: DialogService,
private environmentService: EnvironmentService,
private platformUtilsService: PlatformUtilsService,
private configService: ConfigService,
) {}
about() {
this.dialogService.open(AboutDialogComponent);
}
protected isNudgeFeatureEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.PM8851_BrowserOnboardingNudge,
);
async launchHelp() {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "continueToHelpCenter" },

View File

@@ -66,7 +66,7 @@
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</bit-item>
<bit-item>
<bit-item *ngIf="isNudgeFeatureEnabled$ | async">
<a bit-item-content routerLink="/download-bitwarden">
<i slot="start" class="bwi bwi-mobile" aria-hidden="true"></i>
<div class="tw-flex tw-items-center tw-justify-center">
@@ -81,7 +81,7 @@
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</bit-item>
<bit-item>
<bit-item *ngIf="isNudgeFeatureEnabled$ | async">
<a bit-item-content routerLink="/more-from-bitwarden">
<i slot="start" class="bwi bwi-filter" aria-hidden="true"></i>
{{ "moreFromBitwarden" | i18n }}

View File

@@ -5,6 +5,8 @@ import { filter, firstValueFrom, Observable, shareReplay, switchMap } from "rxjs
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { UserId } from "@bitwarden/common/types/guid";
import { BadgeComponent, ItemModule } from "@bitwarden/components";
import { NudgeStatus, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault";
@@ -49,9 +51,14 @@ export class SettingsV2Component {
),
);
protected isNudgeFeatureEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.PM8851_BrowserOnboardingNudge,
);
constructor(
private readonly vaultNudgesService: VaultNudgesService,
private readonly accountService: AccountService,
private readonly configService: ConfigService,
) {}
async dismissBadge(type: VaultNudgeType) {

View File

@@ -32,6 +32,7 @@ import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request
import { ClientType } from "@bitwarden/common/enums";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -77,6 +78,7 @@ export class LoginCommand {
protected logoutCallback: () => Promise<void>,
protected kdfConfigService: KdfConfigService,
protected ssoUrlService: SsoUrlService,
protected masterPasswordService: MasterPasswordServiceAbstraction,
) {}
async run(email: string, password: string, options: OptionValues) {
@@ -361,14 +363,14 @@ export class LoginCommand {
await this.syncService.fullSync(true);
// Handle updating passwords if NOT using an API Key for authentication
if (
response.forcePasswordReset != ForceSetPasswordReason.None &&
clientId == null &&
clientSecret == null
) {
if (response.forcePasswordReset === ForceSetPasswordReason.AdminForcePasswordReset) {
if (clientId == null && clientSecret == null) {
const forceSetPasswordReason = await firstValueFrom(
this.masterPasswordService.forceSetPasswordReason$(response.userId),
);
if (forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset) {
return await this.updateTempPassword(response.userId);
} else if (response.forcePasswordReset === ForceSetPasswordReason.WeakMasterPassword) {
} else if (forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword) {
return await this.updateWeakPassword(response.userId, password);
}
}

View File

@@ -172,6 +172,7 @@ export class Program extends BaseProgram {
async () => await this.serviceContainer.logout(),
this.serviceContainer.kdfConfigService,
this.serviceContainer.ssoUrlService,
this.serviceContainer.masterPasswordService,
);
const response = await command.run(email, password, options);
this.processResponse(response, true);

View File

@@ -147,6 +147,23 @@ dependencies = [
"zeroize",
]
[[package]]
name = "ashpd"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
dependencies = [
"enumflags2",
"futures-channel",
"futures-util",
"rand 0.9.1",
"serde",
"serde_repr",
"tokio",
"url",
"zbus 5.6.0",
]
[[package]]
name = "askama"
version = "0.12.1"
@@ -266,17 +283,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-net"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
dependencies = [
"async-io",
"blocking",
"futures-lite",
]
[[package]]
name = "async-process"
version = "2.3.0"
@@ -739,7 +745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core",
"rand_core 0.6.4",
"typenum",
]
@@ -917,7 +923,7 @@ dependencies = [
"oo7",
"pin-project",
"pkcs8",
"rand",
"rand 0.8.5",
"rsa",
"russh-cryptovec",
"scopeguard",
@@ -935,7 +941,7 @@ dependencies = [
"widestring",
"windows 0.61.1",
"windows-future",
"zbus",
"zbus 4.4.0",
"zbus_polkit",
"zeroizing-alloc",
]
@@ -1020,6 +1026,17 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "doctest-file"
version = "1.0.0"
@@ -1166,6 +1183,15 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "fs-err"
version = "2.11.0"
@@ -1413,6 +1439,145 @@ dependencies = [
"windows 0.57.0",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.7.1"
@@ -1537,6 +1702,12 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "litemap"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
[[package]]
name = "lock_api"
version = "0.4.12"
@@ -1773,7 +1944,7 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
"rand",
"rand 0.8.5",
"serde",
"smallvec",
"zeroize",
@@ -1960,35 +2131,33 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "oo7"
version = "0.3.3"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24"
checksum = "6cb23d3ec3527d65a83be1c1795cb883c52cfa57147d42acc797127df56fc489"
dependencies = [
"aes",
"async-fs",
"async-io",
"async-lock",
"async-net",
"blocking",
"ashpd",
"cbc",
"cipher",
"digest",
"endi",
"futures-lite",
"futures-util",
"getrandom 0.3.1",
"hkdf",
"hmac",
"md-5",
"num",
"num-bigint-dig",
"pbkdf2",
"rand",
"rand 0.9.1",
"serde",
"sha2",
"subtle",
"zbus",
"tokio",
"zbus 5.6.0",
"zbus_macros 5.6.0",
"zeroize",
"zvariant",
"zvariant 5.5.1",
]
[[package]]
@@ -2070,7 +2239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"rand_core 0.6.4",
"subtle",
]
@@ -2099,6 +2268,12 @@ dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "petgraph"
version = "0.6.5"
@@ -2186,7 +2361,7 @@ checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"pkcs5",
"rand_core",
"rand_core 0.6.4",
"spki",
]
@@ -2298,8 +2473,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
]
[[package]]
@@ -2309,7 +2494,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
]
[[package]]
@@ -2321,6 +2516,15 @@ dependencies = [
"getrandom 0.2.15",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.1",
]
[[package]]
name = "rayon"
version = "1.10.0"
@@ -2409,7 +2613,7 @@ dependencies = [
"num-traits",
"pkcs1",
"pkcs8",
"rand_core",
"rand_core 0.6.4",
"sha2",
"signature",
"spki",
@@ -2638,7 +2842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
@@ -2742,7 +2946,7 @@ dependencies = [
"bcrypt-pbkdf",
"ed25519-dalek",
"num-bigint-dig",
"rand_core",
"rand_core 0.6.4",
"rsa",
"sha2",
"signature",
@@ -2752,6 +2956,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -2781,6 +2991,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sysinfo"
version = "0.33.1"
@@ -2900,6 +3121,16 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tokio"
version = "1.43.1"
@@ -2911,8 +3142,10 @@ dependencies = [
"libc",
"mio",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"tracing",
"windows-sys 0.52.0",
]
@@ -3202,6 +3435,30 @@ dependencies = [
"subtle",
]
[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
@@ -3769,6 +4026,18 @@ dependencies = [
"wayland-protocols-wlr",
]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "x11rb"
version = "0.13.1"
@@ -3796,6 +4065,30 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "yoke"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zbus"
version = "4.4.0"
@@ -3820,7 +4113,7 @@ dependencies = [
"hex",
"nix 0.29.0",
"ordered-stream",
"rand",
"rand 0.8.5",
"serde",
"serde_repr",
"sha1",
@@ -3829,9 +4122,37 @@ dependencies = [
"uds_windows",
"windows-sys 0.52.0",
"xdg-home",
"zbus_macros",
"zbus_names",
"zvariant",
"zbus_macros 4.4.0",
"zbus_names 3.0.0",
"zvariant 4.2.0",
]
[[package]]
name = "zbus"
version = "5.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2522b82023923eecb0b366da727ec883ace092e7887b61d3da5139f26b44da58"
dependencies = [
"async-broadcast",
"async-recursion",
"async-trait",
"enumflags2",
"event-listener",
"futures-core",
"futures-lite",
"hex",
"nix 0.29.0",
"ordered-stream",
"serde",
"serde_repr",
"tokio",
"tracing",
"uds_windows",
"windows-sys 0.59.0",
"winnow",
"zbus_macros 5.6.0",
"zbus_names 4.2.0",
"zvariant 5.5.1",
]
[[package]]
@@ -3844,7 +4165,22 @@ dependencies = [
"proc-macro2",
"quote",
"syn",
"zvariant_utils",
"zvariant_utils 2.1.0",
]
[[package]]
name = "zbus_macros"
version = "5.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d2e12843c75108c00c618c2e8ef9675b50b6ec095b36dc965f2e5aed463c15"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
"zbus_names 4.2.0",
"zvariant 5.5.1",
"zvariant_utils 3.2.0",
]
[[package]]
@@ -3855,7 +4191,19 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
dependencies = [
"serde",
"static_assertions",
"zvariant",
"zvariant 4.2.0",
]
[[package]]
name = "zbus_names"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97"
dependencies = [
"serde",
"static_assertions",
"winnow",
"zvariant 5.5.1",
]
[[package]]
@@ -3868,7 +4216,7 @@ dependencies = [
"serde",
"serde_repr",
"static_assertions",
"zbus",
"zbus 4.4.0",
]
[[package]]
@@ -3892,6 +4240,27 @@ dependencies = [
"syn",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.1"
@@ -3918,6 +4287,28 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebff5e6b81c1c7dca2d0bd333b2006da48cb37dbcae5a8da888f31fcb3c19934"
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zvariant"
version = "4.2.0"
@@ -3928,7 +4319,22 @@ dependencies = [
"enumflags2",
"serde",
"static_assertions",
"zvariant_derive",
"zvariant_derive 4.2.0",
]
[[package]]
name = "zvariant"
version = "5.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "557e89d54880377a507c94cd5452f20e35d14325faf9d2958ebeadce0966c1b2"
dependencies = [
"endi",
"enumflags2",
"serde",
"url",
"winnow",
"zvariant_derive 5.5.1",
"zvariant_utils 3.2.0",
]
[[package]]
@@ -3941,7 +4347,20 @@ dependencies = [
"proc-macro2",
"quote",
"syn",
"zvariant_utils",
"zvariant_utils 2.1.0",
]
[[package]]
name = "zvariant_derive"
version = "5.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "757779842a0d242061d24c28be589ce392e45350dfb9186dfd7a042a2e19870c"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
"zvariant_utils 3.2.0",
]
[[package]]
@@ -3954,3 +4373,17 @@ dependencies = [
"quote",
"syn",
]
[[package]]
name = "zvariant_utils"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34"
dependencies = [
"proc-macro2",
"quote",
"serde",
"static_assertions",
"syn",
"winnow",
]

View File

@@ -33,11 +33,11 @@ log = "=0.4.25"
napi = "=2.16.15"
napi-build = "=2.1.4"
napi-derive = "=2.16.13"
oo7 = "=0.3.3"
oo7 = "=0.4.3"
oslog = "=0.2.0"
pin-project = "=1.1.10"
pkcs8 = "=0.10.2"
rand = "=0.8.5"
rand = "=0.9.1"
rsa = "=0.9.8"
russh-cryptovec = "=0.7.3"
scopeguard = "=1.2.0"

View File

@@ -104,6 +104,6 @@ impl super::BiometricTrait for Biometric {
fn random_challenge() -> [u8; 16] {
let mut challenge = [0u8; 16];
rand::thread_rng().fill_bytes(&mut challenge);
rand::rng().fill_bytes(&mut challenge);
challenge
}

View File

@@ -174,7 +174,7 @@ impl super::BiometricTrait for Biometric {
fn random_challenge() -> [u8; 16] {
let mut challenge = [0u8; 16];
rand::thread_rng().fill_bytes(&mut challenge);
rand::rng().fill_bytes(&mut challenge);
challenge
}

View File

@@ -27,7 +27,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
@@ -409,17 +408,9 @@ export class AppComponent implements OnInit, OnDestroy {
const locked =
(await this.authService.getAuthStatus(message.userId)) ===
AuthenticationStatus.Locked;
const forcedPasswordReset =
(await firstValueFrom(
this.masterPasswordService.forceSetPasswordReason$(message.userId),
)) != ForceSetPasswordReason.None;
if (locked) {
this.modalService.closeAll();
await this.router.navigate(["lock"]);
} else if (forcedPasswordReset) {
// 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
this.router.navigate(["update-temp-password"]);
} else {
this.messagingService.send("unlocked");
this.loading = true;

View File

@@ -35,7 +35,6 @@
[bitMenuTriggerFor]="editCollectionMenu"
size="small"
type="button"
aria-haspopup="true"
></button>
<bit-menu #editCollectionMenu>
<ng-container *ngIf="canEditCollection">

View File

@@ -153,6 +153,3 @@
</div>
</div>
</div>
<ng-template #attachments></ng-template>
<ng-template #cipherAddEdit></ng-template>
<ng-template #collectionsModal></ng-template>

View File

@@ -456,7 +456,13 @@ export class MemberDialogComponent implements OnDestroy {
return Object.assign(p, partialPermissions);
}
handleDependentPermissions() {
async handleDependentPermissions() {
const separateCustomRolePermissions = await this.configService.getFeatureFlag(
FeatureFlag.SeparateCustomRolePermissions,
);
if (separateCustomRolePermissions) {
return;
}
// Manage Password Reset (Account Recovery) must have Manage Users enabled
if (
this.permissionsGroup.value.manageResetPassword &&

View File

@@ -374,4 +374,3 @@
</cdk-virtual-scroll-viewport>
</ng-container>
</ng-container>
<ng-template #resetPasswordTemplate></ng-template>

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, Router } from "@angular/router";
import {
@@ -90,9 +90,6 @@ class MembersTableDataSource extends PeopleTableDataSource<OrganizationUserView>
templateUrl: "members.component.html",
})
export class MembersComponent extends BaseMembersComponent<OrganizationUserView> {
@ViewChild("resetPasswordTemplate", { read: ViewContainerRef, static: true })
resetPasswordModalRef: ViewContainerRef;
userType = OrganizationUserType;
userStatusType = OrganizationUserStatusType;
memberTab = MemberDialogTab;

View File

@@ -35,5 +35,4 @@
</tr>
</ng-template>
</bit-table>
<ng-template #editTemplate></ng-template>
</bit-container>

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, lastValueFrom, map, Observable, switchMap } from "rxjs";
import { first } from "rxjs/operators";
@@ -33,9 +33,6 @@ import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.compo
templateUrl: "policies.component.html",
})
export class PoliciesComponent implements OnInit {
@ViewChild("editTemplate", { read: ViewContainerRef, static: true })
editModalRef: ViewContainerRef;
loading = true;
organizationId: string;
policies: BasePolicy[];

View File

@@ -93,7 +93,4 @@
{{ "purgeVault" | i18n }}
</button>
</app-danger-zone>
<ng-template #apiKeyTemplate></ng-template>
<ng-template #rotateApiKeyTemplate></ng-template>
</bit-container>

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import {
@@ -43,11 +43,6 @@ import { DeleteOrganizationDialogResult, openDeleteOrganizationDialog } from "./
templateUrl: "account.component.html",
})
export class AccountComponent implements OnInit, OnDestroy {
@ViewChild("apiKeyTemplate", { read: ViewContainerRef, static: true })
apiKeyModalRef: ViewContainerRef;
@ViewChild("rotateApiKeyTemplate", { read: ViewContainerRef, static: true })
rotateApiKeyModalRef: ViewContainerRef;
selfHosted = false;
canEditSubscription = true;
loading = true;

View File

@@ -51,7 +51,4 @@
{{ "deleteAccount" | i18n }}
</button>
</app-danger-zone>
<ng-template #viewUserApiKeyTemplate></ng-template>
<ng-template #rotateUserApiKeyTemplate></ng-template>
</bit-container>

View File

@@ -272,7 +272,3 @@
</ng-container>
</bit-section>
</bit-container>
<ng-template #addEdit></ng-template>
<ng-template #takeoverTemplate></ng-template>
<ng-template #confirmTemplate></ng-template>

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { lastValueFrom, Observable, firstValueFrom, switchMap } from "rxjs";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
@@ -44,12 +44,6 @@ import {
templateUrl: "emergency-access.component.html",
})
export class EmergencyAccessComponent implements OnInit {
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild("takeoverTemplate", { read: ViewContainerRef, static: true })
takeoverModalRef: ViewContainerRef;
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
confirmModalRef: ViewContainerRef;
loaded = false;
canAccessPremium$: Observable<boolean>;
trustedContacts: GranteeEmergencyAccess[];

View File

@@ -51,5 +51,3 @@
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
<ng-template #attachments></ng-template>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
@@ -17,7 +17,6 @@ import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component"
providers: [{ provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }],
})
export class EmergencyAccessViewComponent implements OnInit {
@ViewChild("attachments", { read: ViewContainerRef, static: true })
id: EmergencyAccessId | null = null;
ciphers: CipherView[] = [];
loaded = false;

View File

@@ -84,8 +84,3 @@
</bit-item>
</bit-item-group>
</bit-container>
<ng-template #duoTemplate></ng-template>
<ng-template #emailTemplate></ng-template>
<ng-template #yubikeyTemplate></ng-template>
<ng-template #webAuthnTemplate></ng-template>

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { Component, OnDestroy, OnInit } from "@angular/core";
import {
first,
firstValueFrom,
@@ -12,7 +12,6 @@ import {
switchMap,
} from "rxjs";
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
@@ -52,9 +51,6 @@ import { TwoFactorVerifyComponent } from "./two-factor-verify.component";
imports: [ItemModule, LooseComponentsModule, SharedModule],
})
export class TwoFactorSetupComponent implements OnInit, OnDestroy {
@ViewChild("yubikeyTemplate", { read: ViewContainerRef, static: true })
yubikeyModalRef: ViewContainerRef;
organizationId: string;
organization: Organization;
providers: any[] = [];
@@ -62,7 +58,6 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
recoveryCodeWarningMessage: string;
showPolicyWarning = false;
loading = true;
modal: ModalRef;
formPromise: Promise<any>;
tabbedHeader = true;
@@ -283,9 +278,6 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
}
protected updateStatus(enabled: boolean, type: TwoFactorProviderType) {
if (!enabled && this.modal != null) {
this.modal.close();
}
this.providers.forEach((p) => {
if (p.type === type && enabled !== undefined) {
p.enabled = enabled;

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, ViewChild, ViewContainerRef, OnDestroy } from "@angular/core";
import { Directive, OnDestroy } from "@angular/core";
import {
BehaviorSubject,
lastValueFrom,
@@ -37,8 +37,6 @@ import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/se
@Directive()
export class CipherReportComponent implements OnDestroy {
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
cipherAddEditModalRef: ViewContainerRef;
isAdminConsoleActive = false;
loading = false;

View File

@@ -96,5 +96,4 @@
</bit-table-scroll>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -108,5 +108,4 @@
>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -98,5 +98,4 @@
</bit-table-scroll>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -96,5 +96,4 @@
</bit-table>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -100,5 +100,4 @@
</bit-table-scroll>
</ng-container>
</div>
<ng-template #cipherAddEdit></ng-template>
</bit-container>

View File

@@ -206,4 +206,3 @@
</div>
</div>
</div>
<ng-template #sendAddEdit></ng-template>

View File

@@ -13,7 +13,7 @@
<button bitButton buttonType="primary" bitFormButton type="submit">
<span>{{ "save" | i18n }}</span>
</button>
<button bitButton buttonType="secondary" bitDialogClose type="button" data-dismiss="modal">
<button bitButton buttonType="secondary" bitDialogClose type="button">
{{ "cancel" | i18n }}
</button>
<div class="tw-m-0 tw-ml-auto">

View File

@@ -84,10 +84,3 @@
</div>
</div>
</div>
<ng-template #attachments></ng-template>
<ng-template #folderAddEdit></ng-template>
<ng-template #cipherAddEdit></ng-template>
<ng-template #share></ng-template>
<ng-template #collectionsModal></ng-template>
<ng-template #updateKeyTemplate></ng-template>

View File

@@ -57,9 +57,6 @@
bitMenuItem
buttonType="secondary"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>

View File

@@ -6,7 +6,6 @@ import { firstValueFrom } from "rxjs";
import { LoginSuccessHandlerService } from "@bitwarden/auth/common";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -21,7 +20,6 @@ export class BaseLoginViaWebAuthnComponent implements OnInit {
protected currentState: State = "assert";
protected successRoute = "/vault";
protected forcePasswordResetRoute = "/update-temp-password";
constructor(
private webAuthnLoginService: WebAuthnLoginServiceAbstraction,
@@ -73,11 +71,6 @@ export class BaseLoginViaWebAuthnComponent implements OnInit {
await this.loginSuccessHandlerService.run(authResult.userId);
}
if (authResult.forcePasswordReset == ForceSetPasswordReason.AdminForcePasswordReset) {
await this.router.navigate([this.forcePasswordResetRoute]);
return;
}
await this.router.navigate([this.successRoute]);
} catch (error) {
if (error instanceof ErrorResponse) {

View File

@@ -19,7 +19,6 @@ import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type"
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view";
@@ -820,8 +819,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
private async handlePostLoginNavigation(loginResponse: AuthResult) {
if (loginResponse.requiresTwoFactor) {
await this.router.navigate(["2fa"]);
} else if (loginResponse.forcePasswordReset != ForceSetPasswordReason.None) {
await this.router.navigate(["update-temp-password"]);
} else {
await this.handleSuccessfulLoginNavigation(loginResponse.userId);
}

View File

@@ -17,7 +17,6 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { ClientType, HttpStatusCode } from "@bitwarden/common/enums";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@@ -307,10 +306,7 @@ export class LoginComponent implements OnInit, OnDestroy {
await this.loginSuccessHandlerService.run(authResult.userId);
// Determine where to send the user next
if (authResult.forcePasswordReset != ForceSetPasswordReason.None) {
await this.router.navigate(["update-temp-password"]);
return;
}
// The AuthGuard will handle routing to update-temp-password based on state
// TODO: PM-18269 - evaluate if we can combine this with the
// password evaluation done in the password login strategy.

View File

@@ -136,11 +136,6 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy {
return;
}
if (authResult.forcePasswordReset) {
await this.router.navigate(["/update-temp-password"]);
return;
}
this.loginSuccessHandlerService.run(authResult.userId);
// If verification succeeds, navigate to vault

View File

@@ -541,14 +541,6 @@ export class SsoComponent implements OnInit {
});
}
private async handleForcePasswordReset(orgIdentifier: string) {
await this.router.navigate(["update-temp-password"], {
queryParams: {
identifier: orgIdentifier,
},
});
}
private async handleSuccessfulLogin() {
await this.router.navigate(["lock"]);
}

View File

@@ -575,25 +575,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
});
}
/**
* Determines if a user needs to reset their password based on certain conditions.
* Users can be forced to reset their password via an admin or org policy disallowing weak passwords.
* Note: this is different from the SSO component login flow as a user can
* login with MP and then have to pass 2FA to finish login and we can actually
* evaluate if they have a weak password at that time.
*
* @param {AuthResult} authResult - The authentication result.
* @returns {boolean} Returns true if a password reset is required, false otherwise.
*/
private isForcePasswordResetRequired(authResult: AuthResult): boolean {
const forceResetReasons = [
ForceSetPasswordReason.AdminForcePasswordReset,
ForceSetPasswordReason.WeakMasterPassword,
];
return forceResetReasons.includes(authResult.forcePasswordReset);
}
showContinueButton() {
return (
this.selectedProviderType != null &&

View File

@@ -296,13 +296,9 @@ describe("LoginStrategy", () => {
const expected = new AuthResult();
expected.userId = userId;
expected.forcePasswordReset = ForceSetPasswordReason.AdminForcePasswordReset;
expected.resetMasterPassword = true;
expected.twoFactorProviders = {} as Partial<
Record<TwoFactorProviderType, Record<string, string>>
>;
expected.captchaSiteKey = "";
expected.twoFactorProviders = null;
expected.captchaSiteKey = "";
expect(result).toEqual(expected);
});
@@ -316,13 +312,9 @@ describe("LoginStrategy", () => {
const expected = new AuthResult();
expected.userId = userId;
expected.forcePasswordReset = ForceSetPasswordReason.AdminForcePasswordReset;
expected.resetMasterPassword = false;
expected.twoFactorProviders = {} as Partial<
Record<TwoFactorProviderType, Record<string, string>>
>;
expected.captchaSiteKey = "";
expected.twoFactorProviders = null;
expected.captchaSiteKey = "";
expect(result).toEqual(expected);
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(

View File

@@ -277,17 +277,7 @@ export abstract class LoginStrategy {
result.resetMasterPassword = response.resetMasterPassword;
// Convert boolean to enum and set the state for the master password service to
// so we know when we reach the auth guard that we need to guide them properly to admin
// password reset.
if (response.forcePasswordReset) {
result.forcePasswordReset = ForceSetPasswordReason.AdminForcePasswordReset;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.AdminForcePasswordReset,
userId,
);
}
await this.processForceSetPasswordReason(response.forcePasswordReset, userId);
if (response.twoFactorToken != null) {
// note: we can read email from access token b/c it was saved in saveAccountInformation
@@ -318,6 +308,30 @@ export abstract class LoginStrategy {
return false;
}
/**
* Checks if adminForcePasswordReset is true and sets the ForceSetPasswordReason.AdminForcePasswordReset flag in the master password service.
* @param adminForcePasswordReset - The admin force password reset flag
* @param userId - The user ID
* @returns a promise that resolves to a boolean indicating whether the admin force password reset flag was set
*/
async processForceSetPasswordReason(
adminForcePasswordReset: boolean,
userId: UserId,
): Promise<boolean> {
if (!adminForcePasswordReset) {
return false;
}
// set the flag in the master password service so we know when we reach the auth guard
// that we need to guide them properly to admin password reset.
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.AdminForcePasswordReset,
userId,
);
return true;
}
protected async createKeyPairForOldAccount(userId: UserId) {
try {
const userKey = await this.keyService.getUserKeyWithLegacySupport(userId);

View File

@@ -211,20 +211,18 @@ describe("PasswordLoginStrategy", () => {
it("does not force the user to update their master password when there are no requirements", async () => {
apiService.postIdentityToken.mockResolvedValueOnce(identityTokenResponseFactory());
const result = await passwordLoginStrategy.logIn(credentials);
await passwordLoginStrategy.logIn(credentials);
expect(policyService.evaluateMasterPassword).not.toHaveBeenCalled();
expect(result.forcePasswordReset).toEqual(ForceSetPasswordReason.None);
});
it("does not force the user to update their master password when it meets requirements", async () => {
passwordStrengthService.getPasswordStrength.mockReturnValue({ score: 5 } as any);
policyService.evaluateMasterPassword.mockReturnValue(true);
const result = await passwordLoginStrategy.logIn(credentials);
await passwordLoginStrategy.logIn(credentials);
expect(policyService.evaluateMasterPassword).toHaveBeenCalled();
expect(result.forcePasswordReset).toEqual(ForceSetPasswordReason.None);
});
it("forces the user to update their master password on successful login when it does not meet master password policy requirements", async () => {
@@ -232,14 +230,13 @@ describe("PasswordLoginStrategy", () => {
policyService.evaluateMasterPassword.mockReturnValue(false);
tokenService.decodeAccessToken.mockResolvedValue({ sub: userId });
const result = await passwordLoginStrategy.logIn(credentials);
await passwordLoginStrategy.logIn(credentials);
expect(policyService.evaluateMasterPassword).toHaveBeenCalled();
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
ForceSetPasswordReason.WeakMasterPassword,
userId,
);
expect(result.forcePasswordReset).toEqual(ForceSetPasswordReason.WeakMasterPassword);
});
it("forces the user to update their master password on successful 2FA login when it does not meet master password policy requirements", async () => {
@@ -257,13 +254,13 @@ describe("PasswordLoginStrategy", () => {
// First login request fails requiring 2FA
apiService.postIdentityToken.mockResolvedValueOnce(token2FAResponse);
const firstResult = await passwordLoginStrategy.logIn(credentials);
await passwordLoginStrategy.logIn(credentials);
// Second login request succeeds
apiService.postIdentityToken.mockResolvedValueOnce(
identityTokenResponseFactory(masterPasswordPolicy),
);
const secondResult = await passwordLoginStrategy.logInTwoFactor(
await passwordLoginStrategy.logInTwoFactor(
{
provider: TwoFactorProviderType.Authenticator,
token: "123456",
@@ -272,15 +269,11 @@ describe("PasswordLoginStrategy", () => {
"",
);
// First login attempt should not save the force password reset options
expect(firstResult.forcePasswordReset).toEqual(ForceSetPasswordReason.None);
// Second login attempt should save the force password reset options and return in result
// Second login attempt should save the force password reset options
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
ForceSetPasswordReason.WeakMasterPassword,
userId,
);
expect(secondResult.forcePasswordReset).toEqual(ForceSetPasswordReason.WeakMasterPassword);
});
it("handles new device verification login with OTP", async () => {
@@ -298,7 +291,6 @@ describe("PasswordLoginStrategy", () => {
newDeviceOtp: deviceVerificationOtp,
}),
);
expect(result.forcePasswordReset).toBe(ForceSetPasswordReason.None);
expect(result.resetMasterPassword).toBe(false);
expect(result.userId).toBe(userId);
});

View File

@@ -109,35 +109,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
return authResult;
}
const masterPasswordPolicyOptions =
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse);
await this.evaluateMasterPasswordIfRequired(identityResponse, credentials, authResult);
// The identity result can contain master password policies for the user's organizations
if (masterPasswordPolicyOptions?.enforceOnLogin) {
// If there is a policy active, evaluate the supplied password before its no longer in memory
const meetsRequirements = this.evaluateMasterPassword(
credentials,
masterPasswordPolicyOptions,
);
if (meetsRequirements) {
return authResult;
}
if (identityResponse instanceof IdentityTwoFactorResponse) {
// Save the flag to this strategy for use in 2fa login as the master password is about to pass out of scope
this.cache.next({
...this.cache.value,
forcePasswordResetReason: ForceSetPasswordReason.WeakMasterPassword,
});
} else {
// Authentication was successful, save the force update password options with the state service
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.WeakMasterPassword,
authResult.userId, // userId is only available on successful login
);
authResult.forcePasswordReset = ForceSetPasswordReason.WeakMasterPassword;
}
}
return authResult;
}
@@ -151,20 +124,6 @@ export class PasswordLoginStrategy extends LoginStrategy {
const result = await super.logInTwoFactor(twoFactor);
// 2FA was successful, save the force update password options with the state service if defined
const forcePasswordResetReason = this.cache.value.forcePasswordResetReason;
if (
!result.requiresTwoFactor &&
!result.requiresCaptcha &&
forcePasswordResetReason != ForceSetPasswordReason.None
) {
await this.masterPasswordService.setForceSetPasswordReason(
forcePasswordResetReason,
result.userId,
);
result.forcePasswordReset = forcePasswordResetReason;
}
return result;
}
@@ -208,13 +167,58 @@ export class PasswordLoginStrategy extends LoginStrategy {
return !response.key;
}
private getMasterPasswordPolicyOptionsFromResponse(
response:
private async evaluateMasterPasswordIfRequired(
identityResponse:
| IdentityTokenResponse
| IdentityTwoFactorResponse
| IdentityDeviceVerificationResponse,
): MasterPasswordPolicyOptions {
if (response == null || response instanceof IdentityDeviceVerificationResponse) {
credentials: PasswordLoginCredentials,
authResult: AuthResult,
): Promise<void> {
// TODO: PM-21084 - investigate if we should be sending down masterPasswordPolicy on the IdentityDeviceVerificationResponse like we do for the IdentityTwoFactorResponse
// If the response is a device verification response, we don't need to evaluate the password
if (identityResponse instanceof IdentityDeviceVerificationResponse) {
return;
}
// The identity result can contain master password policies for the user's organizations
const masterPasswordPolicyOptions =
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse);
if (!masterPasswordPolicyOptions?.enforceOnLogin) {
return;
}
// If there is a policy active, evaluate the supplied password before its no longer in memory
const meetsRequirements = this.evaluateMasterPassword(credentials, masterPasswordPolicyOptions);
if (meetsRequirements) {
return;
}
if (identityResponse instanceof IdentityTwoFactorResponse) {
// Save the flag to this strategy for use in 2fa as the master password is about to pass out of scope
this.cache.next({
...this.cache.value,
forcePasswordResetReason: ForceSetPasswordReason.WeakMasterPassword,
});
}
// Authentication was successful, save the force update password options with the state service
// if there isn't already a reason set (this would only be AdminForcePasswordReset as that can be set server side
// and would have already been processed in the base login strategy processForceSetPasswordReason method)
// Note: masterPasswordService.setForceSetPasswordReason will not allow overwriting
// AdminForcePasswordReset with any other reason except for None. This is because
// an AdminForcePasswordReset will always force a user to update their password to a password that meets the policy.
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.WeakMasterPassword,
authResult.userId, // userId is only available on successful login
);
}
private getMasterPasswordPolicyOptionsFromResponse(
response: IdentityTokenResponse | IdentityTwoFactorResponse,
): MasterPasswordPolicyOptions | null {
if (response == null) {
return null;
}
return MasterPasswordPolicyOptions.fromResponse(response.masterPasswordPolicy);
@@ -246,4 +250,35 @@ export class PasswordLoginStrategy extends LoginStrategy {
const [authResult] = await this.startLogIn();
return authResult;
}
/**
* Override to handle the WeakMasterPassword reason if no other reason is set.
* @param authResult - The authentication result
* @param userId - The user ID
*/
override async processForceSetPasswordReason(
adminForcePasswordReset: boolean,
userId: UserId,
): Promise<boolean> {
// handle any existing reasons
const adminForcePasswordResetFlagSet = await super.processForceSetPasswordReason(
adminForcePasswordReset,
userId,
);
// If we are already processing an admin force password reset, don't process other reasons
if (adminForcePasswordResetFlagSet) {
return false;
}
// If we have a cached weak password reason from login/logInTwoFactor apply it
const cachedReason = this.cache.value.forcePasswordResetReason;
if (cachedReason !== ForceSetPasswordReason.None) {
await this.masterPasswordService.setForceSetPasswordReason(cachedReason, userId);
return true;
}
// If none of the conditions are met, return false
return false;
}
}

View File

@@ -1,5 +1,5 @@
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { BehaviorSubject, of } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
@@ -37,10 +37,11 @@ import {
AuthRequestServiceAbstraction,
InternalUserDecryptionOptionsServiceAbstraction,
} from "../abstractions";
import { UserDecryptionOptions } from "../models";
import { SsoLoginCredentials } from "../models/domain/login-credentials";
import { identityTokenResponseFactory } from "./login.strategy.spec";
import { SsoLoginStrategy } from "./sso-login.strategy";
import { SsoLoginStrategy, SsoLoginStrategyData } from "./sso-login.strategy";
describe("SsoLoginStrategy", () => {
let accountService: FakeAccountService;
@@ -123,8 +124,11 @@ describe("SsoLoginStrategy", () => {
mockVaultTimeoutBSub.asObservable(),
);
const userDecryptionOptions = new UserDecryptionOptions();
userDecryptionOptionsService.userDecryptionOptions$ = of(userDecryptionOptions);
ssoLoginStrategy = new SsoLoginStrategy(
null,
{} as SsoLoginStrategyData,
keyConnectorService,
deviceTrustService,
authRequestService,

View File

@@ -4,6 +4,7 @@ import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs";
import { Jsonify } from "type-fest";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { SsoTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/sso-token.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
@@ -355,4 +356,75 @@ export class SsoLoginStrategy extends LoginStrategy {
sso: this.cache.value,
};
}
/**
* Override to handle SSO-specific ForceSetPasswordReason flags,including TdeOffboarding,
* TdeUserWithoutPasswordHasPasswordResetPermission, and SsoNewJitProvisionedUser cases.
* @param authResult - The authentication result
* @param userId - The user ID
*/
override async processForceSetPasswordReason(
adminForcePasswordReset: boolean,
userId: UserId,
): Promise<boolean> {
// handle any existing reasons
const adminForcePasswordResetFlagSet = await super.processForceSetPasswordReason(
adminForcePasswordReset,
userId,
);
// If we are already processing an admin force password reset, don't process other reasons
if (adminForcePasswordResetFlagSet) {
return false;
}
// Check for TDE-related conditions
const userDecryptionOptions = await firstValueFrom(
this.userDecryptionOptionsService.userDecryptionOptions$,
);
if (!userDecryptionOptions) {
return false;
}
// Check for TDE offboarding - user is being offboarded from TDE and needs to set a password
if (userDecryptionOptions.trustedDeviceOption?.isTdeOffboarding) {
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.TdeOffboarding,
userId,
);
return true;
}
// Check if user has permission to set password but hasn't yet
if (
!userDecryptionOptions.hasMasterPassword &&
userDecryptionOptions.trustedDeviceOption?.hasManageResetPasswordPermission
) {
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
userId,
);
return true;
}
// Check for new SSO JIT provisioned user
// If a user logs in via SSO but has no master password and no alternative encryption methods
// Then they must be a newly provisioned user who needs to set up their encryption
if (
!userDecryptionOptions.hasMasterPassword &&
!userDecryptionOptions.keyConnectorOption?.keyConnectorUrl &&
!userDecryptionOptions.trustedDeviceOption
) {
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.SsoNewJitProvisionedUser,
userId,
);
return true;
}
// If none of the conditions are met, return false
return false;
}
}

View File

@@ -209,7 +209,6 @@ describe("WebAuthnLoginStrategy", () => {
expect(authResult).toBeInstanceOf(AuthResult);
expect(authResult).toMatchObject({
captchaSiteKey: "",
forcePasswordReset: 0,
resetMasterPassword: false,
twoFactorProviders: null,
requiresTwoFactor: false,

View File

@@ -4,8 +4,6 @@ import { Utils } from "../../../platform/misc/utils";
import { UserId } from "../../../types/guid";
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
import { ForceSetPasswordReason } from "./force-set-password-reason";
export class AuthResult {
userId: UserId;
captchaSiteKey = "";
@@ -17,7 +15,6 @@ export class AuthResult {
* */
resetMasterPassword = false;
forcePasswordReset: ForceSetPasswordReason = ForceSetPasswordReason.None;
twoFactorProviders: Partial<Record<TwoFactorProviderType, Record<string, string>>> = null;
ssoEmail2FaSessionToken?: string;
email: string;

View File

@@ -31,4 +31,9 @@ export enum ForceSetPasswordReason {
* Occurs when TDE is disabled and master password has to be set.
*/
TdeOffboarding,
/**
* Occurs when a new SSO user is JIT provisioned and needs to set their master password.
*/
SsoNewJitProvisionedUser,
}

View File

@@ -13,6 +13,7 @@ export enum FeatureFlag {
LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission",
SsoExternalIdVisibility = "pm-18630-sso-external-id-visibility",
AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions",
/* Auth */
PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
@@ -83,6 +84,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.LimitItemDeletion]: FALSE,
[FeatureFlag.SsoExternalIdVisibility]: FALSE,
[FeatureFlag.AccountDeprovisioningBanner]: FALSE,
[FeatureFlag.SeparateCustomRolePermissions]: FALSE,
/* Autofill */
[FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE,

View File

@@ -0,0 +1,104 @@
import { mock, MockProxy } from "jest-mock-extended";
import { of } from "rxjs";
import * as rxjs from "rxjs";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { StateService } from "../../../platform/abstractions/state.service";
import { StateProvider } from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { EncryptService } from "../../crypto/abstractions/encrypt.service";
import { MasterPasswordService } from "./master-password.service";
describe("MasterPasswordService", () => {
let sut: MasterPasswordService;
let stateProvider: MockProxy<StateProvider>;
let stateService: MockProxy<StateService>;
let keyGenerationService: MockProxy<KeyGenerationService>;
let encryptService: MockProxy<EncryptService>;
let logService: MockProxy<LogService>;
const userId = "user-id" as UserId;
const mockUserState = {
state$: of(null),
update: jest.fn().mockResolvedValue(null),
};
beforeEach(() => {
stateProvider = mock<StateProvider>();
stateService = mock<StateService>();
keyGenerationService = mock<KeyGenerationService>();
encryptService = mock<EncryptService>();
logService = mock<LogService>();
stateProvider.getUser.mockReturnValue(mockUserState as any);
mockUserState.update.mockReset();
sut = new MasterPasswordService(
stateProvider,
stateService,
keyGenerationService,
encryptService,
logService,
);
});
describe("setForceSetPasswordReason", () => {
it("calls stateProvider with the provided reason and user ID", async () => {
const reason = ForceSetPasswordReason.WeakMasterPassword;
await sut.setForceSetPasswordReason(reason, userId);
expect(stateProvider.getUser).toHaveBeenCalled();
expect(mockUserState.update).toHaveBeenCalled();
// Call the update function to verify it returns the correct reason
const updateFn = mockUserState.update.mock.calls[0][0];
expect(updateFn(null)).toBe(reason);
});
it("throws an error if reason is null", async () => {
await expect(
sut.setForceSetPasswordReason(null as unknown as ForceSetPasswordReason, userId),
).rejects.toThrow("Reason is required.");
});
it("throws an error if user ID is null", async () => {
await expect(
sut.setForceSetPasswordReason(ForceSetPasswordReason.None, null as unknown as UserId),
).rejects.toThrow("User ID is required.");
});
it("does not overwrite AdminForcePasswordReset with other reasons except None", async () => {
jest
.spyOn(sut, "forceSetPasswordReason$")
.mockReturnValue(of(ForceSetPasswordReason.AdminForcePasswordReset));
jest
.spyOn(rxjs, "firstValueFrom")
.mockResolvedValue(ForceSetPasswordReason.AdminForcePasswordReset);
await sut.setForceSetPasswordReason(ForceSetPasswordReason.WeakMasterPassword, userId);
expect(mockUserState.update).not.toHaveBeenCalled();
});
it("allows overwriting AdminForcePasswordReset with None", async () => {
jest
.spyOn(sut, "forceSetPasswordReason$")
.mockReturnValue(of(ForceSetPasswordReason.AdminForcePasswordReset));
jest
.spyOn(rxjs, "firstValueFrom")
.mockResolvedValue(ForceSetPasswordReason.AdminForcePasswordReset);
await sut.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
expect(mockUserState.update).toHaveBeenCalled();
});
});
});

View File

@@ -148,6 +148,17 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
if (userId == null) {
throw new Error("User ID is required.");
}
// Don't overwrite AdminForcePasswordReset with any other reasons other than None
// as we must allow a reset when the user has completed admin account recovery
const currentReason = await firstValueFrom(this.forceSetPasswordReason$(userId));
if (
currentReason === ForceSetPasswordReason.AdminForcePasswordReset &&
reason !== ForceSetPasswordReason.None
) {
return;
}
await this.stateProvider.getUser(userId, FORCE_SET_PASSWORD_REASON).update((_) => reason);
}

View File

@@ -124,8 +124,12 @@ export class CipherService implements CipherServiceAbstraction {
* decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete.
*/
cipherViews$ = perUserCache$((userId: UserId): Observable<CipherView[] | null> => {
return combineLatest([this.encryptedCiphersState(userId).state$, this.localData$(userId)]).pipe(
filter(([ciphers]) => ciphers != null), // Skip if ciphers haven't been loaded yor synced yet
return combineLatest([
this.encryptedCiphersState(userId).state$,
this.localData$(userId),
this.keyService.cipherDecryptionKeys$(userId, true),
]).pipe(
filter(([ciphers, _, keys]) => ciphers != null && keys != null), // Skip if ciphers haven't been loaded yor synced yet
switchMap(() => this.getAllDecrypted(userId)),
);
}, this.clearCipherViewsForUser$);

View File

@@ -36,7 +36,6 @@
bitIconButton="bwi-ellipsis-h"
[bitMenuTriggerFor]="overflowMenu"
size="small"
aria-haspopup
></button>
<bit-menu #overflowMenu>
@for (breadcrumb of overflow; track breadcrumb) {

View File

@@ -121,8 +121,6 @@ export class LockComponent implements OnInit, OnDestroy {
showPassword = false;
private enforcedMasterPasswordOptions?: MasterPasswordPolicyOptions = undefined;
forcePasswordResetRoute = "update-temp-password";
formGroup: FormGroup | null = null;
// Browser extension properties:
@@ -605,8 +603,6 @@ export class LockComponent implements OnInit, OnDestroy {
ForceSetPasswordReason.WeakMasterPassword,
userId,
);
await this.router.navigate([this.forcePasswordResetRoute]);
return;
}
} catch (e) {
// Do not prevent unlock if there is an error evaluating policies

98
package-lock.json generated
View File

@@ -87,7 +87,7 @@
"@ngtools/webpack": "18.2.12",
"@storybook/addon-a11y": "8.5.2",
"@storybook/addon-actions": "8.5.2",
"@storybook/addon-designs": "8.0.4",
"@storybook/addon-designs": "8.2.1",
"@storybook/addon-essentials": "8.5.2",
"@storybook/addon-interactions": "8.5.2",
"@storybook/addon-links": "8.5.2",
@@ -162,7 +162,7 @@
"prettier": "3.5.3",
"prettier-plugin-tailwindcss": "0.6.11",
"process": "0.11.10",
"remark-gfm": "4.0.0",
"remark-gfm": "4.0.1",
"rimraf": "6.0.1",
"sass": "1.83.4",
"sass-loader": "16.0.4",
@@ -179,7 +179,7 @@
"url": "0.11.4",
"util": "0.12.5",
"wait-on": "8.0.3",
"webpack": "5.97.1",
"webpack": "5.99.7",
"webpack-cli": "6.0.1",
"webpack-dev-server": "5.2.0",
"webpack-node-externals": "3.0.0"
@@ -10163,9 +10163,9 @@
}
},
"node_modules/@storybook/addon-designs": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@storybook/addon-designs/-/addon-designs-8.0.4.tgz",
"integrity": "sha512-BrEWks1BRnZis2e8OoE1LhFS+x2d094Tzpbb3jQBve2IfDv/X006RSuy1WyplNxskdYdBESCH45MlRn4lhP5ew==",
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@storybook/addon-designs/-/addon-designs-8.2.1.tgz",
"integrity": "sha512-orwihs1D5alhh4Qu3BSJKbSgQOdSagvRX/25m5fYZQAaqVErBY0lRR4vCAU/G/STkcdv+MHwIQ5U+0kX5Tm2+w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10175,8 +10175,8 @@
"@storybook/blocks": "^8.0.0 || ^8.1.0-0 || ^8.2.0-0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0",
"@storybook/components": "^8.0.0 || ^8.1.0-0 || ^8.2.0-0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0",
"@storybook/theming": "^8.0.0 || ^8.1.0-0 || ^8.2.0-0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta"
},
"peerDependenciesMeta": {
"@storybook/blocks": {
@@ -31490,9 +31490,9 @@
}
},
"node_modules/remark-gfm": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz",
"integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
"integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -32259,9 +32259,9 @@
}
},
"node_modules/schema-utils": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -36640,14 +36640,15 @@
}
},
"node_modules/webpack": {
"version": "5.97.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
"version": "5.99.7",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz",
"integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
@@ -36664,9 +36665,9 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^3.2.0",
"schema-utils": "^4.3.2",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.10",
"terser-webpack-plugin": "^5.3.11",
"watchpack": "^2.4.1",
"webpack-sources": "^3.2.3"
},
@@ -37112,39 +37113,12 @@
"license": "MIT"
},
"node_modules/webpack/node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/webpack/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/webpack/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/webpack/node_modules/browserslist": {
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
@@ -37209,32 +37183,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/webpack/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/websocket-driver": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",

View File

@@ -49,7 +49,7 @@
"@ngtools/webpack": "18.2.12",
"@storybook/addon-a11y": "8.5.2",
"@storybook/addon-actions": "8.5.2",
"@storybook/addon-designs": "8.0.4",
"@storybook/addon-designs": "8.2.1",
"@storybook/addon-essentials": "8.5.2",
"@storybook/addon-interactions": "8.5.2",
"@storybook/addon-links": "8.5.2",
@@ -124,7 +124,7 @@
"prettier": "3.5.3",
"prettier-plugin-tailwindcss": "0.6.11",
"process": "0.11.10",
"remark-gfm": "4.0.0",
"remark-gfm": "4.0.1",
"rimraf": "6.0.1",
"sass": "1.83.4",
"sass-loader": "16.0.4",
@@ -141,7 +141,7 @@
"url": "0.11.4",
"util": "0.12.5",
"wait-on": "8.0.3",
"webpack": "5.97.1",
"webpack": "5.99.7",
"webpack-cli": "6.0.1",
"webpack-dev-server": "5.2.0",
"webpack-node-externals": "3.0.0"