mirror of
https://github.com/bitwarden/browser
synced 2026-03-02 03:21:19 +00:00
Merge branch 'main' into feature/PM-27792-scaffold-layout-desktop-migration
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { OrganizationKeysRequest } from "./organization-keys.request";
|
||||
|
||||
export class OrganizationUpdateRequest {
|
||||
name: string;
|
||||
businessName: string;
|
||||
billingEmail: string;
|
||||
keys: OrganizationKeysRequest;
|
||||
export interface OrganizationUpdateRequest {
|
||||
name?: string;
|
||||
billingEmail?: string;
|
||||
keys?: OrganizationKeysRequest;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ export abstract class UserVerificationService {
|
||||
* @param userId The user id to check. If not provided, the current user is used
|
||||
* @returns True if the user has a master password
|
||||
* @deprecated Use UserDecryptionOptionsService.hasMasterPassword$ instead
|
||||
* @remark To facilitate deprecation, many call sites were removed as part of PM-26413.
|
||||
* Those remaining are blocked by currently-disallowed imports of auth/common.
|
||||
* PM-27009 has been filed to track completion of this deprecation.
|
||||
*/
|
||||
abstract hasMasterPassword(userId?: string): Promise<boolean>;
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,7 @@ import { of } from "rxjs";
|
||||
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
@@ -146,11 +143,7 @@ describe("UserVerificationService", () => {
|
||||
|
||||
describe("server verification type", () => {
|
||||
it("correctly returns master password availability", async () => {
|
||||
userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(
|
||||
of({
|
||||
hasMasterPassword: true,
|
||||
} as UserDecryptionOptions),
|
||||
);
|
||||
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(true));
|
||||
|
||||
const result = await sut.getAvailableVerificationOptions("server");
|
||||
|
||||
@@ -168,11 +161,7 @@ describe("UserVerificationService", () => {
|
||||
});
|
||||
|
||||
it("correctly returns OTP availability", async () => {
|
||||
userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(
|
||||
of({
|
||||
hasMasterPassword: false,
|
||||
} as UserDecryptionOptions),
|
||||
);
|
||||
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false));
|
||||
|
||||
const result = await sut.getAvailableVerificationOptions("server");
|
||||
|
||||
@@ -526,11 +515,7 @@ describe("UserVerificationService", () => {
|
||||
|
||||
// Helpers
|
||||
function setMasterPasswordAvailability(hasMasterPassword: boolean) {
|
||||
userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(
|
||||
of({
|
||||
hasMasterPassword: hasMasterPassword,
|
||||
} as UserDecryptionOptions),
|
||||
);
|
||||
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(hasMasterPassword));
|
||||
masterPasswordService.masterKeyHash$.mockReturnValue(
|
||||
of(hasMasterPassword ? "masterKeyHash" : null),
|
||||
);
|
||||
|
||||
@@ -258,16 +258,19 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
}
|
||||
|
||||
async hasMasterPassword(userId?: string): Promise<boolean> {
|
||||
if (userId) {
|
||||
const decryptionOptions = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
|
||||
);
|
||||
const resolvedUserId = userId ?? (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
|
||||
if (decryptionOptions?.hasMasterPassword != undefined) {
|
||||
return decryptionOptions.hasMasterPassword;
|
||||
}
|
||||
if (!resolvedUserId) {
|
||||
return false;
|
||||
}
|
||||
return await firstValueFrom(this.userDecryptionOptionsService.hasMasterPassword$);
|
||||
|
||||
// Ideally, this method would accept a UserId over string. To avoid scope creep in PM-26413, we are
|
||||
// doing the cast here. Future work should be done to make this type-safe, and should be considered
|
||||
// as part of PM-27009.
|
||||
|
||||
return await firstValueFrom(
|
||||
this.userDecryptionOptionsService.hasMasterPasswordById$(resolvedUserId as UserId),
|
||||
);
|
||||
}
|
||||
|
||||
async hasMasterPasswordAndMasterKeyHash(userId?: string): Promise<boolean> {
|
||||
|
||||
@@ -13,7 +13,7 @@ export enum FeatureFlag {
|
||||
/* Admin Console Team */
|
||||
CreateDefaultLocation = "pm-19467-create-default-location",
|
||||
AutoConfirm = "pm-19934-auto-confirm-organization-users",
|
||||
BlockClaimedDomainAccountCreation = "block-claimed-domain-account-creation",
|
||||
BlockClaimedDomainAccountCreation = "pm-28297-block-uninvited-claimed-domain-registration",
|
||||
|
||||
/* Auth */
|
||||
PM23801_PrefetchPasswordPrelogin = "pm-23801-prefetch-password-prelogin",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { firstValueFrom, map, Observable, Subject } from "rxjs";
|
||||
import { firstValueFrom, map, Observable, Subject, switchMap } from "rxjs";
|
||||
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
@@ -9,6 +9,7 @@ import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||
import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response";
|
||||
import { DevicesApiServiceAbstraction } from "../../../auth/abstractions/devices-api.service.abstraction";
|
||||
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
|
||||
@@ -87,10 +88,18 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
|
||||
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
private logService: LogService,
|
||||
private configService: ConfigService,
|
||||
private accountService: AccountService,
|
||||
) {
|
||||
this.supportsDeviceTrust$ = this.userDecryptionOptionsService.userDecryptionOptions$.pipe(
|
||||
map((options) => {
|
||||
return options?.trustedDeviceOption != null;
|
||||
this.supportsDeviceTrust$ = this.accountService.activeAccount$.pipe(
|
||||
switchMap((account) => {
|
||||
if (account == null) {
|
||||
return [false];
|
||||
}
|
||||
return this.userDecryptionOptionsService.userDecryptionOptionsById$(account.id).pipe(
|
||||
map((options) => {
|
||||
return options?.trustedDeviceOption != null;
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -914,7 +914,7 @@ describe("deviceTrustService", () => {
|
||||
platformUtilsService.supportsSecureStorage.mockReturnValue(supportsSecureStorage);
|
||||
|
||||
decryptionOptions.next({} as any);
|
||||
userDecryptionOptionsService.userDecryptionOptions$ = decryptionOptions;
|
||||
userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(decryptionOptions);
|
||||
|
||||
return new DeviceTrustService(
|
||||
keyGenerationService,
|
||||
@@ -930,6 +930,7 @@ describe("deviceTrustService", () => {
|
||||
userDecryptionOptionsService,
|
||||
logService,
|
||||
configService,
|
||||
accountService,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -25,7 +25,10 @@ export type MasterPasswordSalt = Opaque<string, "MasterPasswordSalt">;
|
||||
export type MasterKeyWrappedUserKey = Opaque<EncString, "MasterKeyWrappedUserKey">;
|
||||
|
||||
/**
|
||||
* The data required to unlock with the master password.
|
||||
* Encapsulates the data needed to unlock a vault using a master password.
|
||||
* It contains the masterKeyWrappedUserKey along with the KDF settings and salt used to derive the master key.
|
||||
* It is currently backwards compatible to master-key based unlock, but this will not be the case in the future.
|
||||
* Features relating to master-password-based unlock should use this abstraction.
|
||||
*/
|
||||
export class MasterPasswordUnlockData {
|
||||
constructor(
|
||||
@@ -66,7 +69,9 @@ export class MasterPasswordUnlockData {
|
||||
}
|
||||
|
||||
/**
|
||||
* The data required to authenticate with the master password.
|
||||
* Encapsulates the data required to authenticate using a master password.
|
||||
* It contains the masterPasswordAuthenticationHash, along with the KDF settings and salt used to derive it.
|
||||
* The encapsulated abstraction prevents authentication issues resulting from unsynchronized state.
|
||||
*/
|
||||
export type MasterPasswordAuthenticationData = {
|
||||
salt: MasterPasswordSalt;
|
||||
|
||||
@@ -53,9 +53,11 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
policyService = mock<PolicyService>();
|
||||
|
||||
userDecryptionOptionsSubject = new BehaviorSubject(null);
|
||||
userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject;
|
||||
userDecryptionOptionsService.hasMasterPassword$ = userDecryptionOptionsSubject.pipe(
|
||||
map((options) => options?.hasMasterPassword ?? false),
|
||||
userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(
|
||||
userDecryptionOptionsSubject,
|
||||
);
|
||||
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(
|
||||
userDecryptionOptionsSubject.pipe(map((options) => options?.hasMasterPassword ?? false)),
|
||||
);
|
||||
userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(
|
||||
userDecryptionOptionsSubject,
|
||||
@@ -127,6 +129,23 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
|
||||
expect(result).not.toContain(VaultTimeoutAction.Lock);
|
||||
});
|
||||
|
||||
it("should return only LogOut when userId is not provided and there is no active account", async () => {
|
||||
// Set up accountService to return null for activeAccount
|
||||
accountService.activeAccount$ = of(null);
|
||||
pinStateService.isPinSet.mockResolvedValue(false);
|
||||
biometricStateService.biometricUnlockEnabled$ = of(false);
|
||||
|
||||
// Call availableVaultTimeoutActions$ which internally calls userHasMasterPassword without a userId
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.availableVaultTimeoutActions$(),
|
||||
);
|
||||
|
||||
// Since there's no active account, userHasMasterPassword returns false,
|
||||
// meaning no master password is available, so Lock should not be available
|
||||
expect(result).toEqual([VaultTimeoutAction.LogOut]);
|
||||
expect(result).not.toContain(VaultTimeoutAction.Lock);
|
||||
});
|
||||
});
|
||||
|
||||
describe("canLock", () => {
|
||||
|
||||
@@ -290,14 +290,19 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
}
|
||||
|
||||
private async userHasMasterPassword(userId: string): Promise<boolean> {
|
||||
let resolvedUserId: UserId;
|
||||
if (userId) {
|
||||
const decryptionOptions = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
|
||||
);
|
||||
|
||||
return !!decryptionOptions?.hasMasterPassword;
|
||||
resolvedUserId = userId as UserId;
|
||||
} else {
|
||||
return await firstValueFrom(this.userDecryptionOptionsService.hasMasterPassword$);
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
if (!activeAccount) {
|
||||
return false; // No account, can't have master password
|
||||
}
|
||||
resolvedUserId = activeAccount.id;
|
||||
}
|
||||
|
||||
return await firstValueFrom(
|
||||
this.userDecryptionOptionsService.hasMasterPasswordById$(resolvedUserId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,7 +414,10 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
creationDate: this.creationDate.toISOString(),
|
||||
deletedDate: this.deletedDate?.toISOString(),
|
||||
archivedDate: this.archivedDate?.toISOString(),
|
||||
reprompt: this.reprompt,
|
||||
reprompt:
|
||||
this.reprompt === CipherRepromptType.Password
|
||||
? CipherRepromptType.Password
|
||||
: CipherRepromptType.None,
|
||||
// Initialize all cipher-type-specific properties as undefined
|
||||
login: undefined,
|
||||
identity: undefined,
|
||||
|
||||
Reference in New Issue
Block a user