1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 08:43:33 +00:00

Merge branch 'master' into PM-2135-beeep-refactor-and-refresh-web-user-verification-components

This commit is contained in:
Andreas Coroiu
2023-06-19 14:43:36 +02:00
445 changed files with 8485 additions and 2207 deletions

View File

@@ -1,13 +1,13 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
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 { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@Directive()
export class CollectionsComponent implements OnInit {

View File

@@ -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,8 +40,8 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
euServerFlagEnabled: boolean;
isOpen = false;
showingModal = false;
selectedEnvironment: ServerEnvironment;
ServerEnvironmentType = ServerEnvironment;
selectedEnvironment: Region;
ServerEnvironmentType = Region;
overlayPostition: ConnectedPosition[] = [
{
originX: "start",
@@ -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,18 +70,20 @@ 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();
}
@@ -86,14 +91,8 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
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;
}
this.selectedEnvironment = this.environmentService.selectedRegion;
}
close() {
@@ -101,9 +100,3 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
this.updateEnvironmentInfo();
}
}
enum ServerEnvironment {
US = "US",
EU = "EU",
SelfHosted = "Self-hosted",
}

View File

@@ -24,7 +24,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
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 { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@@ -69,7 +69,7 @@ export class LockComponent implements OnInit, OnDestroy {
protected ngZone: NgZone,
protected policyApiService: PolicyApiServiceAbstraction,
protected policyService: InternalPolicyService,
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
protected dialogService: DialogServiceAbstraction
) {}
@@ -333,7 +333,7 @@ export class LockComponent implements OnInit, OnDestroy {
return false;
}
const passwordStrength = this.passwordGenerationService.passwordStrength(
const passwordStrength = this.passwordStrengthService.getPasswordStrength(
this.masterPassword,
this.email
)?.score;

View File

@@ -12,10 +12,6 @@ import { PasswordLogInCredentials } from "@bitwarden/common/auth/models/domain/l
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import {
AllValidationErrors,
FormValidationErrorsService,
} from "@bitwarden/common/platform/abstractions/form-validation-errors.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";
@@ -23,6 +19,11 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import {
AllValidationErrors,
FormValidationErrorsService,
} from "../../platform/abstractions/form-validation-errors.service";
import { CaptchaProtectedComponent } from "./captcha-protected.component";
@Directive()

View File

@@ -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 || "";

View File

@@ -13,10 +13,6 @@ import { ReferenceEventRequest } from "@bitwarden/common/models/request/referenc
import { RegisterRequest } from "@bitwarden/common/models/request/register.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import {
AllValidationErrors,
FormValidationErrorsService,
} from "@bitwarden/common/platform/abstractions/form-validation-errors.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";
@@ -25,6 +21,10 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { CaptchaProtectedComponent } from "../auth/components/captcha-protected.component";
import {
AllValidationErrors,
FormValidationErrorsService,
} from "../platform/abstractions/form-validation-errors.service";
import { DialogServiceAbstraction, SimpleDialogType } from "../services/dialog";
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
import { InputsFieldMatch } from "../validators/inputsFieldMatch.validator";

View File

@@ -1,18 +1,18 @@
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs";
import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
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 { Utils } from "@bitwarden/common/platform/misc/utils";
import { Checkable, isChecked } from "@bitwarden/common/types/checkable";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@Directive()
export class ShareComponent implements OnInit, OnDestroy {

View 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();
});
});

View 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;
}
}
}

View File

