1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-26 09:33:22 +00:00

Merge main

This commit is contained in:
Bernd Schoolmann
2026-02-13 14:21:59 +01:00
1511 changed files with 96557 additions and 20566 deletions

View File

@@ -1,10 +1,12 @@
import { Observable } from "rxjs";
import { CollectionDetailsResponse } from "@bitwarden/admin-console/common";
import {
CollectionAdminView,
CollectionAccessSelectionView,
CollectionDetailsResponse,
} from "@bitwarden/common/admin-console/models/collections";
import { UserId } from "@bitwarden/common/types/guid";
import { CollectionAccessSelectionView, CollectionAdminView } from "../models";
export abstract class CollectionAdminService {
abstract collectionAdminViews$(
organizationId: string,

View File

@@ -1,11 +1,14 @@
import { Observable } from "rxjs";
import {
CollectionView,
Collection,
CollectionData,
} from "@bitwarden/common/admin-console/models/collections";
import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { OrgKey } from "@bitwarden/common/types/key";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { CollectionData, Collection, CollectionView } from "../models";
export abstract class CollectionService {
abstract encryptedCollections$(userId: UserId): Observable<Collection[] | null>;
abstract decryptedCollections$(userId: UserId): Observable<CollectionView[]>;

View File

@@ -1,4 +1,5 @@
import { Collection } from "./collection";
import { Collection } from "@bitwarden/common/admin-console/models/collections";
import { BaseCollectionRequest } from "./collection.request";
export class CollectionWithIdRequest extends BaseCollectionRequest {

View File

@@ -1,15 +1,17 @@
import { MockProxy, mock } from "jest-mock-extended";
import {
CollectionDetailsResponse,
Collection,
CollectionTypes,
CollectionData,
} from "@bitwarden/common/admin-console/models/collections";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { makeSymmetricCryptoKey } from "@bitwarden/common/spec";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { OrgKey } from "@bitwarden/common/types/key";
import { Collection, CollectionTypes } from "./collection";
import { CollectionData } from "./collection.data";
import { CollectionDetailsResponse } from "./collection.response";
describe("Collection", () => {
let data: CollectionData;
let encService: MockProxy<EncryptService>;

View File

@@ -1,9 +1,3 @@
export * from "./bulk-collection-access.request";
export * from "./collection-access-selection.view";
export * from "./collection-admin.view";
export * from "./collection";
export * from "./collection.data";
export * from "./collection.view";
export * from "./collection.request";
export * from "./collection.response";
export * from "./collection-with-id.request";

View File

@@ -1,5 +1,6 @@
import { Jsonify } from "type-fest";
import { CollectionView, CollectionData } from "@bitwarden/common/admin-console/models/collections";
import {
COLLECTION_DISK,
COLLECTION_MEMORY,
@@ -7,8 +8,6 @@ import {
} from "@bitwarden/common/platform/state";
import { CollectionId } from "@bitwarden/common/types/guid";
import { CollectionData, CollectionView } from "../models";
export const ENCRYPTED_COLLECTION_DATA_KEY = UserKeyDefinition.record<CollectionData, CollectionId>(
COLLECTION_DISK,
"collections",

View File

@@ -5,6 +5,14 @@ import {
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
CollectionAccessSelectionView,
CollectionAdminView,
CollectionAccessDetailsResponse,
CollectionDetailsResponse,
CollectionResponse,
CollectionData,
} from "@bitwarden/common/admin-console/models/collections";
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
@@ -13,13 +21,7 @@ import { KeyService } from "@bitwarden/key-management";
import { CollectionAdminService, CollectionService } from "../abstractions";
import {
CollectionData,
CollectionAccessDetailsResponse,
CollectionDetailsResponse,
CollectionResponse,
BulkCollectionAccessRequest,
CollectionAccessSelectionView,
CollectionAdminView,
BaseCollectionRequest,
UpdateCollectionRequest,
CreateCollectionRequest,

View File

@@ -1,6 +1,11 @@
import { mock, MockProxy } from "jest-mock-extended";
import { combineLatest, first, firstValueFrom, of, ReplaySubject, takeWhile } from "rxjs";
import {
CollectionView,
CollectionTypes,
CollectionData,
} from "@bitwarden/common/admin-console/models/collections";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -18,8 +23,6 @@ import { OrgKey } from "@bitwarden/common/types/key";
import { newGuid } from "@bitwarden/guid";
import { KeyService } from "@bitwarden/key-management";
import { CollectionData, CollectionTypes, CollectionView } from "../models";
import { DECRYPTED_COLLECTION_DATA_KEY, ENCRYPTED_COLLECTION_DATA_KEY } from "./collection.state";
import { DefaultCollectionService } from "./default-collection.service";

View File

@@ -12,6 +12,11 @@ import {
switchMap,
} from "rxjs";
import {
CollectionView,
Collection,
CollectionData,
} from "@bitwarden/common/admin-console/models/collections";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -23,7 +28,6 @@ import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
import { KeyService } from "@bitwarden/key-management";
import { CollectionService } from "../abstractions/collection.service";
import { Collection, CollectionData, CollectionView } from "../models";
import { DECRYPTED_COLLECTION_DATA_KEY, ENCRYPTED_COLLECTION_DATA_KEY } from "./collection.state";

View File

@@ -10,6 +10,8 @@ import {
OrganizationUserResetPasswordRequest,
OrganizationUserUpdateRequest,
} from "../models/requests";
import { OrganizationUserBulkRestoreRequest } from "../models/requests/organization-user-bulk-restore.request";
import { OrganizationUserRestoreRequest } from "../models/requests/organization-user-restore.request";
import {
OrganizationUserBulkPublicKeyResponse,
OrganizationUserBulkResponse,
@@ -264,6 +266,13 @@ export abstract class OrganizationUserApiService {
ids: string[],
): Promise<ListResponse<OrganizationUserBulkResponse>>;
/**
* Revoke the current user's access to the organization
* if they decline an item transfer under the Organization Data Ownership policy.
* @param organizationId - Identifier for the organization the user belongs to
*/
abstract revokeSelf(organizationId: string): Promise<void>;
/**
* Restore an organization user's access to the organization
* @param organizationId - Identifier for the organization the user belongs to
@@ -271,6 +280,18 @@ export abstract class OrganizationUserApiService {
*/
abstract restoreOrganizationUser(organizationId: string, id: string): Promise<void>;
/**
* Restore an organization user's access to the organization
* @param organizationId - Identifier for the organization the user belongs to
* @param id - Organization user identifier
* @param request - Restore request containing default user collection name
*/
abstract restoreOrganizationUser_vNext(
organizationId: string,
id: string,
request: OrganizationUserRestoreRequest,
): Promise<void>;
/**
* Restore many organization users' access to the organization
* @param organizationId - Identifier for the organization the users belongs to
@@ -282,6 +303,17 @@ export abstract class OrganizationUserApiService {
ids: string[],
): Promise<ListResponse<OrganizationUserBulkResponse>>;
/**
* Restore many organization users' access to the organization
* @param organizationId - Identifier for the organization the users belongs to
* @param request - Restore request containing default user collection name
* @return List of user ids, including both those that were successfully restored and those that had an error
*/
abstract restoreManyOrganizationUsers_vNext(
organizationId: string,
request: OrganizationUserBulkRestoreRequest,
): Promise<ListResponse<OrganizationUserBulkResponse>>;
/**
* Remove an organization user's access to the organization and delete their account data
* @param organizationId - Identifier for the organization the user belongs to

View File

@@ -42,4 +42,11 @@ export abstract class OrganizationUserService {
organization: Organization,
userIdsWithKeys: { id: string; key: string }[],
): Observable<ListResponse<OrganizationUserBulkResponse>>;
abstract restoreUser(organization: Organization, userId: string): Observable<void>;
abstract bulkRestoreUsers(
organization: Organization,
userIds: string[],
): Observable<ListResponse<OrganizationUserBulkResponse>>;
}

View File

@@ -0,0 +1,11 @@
import { EncString } from "@bitwarden/sdk-internal";
export class OrganizationUserBulkRestoreRequest {
ids: string[];
defaultUserCollectionName: EncString | undefined;
constructor(ids: string[], defaultUserCollectionName?: EncString) {
this.ids = ids;
this.defaultUserCollectionName = defaultUserCollectionName;
}
}

View File

@@ -0,0 +1,9 @@
import { EncString } from "@bitwarden/sdk-internal";
export class OrganizationUserRestoreRequest {
defaultUserCollectionName: EncString | undefined;
constructor(defaultUserCollectionName?: EncString) {
this.defaultUserCollectionName = defaultUserCollectionName;
}
}

View File

@@ -13,6 +13,8 @@ import {
OrganizationUserUpdateRequest,
OrganizationUserBulkRequest,
} from "../models/requests";
import { OrganizationUserBulkRestoreRequest } from "../models/requests/organization-user-bulk-restore.request";
import { OrganizationUserRestoreRequest } from "../models/requests/organization-user-restore.request";
import {
OrganizationUserBulkPublicKeyResponse,
OrganizationUserBulkResponse,
@@ -339,6 +341,16 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
return new ListResponse(r, OrganizationUserBulkResponse);
}
revokeSelf(organizationId: string): Promise<void> {
return this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/revoke-self",
null,
true,
false,
);
}
restoreOrganizationUser(organizationId: string, id: string): Promise<void> {
return this.apiService.send(
"PUT",
@@ -349,6 +361,20 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
);
}
restoreOrganizationUser_vNext(
organizationId: string,
id: string,
request: OrganizationUserRestoreRequest,
): Promise<void> {
return this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/restore/vnext",
request,
true,
false,
);
}
async restoreManyOrganizationUsers(
organizationId: string,
ids: string[],
@@ -363,6 +389,20 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
return new ListResponse(r, OrganizationUserBulkResponse);
}
async restoreManyOrganizationUsers_vNext(
organizationId: string,
request: OrganizationUserBulkRestoreRequest,
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/restore",
request,
true,
true,
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
deleteOrganizationUser(organizationId: string, id: string): Promise<void> {
return this.apiService.send(
"DELETE",

View File

@@ -61,6 +61,8 @@ describe("DefaultOrganizationUserService", () => {
organizationUserApiService = {
postOrganizationUserConfirm: jest.fn(),
postOrganizationUserBulkConfirm: jest.fn(),
restoreOrganizationUser_vNext: jest.fn(),
restoreManyOrganizationUsers_vNext: jest.fn(),
} as any;
accountService = {
@@ -174,4 +176,97 @@ describe("DefaultOrganizationUserService", () => {
});
});
});
describe("buildRestoreUserRequest", () => {
beforeEach(() => {
setupCommonMocks();
});
it("should build a restore request with encrypted collection name", (done) => {
service.buildRestoreUserRequest(mockOrganization).subscribe({
next: (request) => {
expect(i18nService.t).toHaveBeenCalledWith("myItems");
expect(encryptService.encryptString).toHaveBeenCalledWith(
mockDefaultCollectionName,
mockOrgKey,
);
expect(request).toEqual({
defaultUserCollectionName: mockEncryptedCollectionName.encryptedString,
});
done();
},
error: done,
});
});
});
describe("restoreUser", () => {
beforeEach(() => {
setupCommonMocks();
organizationUserApiService.restoreOrganizationUser_vNext.mockReturnValue(Promise.resolve());
});
it("should restore a user successfully", (done) => {
service.restoreUser(mockOrganization, mockUserId).subscribe({
next: () => {
expect(i18nService.t).toHaveBeenCalledWith("myItems");
expect(encryptService.encryptString).toHaveBeenCalledWith(
mockDefaultCollectionName,
mockOrgKey,
);
expect(organizationUserApiService.restoreOrganizationUser_vNext).toHaveBeenCalledWith(
mockOrganization.id,
mockUserId,
{
defaultUserCollectionName: mockEncryptedCollectionName.encryptedString,
},
);
done();
},
error: done,
});
});
});
describe("bulkRestoreUsers", () => {
const mockUserIds = ["user-1", "user-2"];
const mockBulkResponse = {
data: [
{ id: "user-1", error: null } as OrganizationUserBulkResponse,
{ id: "user-2", error: null } as OrganizationUserBulkResponse,
],
} as ListResponse<OrganizationUserBulkResponse>;
beforeEach(() => {
setupCommonMocks();
organizationUserApiService.restoreManyOrganizationUsers_vNext.mockReturnValue(
Promise.resolve(mockBulkResponse),
);
});
it("should bulk restore users successfully", (done) => {
service.bulkRestoreUsers(mockOrganization, mockUserIds).subscribe({
next: (response) => {
expect(i18nService.t).toHaveBeenCalledWith("myItems");
expect(encryptService.encryptString).toHaveBeenCalledWith(
mockDefaultCollectionName,
mockOrgKey,
);
expect(
organizationUserApiService.restoreManyOrganizationUsers_vNext,
).toHaveBeenCalledWith(
mockOrganization.id,
expect.objectContaining({
ids: mockUserIds,
defaultUserCollectionName: mockEncryptedCollectionName.encryptedString,
}),
);
expect(response).toEqual(mockBulkResponse);
done();
},
error: done,
});
});
});
});

View File

@@ -1,10 +1,10 @@
import { combineLatest, filter, map, Observable, switchMap } from "rxjs";
import {
OrganizationUserConfirmRequest,
OrganizationUserBulkConfirmRequest,
OrganizationUserApiService,
OrganizationUserBulkConfirmRequest,
OrganizationUserBulkResponse,
OrganizationUserConfirmRequest,
OrganizationUserService,
} from "@bitwarden/admin-console/common";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
@@ -16,6 +16,9 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { OrganizationId } from "@bitwarden/common/types/guid";
import { KeyService } from "@bitwarden/key-management";
import { OrganizationUserBulkRestoreRequest } from "../models/requests/organization-user-bulk-restore.request";
import { OrganizationUserRestoreRequest } from "../models/requests/organization-user-restore.request";
export class DefaultOrganizationUserService implements OrganizationUserService {
constructor(
protected keyService: KeyService,
@@ -83,6 +86,43 @@ export class DefaultOrganizationUserService implements OrganizationUserService {
);
}
buildRestoreUserRequest(organization: Organization): Observable<OrganizationUserRestoreRequest> {
return this.getEncryptedDefaultCollectionName$(organization).pipe(
map((collectionName) => new OrganizationUserRestoreRequest(collectionName.encryptedString)),
);
}
restoreUser(organization: Organization, userId: string): Observable<void> {
return this.buildRestoreUserRequest(organization).pipe(
switchMap((request) =>
this.organizationUserApiService.restoreOrganizationUser_vNext(
organization.id,
userId,
request,
),
),
);
}
bulkRestoreUsers(
organization: Organization,
userIds: string[],
): Observable<ListResponse<OrganizationUserBulkResponse>> {
return this.getEncryptedDefaultCollectionName$(organization).pipe(
switchMap((collectionName) => {
const request = new OrganizationUserBulkRestoreRequest(
userIds,
collectionName.encryptedString,
);
return this.organizationUserApiService.restoreManyOrganizationUsers_vNext(
organization.id,
request,
);
}),
);
}
private getEncryptedDefaultCollectionName$(organization: Organization) {
return this.orgKey$(organization).pipe(
switchMap((orgKey) =>

View File

@@ -1,6 +1,6 @@
<ng-container>
<div class="tw-size-[70px] tw-content-center" *ngIf="!!IconProviderMap[provider]">
<bit-icon [icon]="IconProviderMap[provider]"></bit-icon>
<bit-svg [content]="IconProviderMap[provider]"></bit-svg>
</div>
<!-- Other 2FA Types (Duo, Yubico, U2F as PNG) -->
<img

View File

@@ -3,7 +3,7 @@
import { Component, Input } from "@angular/core";
import {
Icon,
BitSvg,
TwoFactorAuthAuthenticatorIcon,
TwoFactorAuthEmailIcon,
TwoFactorAuthWebAuthnIcon,
@@ -24,7 +24,7 @@ export class TwoFactorIconComponent {
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() name: string;
protected readonly IconProviderMap: { [key: number | string]: Icon } = {
protected readonly IconProviderMap: { [key: number | string]: BitSvg } = {
0: TwoFactorAuthAuthenticatorIcon,
1: TwoFactorAuthEmailIcon,
7: TwoFactorAuthWebAuthnIcon,

View File

@@ -25,7 +25,7 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid
import {
AnonLayoutWrapperDataService,
ButtonModule,
IconModule,
SvgModule,
LinkModule,
TypographyModule,
} from "@bitwarden/components";
@@ -43,7 +43,7 @@ export type State = "assert" | "assertFailed";
RouterModule,
JslibModule,
ButtonModule,
IconModule,
SvgModule,
LinkModule,
TypographyModule,
],

View File

@@ -1,4 +1,4 @@
import { firstValueFrom } from "rxjs";
import { concatMap, firstValueFrom } 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
@@ -19,19 +19,32 @@ import { AccountCryptographicStateService } from "@bitwarden/common/key-manageme
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import {
MasterPasswordSalt,
MasterPasswordUnlockData,
} from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { asUuid } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { KdfConfigService, KeyService, KdfConfig } from "@bitwarden/key-management";
import {
fromSdkKdfConfig,
KdfConfig,
KdfConfigService,
KeyService,
} from "@bitwarden/key-management";
import { OrganizationId as SdkOrganizationId, UserId as SdkUserId } from "@bitwarden/sdk-internal";
import {
SetInitialPasswordService,
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordUserType,
SetInitialPasswordService,
SetInitialPasswordTdeOffboardingCredentials,
SetInitialPasswordUserType,
} from "./set-initial-password.service.abstraction";
export class DefaultSetInitialPasswordService implements SetInitialPasswordService {
@@ -47,8 +60,13 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
protected organizationUserApiService: OrganizationUserApiService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
protected accountCryptographicStateService: AccountCryptographicStateService,
protected registerSdkService: RegisterSdkService,
) {}
/**
* @deprecated To be removed in PM-28143. When you remove this, also check for any objects/methods
* in this default service that are now un-used and can also be removed.
*/
async setInitialPassword(
credentials: SetInitialPasswordCredentials,
userType: SetInitialPasswordUserType,
@@ -180,7 +198,6 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
if (!keyPair[1].encryptedString) {
throw new Error("encrypted private key not found. Could not set private key in state.");
}
await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId);
await this.accountCryptographicStateService.setAccountCryptographicState(
{
V1: {
@@ -199,6 +216,129 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
}
}
async setInitialPasswordTdeOffboarding(
credentials: SetInitialPasswordTdeOffboardingCredentials,
userId: UserId,
) {
const { newMasterKey, newServerMasterKeyHash, newPasswordHint } = credentials;
for (const [key, value] of Object.entries(credentials)) {
if (value == null) {
throw new Error(`${key} not found. Could not set password.`);
}
}
if (userId == null) {
throw new Error("userId not found. Could not set password.");
}
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
throw new Error("userKey not found. Could not set password.");
}
const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
newMasterKey,
userKey,
);
if (!newMasterKeyEncryptedUserKey[1].encryptedString) {
throw new Error("newMasterKeyEncryptedUserKey not found. Could not set password.");
}
const request = new UpdateTdeOffboardingPasswordRequest();
request.key = newMasterKeyEncryptedUserKey[1].encryptedString;
request.newMasterPasswordHash = newServerMasterKeyHash;
request.masterPasswordHint = newPasswordHint;
await this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request);
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
}
async initializePasswordJitPasswordUserV2Encryption(
credentials: InitializeJitPasswordCredentials,
userId: UserId,
): Promise<void> {
if (userId == null) {
throw new Error("User ID is required.");
}
for (const [key, value] of Object.entries(credentials)) {
if (value == null) {
throw new Error(`${key} is required.`);
}
}
const { newPasswordHint, orgSsoIdentifier, orgId, resetPasswordAutoEnroll, newPassword, salt } =
credentials;
const organizationKeys = await this.organizationApiService.getKeys(orgId);
if (organizationKeys == null) {
throw new Error("Organization keys response is null.");
}
const registerResult = await firstValueFrom(
this.registerSdkService.registerClient$(userId).pipe(
concatMap(async (sdk) => {
if (!sdk) {
throw new Error("SDK not available");
}
using ref = sdk.take();
return await ref.value
.auth()
.registration()
.post_keys_for_jit_password_registration({
org_id: asUuid<SdkOrganizationId>(orgId),
org_public_key: organizationKeys.publicKey,
master_password: newPassword,
master_password_hint: newPasswordHint,
salt: salt,
organization_sso_identifier: orgSsoIdentifier,
user_id: asUuid<SdkUserId>(userId),
reset_password_enroll: resetPasswordAutoEnroll,
});
}),
),
);
if (!("V2" in registerResult.account_cryptographic_state)) {
throw new Error("Unexpected V2 account cryptographic state");
}
// Note: When SDK state management matures, these should be moved into post_keys_for_tde_registration
// Set account cryptography state
await this.accountCryptographicStateService.setAccountCryptographicState(
registerResult.account_cryptographic_state,
userId,
);
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
const masterPasswordUnlockData = MasterPasswordUnlockData.fromSdk(
registerResult.master_password_unlock,
);
await this.masterPasswordService.setMasterPasswordUnlockData(masterPasswordUnlockData, userId);
await this.keyService.setUserKey(
SymmetricCryptoKey.fromString(registerResult.user_key) as UserKey,
userId,
);
await this.updateLegacyState(
newPassword,
fromSdkKdfConfig(registerResult.master_password_unlock.kdf),
new EncString(registerResult.master_password_unlock.masterKeyWrappedUserKey),
userId,
masterPasswordUnlockData,
);
}
/**
* @deprecated To be removed in PM-28143
*/
private async makeMasterKeyEncryptedUserKey(
masterKey: MasterKey,
userId: UserId,
@@ -244,7 +384,40 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId);
}
// Deprecated legacy support - to be removed in future
private async updateLegacyState(
newPassword: string,
kdfConfig: KdfConfig,
masterKeyWrappedUserKey: EncString,
userId: UserId,
masterPasswordUnlockData: MasterPasswordUnlockData,
) {
// TODO Remove HasMasterPassword from UserDecryptionOptions https://bitwarden.atlassian.net/browse/PM-23475
const userDecryptionOpts = await firstValueFrom(
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
);
userDecryptionOpts.hasMasterPassword = true;
await this.userDecryptionOptionsService.setUserDecryptionOptionsById(
userId,
userDecryptionOpts,
);
// TODO Remove KDF state https://bitwarden.atlassian.net/browse/PM-30661
await this.kdfConfigService.setKdfConfig(userId, kdfConfig);
// TODO Remove master key memory state https://bitwarden.atlassian.net/browse/PM-23477
await this.masterPasswordService.setMasterKeyEncryptedUserKey(masterKeyWrappedUserKey, userId);
// TODO Removed with https://bitwarden.atlassian.net/browse/PM-30676
await this.masterPasswordService.setLegacyMasterKeyFromUnlockData(
newPassword,
masterPasswordUnlockData,
userId,
);
}
/**
* @deprecated To be removed in PM-28143
*
* As part of [PM-28494], adding this setting path to accommodate the changes that are
* emerging with pm-23246-unlock-with-master-password-unlock-data.
* Without this, immediately locking/unlocking the vault with the new password _may_ still fail
@@ -310,44 +483,4 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
enrollmentRequest,
);
}
async setInitialPasswordTdeOffboarding(
credentials: SetInitialPasswordTdeOffboardingCredentials,
userId: UserId,
) {
const { newMasterKey, newServerMasterKeyHash, newPasswordHint } = credentials;
for (const [key, value] of Object.entries(credentials)) {
if (value == null) {
throw new Error(`${key} not found. Could not set password.`);
}
}
if (userId == null) {
throw new Error("userId not found. Could not set password.");
}
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
throw new Error("userKey not found. Could not set password.");
}
const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
newMasterKey,
userKey,
);
if (!newMasterKeyEncryptedUserKey[1].encryptedString) {
throw new Error("newMasterKeyEncryptedUserKey not found. Could not set password.");
}
const request = new UpdateTdeOffboardingPasswordRequest();
request.key = newMasterKeyEncryptedUserKey[1].encryptedString;
request.newMasterPasswordHash = newServerMasterKeyHash;
request.masterPasswordHint = newPasswordHint;
await this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request);
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
}
}

View File

@@ -1,5 +1,8 @@
import { MockProxy, mock } from "jest-mock-extended";
import { BehaviorSubject, of } from "rxjs";
// Polyfill for Symbol.dispose required by the service's use of `using` keyword
import "core-js/proposals/explicit-resource-management";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, Observable, 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
@@ -27,17 +30,35 @@ import {
EncString,
} from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import {
MasterPasswordSalt,
MasterPasswordUnlockData,
} from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { Rc } from "@bitwarden/common/platform/misc/reference-counting/rc";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { makeEncString, makeSymmetricCryptoKey } from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey, UserPrivateKey, UserPublicKey } from "@bitwarden/common/types/key";
import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management";
import {
DEFAULT_KDF_CONFIG,
fromSdkKdfConfig,
KdfConfigService,
KeyService,
} from "@bitwarden/key-management";
import {
AuthClient,
BitwardenClient,
WrappedAccountCryptographicState,
} from "@bitwarden/sdk-internal";
import { DefaultSetInitialPasswordService } from "./default-set-initial-password.service.implementation";
import {
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordService,
SetInitialPasswordTdeOffboardingCredentials,
@@ -58,6 +79,7 @@ describe("DefaultSetInitialPasswordService", () => {
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
const registerSdkService = mock<RegisterSdkService>();
let userId: UserId;
let userKey: UserKey;
@@ -94,6 +116,7 @@ describe("DefaultSetInitialPasswordService", () => {
organizationUserApiService,
userDecryptionOptionsService,
accountCryptographicStateService,
registerSdkService,
);
});
@@ -101,6 +124,10 @@ describe("DefaultSetInitialPasswordService", () => {
expect(sut).not.toBeFalsy();
});
/**
* @deprecated To be removed in PM-28143. When you remove this, check also if there are any imports/properties
* in the test setup above that are now un-used and can also be removed.
*/
describe("setInitialPassword(...)", () => {
// Mock function parameters
let credentials: SetInitialPasswordCredentials;
@@ -397,7 +424,6 @@ describe("DefaultSetInitialPasswordService", () => {
// Assert
expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest);
expect(keyService.setPrivateKey).toHaveBeenCalledWith(keyPair[1].encryptedString, userId);
expect(
accountCryptographicStateService.setAccountCryptographicState,
).toHaveBeenCalledWith(
@@ -640,7 +666,9 @@ describe("DefaultSetInitialPasswordService", () => {
// Assert
expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest);
expect(keyService.setPrivateKey).not.toHaveBeenCalled();
expect(
accountCryptographicStateService.setAccountCryptographicState,
).not.toHaveBeenCalled();
});
it("should set the local master key hash to state", async () => {
@@ -834,4 +862,246 @@ describe("DefaultSetInitialPasswordService", () => {
});
});
});
describe("initializePasswordJitPasswordUserV2Encryption()", () => {
let mockSdkRef: {
value: MockProxy<BitwardenClient>;
[Symbol.dispose]: jest.Mock;
};
let mockSdk: {
take: jest.Mock;
};
let mockRegistration: jest.Mock;
const userId = "d4e2e3a1-1b5e-4c3b-8d7a-9f8e7d6c5b4a" as UserId;
const orgId = "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d" as OrganizationId;
const credentials: InitializeJitPasswordCredentials = {
newPasswordHint: "test-hint",
orgSsoIdentifier: "org-sso-id",
orgId: orgId,
resetPasswordAutoEnroll: false,
newPassword: "Test@Password123!",
salt: "user@example.com" as unknown as MasterPasswordSalt,
};
const orgKeys: OrganizationKeysResponse = {
publicKey: "org-public-key-base64",
privateKey: "org-private-key-encrypted",
} as OrganizationKeysResponse;
const sdkRegistrationResult = {
account_cryptographic_state: {
V2: {
private_key: makeEncString().encryptedString!,
signed_public_key: "test-signed-public-key",
signing_key: makeEncString().encryptedString!,
security_state: "test-security-state",
},
},
master_password_unlock: {
kdf: {
pBKDF2: {
iterations: 600000,
},
},
masterKeyWrappedUserKey: makeEncString().encryptedString!,
salt: "user@example.com" as unknown as MasterPasswordSalt,
},
user_key: makeSymmetricCryptoKey(64).keyB64,
};
beforeEach(() => {
jest.clearAllMocks();
mockSdkRef = {
value: mock<BitwardenClient>(),
[Symbol.dispose]: jest.fn(),
};
mockSdkRef.value.auth.mockReturnValue({
registration: jest.fn().mockReturnValue({
post_keys_for_jit_password_registration: jest.fn(),
}),
} as unknown as AuthClient);
mockSdk = {
take: jest.fn().mockReturnValue(mockSdkRef),
};
registerSdkService.registerClient$.mockReturnValue(
of(mockSdk) as unknown as Observable<Rc<BitwardenClient>>,
);
organizationApiService.getKeys.mockResolvedValue(orgKeys);
mockRegistration = mockSdkRef.value.auth().registration()
.post_keys_for_jit_password_registration as unknown as jest.Mock;
mockRegistration.mockResolvedValue(sdkRegistrationResult);
const mockUserDecryptionOpts = new UserDecryptionOptions({ hasMasterPassword: false });
userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(
of(mockUserDecryptionOpts),
);
});
it("should successfully initialize JIT password user", async () => {
await sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
expect(organizationApiService.getKeys).toHaveBeenCalledWith(credentials.orgId);
expect(registerSdkService.registerClient$).toHaveBeenCalledWith(userId);
expect(mockRegistration).toHaveBeenCalledWith(
expect.objectContaining({
org_id: credentials.orgId,
org_public_key: orgKeys.publicKey,
master_password: credentials.newPassword,
master_password_hint: credentials.newPasswordHint,
salt: credentials.salt,
organization_sso_identifier: credentials.orgSsoIdentifier,
user_id: userId,
reset_password_enroll: credentials.resetPasswordAutoEnroll,
}),
);
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith(
sdkRegistrationResult.account_cryptographic_state,
userId,
);
expect(masterPasswordService.setForceSetPasswordReason).toHaveBeenCalledWith(
ForceSetPasswordReason.None,
userId,
);
expect(masterPasswordService.setMasterPasswordUnlockData).toHaveBeenCalledWith(
MasterPasswordUnlockData.fromSdk(sdkRegistrationResult.master_password_unlock),
userId,
);
expect(keyService.setUserKey).toHaveBeenCalledWith(
SymmetricCryptoKey.fromString(sdkRegistrationResult.user_key) as UserKey,
userId,
);
// Verify legacy state updates below
expect(userDecryptionOptionsService.userDecryptionOptionsById$).toHaveBeenCalledWith(userId);
expect(userDecryptionOptionsService.setUserDecryptionOptionsById).toHaveBeenCalledWith(
userId,
expect.objectContaining({ hasMasterPassword: true }),
);
expect(kdfConfigService.setKdfConfig).toHaveBeenCalledWith(
userId,
fromSdkKdfConfig(sdkRegistrationResult.master_password_unlock.kdf),
);
expect(masterPasswordService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
new EncString(sdkRegistrationResult.master_password_unlock.masterKeyWrappedUserKey),
userId,
);
expect(masterPasswordService.setLegacyMasterKeyFromUnlockData).toHaveBeenCalledWith(
credentials.newPassword,
MasterPasswordUnlockData.fromSdk(sdkRegistrationResult.master_password_unlock),
userId,
);
});
describe("input validation", () => {
it.each([
"newPasswordHint",
"orgSsoIdentifier",
"orgId",
"resetPasswordAutoEnroll",
"newPassword",
"salt",
])("should throw error when %s is null", async (field) => {
const invalidCredentials = {
...credentials,
[field]: null,
} as unknown as InitializeJitPasswordCredentials;
const promise = sut.initializePasswordJitPasswordUserV2Encryption(
invalidCredentials,
userId,
);
await expect(promise).rejects.toThrow(`${field} is required.`);
expect(organizationApiService.getKeys).not.toHaveBeenCalled();
expect(registerSdkService.registerClient$).not.toHaveBeenCalled();
});
it("should throw error when userId is null", async () => {
const nullUserId = null as unknown as UserId;
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, nullUserId);
await expect(promise).rejects.toThrow("User ID is required.");
expect(organizationApiService.getKeys).not.toHaveBeenCalled();
});
});
describe("organization API error handling", () => {
it("should throw when organizationApiService.getKeys returns null", async () => {
organizationApiService.getKeys.mockResolvedValue(
null as unknown as OrganizationKeysResponse,
);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("Organization keys response is null.");
expect(organizationApiService.getKeys).toHaveBeenCalledWith(credentials.orgId);
expect(registerSdkService.registerClient$).not.toHaveBeenCalled();
});
it("should throw when organizationApiService.getKeys rejects", async () => {
const apiError = new Error("API network error");
organizationApiService.getKeys.mockRejectedValue(apiError);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("API network error");
expect(registerSdkService.registerClient$).not.toHaveBeenCalled();
});
});
describe("SDK error handling", () => {
it("should throw when SDK is not available", async () => {
organizationApiService.getKeys.mockResolvedValue(orgKeys);
registerSdkService.registerClient$.mockReturnValue(
of(null) as unknown as Observable<Rc<BitwardenClient>>,
);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("SDK not available");
});
it("should throw when SDK registration fails", async () => {
const sdkError = new Error("SDK crypto operation failed");
organizationApiService.getKeys.mockResolvedValue(orgKeys);
mockRegistration.mockRejectedValue(sdkError);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("SDK crypto operation failed");
});
});
it("should throw when account_cryptographic_state is not V2", async () => {
const invalidResult = {
...sdkRegistrationResult,
account_cryptographic_state: { V1: {} } as unknown as WrappedAccountCryptographicState,
};
mockRegistration.mockResolvedValue(invalidResult);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("Unexpected V2 account cryptographic state");
});
});
});

View File

@@ -21,14 +21,17 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { assertTruthy, assertNonNullish } from "@bitwarden/common/auth/utils";
import { assertNonNullish, assertTruthy } from "@bitwarden/common/auth/utils";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { HashPurpose } from "@bitwarden/common/platform/enums";
import { SyncService } from "@bitwarden/common/platform/sync";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import {
AnonLayoutWrapperDataService,
ButtonModule,
@@ -36,9 +39,11 @@ import {
DialogService,
ToastService,
} from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { I18nPipe } from "@bitwarden/ui-common";
import {
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordService,
SetInitialPasswordTdeOffboardingCredentials,
@@ -73,6 +78,7 @@ export class SetInitialPasswordComponent implements OnInit {
private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,
private dialogService: DialogService,
private i18nService: I18nService,
private keyService: KeyService,
private logoutService: LogoutService,
private logService: LogService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
@@ -86,6 +92,7 @@ export class SetInitialPasswordComponent implements OnInit {
private syncService: SyncService,
private toastService: ToastService,
private validationService: ValidationService,
private configService: ConfigService,
) {}
async ngOnInit() {
@@ -101,6 +108,107 @@ export class SetInitialPasswordComponent implements OnInit {
this.initializing = false;
}
protected async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
this.submitting = true;
switch (this.userType) {
case SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER: {
/**
* "KM flag" = EnableAccountEncryptionV2JitPasswordRegistration
* "Auth flag" = PM27086_UpdateAuthenticationApisForInputPassword (checked in InputPasswordComponent and
* passed through via PasswordInputResult)
*
* Flag unwinding for this specific `case` will depend on which flag gets unwound first:
* - If KM flag gets unwound first, remove all code (in this `case`) after the call
* to setInitialPasswordJitMPUserV2Encryption(), as the V2Encryption method is the
* end-goal for this `case`.
* - If Auth flag gets unwound first (in PM-28143), keep the KM code & early return,
* but unwind the auth flagging logic and then remove the method call marked with
* the "Default Scenario" comment.
*/
const accountEncryptionV2 = await this.configService.getFeatureFlag(
FeatureFlag.EnableAccountEncryptionV2JitPasswordRegistration,
);
// Scenario 1: KM flag ON
if (accountEncryptionV2) {
await this.setInitialPasswordJitMPUserV2Encryption(passwordInputResult);
return;
}
// Scenario 2: KM flag OFF, Auth flag ON
if (passwordInputResult.newApisWithInputPasswordFlagEnabled) {
/**
* If the Auth flag is enabled, it means the InputPasswordComponent will not emit a newMasterKey,
* newServerMasterKeyHash, and newLocalMasterKeyHash. So we must create them here and add them late
* to the PasswordInputResult before calling setInitialPassword().
*
* This is a temporary state. The end-goal will be to use KM's V2Encryption method above.
*/
const ctx = "Could not set initial password.";
assertTruthy(passwordInputResult.newPassword, "newPassword", ctx);
assertNonNullish(passwordInputResult.kdfConfig, "kdfConfig", ctx);
assertTruthy(this.email, "email", ctx);
const newMasterKey = await this.keyService.makeMasterKey(
passwordInputResult.newPassword,
this.email.trim().toLowerCase(),
passwordInputResult.kdfConfig,
);
const newServerMasterKeyHash = await this.keyService.hashMasterKey(
passwordInputResult.newPassword,
newMasterKey,
HashPurpose.ServerAuthorization,
);
const newLocalMasterKeyHash = await this.keyService.hashMasterKey(
passwordInputResult.newPassword,
newMasterKey,
HashPurpose.LocalAuthorization,
);
passwordInputResult.newMasterKey = newMasterKey;
passwordInputResult.newServerMasterKeyHash = newServerMasterKeyHash;
passwordInputResult.newLocalMasterKeyHash = newLocalMasterKeyHash;
await this.setInitialPassword(passwordInputResult); // passwordInputResult masterKey properties generated on the SetInitialPasswordComponent (just above)
return;
}
// Default Scenario: both flags OFF
await this.setInitialPassword(passwordInputResult); // passwordInputResult masterKey properties generated on the InputPasswordComponent (default)
break;
}
case SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP:
await this.setInitialPassword(passwordInputResult);
break;
case SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER:
await this.setInitialPasswordTdeOffboarding(passwordInputResult);
break;
default:
this.logService.error(
`Unexpected user type: ${this.userType}. Could not set initial password.`,
);
this.validationService.showError("Unexpected user type. Could not set initial password.");
}
}
protected async logout() {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "logOut" },
content: { key: "logOutConfirmation" },
acceptButtonText: { key: "logOut" },
type: "warning",
});
if (confirmed) {
this.messagingService.send("logout");
}
}
private async establishUserType() {
if (!this.userId) {
throw new Error("userId not found. Could not determine user type.");
@@ -189,25 +297,45 @@ export class SetInitialPasswordComponent implements OnInit {
}
}
protected async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
this.submitting = true;
private async setInitialPasswordJitMPUserV2Encryption(passwordInputResult: PasswordInputResult) {
const ctx = "Could not set initial password for SSO JIT master password encryption user.";
assertTruthy(passwordInputResult.newPassword, "newPassword", ctx);
assertTruthy(passwordInputResult.salt, "salt", ctx);
assertTruthy(this.orgSsoIdentifier, "orgSsoIdentifier", ctx);
assertTruthy(this.orgId, "orgId", ctx);
assertTruthy(this.userId, "userId", ctx);
assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", ctx); // can have an empty string as a valid value, so check non-nullish
assertNonNullish(this.resetPasswordAutoEnroll, "resetPasswordAutoEnroll", ctx); // can have `false` as a valid value, so check non-nullish
switch (this.userType) {
case SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER:
case SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP:
await this.setInitialPassword(passwordInputResult);
break;
case SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER:
await this.setInitialPasswordTdeOffboarding(passwordInputResult);
break;
default:
this.logService.error(
`Unexpected user type: ${this.userType}. Could not set initial password.`,
);
this.validationService.showError("Unexpected user type. Could not set initial password.");
try {
const credentials: InitializeJitPasswordCredentials = {
newPasswordHint: passwordInputResult.newPasswordHint,
orgSsoIdentifier: this.orgSsoIdentifier,
orgId: this.orgId as OrganizationId,
resetPasswordAutoEnroll: this.resetPasswordAutoEnroll,
newPassword: passwordInputResult.newPassword,
salt: passwordInputResult.salt,
};
await this.setInitialPasswordService.initializePasswordJitPasswordUserV2Encryption(
credentials,
this.userId,
);
this.showSuccessToastByUserType();
this.submitting = false;
await this.router.navigate(["vault"]);
} catch (e) {
this.logService.error("Error setting initial password", e);
this.validationService.showError(e);
this.submitting = false;
}
}
/**
* @deprecated To be removed in PM-28143
*/
private async setInitialPassword(passwordInputResult: PasswordInputResult) {
const ctx = "Could not set initial password.";
assertTruthy(passwordInputResult.newMasterKey, "newMasterKey", ctx);
@@ -307,17 +435,4 @@ export class SetInitialPasswordComponent implements OnInit {
});
}
}
protected async logout() {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "logOut" },
content: { key: "logOutConfirmation" },
acceptButtonText: { key: "logOut" },
type: "warning",
});
if (confirmed) {
this.messagingService.send("logout");
}
}
}

View File

@@ -1,5 +1,5 @@
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { MasterKey } from "@bitwarden/common/types/key";
import { KdfConfig } from "@bitwarden/key-management";
@@ -61,6 +61,24 @@ export interface SetInitialPasswordTdeOffboardingCredentials {
newPasswordHint: string;
}
/**
* Credentials required to initialize a just-in-time (JIT) provisioned user with a master password.
*/
export interface InitializeJitPasswordCredentials {
/** Hint for the new master password */
newPasswordHint: string;
/** SSO identifier for the organization */
orgSsoIdentifier: string;
/** Organization ID */
orgId: OrganizationId;
/** Whether to auto-enroll the user in account recovery (reset password) */
resetPasswordAutoEnroll: boolean;
/** The new master password */
newPassword: string;
/** Master password salt (typically the user's email) */
salt: MasterPasswordSalt;
}
/**
* Handles setting an initial password for an existing authed user.
*
@@ -69,6 +87,8 @@ export interface SetInitialPasswordTdeOffboardingCredentials {
*/
export abstract class SetInitialPasswordService {
/**
* @deprecated To be removed in PM-28143
*
* Sets an initial password for an existing authed user who is either:
* - {@link SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER}
* - {@link SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP}
@@ -95,4 +115,14 @@ export abstract class SetInitialPasswordService {
credentials: SetInitialPasswordTdeOffboardingCredentials,
userId: UserId,
) => Promise<void>;
/**
* Initializes a JIT-provisioned user's cryptographic state and enrolls them in master password unlock.
* @param credentials The credentials needed to initialize the JIT password user
* @param userId The account userId
*/
abstract initializePasswordJitPasswordUserV2Encryption(
credentials: InitializeJitPasswordCredentials,
userId: UserId,
): Promise<void>;
}

View File

@@ -50,6 +50,7 @@
<!-- Button space (always reserved) -->
<div class="tw-my-5 tw-h-12">
<button
cdkFocusInitial
bitButton
[buttonType]="cardDetails.button.type"
[block]="true"

View File

@@ -1,10 +1,17 @@
import { SubscriptionCadence } from "@bitwarden/common/billing/types/subscription-pricing-tier";
import { ButtonType } from "@bitwarden/components";
import { BitwardenIcon, ButtonType } from "@bitwarden/components";
export type SubscriptionPricingCardDetails = {
title: string;
tagline: string;
price?: { amount: number; cadence: SubscriptionCadence };
button: { text: string; type: ButtonType; icon?: { type: string; position: "before" | "after" } };
price?: {
amount: number;
cadence: "month" | "monthly" | "year" | "annually";
showPerUser?: boolean;
};
button: {
text: string;
type: ButtonType;
icon?: { type: BitwardenIcon; position: "before" | "after" };
};
features: string[];
};

View File

@@ -11,7 +11,7 @@ import {
DialogModule,
FormFieldModule,
IconButtonModule,
IconModule,
SvgModule,
LinkModule,
MenuModule,
RadioButtonModule,
@@ -73,9 +73,9 @@ import { IconComponent } from "./vault/components/icon.component";
MenuModule,
NoItemsModule,
IconButtonModule,
IconModule,
SvgModule,
LinkModule,
IconModule,
SvgModule,
TextDragDirective,
CopyClickDirective,
A11yTitleDirective,

View File

@@ -108,7 +108,7 @@ import { UserVerificationService as UserVerificationServiceAbstraction } from "@
import { WebAuthnLoginApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-api.service.abstraction";
import { WebAuthnLoginPrfKeyServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { SendTokenService, DefaultSendTokenService } from "@bitwarden/common/auth/send-access";
import { DefaultSendTokenService, SendTokenService } from "@bitwarden/common/auth/send-access";
import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AnonymousHubService } from "@bitwarden/common/auth/services/anonymous-hub.service";
@@ -131,10 +131,10 @@ import { WebAuthnLoginApiService } from "@bitwarden/common/auth/services/webauth
import { WebAuthnLoginPrfKeyService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-prf-key.service";
import { WebAuthnLoginService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login.service";
import {
TwoFactorApiService,
DefaultTwoFactorApiService,
TwoFactorService,
DefaultTwoFactorService,
TwoFactorApiService,
TwoFactorService,
} from "@bitwarden/common/auth/two-factor";
import {
AutofillSettingsService,
@@ -208,8 +208,8 @@ import { PinService } from "@bitwarden/common/key-management/pin/pin.service.imp
import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service";
import { DefaultSecurityStateService } from "@bitwarden/common/key-management/security-state/services/security-state.service";
import {
SendPasswordService,
DefaultSendPasswordService,
SendPasswordService,
} from "@bitwarden/common/key-management/sends";
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
import {
@@ -303,6 +303,7 @@ import {
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service";
import { CipherRiskService } from "@bitwarden/common/vault/abstractions/cipher-risk.service";
import { CipherSdkService } from "@bitwarden/common/vault/abstractions/cipher-sdk.service";
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
@@ -321,6 +322,7 @@ import {
CipherAuthorizationService,
DefaultCipherAuthorizationService,
} from "@bitwarden/common/vault/services/cipher-authorization.service";
import { DefaultCipherSdkService } from "@bitwarden/common/vault/services/cipher-sdk.service";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service";
import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service";
@@ -387,12 +389,12 @@ import { SafeInjectionToken } from "@bitwarden/ui-common";
// eslint-disable-next-line no-restricted-imports
import { PasswordRepromptService } from "@bitwarden/vault";
import {
DefaultVaultExportApiService,
IndividualVaultExportService,
IndividualVaultExportServiceAbstraction,
DefaultVaultExportApiService,
VaultExportApiService,
OrganizationVaultExportService,
OrganizationVaultExportServiceAbstraction,
VaultExportApiService,
VaultExportService,
VaultExportServiceAbstraction,
} from "@bitwarden/vault-export-core";
@@ -590,6 +592,11 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultDomainSettingsService,
deps: [StateProvider, PolicyServiceAbstraction, AccountService],
}),
safeProvider({
provide: CipherSdkService,
useClass: DefaultCipherSdkService,
deps: [SdkService, LogService],
}),
safeProvider({
provide: CipherServiceAbstraction,
useFactory: (
@@ -607,6 +614,7 @@ const safeProviders: SafeProvider[] = [
logService: LogService,
cipherEncryptionService: CipherEncryptionService,
messagingService: MessagingServiceAbstraction,
cipherSdkService: CipherSdkService,
) =>
new CipherService(
keyService,
@@ -623,6 +631,7 @@ const safeProviders: SafeProvider[] = [
logService,
cipherEncryptionService,
messagingService,
cipherSdkService,
),
deps: [
KeyService,
@@ -639,6 +648,7 @@ const safeProviders: SafeProvider[] = [
LogService,
CipherEncryptionService,
MessagingServiceAbstraction,
CipherSdkService,
],
}),
safeProvider({
@@ -757,12 +767,13 @@ const safeProviders: SafeProvider[] = [
AccountServiceAbstraction,
StateProvider,
KdfConfigService,
AccountCryptographicStateService,
],
}),
safeProvider({
provide: SecurityStateService,
useClass: DefaultSecurityStateService,
deps: [StateProvider],
deps: [AccountCryptographicStateService],
}),
safeProvider({
provide: RestrictedItemTypesService,
@@ -848,6 +859,7 @@ const safeProviders: SafeProvider[] = [
KeyGenerationService,
SendStateProviderAbstraction,
EncryptService,
ConfigService,
],
}),
safeProvider({
@@ -886,7 +898,7 @@ const safeProviders: SafeProvider[] = [
FolderApiServiceAbstraction,
InternalOrganizationServiceAbstraction,
SendApiServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
InternalUserDecryptionOptionsServiceAbstraction,
AvatarServiceAbstraction,
LOGOUT_CALLBACK,
BillingAccountProfileStateService,
@@ -1583,6 +1595,7 @@ const safeProviders: SafeProvider[] = [
OrganizationUserApiService,
InternalUserDecryptionOptionsServiceAbstraction,
AccountCryptographicStateService,
RegisterSdkService,
],
}),
safeProvider({
@@ -1691,6 +1704,7 @@ const safeProviders: SafeProvider[] = [
SdkService,
ApiServiceAbstraction,
ConfigService,
AccountCryptographicStateService,
],
}),
safeProvider({

View File

@@ -27,13 +27,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { Send } from "@bitwarden/common/tools/send/models/domain/send";
import { SendFileView } from "@bitwarden/common/tools/send/models/view/send-file.view";
import { SendTextView } from "@bitwarden/common/tools/send/models/view/send-text.view";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
import { DialogService, ToastService } from "@bitwarden/components";

View File

@@ -20,10 +20,10 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
import { DialogService, ToastService } from "@bitwarden/components";
@@ -78,7 +78,7 @@ export class SendComponent implements OnInit, OnDestroy {
protected ngZone: NgZone,
protected searchService: SearchService,
protected policyService: PolicyService,
private logService: LogService,
protected logService: LogService,
protected sendApiService: SendApiService,
protected dialogService: DialogService,
protected toastService: ToastService,

View File

@@ -1,8 +1,6 @@
import { Observable } 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 { CollectionView } from "@bitwarden/admin-console/common";
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { UserId } from "@bitwarden/common/types/guid";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";

View File

@@ -94,7 +94,7 @@ export class VaultItemsComponent<C extends CipherViewLike> implements OnDestroy
protected cipherService: CipherService,
protected accountService: AccountService,
protected restrictedItemTypesService: RestrictedItemTypesService,
private configService: ConfigService,
protected configService: ConfigService,
) {
this.subscribeToCiphers();
@@ -194,7 +194,7 @@ export class VaultItemsComponent<C extends CipherViewLike> implements OnDestroy
return this.searchService.searchCiphers(
userId,
searchText,
[filter, this.deletedFilter, this.archivedFilter, restrictedTypeFilter],
[filter, restrictedTypeFilter],
allCiphers,
);
}),

View File

@@ -1,4 +1,8 @@
// Note: Nudge related code is exported from `libs/angular` because it is consumed by multiple
// `libs/*` packages. Exporting from the `libs/vault` package creates circular dependencies.
export { NudgesService, NudgeStatus, NudgeType } from "./services/nudges.service";
export { AUTOFILL_NUDGE_SERVICE } from "./services/nudge-injection-tokens";
export {
AUTOFILL_NUDGE_SERVICE,
AUTO_CONFIRM_NUDGE_SERVICE,
} from "./services/nudge-injection-tokens";
export { AutoConfirmNudgeService } from "./services/custom-nudges-services";

View File

@@ -0,0 +1,204 @@
# Custom Nudge Services
This folder contains custom implementations of `SingleNudgeService` that provide specialized logic for determining when nudges should be shown or dismissed.
## Architecture Overview
### Core Components
- **`NudgesService`** (`../nudges.service.ts`) - The main service that components use to check nudge status and dismiss nudges
- **`SingleNudgeService`** - Interface that all nudge services implement
- **`DefaultSingleNudgeService`** - Base implementation that stores dismissed state in user state
- **Custom nudge services** - Specialized implementations with additional logic
### How It Works
1. Components call `NudgesService.showNudgeSpotlight$()` or `showNudgeBadge$()` with a `NudgeType`
2. `NudgesService` routes to the appropriate custom nudge service (or falls back to `DefaultSingleNudgeService`)
3. The custom service returns a `NudgeStatus` indicating if the badge/spotlight should be shown
4. Custom services can combine the persisted dismissed state with dynamic conditions (e.g., account age, vault contents)
### NudgeStatus
```typescript
type NudgeStatus = {
hasBadgeDismissed: boolean; // True if the badge indicator should be hidden
hasSpotlightDismissed: boolean; // True if the spotlight/callout should be hidden
};
```
## Service Categories
### Universal Services
These services work on **all clients** (browser, web, desktop) and use `@Injectable({ providedIn: "root" })`.
| Service | Purpose |
| --------------------------------- | ---------------------------------------------------------------------- |
| `NewAccountNudgeService` | Auto-dismisses after account is 30 days old |
| `NewItemNudgeService` | Checks cipher counts for "add first item" nudges |
| `HasItemsNudgeService` | Checks if vault has items |
| `EmptyVaultNudgeService` | Checks empty vault state |
| `AccountSecurityNudgeService` | Checks security settings (PIN, biometrics) |
| `VaultSettingsImportNudgeService` | Checks import status |
| `NoOpNudgeService` | Always returns dismissed (used as fallback for client specific nudges) |
### Client-Specific Services
These services require **platform-specific features** and must be explicitly registered in each client that supports them.
| Service | Clients | Requires |
| ----------------------------- | ------------ | -------------------------------------- |
| `AutoConfirmNudgeService` | Browser only | `AutomaticUserConfirmationService` |
| `BrowserAutofillNudgeService` | Browser only | `BrowserApi` (lives in `apps/browser`) |
## Adding a New Nudge Service
### Step 1: Determine if Universal or Client-Specific
**Universal** - If your service only depends on:
- `StateProvider`
- Services available in all clients (e.g., `CipherService`, `OrganizationService`)
**Client-Specific** - If your service depends on:
- Browser APIs (`BrowserApi`, autofill services)
- Services only available in certain clients
- Platform-specific features
### Step 2: Create the Service
#### For Universal Services
```typescript
// my-nudge.service.ts
import { Injectable } from "@angular/core";
import { combineLatest, map, Observable } from "rxjs";
import { StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
import { NudgeStatus, NudgeType } from "../nudges.service";
@Injectable({ providedIn: "root" })
export class MyNudgeService extends DefaultSingleNudgeService {
constructor(
stateProvider: StateProvider,
private myDependency: MyDependency, // Must be available in all clients
) {
super(stateProvider);
}
nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
return combineLatest([
this.getNudgeStatus$(nudgeType, userId), // Gets persisted dismissed state
this.myDependency.someData$,
]).pipe(
map(([persistedStatus, data]) => {
// Return dismissed if user already dismissed OR your condition is met
const autoDismiss = /* your logic */;
return {
hasBadgeDismissed: persistedStatus.hasBadgeDismissed || autoDismiss,
hasSpotlightDismissed: persistedStatus.hasSpotlightDismissed || autoDismiss,
};
}),
);
}
}
```
#### For Client-Specific Services
```typescript
// my-client-specific-nudge.service.ts
import { Injectable } from "@angular/core";
import { combineLatest, map, Observable } from "rxjs";
import { StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
import { NudgeStatus, NudgeType } from "../nudges.service";
@Injectable() // NO providedIn: "root"
export class MyClientSpecificNudgeService extends DefaultSingleNudgeService {
constructor(
stateProvider: StateProvider,
private clientSpecificService: ClientSpecificService,
) {
super(stateProvider);
}
nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
return combineLatest([
this.getNudgeStatus$(nudgeType, userId),
this.clientSpecificService.someData$,
]).pipe(
map(([persistedStatus, data]) => {
const autoDismiss = /* your logic */;
return {
hasBadgeDismissed: persistedStatus.hasBadgeDismissed || autoDismiss,
hasSpotlightDismissed: persistedStatus.hasSpotlightDismissed || autoDismiss,
};
}),
);
}
}
```
### Step 3: Add NudgeType
Add your nudge type to `NudgeType` in `../nudges.service.ts`:
```typescript
export const NudgeType = {
// ... existing types
MyNewNudge: "my-new-nudge",
} as const;
```
### Step 4: Register in NudgesService
#### For Universal Services
Add to `customNudgeServices` map in `../nudges.service.ts`:
```typescript
private customNudgeServices: Partial<Record<NudgeType, SingleNudgeService>> = {
// ... existing
[NudgeType.MyNewNudge]: inject(MyNudgeService),
};
```
#### For Client-Specific Services
1. **Add injection token** in `../nudge-injection-tokens.ts`:
```typescript
export const MY_NUDGE_SERVICE = new InjectionToken<SingleNudgeService>("MyNudgeService");
```
2. **Inject with optional** in `../nudges.service.ts`:
```typescript
private myNudgeService = inject(MY_NUDGE_SERVICE, { optional: true });
private customNudgeServices = {
// ... existing
[NudgeType.MyNewNudge]: this.myNudgeService ?? this.noOpNudgeService,
};
```
3. **Register in each supporting client** (e.g., `apps/browser/src/popup/services/services.module.ts`):
```typescript
import { MY_NUDGE_SERVICE } from "@bitwarden/angular/vault";
safeProvider({
provide: MY_NUDGE_SERVICE as SafeInjectionToken<SingleNudgeService>,
useClass: MyClientSpecificNudgeService,
deps: [StateProvider, ClientSpecificService],
}),
```

View File

@@ -39,7 +39,7 @@ export class AccountSecurityNudgeService extends DefaultSingleNudgeService {
this.getNudgeStatus$(nudgeType, userId),
of(Date.now() - THIRTY_DAYS_MS),
from(this.pinService.isPinSet(userId)),
this.biometricStateService.biometricUnlockEnabled$,
this.biometricStateService.biometricUnlockEnabled$(userId),
this.organizationService.organizations$(userId),
this.policyService.policiesByType$(PolicyType.RemoveUnlockWithPin, userId),
]).pipe(

View File

@@ -1,15 +1,24 @@
import { inject, Injectable } from "@angular/core";
import { Injectable } from "@angular/core";
import { combineLatest, map, Observable } from "rxjs";
import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
import { StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/user-core";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
import { NudgeType, NudgeStatus } from "../nudges.service";
@Injectable({ providedIn: "root" })
/**
* Browser specific nudge service for auto-confirm nudge.
*/
@Injectable()
export class AutoConfirmNudgeService extends DefaultSingleNudgeService {
autoConfirmService = inject(AutomaticUserConfirmationService);
constructor(
stateProvider: StateProvider,
private autoConfirmService: AutomaticUserConfirmationService,
) {
super(stateProvider);
}
nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
return combineLatest([

View File

@@ -1,8 +1,8 @@
import { inject, Injectable } from "@angular/core";
import { combineLatest, from, Observable, of, switchMap } from "rxjs";
import { catchError } from "rxjs/operators";
import { combineLatest, Observable, of, switchMap } from "rxjs";
import { catchError, map } from "rxjs/operators";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -20,11 +20,14 @@ const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
})
export class HasItemsNudgeService extends DefaultSingleNudgeService {
cipherService = inject(CipherService);
vaultProfileService = inject(VaultProfileService);
accountService = inject(AccountService);
logService = inject(LogService);
nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe(
const profileDate$ = this.accountService.activeAccount$.pipe(
map((account) => {
return account?.creationDate ?? new Date();
}),
catchError(() => {
this.logService.error("Error getting profile creation date");
// Default to today to ensure we show the nudge

View File

@@ -1,10 +1,11 @@
import { Injectable, inject } from "@angular/core";
import { Injectable } from "@angular/core";
import { Observable, combineLatest, from, map, of } from "rxjs";
import { catchError } from "rxjs/operators";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { UserId } from "@bitwarden/common/types/guid";
import { StateProvider } from "@bitwarden/state";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
import { NudgeStatus, NudgeType } from "../nudges.service";
@@ -18,8 +19,13 @@ const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
providedIn: "root",
})
export class NewAccountNudgeService extends DefaultSingleNudgeService {
vaultProfileService = inject(VaultProfileService);
logService = inject(LogService);
constructor(
stateProvider: StateProvider,
private vaultProfileService: VaultProfileService,
private logService: LogService,
) {
super(stateProvider);
}
nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe(

View File

@@ -1,4 +1,4 @@
import { inject, Injectable } from "@angular/core";
import { Injectable } from "@angular/core";
import { map, Observable } from "rxjs";
import { StateProvider } from "@bitwarden/common/platform/state";
@@ -22,7 +22,11 @@ export interface SingleNudgeService {
providedIn: "root",
})
export class DefaultSingleNudgeService implements SingleNudgeService {
stateProvider = inject(StateProvider);
protected stateProvider: StateProvider;
constructor(stateProvider: StateProvider) {
this.stateProvider = stateProvider;
}
protected getNudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
return this.stateProvider

View File

@@ -2,6 +2,25 @@ import { InjectionToken } from "@angular/core";
import { SingleNudgeService } from "./default-single-nudge.service";
/**
* Injection tokens for client specific nudge services.
*
* These services require platform-specific features and must be explicitly
* provided by each client that supports them. If not provided, NudgesService
* falls back to NoOpNudgeService.
*
* Client specific services should use constructor injection (not inject())
* to maintain safeProvider type safety.
*
* Universal services use @Injectable({ providedIn: "root" }) and can use inject().
*/
/** Browser: Requires BrowserApi */
export const AUTOFILL_NUDGE_SERVICE = new InjectionToken<SingleNudgeService>(
"AutofillNudgeService",
);
/** Browser: Requires AutomaticUserConfirmationService */
export const AUTO_CONFIRM_NUDGE_SERVICE = new InjectionToken<SingleNudgeService>(
"AutoConfirmNudgeService",
);

View File

@@ -12,11 +12,10 @@ import {
NewItemNudgeService,
AccountSecurityNudgeService,
VaultSettingsImportNudgeService,
AutoConfirmNudgeService,
NoOpNudgeService,
} from "./custom-nudges-services";
import { DefaultSingleNudgeService, SingleNudgeService } from "./default-single-nudge.service";
import { AUTOFILL_NUDGE_SERVICE } from "./nudge-injection-tokens";
import { AUTOFILL_NUDGE_SERVICE, AUTO_CONFIRM_NUDGE_SERVICE } from "./nudge-injection-tokens";
export type NudgeStatus = {
hasBadgeDismissed: boolean;
@@ -63,12 +62,21 @@ export class NudgesService {
// NoOp service that always returns dismissed
private noOpNudgeService = inject(NoOpNudgeService);
// Optional Browser-specific service provided via injection token (not all clients have autofill)
// Client specific services (optional, via injection tokens)
// These services require platform-specific features and fallback to NoOpNudgeService if not provided
private autofillNudgeService = inject(AUTOFILL_NUDGE_SERVICE, { optional: true });
private autoConfirmNudgeService = inject(AUTO_CONFIRM_NUDGE_SERVICE, { optional: true });
/**
* Custom nudge services to use for specific nudge types
* Each nudge type can have its own service to determine when to show the nudge
*
* NOTE: If a custom nudge service requires client specific services/features:
* 1. The custom nudge service must be provided via injection token and marked as optional.
* 2. The custom nudge service must be manually registered with that token in the client(s).
*
* See the README.md in the custom-nudge-services folder for more details on adding custom nudges.
* @private
*/
private customNudgeServices: Partial<Record<NudgeType, SingleNudgeService>> = {
@@ -84,7 +92,7 @@ export class NudgesService {
[NudgeType.NewIdentityItemStatus]: this.newItemNudgeService,
[NudgeType.NewNoteItemStatus]: this.newItemNudgeService,
[NudgeType.NewSshItemStatus]: this.newItemNudgeService,
[NudgeType.AutoConfirmNudge]: inject(AutoConfirmNudgeService),
[NudgeType.AutoConfirmNudge]: this.autoConfirmNudgeService ?? this.noOpNudgeService,
};
/**

View File

@@ -21,6 +21,9 @@ export class VaultProfileService {
* Returns the creation date of the profile.
* Note: `Date`s are mutable in JS, creating a new
* instance is important to avoid unwanted changes.
*
* @deprecated use `creationDate` directly from the `AccountService.activeAccount$` instead,
* PM-31409 will replace all usages of this service.
*/
async getProfileCreationDate(userId: string): Promise<Date> {
if (this.profileCreatedDate && userId === this.userId) {

View File

@@ -2,9 +2,10 @@
// @ts-strict-ignore
import { Directive, EventEmitter, Input, Output } from "@angular/core";
// 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 { CollectionTypes, CollectionView } from "@bitwarden/admin-console/common";
import {
CollectionView,
CollectionTypes,
} from "@bitwarden/common/admin-console/models/collections";
import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node";
import { DynamicTreeNode } from "../models/dynamic-tree-node.model";

View File

@@ -3,9 +3,7 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { firstValueFrom, Observable } 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 { CollectionView } from "@bitwarden/admin-console/common";
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";

View File

@@ -143,6 +143,17 @@ describe("VaultFilter", () => {
expect(result).toBe(true);
});
it("should return true when filtering on unassigned folder via empty string id", () => {
const filterFunction = createFilterFunction({
selectedFolder: true,
selectedFolderId: "",
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
});
describe("given an organizational cipher (with organization and collections)", () => {

View File

@@ -51,15 +51,31 @@ export class VaultFilter {
cipherPassesFilter = CipherViewLikeUtils.isDeleted(cipher);
}
if (this.status === "archive" && cipherPassesFilter) {
cipherPassesFilter = CipherViewLikeUtils.isArchived(cipher);
cipherPassesFilter =
CipherViewLikeUtils.isArchived(cipher) && !CipherViewLikeUtils.isDeleted(cipher);
}
if (this.status !== "archive" && this.status !== "trash" && cipherPassesFilter) {
cipherPassesFilter =
!CipherViewLikeUtils.isArchived(cipher) && !CipherViewLikeUtils.isDeleted(cipher);
}
if (this.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = CipherViewLikeUtils.getType(cipher) === this.cipherType;
}
if (this.selectedFolder && this.selectedFolderId == null && cipherPassesFilter) {
cipherPassesFilter = cipher.folderId == null;
if (
this.selectedFolder &&
(this.selectedFolderId == null || this.selectedFolderId === "") &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId == null || cipher.folderId === "";
}
if (this.selectedFolder && this.selectedFolderId != null && cipherPassesFilter) {
if (
this.selectedFolder &&
this.selectedFolderId != null &&
this.selectedFolderId !== "" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.selectedFolderId;
}
if (this.selectedCollection && this.selectedCollectionId == null && cipherPassesFilter) {

View File

@@ -3,14 +3,14 @@ import { firstValueFrom, from, map, mergeMap, Observable, switchMap, take } from
// 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,
CollectionTypes,
CollectionView,
} from "@bitwarden/admin-console/common";
import { CollectionService } from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import {
CollectionView,
CollectionTypes,
} from "@bitwarden/common/admin-console/models/collections";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
@@ -83,7 +83,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
const ciphers = await this.cipherService.getAllDecrypted(userId);
const orgCiphers = ciphers.filter((c) => c.organizationId == organizationId);
folders = storedFolders.filter(
(f) => orgCiphers.some((oc) => oc.folderId == f.id) || f.id == null,
(f) => orgCiphers.some((oc) => oc.folderId == f.id) || !f.id,
);
}

View File

@@ -8,9 +8,9 @@ This lib contains assets used by the Bitwarden clients. Unused assets are tree-s
### SVGs
SVGs intended to be used with the `bit-icon` component live in `src/svgs`. These SVGs are built with the `icon-service` for security reasons. These SVGs can be viewed in our Component Library [Icon Story](https://components.bitwarden.com/?path=/story/component-library-icon--default).
SVGs intended to be used with the `bit-svg` component live in `src/svgs`. These SVGs are built with the `svg` function for security reasons. These SVGs can be viewed in our Component Library [SVG Story](https://components.bitwarden.com/?path=/story/component-library-svg--default).
When adding a new SVG, follow the instructions in our Component Library: [SVG Icon Docs](https://components.bitwarden.com/?path=/docs/component-library-icon--docs)
When adding a new SVG, follow the instructions in our Component Library: [SVG Docs](https://components.bitwarden.com/?path=/docs/component-library-svg--docs)
When importing an SVG in one of the clients:
`import { ExampleSvg } from "@bitwarden/assets/svg";`

View File

@@ -1,25 +0,0 @@
class Icon {
constructor(readonly svg: string) {}
}
// We only export the type to prohibit the creation of Icons without using
// the `svgIcon` template literal tag.
export type { Icon };
export function isIcon(icon: unknown): icon is Icon {
return icon instanceof Icon;
}
export class DynamicContentNotAllowedError extends Error {
constructor() {
super("Dynamic content in icons is not allowed due to risk of user-injected XSS.");
}
}
export function svgIcon(strings: TemplateStringsArray, ...values: unknown[]): Icon {
if (values.length > 0) {
throw new DynamicContentNotAllowedError();
}
return new Icon(strings[0]);
}

View File

@@ -1,2 +1,2 @@
export * from "./svgs";
export * from "./icon-service";
export * from "./svg";

View File

@@ -1,5 +1,5 @@
import * as IconExports from "./icon-service";
import { DynamicContentNotAllowedError, isIcon, svgIcon } from "./icon-service";
import * as IconExports from "./svg";
import { DynamicContentNotAllowedError, isBitSvg, svg } from "./svg";
describe("Icon", () => {
it("exports should not expose Icon class", () => {
@@ -8,13 +8,13 @@ describe("Icon", () => {
describe("isIcon", () => {
it("should return true when input is icon", () => {
const result = isIcon(svgIcon`icon`);
const result = isBitSvg(svg`icon`);
expect(result).toBe(true);
});
it("should return false when input is not an icon", () => {
const result = isIcon({ svg: "not an icon" });
const result = isBitSvg({ svg: "not an icon" });
expect(result).toBe(false);
});
@@ -24,13 +24,13 @@ describe("Icon", () => {
it("should throw when attempting to create dynamic icons", () => {
const dynamic = "some user input";
const f = () => svgIcon`static and ${dynamic}`;
const f = () => svg`static and ${dynamic}`;
expect(f).toThrow(DynamicContentNotAllowedError);
});
it("should return svg content when supplying icon with svg string", () => {
const icon = svgIcon`safe static content`;
const icon = svg`safe static content`;
expect(icon.svg).toBe("safe static content");
});

View File

@@ -0,0 +1,25 @@
class BitSvg {
constructor(readonly svg: string) {}
}
// We only export the type to prohibit the creation of Svgs without using
// the `svg` template literal tag.
export type { BitSvg };
export function isBitSvg(svgContent: unknown): svgContent is BitSvg {
return svgContent instanceof BitSvg;
}
export class DynamicContentNotAllowedError extends Error {
constructor() {
super("Dynamic content in icons is not allowed due to risk of user-injected XSS.");
}
}
export function svg(strings: TemplateStringsArray, ...values: unknown[]): BitSvg {
if (values.length > 0) {
throw new DynamicContentNotAllowedError();
}
return new BitSvg(strings[0]);
}

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const AccountWarning = svgIcon`
export const AccountWarning = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 12.75 96 72">
<path class="tw-fill-illustration-bg-primary" d="M0 18.512a5.76 5.76 0 0 1 5.76-5.76h54.48a5.76 5.76 0 0 1 5.76 5.76v38.48a5.76 5.76 0 0 1-5.76 5.76H5.76A5.76 5.76 0 0 1 0 56.992v-38.48Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M60.24 14.672H5.76a3.84 3.84 0 0 0-3.84 3.84v38.48a3.84 3.84 0 0 0 3.84 3.84h54.48a3.84 3.84 0 0 0 3.84-3.84v-38.48a3.84 3.84 0 0 0-3.84-3.84Zm-54.48-1.92A5.76 5.76 0 0 0 0 18.512v38.48a5.76 5.76 0 0 0 5.76 5.76h54.48a5.76 5.76 0 0 0 5.76-5.76v-38.48a5.76 5.76 0 0 0-5.76-5.76H5.76Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const ActiveSendIcon = svgIcon`
export const ActiveSendIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="5 4 87 87">
<path class="tw-fill-illustration-bg-primary" d="M90 48c0 23.196-18.804 42-42 42S6 71.196 6 48 24.804 6 48 6s42 18.804 42 42Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M48 7C25.356 7 7 25.356 7 48s18.356 41 41 41 41-18.356 41-41S70.644 7 48 7ZM5 48C5 24.252 24.252 5 48 5s43 19.252 43 43-19.252 43-43 43S5 71.748 5 48Z" clip-rule="evenodd"/>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const BackgroundRightIllustration = svgIcon`
export const BackgroundRightIllustration = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 506 297">
<g clip-path="url(#right-bg-illustration-clip)">
<path class="tw-fill-illustration-bg-primary" d="M45.673 294.742v-29.124l22.323 16.198-15.395 15.75a4.04 4.04 0 0 1-6.928-2.824Z"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const BitwardenIcon = svgIcon`
export const BitwardenIcon = svg`
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_11934_25684)">
<path d="M17.3333 0H2.66667C1.19391 0 0 1.19391 0 2.66667V17.3333C0 18.8061 1.19391 20 2.66667 20H17.3333C18.8061 20 20 18.8061 20 17.3333V2.66667C20 1.19391 18.8061 0 17.3333 0Z" class="tw-fill-bw-blue" />

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const BitwardenLogo = svgIcon`
export const BitwardenLogo = svg`
<svg viewBox="0 0 290 45" xmlns="http://www.w3.org/2000/svg">
<title>Bitwarden</title>
<path class="tw-fill-marketing-logo" fill-rule="evenodd" clip-rule="evenodd" d="M69.799 10.713c3.325 0 5.911 1.248 7.811 3.848 1.9 2.549 2.85 6.033 2.85 10.453 0 4.576-.95 8.113-2.902 10.61-1.953 2.547-4.592 3.743-7.918 3.743-3.325 0-5.858-1.144-7.758-3.536h-.528l-1.003 2.444a.976.976 0 0 1-.897.572H55.23a.94.94 0 0 1-.95-.936V1.352a.94.94 0 0 1 .95-.936h5.7a.94.94 0 0 1 .95.936v8.009c0 1.144-.105 2.964-.316 5.46h.317c1.741-2.704 4.433-4.108 7.917-4.108Zm-2.428 6.084c-1.847 0-3.273.572-4.17 1.717-.844 1.144-1.32 3.068-1.32 5.668v.832c0 2.964.423 5.097 1.32 6.345.897 1.248 2.322 1.924 4.275 1.924 1.531 0 2.85-.728 3.748-2.184.897-1.404 1.372-3.537 1.372-6.189 0-2.704-.475-4.732-1.372-6.084-.95-1.352-2.27-2.029-3.853-2.029ZM93.022 38.9h-5.7a.94.94 0 0 1-.95-.936V12.221a.94.94 0 0 1 .95-.936h5.7a.94.94 0 0 1 .95.936v25.69c.053.468-.422.988-.95.988Zm20.849-5.564c1.108 0 2.428-.208 4.011-.624a.632.632 0 0 1 .792.624v4.316a.64.64 0 0 1-.37.572c-1.794.728-4.064 1.092-6.597 1.092-3.062 0-5.278-.728-6.651-2.288-1.372-1.508-2.111-3.796-2.111-6.812V16.953h-3.008c-.37 0-.634-.26-.634-.624v-2.444c0-.052.053-.104.053-.156l4.17-2.444 2.058-5.408c.106-.26.317-.417.581-.417h3.8c.369 0 .633.26.633.625v5.252h7.548c.158 0 .317.156.317.312v4.68c0 .364-.264.624-.634.624h-7.178v13.21c0 1.04.317 1.872.897 2.34.528.572 1.373.832 2.323.832Zm35.521 5.564c-.739 0-1.319-.468-1.636-1.144l-5.595-16.797c-.369-1.196-.844-3.016-1.478-5.357h-.158l-.528 1.873-1.108 3.536-5.753 16.797c-.211.676-.845 1.092-1.584 1.092a1.628 1.628 0 0 1-1.583-1.196l-7.02-24.182c-.211-.728.369-1.508 1.214-1.508h.158c.528 0 1.003.364 1.161.884l4.117 14.717c1.003 3.849 1.689 6.657 2.006 8.53h.158c.95-3.85 1.689-6.397 2.164-7.698l5.331-15.393c.211-.624.792-1.04 1.531-1.04.686 0 1.267.416 1.478 1.04l4.961 15.29c1.214 3.9 1.953 6.396 2.217 7.696h.158c.159-1.04.792-3.952 2.006-8.633l3.958-14.509c.159-.52.634-.884 1.162-.884.791 0 1.372.728 1.161 1.508l-6.651 24.182c-.211.728-.844 1.196-1.636 1.196h-.211Zm31.352 0a.962.962 0 0 1-.95-.832l-.475-3.432h-.264c-1.372 1.716-2.745 2.964-4.223 3.692-1.425.728-3.166 1.04-5.119 1.04-2.692 0-4.751-.676-6.228-2.028-1.32-1.196-2.059-2.808-2.164-4.836-.212-2.704.95-5.305 3.166-6.813 2.27-1.456 5.437-2.34 9.712-2.34l5.173-.156v-1.768c0-2.6-.528-4.473-1.637-5.773-1.108-1.3-2.744-1.924-5.067-1.924-2.216 0-4.433.52-6.756 1.612-.58.26-1.266 0-1.53-.572s0-1.248.58-1.456c2.639-1.04 5.226-1.612 7.865-1.612 3.008 0 5.225.78 6.756 2.34 1.478 1.508 2.216 3.953 2.216 7.125v16.901c-.052.312-.527.832-1.055.832Zm-10.926-1.768c2.956 0 5.226-.832 6.862-2.444 1.689-1.612 2.533-3.952 2.533-6.813v-2.6l-4.75.208c-3.853.156-6.545.78-8.234 1.768-1.636.988-2.481 2.6-2.481 4.68 0 1.665.528 3.017 1.531 3.953 1.161.78 2.639 1.248 4.539 1.248Zm31.246-25.638c.792 0 1.584.052 2.481.156a1.176 1.176 0 0 1 1.003 1.352c-.106.624-.739.988-1.372.884-.792-.104-1.584-.208-2.375-.208-2.323 0-4.223.988-5.701 2.912-1.478 1.925-2.217 4.42-2.217 7.333v13.625c0 .676-.527 1.196-1.214 1.196-.686 0-1.213-.52-1.213-1.196V13.105c0-.572.475-1.04 1.055-1.04.581 0 1.056.416 1.056.988l.211 3.848h.158c1.109-1.976 2.323-3.38 3.589-4.16 1.214-.832 2.745-1.248 4.539-1.248Zm18.579 0c1.953 0 3.695.364 5.12 1.04 1.478.676 2.745 1.924 3.853 3.64h.158a122.343 122.343 0 0 1-.158-6.084V1.612c0-.676.528-1.196 1.214-1.196.686 0 1.214.52 1.214 1.196v36.351c0 .468-.37.832-.845.832a.852.852 0 0 1-.844-.78l-.528-3.38h-.211c-2.058 3.068-5.067 4.576-8.92 4.576-3.8 0-6.598-1.144-8.656-3.484-1.953-2.34-3.008-5.668-3.008-10.089 0-4.628.95-8.165 2.955-10.66 2.006-2.237 4.856-3.485 8.656-3.485Zm0 2.236c-3.008 0-5.225 1.04-6.756 3.12-1.478 2.029-2.216 4.993-2.216 8.945 0 7.593 3.008 11.39 9.025 11.39 3.114 0 5.331-.885 6.756-2.653 1.478-1.768 2.164-4.68 2.164-8.737v-.416c0-4.16-.686-7.124-2.164-8.893-1.372-1.872-3.642-2.756-6.809-2.756Zm31.616 25.638c-3.959 0-7.02-1.196-9.289-3.64-2.217-2.392-3.326-5.772-3.326-10.089 0-4.316 1.056-7.748 3.22-10.297 2.164-2.6 5.014-3.9 8.656-3.9 3.167 0 5.753 1.092 7.548 3.276 1.9 2.184 2.797 5.2 2.797 8.997v1.976h-19.634c.052 3.692.897 6.5 2.639 8.477 1.741 1.976 4.169 2.86 7.389 2.86 1.531 0 2.956-.104 4.117-.312.844-.156 1.847-.416 3.061-.832.686-.26 1.425.26 1.425.988 0 .416-.264.832-.686.988-1.267.52-2.481.832-3.589 1.04-1.32.364-2.745.468-4.328.468Zm-.739-25.69c-2.639 0-4.75.832-6.334 2.548-1.583 1.665-2.48 4.16-2.797 7.333h16.89c0-3.068-.686-5.564-2.059-7.28-1.372-1.717-3.272-2.6-5.7-2.6ZM288.733 38.9c-.686 0-1.214-.52-1.214-1.196V21.426c0-2.704-.58-4.68-1.689-5.877-1.214-1.196-2.955-1.872-5.383-1.872-3.273 0-5.648.78-7.126 2.444-1.478 1.613-2.322 4.265-2.322 7.853V37.6c0 .676-.528 1.196-1.214 1.196-.686 0-1.214-.52-1.214-1.196V13.105c0-.624.475-1.092 1.108-1.092.581 0 1.003.416 1.109.936l.316 2.704h.159c1.794-2.808 4.908-4.212 9.448-4.212 6.175 0 9.289 3.276 9.289 9.829V37.6c-.053.727-.633 1.3-1.267 1.3ZM90.225 0c-2.48 0-4.486 1.872-4.486 4.212v.416c0 2.289 2.058 4.213 4.486 4.213s4.486-1.924 4.486-4.213v-.364C94.711 1.872 92.653 0 90.225 0Z" />

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const BrowserExtensionIcon = svgIcon`
export const BrowserExtensionIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 14.58 100 75">
<path class="tw-fill-illustration-bg-secondary" d="M0 20.833a6.25 6.25 0 0 1 6.25-6.25h87.5a6.25 6.25 0 0 1 6.25 6.25v62.5a6.25 6.25 0 0 1-6.25 6.25H6.25A6.25 6.25 0 0 1 0 83.333v-62.5Z"/>
<path class="tw-fill-illustration-tertiary" d="M14.583 52.084a4.167 4.167 0 0 1 4.167-4.167h65.625a4.167 4.167 0 0 1 4.166 4.167v6.25a4.167 4.167 0 0 1-4.166 4.166H18.75a4.167 4.167 0 0 1-4.167-4.166v-6.25Z"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
const BusinessUnitPortalLogo = svgIcon`
const BusinessUnitPortalLogo = svg`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 46" fill="none">
<g class="tw-fill-text-alt2" clip-path="url(#business-unit-portal-logo-clip)">
<path fill-rule="evenodd" d="M47.948 7.444c2.284 0 4.06.867 5.366 2.674 1.305 1.77 1.958 4.191 1.958 7.263 0 3.18-.653 5.637-1.995 7.371-1.341 1.77-3.154 2.602-5.438 2.602s-4.025-.795-5.33-2.457h-.363l-.688 1.698a.67.67 0 0 1-.617.398h-2.9a.649.649 0 0 1-.653-.65V.94c0-.362.29-.65.653-.65h3.915c.363 0 .653.288.653.65v5.564c0 .795-.072 2.06-.218 3.794h.218c1.197-1.879 3.046-2.854 5.439-2.854Zm-1.668 4.228c-1.27 0-2.248.397-2.865 1.192-.58.795-.906 2.132-.906 3.939v.578c0 2.06.29 3.541.906 4.408.617.867 1.596 1.337 2.937 1.337 1.052 0 1.958-.506 2.575-1.517.616-.976.942-2.458.942-4.3 0-1.88-.326-3.289-.943-4.228-.652-.94-1.559-1.41-2.646-1.41ZM63.9 27.029h-3.915a.649.649 0 0 1-.653-.65V8.491c0-.362.29-.65.653-.65h3.916c.362 0 .652.288.652.65v17.85c.037.326-.29.687-.652.687Zm14.322-3.867c.762 0 1.668-.144 2.756-.433a.435.435 0 0 1 .544.433v3c0 .18-.109.325-.254.397-1.233.506-2.792.759-4.532.759-2.103 0-3.626-.506-4.569-1.59-.942-1.048-1.45-2.638-1.45-4.734V11.78H68.65a.418.418 0 0 1-.435-.434V9.648c0-.036.037-.072.037-.108l2.864-1.699 1.414-3.758c.073-.18.218-.289.399-.289h2.61c.254 0 .435.18.435.434v3.65h5.185c.109 0 .218.108.218.216v3.252a.418.418 0 0 1-.435.434H76.01v9.178c0 .723.217 1.301.616 1.626.363.398.943.578 1.595.578Zm24.401 3.867c-.507 0-.906-.325-1.124-.795l-3.843-11.672c-.254-.83-.58-2.095-1.015-3.722h-.109l-.362 1.301-.762 2.457-3.952 11.672c-.145.47-.58.759-1.088.759a1.12 1.12 0 0 1-1.087-.831L84.459 9.395c-.145-.506.253-1.048.834-1.048h.108c.363 0 .69.253.798.614l2.828 10.227c.689 2.674 1.16 4.625 1.378 5.926h.108c.653-2.674 1.16-4.445 1.487-5.348L95.662 9.07c.145-.434.544-.723 1.051-.723.472 0 .87.29 1.016.723l3.408 10.623c.834 2.71 1.341 4.445 1.523 5.348h.108c.109-.722.544-2.746 1.378-5.998l2.72-10.082a.848.848 0 0 1 .797-.614c.544 0 .943.506.798 1.048l-4.569 16.803c-.145.506-.58.83-1.124.83h-.145Zm21.537 0a.663.663 0 0 1-.652-.578l-.327-2.385H123c-.943 1.192-1.885 2.06-2.901 2.565-.979.506-2.175.723-3.517.723-1.849 0-3.263-.47-4.278-1.41-.906-.83-1.414-1.95-1.486-3.36-.145-1.879.652-3.686 2.175-4.733 1.559-1.012 3.734-1.627 6.671-1.627l3.554-.108v-1.229c0-1.806-.363-3.107-1.124-4.01-.762-.904-1.886-1.337-3.481-1.337-1.523 0-3.046.36-4.641 1.12-.399.18-.87 0-1.052-.398-.181-.397 0-.867.399-1.011 1.813-.723 3.59-1.12 5.403-1.12 2.066 0 3.589.541 4.641 1.625 1.015 1.048 1.522 2.747 1.522 4.95v11.745c-.036.216-.362.578-.725.578Zm-7.505-1.229c2.03 0 3.589-.578 4.713-1.698 1.161-1.12 1.741-2.746 1.741-4.734v-1.806l-3.263.144c-2.647.108-4.496.542-5.657 1.229-1.124.686-1.704 1.806-1.704 3.252 0 1.156.363 2.096 1.052 2.746.797.542 1.813.867 3.118.867Zm21.464-17.814c.544 0 1.088.036 1.704.108a.815.815 0 0 1 .689.94c-.072.433-.507.686-.942.614-.544-.072-1.088-.145-1.632-.145-1.595 0-2.901.687-3.916 2.024-1.015 1.337-1.523 3.072-1.523 5.095v9.467c0 .47-.362.831-.834.831a.819.819 0 0 1-.833-.83V9.105c0-.398.326-.723.725-.723.398 0 .725.29.725.687l.145 2.674h.109c.761-1.373 1.595-2.349 2.465-2.891.834-.578 1.885-.867 3.118-.867Zm12.763 0c1.341 0 2.538.253 3.517.722 1.015.47 1.885 1.338 2.646 2.53h.109a86.156 86.156 0 0 1-.109-4.228V1.12a.82.82 0 0 1 .834-.83c.472 0 .834.36.834.83v25.258a.571.571 0 0 1-.58.579.588.588 0 0 1-.58-.543l-.363-2.348h-.145c-1.414 2.132-3.48 3.18-6.127 3.18-2.611 0-4.532-.795-5.946-2.421-1.342-1.627-2.067-3.94-2.067-7.01 0-3.217.653-5.674 2.031-7.408 1.377-1.554 3.335-2.421 5.946-2.421Zm0 1.554c-2.067 0-3.59.722-4.641 2.168-1.015 1.409-1.523 3.469-1.523 6.215 0 5.276 2.067 7.913 6.2 7.913 2.139 0 3.662-.614 4.641-1.842 1.015-1.23 1.486-3.253 1.486-6.071v-.29c0-2.89-.471-4.95-1.486-6.178-.943-1.301-2.502-1.915-4.677-1.915ZM172.6 27.354c-2.719 0-4.822-.831-6.381-2.53-1.523-1.662-2.285-4.01-2.285-7.01 0-2.999.725-5.384 2.212-7.154 1.487-1.807 3.444-2.71 5.946-2.71 2.176 0 3.952.758 5.185 2.276 1.305 1.518 1.922 3.614 1.922 6.251v1.374h-13.488c.036 2.565.616 4.516 1.813 5.89 1.196 1.373 2.864 1.987 5.076 1.987 1.051 0 2.03-.072 2.828-.217.58-.108 1.269-.289 2.103-.578.471-.18.979.18.979.687 0 .289-.182.578-.472.686-.87.361-1.704.578-2.465.723-.907.253-1.885.325-2.973.325Zm-.508-17.85c-1.813 0-3.263.578-4.351 1.77-1.087 1.156-1.704 2.89-1.921 5.095h11.602c0-2.132-.471-3.866-1.414-5.059-.943-1.192-2.248-1.807-3.916-1.807Zm26.25 17.525a.819.819 0 0 1-.833-.831v-11.31c0-1.88-.399-3.253-1.161-4.084-.834-.83-2.03-1.3-3.698-1.3-2.248 0-3.879.542-4.895 1.698-1.015 1.12-1.595 2.963-1.595 5.456v9.467a.82.82 0 0 1-.834.832.82.82 0 0 1-.834-.832V9.107c0-.434.326-.759.762-.759.398 0 .688.29.761.65l.218 1.88h.108c1.233-1.952 3.372-2.927 6.49-2.927 4.242 0 6.382 2.276 6.382 6.83v11.345c-.037.506-.435.904-.871.904ZM61.979 0c-1.704 0-3.082 1.3-3.082 2.927v.289c0 1.59 1.414 2.927 3.082 2.927s3.082-1.337 3.082-2.927v-.253C65.061 1.301 63.647 0 61.979 0Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const BusinessWelcome = svgIcon`
export const BusinessWelcome = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4.17 0.75 91.67 95.83">
<path class="tw-fill-illustration-bg-secondary" d="M56.25 9.085A8.333 8.333 0 0 1 64.583.752h8.334a8.333 8.333 0 0 1 8.333 8.333v4.167h-25V9.085Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M72.917 2.835h-8.334a6.25 6.25 0 0 0-6.25 6.25v2.084h20.834V9.085a6.25 6.25 0 0 0-6.25-6.25ZM64.583.752a8.333 8.333 0 0 0-8.333 8.333v4.167h25V9.085A8.333 8.333 0 0 0 72.917.752h-8.334Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const CarouselIcon = svgIcon`
export const CarouselIcon = svg`
<svg class="tw-block" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" data-testid="inactive-carousel-icon">
<rect class="tw-stroke-current" x="0.5" y="0.5" width="11" height="11" rx="5.5" />
</svg>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const CreditCardIcon = svgIcon`
export const CreditCardIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0.98 15.86 95.04 65.76">
<path class="tw-fill-illustration-bg-secondary" d="M.98 21.62a5.76 5.76 0 0 1 5.76-5.76h57.6a5.76 5.76 0 0 1 5.76 5.76v33.6a5.76 5.76 0 0 1-5.76 5.76H6.74a5.76 5.76 0 0 1-5.76-5.76v-33.6Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M64.34 17.78H6.74a3.84 3.84 0 0 0-3.84 3.84v33.6a3.84 3.84 0 0 0 3.84 3.84h57.6a3.84 3.84 0 0 0 3.84-3.84v-33.6a3.84 3.84 0 0 0-3.84-3.84Zm-57.6-1.92a5.76 5.76 0 0 0-5.76 5.76v33.6a5.76 5.76 0 0 0 5.76 5.76h57.6a5.76 5.76 0 0 0 5.76-5.76v-33.6a5.76 5.76 0 0 0-5.76-5.76H6.74Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const DeactivatedOrg = svgIcon`
export const DeactivatedOrg = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4 0.5 90.32 96">
<path class="tw-fill-illustration-bg-secondary" d="M54 8.5a8 8 0 0 1 8-8h8a8 8 0 0 1 8 8v4H54v-4Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M70 2.5h-8a6 6 0 0 0-6 6v2h20v-2a6 6 0 0 0-6-6Zm-8-2a8 8 0 0 0-8 8v4h24v-4a8 8 0 0 0-8-8h-8Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const DevicesIcon = svgIcon`
export const DevicesIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 10 80 60">
<path class="tw-fill-illustration-bg-primary" d="M7.5 15a5 5 0 0 1 5-5h55a5 5 0 0 1 5 5v35a5 5 0 0 1-5 5h-55a5 5 0 0 1-5-5V15Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M67.5 11.667h-55A3.333 3.333 0 0 0 9.167 15v35a3.333 3.333 0 0 0 3.333 3.333h55A3.333 3.333 0 0 0 70.833 50V15a3.333 3.333 0 0 0-3.333-3.333ZM12.5 10a5 5 0 0 0-5 5v35a5 5 0 0 0 5 5h55a5 5 0 0 0 5-5V15a5 5 0 0 0-5-5h-55Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const DomainIcon = svgIcon`
export const DomainIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0.52 0.12 120 120">
<path class="tw-fill-illustration-bg-primary" d="M120.518 60.121c0 33.137-26.863 60-60 60s-60-26.863-60-60 26.863-60 60-60 60 26.863 60 60Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M60.518 117.621c31.756 0 57.5-25.743 57.5-57.5 0-31.756-25.744-57.5-57.5-57.5s-57.5 25.744-57.5 57.5 25.744 57.5 57.5 57.5Zm0 2.5c33.137 0 60-26.863 60-60s-26.863-60-60-60-60 26.863-60 60 26.863 60 60 60Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const EmptyTrash = svgIcon`
export const EmptyTrash = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0.22 7 95.71 88">
<path class="tw-fill-illustration-bg-primary" d="M.232 9.21A2 2 0 0 1 2.222 7h67.556a2 2 0 0 1 1.99 2.21l-7.014 66.627A8 8 0 0 1 56.798 83H15.202a8 8 0 0 1-7.956-7.162L.232 9.209Z"/>
<path class="tw-fill-illustration-bg-secondary" d="M60.283 80.99C63.59 80.76 61.313 77 58 77H14c-3.314 0-5.59 3.759-2.284 3.99.094.007.189.01.284.01h48c.095 0 .19-.003.283-.01Z"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const FavoritesIcon = svgIcon`
export const FavoritesIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="5.29 4.98 86.89 90.19">
<g clip-path="url(#clip0_2211_2391)">
<path class="tw-stroke-illustration-outline tw-fill-illustration-bg-primary" d="M45.7322 7.73645C46.8425 5.06767 50.6228 5.06767 51.7332 7.73645L60.8269 29.5929C61.511 31.2373 63.0575 32.3607 64.8328 32.5031L88.4343 34.3947C91.316 34.6257 92.4843 38.2221 90.2888 40.1027L72.3083 55.5001C70.9554 56.6589 70.3647 58.4773 70.7781 60.2101L76.2712 83.2335C76.9419 86.0452 73.8838 88.2672 71.4167 86.7609L51.2078 74.422C49.688 73.4941 47.7773 73.4941 46.2576 74.422L26.0486 86.7609C23.5815 88.2672 20.5234 86.0452 21.1941 83.2335L26.6873 60.2101C27.1007 58.4773 26.51 56.6589 25.157 55.5001L7.17651 40.1027C4.98107 38.2221 6.14942 34.6258 9.03101 34.3947L32.6326 32.5031C34.4079 32.3607 35.9543 31.2373 36.6384 29.5929L45.7322 7.73645Z" stroke-width="1.5"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const GearIcon = svgIcon`
export const GearIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4.8 6 110.4 107.52">
<path class="tw-fill-illustration-bg-primary" d="M59.64 84.96c12.924 0 23.4-10.316 23.4-23.04 0-12.725-10.476-23.04-23.4-23.04-12.923 0-23.4 10.315-23.4 23.04 0 12.724 10.477 23.04 23.4 23.04Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M59.64 40.08c-12.278 0-22.2 9.795-22.2 21.84 0 12.044 9.922 21.84 22.2 21.84 12.278 0 22.2-9.796 22.2-21.84 0-12.045-9.922-21.84-22.2-21.84Zm-24.6 21.84c0-13.405 11.031-24.24 24.6-24.24s24.6 10.835 24.6 24.24c0 13.405-11.031 24.24-24.6 24.24s-24.6-10.835-24.6-24.24Z" clip-rule="evenodd"/>

View File

@@ -1,12 +1,12 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const GeneratorInactive = svgIcon`
export const GeneratorInactive = svg`
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.05169 10.7766C3.00697 11.1884 3.30455 11.5585 3.71634 11.6032C4.12814 11.6479 4.49821 11.3503 4.54292 10.9386C4.93486 7.32891 8.05586 4.49609 11.8708 4.49609C14.608 4.49609 16.9911 5.95574 18.2619 8.11589H16.2676C15.8534 8.11589 15.5176 8.45168 15.5176 8.86589C15.5176 9.2801 15.8534 9.61589 16.2676 9.61589H20.2491C20.6634 9.61589 20.9991 9.2801 20.9991 8.86589V4.88347C20.9991 4.46926 20.6634 4.13347 20.2491 4.13347C19.8349 4.13347 19.4991 4.46926 19.4991 4.88347V7.26212C17.9511 4.70467 15.1112 2.99609 11.8708 2.99609C7.30492 2.99609 3.52788 6.39108 3.05169 10.7766ZM20.9425 13.2164C20.9872 12.8046 20.6896 12.4345 20.2778 12.3898C19.866 12.3451 19.4959 12.6427 19.4512 13.0545C19.0593 16.6641 15.9383 19.4969 12.1233 19.4969C9.38639 19.4969 7.0034 18.0375 5.73253 15.8776H7.72852C8.14273 15.8776 8.47852 15.5418 8.47852 15.1276C8.47852 14.7134 8.14273 14.3776 7.72852 14.3776H3.74695C3.33273 14.3776 2.99695 14.7134 2.99695 15.1276V19.11C2.99695 19.5242 3.33273 19.86 3.74695 19.86C4.16116 19.86 4.49695 19.5242 4.49695 19.11V16.7341C6.04539 19.2898 8.88426 20.9969 12.1233 20.9969C16.6892 20.9969 20.4663 17.6019 20.9425 13.2164ZM12.7514 9.43718C12.7514 9.02297 12.4156 8.68718 12.0014 8.68718C11.5872 8.68718 11.2514 9.02297 11.2514 9.43718V11.0305L9.75498 10.5402C9.36135 10.4113 8.93772 10.6258 8.80876 11.0195C8.6798 11.4131 8.89436 11.8367 9.28799 11.9657L10.798 12.4604L9.85608 13.7799C9.61543 14.117 9.69364 14.5854 10.0308 14.826C10.3679 15.0667 10.8363 14.9885 11.0769 14.6514L12.0014 13.3562L12.9261 14.6514C13.1667 14.9885 13.6351 15.0667 13.9723 14.826C14.3094 14.5853 14.3876 14.1169 14.1469 13.7798L13.2049 12.4603L14.7146 11.9657C15.1083 11.8367 15.3228 11.4131 15.1939 11.0195C15.0649 10.6258 14.6412 10.4113 14.2476 10.5402L12.7514 11.0305V9.43718Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
</svg>
`;
export const GeneratorActive = svgIcon`
export const GeneratorActive = svg`
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 12C18 15.3137 15.3137 18 12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12Z" class="tw-fill-primary-100" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.05169 10.7766C3.00697 11.1884 3.30455 11.5585 3.71634 11.6032C4.12814 11.6479 4.49821 11.3503 4.54292 10.9386C4.93486 7.32891 8.05586 4.49609 11.8708 4.49609C14.608 4.49609 16.9911 5.95574 18.2619 8.11589H16.2676C15.8534 8.11589 15.5176 8.45168 15.5176 8.86589C15.5176 9.2801 15.8534 9.61589 16.2676 9.61589H20.2491C20.6634 9.61589 20.9991 9.2801 20.9991 8.86589V4.88347C20.9991 4.46926 20.6634 4.13347 20.2491 4.13347C19.8349 4.13347 19.4991 4.46926 19.4991 4.88347V7.26212C17.9511 4.70467 15.1112 2.99609 11.8708 2.99609C7.30492 2.99609 3.52788 6.39108 3.05169 10.7766ZM20.9425 13.2164C20.9872 12.8046 20.6896 12.4345 20.2778 12.3898C19.866 12.3451 19.4959 12.6427 19.4512 13.0545C19.0593 16.6641 15.9383 19.4969 12.1233 19.4969C9.38639 19.4969 7.0034 18.0375 5.73253 15.8776H7.72852C8.14273 15.8776 8.47852 15.5418 8.47852 15.1276C8.47852 14.7134 8.14273 14.3776 7.72852 14.3776H3.74695C3.33273 14.3776 2.99695 14.7134 2.99695 15.1276V19.11C2.99695 19.5242 3.33273 19.86 3.74695 19.86C4.16116 19.86 4.49695 19.5242 4.49695 19.11V16.7341C6.04539 19.2898 8.88426 20.9969 12.1233 20.9969C16.6892 20.9969 20.4663 17.6019 20.9425 13.2164ZM12.7514 9.43718C12.7514 9.02297 12.4156 8.68718 12.0014 8.68718C11.5872 8.68718 11.2514 9.02297 11.2514 9.43718V11.0305L9.75498 10.5402C9.36135 10.4113 8.93772 10.6258 8.80876 11.0195C8.6798 11.4131 8.89436 11.8367 9.28799 11.9657L10.798 12.4604L9.85608 13.7799C9.61543 14.117 9.69364 14.5854 10.0308 14.826C10.3679 15.0667 10.8363 14.9885 11.0769 14.6514L12.0014 13.3562L12.9261 14.6514C13.1667 14.9885 13.6351 15.0667 13.9723 14.826C14.3094 14.5853 14.3876 14.1169 14.1469 13.7798L13.2049 12.4603L14.7146 11.9657C15.1083 11.8367 15.3228 11.4131 15.1939 11.0195C15.0649 10.6258 14.6412 10.4113 14.2476 10.5402L12.7514 11.0305V9.43718Z" class="tw-fill-primary-600" />

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const ItemTypes = svgIcon`
export const ItemTypes = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 18.75 150 112.5">
<path class="tw-fill-illustration-bg-secondary" d="M0 28.125a9.375 9.375 0 0 1 9.375-9.375h50a9.375 9.375 0 0 1 9.375 9.375v31.25a9.375 9.375 0 0 1-9.375 9.375h-50A9.375 9.375 0 0 1 0 59.375v-31.25Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M59.375 21.875h-50a6.25 6.25 0 0 0-6.25 6.25v31.25a6.25 6.25 0 0 0 6.25 6.25h50a6.25 6.25 0 0 0 6.25-6.25v-31.25a6.25 6.25 0 0 0-6.25-6.25Zm-50-3.125A9.375 9.375 0 0 0 0 28.125v31.25a9.375 9.375 0 0 0 9.375 9.375h50a9.375 9.375 0 0 0 9.375-9.375v-31.25a9.375 9.375 0 0 0-9.375-9.375h-50Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const LockIcon = svgIcon`
export const LockIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 80 73.33">
<path class="tw-fill-illustration-bg-primary" d="M11.667 31.666c0-5.523 3.805-10 8.5-10h39.666c4.695 0 8.5 4.477 8.5 10v31.667c0 5.523-3.805 10-8.5 10H20.167c-4.695 0-8.5-4.477-8.5-10V31.666Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M59.833 23.333H20.167c-3.912 0-7.084 3.73-7.084 8.333v31.667c0 4.602 3.172 8.333 7.084 8.333h39.666c3.912 0 7.084-3.73 7.084-8.333V31.666c0-4.602-3.172-8.333-7.084-8.333Zm-39.666-1.667c-4.695 0-8.5 4.477-8.5 10v31.667c0 5.523 3.805 10 8.5 10h39.666c4.695 0 8.5-4.477 8.5-10V31.666c0-5.523-3.805-10-8.5-10H20.167Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const LoginCards = svgIcon`
export const LoginCards = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="6.25 6.25 137.5 137.5">
<path class="tw-fill-illustration-bg-secondary" d="M18.75 53.125a9.375 9.375 0 0 1 9.375-9.375h106.25a9.375 9.375 0 0 1 9.375 9.375v81.25a9.375 9.375 0 0 1-9.375 9.375H28.125a9.375 9.375 0 0 1-9.375-9.375v-81.25Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M134.375 46.875H28.125a6.25 6.25 0 0 0-6.25 6.25v81.25a6.25 6.25 0 0 0 6.25 6.25h106.25a6.25 6.25 0 0 0 6.25-6.25v-81.25a6.25 6.25 0 0 0-6.25-6.25ZM28.125 43.75a9.375 9.375 0 0 0-9.375 9.375v81.25a9.375 9.375 0 0 0 9.375 9.375h106.25a9.375 9.375 0 0 0 9.375-9.375v-81.25a9.375 9.375 0 0 0-9.375-9.375H28.125Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const NoCredentialsIcon = svgIcon`
export const NoCredentialsIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 1.49 96 94.02">
<path class="tw-fill-illustration-bg-primary" d="M79 48.5c0 17.12-13.88 31-31 31-17.12 0-31-13.88-31-31 0-17.12 13.88-31 31-31 17.12 0 31 13.88 31 31Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M48 18.5c-16.569 0-30 13.431-30 30 0 16.569 13.431 30 30 30 16.569 0 30-13.431 30-30 0-16.569-13.431-30-30-30Zm-32 30c0-17.673 14.327-32 32-32 17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32-17.673 0-32-14.327-32-32Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const NoFolders = svgIcon`
export const NoFolders = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="2 12.5 93.63 72">
<path class="tw-fill-illustration-bg-secondary" d="M2 21.5a6 6 0 0 1 6-6h17.24a6 6 0 0 1 4.556 2.095L34 22.5h52a4 4 0 0 1 4 4v36c0 12.15-9.85 22-22 22H7a5 5 0 0 1-5-5v-58Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="m33.08 24.5-4.803-5.603A4 4 0 0 0 25.24 17.5H8a4 4 0 0 0-4 4v58a3 3 0 0 0 3 3h61c11.046 0 20-8.954 20-20v-36a2 2 0 0 0-2-2H33.08Zm.92-2-4.204-4.905A6 6 0 0 0 25.24 15.5H8a6 6 0 0 0-6 6v58a5 5 0 0 0 5 5h61c12.15 0 22-9.85 22-22v-36a4 4 0 0 0-4-4H34Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const NoResults = svgIcon`
export const NoResults = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4 4.5 91.63 88">
<path class="tw-fill-illustration-bg-secondary" d="M62 12.5a2 2 0 0 1 2 2v76a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-76a2 2 0 0 1 2-2h56Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M62 90.5v-76H6v76h56Zm2-76a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v76a2 2 0 0 0 2 2h56a2 2 0 0 0 2-2v-76Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const NoSendsIcon = svgIcon`
export const NoSendsIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="9 4 83.89 91">
<path class="tw-fill-illustration-bg-secondary" d="M67 12a2 2 0 0 1 2 2v76a2 2 0 0 1-2 2H11a2 2 0 0 1-2-2V14a2 2 0 0 1 2-2h56Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M67 90V14H11v76h56Zm2-76a2 2 0 0 0-2-2H11a2 2 0 0 0-2 2v76a2 2 0 0 0 2 2h56a2 2 0 0 0 2-2V14Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const Party = svgIcon`
export const Party = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="3.76 4 88.24 88.24">
<path class="tw-fill-illustration-tertiary" d="M61.342 69.774 10.61 90.912a4.223 4.223 0 0 1-5.523-5.523l21.138-50.73a8 8 0 0 1 9.683-4.586l1.43.428a42 42 0 0 1 28.16 28.16l.428 1.43a8 8 0 0 1-4.585 9.683Z"/>
<path class="tw-fill-illustration-bg-tertiary" fill-rule="evenodd" d="M29.603 82.999a87.777 87.777 0 0 1-8.85-7.751A87.763 87.763 0 0 1 13 66.397l.893-2.143a85.302 85.302 0 0 0 8.273 9.58c3.099 3.099 6.32 5.863 9.579 8.272l-2.143.893Zm-8.438 3.516a104.44 104.44 0 0 1-6.07-5.61 104.496 104.496 0 0 1-5.61-6.07l-.86 2.066a106.756 106.756 0 0 0 5.056 5.418 106.754 106.754 0 0 0 5.418 5.056l2.066-.86Zm19.768-8.237c-4.415-2.637-8.845-6.073-12.991-10.22-4.147-4.147-7.583-8.576-10.22-12.991l.967-2.321c2.625 4.678 6.212 9.443 10.667 13.898s9.22 8.042 13.898 10.667l-2.32.967Zm-17.882-36c1.659 6.049 5.736 12.863 11.772 18.899 6.036 6.036 12.85 10.113 18.9 11.772l3.242-1.351a23.334 23.334 0 0 1-3.26-.734c-5.523-1.644-11.815-5.448-17.468-11.101-5.653-5.654-9.458-11.945-11.101-17.469a23.344 23.344 0 0 1-.734-3.259l-1.351 3.243Z" clip-rule="evenodd"/>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const RegistrationCheckEmailIcon = svgIcon`
export const RegistrationCheckEmailIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="3.33 0.87 76.36 76.8">
<path class="tw-fill-illustration-bg-primary" d="M3.333 33.298a5 5 0 0 1 1.745-3.794L37.287 1.87a4.167 4.167 0 0 1 5.426 0l32.21 27.634a5 5 0 0 1 1.744 3.794v41.035a3.333 3.333 0 0 1-3.334 3.334H6.667a3.333 3.333 0 0 1-3.334-3.334V33.299Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M75 74.334V33.297c0-.972-.425-1.896-1.163-2.53L41.627 3.136a2.5 2.5 0 0 0-3.255 0L6.162 30.769A3.333 3.333 0 0 0 5 33.299v41.035C5 75.254 5.746 76 6.667 76h66.666c.92 0 1.667-.746 1.667-1.666ZM5.078 29.504a5 5 0 0 0-1.745 3.794v41.035a3.333 3.333 0 0 0 3.334 3.334h66.666a3.333 3.333 0 0 0 3.334-3.334V33.299a5 5 0 0 0-1.745-3.794L42.713 1.87a4.167 4.167 0 0 0-5.426 0L5.077 29.504Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const RegistrationUserAddIcon = svgIcon`
export const RegistrationUserAddIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 10 80 60">
<path class="tw-fill-illustration-bg-primary" d="M0 14.8A4.8 4.8 0 0 1 4.8 10h45.4a4.8 4.8 0 0 1 4.8 4.8v32.067a4.8 4.8 0 0 1-4.8 4.8H4.8a4.8 4.8 0 0 1-4.8-4.8V14.8Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M50.2 11.6H4.8a3.2 3.2 0 0 0-3.2 3.2v32.067a3.2 3.2 0 0 0 3.2 3.2h45.4a3.2 3.2 0 0 0 3.2-3.2V14.8a3.2 3.2 0 0 0-3.2-3.2ZM4.8 10A4.8 4.8 0 0 0 0 14.8v32.067a4.8 4.8 0 0 0 4.8 4.8h45.4a4.8 4.8 0 0 0 4.8-4.8V14.8a4.8 4.8 0 0 0-4.8-4.8H4.8Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const ReportBreach = svgIcon`
export const ReportBreach = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="11.6 3.33 56.67 73.33">
<path class="tw-fill-illustration-bg-secondary" d="M60 10a1.6 1.6 0 0 1 1.6 1.6v63.467a1.6 1.6 0 0 1-1.6 1.6H13.2a1.6 1.6 0 0 1-1.6-1.6V11.6a1.6 1.6 0 0 1 1.6-1.6H60Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M60 75.067V11.6H13.2v63.467H60ZM61.6 11.6A1.6 1.6 0 0 0 60 10H13.2a1.6 1.6 0 0 0-1.6 1.6v63.467a1.6 1.6 0 0 0 1.6 1.6H60a1.6 1.6 0 0 0 1.6-1.6V11.6Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const ReportExposedPasswords = svgIcon`
export const ReportExposedPasswords = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0.7 2.4 79.3 77.2">
<path class="tw-fill-illustration-bg-primary" d="M17.6 26.8a4.8 4.8 0 0 1 4.8-4.8h52.8a4.8 4.8 0 0 1 4.8 4.8v33.6a4.8 4.8 0 0 1-4.8 4.8H22.4a4.8 4.8 0 0 1-4.8-4.8V26.8Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M75.2 23.6H22.4a3.2 3.2 0 0 0-3.2 3.2v33.6a3.2 3.2 0 0 0 3.2 3.2h52.8a3.2 3.2 0 0 0 3.2-3.2V26.8a3.2 3.2 0 0 0-3.2-3.2ZM22.4 22a4.8 4.8 0 0 0-4.8 4.8v33.6a4.8 4.8 0 0 0 4.8 4.8h52.8a4.8 4.8 0 0 0 4.8-4.8V26.8a4.8 4.8 0 0 0-4.8-4.8H22.4Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const ReportUnsecuredWebsites = svgIcon`
export const ReportUnsecuredWebsites = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0.4 10.07 79.2 60">
<path class="tw-fill-illustration-bg-primary" d="M.4 14.872a4.8 4.8 0 0 1 4.8-4.8h69.6a4.8 4.8 0 0 1 4.8 4.8v50.4a4.8 4.8 0 0 1-4.8 4.8H5.2a4.8 4.8 0 0 1-4.8-4.8v-50.4Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M74.8 11.672H5.2a3.2 3.2 0 0 0-3.2 3.2v50.4a3.2 3.2 0 0 0 3.2 3.2h69.6a3.2 3.2 0 0 0 3.2-3.2v-50.4a3.2 3.2 0 0 0-3.2-3.2Zm-69.6-1.6a4.8 4.8 0 0 0-4.8 4.8v50.4a4.8 4.8 0 0 0 4.8 4.8h69.6a4.8 4.8 0 0 0 4.8-4.8v-50.4a4.8 4.8 0 0 0-4.8-4.8H5.2Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const RestrictedView = svgIcon`
export const RestrictedView = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="5.52 2.62 110 110">
<path class="tw-fill-illustration-bg-secondary" d="M25.518 12.621c0-5.523 4.477-10 10-10h50c5.523 0 10 4.477 10 10v2.5h-70v-2.5Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M85.518 5.121h-50a7.5 7.5 0 0 0-7.5 7.5h65a7.5 7.5 0 0 0-7.5-7.5Zm-50-2.5c-5.523 0-10 4.477-10 10v2.5h70v-2.5c0-5.523-4.477-10-10-10h-50Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const SecretsManagerAlt = svgIcon`
export const SecretsManagerAlt = svg`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 580 104" fill="none">
<path class="tw-fill-text-alt2" d="M102.539 52.27c0 28.315-22.954 51.269-51.27 51.269C22.955 103.539 0 80.585 0 52.269 0 23.955 22.954 1 51.27 1c28.315 0 51.269 22.954 51.269 51.27Z"/>
<path class="tw-fill-bw-blue" fill-rule="evenodd" d="M75.971 26.765c-.516-.49-1.111-.777-1.786-.777H28.39c-.675 0-1.31.287-1.786.777-.516.49-.754 1.145-.754 1.84v31.47c0 2.371.437 4.66 1.31 6.95.873 2.288 1.984 4.332 3.294 6.13s2.897 3.515 4.682 5.232a62.036 62.036 0 0 0 5 4.25 82.731 82.731 0 0 0 4.802 3.188c1.667.981 2.857 1.676 3.572 2.003.714.368 1.27.613 1.706.817a2.26 2.26 0 0 0 1.032.246 2.26 2.26 0 0 0 1.032-.246c.436-.204.992-.45 1.706-.817.715-.368 1.905-1.022 3.572-2.003a54.848 54.848 0 0 0 4.801-3.188c1.548-1.103 3.215-2.534 5.04-4.25 1.826-1.717 3.373-3.434 4.683-5.232 1.31-1.798 2.42-3.842 3.294-6.13.912-2.29 1.31-4.62 1.31-6.95v-31.47c.04-.695-.239-1.309-.715-1.84Zm-5.913 33.597c0 11.403-18.77 21.172-18.77 21.172V32.732h18.77v27.63Z" clip-rule="evenodd"/>

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const Security = svgIcon`
export const Security = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4 4 88 89">
<path class="tw-fill-illustration-bg-secondary" d="M4 12a8 8 0 0 1 8-8h72a8 8 0 0 1 8 8v63a8 8 0 0 1-8 8H12a8 8 0 0 1-8-8V12Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M84 6H12a6 6 0 0 0-6 6v63a6 6 0 0 0 6 6h72a6 6 0 0 0 6-6V12a6 6 0 0 0-6-6ZM12 4a8 8 0 0 0-8 8v63a8 8 0 0 0 8 8h72a8 8 0 0 0 8-8V12a8 8 0 0 0-8-8H12Z" clip-rule="evenodd"/>

View File

@@ -1,12 +1,12 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const SendInactive = svgIcon`
export const SendInactive = svg`
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.26134 15.8194L3.32764 13.1612C2.24617 12.6767 2.2182 11.1515 3.28118 10.6277L19.4762 2.6472C20.5111 2.13723 21.6841 3.02766 21.471 4.16154L18.5696 19.6026C18.3851 20.5849 17.2603 21.0629 16.4249 20.5141L14.1152 18.9965L11.7106 21.1294C11.4413 21.3683 11.0937 21.5003 10.7337 21.5003C9.92052 21.5003 9.26134 20.8411 9.26134 20.0279V15.8194ZM4.13499 11.8792L19.9599 4.08112L17.1231 19.178L10.8804 15.0764L15.5129 10.6535C15.8125 10.3675 15.8235 9.89271 15.5374 9.59312C15.2514 9.29353 14.7767 9.28254 14.4771 9.56857L9.52695 14.2947L4.13499 11.8792ZM12.8167 18.1433L10.7613 16.7929V19.9664L12.8167 18.1433Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
</svg>
`;
export const SendActive = svgIcon`
export const SendActive = svg`
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0621 20.2221V14.7319L15.4504 9.56824L20.2795 3.68408L17.32 19.6084L13.9377 17.5259L10.9361 20.2221H10.0621Z" class="tw-fill-primary-100" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.26134 15.8194L3.32764 13.1612C2.24617 12.6767 2.2182 11.1515 3.28118 10.6277L19.4762 2.6472C20.5111 2.13723 21.6841 3.02766 21.471 4.16154L18.5696 19.6026C18.3851 20.5849 17.2603 21.0629 16.4249 20.5141L14.1152 18.9965L11.7106 21.1294C11.4413 21.3683 11.0937 21.5003 10.7337 21.5003C9.92052 21.5003 9.26134 20.8411 9.26134 20.0279V15.8194ZM4.13499 11.8792L19.9599 4.08112L17.1231 19.178L10.8804 15.0764L15.5129 10.6535C15.8125 10.3675 15.8235 9.89271 15.5374 9.59312C15.2514 9.29353 14.7767 9.28254 14.4771 9.56857L9.52695 14.2947L4.13499 11.8792ZM12.8167 18.1433L10.7613 16.7929V19.9664L12.8167 18.1433Z" class="tw-fill-primary-600" />

View File

@@ -1,13 +1,13 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const SettingsInactive = svgIcon`
export const SettingsInactive = svg`
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12ZM14.5 12C14.5 13.3807 13.3807 14.5 12 14.5C10.6193 14.5 9.5 13.3807 9.5 12C9.5 10.6193 10.6193 9.5 12 9.5C13.3807 9.5 14.5 10.6193 14.5 12Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.8618 4.17107L14.6161 2.69633C14.5491 2.29451 14.2014 2 13.7941 2H10.2059C9.79857 2 9.45091 2.29451 9.38394 2.69633L9.13815 4.17107C8.22425 4.50524 7.38523 4.99487 6.6531 5.60794L5.25079 5.08259C4.86931 4.93967 4.44043 5.0935 4.23675 5.44629L2.44269 8.55369C2.239 8.90648 2.32023 9.35482 2.63473 9.61373L3.78982 10.5646C3.70886 11.0309 3.66666 11.5105 3.66666 12C3.66666 12.4895 3.70886 12.969 3.78981 13.4354L2.63473 14.3863C2.32023 14.6452 2.239 15.0935 2.44269 15.4463L4.23675 18.5537C4.44043 18.9065 4.86931 19.0603 5.25079 18.9174L6.65308 18.392C7.38521 19.0051 8.22424 19.4948 9.13815 19.8289L9.38394 21.3037C9.45091 21.7055 9.79857 22 10.2059 22H13.7941C14.2014 22 14.5491 21.7055 14.6161 21.3037L14.8618 19.8289C15.7757 19.4948 16.6148 19.0051 17.3469 18.3921L18.7492 18.9174C19.1306 19.0603 19.5595 18.9065 19.7632 18.5537L21.5573 15.4463C21.7609 15.0935 21.6797 14.6452 21.3652 14.3863L20.2102 13.4354C20.2911 12.9691 20.3333 12.4895 20.3333 12C20.3333 11.5105 20.2911 11.0309 20.2102 10.5646L21.3652 9.61373C21.6797 9.35482 21.7609 8.90648 21.5573 8.55369L19.7632 5.44629C19.5595 5.0935 19.1306 4.93967 18.7492 5.08259L17.3469 5.60793C16.6148 4.99486 15.7757 4.50524 14.8618 4.17107ZM14.3467 5.57985L13.5259 5.27973L13.2293 3.5H10.7707L10.4741 5.27973L9.65327 5.57985C8.90522 5.85338 8.21727 6.25458 7.61612 6.75798L6.94571 7.31937L5.25341 6.68538L4.0241 8.8146L5.417 9.96128L5.26771 10.8212C5.2014 11.2031 5.16666 11.5969 5.16666 12C5.16666 12.4031 5.2014 12.7968 5.26771 13.1788L5.417 14.0387L4.0241 15.1854L5.25341 17.3146L6.94569 16.6806L7.6161 17.242C8.21726 17.7454 8.90521 18.1466 9.65327 18.4201L10.4741 18.7203L10.7707 20.5H13.2293L13.5259 18.7203L14.3467 18.4201C15.0948 18.1466 15.7827 17.7454 16.3839 17.242L17.0543 16.6806L18.7465 17.3146L19.9758 15.1854L18.583 14.0387L18.7323 13.1788C18.7986 12.7969 18.8333 12.4031 18.8333 12C18.8333 11.5969 18.7986 11.2031 18.7323 10.8212L18.583 9.96125L19.9758 8.8146L18.7465 6.68538L17.0543 7.31936L16.3839 6.75797C15.7827 6.25458 15.0948 5.85337 14.3467 5.57985ZM20.2582 14.6963L20.2577 14.6973L20.2582 14.6963ZM13.1365 2.94293C13.1364 2.94267 13.1365 2.9432 13.1365 2.94293V2.94293Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
</svg>
`;
export const SettingsActive = svgIcon`
export const SettingsActive = svg`
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.9672 5.03286C19.2837 5.00445 19.5977 5.15974 19.7632 5.44632L21.5573 8.55372C21.7609 8.90651 21.6797 9.35485 21.3652 9.61376L20.2102 10.5646C20.2911 11.0309 20.3333 11.5106 20.3333 12C20.3333 12.4895 20.2911 12.9691 20.2102 13.4354L21.3652 14.3863C21.6797 14.6452 21.7609 15.0935 21.5573 15.4463L19.7632 18.5537C19.5595 18.9065 19.1306 19.0603 18.7492 18.9174L17.3469 18.3921C16.6148 19.0052 15.7757 19.4948 14.8618 19.829L14.6161 21.3037C14.5491 21.7055 14.2014 22 13.7941 22H10.2059C9.79857 22 9.45091 21.7055 9.38394 21.3037L9.13815 19.829C8.22424 19.4948 7.38521 19.0052 6.65308 18.3921L5.25079 18.9174C5.17921 18.9442 5.10596 18.9606 5.03284 18.9672L10.2322 13.7678C10.6846 14.2202 11.3096 14.5 12 14.5C13.3807 14.5 14.5 13.3807 14.5 12C14.5 11.3097 14.2202 10.6847 13.7678 10.2323L18.9672 5.03286Z" class="tw-fill-primary-100" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12ZM14.5 12C14.5 13.3807 13.3807 14.5 12 14.5C10.6193 14.5 9.5 13.3807 9.5 12C9.5 10.6193 10.6193 9.5 12 9.5C13.3807 9.5 14.5 10.6193 14.5 12Z" class="tw-fill-primary-600" />

View File

@@ -1,13 +1,13 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
const BitwardenShield = svgIcon`
const BitwardenShield = svg`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 32" fill="none">
<g clip-path="url(#bitwarden-shield-clip)">
<path class="tw-fill-text-alt2" d="M22.01 17.055V4.135h-9.063v22.954c1.605-.848 3.041-1.77 4.31-2.766 3.169-2.476 4.753-4.899 4.753-7.268Zm3.884-15.504v15.504a9.256 9.256 0 0 1-.677 3.442 12.828 12.828 0 0 1-1.68 3.029 18.708 18.708 0 0 1-2.386 2.574 27.808 27.808 0 0 1-2.56 2.08 32.251 32.251 0 0 1-2.448 1.564c-.85.49-1.453.824-1.81.999-.357.175-.644.31-.86.404-.162.08-.337.12-.526.12s-.364-.04-.526-.12a22.99 22.99 0 0 1-.86-.404c-.357-.175-.96-.508-1.81-1a32.242 32.242 0 0 1-2.448-1.564 27.796 27.796 0 0 1-2.56-2.08 18.706 18.706 0 0 1-2.386-2.573 12.828 12.828 0 0 1-1.68-3.029A9.256 9.256 0 0 1 0 17.055V1.551C0 1.2.128.898.384.642.641.386.944.26 1.294.26H24.6c.35 0 .654.127.91.383s.384.559.384.909Z"/>
<path class="tw-fill-fg-sidenav-text" d="M22.01 17.055V4.135h-9.063v22.954c1.605-.848 3.041-1.77 4.31-2.766 3.169-2.476 4.753-4.899 4.753-7.268Zm3.884-15.504v15.504a9.256 9.256 0 0 1-.677 3.442 12.828 12.828 0 0 1-1.68 3.029 18.708 18.708 0 0 1-2.386 2.574 27.808 27.808 0 0 1-2.56 2.08 32.251 32.251 0 0 1-2.448 1.564c-.85.49-1.453.824-1.81.999-.357.175-.644.31-.86.404-.162.08-.337.12-.526.12s-.364-.04-.526-.12a22.99 22.99 0 0 1-.86-.404c-.357-.175-.96-.508-1.81-1a32.242 32.242 0 0 1-2.448-1.564 27.796 27.796 0 0 1-2.56-2.08 18.706 18.706 0 0 1-2.386-2.573 12.828 12.828 0 0 1-1.68-3.029A9.256 9.256 0 0 1 0 17.055V1.551C0 1.2.128.898.384.642.641.386.944.26 1.294.26H24.6c.35 0 .654.127.91.383s.384.559.384.909Z"/>
</g>
<defs>
<clipPath id="bitwarden-shield-clip">
<path class="tw-fill-text-alt2" d="M0 0h26v32H0z"/>
<path class="tw-fill-fg-sidenav-text" d="M0 0h26v32H0z" />
</clipPath>
</defs>
</svg>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const SsoKeyIcon = svgIcon`
export const SsoKeyIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 12.12 96 76">
<path class="tw-fill-illustration-bg-secondary" fill-rule="evenodd" d="M77 43.121h2c9.389 0 17 7.611 17 17s-7.611 17-17 17H21v-.086C9.234 76.022 0 66.15 0 54.121c0-10.663 7.256-19.63 17.099-22.236C21.599 20.32 32.842 12.121 46 12.121c17.12 0 31 13.88 31 31Z" clip-rule="evenodd"/>
<path class="tw-fill-illustration-bg-primary" fill-rule="evenodd" d="M56 53.121c2.903 0 5.707-.427 8.352-1.22 3.427 4.966 9.157 8.22 15.648 8.22 5.856 0 11.094-2.65 14.579-6.815C91.952 47.31 85.965 43.121 79 43.121h-2c0-17.12-13.88-31-31-31a30.861 30.861 0 0 0-18.387 6.039A29.12 29.12 0 0 0 27 24.121c0 16.016 12.984 29 29 29Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const TwoFactorAuthAuthenticatorIcon = svgIcon`
export const TwoFactorAuthAuthenticatorIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 10 80 66.67">
<path class="tw-fill-illustration-bg-secondary" d="M13.333 15a5 5 0 0 1 5-5H75a5 5 0 0 1 5 5v40a5 5 0 0 1-5 5H18.333a5 5 0 0 1-5-5V15Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M75 11.667H18.333A3.333 3.333 0 0 0 15 15v40a3.333 3.333 0 0 0 3.333 3.333H75A3.333 3.333 0 0 0 78.333 55V15A3.333 3.333 0 0 0 75 11.667ZM18.333 10a5 5 0 0 0-5 5v40a5 5 0 0 0 5 5H75a5 5 0 0 0 5-5V15a5 5 0 0 0-5-5H18.333Z" clip-rule="evenodd"/>

View File

@@ -1,8 +1,10 @@
// this svg includes the Duo logo, which contains colors not part of our bitwarden theme colors
/* eslint-disable @bitwarden/components/require-theme-colors-in-svg */
import { svgIcon } from "../icon-service";
export const TwoFactorAuthDuoIcon = svgIcon`
// this svg includes the Duo logo, which contains colors not part of our bitwarden theme colors
import { svg } from "../svg";
export const TwoFactorAuthDuoIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 120 40">
<g clip-path="url(#two-factor-auth-duo-clip)">
<path fill="#7BCD54" d="M19.359 39.412H0V20.97h38.694c-.505 10.27-8.968 18.44-19.335 18.44Z" />

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const TwoFactorAuthEmailIcon = svgIcon`
export const TwoFactorAuthEmailIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="3.33 0.46 73.33 76.8">
<path class="tw-fill-illustration-bg-primary" d="M3.333 32.89a5 5 0 0 1 1.745-3.796L37.287 1.46a4.167 4.167 0 0 1 5.426 0l32.21 27.635a5 5 0 0 1 1.744 3.794v41.035a3.333 3.333 0 0 1-3.334 3.334H6.667a3.333 3.333 0 0 1-3.334-3.334V32.89Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M75 73.924V32.89c0-.972-.425-1.896-1.163-2.53L41.627 2.725a2.5 2.5 0 0 0-3.255 0L6.162 30.36A3.333 3.333 0 0 0 5 32.89v41.035c0 .92.746 1.667 1.667 1.667h66.666c.92 0 1.667-.746 1.667-1.667ZM5.078 29.094a5 5 0 0 0-1.745 3.795v41.035a3.333 3.333 0 0 0 3.334 3.334h66.666a3.333 3.333 0 0 0 3.334-3.334V32.89a5 5 0 0 0-1.745-3.795L42.713 1.46a4.167 4.167 0 0 0-5.426 0L5.077 29.095Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const TwoFactorAuthSecurityKeyFailedIcon = svgIcon`
export const TwoFactorAuthSecurityKeyFailedIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4.72 12.85 92 80">
<path class="tw-fill-illustration-bg-primary" d="M16.722 18.846a6 6 0 0 1 6-6h68a6 6 0 0 1 6 6v48a6 6 0 0 1-6 6h-68a6 6 0 0 1-6-6v-48Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M90.722 14.846h-68a4 4 0 0 0-4 4v48a4 4 0 0 0 4 4h68a4 4 0 0 0 4-4v-48a4 4 0 0 0-4-4Zm-68-2a6 6 0 0 0-6 6v48a6 6 0 0 0 6 6h68a6 6 0 0 0 6-6v-48a6 6 0 0 0-6-6h-68Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const TwoFactorAuthSecurityKeyIcon = svgIcon`
export const TwoFactorAuthSecurityKeyIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="3.33 10 71.27 66.67">
<path class="tw-fill-illustration-bg-primary" d="M19.6 14.8a4.8 4.8 0 0 1 4.8-4.8h45.4a4.8 4.8 0 0 1 4.8 4.8v32.067a4.8 4.8 0 0 1-4.8 4.8H24.4a4.8 4.8 0 0 1-4.8-4.8V14.8Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M69.8 11.6H24.4a3.2 3.2 0 0 0-3.2 3.2v32.067a3.2 3.2 0 0 0 3.2 3.2h45.4a3.2 3.2 0 0 0 3.2-3.2V14.8a3.2 3.2 0 0 0-3.2-3.2ZM24.4 10a4.8 4.8 0 0 0-4.8 4.8v32.067a4.8 4.8 0 0 0 4.8 4.8h45.4a4.8 4.8 0 0 0 4.8-4.8V14.8a4.8 4.8 0 0 0-4.8-4.8H24.4Z" clip-rule="evenodd"/>

View File

@@ -1,6 +1,6 @@
import { svgIcon } from "../icon-service";
import { svg } from "../svg";
export const TwoFactorAuthWebAuthnIcon = svgIcon`
export const TwoFactorAuthWebAuthnIcon = svg`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="3.33 3.33 76.49 75.83">
<path class="tw-fill-illustration-bg-primary" d="M3.333 8.334a5 5 0 0 1 5-5H35a5 5 0 0 1 5 5v56.667a5 5 0 0 1-5 5H8.333a5 5 0 0 1-5-5V8.334Z"/>
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M35 5H8.333A3.333 3.333 0 0 0 5 8.335v56.667a3.333 3.333 0 0 0 3.333 3.333H35a3.333 3.333 0 0 0 3.333-3.333V8.334A3.333 3.333 0 0 0 35 5.001ZM8.333 3.335a5 5 0 0 0-5 5v56.667a5 5 0 0 0 5 5H35a5 5 0 0 0 5-5V8.334a5 5 0 0 0-5-5H8.333Z" clip-rule="evenodd"/>

Some files were not shown because too many files have changed in this diff Show More