mirror of
https://github.com/bitwarden/browser
synced 2026-03-02 03:21:19 +00:00
Merge branch 'main' into PM-25685
This commit is contained in:
@@ -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,
|
||||
@@ -278,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
|
||||
@@ -289,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
|
||||
|
||||
@@ -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>>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { EncString } from "@bitwarden/sdk-internal";
|
||||
|
||||
export class OrganizationUserRestoreRequest {
|
||||
defaultUserCollectionName: EncString | undefined;
|
||||
|
||||
constructor(defaultUserCollectionName?: EncString) {
|
||||
this.defaultUserCollectionName = defaultUserCollectionName;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -359,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[],
|
||||
@@ -373,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",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,12 +194,7 @@ export class VaultItemsComponent<C extends CipherViewLike> implements OnDestroy
|
||||
return this.searchService.searchCiphers(
|
||||
userId,
|
||||
searchText,
|
||||
[
|
||||
filter,
|
||||
this.deletedFilter,
|
||||
...(this.deleted ? [] : [this.archivedFilter]),
|
||||
restrictedTypeFilter,
|
||||
],
|
||||
[filter, restrictedTypeFilter],
|
||||
allCiphers,
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -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";
|
||||
|
||||
204
libs/angular/src/vault/services/custom-nudges-services/README.md
Normal file
204
libs/angular/src/vault/services/custom-nudges-services/README.md
Normal 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],
|
||||
}),
|
||||
```
|
||||
@@ -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(
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -54,6 +54,12 @@ export class VaultFilter {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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";`
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./svgs";
|
||||
export * from "./icon-service";
|
||||
export * from "./svg";
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
25
libs/assets/src/svg/svg.ts
Normal file
25
libs/assets/src/svg/svg.ts
Normal 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]);
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
@@ -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"/>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
@@ -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"/>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// this svg includes the Yubico 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";
|
||||
// this svg includes the Yubico logo, which contains colors not part of our bitwarden theme colors
|
||||
|
||||
export const TwoFactorAuthYubicoIcon = svgIcon`
|
||||
import { svg } from "../svg";
|
||||
|
||||
export const TwoFactorAuthYubicoIcon = svg`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 121 33">
|
||||
<g fill="#84BD00" clip-path="url(#two-factor-auth-yubico-clip)">
|
||||
<path
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { svgIcon } from "../icon-service";
|
||||
import { svg } from "../svg";
|
||||
|
||||
export const TwoFactorTimeoutIcon = svgIcon`
|
||||
export const TwoFactorTimeoutIcon = svg`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="5 4 87 87">
|
||||
<circle cx="48" cy="48" r="42" class="tw-fill-illustration-bg-primary tw-stroke-illustration-outline" stroke-width="2"/>
|
||||
<rect width="18" height="14" x="12" y="70" class="tw-fill-illustration-bg-secondary tw-stroke-illustration-outline" stroke-width="2" rx="2"/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { svgIcon } from "../icon-service";
|
||||
import { svg } from "../svg";
|
||||
|
||||
export const UnlockedIcon = svgIcon`
|
||||
export const UnlockedIcon = svg`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0.99 80 73.35">
|
||||
<path class="tw-fill-illustration-bg-primary" d="M11.666 32.667c0-5.523 3.806-10 8.5-10h39.667c4.694 0 8.5 4.477 8.5 10v31.667c0 5.522-3.806 10-8.5 10H20.166c-4.694 0-8.5-4.478-8.5-10V32.667Z"/>
|
||||
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M59.833 24.334H20.166c-3.912 0-7.083 3.73-7.083 8.333v31.667c0 4.602 3.171 8.333 7.083 8.333h39.667c3.912 0 7.083-3.731 7.083-8.333V32.667c0-4.602-3.171-8.333-7.083-8.333Zm-39.667-1.667c-4.694 0-8.5 4.477-8.5 10v31.667c0 5.522 3.806 10 8.5 10h39.667c4.694 0 8.5-4.478 8.5-10V32.667c0-5.523-3.806-10-8.5-10H20.166Z" clip-rule="evenodd"/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { svgIcon } from "../icon-service";
|
||||
import { svg } from "../svg";
|
||||
|
||||
export const UserLockIcon = svgIcon`
|
||||
export const UserLockIcon = 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 15a5 5 0 0 1 5-5h45a5 5 0 0 1 5 5v31.667a5 5 0 0 1-5 5H5a5 5 0 0 1-5-5V15Z"/>
|
||||
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M50 11.667H5A3.333 3.333 0 0 0 1.667 15v31.667A3.333 3.333 0 0 0 5 50h45a3.333 3.333 0 0 0 3.333-3.333V15A3.333 3.333 0 0 0 50 11.667ZM5 10a5 5 0 0 0-5 5v31.667a5 5 0 0 0 5 5h45a5 5 0 0 0 5-5V15a5 5 0 0 0-5-5H5Z" clip-rule="evenodd"/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { svgIcon } from "../icon-service";
|
||||
import { svg } from "../svg";
|
||||
|
||||
export const UserVerificationBiometricsIcon = svgIcon`
|
||||
export const UserVerificationBiometricsIcon = svg`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4 4 88 88">
|
||||
<path class="tw-fill-illustration-bg-primary" d="M4 10a6 6 0 0 1 6-6h50a6 6 0 0 1 6 6v50a6 6 0 0 1-6 6H10a6 6 0 0 1-6-6V10Z"/>
|
||||
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M60 6H10a4 4 0 0 0-4 4v50a4 4 0 0 0 4 4h50a4 4 0 0 0 4-4V10a4 4 0 0 0-4-4ZM10 4a6 6 0 0 0-6 6v50a6 6 0 0 0 6 6h50a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10Z" clip-rule="evenodd"/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { svgIcon } from "../icon-service";
|
||||
import { svg } from "../svg";
|
||||
|
||||
export const VaultOpen = svgIcon`
|
||||
export const VaultOpen = svg`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 10 96 80">
|
||||
<path class="tw-fill-illustration-bg-primary" d="M10 80h14l-2 10H12l-2-10Z"/>
|
||||
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="m10 80 2 10h10l2-10H10Zm2.44 2 1.2 6h6.72l1.2-6h-9.12Z" clip-rule="evenodd"/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { svgIcon } from "../icon-service";
|
||||
import { svg } from "../svg";
|
||||
|
||||
export const VaultIcon = svgIcon`
|
||||
export const VaultIcon = svg`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 8.33 80 66.67">
|
||||
<path class="tw-fill-illustration-bg-primary" d="M8.333 66.667H20L18.333 75H10l-1.667-8.333Z"/>
|
||||
<path class="tw-fill-illustration-outline" fill-rule="evenodd" d="M8.333 66.667 10 75h8.333L20 66.667H8.333Zm2.033 1.667 1 5h5.601l1-5h-7.6Z" clip-rule="evenodd"/>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { svgIcon } from "../icon-service";
|
||||
import { svg } from "../svg";
|
||||
|
||||
export const VaultInactive = svgIcon`
|
||||
export const VaultInactive = 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="M7.16151 14.7066C7.57847 15.1817 8.12811 15.5302 8.75 15.6897V16.5488C8.75 16.963 9.08579 17.2988 9.5 17.2988C9.91421 17.2988 10.25 16.963 10.25 16.5488V15.6993C10.881 15.5462 11.4395 15.199 11.8635 14.7222L12.6407 15.1704C12.9995 15.3773 13.4584 15.2544 13.6657 14.8957C13.873 14.5371 13.7501 14.0786 13.3913 13.8717L12.5794 13.4035C12.6587 13.1163 12.7012 12.8131 12.7012 12.4997C12.7012 12.1815 12.6574 11.874 12.5758 11.583L13.3929 11.1118C13.7517 10.9048 13.8745 10.4464 13.6673 10.0877C13.46 9.72912 13.0011 9.60616 12.6423 9.8131L11.8547 10.2673C11.4318 9.79552 10.8766 9.45211 10.25 9.30002V8.43391C10.25 8.0197 9.91421 7.68391 9.5 7.68391C9.08579 7.68391 8.75 8.0197 8.75 8.43391V9.30968C8.13244 9.46809 7.58613 9.81284 7.17024 10.2828L6.35577 9.8131C5.99696 9.60616 5.53805 9.72912 5.33077 10.0877C5.1235 10.4464 5.24635 10.9048 5.60516 11.1118L6.45732 11.6032C6.37929 11.8882 6.33754 12.1889 6.33754 12.4997C6.33754 12.8058 6.37804 13.1021 6.45381 13.3832L5.60676 13.8717C5.24794 14.0786 5.12509 14.5371 5.33237 14.8957C5.53964 15.2544 5.99855 15.3773 6.35737 15.1704L7.16151 14.7066ZM7.83754 12.4997C7.83754 13.5327 8.636 14.2864 9.51935 14.2864C10.4027 14.2864 11.2012 13.5327 11.2012 12.4997C11.2012 11.4667 10.4027 10.713 9.51935 10.713C8.636 10.713 7.83754 11.4667 7.83754 12.4997Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7 5C21.418 5 22 5.58203 22 6.3V18.6838C22 19.4018 21.418 19.9838 20.7 19.9838H19.3105V20.2957C19.3105 20.7099 18.9748 21.0457 18.5605 21.0457C18.1463 21.0457 17.8105 20.7099 17.8105 20.2957V19.9838H6.18555V20.2957C6.18555 20.7099 5.84976 21.0457 5.43555 21.0457C5.02133 21.0457 4.68555 20.7099 4.68555 20.2957V19.9838H3.3C2.58203 19.9838 2 19.4018 2 18.6838V6.3C2 5.58203 2.58203 5 3.3 5H20.7ZM20.5 16.0509V18.4838H3.5V6.5H20.5V8.93202H19.1875C18.7733 8.93202 18.4375 9.26781 18.4375 9.68202C18.4375 10.0962 18.7733 10.432 19.1875 10.432H20.5V14.5509H19.1875C18.7733 14.5509 18.4375 14.8867 18.4375 15.3009C18.4375 15.7152 18.7733 16.0509 19.1875 16.0509H20.5Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
export const VaultActive = svgIcon`
|
||||
export const VaultActive = svg`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.5034 18.4716H3.50201L9.67797 14.294C10.3648 14.1965 10.7743 13.8378 10.9689 13.4272L20.5034 6.47314V18.4716Z" class="tw-fill-primary-100" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.16151 14.7066C7.57847 15.1817 8.12811 15.5302 8.75 15.6897V16.5488C8.75 16.963 9.08579 17.2988 9.5 17.2988C9.91421 17.2988 10.25 16.963 10.25 16.5488V15.6993C10.881 15.5462 11.4395 15.199 11.8635 14.7222L12.6407 15.1704C12.9995 15.3773 13.4584 15.2544 13.6657 14.8957C13.873 14.5371 13.7501 14.0786 13.3913 13.8717L12.5794 13.4035C12.6587 13.1163 12.7012 12.8131 12.7012 12.4997C12.7012 12.1815 12.6574 11.874 12.5758 11.583L13.3929 11.1118C13.7517 10.9048 13.8745 10.4464 13.6673 10.0877C13.46 9.72912 13.0011 9.60616 12.6423 9.8131L11.8547 10.2673C11.4318 9.79552 10.8766 9.45211 10.25 9.30002V8.43391C10.25 8.0197 9.91421 7.68391 9.5 7.68391C9.08579 7.68391 8.75 8.0197 8.75 8.43391V9.30968C8.13244 9.46809 7.58613 9.81284 7.17024 10.2828L6.35577 9.8131C5.99696 9.60616 5.53805 9.72912 5.33077 10.0877C5.1235 10.4464 5.24635 10.9048 5.60516 11.1118L6.45732 11.6032C6.37929 11.8882 6.33754 12.1889 6.33754 12.4997C6.33754 12.8058 6.37804 13.1021 6.45381 13.3832L5.60676 13.8717C5.24794 14.0786 5.12509 14.5371 5.33237 14.8957C5.53964 15.2544 5.99855 15.3773 6.35737 15.1704L7.16151 14.7066ZM7.83754 12.4997C7.83754 13.5327 8.636 14.2864 9.51935 14.2864C10.4027 14.2864 11.2012 13.5327 11.2012 12.4997C11.2012 11.4667 10.4027 10.713 9.51935 10.713C8.636 10.713 7.83754 11.4667 7.83754 12.4997Z" class="tw-fill-primary-600" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { svgIcon } from "../icon-service";
|
||||
import { svg } from "../svg";
|
||||
|
||||
export const WaveIcon = svgIcon`
|
||||
export const WaveIcon = svg`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="3.33 3.33 73.33 73.33">
|
||||
<path class="tw-fill-illustration-bg-primary" d="m16.921 66.662-1.673-1.674a17.425 17.425 0 0 1-3.735-19.09l3.215-7.627c.717-1.7 1.283-3.46 1.692-5.26l3.285-14.46a2.222 2.222 0 0 1 2.666-1.673 5.079 5.079 0 0 1 3.855 5.857l-2.025 11.138a4.14 4.14 0 0 0 .58 2.963c.433.68 1.443.626 1.8-.097l9.292-18.772L41.46 8.09a2.163 2.163 0 0 1 2.85-.87 4.944 4.944 0 0 1 2.158 6.735l-3.95 7.46-4.144 10.261a3.619 3.619 0 0 0 .252 3.218.905.905 0 0 0 1.478.105l9.44-11.595 10.003-10.718a2.359 2.359 0 0 1 3.474.026 5.392 5.392 0 0 1-.048 7.287l-7.595 8.178-8.34 10.405a3.512 3.512 0 0 0-.649 3.12.878.878 0 0 0 1.344.493l11.092-7.608 11.191-5.598a2.29 2.29 0 0 1 3.215 1.375 5.236 5.236 0 0 1-2.74 6.26l-8.625 4.14c-.26.124-.51.267-.749.428l-9.414 6.322a3.73 3.73 0 0 0-1.64 3.383.933.933 0 0 0 1.284.792l8.651-3.544 8.318-2.138a1.731 1.731 0 0 1 2.057 1.084 3.957 3.957 0 0 1-2.402 5.088l-6.886 2.428-9.987 4.926c-.446.22-.863.495-1.241.818l-8.522 7.274c-7.143 6.098-17.774 5.68-24.415-.962Z"/>
|
||||
<path class="tw-fill-illustration-bg-secondary" fill-rule="evenodd" d="M24.185 36.4a.833.833 0 0 1 1.175-.082c6.917 6.003 7.658 16.477 1.655 23.394a.833.833 0 1 1-1.259-1.093c5.4-6.221 4.734-15.643-1.488-21.043a.833.833 0 0 1-.083-1.175Z" clip-rule="evenodd"/>
|
||||
|
||||
@@ -10,7 +10,9 @@ import {
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { MasterPasswordServiceAbstraction } 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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
@@ -209,6 +211,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
constructor(
|
||||
private auditService: AuditService,
|
||||
private cipherService: CipherService,
|
||||
private configService: ConfigService,
|
||||
private dialogService: DialogService,
|
||||
private formBuilder: FormBuilder,
|
||||
private i18nService: I18nService,
|
||||
@@ -312,7 +315,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (!this.email) {
|
||||
throw new Error("Email is required to create master key.");
|
||||
throw new Error("Email not found.");
|
||||
}
|
||||
|
||||
// 1. Determine kdfConfig
|
||||
@@ -320,13 +323,13 @@ export class InputPasswordComponent implements OnInit {
|
||||
this.kdfConfig = DEFAULT_KDF_CONFIG;
|
||||
} else {
|
||||
if (!this.userId) {
|
||||
throw new Error("userId not passed down");
|
||||
throw new Error("userId not found.");
|
||||
}
|
||||
this.kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId));
|
||||
}
|
||||
|
||||
if (this.kdfConfig == null) {
|
||||
throw new Error("KdfConfig is required to create master key.");
|
||||
throw new Error("KdfConfig not found.");
|
||||
}
|
||||
|
||||
const salt =
|
||||
@@ -334,7 +337,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
? await firstValueFrom(this.masterPasswordService.saltForUser$(this.userId))
|
||||
: this.masterPasswordService.emailToSalt(this.email);
|
||||
if (salt == null) {
|
||||
throw new Error("Salt is required to create master key.");
|
||||
throw new Error("Salt not found.");
|
||||
}
|
||||
|
||||
// 2. Verify current password is correct (if necessary)
|
||||
@@ -361,6 +364,41 @@ export class InputPasswordComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
// When you unwind the flag in PM-28143, also remove the ConfigService if it is un-used.
|
||||
const newApisWithInputPasswordFlagEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM27086_UpdateAuthenticationApisForInputPassword,
|
||||
);
|
||||
|
||||
if (newApisWithInputPasswordFlagEnabled) {
|
||||
// 4. Build a PasswordInputResult object
|
||||
const passwordInputResult: PasswordInputResult = {
|
||||
newPassword,
|
||||
kdfConfig: this.kdfConfig,
|
||||
salt,
|
||||
newPasswordHint,
|
||||
newApisWithInputPasswordFlagEnabled, // To be removed in PM-28143
|
||||
};
|
||||
|
||||
if (
|
||||
this.flow === InputPasswordFlow.ChangePassword ||
|
||||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||
) {
|
||||
passwordInputResult.currentPassword = currentPassword;
|
||||
}
|
||||
|
||||
if (this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
|
||||
passwordInputResult.rotateUserKey = this.formGroup.controls.rotateUserKey?.value;
|
||||
}
|
||||
|
||||
// 5. Emit and return PasswordInputResult object
|
||||
this.onPasswordFormSubmit.emit(passwordInputResult);
|
||||
return passwordInputResult;
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
* The following code (within this `try`) to be removed in PM-28143
|
||||
*******************************************************************/
|
||||
|
||||
// 4. Create cryptographic keys and build a PasswordInputResult object
|
||||
const newMasterKey = await this.keyService.makeMasterKey(
|
||||
newPassword,
|
||||
|
||||
@@ -6,14 +6,12 @@ import * as stories from "./input-password.stories.ts";
|
||||
|
||||
# InputPassword Component
|
||||
|
||||
The `InputPasswordComponent` allows a user to enter master password related credentials.
|
||||
Specifically, it does the following:
|
||||
The `InputPasswordComponent` allows a user to enter a new master password for the purpose of setting
|
||||
an initial password or changing an existing password. Specifically, it does the following:
|
||||
|
||||
1. Displays form fields in the UI
|
||||
2. Validates form fields
|
||||
3. Generates cryptographic properties based on the form inputs (e.g. `newMasterKey`,
|
||||
`newServerMasterKeyHash`, etc.)
|
||||
4. Emits the generated properties to the parent component
|
||||
3. Emits values to the parent component
|
||||
|
||||
The `InputPasswordComponent` is central to our set/change password flows, allowing us to keep our
|
||||
form UI and validation logic consistent. As such, it is intended for re-use in different set/change
|
||||
@@ -30,7 +28,6 @@ those values as needed.
|
||||
- [The InputPasswordFlow](#the-inputpasswordflow)
|
||||
- [Use Cases](#use-cases)
|
||||
- [HTML - Form Fields](#html---form-fields)
|
||||
- [TypeScript - Credential Generation](#typescript---credential-generation)
|
||||
- [Difference between SetInitialPasswordAccountRegistration and SetInitialPasswordAuthedUser](#difference-between-setinitialpasswordaccountregistration-and-setinitialpasswordautheduser)
|
||||
- [Validation](#validation)
|
||||
- [Submit Logic](#submit-logic)
|
||||
@@ -44,20 +41,20 @@ those values as needed.
|
||||
**Required**
|
||||
|
||||
- `flow` - the parent component must provide an `InputPasswordFlow`, which is used to determine
|
||||
which form input elements will be displayed in the UI and which cryptographic keys will be created
|
||||
and emitted. [Click here](#the-inputpasswordflow) to learn more about the different
|
||||
`InputPasswordFlow` options.
|
||||
which form input elements will be displayed in the UI and which values will be emitted.
|
||||
[Click here](#the-inputpasswordflow) to learn more about the different `InputPasswordFlow`
|
||||
options.
|
||||
|
||||
**Optional (sometimes)**
|
||||
|
||||
These two `@Inputs` are optional on some flows, but required on others. Therefore these `@Inputs`
|
||||
are not marked as `{ required: true }`, but there _is_ component logic that ensures (requires) that
|
||||
the `email` and/or `userId` is present in certain flows, while not present in other flows.
|
||||
These `@Inputs` are optional on some flows, but required on others. Therefore these `@Inputs` are
|
||||
not marked as `{ required: true }`, but there _is_ component logic that ensures (requires) that the
|
||||
`email` and/or `userId` is present in certain flows, while not present in other flows.
|
||||
|
||||
- `email` - allows the `InputPasswordComponent` to generate a master key
|
||||
- `email` - allows the `InputPasswordComponent` to use the email as a salt (if needed)
|
||||
- `userId` - allows the `InputPasswordComponent` to do things like get the user's `kdfConfig`,
|
||||
verify that a current password is correct, and perform validation prior to user key rotation on
|
||||
the parent
|
||||
verify that a current password is correct, and perform validation prior to user key rotation (if
|
||||
selected) on the parent
|
||||
|
||||
**Optional**
|
||||
|
||||
@@ -87,8 +84,7 @@ These `@Inputs` are truly optional.
|
||||
## The `InputPasswordFlow`
|
||||
|
||||
The `InputPasswordFlow` is a crucial and required `@Input` that influences both the HTML and the
|
||||
credential generation logic of the component. It is important for the dev to understand when to use
|
||||
each flow.
|
||||
logic of the component. It is important for the dev to understand when to use each flow.
|
||||
|
||||
### Use Cases
|
||||
|
||||
@@ -106,8 +102,9 @@ Used in scenarios where we do have an existing and authed user, and thus an acti
|
||||
|
||||
- A "just-in-time" (JIT) provisioned user joins a master password (MP) encryption org and must set
|
||||
their initial password
|
||||
- A "just-in-time" (JIT) provisioned user joins a trusted device encryption (TDE) org with a
|
||||
starting role that requires them to have/set their initial password
|
||||
- A "just-in-time" (JIT) provisioned user joins a trusted device encryption (TDE) org with the reset
|
||||
password permission ("manage account recovery") from the start, which requires them to have/set
|
||||
their initial password
|
||||
- A note on JIT provisioned user flows:
|
||||
- Even though a JIT provisioned user is a brand-new user who was “just” created, we consider
|
||||
them to be an “existing authed user” _from the perspective of the set-password flow_. This is
|
||||
@@ -117,8 +114,9 @@ Used in scenarios where we do have an existing and authed user, and thus an acti
|
||||
registration when a user reaches the `/finish-signup` or `/trial-initiation` page to set their
|
||||
initial password, their account does not yet exist in the database, and will only be created
|
||||
once they set an initial password.
|
||||
- An existing user in a TDE org logs in after the org admin upgraded the user to a role that now
|
||||
requires them to have/set their initial password
|
||||
- An existing user in a TDE org logs in after an org admin upgraded the user to have the reset
|
||||
password persmission ("manage account recovery"), which now requires the user to have/set their
|
||||
initial password
|
||||
- An existing user logs in after their org admin offboarded the org from TDE, and the user must now
|
||||
have/set their initial password<br /><br />
|
||||
|
||||
@@ -126,7 +124,7 @@ Used in scenarios where we do have an existing and authed user, and thus an acti
|
||||
|
||||
Used in scenarios where we simply want to offer the user the ability to change their password:
|
||||
|
||||
- User clicks an org email invite link an logs in with their password which does not meet the org's
|
||||
- User clicks an org email invite link and logs in with their password which does not meet the org's
|
||||
policy requirements
|
||||
- User logs in with password that does not meet the org's policy requirements
|
||||
- User logs in after their password was reset via Account Recovery (and now they must change their
|
||||
@@ -156,26 +154,10 @@ which form field UI elements get displayed.
|
||||
|
||||
<br />
|
||||
|
||||
### TypeScript - Credential Generation
|
||||
|
||||
- **`SetInitialPasswordAccountRegistration`** and **`SetInitialPasswordAuthedUser`**
|
||||
- These flows involve a user setting their password for the first time. Therefore on submit the
|
||||
component will only generate new credentials (`newMasterKey`) and not current credentials
|
||||
(`currentMasterKey`).<br /><br />
|
||||
- **`ChangePassword`** and **`ChangePasswordWithOptionalUserKeyRotation`**
|
||||
- These flows both require the user to enter a current password along with a new password.
|
||||
Therefore on submit the component will generate current credentials (`currentMasterKey`) along
|
||||
with new credentials (`newMasterKey`).<br /><br />
|
||||
- **`ChangePasswordDelegation`**
|
||||
- This flow does not generate any credentials, but simply validates the new password and emits it
|
||||
up to the parent.
|
||||
|
||||
<br />
|
||||
|
||||
### Difference between `SetInitialPasswordAccountRegistration` and `SetInitialPasswordAuthedUser`
|
||||
|
||||
These two flows are similar in that they display the same form fields and only generate new
|
||||
credentials, but we need to keep them separate for the following reasons:
|
||||
These two flows are similar in that they display the same form fields, but we need to keep them
|
||||
separate for the following reasons:
|
||||
|
||||
- `SetInitialPasswordAccountRegistration` involves scenarios where we have no existing user, and
|
||||
**thus NO active account `userId`**:
|
||||
@@ -183,7 +165,7 @@ credentials, but we need to keep them separate for the following reasons:
|
||||
and **thus an active account `userId`**:
|
||||
|
||||
The presence or absence of an active account `userId` is important because it determines how we get
|
||||
the correct `kdfConfig` prior to key generation:
|
||||
the correct `kdfConfig`:
|
||||
|
||||
- If there is no `userId` passed down from the parent, we default to `DEFAULT_KDF_CONFIG`
|
||||
- If there is a `userId` passed down from the parent, we get the `kdfConfig` from state using the
|
||||
@@ -223,25 +205,16 @@ When the form is submitted, the `InputPasswordComponent` does the following in o
|
||||
checkbox)
|
||||
- Checks that the new password adheres to any enforced master password policies that were
|
||||
optionally passed down by the parent
|
||||
2. Uses the form inputs to create cryptographic properties (`newMasterKey`,
|
||||
`newServerMasterKeyHash`, etc.)
|
||||
3. Emits those cryptographic properties up to the parent (along with other values defined in
|
||||
`PasswordInputResult`) to be used by the parent as needed.
|
||||
2. Emits values up to the parent (along with other values defined in `PasswordInputResult`) to be
|
||||
used by the parent as needed.
|
||||
|
||||
```typescript
|
||||
export interface PasswordInputResult {
|
||||
currentPassword?: string;
|
||||
currentMasterKey?: MasterKey;
|
||||
currentServerMasterKeyHash?: string;
|
||||
currentLocalMasterKeyHash?: string;
|
||||
|
||||
newPassword: string;
|
||||
newPasswordHint?: string;
|
||||
newMasterKey?: MasterKey;
|
||||
newServerMasterKeyHash?: string;
|
||||
newLocalMasterKeyHash?: string;
|
||||
|
||||
kdfConfig?: KdfConfig;
|
||||
salt?: MasterPasswordSalt;
|
||||
newPasswordHint?: string;
|
||||
rotateUserKey?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -10,6 +10,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@@ -59,6 +60,13 @@ export default {
|
||||
getAllDecrypted: () => Promise.resolve([]),
|
||||
},
|
||||
},
|
||||
// Can remove ConfigService from component and stories in PM-28143 (if it is no longer used)
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
getFeatureFlag: () => false, // default to false since flag does not effect UI
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: KdfConfigService,
|
||||
useValue: {
|
||||
|
||||
@@ -10,6 +10,20 @@ export interface PasswordInputResult {
|
||||
newPasswordHint?: string;
|
||||
rotateUserKey?: boolean;
|
||||
|
||||
/**
|
||||
* Temporary property that persists the flag state through the entire set/change password process.
|
||||
* This allows flows to consume this value instead of re-checking the flag state via ConfigService themselves.
|
||||
*
|
||||
* The ChangePasswordDelegation flows (Emergency Access Takeover and Account Recovery), however, only ever
|
||||
* require a raw newPassword from the InputPasswordComponent regardless of whether the flag is on or off.
|
||||
* Flagging for those 2 flows will be done via the ConfigService in their respective services.
|
||||
*
|
||||
* To be removed in PM-28143
|
||||
*/
|
||||
newApisWithInputPasswordFlagEnabled?: boolean;
|
||||
|
||||
// The deprecated properties below will be removed in PM-28143: https://bitwarden.atlassian.net/browse/PM-28143
|
||||
|
||||
/** @deprecated This low-level cryptographic state will be removed. It will be replaced by high level calls to masterpassword service, in the consumers of this interface. */
|
||||
currentMasterKey?: MasterKey;
|
||||
/** @deprecated */
|
||||
|
||||
@@ -278,13 +278,6 @@ describe("LoginDecryptionOptionsComponent", () => {
|
||||
const expectedUserKey = new SymmetricCryptoKey(new Uint8Array(mockUserKeyBytes));
|
||||
|
||||
// Verify keys were set
|
||||
expect(keyService.setPrivateKey).toHaveBeenCalledWith(mockPrivateKey, mockUserId);
|
||||
expect(keyService.setSignedPublicKey).toHaveBeenCalledWith(mockSignedPublicKey, mockUserId);
|
||||
expect(keyService.setUserSigningKey).toHaveBeenCalledWith(mockSigningKey, mockUserId);
|
||||
expect(securityStateService.setAccountSecurityState).toHaveBeenCalledWith(
|
||||
mockSecurityState,
|
||||
mockUserId,
|
||||
);
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
V2: {
|
||||
|
||||
@@ -34,11 +34,6 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
|
||||
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
|
||||
import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service";
|
||||
import {
|
||||
SignedPublicKey,
|
||||
SignedSecurityState,
|
||||
WrappedSigningKey,
|
||||
} from "@bitwarden/common/key-management/types";
|
||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@@ -322,23 +317,6 @@ export class LoginDecryptionOptionsComponent implements OnInit {
|
||||
register_result.account_cryptographic_state,
|
||||
userId,
|
||||
);
|
||||
// Legacy individual states
|
||||
await this.keyService.setPrivateKey(
|
||||
register_result.account_cryptographic_state.V2.private_key,
|
||||
userId,
|
||||
);
|
||||
await this.keyService.setSignedPublicKey(
|
||||
register_result.account_cryptographic_state.V2.signed_public_key as SignedPublicKey,
|
||||
userId,
|
||||
);
|
||||
await this.keyService.setUserSigningKey(
|
||||
register_result.account_cryptographic_state.V2.signing_key as WrappedSigningKey,
|
||||
userId,
|
||||
);
|
||||
await this.securityStateService.setAccountSecurityState(
|
||||
register_result.account_cryptographic_state.V2.security_state as SignedSecurityState,
|
||||
userId,
|
||||
);
|
||||
|
||||
// TDE unlock
|
||||
await this.deviceTrustService.setDeviceKey(
|
||||
|
||||
@@ -9,7 +9,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { TwoFactorTimeoutIcon } from "@bitwarden/assets/svg";
|
||||
// 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 { ButtonModule, IconModule } from "@bitwarden/components";
|
||||
import { ButtonModule, SvgModule } from "@bitwarden/components";
|
||||
|
||||
/**
|
||||
* RegistrationLinkExpiredComponentData
|
||||
@@ -24,7 +24,7 @@ export interface RegistrationLinkExpiredComponentData {
|
||||
@Component({
|
||||
selector: "auth-registration-link-expired",
|
||||
templateUrl: "./registration-link-expired.component.html",
|
||||
imports: [CommonModule, JslibModule, RouterModule, IconModule, ButtonModule],
|
||||
imports: [CommonModule, JslibModule, RouterModule, SvgModule, ButtonModule],
|
||||
})
|
||||
export class RegistrationLinkExpiredComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
ButtonModule,
|
||||
CheckboxModule,
|
||||
FormFieldModule,
|
||||
IconModule,
|
||||
SvgModule,
|
||||
LinkModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
@@ -54,7 +54,7 @@ const DEFAULT_MARKETING_EMAILS_PREF_BY_REGION: Record<Region, boolean> = {
|
||||
CheckboxModule,
|
||||
ButtonModule,
|
||||
LinkModule,
|
||||
IconModule,
|
||||
SvgModule,
|
||||
RegistrationEnvSelectorComponent,
|
||||
],
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user