@@ -0,0 +1,152 @@
import { Component } from "@angular/core";
import { TestBed } from "@angular/core/testing";
import { CanActivateFn, Router } from "@angular/router";
import { RouterTestingModule } from "@angular/router/testing";
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 { 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 { I18nMockService } from "@bitwarden/components/src";
import { canAccessFeature } from "./feature-flag.guard";
@Component({ template: "" })
export class EmptyComponent {}
describe("canAccessFeature", () => {
const testFlag: FeatureFlag = "test-flag" as FeatureFlag;
const featureRoute = "enabled-feature";
const redirectRoute = "redirect";
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
let mockPlatformUtilsService: MockProxy<PlatformUtilsService>;
const setup = (featureGuard: CanActivateFn, flagValue: any) => {
mockConfigService = mock<ConfigServiceAbstraction>();
mockPlatformUtilsService = mock<PlatformUtilsService>();
// Mock the correct getter based on the type of flagValue; also mock default values if one is not provided
if (typeof flagValue === "boolean") {
mockConfigService.getFeatureFlagBool.mockImplementation((flag, defaultValue = false) =>
flag == testFlag ? Promise.resolve(flagValue) : Promise.resolve(defaultValue)
);
} else if (typeof flagValue === "string") {
mockConfigService.getFeatureFlagString.mockImplementation((flag, defaultValue = "") =>
flag == testFlag ? Promise.resolve(flagValue) : Promise.resolve(defaultValue)
);
} else if (typeof flagValue === "number") {
mockConfigService.getFeatureFlagNumber.mockImplementation((flag, defaultValue = 0) =>
flag == testFlag ? Promise.resolve(flagValue) : Promise.resolve(defaultValue)
);
}
const testBed = TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{ path: "", component: EmptyComponent },
{
path: featureRoute,
component: EmptyComponent,
canActivate: [featureGuard],
},
{ path: redirectRoute, component: EmptyComponent },
]),
],
providers: [
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
{ provide: PlatformUtilsService, useValue: mockPlatformUtilsService },
{ provide: LogService, useValue: mock<LogService>() },
{
provide: I18nService,
useValue: new I18nMockService({
accessDenied: "Access Denied!",
}),
},
],
});
return {
router: testBed.inject(Router),
};
};
it("successfully navigates when the feature flag is enabled", async () => {
const { router } = setup(canAccessFeature(testFlag), true);
await router.navigate([featureRoute]);
expect(router.url).toBe(`/${featureRoute}`);
});
it("successfully navigates when the feature flag value matches the required value", async () => {
const { router } = setup(canAccessFeature(testFlag, "some-value"), "some-value");
await router.navigate([featureRoute]);
expect(router.url).toBe(`/${featureRoute}`);
});
it("fails to navigate when the feature flag is disabled", async () => {
const { router } = setup(canAccessFeature(testFlag), false);
await router.navigate([featureRoute]);
expect(router.url).toBe("/");
});
it("fails to navigate when the feature flag value does not match the required value", async () => {
const { router } = setup(canAccessFeature(testFlag, "some-value"), "some-wrong-value");
await router.navigate([featureRoute]);
expect(router.url).toBe("/");
});
it("fails to navigate when the feature flag does not exist", async () => {
const { router } = setup(canAccessFeature("missing-flag" as FeatureFlag), true);
await router.navigate([featureRoute]);
expect(router.url).toBe("/");
});
it("shows an error toast when the feature flag is disabled", async () => {
const { router } = setup(canAccessFeature(testFlag), false);
await router.navigate([featureRoute]);
expect(mockPlatformUtilsService.showToast).toHaveBeenCalledWith(
"error",
null,
"Access Denied!"
);
});
it("does not show an error toast when the feature flag is enabled", async () => {
const { router } = setup(canAccessFeature(testFlag), true);
await router.navigate([featureRoute]);
expect(mockPlatformUtilsService.showToast).not.toHaveBeenCalled();
});
it("redirects to the specified redirect url when the feature flag is disabled", async () => {
const { router } = setup(canAccessFeature(testFlag, true, redirectRoute), false);
await router.navigate([featureRoute]);
expect(router.url).toBe(`/${redirectRoute}`);
});
it("fails to navigate when the config service throws an unexpected exception", async () => {
const { router } = setup(canAccessFeature(testFlag), true);
mockConfigService.getFeatureFlagBool.mockImplementation(() => Promise.reject("Some error"));
await router.navigate([featureRoute]);
expect(router.url).toBe("/");
});
});

View File

@@ -0,0 +1,58 @@
import { inject } from "@angular/core";
import { CanActivateFn, Router } from "@angular/router";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
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";
// Replace this with a type safe lookup of the feature flag values in PM-2282
type FlagValue = boolean | number | string;
/**
* Returns a CanActivateFn that checks if the feature flag is enabled. If not, it shows an "Access Denied!"
* toast and optionally redirects to the specified url.
* @param featureFlag - The feature flag to check
* @param requiredFlagValue - Optional value to the feature flag must be equal to, defaults to true
* @param redirectUrlOnDisabled - Optional url to redirect to if the feature flag is disabled
*/
export const canAccessFeature = (
featureFlag: FeatureFlag,
requiredFlagValue: FlagValue = true,
redirectUrlOnDisabled?: string
): CanActivateFn => {
return async () => {
const configService = inject(ConfigServiceAbstraction);
const platformUtilsService = inject(PlatformUtilsService);
const router = inject(Router);
const i18nService = inject(I18nService);
const logService = inject(LogService);
let flagValue: FlagValue;
try {
if (typeof requiredFlagValue === "boolean") {
flagValue = await configService.getFeatureFlagBool(featureFlag);
} else if (typeof requiredFlagValue === "number") {
flagValue = await configService.getFeatureFlagNumber(featureFlag);
} else if (typeof requiredFlagValue === "string") {
flagValue = await configService.getFeatureFlagString(featureFlag);
}
if (flagValue === requiredFlagValue) {
return true;
}
platformUtilsService.showToast("error", null, i18nService.t("accessDenied"));
if (redirectUrlOnDisabled != null) {
return router.createUrlTree([redirectUrlOnDisabled]);
}
return false;
} catch (e) {
logService.error(e);
return false;
}
};
};

