mirror of
https://github.com/bitwarden/browser
synced 2026-02-10 21:50:15 +00:00
Merge branch 'main' into nathan/fix-autofill-signing
# Conflicts: # apps/desktop/resources/entitlements.mas.plist
This commit is contained in:
13
.github/CODEOWNERS
vendored
13
.github/CODEOWNERS
vendored
@@ -29,12 +29,12 @@ libs/tools @bitwarden/team-tools-dev
|
||||
bitwarden_license/bit-web/src/app/tools @bitwarden/team-tools-dev
|
||||
bitwarden_license/bit-common/src/tools @bitwarden/team-tools-dev
|
||||
|
||||
## Localization/Crowdin (Tools team)
|
||||
apps/browser/src/_locales @bitwarden/team-tools-dev
|
||||
apps/browser/store/locales @bitwarden/team-tools-dev
|
||||
apps/cli/src/locales @bitwarden/team-tools-dev
|
||||
apps/desktop/src/locales @bitwarden/team-tools-dev
|
||||
apps/web/src/locales @bitwarden/team-tools-dev
|
||||
## Localization/Crowdin (Platform and Tools team)
|
||||
apps/browser/src/_locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev
|
||||
apps/browser/store/locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev
|
||||
apps/cli/src/locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev
|
||||
apps/desktop/src/locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev
|
||||
apps/web/src/locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev
|
||||
|
||||
## Vault team files ##
|
||||
apps/browser/src/vault @bitwarden/team-vault-dev
|
||||
@@ -131,6 +131,7 @@ apps/web/src/app/key-management @bitwarden/team-key-management-dev
|
||||
apps/browser/src/key-management @bitwarden/team-key-management-dev
|
||||
apps/cli/src/key-management @bitwarden/team-key-management-dev
|
||||
libs/key-management @bitwarden/team-key-management-dev
|
||||
libs/key-management-ui @bitwarden/team-key-management-dev
|
||||
libs/common/src/key-management @bitwarden/team-key-management-dev
|
||||
|
||||
apps/desktop/destkop_native/core/src/biometric/ @bitwarden/team-key-management-dev
|
||||
|
||||
64
.github/codecov.yml
vendored
64
.github/codecov.yml
vendored
@@ -1,2 +1,66 @@
|
||||
ignore:
|
||||
- "**/*.spec.ts" # Tests
|
||||
|
||||
component_management:
|
||||
default_rules:
|
||||
statuses:
|
||||
- type: project
|
||||
target: auto
|
||||
individual_components:
|
||||
- component_id: key-management-biometrics
|
||||
name: Key Management - Biometrics
|
||||
paths:
|
||||
- apps/browser/src/key-management/biometrics/**
|
||||
- apps/cli/src/key-management/cli-biometrics-service.ts
|
||||
- apps/desktop/destkop_native/core/src/biometric/**
|
||||
- apps/desktop/src/key-management/biometrics/**
|
||||
- apps/desktop/src/services/biometric-message-handler.service.ts
|
||||
- apps/web/src/app/key-management/web-biometric.service.ts
|
||||
- libs/key-management/src/biometrics/**
|
||||
- component_id: key-management-lock
|
||||
name: Key Management - Lock
|
||||
paths:
|
||||
- apps/browser/src/key-management/lock/**
|
||||
- apps/desktop/src/key-management/lock/**
|
||||
- apps/web/src/app/key-management/lock/**
|
||||
- libs/key-management-ui/src/lock/**
|
||||
- component_id: key-management-ipc
|
||||
name: Key Management - IPC
|
||||
paths:
|
||||
- apps/browser/src/background/nativeMessaging.background.ts
|
||||
- apps/desktop/src/services/native-messaging.service.ts
|
||||
- component_id: key-management-key-rotation
|
||||
name: Key Management - Key Rotation
|
||||
paths:
|
||||
- apps/web/src/app/key-management/key-rotation/**
|
||||
- apps/web/src/app/key-management/migrate-encryption/**
|
||||
- libs/key-management/src/user-asymmetric-key-regeneration/**
|
||||
- component_id: key-management-process-reload
|
||||
name: Key Management - Process Reload
|
||||
paths:
|
||||
- apps/web/src/app/key-management/services/web-process-reload.service.ts
|
||||
- libs/common/src/key-management/services/default-process-reload.service.ts
|
||||
- component_id: key-management-keys
|
||||
name: Key Management - Keys
|
||||
paths:
|
||||
- libs/key-management/src/kdf-config.service.ts
|
||||
- libs/key-management/src/key.service.ts
|
||||
- libs/common/src/key-management/master-password/**
|
||||
- component_id: key-management-crypto
|
||||
name: Key Management - Crypto
|
||||
paths:
|
||||
- libs/common/src/key-management/crypto/**
|
||||
- component_id: key-management
|
||||
name: Key Management
|
||||
paths:
|
||||
- apps/browser/src/key-management/**
|
||||
- apps/browser/src/background/nativeMessaging.background.ts
|
||||
- apps/cli/src/key-management/**
|
||||
- apps/desktop/destkop_native/core/src/biometric/**
|
||||
- apps/desktop/src/key-management/**
|
||||
- apps/desktop/src/services/biometric-message-handler.service.ts
|
||||
- apps/desktop/src/services/native-messaging.service.ts
|
||||
- apps/web/src/app/key-managemen/**
|
||||
- libs/common/src/key-management/**
|
||||
- libs/key-management/**
|
||||
- libs/key-management-ui/**
|
||||
|
||||
42
.github/workflows/test.yml
vendored
42
.github/workflows/test.yml
vendored
@@ -103,15 +103,15 @@ jobs:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-22.04
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
- macos-14
|
||||
- windows-2022
|
||||
|
||||
steps:
|
||||
- name: Check Rust version
|
||||
run: rustup --version
|
||||
|
||||
- name: Install gnome-keyring
|
||||
if: ${{ matrix.os=='ubuntu-latest' }}
|
||||
if: ${{ matrix.os=='ubuntu-22.04' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gnome-keyring dbus-x11
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
run: cargo build
|
||||
|
||||
- name: Test Ubuntu
|
||||
if: ${{ matrix.os=='ubuntu-latest' }}
|
||||
if: ${{ matrix.os=='ubuntu-22.04' }}
|
||||
working-directory: ./apps/desktop/desktop_native
|
||||
run: |
|
||||
eval "$(dbus-launch --sh-syntax)"
|
||||
@@ -135,11 +135,41 @@ jobs:
|
||||
cargo test -- --test-threads=1
|
||||
|
||||
- name: Test macOS
|
||||
if: ${{ matrix.os=='macos-latest' }}
|
||||
if: ${{ matrix.os=='macos-14' }}
|
||||
working-directory: ./apps/desktop/desktop_native
|
||||
run: cargo test -- --test-threads=1
|
||||
|
||||
- name: Test Windows
|
||||
if: ${{ matrix.os=='windows-latest'}}
|
||||
if: ${{ matrix.os=='windows-2022'}}
|
||||
working-directory: ./apps/desktop/desktop_native/core
|
||||
run: cargo test -- --test-threads=1
|
||||
|
||||
rust-coverage:
|
||||
name: Rust Coverage
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install rust
|
||||
uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203 # stable
|
||||
with:
|
||||
toolchain: stable
|
||||
components: llvm-tools
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5
|
||||
with:
|
||||
workspaces: "apps/desktop/desktop_native -> target"
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
run: cargo install cargo-llvm-cov --version 0.6.16
|
||||
|
||||
- name: Generate coverage
|
||||
working-directory: ./apps/desktop/desktop_native
|
||||
run: cargo llvm-cov --all-features --lcov --output-path lcov.info --workspace --no-cfg-coverage
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
|
||||
with:
|
||||
files: ./apps/desktop/desktop_native/lcov.info
|
||||
|
||||
@@ -52,7 +52,7 @@ foreach ($subBuildPath in $subBuildPaths) {
|
||||
"--verbose",
|
||||
"--force",
|
||||
"--sign",
|
||||
"E7C9978F6FBCE0553429185C405E61F5380BE8EB",
|
||||
"4B9662CAB74E8E4F4ECBDD9EDEF2543659D95E3C",
|
||||
"--entitlements",
|
||||
$entitlementsPath
|
||||
)
|
||||
|
||||
@@ -4155,15 +4155,6 @@
|
||||
"itemName": {
|
||||
"message": "Item name"
|
||||
},
|
||||
"cannotRemoveViewOnlyCollections": {
|
||||
"message": "You cannot remove collections with View only permissions: $COLLECTIONS$",
|
||||
"placeholders": {
|
||||
"collections": {
|
||||
"content": "$1",
|
||||
"example": "Work, Personal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"organizationIsDeactivated": {
|
||||
"message": "Organization is deactivated"
|
||||
},
|
||||
@@ -4896,6 +4887,15 @@
|
||||
"extraWide": {
|
||||
"message": "Extra wide"
|
||||
},
|
||||
"cannotRemoveViewOnlyCollections": {
|
||||
"message": "You cannot remove collections with View only permissions: $COLLECTIONS$",
|
||||
"placeholders": {
|
||||
"collections": {
|
||||
"content": "$1",
|
||||
"example": "Work, Personal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"updateDesktopAppOrDisableFingerprintDialogTitle": {
|
||||
"message": "Please update your desktop application"
|
||||
},
|
||||
|
||||
@@ -424,6 +424,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
}
|
||||
|
||||
await this.setupSubmitListenerOnFormlessField(formFieldElement);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,15 +440,16 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
this.formElements.add(formElement);
|
||||
formElement.addEventListener(EVENTS.SUBMIT, this.handleFormFieldSubmitEvent);
|
||||
|
||||
const closesSubmitButton = await this.findSubmitButton(formElement);
|
||||
const closestSubmitButton = await this.findSubmitButton(formElement);
|
||||
|
||||
// If we cannot find a submit button within the form, check for a submit button outside the form.
|
||||
if (!closesSubmitButton) {
|
||||
if (!closestSubmitButton) {
|
||||
await this.setupSubmitListenerOnFormlessField(formFieldElement);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupSubmitButtonEventListeners(closesSubmitButton);
|
||||
this.setupSubmitButtonEventListeners(closestSubmitButton);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,9 +461,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
*/
|
||||
private async setupSubmitListenerOnFormlessField(formFieldElement: FillableFormFieldElement) {
|
||||
if (formFieldElement && !this.fieldsWithSubmitElements.has(formFieldElement)) {
|
||||
const closesSubmitButton = await this.findClosestFormlessSubmitButton(formFieldElement);
|
||||
this.setupSubmitButtonEventListeners(closesSubmitButton);
|
||||
const closestSubmitButton = await this.findClosestFormlessSubmitButton(formFieldElement);
|
||||
|
||||
this.setupSubmitButtonEventListeners(closestSubmitButton);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -747,7 +747,7 @@ describe("AutofillService", () => {
|
||||
jest.spyOn(autofillService as any, "generateFillScript");
|
||||
jest.spyOn(autofillService as any, "generateLoginFillScript");
|
||||
jest.spyOn(logService, "info");
|
||||
jest.spyOn(cipherService, "updateLastUsedDate");
|
||||
jest.spyOn(chrome.runtime, "sendMessage");
|
||||
jest.spyOn(eventCollectionService, "collect");
|
||||
|
||||
const autofillResult = await autofillService.doAutoFill(autofillOptions);
|
||||
@@ -769,7 +769,10 @@ describe("AutofillService", () => {
|
||||
);
|
||||
expect(autofillService["generateLoginFillScript"]).toHaveBeenCalled();
|
||||
expect(logService.info).not.toHaveBeenCalled();
|
||||
expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith(autofillOptions.cipher.id);
|
||||
expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({
|
||||
cipherId: autofillOptions.cipher.id,
|
||||
command: "updateLastUsedDate",
|
||||
});
|
||||
expect(chrome.tabs.sendMessage).toHaveBeenCalledWith(
|
||||
autofillOptions.pageDetails[0].tab.id,
|
||||
{
|
||||
@@ -890,11 +893,11 @@ describe("AutofillService", () => {
|
||||
|
||||
it("skips updating the cipher's last used date if the passed options indicate that we should skip the last used cipher", async () => {
|
||||
autofillOptions.skipLastUsed = true;
|
||||
jest.spyOn(cipherService, "updateLastUsedDate");
|
||||
jest.spyOn(chrome.runtime, "sendMessage");
|
||||
|
||||
await autofillService.doAutoFill(autofillOptions);
|
||||
|
||||
expect(cipherService.updateLastUsedDate).not.toHaveBeenCalled();
|
||||
expect(chrome.runtime.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns early if the fillScript cannot be generated", async () => {
|
||||
|
||||
@@ -463,8 +463,13 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
fillScript.properties.delay_between_operations = 20;
|
||||
|
||||
didAutofill = true;
|
||||
|
||||
if (!options.skipLastUsed) {
|
||||
await this.cipherService.updateLastUsedDate(options.cipher.id);
|
||||
// In order to prevent a UI update send message to background script to update last used date
|
||||
await chrome.runtime.sendMessage({
|
||||
command: "updateLastUsedDate",
|
||||
cipherId: options.cipher.id,
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
|
||||
@@ -317,6 +317,7 @@ describe("CollectAutofillContentService", () => {
|
||||
__form__0: {
|
||||
opid: "__form__0",
|
||||
htmlAction: formAction,
|
||||
htmlClass: null,
|
||||
htmlName: formName,
|
||||
htmlID: formId,
|
||||
htmlMethod: formMethod,
|
||||
@@ -544,6 +545,7 @@ describe("CollectAutofillContentService", () => {
|
||||
__form__0: {
|
||||
opid: "__form__0",
|
||||
htmlAction: formAction1,
|
||||
htmlClass: null,
|
||||
htmlName: formName1,
|
||||
htmlID: formId1,
|
||||
htmlMethod: formMethod1,
|
||||
@@ -551,6 +553,7 @@ describe("CollectAutofillContentService", () => {
|
||||
__form__1: {
|
||||
opid: "__form__1",
|
||||
htmlAction: formAction2,
|
||||
htmlClass: null,
|
||||
htmlName: formName2,
|
||||
htmlID: formId2,
|
||||
htmlMethod: formMethod2,
|
||||
|
||||
@@ -228,6 +228,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
|
||||
opid: formElement.opid,
|
||||
htmlAction: this.getFormActionAttribute(formElement),
|
||||
htmlName: this.getPropertyOrAttribute(formElement, "name"),
|
||||
htmlClass: this.getPropertyOrAttribute(formElement, "class"),
|
||||
htmlID: this.getPropertyOrAttribute(formElement, "id"),
|
||||
htmlMethod: this.getPropertyOrAttribute(formElement, "method"),
|
||||
});
|
||||
@@ -982,8 +983,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
|
||||
const queueLength = this.mutationsQueue.length;
|
||||
|
||||
if (!this.domQueryService.pageContainsShadowDomElements()) {
|
||||
// Checking if a page contains shadowDOM elements is a heavy operation and doesn't have to be done immediately, so we can call this within an idle moment on the event loop.
|
||||
requestIdleCallbackPolyfill(this.checkPageContainsShadowDom, { timeout: 500 });
|
||||
this.checkPageContainsShadowDom();
|
||||
}
|
||||
|
||||
for (let queueIndex = 0; queueIndex < queueLength; queueIndex++) {
|
||||
|
||||
@@ -56,6 +56,7 @@ export class InlineMenuFieldQualificationService
|
||||
"neuer benutzer",
|
||||
"neues passwort",
|
||||
"neue e-mail",
|
||||
"pwdcheck",
|
||||
];
|
||||
private updatePasswordFieldKeywords = [
|
||||
"update password",
|
||||
|
||||
@@ -75,12 +75,16 @@ import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/bill
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { BulkEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/bulk-encrypt.service.implementation";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
|
||||
import { FallbackBulkEncryptService } from "@bitwarden/common/key-management/crypto/services/fallback-bulk-encrypt.service";
|
||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/multithread-encrypt.service.implementation";
|
||||
import { DefaultProcessReloadService } from "@bitwarden/common/key-management/services/default-process-reload.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { Fido2ActiveRequestManager as Fido2ActiveRequestManagerAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction";
|
||||
import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction";
|
||||
@@ -123,10 +127,6 @@ import { ConfigApiService } from "@bitwarden/common/platform/services/config/con
|
||||
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
|
||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
import { BulkEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/bulk-encrypt.service.implementation";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||
import { FallbackBulkEncryptService } from "@bitwarden/common/platform/services/cryptography/fallback-bulk-encrypt.service";
|
||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
||||
import { Fido2ActiveRequestManager } from "@bitwarden/common/platform/services/fido2/fido2-active-request-manager";
|
||||
import { Fido2AuthenticatorService } from "@bitwarden/common/platform/services/fido2/fido2-authenticator.service";
|
||||
import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service";
|
||||
@@ -208,7 +208,7 @@ import {
|
||||
ImportApiServiceAbstraction,
|
||||
ImportService,
|
||||
ImportServiceAbstraction,
|
||||
} from "@bitwarden/importer/core";
|
||||
} from "@bitwarden/importer-core";
|
||||
import {
|
||||
BiometricsService,
|
||||
BiometricStateService,
|
||||
@@ -809,7 +809,7 @@ export default class MainBackground {
|
||||
this.apiService,
|
||||
);
|
||||
|
||||
this.ssoLoginService = new SsoLoginService(this.stateProvider);
|
||||
this.ssoLoginService = new SsoLoginService(this.stateProvider, this.logService);
|
||||
|
||||
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
|
||||
|
||||
@@ -1142,6 +1142,7 @@ export default class MainBackground {
|
||||
this.accountService,
|
||||
lockService,
|
||||
this.billingAccountProfileStateService,
|
||||
this.cipherService,
|
||||
);
|
||||
this.nativeMessagingBackground = new NativeMessagingBackground(
|
||||
this.keyService,
|
||||
|
||||
@@ -4,9 +4,9 @@ import { delay, filter, firstValueFrom, from, map, race, timer } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -16,6 +16,7 @@ import { MessageListener, isExternalMessage } from "@bitwarden/common/platform/m
|
||||
import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { BiometricsCommands } from "@bitwarden/key-management";
|
||||
|
||||
@@ -53,6 +54,7 @@ export default class RuntimeBackground {
|
||||
private accountService: AccountService,
|
||||
private readonly lockService: LockService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private cipherService: CipherService,
|
||||
) {
|
||||
// onInstalled listener must be wired up before anything else, so we do it in the ctor
|
||||
chrome.runtime.onInstalled.addListener((details: any) => {
|
||||
@@ -200,6 +202,9 @@ export default class RuntimeBackground {
|
||||
case BiometricsCommands.GetBiometricsStatusForUser: {
|
||||
return await this.main.biometricsService.getBiometricsStatusForUser(msg.userId);
|
||||
}
|
||||
case "updateLastUsedDate": {
|
||||
return await this.cipherService.updateLastUsedDate(msg.cipherId);
|
||||
}
|
||||
case "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag": {
|
||||
return await this.configService.getFeatureFlag(
|
||||
FeatureFlag.UseTreeWalkerApiForPageDetailsCollection,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// @ts-strict-ignore
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
|
||||
@@ -147,7 +147,9 @@ describe("Browser Utils Service", () => {
|
||||
|
||||
describe("isViewOpen", () => {
|
||||
it("returns false if a heartbeat response is not received", async () => {
|
||||
BrowserApi.sendMessageWithResponse = jest.fn().mockResolvedValueOnce(undefined);
|
||||
chrome.runtime.sendMessage = jest.fn().mockImplementation((message, callback) => {
|
||||
callback(undefined);
|
||||
});
|
||||
|
||||
const isViewOpen = await browserPlatformUtilsService.isViewOpen();
|
||||
|
||||
@@ -155,16 +157,29 @@ describe("Browser Utils Service", () => {
|
||||
});
|
||||
|
||||
it("returns true if a heartbeat response is received", async () => {
|
||||
BrowserApi.sendMessageWithResponse = jest
|
||||
.fn()
|
||||
.mockImplementationOnce((subscriber) =>
|
||||
Promise.resolve((subscriber === "checkVaultPopupHeartbeat") as any),
|
||||
);
|
||||
chrome.runtime.sendMessage = jest.fn().mockImplementation((message, callback) => {
|
||||
callback(message.command === "checkVaultPopupHeartbeat");
|
||||
});
|
||||
|
||||
const isViewOpen = await browserPlatformUtilsService.isViewOpen();
|
||||
|
||||
expect(isViewOpen).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false if special error is sent", async () => {
|
||||
chrome.runtime.sendMessage = jest.fn().mockImplementation((message, callback) => {
|
||||
chrome.runtime.lastError = new Error(
|
||||
"Could not establish connection. Receiving end does not exist.",
|
||||
);
|
||||
callback(undefined);
|
||||
});
|
||||
|
||||
const isViewOpen = await browserPlatformUtilsService.isViewOpen();
|
||||
|
||||
expect(isViewOpen).toBe(false);
|
||||
|
||||
chrome.runtime.lastError = null;
|
||||
});
|
||||
});
|
||||
|
||||
describe("copyToClipboard", () => {
|
||||
@@ -228,6 +243,7 @@ describe("Browser Utils Service", () => {
|
||||
});
|
||||
|
||||
it("copies the passed text using the offscreen document if the extension is using manifest v3", async () => {
|
||||
BrowserApi.sendMessageWithResponse = jest.fn();
|
||||
const text = "test";
|
||||
offscreenDocumentService.offscreenApiSupported.mockReturnValue(true);
|
||||
getManifestVersionSpy.mockReturnValue(3);
|
||||
@@ -302,6 +318,7 @@ describe("Browser Utils Service", () => {
|
||||
});
|
||||
|
||||
it("reads the clipboard text using the offscreen document", async () => {
|
||||
BrowserApi.sendMessageWithResponse = jest.fn();
|
||||
offscreenDocumentService.offscreenApiSupported.mockReturnValue(true);
|
||||
getManifestVersionSpy.mockReturnValue(3);
|
||||
offscreenDocumentService.withDocument.mockImplementationOnce((_, __, callback) =>
|
||||
|
||||
@@ -169,7 +169,28 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
||||
// Query views on safari since chrome.runtime.sendMessage does not timeout and will hang.
|
||||
return BrowserApi.isPopupOpen();
|
||||
}
|
||||
return Boolean(await BrowserApi.sendMessageWithResponse("checkVaultPopupHeartbeat"));
|
||||
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
chrome.runtime.sendMessage({ command: "checkVaultPopupHeartbeat" }, (response) => {
|
||||
if (chrome.runtime.lastError != null) {
|
||||
// This error means that nothing was there to listen to the message,
|
||||
// meaning the view is not open.
|
||||
if (
|
||||
chrome.runtime.lastError.message ===
|
||||
"Could not establish connection. Receiving end does not exist."
|
||||
) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// All unhandled errors still reject
|
||||
reject(chrome.runtime.lastError);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(Boolean(response));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
lockTimeout(): number {
|
||||
|
||||
@@ -55,13 +55,13 @@ import {
|
||||
} from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import {
|
||||
AnimationControlService,
|
||||
DefaultAnimationControlService,
|
||||
} from "@bitwarden/common/platform/abstractions/animation-control.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
[description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : null"
|
||||
showAutofillButton
|
||||
[primaryActionAutofill]="clickItemsToAutofillVaultView"
|
||||
[groupByType]="groupByType()"
|
||||
></app-vault-list-items-container>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
import { combineLatest, firstValueFrom, map, Observable } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
@@ -48,6 +49,10 @@ export class AutofillVaultListItemsComponent implements OnInit {
|
||||
|
||||
clickItemsToAutofillVaultView = false;
|
||||
|
||||
protected groupByType = toSignal(
|
||||
this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)),
|
||||
);
|
||||
|
||||
/**
|
||||
* Observable that determines whether the empty autofill tip should be shown.
|
||||
* The tip is shown when there are no login ciphers to autofill, no filter is applied, and autofill is allowed in
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<button type="button" bitMenuItem (click)="toggleFavorite()">
|
||||
{{ favoriteText | i18n }}
|
||||
</button>
|
||||
<ng-container *ngIf="canEdit">
|
||||
<ng-container *ngIf="canEdit && canViewPassword">
|
||||
<a bitMenuItem (click)="clone()" *ngIf="canClone$ | async">
|
||||
{{ "clone" | i18n }}
|
||||
</a>
|
||||
|
||||
@@ -97,6 +97,9 @@ export class ItemMoreOptionsComponent implements OnInit {
|
||||
return this.cipher.edit;
|
||||
}
|
||||
|
||||
get canViewPassword() {
|
||||
return this.cipher.viewPassword;
|
||||
}
|
||||
/**
|
||||
* Determines if the cipher can be autofilled.
|
||||
*/
|
||||
|
||||
@@ -11,11 +11,11 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components";
|
||||
import { AddEditFolderDialogComponent } from "@bitwarden/vault";
|
||||
|
||||
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||
import { AddEditQueryParams } from "../add-edit/add-edit-v2.component";
|
||||
import { AddEditFolderDialogComponent } from "../add-edit-folder-dialog/add-edit-folder-dialog.component";
|
||||
|
||||
export interface NewItemInitialValues {
|
||||
folderId?: string;
|
||||
@@ -72,6 +72,6 @@ export class NewItemDropdownV2Component implements OnInit {
|
||||
}
|
||||
|
||||
openFolderDialog() {
|
||||
this.dialogService.open(AddEditFolderDialogComponent);
|
||||
AddEditFolderDialogComponent.open(this.dialogService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ import { combineLatest, map, take } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DisclosureTriggerForDirective, IconButtonModule } from "@bitwarden/components";
|
||||
import {
|
||||
DisclosureComponent,
|
||||
DisclosureTriggerForDirective,
|
||||
IconButtonModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { DisclosureComponent } from "../../../../../../../../libs/components/src/disclosure/disclosure.component";
|
||||
import { runInsideAngular } from "../../../../../platform/browser/run-inside-angular.operator";
|
||||
import { VaultPopupListFiltersService } from "../../../../../vault/popup/services/vault-popup-list-filters.service";
|
||||
import { VaultListFiltersComponent } from "../vault-list-filters/vault-list-filters.component";
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<bit-section *ngIf="ciphers?.length > 0 || description" [disableMargin]="disableSectionMargin">
|
||||
<bit-section
|
||||
*ngIf="cipherGroups$().length > 0 || description"
|
||||
[disableMargin]="disableSectionMargin"
|
||||
>
|
||||
<ng-container *ngIf="collapsibleKey">
|
||||
<button
|
||||
class="tw-group/vault-section-header hover:tw-bg-secondary-100 tw-pl-1 tw-w-full tw-border-x-0 tw-border-t-0 tw-border-b tw-border-solid focus-visible:tw-outline-none focus-visible:tw-rounded-md focus-visible:tw-ring-inset focus-visible:tw-ring-2 focus-visible:tw-ring-primary-600"
|
||||
class="tw-group/vault-section-header hover:tw-bg-primary-100 tw-rounded-md tw-pl-1 tw-w-full tw-border-x-0 tw-border-t-0 tw-border-b tw-border-solid focus-visible:tw-outline-none focus-visible:tw-ring-inset focus-visible:tw-ring-2 focus-visible:tw-ring-primary-600"
|
||||
[ngClass]="{
|
||||
'tw-border-b-secondary-300': !sectionOpenState(),
|
||||
'tw-border-b-secondary-300 tw-rounded-b-none [&:is(:hover,:focus-visible)]:tw-border-b-transparent [&:is(:hover,:focus-visible)]:tw-rounded-b-md':
|
||||
!sectionOpenState(),
|
||||
'tw-border-b-transparent': sectionOpenState(),
|
||||
}"
|
||||
type="button"
|
||||
@@ -17,6 +21,7 @@
|
||||
<ng-container *ngTemplateOutlet="itemGroup"></ng-container>
|
||||
</bit-disclosure>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!collapsibleKey">
|
||||
<div class="tw-pl-1">
|
||||
<ng-container *ngTemplateOutlet="sectionHeader"></ng-container>
|
||||
@@ -27,7 +32,7 @@
|
||||
</bit-section>
|
||||
|
||||
<ng-template #sectionHeader>
|
||||
<bit-section-header class="tw-p-0.5 -tw-mt-0.5 -tw-mx-0.5">
|
||||
<bit-section-header class="tw-p-0.5 -tw-mx-0.5">
|
||||
<h2 bitTypography="h6">
|
||||
{{ title }}
|
||||
</h2>
|
||||
@@ -47,11 +52,11 @@
|
||||
'tw-hidden': collapsibleKey && !sectionOpenState(),
|
||||
}"
|
||||
>
|
||||
{{ ciphers.length }}
|
||||
{{ ciphers().length }}
|
||||
</span>
|
||||
<span class="tw-pr-1" *ngIf="collapsibleKey">
|
||||
<i
|
||||
class="bwi"
|
||||
class="bwi tw-text-main"
|
||||
[ngClass]="{
|
||||
'bwi-angle-down tw-inline-block': !sectionOpenState(),
|
||||
'bwi-angle-up tw-hidden group-hover/vault-section-header:tw-inline-block group-focus-visible/vault-section-header:tw-inline-block':
|
||||
@@ -72,69 +77,78 @@
|
||||
|
||||
<ng-template #itemGroup>
|
||||
<bit-item-group>
|
||||
<cdk-virtual-scroll-viewport
|
||||
[itemSize]="itemHeight$ | async"
|
||||
class="tw-overflow-visible [&>.cdk-virtual-scroll-content-wrapper]:[contain:layout_style]"
|
||||
>
|
||||
<bit-item *cdkVirtualFor="let cipher of ciphers">
|
||||
<button
|
||||
bit-item-content
|
||||
type="button"
|
||||
(click)="primaryActionOnSelect(cipher)"
|
||||
(dblclick)="launchCipher(cipher)"
|
||||
[appA11yTitle]="cipherItemTitleKey | async | i18n: cipher.name"
|
||||
class="{{ itemHeightClass }}"
|
||||
>
|
||||
<div slot="start" class="tw-justify-start tw-w-7 tw-flex">
|
||||
<app-vault-icon [cipher]="cipher"></app-vault-icon>
|
||||
</div>
|
||||
<span data-testid="item-name">{{ cipher.name }}</span>
|
||||
<i
|
||||
*ngIf="cipher.organizationId"
|
||||
slot="default-trailing"
|
||||
appOrgIcon
|
||||
[tierType]="cipher.organization.productTierType"
|
||||
[size]="'small'"
|
||||
[appA11yTitle]="orgIconTooltip(cipher)"
|
||||
></i>
|
||||
<i
|
||||
*ngIf="cipher.hasAttachments"
|
||||
class="bwi bwi-paperclip bwi-sm"
|
||||
[appA11yTitle]="'attachments' | i18n"
|
||||
></i>
|
||||
<span slot="secondary">{{ cipher.subTitle }}</span>
|
||||
</button>
|
||||
<ng-container slot="end">
|
||||
<bit-item-action *ngIf="!(hideAutofillButton$ | async)">
|
||||
<button
|
||||
type="button"
|
||||
bitBadge
|
||||
variant="primary"
|
||||
(click)="doAutofill(cipher)"
|
||||
[title]="autofillShortcutTooltip() ?? ('autofillTitle' | i18n: cipher.name)"
|
||||
[attr.aria-label]="'autofillTitle' | i18n: cipher.name"
|
||||
>
|
||||
{{ "fill" | i18n }}
|
||||
</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action *ngIf="!showAutofillButton && cipher.canLaunch">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-external-link"
|
||||
size="small"
|
||||
(click)="launchCipher(cipher)"
|
||||
[attr.aria-label]="'launchWebsiteName' | i18n: cipher.name"
|
||||
[title]="'launchWebsiteName' | i18n: cipher.name"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
|
||||
<app-item-more-options
|
||||
[cipher]="cipher"
|
||||
[hideAutofillOptions]="hideAutofillOptions$ | async"
|
||||
[showViewOption]="primaryActionAutofill"
|
||||
></app-item-more-options>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<ng-container *ngFor="let group of cipherGroups$()">
|
||||
<ng-container *ngIf="group.subHeaderKey">
|
||||
<h3 class="tw-text-muted tw-text-xs tw-font-semibold tw-pl-1 tw-mb-1 bit-compact:tw-m-0">
|
||||
{{ group.subHeaderKey | i18n }}
|
||||
</h3>
|
||||
</ng-container>
|
||||
|
||||
<cdk-virtual-scroll-viewport
|
||||
[itemSize]="itemHeight$ | async"
|
||||
class="tw-overflow-visible [&>.cdk-virtual-scroll-content-wrapper]:[contain:layout_style]"
|
||||
>
|
||||
<bit-item *cdkVirtualFor="let cipher of group.ciphers">
|
||||
<button
|
||||
bit-item-content
|
||||
type="button"
|
||||
(click)="primaryActionOnSelect(cipher)"
|
||||
(dblclick)="launchCipher(cipher)"
|
||||
[appA11yTitle]="cipherItemTitleKey | async | i18n: cipher.name"
|
||||
class="{{ itemHeightClass }}"
|
||||
>
|
||||
<div slot="start" class="tw-justify-start tw-w-7 tw-flex">
|
||||
<app-vault-icon [cipher]="cipher"></app-vault-icon>
|
||||
</div>
|
||||
<span data-testid="item-name">{{ cipher.name }}</span>
|
||||
<i
|
||||
*ngIf="cipher.organizationId"
|
||||
slot="default-trailing"
|
||||
appOrgIcon
|
||||
[tierType]="cipher.organization.productTierType"
|
||||
[size]="'small'"
|
||||
[appA11yTitle]="orgIconTooltip(cipher)"
|
||||
></i>
|
||||
<i
|
||||
*ngIf="cipher.hasAttachments"
|
||||
class="bwi bwi-paperclip bwi-sm"
|
||||
[appA11yTitle]="'attachments' | i18n"
|
||||
></i>
|
||||
<span slot="secondary">{{ cipher.subTitle }}</span>
|
||||
</button>
|
||||
|
||||
<ng-container slot="end">
|
||||
<bit-item-action *ngIf="!(hideAutofillButton$ | async)">
|
||||
<button
|
||||
type="button"
|
||||
bitBadge
|
||||
variant="primary"
|
||||
(click)="doAutofill(cipher)"
|
||||
[title]="autofillShortcutTooltip() ?? ('autofillTitle' | i18n: cipher.name)"
|
||||
[attr.aria-label]="'autofillTitle' | i18n: cipher.name"
|
||||
>
|
||||
{{ "fill" | i18n }}
|
||||
</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action *ngIf="!showAutofillButton && cipher.canLaunch">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-external-link"
|
||||
size="small"
|
||||
(click)="launchCipher(cipher)"
|
||||
[attr.aria-label]="'launchWebsiteName' | i18n: cipher.name"
|
||||
[title]="'launchWebsiteName' | i18n: cipher.name"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
|
||||
<app-item-more-options
|
||||
[cipher]="cipher"
|
||||
[hideAutofillOptions]="hideAutofillOptions$ | async"
|
||||
[showViewOption]="primaryActionAutofill"
|
||||
></app-item-more-options>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</ng-container>
|
||||
</bit-item-group>
|
||||
</ng-template>
|
||||
|
||||
@@ -9,11 +9,14 @@ import {
|
||||
EventEmitter,
|
||||
inject,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
Signal,
|
||||
signal,
|
||||
ViewChild,
|
||||
computed,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
input,
|
||||
} from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom, Observable, map } from "rxjs";
|
||||
@@ -23,6 +26,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
BadgeModule,
|
||||
@@ -73,6 +77,7 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options
|
||||
selector: "app-vault-list-items-container",
|
||||
templateUrl: "vault-list-items-container.component.html",
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
|
||||
private compactModeService = inject(CompactModeService);
|
||||
@@ -110,11 +115,51 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
|
||||
*/
|
||||
private viewCipherTimeout: number | null;
|
||||
|
||||
ciphers = input<PopupCipherView[]>([]);
|
||||
|
||||
/**
|
||||
* The list of ciphers to display.
|
||||
* If true, we will group ciphers by type (Login, Card, Identity)
|
||||
* within subheadings in a single container, converted to a WritableSignal.
|
||||
*/
|
||||
@Input()
|
||||
ciphers: PopupCipherView[] = [];
|
||||
groupByType = input<boolean>(false);
|
||||
|
||||
/**
|
||||
* Computed signal for a grouped list of ciphers with an optional header
|
||||
*/
|
||||
cipherGroups$ = computed<
|
||||
{
|
||||
subHeaderKey?: string | null;
|
||||
ciphers: PopupCipherView[];
|
||||
}[]
|
||||
>(() => {
|
||||
const groups: { [key: string]: CipherView[] } = {};
|
||||
|
||||
this.ciphers().forEach((cipher) => {
|
||||
let groupKey;
|
||||
|
||||
if (this.groupByType()) {
|
||||
switch (cipher.type) {
|
||||
case CipherType.Card:
|
||||
groupKey = "cards";
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
groupKey = "identities";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!groups[groupKey]) {
|
||||
groups[groupKey] = [];
|
||||
}
|
||||
|
||||
groups[groupKey].push(cipher);
|
||||
});
|
||||
|
||||
return Object.keys(groups).map((key) => ({
|
||||
subHeaderKey: this.groupByType ? key : "",
|
||||
ciphers: groups[key],
|
||||
}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Title for the vault list item section.
|
||||
|
||||
@@ -11,10 +11,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { PasswordHistoryViewComponent } from "@bitwarden/vault";
|
||||
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHistoryViewComponent } from "../../../../../../../../libs/vault/src/components/password-history-view/password-history-view.component";
|
||||
import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component";
|
||||
import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component";
|
||||
|
||||
@@ -69,13 +69,13 @@
|
||||
<app-autofill-vault-list-items></app-autofill-vault-list-items>
|
||||
<app-vault-list-items-container
|
||||
[title]="'favorites' | i18n"
|
||||
[ciphers]="favoriteCiphers$ | async"
|
||||
[ciphers]="(favoriteCiphers$ | async) || []"
|
||||
id="favorites"
|
||||
collapsibleKey="favorites"
|
||||
></app-vault-list-items-container>
|
||||
<app-vault-list-items-container
|
||||
[title]="'allItems' | i18n"
|
||||
[ciphers]="remainingCiphers$ | async"
|
||||
[ciphers]="(remainingCiphers$ | async) || []"
|
||||
id="allItems"
|
||||
disableSectionMargin
|
||||
collapsibleKey="allItems"
|
||||
|
||||
@@ -23,6 +23,7 @@ import { EventType } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -35,14 +36,8 @@ import {
|
||||
SearchModule,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import { CopyCipherFieldService } from "@bitwarden/vault";
|
||||
import { CipherViewComponent, CopyCipherFieldService } from "@bitwarden/vault";
|
||||
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PremiumUpgradePromptService } from "../../../../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service";
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CipherViewComponent } from "../../../../../../../../libs/vault/src/cipher-view";
|
||||
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||
import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component";
|
||||
|
||||
@@ -21,14 +21,12 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service";
|
||||
import {
|
||||
AutoFillOptions,
|
||||
AutofillService,
|
||||
PageDetail,
|
||||
} from "../../../autofill/services/abstractions/autofill.service";
|
||||
import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service";
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
|
||||
|
||||
|
||||
@@ -27,13 +27,11 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service";
|
||||
import {
|
||||
AutofillService,
|
||||
PageDetail,
|
||||
} from "../../../autofill/services/abstractions/autofill.service";
|
||||
import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service";
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
|
||||
import { closeViewVaultItemPopout, VaultPopoutType } from "../utils/vault-popout-window";
|
||||
|
||||
@@ -17,9 +17,7 @@ import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service";
|
||||
import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service";
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
|
||||
import { VaultPopupAutofillService } from "./vault-popup-autofill.service";
|
||||
|
||||
@@ -214,6 +214,7 @@ export class VaultPopupItemsService {
|
||||
map(([hasSearchText, filters]) => {
|
||||
return hasSearchText || Object.values(filters).some((filter) => filter !== null);
|
||||
}),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,17 +14,15 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { BadgeModule, CheckboxModule, Option } from "@bitwarden/components";
|
||||
import {
|
||||
BadgeModule,
|
||||
CardComponent,
|
||||
CheckboxModule,
|
||||
FormFieldModule,
|
||||
Option,
|
||||
SelectModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CardComponent } from "../../../../../../libs/components/src/card/card.component";
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { FormFieldModule } from "../../../../../../libs/components/src/form-field/form-field.module";
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { SelectModule } from "../../../../../../libs/components/src/select/select.module";
|
||||
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
|
||||
import { PopupCompactModeService } from "../../../platform/popup/layout/popup-compact-mode.service";
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
|
||||
@@ -14,10 +14,10 @@ import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { AddEditFolderDialogComponent } from "@bitwarden/vault";
|
||||
|
||||
import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component";
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
import { AddEditFolderDialogComponent } from "../components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component";
|
||||
|
||||
import { FoldersV2Component } from "./folders-v2.component";
|
||||
|
||||
@@ -27,8 +27,8 @@ import { FoldersV2Component } from "./folders-v2.component";
|
||||
template: `<ng-content></ng-content>`,
|
||||
})
|
||||
class MockPopupHeaderComponent {
|
||||
@Input() pageTitle: string;
|
||||
@Input() backAction: () => void;
|
||||
@Input() pageTitle: string = "";
|
||||
@Input() backAction: () => void = () => {};
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -37,14 +37,15 @@ class MockPopupHeaderComponent {
|
||||
template: `<ng-content></ng-content>`,
|
||||
})
|
||||
class MockPopupFooterComponent {
|
||||
@Input() pageTitle: string;
|
||||
@Input() pageTitle: string = "";
|
||||
}
|
||||
|
||||
describe("FoldersV2Component", () => {
|
||||
let component: FoldersV2Component;
|
||||
let fixture: ComponentFixture<FoldersV2Component>;
|
||||
const folderViews$ = new BehaviorSubject<FolderView[]>([]);
|
||||
const open = jest.fn();
|
||||
const open = jest.spyOn(AddEditFolderDialogComponent, "open");
|
||||
const mockDialogService = { open: jest.fn() };
|
||||
|
||||
beforeEach(async () => {
|
||||
open.mockClear();
|
||||
@@ -68,7 +69,7 @@ describe("FoldersV2Component", () => {
|
||||
imports: [MockPopupHeaderComponent, MockPopupFooterComponent],
|
||||
},
|
||||
})
|
||||
.overrideProvider(DialogService, { useValue: { open } })
|
||||
.overrideProvider(DialogService, { useValue: mockDialogService })
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(FoldersV2Component);
|
||||
@@ -101,9 +102,7 @@ describe("FoldersV2Component", () => {
|
||||
|
||||
editButton.triggerEventHandler("click");
|
||||
|
||||
expect(open).toHaveBeenCalledWith(AddEditFolderDialogComponent, {
|
||||
data: { editFolderConfig: { folder } },
|
||||
});
|
||||
expect(open).toHaveBeenCalledWith(mockDialogService, { editFolderConfig: { folder } });
|
||||
});
|
||||
|
||||
it("opens add dialog for new folder when there are no folders", () => {
|
||||
@@ -114,6 +113,6 @@ describe("FoldersV2Component", () => {
|
||||
|
||||
addButton.triggerEventHandler("click");
|
||||
|
||||
expect(open).toHaveBeenCalledWith(AddEditFolderDialogComponent, { data: {} });
|
||||
expect(open).toHaveBeenCalledWith(mockDialogService, {});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,25 +12,14 @@ import {
|
||||
ButtonModule,
|
||||
DialogService,
|
||||
IconButtonModule,
|
||||
ItemModule,
|
||||
NoItemsModule,
|
||||
} from "@bitwarden/components";
|
||||
import { VaultIcons } from "@bitwarden/vault";
|
||||
import { AddEditFolderDialogComponent, VaultIcons } from "@bitwarden/vault";
|
||||
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { ItemGroupComponent } from "../../../../../../libs/components/src/item/item-group.component";
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { ItemModule } from "../../../../../../libs/components/src/item/item.module";
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { NoItemsModule } from "../../../../../../libs/components/src/no-items/no-items.module";
|
||||
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
|
||||
import {
|
||||
AddEditFolderDialogComponent,
|
||||
AddEditFolderDialogData,
|
||||
} from "../components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -42,7 +31,6 @@ import {
|
||||
PopupPageComponent,
|
||||
PopupHeaderComponent,
|
||||
ItemModule,
|
||||
ItemGroupComponent,
|
||||
NoItemsModule,
|
||||
IconButtonModule,
|
||||
ButtonModule,
|
||||
@@ -78,8 +66,6 @@ export class FoldersV2Component {
|
||||
// If a folder is provided, the edit variant should be shown
|
||||
const editFolderConfig = folder ? { folder } : undefined;
|
||||
|
||||
this.dialogService.open<unknown, AddEditFolderDialogData>(AddEditFolderDialogComponent, {
|
||||
data: { editFolderConfig },
|
||||
});
|
||||
AddEditFolderDialogComponent.open(this.dialogService, { editFolderConfig });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"],
|
||||
"@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"],
|
||||
"@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"],
|
||||
"@bitwarden/importer/core": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer-core": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer/ui": ["../../libs/importer/src/components"],
|
||||
"@bitwarden/key-management": ["../../libs/key-management/src"],
|
||||
"@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"],
|
||||
@@ -51,8 +51,8 @@
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"../../libs/common/src/platform/services/**/*.worker.ts",
|
||||
"../../libs/common/src/autofill/constants",
|
||||
"../../libs/common/custom-matchers.d.ts"
|
||||
"../../libs/common/custom-matchers.d.ts",
|
||||
"../../libs/common/src/key-management/crypto/services/encrypt.worker.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ const mainConfig = {
|
||||
"./src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts",
|
||||
"overlay/list":
|
||||
"./src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts",
|
||||
"encrypt-worker": "../../libs/common/src/platform/services/cryptography/encrypt.worker.ts",
|
||||
"encrypt-worker": "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts",
|
||||
"content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts",
|
||||
},
|
||||
optimization: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
OrganizationUserConfirmRequest,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ import { CollectionRequest } from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
|
||||
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
|
||||
import { FolderExport } from "@bitwarden/common/models/export/folder.export";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
|
||||
@@ -123,6 +123,9 @@ export class EditCommand {
|
||||
"Item does not belong to an organization. Consider moving it first.",
|
||||
);
|
||||
}
|
||||
if (!cipher.viewPassword) {
|
||||
return Response.noEditPermission();
|
||||
}
|
||||
|
||||
cipher.collectionIds = req;
|
||||
try {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { CardExport } from "@bitwarden/common/models/export/card.export";
|
||||
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
|
||||
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
|
||||
@@ -23,7 +24,6 @@ import { LoginUriExport } from "@bitwarden/common/models/export/login-uri.export
|
||||
import { LoginExport } from "@bitwarden/common/models/export/login.export";
|
||||
import { SecureNoteExport } from "@bitwarden/common/models/export/secure-note.export";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||
|
||||
@@ -39,6 +39,10 @@ export class Response {
|
||||
return Response.error("Not found.");
|
||||
}
|
||||
|
||||
static noEditPermission(): Response {
|
||||
return Response.error("You do not have permission to edit this item");
|
||||
}
|
||||
|
||||
static badRequest(message: string): Response {
|
||||
return Response.error(message);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// @ts-strict-ignore
|
||||
import { throwError } from "rxjs";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
@@ -61,6 +61,8 @@ import {
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
|
||||
import { FallbackBulkEncryptService } from "@bitwarden/common/key-management/crypto/services/fallback-bulk-encrypt.service";
|
||||
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import {
|
||||
@@ -83,8 +85,6 @@ import { AppIdService } from "@bitwarden/common/platform/services/app-id.service
|
||||
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
|
||||
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||
import { FallbackBulkEncryptService } from "@bitwarden/common/platform/services/cryptography/fallback-bulk-encrypt.service";
|
||||
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
|
||||
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
|
||||
@@ -151,7 +151,7 @@ import {
|
||||
ImportApiServiceAbstraction,
|
||||
ImportService,
|
||||
ImportServiceAbstraction,
|
||||
} from "@bitwarden/importer/core";
|
||||
} from "@bitwarden/importer-core";
|
||||
import {
|
||||
DefaultKdfConfigService,
|
||||
KdfConfigService,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ImportServiceAbstraction, ImportType } from "@bitwarden/importer/core";
|
||||
import { ImportServiceAbstraction, ImportType } from "@bitwarden/importer-core";
|
||||
|
||||
import { Response } from "../models/response";
|
||||
import { MessageResponse } from "../models/response/message.response";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||
|
||||
@@ -5,9 +5,9 @@ import * as inquirer from "inquirer";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
@@ -11,10 +11,10 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
||||
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
|
||||
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
|
||||
import { FolderExport } from "@bitwarden/common/models/export/folder.export";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"@bitwarden/auth/common": ["../../libs/auth/src/common"],
|
||||
"@bitwarden/auth/angular": ["../../libs/auth/src/angular"],
|
||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||
"@bitwarden/importer/core": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer-core": ["../../libs/importer/src"],
|
||||
"@bitwarden/generator-core": ["../../libs/tools/generator/core/src"],
|
||||
"@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"],
|
||||
"@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"],
|
||||
|
||||
@@ -3,11 +3,11 @@ import "module-alias/register";
|
||||
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||
import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.usb</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
|
||||
<true/>
|
||||
<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
|
||||
|
||||
@@ -54,7 +54,6 @@ import { InternalFolderService } from "@bitwarden/common/vault/abstractions/fold
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { DialogService, ToastOptions, ToastService } from "@bitwarden/components";
|
||||
import { CredentialGeneratorHistoryDialogComponent } from "@bitwarden/generator-components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
import { KeyService, BiometricStateService } from "@bitwarden/key-management";
|
||||
|
||||
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||
@@ -65,9 +64,7 @@ import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.compo
|
||||
import { SettingsComponent } from "./accounts/settings.component";
|
||||
import { ExportDesktopComponent } from "./tools/export/export-desktop.component";
|
||||
import { CredentialGeneratorComponent } from "./tools/generator/credential-generator.component";
|
||||
import { GeneratorComponent } from "./tools/generator.component";
|
||||
import { ImportDesktopComponent } from "./tools/import/import-desktop.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
|
||||
|
||||
const BroadcasterSubscriptionId = "AppComponent";
|
||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||
@@ -126,7 +123,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private broadcasterService: BroadcasterService,
|
||||
private folderService: InternalFolderService,
|
||||
private syncService: SyncService,
|
||||
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
private cipherService: CipherService,
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
@@ -508,41 +504,13 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async openGenerator() {
|
||||
const isGeneratorSwapEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.GeneratorToolsModernization,
|
||||
);
|
||||
if (isGeneratorSwapEnabled) {
|
||||
await this.dialogService.open(CredentialGeneratorComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalService.closeAll();
|
||||
|
||||
[this.modal] = await this.modalService.openViewRef(
|
||||
GeneratorComponent,
|
||||
this.generatorModalRef,
|
||||
(comp) => (comp.comingFromAddEdit = false),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
this.modal.onClosed.subscribe(() => {
|
||||
this.modal = null;
|
||||
});
|
||||
await this.dialogService.open(CredentialGeneratorComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
async openGeneratorHistory() {
|
||||
const isGeneratorSwapEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.GeneratorToolsModernization,
|
||||
);
|
||||
if (isGeneratorSwapEnabled) {
|
||||
await this.dialogService.open(CredentialGeneratorHistoryDialogComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.openModal<PasswordGeneratorHistoryComponent>(
|
||||
PasswordGeneratorHistoryComponent,
|
||||
this.passwordHistoryRef,
|
||||
);
|
||||
await this.dialogService.open(CredentialGeneratorHistoryDialogComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
private async updateAppMenu() {
|
||||
|
||||
@@ -47,8 +47,6 @@ import { HeaderComponent } from "./layout/header.component";
|
||||
import { NavComponent } from "./layout/nav.component";
|
||||
import { SearchComponent } from "./layout/search/search.component";
|
||||
import { SharedModule } from "./shared/shared.module";
|
||||
import { GeneratorComponent } from "./tools/generator.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
|
||||
import { AddEditComponent as SendAddEditComponent } from "./tools/send/add-edit.component";
|
||||
import { SendComponent } from "./tools/send/send.component";
|
||||
|
||||
@@ -80,8 +78,6 @@ import { SendComponent } from "./tools/send/send.component";
|
||||
HeaderComponent,
|
||||
HintComponent,
|
||||
NavComponent,
|
||||
GeneratorComponent,
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PasswordHistoryComponent,
|
||||
PremiumComponent,
|
||||
RegisterComponent,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||
|
||||
@@ -50,10 +50,10 @@ import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { DefaultProcessReloadService } from "@bitwarden/common/key-management/services/default-process-reload.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction";
|
||||
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||
|
||||
@@ -1,636 +0,0 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="generatorTitle">
|
||||
<div class="modal-dialog modal-md" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<h1 class="modal-title" id="generatorTitle">
|
||||
{{ "generator" | i18n }}
|
||||
</h1>
|
||||
<bit-callout
|
||||
type="info"
|
||||
*ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'"
|
||||
>
|
||||
{{ "passwordGeneratorPolicyInEffect" | i18n }}
|
||||
</bit-callout>
|
||||
<div class="generated-block" *ngIf="type === 'password'">
|
||||
<div
|
||||
class="generated-wrapper"
|
||||
[innerHTML]="password | colorPassword"
|
||||
[appCopyText]="password"
|
||||
></div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn primary"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'copyPassword' | i18n }}"
|
||||
(click)="copy()"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn primary"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'regeneratePassword' | i18n }}"
|
||||
(click)="regenerate()"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="generated-block" *ngIf="type === 'username'">
|
||||
<div
|
||||
class="generated-wrapper"
|
||||
[innerHTML]="username | colorPassword"
|
||||
[appCopyText]="username"
|
||||
></div>
|
||||
<div class="action-buttons" #form [appApiAction]="usernameGeneratingPromise">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn primary"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'copyUsername' | i18n }}"
|
||||
(click)="copy()"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn primary"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'regenerateUsername' | i18n }}"
|
||||
(click)="$any(form).loading ? false : regenerate()"
|
||||
[attr.aria-disabled]="$any(form).loading ? 'true' : null"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg bwi-generate"
|
||||
[ngClass]="$any(form).loading ? 'bwi-spin' : ''"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="!comingFromAddEdit">
|
||||
<div class="box-content condensed">
|
||||
<div
|
||||
class="box-content-row box-content-row-radio"
|
||||
role="radiogroup"
|
||||
aria-labelledby="typeHeading"
|
||||
>
|
||||
<label id="typeHeading" class="radio-header">{{
|
||||
"whatWouldYouLikeToGenerate" | i18n
|
||||
}}</label>
|
||||
<div
|
||||
class="radio-group text-default"
|
||||
appBoxRow
|
||||
name="TypeOptions"
|
||||
*ngFor="let o of typeOptions"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
class="radio"
|
||||
[(ngModel)]="type"
|
||||
name="Type"
|
||||
id="type_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="typeChanged()"
|
||||
[checked]="type === o.value"
|
||||
/>
|
||||
<label class="unstyled" for="type_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="type === 'password'">
|
||||
<div class="box">
|
||||
<h2 class="box-header">
|
||||
<button type="button" (click)="toggleOptions()" [attr.aria-expanded]="showOptions">
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-angle-right': !showOptions, 'bwi-angle-down': showOptions }"
|
||||
></i>
|
||||
{{ "options" | i18n }}
|
||||
</button>
|
||||
</h2>
|
||||
<div class="box-content condensed" [hidden]="!showOptions">
|
||||
<div
|
||||
class="box-content-row box-content-row-radio"
|
||||
role="radiogroup"
|
||||
aria-labelledby="passwordTypeHeading"
|
||||
>
|
||||
<label id="passwordTypeHeading" class="radio-header">{{
|
||||
"passwordType" | i18n
|
||||
}}</label>
|
||||
<div
|
||||
class="radio-group text-default"
|
||||
appBoxRow
|
||||
name="PassTypeOptions"
|
||||
*ngFor="let o of passTypeOptions"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
class="radio"
|
||||
[(ngModel)]="passwordOptions.type"
|
||||
name="PasswordType"
|
||||
id="passwordType_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="savePasswordOptions()"
|
||||
[checked]="passwordOptions.type === o.value"
|
||||
/>
|
||||
<label class="unstyled" for="passwordType_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" [hidden]="!showOptions" *ngIf="passwordOptions.type === 'passphrase'">
|
||||
<div class="box-content condensed">
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="num-words">{{ "numWords" | i18n }}</label>
|
||||
<input
|
||||
id="num-words"
|
||||
type="number"
|
||||
min="3"
|
||||
max="20"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.numWords"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
|
||||
<input
|
||||
id="word-separator"
|
||||
type="text"
|
||||
maxlength="1"
|
||||
(input)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.wordSeparator"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="capitalize">{{ "capitalize" | i18n }}</label>
|
||||
<input
|
||||
id="capitalize"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.capitalize"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="include-number">{{ "includeNumber" | i18n }}</label>
|
||||
<input
|
||||
id="include-number"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.includeNumber"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="passwordOptions.type === 'password'">
|
||||
<div class="box" [hidden]="!showOptions">
|
||||
<div class="box-content condensed">
|
||||
<div class="box-content-row box-content-row-slider" appBoxRow>
|
||||
<label for="length">{{ "length" | i18n }}</label>
|
||||
<input
|
||||
id="length"
|
||||
type="number"
|
||||
[min]="passwordOptions.minLength"
|
||||
max="128"
|
||||
[(ngModel)]="passwordOptions.length"
|
||||
(blur)="savePasswordOptions()"
|
||||
/>
|
||||
<input
|
||||
id="lengthRange"
|
||||
type="range"
|
||||
[min]="passwordOptions.minLength"
|
||||
max="128"
|
||||
step="1"
|
||||
[(ngModel)]="passwordOptions.length"
|
||||
(change)="sliderChanged()"
|
||||
(input)="sliderInput()"
|
||||
attr.aria-label="{{ 'length' | i18n }}"
|
||||
tabindex="-1"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<span>{{ "passwordMinLength" | i18n }}</span>
|
||||
<span class="txt-right">{{ passwordOptions.minLength }}</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
attr.aria-label="{{ 'passwordMinLength' | i18n }}"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
{{ passwordOptionsMinLengthForReader$ | async }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="uppercase">A-Z</label>
|
||||
<input
|
||||
id="uppercase"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
|
||||
[(ngModel)]="passwordOptions.uppercase"
|
||||
attr.aria-label="{{ 'uppercase' | i18n }}"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="lowercase">a-z</label>
|
||||
<input
|
||||
id="lowercase"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
|
||||
[(ngModel)]="passwordOptions.lowercase"
|
||||
attr.aria-label="{{ 'lowercase' | i18n }}"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="numbers">0-9</label>
|
||||
<input
|
||||
id="numbers"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
|
||||
[ngModel]="passwordOptions.number"
|
||||
(ngModelChange)="setPasswordOptionsNumber($event)"
|
||||
attr.aria-label="{{ 'numbers' | i18n }}"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="special">!@#$%^&*</label>
|
||||
<input
|
||||
id="special"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
|
||||
[ngModel]="passwordOptions.special"
|
||||
(ngModelChange)="setPasswordOptionsSpecial($event)"
|
||||
attr.aria-label="{{ 'specialCharacters' | i18n }}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" [hidden]="!showOptions">
|
||||
<div class="box-content condensed">
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="min-number">{{ "minNumbers" | i18n }}</label>
|
||||
<input
|
||||
id="min-number"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.minNumber"
|
||||
(input)="onPasswordOptionsMinNumberInput($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="min-special">{{ "minSpecial" | i18n }}</label>
|
||||
<input
|
||||
id="min-special"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.minSpecial"
|
||||
(input)="onPasswordOptionsMinSpecialInput($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="ambiguous">{{ "ambiguous" | i18n }}</label>
|
||||
<input
|
||||
id="ambiguous"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="avoidAmbiguous"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="type === 'username'">
|
||||
<div class="box">
|
||||
<h2 class="box-header">
|
||||
<button type="button" (click)="toggleOptions()" [attr.aria-expanded]="showOptions">
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-angle-right': !showOptions, 'bwi-angle-down': showOptions }"
|
||||
></i>
|
||||
{{ "options" | i18n }}
|
||||
</button>
|
||||
</h2>
|
||||
<div class="box-content condensed" [hidden]="!showOptions">
|
||||
<div
|
||||
class="box-content-row box-content-row-radio"
|
||||
role="radiogroup"
|
||||
aria-labelledby="usernameTypeHeading"
|
||||
>
|
||||
<label id="usernameTypeHeading" class="radio-header">
|
||||
{{ "usernameType" | i18n }}
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="usernameTypesLearnMore()"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</label>
|
||||
<div
|
||||
class="radio-group align-start text-default"
|
||||
appBoxRow
|
||||
name="UsernameTypeOptions"
|
||||
*ngFor="let o of usernameTypeOptions"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
class="radio"
|
||||
[(ngModel)]="usernameOptions.type"
|
||||
name="UsernameType"
|
||||
id="usernameType_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveUsernameOptions()"
|
||||
[checked]="usernameOptions.type === o.value"
|
||||
/>
|
||||
<label class="unstyled" for="usernameType_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
<small class="help-block" *ngIf="o.desc">{{ o.desc }}</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="usernameOptions.type === 'forwarded'" [hidden]="!showOptions">
|
||||
<div class="box-content condensed">
|
||||
<div class="box-content-row" role="listbox" aria-labelledby="forwardTypeHeading">
|
||||
<label id="forwardTypeHeading">{{ "service" | i18n }}</label>
|
||||
<select
|
||||
id="ForwardTypeDropdown"
|
||||
name="ForwardType"
|
||||
[(ngModel)]="usernameOptions.forwardedService"
|
||||
(change)="saveUsernameOptions()"
|
||||
>
|
||||
<option *ngFor="let o of forwardOptions" [ngValue]="o.value" role="option">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<ng-container *ngIf="usernameOptions.forwardedService === 'simplelogin'">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="simplelogin-apikey">{{ "apiKey" | i18n }}</label>
|
||||
<input
|
||||
id="simplelogin-apikey"
|
||||
type="password"
|
||||
name="SimpleLoginApiKey"
|
||||
[(ngModel)]="usernameOptions.forwardedSimpleLoginApiKey"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="simplelogin-baseUrl">{{ "baseUrl" | i18n }}</label>
|
||||
<input
|
||||
id="simplelogin-baseUrl"
|
||||
type="text"
|
||||
name="SimpleLoginDomain"
|
||||
[(ngModel)]="usernameOptions.forwardedSimpleLoginBaseUrl"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="usernameOptions.forwardedService === 'duckduckgo'">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="duckduckgo-apikey">{{ "apiKey" | i18n }}</label>
|
||||
<input
|
||||
id="duckduckgo-apikey"
|
||||
type="password"
|
||||
name="DuckDuckGoApiKey"
|
||||
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="usernameOptions.forwardedService === 'anonaddy'">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="anonaddy-accessToken">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="anonaddy-accessToken"
|
||||
type="password"
|
||||
name="AnonAddyAccessToken"
|
||||
[(ngModel)]="usernameOptions.forwardedAnonAddyApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="anonaddy-domain">{{ "aliasDomain" | i18n }}</label>
|
||||
<input
|
||||
id="anonaddy-domain"
|
||||
type="text"
|
||||
name="AnonAddyDomain"
|
||||
[(ngModel)]="usernameOptions.forwardedAnonAddyDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="anonaddy-baseUrl">{{ "baseUrl" | i18n }}</label>
|
||||
<input
|
||||
id="anonaddy-baseUrl"
|
||||
type="text"
|
||||
name="AnonAddyDomain"
|
||||
[(ngModel)]="usernameOptions.forwardedAnonAddyBaseUrl"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="usernameOptions.forwardedService === 'firefoxrelay'">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="firefox-apikey">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="firefox-apikey"
|
||||
type="password"
|
||||
name="FirefoxApiKey"
|
||||
[(ngModel)]="usernameOptions.forwardedFirefoxApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="usernameOptions.forwardedService === 'fastmail'">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="fastmail-apiToken">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="fastmail-apiToken"
|
||||
type="password"
|
||||
name="FastmailApiToken"
|
||||
[(ngModel)]="usernameOptions.forwardedFastmailApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="usernameOptions.forwardedService === 'forwardemail'">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="forwardemail-accessToken">{{ "apiAccessToken" | i18n }}</label>
|
||||
<input
|
||||
id="forwardemail-accessToken"
|
||||
type="password"
|
||||
name="ForwardEmailAccessToken"
|
||||
[(ngModel)]="usernameOptions.forwardedForwardEmailApiToken"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="forwardemail-domain">{{ "aliasDomain" | i18n }}</label>
|
||||
<input
|
||||
id="forwardemail-domain"
|
||||
type="text"
|
||||
name="ForwardEmailDomain"
|
||||
[(ngModel)]="usernameOptions.forwardedForwardEmailDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="usernameOptions.type === 'subaddress'" [hidden]="!showOptions">
|
||||
<div class="box-content condensed">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="subaddress-email"
|
||||
type="text"
|
||||
name="SubaddressEmail"
|
||||
[(ngModel)]="usernameOptions.subaddressEmail"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="box-content-row"
|
||||
role="radiogroup"
|
||||
aria-labelledby="subaddressTypeHeading"
|
||||
*ngIf="subaddressOptions.length > 1"
|
||||
>
|
||||
<label id="subaddressTypeHeading" class="radio-header">{{ "type" | i18n }}</label>
|
||||
<div class="radio-group text-default" appBoxRow *ngFor="let o of subaddressOptions">
|
||||
<input
|
||||
type="radio"
|
||||
[(ngModel)]="usernameOptions.subaddressType"
|
||||
name="SubaddressType"
|
||||
id="subaddresstype_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveUsernameOptions()"
|
||||
[checked]="usernameOptions.subaddressType === o.value"
|
||||
/>
|
||||
<label for="subaddresstype_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow *ngIf="usernameWebsite">
|
||||
<label for="subaddress-website">{{ "website" | i18n }}</label>
|
||||
<input
|
||||
id="subaddress-website"
|
||||
type="text"
|
||||
name="SubaddressWebsite"
|
||||
[value]="usernameOptions.website"
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="usernameOptions.type === 'catchall'" [hidden]="!showOptions">
|
||||
<div class="box-content condensed">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
|
||||
<input
|
||||
id="catchall-domain"
|
||||
type="text"
|
||||
name="CatchallDomain"
|
||||
[(ngModel)]="usernameOptions.catchallDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="box-content-row"
|
||||
role="radiogroup"
|
||||
aria-labelledby="catchallTypeHeading"
|
||||
*ngIf="catchallOptions.length > 1"
|
||||
>
|
||||
<label id="catchallTypeHeading" class="radio-header">{{ "type" | i18n }}</label>
|
||||
<div class="radio-group text-default" appBoxRow *ngFor="let o of catchallOptions">
|
||||
<input
|
||||
type="radio"
|
||||
[(ngModel)]="usernameOptions.catchallType"
|
||||
name="CatchallType"
|
||||
id="catchalltype_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveUsernameOptions()"
|
||||
[checked]="usernameOptions.catchallType === o.value"
|
||||
/>
|
||||
<label for="catchalltype_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow *ngIf="usernameWebsite">
|
||||
<label for="catchall-website">{{ "website" | i18n }}</label>
|
||||
<input
|
||||
id="catchall-website"
|
||||
type="text"
|
||||
name="CatchallWebsite"
|
||||
[value]="usernameOptions.website"
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="usernameOptions.type === 'word'" [hidden]="!showOptions">
|
||||
<div class="box-content condensed">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="capitalize">{{ "capitalize" | i18n }}</label>
|
||||
<input
|
||||
id="capitalize"
|
||||
type="checkbox"
|
||||
(change)="saveUsernameOptions()"
|
||||
[(ngModel)]="usernameOptions.wordCapitalize"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="include-number">{{ "includeNumber" | i18n }}</label>
|
||||
<input
|
||||
id="include-number"
|
||||
type="checkbox"
|
||||
(change)="saveUsernameOptions()"
|
||||
[(ngModel)]="usernameOptions.wordIncludeNumber"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="primary"
|
||||
*ngIf="comingFromAddEdit"
|
||||
(click)="select()"
|
||||
appA11yTitle="{{ 'select' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" data-dismiss="modal">
|
||||
{{ (comingFromAddEdit ? "cancel" : "close") | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,90 +0,0 @@
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import {
|
||||
PasswordGenerationServiceAbstraction,
|
||||
UsernameGenerationServiceAbstraction,
|
||||
} from "@bitwarden/generator-legacy";
|
||||
|
||||
import { GeneratorComponent } from "./generator.component";
|
||||
|
||||
describe("GeneratorComponent", () => {
|
||||
let component: GeneratorComponent;
|
||||
let fixture: ComponentFixture<GeneratorComponent>;
|
||||
let platformUtilsServiceMock: MockProxy<PlatformUtilsService>;
|
||||
|
||||
beforeEach(() => {
|
||||
platformUtilsServiceMock = mock<PlatformUtilsService>();
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GeneratorComponent, I18nPipe],
|
||||
providers: [
|
||||
{
|
||||
provide: PasswordGenerationServiceAbstraction,
|
||||
useValue: mock<PasswordGenerationServiceAbstraction>(),
|
||||
},
|
||||
{
|
||||
provide: UsernameGenerationServiceAbstraction,
|
||||
useValue: mock<UsernameGenerationServiceAbstraction>(),
|
||||
},
|
||||
{
|
||||
provide: PlatformUtilsService,
|
||||
useValue: platformUtilsServiceMock,
|
||||
},
|
||||
{
|
||||
provide: I18nService,
|
||||
useValue: mock<I18nService>(),
|
||||
},
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: mock<ActivatedRoute>(),
|
||||
},
|
||||
{
|
||||
provide: LogService,
|
||||
useValue: mock<LogService>(),
|
||||
},
|
||||
{
|
||||
provide: CipherService,
|
||||
useValue: mock<CipherService>(),
|
||||
},
|
||||
{
|
||||
provide: AccountService,
|
||||
useValue: mock<AccountService>(),
|
||||
},
|
||||
{
|
||||
provide: ToastService,
|
||||
useValue: mock<ToastService>(),
|
||||
},
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GeneratorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("usernameTypesLearnMore()", () => {
|
||||
it("should call platformUtilsService.launchUri() once", () => {
|
||||
component.usernameTypesLearnMore();
|
||||
expect(platformUtilsServiceMock.launchUri).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Component, NgZone } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { GeneratorComponent as BaseGeneratorComponent } from "@bitwarden/angular/tools/generator/components/generator.component";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import {
|
||||
PasswordGenerationServiceAbstraction,
|
||||
UsernameGenerationServiceAbstraction,
|
||||
} from "@bitwarden/generator-legacy";
|
||||
|
||||
@Component({
|
||||
selector: "app-generator",
|
||||
templateUrl: "generator.component.html",
|
||||
})
|
||||
export class GeneratorComponent extends BaseGeneratorComponent {
|
||||
constructor(
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
usernameGenerationService: UsernameGenerationServiceAbstraction,
|
||||
accountService: AccountService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
ngZone: NgZone,
|
||||
logService: LogService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
passwordGenerationService,
|
||||
usernameGenerationService,
|
||||
platformUtilsService,
|
||||
accountService,
|
||||
i18nService,
|
||||
logService,
|
||||
route,
|
||||
ngZone,
|
||||
window,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
|
||||
usernameTypesLearnMore() {
|
||||
this.platformUtilsService.launchUri("https://bitwarden.com/help/generator/#username-types");
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="passwordGenHistoryTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="box">
|
||||
<h1 class="box-header" id="passwordGenHistoryTitle">
|
||||
{{ "passwordHistory" | i18n }}
|
||||
</h1>
|
||||
<div class="box-content condensed">
|
||||
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
|
||||
<div class="row-main">
|
||||
<div
|
||||
class="password-wrapper monospaced"
|
||||
[appCopyText]="h.password"
|
||||
[innerHTML]="h.password | colorPassword"
|
||||
></div>
|
||||
<span class="detail">{{ h.date | date: "medium" }}</span>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'copyPassword' | i18n }}"
|
||||
(click)="copy(h.password)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row" *ngIf="!history.length">
|
||||
{{ "noPasswordsInList" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
|
||||
<div class="right">
|
||||
<button
|
||||
type="button"
|
||||
(click)="clear()"
|
||||
class="danger"
|
||||
appA11yTitle="{{ 'clear' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-trash bwi-lg bwi-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "@bitwarden/angular/tools/generator/components/password-generator-history.component";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
|
||||
@Component({
|
||||
selector: "app-password-generator-history",
|
||||
templateUrl: "password-generator-history.component.html",
|
||||
})
|
||||
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
|
||||
constructor(
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(passwordGenerationService, platformUtilsService, i18nService, window, toastService);
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -66,9 +66,9 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
input:not(bit-form-field input),
|
||||
select,
|
||||
textarea {
|
||||
textarea:not(bit-form-field textarea) {
|
||||
@include themify($themes) {
|
||||
color: themed("textColor");
|
||||
background-color: themed("inputBackgroundColor");
|
||||
|
||||
@@ -188,44 +188,6 @@ p.lead {
|
||||
}
|
||||
}
|
||||
|
||||
.generated-block {
|
||||
font-size: $font-size-large;
|
||||
font-family: $font-family-monospace;
|
||||
padding: 8px 10px 8px 0;
|
||||
display: flex;
|
||||
border-radius: $border-radius;
|
||||
border: 1px solid;
|
||||
|
||||
@include themify($themes) {
|
||||
background-color: transparent;
|
||||
border-color: themed("borderColorAlt");
|
||||
}
|
||||
|
||||
.modal-body & {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.generated-wrapper {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
height: 100%;
|
||||
margin-left: 10px;
|
||||
|
||||
button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.password-wrapper {
|
||||
overflow-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
|
||||
@@ -5,8 +5,8 @@ import { of } from "rxjs";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
@@ -6,8 +6,8 @@ import { combineLatest, concatMap, firstValueFrom, map } from "rxjs";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.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";
|
||||
|
||||
@@ -4,8 +4,8 @@ import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { NativeMessagingVersion } from "@bitwarden/common/enums";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/ang
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
type="checkbox"
|
||||
[(ngModel)]="$any(c).checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
[disabled]="!cipher.canAssignToCollections"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
(onCancelled)="cancelledAddEdit($event)"
|
||||
(onShareCipher)="shareCipher($event)"
|
||||
(onEditCollections)="cipherCollections($event)"
|
||||
(onGeneratePassword)="openGenerator(true, true)"
|
||||
(onGenerateUsername)="openGenerator(true, false)"
|
||||
(onGeneratePassword)="openGenerator(true)"
|
||||
(onGenerateUsername)="openGenerator(false)"
|
||||
>
|
||||
</app-vault-add-edit>
|
||||
<div
|
||||
|
||||
@@ -21,7 +21,6 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -39,7 +38,6 @@ import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { DecryptionFailureDialogComponent, PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { SearchBarService } from "../../../app/layout/search/search-bar.service";
|
||||
import { GeneratorComponent } from "../../../app/tools/generator.component";
|
||||
import { invokeMenu, RendererMenuItem } from "../../../utils";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
@@ -666,65 +664,21 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
return "searchVault";
|
||||
}
|
||||
|
||||
async openGenerator(comingFromAddEdit: boolean, passwordType = true) {
|
||||
const isGeneratorSwapEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.GeneratorToolsModernization,
|
||||
);
|
||||
|
||||
if (isGeneratorSwapEnabled) {
|
||||
CredentialGeneratorDialogComponent.open(this.dialogService, {
|
||||
onCredentialGenerated: (value?: string) => {
|
||||
if (this.addEditComponent != null) {
|
||||
this.addEditComponent.markPasswordAsDirty();
|
||||
if (passwordType) {
|
||||
this.addEditComponent.cipher.login.password = value ?? "";
|
||||
} else {
|
||||
this.addEditComponent.cipher.login.username = value ?? "";
|
||||
}
|
||||
}
|
||||
},
|
||||
type: passwordType ? "password" : "username",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Legacy code below, remove once the new generator is fully implemented
|
||||
// https://bitwarden.atlassian.net/browse/PM-7121
|
||||
const cipher = this.addEditComponent?.cipher;
|
||||
const loginType = cipher != null && cipher.type === CipherType.Login && cipher.login != null;
|
||||
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
GeneratorComponent,
|
||||
this.generatorModalRef,
|
||||
(comp) => {
|
||||
comp.comingFromAddEdit = comingFromAddEdit;
|
||||
if (comingFromAddEdit) {
|
||||
comp.type = passwordType ? "password" : "username";
|
||||
if (loginType && cipher.login.hasUris && cipher.login.uris[0].hostname != null) {
|
||||
comp.usernameWebsite = cipher.login.uris[0].hostname;
|
||||
async openGenerator(passwordType = true) {
|
||||
CredentialGeneratorDialogComponent.open(this.dialogService, {
|
||||
onCredentialGenerated: (value?: string) => {
|
||||
if (this.addEditComponent != null) {
|
||||
this.addEditComponent.markPasswordAsDirty();
|
||||
if (passwordType) {
|
||||
this.addEditComponent.cipher.login.password = value ?? "";
|
||||
} else {
|
||||
this.addEditComponent.cipher.login.username = value ?? "";
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
this.modal = modal;
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
childComponent.onSelected.subscribe((value: string) => {
|
||||
this.modal.close();
|
||||
if (loginType) {
|
||||
this.addEditComponent.markPasswordAsDirty();
|
||||
if (passwordType) {
|
||||
this.addEditComponent.cipher.login.password = value;
|
||||
} else {
|
||||
this.addEditComponent.cipher.login.username = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
this.modal.onClosed.subscribe(() => {
|
||||
this.modal = null;
|
||||
type: passwordType ? "password" : "username",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
async addFolder() {
|
||||
|
||||
@@ -17,8 +17,8 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"],
|
||||
"@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"],
|
||||
"@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"],
|
||||
"@bitwarden/importer/core": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer-core": ["../../libs/importer/src"],
|
||||
"@bitwarden/importer/ui": ["../../libs/importer/src/components"],
|
||||
"@bitwarden/key-management": ["../../libs/key-management/src"],
|
||||
"@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"],
|
||||
@@ -48,5 +48,5 @@
|
||||
"strictTemplates": true,
|
||||
"preserveWhitespaces": true
|
||||
},
|
||||
"include": ["src", "../../libs/common/src/platform/services/**/*.worker.ts"]
|
||||
"include": ["src", "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts"]
|
||||
}
|
||||
|
||||
@@ -121,6 +121,22 @@
|
||||
</app-side-nav>
|
||||
|
||||
<ng-container *ngIf="organization$ | async as organization">
|
||||
<bit-banner
|
||||
*ngIf="showAccountDeprovisioningBanner$ | async"
|
||||
(onClose)="bannerService.hideBanner(organization)"
|
||||
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
|
||||
>
|
||||
{{ "accountDeprovisioningNotification" | i18n }}
|
||||
<a
|
||||
href="https://bitwarden.com/help/claimed-accounts"
|
||||
bitLink
|
||||
linkType="contrast"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ "learnMore" | i18n }}
|
||||
</a>
|
||||
</bit-banner>
|
||||
<bit-banner
|
||||
*ngIf="organization.isProviderUser"
|
||||
[showClose]="false"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||
import { combineLatest, filter, firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
import { combineLatest, filter, map, Observable, switchMap, withLatestFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
@@ -22,6 +22,7 @@ import { PolicyType, ProviderStatusType } from "@bitwarden/common/admin-console/
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -32,6 +33,8 @@ import { OrgSwitcherComponent } from "../../../layouts/org-switcher/org-switcher
|
||||
import { WebLayoutModule } from "../../../layouts/web-layout.module";
|
||||
import { AdminConsoleLogo } from "../../icons/admin-console-logo";
|
||||
|
||||
import { AccountDeprovisioningBannerService } from "./services/account-deprovisioning-banner.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-organization-layout",
|
||||
templateUrl: "organization-layout.component.html",
|
||||
@@ -61,6 +64,8 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
organizationIsUnmanaged$: Observable<boolean>;
|
||||
enterpriseOrganization$: Observable<boolean>;
|
||||
|
||||
showAccountDeprovisioningBanner$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
@@ -68,19 +73,36 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
private configService: ConfigService,
|
||||
private policyService: PolicyService,
|
||||
private providerService: ProviderService,
|
||||
protected bannerService: AccountDeprovisioningBannerService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
this.organization$ = this.route.params.pipe(
|
||||
map((p) => p.organizationId),
|
||||
switchMap((id) => this.organizationService.organizations$(userId).pipe(getById(id))),
|
||||
withLatestFrom(this.accountService.activeAccount$.pipe(getUserId)),
|
||||
switchMap(([orgId, userId]) =>
|
||||
this.organizationService.organizations$(userId).pipe(getById(orgId)),
|
||||
),
|
||||
filter((org) => org != null),
|
||||
);
|
||||
|
||||
this.showAccountDeprovisioningBanner$ = combineLatest([
|
||||
this.bannerService.showBanner$,
|
||||
this.configService.getFeatureFlag$(FeatureFlag.AccountDeprovisioningBanner),
|
||||
this.organization$,
|
||||
]).pipe(
|
||||
map(
|
||||
([dismissedOrgs, featureFlagEnabled, organization]) =>
|
||||
organization.productTierType === ProductTierType.Enterprise &&
|
||||
organization.isAdmin &&
|
||||
!dismissedOrgs?.includes(organization.id) &&
|
||||
featureFlagEnabled,
|
||||
),
|
||||
);
|
||||
|
||||
this.canAccessExport$ = this.organization$.pipe(map((org) => org.canAccessExport));
|
||||
|
||||
this.showPaymentAndHistory$ = this.organization$.pipe(
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
FakeAccountService,
|
||||
FakeStateProvider,
|
||||
mockAccountServiceWith,
|
||||
} from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { AccountDeprovisioningBannerService } from "./account-deprovisioning-banner.service";
|
||||
|
||||
describe("Account Deprovisioning Banner Service", () => {
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
let accountService: FakeAccountService;
|
||||
let stateProvider: FakeStateProvider;
|
||||
let bannerService: AccountDeprovisioningBannerService;
|
||||
|
||||
beforeEach(async () => {
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
bannerService = new AccountDeprovisioningBannerService(stateProvider);
|
||||
});
|
||||
|
||||
it("updates state with single org", async () => {
|
||||
const fakeOrg = new Organization();
|
||||
fakeOrg.id = "123";
|
||||
|
||||
await bannerService.hideBanner(fakeOrg);
|
||||
const state = await firstValueFrom(bannerService.showBanner$);
|
||||
|
||||
expect(state).toEqual([fakeOrg.id]);
|
||||
});
|
||||
|
||||
it("updates state with multiple orgs", async () => {
|
||||
const fakeOrg1 = new Organization();
|
||||
fakeOrg1.id = "123";
|
||||
const fakeOrg2 = new Organization();
|
||||
fakeOrg2.id = "234";
|
||||
const fakeOrg3 = new Organization();
|
||||
fakeOrg3.id = "987";
|
||||
|
||||
await bannerService.hideBanner(fakeOrg1);
|
||||
await bannerService.hideBanner(fakeOrg2);
|
||||
await bannerService.hideBanner(fakeOrg3);
|
||||
|
||||
const state = await firstValueFrom(bannerService.showBanner$);
|
||||
|
||||
expect(state).toContain(fakeOrg1.id);
|
||||
expect(state).toContain(fakeOrg2.id);
|
||||
expect(state).toContain(fakeOrg3.id);
|
||||
});
|
||||
|
||||
it("does not add the same org id multiple times", async () => {
|
||||
const fakeOrg = new Organization();
|
||||
fakeOrg.id = "123";
|
||||
|
||||
await bannerService.hideBanner(fakeOrg);
|
||||
await bannerService.hideBanner(fakeOrg);
|
||||
|
||||
const state = await firstValueFrom(bannerService.showBanner$);
|
||||
|
||||
expect(state).toEqual([fakeOrg.id]);
|
||||
});
|
||||
|
||||
it("does not add null to the state", async () => {
|
||||
await bannerService.hideBanner(null as unknown as Organization);
|
||||
await bannerService.hideBanner(undefined as unknown as Organization);
|
||||
|
||||
const state = await firstValueFrom(bannerService.showBanner$);
|
||||
|
||||
expect(state).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import {
|
||||
ACCOUNT_DEPROVISIONING_BANNER_DISK,
|
||||
StateProvider,
|
||||
UserKeyDefinition,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
|
||||
export const SHOW_BANNER_KEY = new UserKeyDefinition<string[]>(
|
||||
ACCOUNT_DEPROVISIONING_BANNER_DISK,
|
||||
"accountDeprovisioningBanner",
|
||||
{
|
||||
deserializer: (b) => b,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class AccountDeprovisioningBannerService {
|
||||
private _showBanner = this.stateProvider.getActive(SHOW_BANNER_KEY);
|
||||
|
||||
showBanner$ = this._showBanner.state$;
|
||||
|
||||
constructor(private stateProvider: StateProvider) {}
|
||||
|
||||
async hideBanner(organization: Organization) {
|
||||
await this._showBanner.update((state) => {
|
||||
if (!organization) {
|
||||
return state;
|
||||
}
|
||||
if (!state) {
|
||||
return [organization.id];
|
||||
} else if (!state.includes(organization.id)) {
|
||||
return [...state, organization.id];
|
||||
}
|
||||
return state;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ProviderUserBulkPublicKeyResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk-public-key.response";
|
||||
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { ProviderUserBulkPublicKeyResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk-public-key.response";
|
||||
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { DeleteManagedMemberWarningService } from "../../services/delete-managed-member/delete-managed-member-warning.service";
|
||||
|
||||
import { BulkUserDetails } from "./bulk-status.component";
|
||||
|
||||
type BulkDeleteDialogParams = {
|
||||
@@ -31,12 +36,20 @@ export class BulkDeleteDialogComponent {
|
||||
@Inject(DIALOG_DATA) protected dialogParams: BulkDeleteDialogParams,
|
||||
protected i18nService: I18nService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private configService: ConfigService,
|
||||
private deleteManagedMemberWarningService: DeleteManagedMemberWarningService,
|
||||
) {
|
||||
this.organizationId = dialogParams.organizationId;
|
||||
this.users = dialogParams.users;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (
|
||||
await firstValueFrom(this.configService.getFeatureFlag$(FeatureFlag.AccountDeprovisioning))
|
||||
) {
|
||||
await this.deleteManagedMemberWarningService.acknowledgeWarning(this.organizationId);
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
convertToSelectionView,
|
||||
PermissionMode,
|
||||
} from "../../../shared/components/access-selector";
|
||||
import { DeleteManagedMemberWarningService } from "../../services/delete-managed-member/delete-managed-member-warning.service";
|
||||
|
||||
import { commaSeparatedEmails } from "./validators/comma-separated-emails.validator";
|
||||
import { inputEmailLimitValidator } from "./validators/input-email-limit.validator";
|
||||
@@ -176,6 +177,7 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
organizationService: OrganizationService,
|
||||
private toastService: ToastService,
|
||||
private configService: ConfigService,
|
||||
private deleteManagedMemberWarningService: DeleteManagedMemberWarningService,
|
||||
) {
|
||||
this.organization$ = accountService.activeAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
@@ -639,6 +641,27 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
const showWarningDialog = combineLatest([
|
||||
this.organization$,
|
||||
this.deleteManagedMemberWarningService.warningAcknowledged(this.params.organizationId),
|
||||
this.accountDeprovisioningEnabled$,
|
||||
]).pipe(
|
||||
map(
|
||||
([organization, acknowledged, featureFlagEnabled]) =>
|
||||
featureFlagEnabled &&
|
||||
organization.isOwner &&
|
||||
organization.productTierType === ProductTierType.Enterprise &&
|
||||
!acknowledged,
|
||||
),
|
||||
);
|
||||
|
||||
if (await firstValueFrom(showWarningDialog)) {
|
||||
const acknowledged = await this.deleteManagedMemberWarningService.showWarning();
|
||||
if (!acknowledged) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "deleteOrganizationUser",
|
||||
@@ -667,6 +690,10 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
title: null,
|
||||
message: this.i18nService.t("organizationUserDeleted", this.params.name),
|
||||
});
|
||||
|
||||
if (await firstValueFrom(this.accountDeprovisioningEnabled$)) {
|
||||
await this.deleteManagedMemberWarningService.acknowledgeWarning(this.params.organizationId);
|
||||
}
|
||||
this.close(MemberDialogResult.Deleted);
|
||||
};
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { isNotSelfUpgradable, ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
@@ -81,6 +81,7 @@ import {
|
||||
ResetPasswordComponent,
|
||||
ResetPasswordDialogResult,
|
||||
} from "./components/reset-password.component";
|
||||
import { DeleteManagedMemberWarningService } from "./services/delete-managed-member/delete-managed-member-warning.service";
|
||||
|
||||
class MembersTableDataSource extends PeopleTableDataSource<OrganizationUserView> {
|
||||
protected statusType = OrganizationUserStatusType;
|
||||
@@ -138,6 +139,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
private collectionService: CollectionService,
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
private configService: ConfigService,
|
||||
protected deleteManagedMemberWarningService: DeleteManagedMemberWarningService,
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
@@ -585,6 +587,23 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
}
|
||||
|
||||
async bulkDelete() {
|
||||
if (this.accountDeprovisioningEnabled) {
|
||||
const warningAcknowledged = await firstValueFrom(
|
||||
this.deleteManagedMemberWarningService.warningAcknowledged(this.organization.id),
|
||||
);
|
||||
|
||||
if (
|
||||
!warningAcknowledged &&
|
||||
this.organization.isOwner &&
|
||||
this.organization.productTierType === ProductTierType.Enterprise
|
||||
) {
|
||||
const acknowledged = await this.deleteManagedMemberWarningService.showWarning();
|
||||
if (!acknowledged) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
@@ -774,6 +793,23 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
}
|
||||
|
||||
async deleteUser(user: OrganizationUserView) {
|
||||
if (this.accountDeprovisioningEnabled) {
|
||||
const warningAcknowledged = await firstValueFrom(
|
||||
this.deleteManagedMemberWarningService.warningAcknowledged(this.organization.id),
|
||||
);
|
||||
|
||||
if (
|
||||
!warningAcknowledged &&
|
||||
this.organization.isOwner &&
|
||||
this.organization.productTierType === ProductTierType.Enterprise
|
||||
) {
|
||||
const acknowledged = await this.deleteManagedMemberWarningService.showWarning();
|
||||
if (!acknowledged) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "deleteOrganizationUser",
|
||||
@@ -792,6 +828,10 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.accountDeprovisioningEnabled) {
|
||||
await this.deleteManagedMemberWarningService.acknowledgeWarning(this.organization.id);
|
||||
}
|
||||
|
||||
this.actionPromise = this.organizationUserApiService.deleteOrganizationUser(
|
||||
this.organization.id,
|
||||
user.id,
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
FakeAccountService,
|
||||
FakeStateProvider,
|
||||
mockAccountServiceWith,
|
||||
} from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { DeleteManagedMemberWarningService } from "./delete-managed-member-warning.service";
|
||||
|
||||
describe("Delete managed member warning service", () => {
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
let accountService: FakeAccountService;
|
||||
let stateProvider: FakeStateProvider;
|
||||
let dialogService: MockProxy<DialogService>;
|
||||
let warningService: DeleteManagedMemberWarningService;
|
||||
|
||||
beforeEach(() => {
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
dialogService = mock();
|
||||
warningService = new DeleteManagedMemberWarningService(stateProvider, dialogService);
|
||||
});
|
||||
|
||||
it("warningAcknowledged returns false for ids that have not acknowledged the warning", async () => {
|
||||
const id = Utils.newGuid();
|
||||
const acknowledged = await firstValueFrom(warningService.warningAcknowledged(id));
|
||||
|
||||
expect(acknowledged).toEqual(false);
|
||||
});
|
||||
|
||||
it("warningAcknowledged returns true for ids that have acknowledged the warning", async () => {
|
||||
const id1 = Utils.newGuid();
|
||||
const id2 = Utils.newGuid();
|
||||
const id3 = Utils.newGuid();
|
||||
await warningService.acknowledgeWarning(id1);
|
||||
await warningService.acknowledgeWarning(id3);
|
||||
|
||||
const acknowledged1 = await firstValueFrom(warningService.warningAcknowledged(id1));
|
||||
const acknowledged2 = await firstValueFrom(warningService.warningAcknowledged(id2));
|
||||
const acknowledged3 = await firstValueFrom(warningService.warningAcknowledged(id3));
|
||||
|
||||
expect(acknowledged1).toEqual(true);
|
||||
expect(acknowledged2).toEqual(false);
|
||||
expect(acknowledged3).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { map } from "rxjs";
|
||||
|
||||
import {
|
||||
DELETE_MANAGED_USER_WARNING,
|
||||
StateProvider,
|
||||
UserKeyDefinition,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
export const SHOW_WARNING_KEY = new UserKeyDefinition<string[]>(
|
||||
DELETE_MANAGED_USER_WARNING,
|
||||
"showDeleteManagedUserWarning",
|
||||
{
|
||||
deserializer: (b) => b,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class DeleteManagedMemberWarningService {
|
||||
private _acknowledged = this.stateProvider.getActive(SHOW_WARNING_KEY);
|
||||
private acknowledgedState$ = this._acknowledged.state$;
|
||||
|
||||
constructor(
|
||||
private stateProvider: StateProvider,
|
||||
private dialogService: DialogService,
|
||||
) {}
|
||||
|
||||
async acknowledgeWarning(organizationId: string) {
|
||||
await this._acknowledged.update((state) => {
|
||||
if (!organizationId) {
|
||||
return state;
|
||||
}
|
||||
if (!state) {
|
||||
return [organizationId];
|
||||
} else if (!state.includes(organizationId)) {
|
||||
return [...state, organizationId];
|
||||
}
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
async showWarning() {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "deleteManagedUserWarning",
|
||||
},
|
||||
content: {
|
||||
key: "deleteManagedUserWarningDesc",
|
||||
},
|
||||
type: "danger",
|
||||
icon: "bwi-exclamation-circle",
|
||||
acceptButtonText: { key: "continue" },
|
||||
cancelButtonText: { key: "cancel" },
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
warningAcknowledged(organizationId: string) {
|
||||
return this.acknowledgedState$.pipe(
|
||||
map((acknowledgedIds) => acknowledgedIds?.includes(organizationId) ?? false),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
||||
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ImportCollectionServiceAbstraction } from "@bitwarden/importer/core";
|
||||
import { ImportComponent } from "@bitwarden/importer/ui";
|
||||
import { ImportCollectionServiceAbstraction } from "@bitwarden/importer-core";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../../shared";
|
||||
import { ImportCollectionAdminService } from "../../../tools/import/import-collection-admin.service";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
|
||||
import { RotateableKeySet } from "@bitwarden/auth/common";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
@@ -4,11 +4,11 @@ import { MockProxy } from "jest-mock-extended";
|
||||
import mock from "jest-mock-extended/lib/Mock";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response";
|
||||
import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
|
||||
@@ -6,9 +6,9 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service";
|
||||
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
|
||||
@@ -12,7 +12,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/reset-password-policy-options";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { FakeGlobalState } from "@bitwarden/common/spec/fake-state";
|
||||
|
||||
@@ -16,7 +16,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
<!-- Please remove this disable statement when editing this file! -->
|
||||
<!-- eslint-disable tailwindcss/no-custom-classname -->
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
class="tw-container tw-mx-auto"
|
||||
[formGroup]="formGroup"
|
||||
>
|
||||
<div>
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
||||
<input
|
||||
id="register-form_input_email"
|
||||
bitInput
|
||||
type="email"
|
||||
formControlName="email"
|
||||
[attr.readonly]="queryParamFromOrgInvite ? true : null"
|
||||
/>
|
||||
<bit-hint>{{ "emailAddressDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "name" | i18n }}</bit-label>
|
||||
<input id="register-form_input_name" bitInput type="text" formControlName="name" />
|
||||
<bit-hint>{{ "yourNameDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
|
||||
</auth-password-callout>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
id="register-form_input_master-password"
|
||||
bitInput
|
||||
type="password"
|
||||
formControlName="masterPassword"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
bitSuffix
|
||||
bitIconButton
|
||||
bitPasswordInputToggle
|
||||
[(toggled)]="showPassword"
|
||||
></button>
|
||||
<bit-hint>
|
||||
<span class="tw-font-semibold">{{ "important" | i18n }}</span>
|
||||
{{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
<app-password-strength
|
||||
[password]="formGroup.get('masterPassword')?.value"
|
||||
[email]="formGroup.get('email')?.value"
|
||||
[name]="formGroup.get('name')?.value"
|
||||
[showText]="true"
|
||||
(passwordStrengthResult)="getStrengthResult($event)"
|
||||
>
|
||||
</app-password-strength>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "reTypeMasterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
id="register-form_input_confirm-master-password"
|
||||
bitInput
|
||||
type="password"
|
||||
formControlName="confirmMasterPassword"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
bitSuffix
|
||||
bitIconButton
|
||||
bitPasswordInputToggle
|
||||
[(toggled)]="showPassword"
|
||||
></button>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPassHintLabel" | i18n }}</bit-label>
|
||||
<input id="register-form_input_hint" bitInput type="text" formControlName="hint" />
|
||||
<bit-hint>{{ "masterPassHintDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||
</div>
|
||||
<div class="tw-mb-4 tw-flex tw-items-start">
|
||||
<input
|
||||
class="mt-1"
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
id="checkForBreaches"
|
||||
name="CheckBreach"
|
||||
formControlName="checkForBreaches"
|
||||
/>
|
||||
<bit-label for="checkForBreaches"> {{ "checkForBreaches" | i18n }}</bit-label>
|
||||
</div>
|
||||
<div class="tw-mb-3 tw-flex tw-items-start" *ngIf="showTerms">
|
||||
<input
|
||||
class="mt-1"
|
||||
id="register-form-input-accept-policies"
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="acceptPolicies"
|
||||
/>
|
||||
|
||||
<bit-label for="register-form-input-accept-policies">
|
||||
{{ "acceptPolicies" | i18n }}<br />
|
||||
<a bitLink href="https://bitwarden.com/terms/" target="_blank" rel="noreferrer">{{
|
||||
"termsOfService" | i18n
|
||||
}}</a
|
||||
>,
|
||||
<a bitLink href="https://bitwarden.com/privacy/" target="_blank" rel="noreferrer">{{
|
||||
"privacyPolicy" | i18n
|
||||
}}</a>
|
||||
</bit-label>
|
||||
</div>
|
||||
|
||||
<div class="tw-space-x-2 tw-pt-2">
|
||||
<ng-container *ngIf="!accountCreated">
|
||||
<button
|
||||
[block]="true"
|
||||
type="submit"
|
||||
buttonType="primary"
|
||||
bitButton
|
||||
[loading]="form.loading"
|
||||
>
|
||||
{{ "createAccount" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="accountCreated">
|
||||
<button
|
||||
[block]="true"
|
||||
type="submit"
|
||||
buttonType="primary"
|
||||
bitButton
|
||||
[loading]="form.loading"
|
||||
>
|
||||
{{ "logIn" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
<p class="tw-m-0 tw-mt-5 tw-text-sm">
|
||||
{{ "alreadyHaveAccount" | i18n }}
|
||||
<a bitLink routerLink="/login">{{ "logIn" | i18n }}</a>
|
||||
</p>
|
||||
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,115 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { UntypedFormBuilder } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/auth/components/register.component";
|
||||
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
|
||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
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 { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request";
|
||||
import { RegisterRequest } from "@bitwarden/common/models/request/register.request";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-register-form",
|
||||
templateUrl: "./register-form.component.html",
|
||||
})
|
||||
export class RegisterFormComponent extends BaseRegisterComponent implements OnInit {
|
||||
@Input() queryParamEmail: string;
|
||||
@Input() queryParamFromOrgInvite: boolean;
|
||||
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
@Input() referenceDataValue: ReferenceEventRequest;
|
||||
|
||||
showErrorSummary = false;
|
||||
characterMinimumMessage: string;
|
||||
|
||||
constructor(
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
formBuilder: UntypedFormBuilder,
|
||||
loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
keyService: KeyService,
|
||||
apiService: ApiService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
private policyService: PolicyService,
|
||||
environmentService: EnvironmentService,
|
||||
logService: LogService,
|
||||
auditService: AuditService,
|
||||
dialogService: DialogService,
|
||||
acceptOrgInviteService: AcceptOrganizationInviteService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
formValidationErrorService,
|
||||
formBuilder,
|
||||
loginStrategyService,
|
||||
router,
|
||||
i18nService,
|
||||
keyService,
|
||||
apiService,
|
||||
platformUtilsService,
|
||||
environmentService,
|
||||
logService,
|
||||
auditService,
|
||||
dialogService,
|
||||
toastService,
|
||||
);
|
||||
this.modifyRegisterRequest = async (request: RegisterRequest) => {
|
||||
// Org invites are deep linked. Non-existent accounts are redirected to the register page.
|
||||
// Org user id and token are included here only for validation and two factor purposes.
|
||||
const orgInvite = await acceptOrgInviteService.getOrganizationInvite();
|
||||
if (orgInvite != null) {
|
||||
request.organizationUserId = orgInvite.organizationUserId;
|
||||
request.token = orgInvite.token;
|
||||
}
|
||||
// Invite is accepted after login (on deep link redirect).
|
||||
};
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
this.referenceData = this.referenceDataValue;
|
||||
if (this.queryParamEmail) {
|
||||
this.formGroup.get("email")?.setValue(this.queryParamEmail);
|
||||
}
|
||||
|
||||
if (this.enforcedPolicyOptions != null && this.enforcedPolicyOptions.minLength > 0) {
|
||||
this.characterMinimumMessage = "";
|
||||
} else {
|
||||
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (
|
||||
this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
this.passwordStrengthResult.score,
|
||||
this.formGroup.value.masterPassword,
|
||||
this.enforcedPolicyOptions,
|
||||
)
|
||||
) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("masterPasswordPolicyRequirementsNotMet"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await super.submit(false);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
|
||||
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
import { RegisterFormComponent } from "./register-form.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, PasswordCalloutComponent],
|
||||
declarations: [RegisterFormComponent],
|
||||
exports: [RegisterFormComponent],
|
||||
})
|
||||
export class RegisterFormModule {}
|
||||
@@ -4,7 +4,7 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/ang
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
@@ -118,7 +118,13 @@
|
||||
) | currency: "$"
|
||||
}}
|
||||
</b>
|
||||
<span class="tw-text-xs tw-px-0"> /{{ "monthPerMember" | i18n }}</span>
|
||||
<span class="tw-text-xs tw-px-0">
|
||||
/{{
|
||||
selectableProduct.productTier === productTypes.Families
|
||||
? "month"
|
||||
: ("monthPerMember" | i18n)
|
||||
}}</span
|
||||
>
|
||||
<b class="tw-text-sm tw-font-semibold">
|
||||
<ng-container
|
||||
*ngIf="selectableProduct.PasswordManager.hasAdditionalSeatsOption"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user