mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 23:33:31 +00:00
fixed merge conflict
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<label class="environment-selector-btn">
|
||||
{{ "region" | i18n }}:
|
||||
{{ "loggingInOn" | i18n }}:
|
||||
<a
|
||||
(click)="toggle(null)"
|
||||
cdkOverlayOrigin
|
||||
@@ -8,8 +8,12 @@
|
||||
aria-controls="cdk-overlay-container"
|
||||
[ngSwitch]="selectedEnvironment"
|
||||
>
|
||||
<label *ngSwitchCase="ServerEnvironmentType.US" class="text-primary">{{ "us" | i18n }}</label>
|
||||
<label *ngSwitchCase="ServerEnvironmentType.EU" class="text-primary">{{ "eu" | i18n }}</label>
|
||||
<label *ngSwitchCase="ServerEnvironmentType.US" class="text-primary">{{
|
||||
"usDomain" | i18n
|
||||
}}</label>
|
||||
<label *ngSwitchCase="ServerEnvironmentType.EU" class="text-primary">{{
|
||||
"euDomain" | i18n
|
||||
}}</label>
|
||||
<label *ngSwitchCase="ServerEnvironmentType.SelfHosted" class="text-primary">{{
|
||||
"selfHosted" | i18n
|
||||
}}</label>
|
||||
@@ -23,7 +27,7 @@
|
||||
(backdropClick)="close()"
|
||||
(detach)="close()"
|
||||
[cdkConnectedOverlayOpen]="isOpen"
|
||||
[cdkConnectedOverlayPositions]="overlayPostition"
|
||||
[cdkConnectedOverlayPositions]="overlayPosition"
|
||||
>
|
||||
<div class="box-content">
|
||||
<div class="environment-selector-dialog" [@transformPanel]="'open'" role="dialog">
|
||||
@@ -41,7 +45,7 @@
|
||||
"
|
||||
></i>
|
||||
<img class="img-us" alt="" />
|
||||
<span>{{ "us" | i18n }}</span>
|
||||
<span>{{ "usDomain" | i18n }}</span>
|
||||
</button>
|
||||
<br />
|
||||
<button
|
||||
@@ -59,7 +63,7 @@
|
||||
"
|
||||
></i>
|
||||
<img class="img-eu" alt="" />
|
||||
<span>{{ "eu" | i18n }}</span>
|
||||
<span>{{ "euDomain" | i18n }}</span>
|
||||
</button>
|
||||
<br *ngIf="euServerFlagEnabled" />
|
||||
<button
|
||||
@@ -76,8 +80,8 @@
|
||||
"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-fw bwi-sm bwi-pencil-square"
|
||||
style="padding-bottom: 1px"
|
||||
class="bwi bwi-fw bwi-md bwi-pencil-square"
|
||||
style="padding-bottom: 1px; margin-right: 5px"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ "selfHosted" | i18n }}</span>
|
||||
|
||||
@@ -6,7 +6,10 @@ import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import {
|
||||
EnvironmentService as EnvironmentServiceAbstraction,
|
||||
Region,
|
||||
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
|
||||
@Component({
|
||||
selector: "environment-selector",
|
||||
@@ -37,9 +40,9 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
|
||||
euServerFlagEnabled: boolean;
|
||||
isOpen = false;
|
||||
showingModal = false;
|
||||
selectedEnvironment: ServerEnvironment;
|
||||
ServerEnvironmentType = ServerEnvironment;
|
||||
overlayPostition: ConnectedPosition[] = [
|
||||
selectedEnvironment: Region;
|
||||
ServerEnvironmentType = Region;
|
||||
overlayPosition: ConnectedPosition[] = [
|
||||
{
|
||||
originX: "start",
|
||||
originY: "bottom",
|
||||
@@ -50,7 +53,7 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
|
||||
protected componentDestroyed$: Subject<void> = new Subject();
|
||||
|
||||
constructor(
|
||||
protected environmentService: EnvironmentService,
|
||||
protected environmentService: EnvironmentServiceAbstraction,
|
||||
protected configService: ConfigServiceAbstraction,
|
||||
protected router: Router
|
||||
) {}
|
||||
@@ -67,33 +70,28 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
|
||||
this.componentDestroyed$.complete();
|
||||
}
|
||||
|
||||
async toggle(option: ServerEnvironment) {
|
||||
async toggle(option: Region) {
|
||||
this.isOpen = !this.isOpen;
|
||||
if (option === null) {
|
||||
return;
|
||||
}
|
||||
if (option === ServerEnvironment.EU) {
|
||||
await this.environmentService.setUrls({ base: "https://vault.bitwarden.eu" });
|
||||
} else if (option === ServerEnvironment.US) {
|
||||
await this.environmentService.setUrls({ base: "https://vault.bitwarden.com" });
|
||||
} else if (option === ServerEnvironment.SelfHosted) {
|
||||
|
||||
this.updateEnvironmentInfo();
|
||||
|
||||
if (option === Region.SelfHosted) {
|
||||
this.onOpenSelfHostedSettings.emit();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.environmentService.setRegion(option);
|
||||
this.updateEnvironmentInfo();
|
||||
}
|
||||
|
||||
async updateEnvironmentInfo() {
|
||||
this.selectedEnvironment = this.environmentService.selectedRegion;
|
||||
this.euServerFlagEnabled = await this.configService.getFeatureFlagBool(
|
||||
FeatureFlag.DisplayEuEnvironmentFlag
|
||||
);
|
||||
const webvaultUrl = this.environmentService.getWebVaultUrl();
|
||||
if (this.environmentService.isSelfHosted()) {
|
||||
this.selectedEnvironment = ServerEnvironment.SelfHosted;
|
||||
} else if (webvaultUrl != null && webvaultUrl.includes("bitwarden.eu")) {
|
||||
this.selectedEnvironment = ServerEnvironment.EU;
|
||||
} else {
|
||||
this.selectedEnvironment = ServerEnvironment.US;
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
@@ -101,9 +99,3 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
|
||||
this.updateEnvironmentInfo();
|
||||
}
|
||||
}
|
||||
|
||||
enum ServerEnvironment {
|
||||
US = "US",
|
||||
EU = "EU",
|
||||
SelfHosted = "Self-hosted",
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export class LoginWithDeviceComponent
|
||||
protected successRoute = "vault";
|
||||
protected forcePasswordResetRoute = "update-temp-password";
|
||||
private resendTimeout = 12000;
|
||||
private authRequestKeyPair: [publicKey: ArrayBuffer, privateKey: ArrayBuffer];
|
||||
private authRequestKeyPair: [publicKey: Uint8Array, privateKey: Uint8Array];
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Directive } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
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 { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Directive } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
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 { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
|
||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { FormBuilder, FormControl } from "@angular/forms";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Verification } from "@bitwarden/common/types/verification";
|
||||
|
||||
import { ModalRef } from "../../components/modal/modal.ref";
|
||||
import { ModalConfig } from "../../services/modal.service";
|
||||
@@ -16,7 +17,12 @@ export class UserVerificationPromptComponent {
|
||||
confirmDescription = this.config.data.confirmDescription;
|
||||
confirmButtonText = this.config.data.confirmButtonText;
|
||||
modalTitle = this.config.data.modalTitle;
|
||||
secret = new FormControl();
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
secret: this.formBuilder.control<Verification | null>(null),
|
||||
});
|
||||
|
||||
protected invalidSecret = false;
|
||||
|
||||
constructor(
|
||||
private modalRef: ModalRef,
|
||||
@@ -27,19 +33,31 @@ export class UserVerificationPromptComponent {
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
//Incorrect secret will throw an invalid password error.
|
||||
await this.userVerificationService.verifyUser(this.secret.value);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("error"),
|
||||
this.i18nService.t("invalidMasterPassword")
|
||||
);
|
||||
get secret() {
|
||||
return this.formGroup.controls.secret;
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalRef.close(true);
|
||||
try {
|
||||
//Incorrect secret will throw an invalid password error.
|
||||
await this.userVerificationService.verifyUser(this.secret.value);
|
||||
this.invalidSecret = false;
|
||||
} catch (e) {
|
||||
this.invalidSecret = true;
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("error"), e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.close(true);
|
||||
};
|
||||
|
||||
close(success: boolean) {
|
||||
this.modalRef.close(success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { ControlValueAccessor, FormControl } from "@angular/forms";
|
||||
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { ControlValueAccessor, FormControl, Validators } from "@angular/forms";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { Verification } from "@bitwarden/common/types/verification";
|
||||
|
||||
@@ -17,29 +19,65 @@ import { Verification } from "@bitwarden/common/types/verification";
|
||||
selector: "app-user-verification",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
||||
usesKeyConnector = false;
|
||||
export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy {
|
||||
private _invalidSecret = false;
|
||||
@Input()
|
||||
get invalidSecret() {
|
||||
return this._invalidSecret;
|
||||
}
|
||||
set invalidSecret(value: boolean) {
|
||||
this._invalidSecret = value;
|
||||
this.invalidSecretChange.emit(value);
|
||||
|
||||
// ISSUE: This is pretty hacky but unfortunately there is no way of knowing if the parent
|
||||
// control has been marked as touched, see: https://github.com/angular/angular/issues/10887
|
||||
// When that functionality has been added we should also look into forwarding reactive form
|
||||
// controls errors so that we don't need a separate input/output `invalidSecret`.
|
||||
if (value) {
|
||||
this.secret.markAsTouched();
|
||||
}
|
||||
this.secret.updateValueAndValidity({ emitEvent: false });
|
||||
}
|
||||
@Output() invalidSecretChange = new EventEmitter<boolean>();
|
||||
|
||||
usesKeyConnector = true;
|
||||
disableRequestOTP = false;
|
||||
sentCode = false;
|
||||
|
||||
secret = new FormControl("");
|
||||
secret = new FormControl("", [
|
||||
Validators.required,
|
||||
() => {
|
||||
if (this.invalidSecret) {
|
||||
return {
|
||||
invalidSecret: {
|
||||
message: this.usesKeyConnector
|
||||
? this.i18nService.t("incorrectCode")
|
||||
: this.i18nService.t("incorrectPassword"),
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
private onChange: (value: Verification) => void;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private userVerificationService: UserVerificationService
|
||||
private userVerificationService: UserVerificationService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
|
||||
this.processChanges(this.secret.value);
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
this.secret.valueChanges.subscribe((secret: string) => this.processChanges(secret));
|
||||
this.secret.valueChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((secret: string) => this.processChanges(secret));
|
||||
}
|
||||
|
||||
async requestOTP() {
|
||||
requestOTP = async () => {
|
||||
if (this.usesKeyConnector) {
|
||||
this.disableRequestOTP = true;
|
||||
try {
|
||||
@@ -49,7 +87,7 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
||||
this.disableRequestOTP = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.secret.setValue(obj);
|
||||
@@ -72,7 +110,14 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private processChanges(secret: string) {
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
protected processChanges(secret: string) {
|
||||
this.invalidSecret = false;
|
||||
|
||||
if (this.onChange == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Directive, EventEmitter, Output } from "@angular/core";
|
||||
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import {
|
||||
EnvironmentService,
|
||||
Region,
|
||||
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -25,6 +28,9 @@ export class EnvironmentComponent {
|
||||
private modalService: ModalService
|
||||
) {
|
||||
const urls = this.environmentService.getUrls();
|
||||
if (this.environmentService.selectedRegion != Region.SelfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.baseUrl = urls.base || "";
|
||||
this.webVaultUrl = urls.webVault || "";
|
||||
|
||||
@@ -133,10 +133,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||
|
||||
// RSA Encrypt user's encKey.key with organization public key
|
||||
const userEncKey = await this.cryptoService.getEncKey();
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(
|
||||
userEncKey.key,
|
||||
publicKey.buffer
|
||||
);
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(userEncKey.key, publicKey);
|
||||
|
||||
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
resetRequest.masterPasswordHash = masterPasswordHash;
|
||||
|
||||
20
libs/angular/src/directives/copy-text.directive.ts
Normal file
20
libs/angular/src/directives/copy-text.directive.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Directive, ElementRef, HostListener, Input } from "@angular/core";
|
||||
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@Directive({
|
||||
selector: "[appCopyText]",
|
||||
})
|
||||
export class CopyTextDirective {
|
||||
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) {}
|
||||
|
||||
@Input("appCopyText") copyText: string;
|
||||
|
||||
@HostListener("copy") onCopy() {
|
||||
if (window == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.platformUtilsService.copyToClipboard(this.copyText, { window: window });
|
||||
}
|
||||
}
|
||||
137
libs/angular/src/directives/if-feature.directive.spec.ts
Normal file
137
libs/angular/src/directives/if-feature.directive.spec.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { By } from "@angular/platform-browser";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
import { IfFeatureDirective } from "./if-feature.directive";
|
||||
|
||||
const testBooleanFeature: FeatureFlag = "boolean-feature" as FeatureFlag;
|
||||
const testStringFeature: FeatureFlag = "string-feature" as FeatureFlag;
|
||||
const testStringFeatureValue = "test-value";
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div *appIfFeature="testBooleanFeature">
|
||||
<div data-testid="boolean-content">Hidden behind feature flag</div>
|
||||
</div>
|
||||
<div *appIfFeature="stringFeature; value: stringFeatureValue">
|
||||
<div data-testid="string-content">Hidden behind feature flag</div>
|
||||
</div>
|
||||
<div *appIfFeature="missingFlag">
|
||||
<div data-testid="missing-flag-content">
|
||||
Hidden behind missing flag. Should not be visible.
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class TestComponent {
|
||||
testBooleanFeature = testBooleanFeature;
|
||||
stringFeature = testStringFeature;
|
||||
stringFeatureValue = testStringFeatureValue;
|
||||
|
||||
missingFlag = "missing-flag" as FeatureFlag;
|
||||
}
|
||||
|
||||
describe("IfFeatureDirective", () => {
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let content: HTMLElement;
|
||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||
|
||||
const mockConfigFlagValue = (flag: FeatureFlag, flagValue: any) => {
|
||||
if (typeof flagValue === "boolean") {
|
||||
mockConfigService.getFeatureFlagBool.mockImplementation((f, defaultValue = false) =>
|
||||
flag == f ? Promise.resolve(flagValue) : Promise.resolve(defaultValue)
|
||||
);
|
||||
} else if (typeof flagValue === "string") {
|
||||
mockConfigService.getFeatureFlagString.mockImplementation((f, defaultValue = "") =>
|
||||
flag == f ? Promise.resolve(flagValue) : Promise.resolve(defaultValue)
|
||||
);
|
||||
} else if (typeof flagValue === "number") {
|
||||
mockConfigService.getFeatureFlagNumber.mockImplementation((f, defaultValue = 0) =>
|
||||
flag == f ? Promise.resolve(flagValue) : Promise.resolve(defaultValue)
|
||||
);
|
||||
}
|
||||
};
|
||||
const queryContent = (testId: string) =>
|
||||
fixture.debugElement.query(By.css(`[data-testid="${testId}"]`))?.nativeElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [IfFeatureDirective, TestComponent],
|
||||
providers: [
|
||||
{ provide: LogService, useValue: mock<LogService>() },
|
||||
{
|
||||
provide: ConfigServiceAbstraction,
|
||||
useValue: mockConfigService,
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
});
|
||||
|
||||
it("renders content when the feature flag is enabled", async () => {
|
||||
mockConfigFlagValue(testBooleanFeature, true);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
content = queryContent("boolean-content");
|
||||
|
||||
expect(content).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders content when the feature flag value matches the provided value", async () => {
|
||||
mockConfigFlagValue(testStringFeature, testStringFeatureValue);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
content = queryContent("string-content");
|
||||
|
||||
expect(content).toBeDefined();
|
||||
});
|
||||
|
||||
it("hides content when the feature flag is disabled", async () => {
|
||||
mockConfigFlagValue(testBooleanFeature, false);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
content = queryContent("boolean-content");
|
||||
|
||||
expect(content).toBeUndefined();
|
||||
});
|
||||
|
||||
it("hides content when the feature flag value does not match the provided value", async () => {
|
||||
mockConfigFlagValue(testStringFeature, "wrong-value");
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
content = queryContent("string-content");
|
||||
|
||||
expect(content).toBeUndefined();
|
||||
});
|
||||
|
||||
it("hides content when the feature flag is missing", async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
content = queryContent("missing-flag-content");
|
||||
|
||||
expect(content).toBeUndefined();
|
||||
});
|
||||
|
||||
it("hides content when the directive throws an unexpected exception", async () => {
|
||||
mockConfigService.getFeatureFlagBool.mockImplementation(() => Promise.reject("Some error"));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
content = queryContent("boolean-content");
|
||||
|
||||
expect(content).toBeUndefined();
|
||||
});
|
||||
});
|
||||
67
libs/angular/src/directives/if-feature.directive.ts
Normal file
67
libs/angular/src/directives/if-feature.directive.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Directive, Input, OnInit, TemplateRef, ViewContainerRef } from "@angular/core";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
// Replace this with a type safe lookup of the feature flag values in PM-2282
|
||||
type FlagValue = boolean | number | string;
|
||||
|
||||
/**
|
||||
* Directive that conditionally renders the element when the feature flag is enabled and/or
|
||||
* matches the value specified by {@link appIfFeatureValue}.
|
||||
*
|
||||
* When a feature flag is not found in the config service, the element is hidden.
|
||||
*/
|
||||
@Directive({
|
||||
selector: "[appIfFeature]",
|
||||
})
|
||||
export class IfFeatureDirective implements OnInit {
|
||||
/**
|
||||
* The feature flag to check.
|
||||
*/
|
||||
@Input() appIfFeature: FeatureFlag;
|
||||
|
||||
/**
|
||||
* Optional value to compare against the value of the feature flag in the config service.
|
||||
* @default true
|
||||
*/
|
||||
@Input() appIfFeatureValue: FlagValue = true;
|
||||
|
||||
private hasView = false;
|
||||
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private viewContainer: ViewContainerRef,
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
try {
|
||||
let flagValue: FlagValue;
|
||||
|
||||
if (typeof this.appIfFeatureValue === "boolean") {
|
||||
flagValue = await this.configService.getFeatureFlagBool(this.appIfFeature);
|
||||
} else if (typeof this.appIfFeatureValue === "number") {
|
||||
flagValue = await this.configService.getFeatureFlagNumber(this.appIfFeature);
|
||||
} else if (typeof this.appIfFeatureValue === "string") {
|
||||
flagValue = await this.configService.getFeatureFlagString(this.appIfFeature);
|
||||
}
|
||||
|
||||
if (this.appIfFeatureValue === flagValue) {
|
||||
if (!this.hasView) {
|
||||
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
this.hasView = true;
|
||||
}
|
||||
} else {
|
||||
this.viewContainer.clear();
|
||||
this.hasView = false;
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
this.viewContainer.clear();
|
||||
this.hasView = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Directive, ElementRef, HostListener } from "@angular/core";
|
||||
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@Directive({
|
||||
selector: "[appSelectCopy]",
|
||||
})
|
||||
export class SelectCopyDirective {
|
||||
constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) {}
|
||||
|
||||
@HostListener("copy") onCopy() {
|
||||
if (window == null) {
|
||||
return;
|
||||
}
|
||||
let copyText = "";
|
||||
const selection = window.getSelection();
|
||||
for (let i = 0; i < selection.rangeCount; i++) {
|
||||
const range = selection.getRangeAt(i);
|
||||
const text = range.toString();
|
||||
|
||||
// The selection should only contain one line of text. In some cases however, the
|
||||
// selection contains newlines and space characters from the indentation of following
|
||||
// sibling nodes. To avoid copying passwords containing trailing newlines and spaces
|
||||
// that aren't part of the password, the selection has to be trimmed.
|
||||
let stringEndPos = text.length;
|
||||
const newLinePos = text.search(/(?:\r\n|\r|\n)/);
|
||||
if (newLinePos > -1) {
|
||||
const otherPart = text.substr(newLinePos).trim();
|
||||
if (otherPart === "") {
|
||||
stringEndPos = newLinePos;
|
||||
}
|
||||
}
|
||||
copyText += text.substring(0, stringEndPos);
|
||||
}
|
||||
this.platformUtilsService.copyToClipboard(copyText, { window: window });
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,13 @@ import { ApiActionDirective } from "./directives/api-action.directive";
|
||||
import { AutofocusDirective } from "./directives/autofocus.directive";
|
||||
import { BoxRowDirective } from "./directives/box-row.directive";
|
||||
import { CopyClickDirective } from "./directives/copy-click.directive";
|
||||
import { CopyTextDirective } from "./directives/copy-text.directive";
|
||||
import { FallbackSrcDirective } from "./directives/fallback-src.directive";
|
||||
import { IfFeatureDirective } from "./directives/if-feature.directive";
|
||||
import { InputStripSpacesDirective } from "./directives/input-strip-spaces.directive";
|
||||
import { InputVerbatimDirective } from "./directives/input-verbatim.directive";
|
||||
import { LaunchClickDirective } from "./directives/launch-click.directive";
|
||||
import { NotPremiumDirective } from "./directives/not-premium.directive";
|
||||
import { SelectCopyDirective } from "./directives/select-copy.directive";
|
||||
import { StopClickDirective } from "./directives/stop-click.directive";
|
||||
import { StopPropDirective } from "./directives/stop-prop.directive";
|
||||
import { TrueFalseValueDirective } from "./directives/true-false-value.directive";
|
||||
@@ -25,6 +26,7 @@ import { SearchPipe } from "./pipes/search.pipe";
|
||||
import { UserNamePipe } from "./pipes/user-name.pipe";
|
||||
import { UserTypePipe } from "./pipes/user-type.pipe";
|
||||
import { EllipsisPipe } from "./platform/pipes/ellipsis.pipe";
|
||||
import { FingerprintPipe } from "./platform/pipes/fingerprint.pipe";
|
||||
import { I18nPipe } from "./platform/pipes/i18n.pipe";
|
||||
import { PasswordStrengthComponent } from "./shared/components/password-strength/password-strength.component";
|
||||
import { ExportScopeCalloutComponent } from "./tools/export/components/export-scope-callout.component";
|
||||
@@ -48,6 +50,7 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
AutofocusDirective,
|
||||
BoxRowDirective,
|
||||
CalloutComponent,
|
||||
CopyTextDirective,
|
||||
CreditCardNumberPipe,
|
||||
EllipsisPipe,
|
||||
ExportScopeCalloutComponent,
|
||||
@@ -59,7 +62,6 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
NotPremiumDirective,
|
||||
SearchCiphersPipe,
|
||||
SearchPipe,
|
||||
SelectCopyDirective,
|
||||
StopClickDirective,
|
||||
StopPropDirective,
|
||||
TrueFalseValueDirective,
|
||||
@@ -68,6 +70,8 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
UserNamePipe,
|
||||
PasswordStrengthComponent,
|
||||
UserTypePipe,
|
||||
IfFeatureDirective,
|
||||
FingerprintPipe,
|
||||
],
|
||||
exports: [
|
||||
A11yInvalidDirective,
|
||||
@@ -77,6 +81,7 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
BitwardenToastModule,
|
||||
BoxRowDirective,
|
||||
CalloutComponent,
|
||||
CopyTextDirective,
|
||||
CreditCardNumberPipe,
|
||||
EllipsisPipe,
|
||||
ExportScopeCalloutComponent,
|
||||
@@ -88,7 +93,6 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
NotPremiumDirective,
|
||||
SearchCiphersPipe,
|
||||
SearchPipe,
|
||||
SelectCopyDirective,
|
||||
StopClickDirective,
|
||||
StopPropDirective,
|
||||
TrueFalseValueDirective,
|
||||
@@ -97,7 +101,17 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
UserNamePipe,
|
||||
PasswordStrengthComponent,
|
||||
UserTypePipe,
|
||||
IfFeatureDirective,
|
||||
FingerprintPipe,
|
||||
],
|
||||
providers: [
|
||||
CreditCardNumberPipe,
|
||||
DatePipe,
|
||||
I18nPipe,
|
||||
SearchPipe,
|
||||
UserNamePipe,
|
||||
UserTypePipe,
|
||||
FingerprintPipe,
|
||||
],
|
||||
providers: [CreditCardNumberPipe, DatePipe, I18nPipe, SearchPipe, UserNamePipe, UserTypePipe],
|
||||
})
|
||||
export class JslibModule {}
|
||||
|
||||
29
libs/angular/src/platform/pipes/fingerprint.pipe.ts
Normal file
29
libs/angular/src/platform/pipes/fingerprint.pipe.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Pipe } from "@angular/core";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
@Pipe({
|
||||
name: "fingerprint",
|
||||
})
|
||||
export class FingerprintPipe {
|
||||
constructor(private cryptoService: CryptoService) {}
|
||||
|
||||
async transform(publicKey: string | Uint8Array, fingerprintMaterial: string): Promise<string> {
|
||||
try {
|
||||
if (typeof publicKey === "string") {
|
||||
publicKey = Utils.fromB64ToArray(publicKey);
|
||||
}
|
||||
|
||||
const fingerprint = await this.cryptoService.getFingerprint(fingerprintMaterial, publicKey);
|
||||
|
||||
if (fingerprint != null) {
|
||||
return fingerprint.join("-");
|
||||
}
|
||||
|
||||
return "";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,4 +48,10 @@ export type SimpleDialogOptions = {
|
||||
|
||||
/** Whether or not the user can use escape or clicking the backdrop to close the dialog */
|
||||
disableClose?: boolean;
|
||||
|
||||
/**
|
||||
* Custom accept action. Runs when the user clicks the accept button and shows a loading spinner until the promise
|
||||
* is resolved.
|
||||
*/
|
||||
acceptAction?: () => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -18,13 +18,11 @@ import { OrganizationUserService } from "@bitwarden/common/abstractions/organiza
|
||||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
|
||||
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import {
|
||||
InternalOrganizationService,
|
||||
InternalOrganizationServiceAbstraction,
|
||||
OrganizationService as OrganizationServiceAbstraction,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
@@ -48,6 +46,8 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarde
|
||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service";
|
||||
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||
@@ -583,7 +583,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
deps: [StateServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: InternalOrganizationService,
|
||||
provide: InternalOrganizationServiceAbstraction,
|
||||
useExisting: OrganizationServiceAbstraction,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ export class ExportScopeCalloutComponent implements OnInit {
|
||||
this.organizationId != null
|
||||
? {
|
||||
title: "exportingOrganizationVaultTitle",
|
||||
description: "exportingOrganizationVaultDescription",
|
||||
description: "exportingOrganizationVaultDesc",
|
||||
scopeIdentifier: this.organizationService.get(this.organizationId).name,
|
||||
}
|
||||
: {
|
||||
|
||||
@@ -3,9 +3,9 @@ import { UntypedFormBuilder, Validators } from "@angular/forms";
|
||||
import { merge, startWith, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { EncryptedExportType, EventType } from "@bitwarden/common/enums";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
|
||||
@@ -25,11 +25,9 @@ export class SendComponent implements OnInit, OnDestroy {
|
||||
expired = false;
|
||||
type: SendType = null;
|
||||
sends: SendView[] = [];
|
||||
filteredSends: SendView[] = [];
|
||||
searchText: string;
|
||||
selectedType: SendType;
|
||||
selectedAll: boolean;
|
||||
searchPlaceholder: string;
|
||||
filter: (cipher: SendView) => boolean;
|
||||
searchPending = false;
|
||||
hasSearched = false; // search() function called - returns true if text qualifies for search
|
||||
@@ -41,6 +39,15 @@ export class SendComponent implements OnInit, OnDestroy {
|
||||
|
||||
private searchTimeout: any;
|
||||
private destroy$ = new Subject<void>();
|
||||
private _filteredSends: SendView[];
|
||||
|
||||
get filteredSends(): SendView[] {
|
||||
return this._filteredSends;
|
||||
}
|
||||
|
||||
set filteredSends(filteredSends: SendView[]) {
|
||||
this._filteredSends = filteredSends;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected sendService: SendService,
|
||||
|
||||
@@ -604,9 +604,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected saveCipher(cipher: Cipher) {
|
||||
const isNotClone = this.editMode && !this.cloneMode;
|
||||
const orgAdmin = this.organization?.isAdmin;
|
||||
return this.cipher.id == null
|
||||
? this.cipherService.createWithServer(cipher)
|
||||
: this.cipherService.updateWithServer(cipher);
|
||||
? this.cipherService.createWithServer(cipher, orgAdmin)
|
||||
: this.cipherService.updateWithServer(cipher, orgAdmin, isNotClone);
|
||||
}
|
||||
|
||||
protected deleteCipher() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { Validators, FormBuilder } from "@angular/forms";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -22,13 +23,18 @@ export class FolderAddEditComponent implements OnInit {
|
||||
deletePromise: Promise<any>;
|
||||
protected componentName = "";
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
name: ["", [Validators.required]],
|
||||
});
|
||||
|
||||
constructor(
|
||||
protected folderService: FolderService,
|
||||
protected folderApiService: FolderApiServiceAbstraction,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService,
|
||||
protected dialogService: DialogServiceAbstraction
|
||||
protected logService: LogService,
|
||||
protected dialogService: DialogServiceAbstraction,
|
||||
protected formBuilder: FormBuilder
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -36,6 +42,7 @@ export class FolderAddEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
async submit(): Promise<boolean> {
|
||||
this.folder.name = this.formGroup.controls.name.value;
|
||||
if (this.folder.name == null || this.folder.name === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
@@ -97,5 +104,6 @@ export class FolderAddEditComponent implements OnInit {
|
||||
} else {
|
||||
this.title = this.i18nService.t("addFolder");
|
||||
}
|
||||
this.formGroup.controls.name.setValue(this.folder.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
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";
|
||||
@@ -13,6 +14,7 @@ export class PremiumComponent implements OnInit {
|
||||
isPremium = false;
|
||||
price = 10;
|
||||
refreshPromise: Promise<any>;
|
||||
cloudWebVaultUrl: string;
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
@@ -20,8 +22,11 @@ export class PremiumComponent implements OnInit {
|
||||
protected apiService: ApiService,
|
||||
private logService: LogService,
|
||||
protected stateService: StateService,
|
||||
protected dialogService: DialogServiceAbstraction
|
||||
) {}
|
||||
protected dialogService: DialogServiceAbstraction,
|
||||
private environmentService: EnvironmentService
|
||||
) {
|
||||
this.cloudWebVaultUrl = this.environmentService.getCloudWebVaultUrl();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.isPremium = await this.stateService.getCanAccessPremium();
|
||||
@@ -46,7 +51,7 @@ export class PremiumComponent implements OnInit {
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase");
|
||||
this.platformUtilsService.launchUri(`${this.cloudWebVaultUrl}/#/?premium=purchase`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +63,7 @@ export class PremiumComponent implements OnInit {
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=manage");
|
||||
this.platformUtilsService.launchUri(`${this.cloudWebVaultUrl}/#/?premium=manage`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,16 +329,16 @@ export class ViewComponent implements OnDestroy, OnInit {
|
||||
this.platformUtilsService.launchUri(uri.launchUri);
|
||||
}
|
||||
|
||||
async copy(value: string, typeI18nKey: string, aType: string) {
|
||||
async copy(value: string, typeI18nKey: string, aType: string): Promise<boolean> {
|
||||
if (value == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.passwordRepromptService.protectedFields().includes(aType) &&
|
||||
!(await this.promptPassword())
|
||||
) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const copyOptions = this.win != null ? { window: this.win } : null;
|
||||
@@ -356,6 +356,8 @@ export class ViewComponent implements OnDestroy, OnInit {
|
||||
} else if (aType === "H_Field") {
|
||||
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
setTextDataOnDrag(event: DragEvent, data: string) {
|
||||
|
||||
Reference in New Issue
Block a user