View File

@@ -11,6 +11,7 @@ import { AutofocusDirective } from "./directives/autofocus.directive";
import { BoxRowDirective } from "./directives/box-row.directive";
import { CopyClickDirective } from "./directives/copy-click.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";
@@ -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";
@@ -68,6 +70,8 @@ import { IconComponent } from "./vault/components/icon.component";
UserNamePipe,
PasswordStrengthComponent,
UserTypePipe,
IfFeatureDirective,
FingerprintPipe,
],
exports: [
A11yInvalidDirective,
@@ -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 {}

View File

@@ -0,0 +1,13 @@
import { AbstractControl } from "@angular/forms";
export interface AllValidationErrors {
controlName: string;
errorName: string;
}
export interface FormGroupControls {
[key: string]: AbstractControl;
}
export abstract class FormValidationErrorsService {
getFormValidationErrors: (controls: FormGroupControls) => AllValidationErrors[];
}

View File

@@ -3,6 +3,9 @@ import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: "ellipsis",
})
/**
* @deprecated Use the tailwind class 'tw-truncate' instead
*/
export class EllipsisPipe implements PipeTransform {
transform(value: string, limit = 25, completeWords = false, ellipsis = "...") {
if (value.length <= limit) {

View File

@@ -0,0 +1,32 @@
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.buffer
);
if (fingerprint != null) {
return fingerprint.join("-");
}
return "";
} catch {
return "";
}
}
}

View File

@@ -0,0 +1,31 @@
import { UntypedFormGroup, ValidationErrors } from "@angular/forms";
import {
FormGroupControls,
FormValidationErrorsService as FormValidationErrorsAbstraction,
AllValidationErrors,
} from "../abstractions/form-validation-errors.service";
export class FormValidationErrorsService implements FormValidationErrorsAbstraction {
getFormValidationErrors(controls: FormGroupControls): AllValidationErrors[] {
let errors: AllValidationErrors[] = [];
Object.keys(controls).forEach((key) => {
const control = controls[key];
if (control instanceof UntypedFormGroup) {
errors = errors.concat(this.getFormValidationErrors(control.controls));
}
const controlErrors: ValidationErrors = controls[key].errors;
if (controlErrors !== null) {
Object.keys(controlErrors).forEach((keyError) => {
errors.push({
controlName: key,
errorName: keyError,
});
});
}
});
return errors;
}
}

View File

