mirror of
https://github.com/bitwarden/browser
synced 2026-02-21 11:54:02 +00:00
Merge remote-tracking branch 'origin' into auth/pm-18720/change-password-component-non-dialog-v3
This commit is contained in:
@@ -208,7 +208,7 @@ export abstract class ApiService {
|
||||
deleteManyCiphersAdmin: (request: CipherBulkDeleteRequest) => Promise<any>;
|
||||
putMoveCiphers: (request: CipherBulkMoveRequest) => Promise<any>;
|
||||
putShareCipher: (id: string, request: CipherShareRequest) => Promise<CipherResponse>;
|
||||
putShareCiphers: (request: CipherBulkShareRequest) => Promise<CipherResponse[]>;
|
||||
putShareCiphers: (request: CipherBulkShareRequest) => Promise<ListResponse<CipherResponse>>;
|
||||
putCipherCollections: (
|
||||
id: string,
|
||||
request: CipherCollectionsRequest,
|
||||
|
||||
@@ -16,4 +16,5 @@ export enum PolicyType {
|
||||
AutomaticAppLogIn = 12, // Enables automatic log in of apps from configured identity provider
|
||||
FreeFamiliesSponsorshipPolicy = 13, // Disables free families plan for organization
|
||||
RemoveUnlockWithPin = 14, // Do not allow members to unlock their account with a PIN.
|
||||
RestrictedItemTypesPolicy = 15, // Restricts item types that can be created within an organization
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import { ServerConfig } from "../platform/abstractions/config/server-config";
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum FeatureFlag {
|
||||
/* Admin Console Team */
|
||||
LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission",
|
||||
SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions",
|
||||
OptimizeNestedTraverseTypescript = "pm-21695-optimize-nested-traverse-typescript",
|
||||
|
||||
@@ -38,7 +37,6 @@ export enum FeatureFlag {
|
||||
|
||||
/* Key Management */
|
||||
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
|
||||
UserKeyRotationV2 = "userkey-rotation-v2",
|
||||
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
|
||||
UseSDKForDecryption = "use-sdk-for-decryption",
|
||||
PM17987_BlockType0 = "pm-17987-block-type-0",
|
||||
@@ -51,7 +49,6 @@ export enum FeatureFlag {
|
||||
/* Vault */
|
||||
PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge",
|
||||
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
|
||||
SecurityTasks = "security-tasks",
|
||||
PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk",
|
||||
CipherKeyEncryption = "cipher-key-encryption",
|
||||
PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms",
|
||||
@@ -77,7 +74,6 @@ const FALSE = false as boolean;
|
||||
*/
|
||||
export const DefaultFeatureFlagValue = {
|
||||
/* Admin Console Team */
|
||||
[FeatureFlag.LimitItemDeletion]: FALSE,
|
||||
[FeatureFlag.SeparateCustomRolePermissions]: FALSE,
|
||||
[FeatureFlag.OptimizeNestedTraverseTypescript]: FALSE,
|
||||
|
||||
@@ -98,7 +94,6 @@ export const DefaultFeatureFlagValue = {
|
||||
/* Vault */
|
||||
[FeatureFlag.PM8851_BrowserOnboardingNudge]: FALSE,
|
||||
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,
|
||||
[FeatureFlag.SecurityTasks]: FALSE,
|
||||
[FeatureFlag.CipherKeyEncryption]: FALSE,
|
||||
[FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE,
|
||||
[FeatureFlag.EndUserNotifications]: FALSE,
|
||||
@@ -118,7 +113,6 @@ export const DefaultFeatureFlagValue = {
|
||||
|
||||
/* Key Management */
|
||||
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
|
||||
[FeatureFlag.UserKeyRotationV2]: FALSE,
|
||||
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
|
||||
[FeatureFlag.UseSDKForDecryption]: FALSE,
|
||||
[FeatureFlag.PM17987_BlockType0]: FALSE,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// @ts-strict-ignore
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { DeviceKeysUpdateRequest } from "@bitwarden/common/auth/models/request/update-devices-trust.request";
|
||||
import { OtherDeviceKeysUpdateRequest } from "@bitwarden/common/auth/models/request/update-devices-trust.request";
|
||||
|
||||
import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response";
|
||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||
@@ -61,5 +61,5 @@ export abstract class DeviceTrustServiceAbstraction {
|
||||
oldUserKey: UserKey,
|
||||
newUserKey: UserKey,
|
||||
userId: UserId,
|
||||
) => Promise<DeviceKeysUpdateRequest[]>;
|
||||
) => Promise<OtherDeviceKeysUpdateRequest[]>;
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
|
||||
oldUserKey: UserKey,
|
||||
newUserKey: UserKey,
|
||||
userId: UserId,
|
||||
): Promise<DeviceKeysUpdateRequest[]> {
|
||||
): Promise<OtherDeviceKeysUpdateRequest[]> {
|
||||
if (!userId) {
|
||||
throw new Error("UserId is required. Cannot get rotated data.");
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
clientSecret,
|
||||
]);
|
||||
|
||||
await this.keyService.refreshAdditionalKeys();
|
||||
await this.keyService.refreshAdditionalKeys(userId);
|
||||
}
|
||||
|
||||
availableVaultTimeoutActions$(userId?: string): Observable<VaultTimeoutAction[]> {
|
||||
|
||||
@@ -417,16 +417,12 @@ describe("VaultTimeoutService", () => {
|
||||
expect(stateEventRunnerService.handleEvent).toHaveBeenCalledWith("lock", "user1");
|
||||
});
|
||||
|
||||
it("should call locked callback if no user passed into lock", async () => {
|
||||
it("should call locked callback with the locking user if no userID is passed in.", async () => {
|
||||
setupLock();
|
||||
|
||||
await vaultTimeoutService.lock();
|
||||
|
||||
// Currently these pass `undefined` (or what they were given) as the userId back
|
||||
// but we could change this to give the user that was locked (active) to these methods
|
||||
// so they don't have to get it their own way, but that is a behavioral change that needs
|
||||
// to be tested.
|
||||
expect(lockedCallback).toHaveBeenCalledWith(undefined);
|
||||
expect(lockedCallback).toHaveBeenCalledWith("user1");
|
||||
});
|
||||
|
||||
it("should call state event runner with user passed into lock", async () => {
|
||||
|
||||
@@ -49,7 +49,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||
private taskSchedulerService: TaskSchedulerService,
|
||||
protected logService: LogService,
|
||||
private biometricService: BiometricsService,
|
||||
private lockedCallback: (userId?: string) => Promise<void> = null,
|
||||
private lockedCallback: (userId: UserId) => Promise<void> = null,
|
||||
private loggedOutCallback: (
|
||||
logoutReason: LogoutReason,
|
||||
userId?: string,
|
||||
@@ -166,7 +166,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||
this.messagingService.send("locked", { userId: lockingUserId });
|
||||
|
||||
if (this.lockedCallback != null) {
|
||||
await this.lockedCallback(userId);
|
||||
await this.lockedCallback(lockingUserId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ export class Utils {
|
||||
});
|
||||
}
|
||||
|
||||
static guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
||||
static guidRegex = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/;
|
||||
|
||||
static isGuid(id: string) {
|
||||
return RegExp(Utils.guidRegex, "i").test(id);
|
||||
|
||||
@@ -108,14 +108,19 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
|
||||
return this.webPushConnectionService.supportStatus$(userId);
|
||||
}),
|
||||
supportSwitch({
|
||||
supported: (service) =>
|
||||
service.notifications$.pipe(
|
||||
supported: (service) => {
|
||||
this.logService.info("Using WebPush for notifications");
|
||||
return service.notifications$.pipe(
|
||||
catchError((err: unknown) => {
|
||||
this.logService.warning("Issue with web push, falling back to SignalR", err);
|
||||
return this.connectSignalR$(userId, notificationsUrl);
|
||||
}),
|
||||
),
|
||||
notSupported: () => this.connectSignalR$(userId, notificationsUrl),
|
||||
);
|
||||
},
|
||||
notSupported: () => {
|
||||
this.logService.info("Using SignalR for notifications");
|
||||
return this.connectSignalR$(userId, notificationsUrl);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/
|
||||
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
|
||||
/** Private array used for optimization */
|
||||
const byteToHex = Array.from({ length: 256 }, (_, i) => (i + 0x100).toString(16).substring(1));
|
||||
|
||||
/** Convert standard format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX) UUID to raw 16 byte array. */
|
||||
export function guidToRawFormat(guid: string) {
|
||||
if (!isValidGuid(guid)) {
|
||||
if (!Utils.isGuid(guid)) {
|
||||
throw TypeError("GUID parameter is invalid");
|
||||
}
|
||||
|
||||
@@ -81,15 +83,13 @@ export function guidToStandardFormat(bufferSource: BufferSource) {
|
||||
).toLowerCase();
|
||||
|
||||
// Consistency check for valid UUID. If this throws, it's likely due to one
|
||||
// or more input array values not mapping to a hex octet (leading to "undefined" in the uuid)
|
||||
if (!isValidGuid(guid)) {
|
||||
// of the following:
|
||||
// - One or more input array values don't map to a hex octet (leading to
|
||||
// "undefined" in the uuid)
|
||||
// - Invalid input values for the RFC `version` or `variant` fields
|
||||
if (!Utils.isGuid(guid)) {
|
||||
throw TypeError("Converted GUID is invalid");
|
||||
}
|
||||
|
||||
return guid;
|
||||
}
|
||||
|
||||
// Perform format validation, without enforcing any variant restrictions as Utils.isGuid does
|
||||
function isValidGuid(guid: string): boolean {
|
||||
return RegExp(/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/, "i").test(guid);
|
||||
}
|
||||
|
||||
@@ -532,8 +532,9 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return new CipherResponse(r);
|
||||
}
|
||||
|
||||
async putShareCiphers(request: CipherBulkShareRequest): Promise<CipherResponse[]> {
|
||||
return await this.send("PUT", "/ciphers/share", request, true, true);
|
||||
async putShareCiphers(request: CipherBulkShareRequest): Promise<ListResponse<CipherResponse>> {
|
||||
const r = await this.send("PUT", "/ciphers/share", request, true, true);
|
||||
return new ListResponse<CipherResponse>(r, CipherResponse);
|
||||
}
|
||||
|
||||
async putCipherCollections(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum CipherRepromptType {
|
||||
None = 0,
|
||||
Password = 1,
|
||||
}
|
||||
import { UnionOfValues } from "../types/union-of-values";
|
||||
|
||||
export const CipherRepromptType = {
|
||||
None: 0,
|
||||
Password: 1,
|
||||
} as const;
|
||||
|
||||
export type CipherRepromptType = UnionOfValues<typeof CipherRepromptType>;
|
||||
|
||||
66
libs/common/src/vault/enums/cipher-type.spec.ts
Normal file
66
libs/common/src/vault/enums/cipher-type.spec.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
CipherType,
|
||||
cipherTypeNames,
|
||||
isCipherType,
|
||||
toCipherType,
|
||||
toCipherTypeName,
|
||||
} from "./cipher-type";
|
||||
|
||||
describe("CipherType", () => {
|
||||
describe("toCipherTypeName", () => {
|
||||
it("should map CipherType correctly", () => {
|
||||
// identity test as the value is calculated
|
||||
expect(cipherTypeNames).toEqual({
|
||||
1: "Login",
|
||||
2: "SecureNote",
|
||||
3: "Card",
|
||||
4: "Identity",
|
||||
5: "SshKey",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("toCipherTypeName", () => {
|
||||
it("returns the associated name for the cipher type", () => {
|
||||
expect(toCipherTypeName(1)).toBe("Login");
|
||||
expect(toCipherTypeName(2)).toBe("SecureNote");
|
||||
expect(toCipherTypeName(3)).toBe("Card");
|
||||
expect(toCipherTypeName(4)).toBe("Identity");
|
||||
expect(toCipherTypeName(5)).toBe("SshKey");
|
||||
});
|
||||
|
||||
it("returns undefined for an invalid cipher type", () => {
|
||||
expect(toCipherTypeName(999 as any)).toBeUndefined();
|
||||
expect(toCipherTypeName("" as any)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("isCipherType", () => {
|
||||
it("returns true for valid CipherType values", () => {
|
||||
[1, 2, 3, 4, 5].forEach((value) => {
|
||||
expect(isCipherType(value)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("returns false for invalid CipherType values", () => {
|
||||
expect(isCipherType(999 as any)).toBe(false);
|
||||
expect(isCipherType("Login" as any)).toBe(false);
|
||||
expect(isCipherType(null)).toBe(false);
|
||||
expect(isCipherType(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toCipherType", () => {
|
||||
it("converts valid values to CipherType", () => {
|
||||
expect(toCipherType("1")).toBe(CipherType.Login);
|
||||
expect(toCipherType("02")).toBe(CipherType.SecureNote);
|
||||
});
|
||||
|
||||
it("returns null for invalid values", () => {
|
||||
expect(toCipherType(999 as any)).toBeUndefined();
|
||||
expect(toCipherType("Login" as any)).toBeUndefined();
|
||||
expect(toCipherType(null)).toBeUndefined();
|
||||
expect(toCipherType(undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,60 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum CipherType {
|
||||
Login = 1,
|
||||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4,
|
||||
SshKey = 5,
|
||||
const _CipherType = Object.freeze({
|
||||
Login: 1,
|
||||
SecureNote: 2,
|
||||
Card: 3,
|
||||
Identity: 4,
|
||||
SshKey: 5,
|
||||
} as const);
|
||||
|
||||
type _CipherType = typeof _CipherType;
|
||||
|
||||
export type CipherType = _CipherType[keyof _CipherType];
|
||||
|
||||
// FIXME: Update typing of `CipherType` to be `Record<keyof _CipherType, CipherType>` which is ADR-0025 compliant when the TypeScript version is at least 5.8.
|
||||
export const CipherType: typeof _CipherType = _CipherType;
|
||||
|
||||
/**
|
||||
* Reverse mapping of Cipher Types to their associated names.
|
||||
* Prefer using {@link toCipherTypeName} rather than accessing this object directly.
|
||||
*
|
||||
* When represented as an enum in TypeScript, this mapping was provided
|
||||
* by default. Now using a constant object it needs to be defined manually.
|
||||
*/
|
||||
export const cipherTypeNames = Object.freeze(
|
||||
Object.fromEntries(Object.entries(CipherType).map(([key, value]) => [value, key])),
|
||||
) as Readonly<Record<CipherType, keyof typeof CipherType>>;
|
||||
|
||||
/**
|
||||
* Returns the associated name for the cipher type, will throw when the name is not found.
|
||||
*/
|
||||
export function toCipherTypeName(type: CipherType): keyof typeof CipherType | undefined {
|
||||
const name = cipherTypeNames[type];
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` if the value is a valid `CipherType`, `false` otherwise.
|
||||
*/
|
||||
export const isCipherType = (value: unknown): value is CipherType => {
|
||||
return Object.values(CipherType).includes(value as CipherType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a value to a `CipherType` if it is valid, otherwise returns `null`.
|
||||
*/
|
||||
export const toCipherType = (value: unknown): CipherType | undefined => {
|
||||
if (isCipherType(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
const valueAsInt = parseInt(value, 10);
|
||||
|
||||
if (isCipherType(valueAsInt)) {
|
||||
return valueAsInt;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum FieldType {
|
||||
Text = 0,
|
||||
Hidden = 1,
|
||||
Boolean = 2,
|
||||
Linked = 3,
|
||||
}
|
||||
const _FieldType = Object.freeze({
|
||||
Text: 0,
|
||||
Hidden: 1,
|
||||
Boolean: 2,
|
||||
Linked: 3,
|
||||
} as const);
|
||||
|
||||
type _FieldType = typeof _FieldType;
|
||||
|
||||
export type FieldType = _FieldType[keyof _FieldType];
|
||||
|
||||
export const FieldType: Record<keyof _FieldType, FieldType> = _FieldType;
|
||||
|
||||
@@ -1,46 +1,48 @@
|
||||
import { UnionOfValues } from "../types/union-of-values";
|
||||
|
||||
export type LinkedIdType = LoginLinkedId | CardLinkedId | IdentityLinkedId;
|
||||
|
||||
// LoginView
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum LoginLinkedId {
|
||||
Username = 100,
|
||||
Password = 101,
|
||||
}
|
||||
export const LoginLinkedId = {
|
||||
Username: 100,
|
||||
Password: 101,
|
||||
} as const;
|
||||
|
||||
export type LoginLinkedId = UnionOfValues<typeof LoginLinkedId>;
|
||||
|
||||
// CardView
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum CardLinkedId {
|
||||
CardholderName = 300,
|
||||
ExpMonth = 301,
|
||||
ExpYear = 302,
|
||||
Code = 303,
|
||||
Brand = 304,
|
||||
Number = 305,
|
||||
}
|
||||
export const CardLinkedId = {
|
||||
CardholderName: 300,
|
||||
ExpMonth: 301,
|
||||
ExpYear: 302,
|
||||
Code: 303,
|
||||
Brand: 304,
|
||||
Number: 305,
|
||||
} as const;
|
||||
|
||||
export type CardLinkedId = UnionOfValues<typeof CardLinkedId>;
|
||||
|
||||
// IdentityView
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum IdentityLinkedId {
|
||||
Title = 400,
|
||||
MiddleName = 401,
|
||||
Address1 = 402,
|
||||
Address2 = 403,
|
||||
Address3 = 404,
|
||||
City = 405,
|
||||
State = 406,
|
||||
PostalCode = 407,
|
||||
Country = 408,
|
||||
Company = 409,
|
||||
Email = 410,
|
||||
Phone = 411,
|
||||
Ssn = 412,
|
||||
Username = 413,
|
||||
PassportNumber = 414,
|
||||
LicenseNumber = 415,
|
||||
FirstName = 416,
|
||||
LastName = 417,
|
||||
FullName = 418,
|
||||
}
|
||||
export const IdentityLinkedId = {
|
||||
Title: 400,
|
||||
MiddleName: 401,
|
||||
Address1: 402,
|
||||
Address2: 403,
|
||||
Address3: 404,
|
||||
City: 405,
|
||||
State: 406,
|
||||
PostalCode: 407,
|
||||
Country: 408,
|
||||
Company: 409,
|
||||
Email: 410,
|
||||
Phone: 411,
|
||||
Ssn: 412,
|
||||
Username: 413,
|
||||
PassportNumber: 414,
|
||||
LicenseNumber: 415,
|
||||
FirstName: 416,
|
||||
LastName: 417,
|
||||
FullName: 418,
|
||||
} as const;
|
||||
|
||||
export type IdentityLinkedId = UnionOfValues<typeof IdentityLinkedId>;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum SecureNoteType {
|
||||
Generic = 0,
|
||||
}
|
||||
import { UnionOfValues } from "../types/union-of-values";
|
||||
|
||||
export const SecureNoteType = {
|
||||
Generic: 0,
|
||||
} as const;
|
||||
|
||||
export type SecureNoteType = UnionOfValues<typeof SecureNoteType>;
|
||||
|
||||
@@ -57,7 +57,7 @@ export class CipherData {
|
||||
this.organizationUseTotp = response.organizationUseTotp;
|
||||
this.favorite = response.favorite;
|
||||
this.revisionDate = response.revisionDate;
|
||||
this.type = response.type;
|
||||
this.type = response.type as CipherType;
|
||||
this.name = response.name;
|
||||
this.notes = response.notes;
|
||||
this.collectionIds = collectionIds != null ? collectionIds : response.collectionIds;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { CipherType } from "../../enums";
|
||||
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
||||
import { CardApi } from "../api/card.api";
|
||||
import { CipherPermissionsApi } from "../api/cipher-permissions.api";
|
||||
@@ -17,7 +18,7 @@ export class CipherResponse extends BaseResponse {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
folderId: string;
|
||||
type: number;
|
||||
type: CipherType;
|
||||
name: string;
|
||||
notes: string;
|
||||
fields: FieldApi[];
|
||||
|
||||
@@ -7,10 +7,9 @@ import { CollectionService, CollectionView } from "@bitwarden/admin-console/comm
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CollectionId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { FakeAccountService, mockAccountServiceWith } from "../../../spec";
|
||||
import { ConfigService } from "../../platform/abstractions/config/config.service";
|
||||
import { CipherPermissionsApi } from "../models/api/cipher-permissions.api";
|
||||
import { CipherView } from "../models/view/cipher.view";
|
||||
|
||||
@@ -24,7 +23,6 @@ describe("CipherAuthorizationService", () => {
|
||||
|
||||
const mockCollectionService = mock<CollectionService>();
|
||||
const mockOrganizationService = mock<OrganizationService>();
|
||||
const mockConfigService = mock<ConfigService>();
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
let mockAccountService: FakeAccountService;
|
||||
|
||||
@@ -70,10 +68,7 @@ describe("CipherAuthorizationService", () => {
|
||||
mockCollectionService,
|
||||
mockOrganizationService,
|
||||
mockAccountService,
|
||||
mockConfigService,
|
||||
);
|
||||
|
||||
mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
});
|
||||
|
||||
describe("canRestoreCipher$", () => {
|
||||
@@ -90,7 +85,7 @@ describe("CipherAuthorizationService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if isAdminConsleAction and user can edit all ciphers in the org", (done) => {
|
||||
it("should return true if isAdminConsoleAction and user can edit all ciphers in the org", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1"]) as CipherView;
|
||||
const organization = createMockOrganization({ canEditAllCiphers: true });
|
||||
mockOrganizationService.organizations$.mockReturnValue(
|
||||
@@ -145,15 +140,6 @@ describe("CipherAuthorizationService", () => {
|
||||
});
|
||||
|
||||
describe("canDeleteCipher$", () => {
|
||||
it("should return true if cipher has no organizationId", (done) => {
|
||||
const cipher = createMockCipher(null, []) as CipherView;
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher).subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if isAdminConsoleAction is true and cipher is unassigned", (done) => {
|
||||
const cipher = createMockCipher("org1", []) as CipherView;
|
||||
const organization = createMockOrganization({ canEditUnassignedCiphers: true });
|
||||
@@ -161,7 +147,7 @@ describe("CipherAuthorizationService", () => {
|
||||
of([organization]) as Observable<Organization[]>,
|
||||
);
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => {
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, true).subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
done();
|
||||
});
|
||||
@@ -174,7 +160,7 @@ describe("CipherAuthorizationService", () => {
|
||||
of([organization]) as Observable<Organization[]>,
|
||||
);
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => {
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, true).subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
expect(mockOrganizationService.organizations$).toHaveBeenCalledWith(mockUserId);
|
||||
done();
|
||||
@@ -186,136 +172,32 @@ describe("CipherAuthorizationService", () => {
|
||||
const organization = createMockOrganization({ canEditUnassignedCiphers: false });
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => {
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, true).subscribe((result) => {
|
||||
expect(result).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if activeCollectionId is provided and has manage permission", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
||||
const activeCollectionId = "col1" as CollectionId;
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", true),
|
||||
createMockCollection("col2", false),
|
||||
];
|
||||
mockCollectionService.decryptedCollectionViews$.mockReturnValue(
|
||||
of(allCollections as CollectionView[]),
|
||||
);
|
||||
|
||||
cipherAuthorizationService
|
||||
.canDeleteCipher$(cipher, [activeCollectionId])
|
||||
.subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
expect(mockCollectionService.decryptedCollectionViews$).toHaveBeenCalledWith([
|
||||
"col1",
|
||||
"col2",
|
||||
] as CollectionId[]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return false if activeCollectionId is provided and manage permission is not present", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
||||
const activeCollectionId = "col1" as CollectionId;
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", false),
|
||||
createMockCollection("col2", true),
|
||||
];
|
||||
mockCollectionService.decryptedCollectionViews$.mockReturnValue(
|
||||
of(allCollections as CollectionView[]),
|
||||
);
|
||||
|
||||
cipherAuthorizationService
|
||||
.canDeleteCipher$(cipher, [activeCollectionId])
|
||||
.subscribe((result) => {
|
||||
expect(result).toBe(false);
|
||||
expect(mockCollectionService.decryptedCollectionViews$).toHaveBeenCalledWith([
|
||||
"col1",
|
||||
"col2",
|
||||
] as CollectionId[]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if any collection has manage permission", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2", "col3"]) as CipherView;
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", false),
|
||||
createMockCollection("col2", true),
|
||||
createMockCollection("col3", false),
|
||||
];
|
||||
mockCollectionService.decryptedCollectionViews$.mockReturnValue(
|
||||
of(allCollections as CollectionView[]),
|
||||
);
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher).subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
expect(mockCollectionService.decryptedCollectionViews$).toHaveBeenCalledWith([
|
||||
"col1",
|
||||
"col2",
|
||||
"col3",
|
||||
] as CollectionId[]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return false if no collection has manage permission", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", false),
|
||||
createMockCollection("col2", false),
|
||||
];
|
||||
mockCollectionService.decryptedCollectionViews$.mockReturnValue(
|
||||
of(allCollections as CollectionView[]),
|
||||
);
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher).subscribe((result) => {
|
||||
expect(result).toBe(false);
|
||||
expect(mockCollectionService.decryptedCollectionViews$).toHaveBeenCalledWith([
|
||||
"col1",
|
||||
"col2",
|
||||
] as CollectionId[]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if feature flag enabled and cipher.permissions.delete is true", (done) => {
|
||||
it("should return true when cipher.permissions.delete is true", (done) => {
|
||||
const cipher = createMockCipher("org1", [], true, {
|
||||
delete: true,
|
||||
} as CipherPermissionsApi) as CipherView;
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], false).subscribe((result) => {
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, false).subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return false if feature flag enabled and cipher.permissions.delete is false", (done) => {
|
||||
it("should return false when cipher.permissions.delete is false", (done) => {
|
||||
const cipher = createMockCipher("org1", []) as CipherView;
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], false).subscribe((result) => {
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, false).subscribe((result) => {
|
||||
expect(result).toBe(false);
|
||||
expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { combineLatest, map, Observable, of, shareReplay, switchMap } from "rxjs";
|
||||
import { map, Observable, of, shareReplay, 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
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CollectionId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { getUserId } from "../../auth/services/account.service";
|
||||
import { FeatureFlag } from "../../enums/feature-flag.enum";
|
||||
import { Cipher } from "../models/domain/cipher";
|
||||
import { CipherView } from "../models/view/cipher.view";
|
||||
|
||||
@@ -26,14 +24,12 @@ export abstract class CipherAuthorizationService {
|
||||
* Determines if the user can delete the specified cipher.
|
||||
*
|
||||
* @param {CipherLike} cipher - The cipher object to evaluate for deletion permissions.
|
||||
* @param {CollectionId[]} [allowedCollections] - Optional. The selected collection id from the vault filter.
|
||||
* @param {boolean} isAdminConsoleAction - Optional. A flag indicating if the action is being performed from the admin console.
|
||||
*
|
||||
* @returns {Observable<boolean>} - An observable that emits a boolean value indicating if the user can delete the cipher.
|
||||
*/
|
||||
abstract canDeleteCipher$: (
|
||||
cipher: CipherLike,
|
||||
allowedCollections?: CollectionId[],
|
||||
isAdminConsoleAction?: boolean,
|
||||
) => Observable<boolean>;
|
||||
|
||||
@@ -72,7 +68,6 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
|
||||
private collectionService: CollectionService,
|
||||
private organizationService: OrganizationService,
|
||||
private accountService: AccountService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
private organization$ = (cipher: CipherLike) =>
|
||||
@@ -86,48 +81,21 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
|
||||
*
|
||||
* {@link CipherAuthorizationService.canDeleteCipher$}
|
||||
*/
|
||||
canDeleteCipher$(
|
||||
cipher: CipherLike,
|
||||
allowedCollections?: CollectionId[],
|
||||
isAdminConsoleAction?: boolean,
|
||||
): Observable<boolean> {
|
||||
return combineLatest([
|
||||
this.organization$(cipher),
|
||||
this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion),
|
||||
]).pipe(
|
||||
switchMap(([organization, featureFlagEnabled]) => {
|
||||
canDeleteCipher$(cipher: CipherLike, isAdminConsoleAction?: boolean): Observable<boolean> {
|
||||
return this.organization$(cipher).pipe(
|
||||
map((organization) => {
|
||||
if (isAdminConsoleAction) {
|
||||
// If the user is an admin, they can delete an unassigned cipher
|
||||
if (!cipher.collectionIds || cipher.collectionIds.length === 0) {
|
||||
return of(organization?.canEditUnassignedCiphers === true);
|
||||
return organization?.canEditUnassignedCiphers === true;
|
||||
}
|
||||
|
||||
if (organization?.canEditAllCiphers) {
|
||||
return of(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (featureFlagEnabled) {
|
||||
return of(cipher.permissions.delete);
|
||||
}
|
||||
|
||||
if (cipher.organizationId == null) {
|
||||
return of(true);
|
||||
}
|
||||
|
||||
return this.collectionService
|
||||
.decryptedCollectionViews$(cipher.collectionIds as CollectionId[])
|
||||
.pipe(
|
||||
map((allCollections) => {
|
||||
const shouldFilter = allowedCollections?.some(Boolean);
|
||||
|
||||
const collections = shouldFilter
|
||||
? allCollections.filter((c) => allowedCollections?.includes(c.id as CollectionId))
|
||||
: allCollections;
|
||||
|
||||
return collections.some((collection) => collection.manage);
|
||||
}),
|
||||
);
|
||||
return cipher.permissions.delete;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -852,7 +852,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
const request = new CipherBulkShareRequest(encCiphers, collectionIds, userId);
|
||||
try {
|
||||
const response = await this.apiService.putShareCiphers(request);
|
||||
const responseMap = new Map(response.map((c) => [c.id, c]));
|
||||
const responseMap = new Map(response.data.map((r) => [r.id, r]));
|
||||
|
||||
encCiphers.forEach((cipher) => {
|
||||
const matchingCipher = responseMap.get(cipher.id);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum SecurityTaskStatus {
|
||||
import { UnionOfValues } from "../../types/union-of-values";
|
||||
|
||||
export const SecurityTaskStatus = {
|
||||
/**
|
||||
* Default status for newly created tasks that have not been completed.
|
||||
*/
|
||||
Pending = 0,
|
||||
Pending: 0,
|
||||
|
||||
/**
|
||||
* Status when a task is considered complete and has no remaining actions
|
||||
*/
|
||||
Completed = 1,
|
||||
}
|
||||
Completed: 1,
|
||||
} as const;
|
||||
|
||||
export type SecurityTaskStatus = UnionOfValues<typeof SecurityTaskStatus>;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum SecurityTaskType {
|
||||
import { UnionOfValues } from "../../types/union-of-values";
|
||||
|
||||
export const SecurityTaskType = {
|
||||
/**
|
||||
* Task to update a cipher's password that was found to be at-risk by an administrator
|
||||
*/
|
||||
UpdateAtRiskCredential = 0,
|
||||
}
|
||||
UpdateAtRiskCredential: 0,
|
||||
} as const;
|
||||
|
||||
export type SecurityTaskType = UnionOfValues<typeof SecurityTaskType>;
|
||||
|
||||
@@ -7,7 +7,6 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { NotificationType } from "@bitwarden/common/enums";
|
||||
import { NotificationResponse } from "@bitwarden/common/models/response/notification.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { Message, MessageListener } from "@bitwarden/common/platform/messaging";
|
||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||
import { SecurityTaskId, UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -25,7 +24,6 @@ describe("Default task service", () => {
|
||||
const userId = "user-id" as UserId;
|
||||
const mockApiSend = jest.fn();
|
||||
const mockGetAllOrgs$ = jest.fn();
|
||||
const mockGetFeatureFlag$ = jest.fn();
|
||||
const mockAuthStatuses$ = new BehaviorSubject<Record<UserId, AuthenticationStatus>>({});
|
||||
const mockNotifications$ = new Subject<readonly [NotificationResponse, UserId]>();
|
||||
const mockMessages$ = new Subject<Message<Record<string, unknown>>>();
|
||||
@@ -34,14 +32,12 @@ describe("Default task service", () => {
|
||||
beforeEach(async () => {
|
||||
mockApiSend.mockClear();
|
||||
mockGetAllOrgs$.mockClear();
|
||||
mockGetFeatureFlag$.mockClear();
|
||||
|
||||
fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId));
|
||||
service = new DefaultTaskService(
|
||||
fakeStateProvider,
|
||||
{ send: mockApiSend } as unknown as ApiService,
|
||||
{ organizations$: mockGetAllOrgs$ } as unknown as OrganizationService,
|
||||
{ getFeatureFlag$: mockGetFeatureFlag$ } as unknown as ConfigService,
|
||||
{ authStatuses$: mockAuthStatuses$.asObservable() } as unknown as AuthService,
|
||||
{ notifications$: mockNotifications$.asObservable() } as unknown as NotificationsService,
|
||||
{ allMessages$: mockMessages$.asObservable() } as unknown as MessageListener,
|
||||
@@ -50,7 +46,6 @@ describe("Default task service", () => {
|
||||
|
||||
describe("tasksEnabled$", () => {
|
||||
it("should emit true if any organization uses risk insights", async () => {
|
||||
mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true));
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
@@ -70,7 +65,6 @@ describe("Default task service", () => {
|
||||
});
|
||||
|
||||
it("should emit false if no organization uses risk insights", async () => {
|
||||
mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true));
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
@@ -88,28 +82,10 @@ describe("Default task service", () => {
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should emit false if the feature flag is off", async () => {
|
||||
mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(false));
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
useRiskInsights: true,
|
||||
},
|
||||
] as Organization[]),
|
||||
);
|
||||
|
||||
const { tasksEnabled$ } = service;
|
||||
|
||||
const result = await firstValueFrom(tasksEnabled$("user-id" as UserId));
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("tasks$", () => {
|
||||
beforeEach(() => {
|
||||
mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true));
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
@@ -182,7 +158,6 @@ describe("Default task service", () => {
|
||||
|
||||
describe("pendingTasks$", () => {
|
||||
beforeEach(() => {
|
||||
mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true));
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
|
||||
@@ -15,9 +15,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { NotificationType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { MessageListener } from "@bitwarden/common/platform/messaging";
|
||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
@@ -43,20 +41,14 @@ export class DefaultTaskService implements TaskService {
|
||||
private stateProvider: StateProvider,
|
||||
private apiService: ApiService,
|
||||
private organizationService: OrganizationService,
|
||||
private configService: ConfigService,
|
||||
private authService: AuthService,
|
||||
private notificationService: NotificationsService,
|
||||
private messageListener: MessageListener,
|
||||
) {}
|
||||
|
||||
tasksEnabled$ = perUserCache$((userId) => {
|
||||
return combineLatest([
|
||||
this.organizationService
|
||||
.organizations$(userId)
|
||||
.pipe(map((orgs) => orgs.some((o) => o.useRiskInsights))),
|
||||
this.configService.getFeatureFlag$(FeatureFlag.SecurityTasks),
|
||||
]).pipe(
|
||||
map(([atLeastOneOrgEnabled, flagEnabled]) => atLeastOneOrgEnabled && flagEnabled),
|
||||
return this.organizationService.organizations$(userId).pipe(
|
||||
map((orgs) => orgs.some((o) => o.useRiskInsights)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
});
|
||||
|
||||
2
libs/common/src/vault/types/union-of-values.ts
Normal file
2
libs/common/src/vault/types/union-of-values.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
/** Creates a union type consisting of all values within the record. */
|
||||
export type UnionOfValues<T extends Record<string, unknown>> = T[keyof T];
|
||||
Reference in New Issue
Block a user