@@ -20,7 +20,6 @@ import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/collection.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import {
InternalOrganizationService,
@@ -32,7 +31,6 @@ import {
PolicyService as PolicyServiceAbstraction,
} from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { CollectionService } from "@bitwarden/common/admin-console/services/collection.service";
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
@@ -68,7 +66,6 @@ import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/pla
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/platform/abstractions/environment.service";
import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/platform/abstractions/file-upload/file-upload.service";
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "@bitwarden/common/platform/abstractions/form-validation-errors.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service";
@@ -90,7 +87,6 @@ import { EncryptServiceImplementation } from "@bitwarden/common/platform/service
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
import { FormValidationErrorsService } from "@bitwarden/common/platform/services/form-validation-errors.service";
import { StateMigrationService } from "@bitwarden/common/platform/services/state-migration.service";
import { StateService } from "@bitwarden/common/platform/services/state.service";
import { ValidationService } from "@bitwarden/common/platform/services/validation.service";
@@ -120,11 +116,16 @@ import {
UsernameGenerationService,
UsernameGenerationServiceAbstraction,
} from "@bitwarden/common/tools/generator/username";
import {
PasswordStrengthService,
PasswordStrengthServiceAbstraction,
} from "@bitwarden/common/tools/password-strength";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service";
import { SendApiService as SendApiServiceAbstraction } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service";
import { SendService as SendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/vault/abstractions/collection.service";
import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
import {
@@ -135,6 +136,7 @@ import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@
import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync-notifier.service.abstraction";
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/services/collection.service";
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
@@ -148,7 +150,9 @@ import {
import { AuthGuard } from "../auth/guards/auth.guard";
import { LockGuard } from "../auth/guards/lock.guard";
import { UnauthGuard } from "../auth/guards/unauth.guard";
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
import { BroadcasterService } from "../platform/services/broadcaster.service";
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
import { PasswordRepromptService } from "../vault/services/password-reprompt.service";
import {
@@ -239,7 +243,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
TwoFactorServiceAbstraction,
I18nServiceAbstraction,
EncryptService,
PasswordGenerationServiceAbstraction,
PasswordStrengthServiceAbstraction,
PolicyServiceAbstraction,
],
},
@@ -359,6 +363,11 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
DevicesApiServiceAbstraction,
],
},
{
provide: PasswordStrengthServiceAbstraction,
useClass: PasswordStrengthService,
deps: [],
},
{
provide: PasswordGenerationServiceAbstraction,
useClass: PasswordGenerationService,

View File

@@ -1,7 +1,7 @@
import { Component, EventEmitter, Input, OnChanges, Output } from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
export interface PasswordColorText {
color: string;
@@ -59,7 +59,7 @@ export class PasswordStrengthComponent implements OnChanges {
constructor(
private i18nService: I18nService,
private passwordGenerationService: PasswordGenerationServiceAbstraction
private passwordStrengthService: PasswordStrengthServiceAbstraction
) {}
ngOnChanges(): void {
@@ -96,7 +96,7 @@ export class PasswordStrengthComponent implements OnChanges {
clearTimeout(this.masterPasswordStrengthTimeout);
}
const strengthResult = this.passwordGenerationService.passwordStrength(
const strengthResult = this.passwordStrengthService.getPasswordStrength(
masterPassword,
this.email,
this.name?.trim().toLowerCase().split(" ")

View File

@@ -242,6 +242,7 @@ export class GeneratorComponent implements OnInit {
{ name: "Fastmail", value: "fastmail", validForSelfHosted: true },
{ name: "Firefox Relay", value: "firefoxrelay", validForSelfHosted: false },
{ name: "SimpleLogin", value: "simplelogin", validForSelfHosted: true },
{ name: "Forward Email", value: "forwardemail", validForSelfHosted: true },
];
this.usernameOptions = await this.usernameGenerationService.getOptions();

View File

@@ -1,6 +1,6 @@
import { AbstractControl, UntypedFormGroup, ValidatorFn } from "@angular/forms";
import { FormGroupControls } from "@bitwarden/common/platform/abstractions/form-validation-errors.service";
import { FormGroupControls } from "../platform/abstractions/form-validation-errors.service";
export class InputsFieldMatch {
//check to ensure two fields do not have the same value

View File

@@ -1,7 +1,7 @@
import { Observable } from "rxjs";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { DynamicTreeNode } from "../vault-filter/models/dynamic-tree-node.model";

View File

@@ -3,7 +3,6 @@ import { Observable, Subject, takeUntil, concatMap } from "rxjs";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service";
import {
isMember,
OrganizationService,
@@ -11,7 +10,6 @@ import {
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
import { EventType, SecureNoteType, UriMatchType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -21,6 +19,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
@@ -28,6 +27,7 @@ import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view";
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
@@ -368,6 +368,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
}
onCardNumberChange(): void {
this.cipher.card.brand = CardView.getCardBrandByPatterns(this.cipher.card.number);
}
getCardExpMonthDisplay() {
return this.cardExpMonthOptions.find((x) => x.value == this.cipher.card.expMonth)?.name;
}

View File

@@ -1,7 +1,7 @@
import { Directive, EventEmitter, Input, Output } from "@angular/core";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
import { ITreeNodeObject } from "@bitwarden/common/models/domain/tree-node";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { DynamicTreeNode } from "../models/dynamic-tree-node.model";
import { TopLevelTreeNode } from "../models/top-level-tree-node.model";

View File

@@ -2,8 +2,8 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { firstValueFrom, Observable } from "rxjs";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
import { ITreeNodeObject } from "@bitwarden/common/models/domain/tree-node";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { DeprecatedVaultFilterService } from "../../abstractions/deprecated-vault-filter.service";

View File

@@ -1,7 +1,6 @@
import { Injectable } from "@angular/core";
import { firstValueFrom, from, mergeMap, Observable } from "rxjs";
import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service";
import {
isMember,
OrganizationService,
@@ -9,12 +8,13 @@ import {
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CollectionView } from "@bitwarden/common/admin-console/models/view/collection.view";
import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "../../abstractions/deprecated-vault-filter.service";