1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 22:44:11 +00:00

Merge branch 'main' into ps/extension-refresh

This commit is contained in:
Vicki League
2024-08-27 10:55:30 -04:00
154 changed files with 2288 additions and 1265 deletions

View File

@@ -30,6 +30,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { BiometricsService } from "@bitwarden/common/platform/biometrics/biometric.service";
import { KeySuffixOptions } from "@bitwarden/common/platform/enums";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { UserId } from "@bitwarden/common/types/guid";
@@ -84,6 +85,7 @@ export class LockComponent implements OnInit, OnDestroy {
protected userVerificationService: UserVerificationService,
protected pinService: PinServiceAbstraction,
protected biometricStateService: BiometricStateService,
protected biometricsService: BiometricsService,
protected accountService: AccountService,
protected authService: AuthService,
protected kdfConfigService: KdfConfigService,
@@ -146,6 +148,13 @@ export class LockComponent implements OnInit, OnDestroy {
return !!userKey;
}
async isBiometricUnlockAvailable(): Promise<boolean> {
if (!(await this.biometricsService.supportsBiometric())) {
return false;
}
return this.biometricsService.isBiometricUnlockAvailable();
}
togglePassword() {
this.showPassword = !this.showPassword;
const input = document.getElementById(this.pinEnabled ? "pin" : "masterPassword");
@@ -327,7 +336,7 @@ export class LockComponent implements OnInit, OnDestroy {
this.masterPasswordEnabled = await this.userVerificationService.hasMasterPassword();
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.supportsBiometric = await this.biometricsService.supportsBiometric();
this.biometricLock =
(await this.vaultTimeoutSettingsService.isBiometricLockSet()) &&
((await this.cryptoService.hasUserKeyStored(KeySuffixOptions.Biometric)) ||

View File

@@ -23,6 +23,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import {
@@ -39,7 +40,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
showPassword = false;
formPromise: Promise<AuthResult>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginNavigate: (userId: UserId) => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
showLoginWithDevice: boolean;
@@ -185,7 +186,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
if (this.onSuccessfulLoginNavigate != null) {
// 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
this.onSuccessfulLoginNavigate();
this.onSuccessfulLoginNavigate(response.userId);
} else {
this.loginEmailService.clearValues();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.

View File

@@ -141,7 +141,7 @@
</ng-container>
<!-- Account Credit -->
<ng-container *ngIf="showAccountCredit && usingAccountCredit">
<app-callout type="note">
<app-callout type="info">
{{ "makeSureEnoughCredit" | i18n }}
</app-callout>
</ng-container>

View File

@@ -1,14 +1,5 @@
<div
#callout
class="callout callout-{{ calloutStyle }}"
[ngClass]="{ clickable: clickable }"
[attr.role]="useAlertRole ? 'alert' : null"
>
<h3 class="callout-heading" *ngIf="title">
<i class="bwi {{ icon }}" *ngIf="icon" aria-hidden="true"></i>
{{ title }}
</h3>
<div class="enforced-policy-options" *ngIf="enforcedPolicyOptions">
<bit-callout [icon]="icon" [title]="title" [type]="$any(type)" [useAlertRole]="useAlertRole">
<div class="tw-pl-7 tw-m-0" *ngIf="enforcedPolicyOptions">
{{ enforcedPolicyMessage }}
<ul>
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
@@ -32,4 +23,4 @@
</ul>
</div>
<ng-content></ng-content>
</div>
</bit-callout>

View File

@@ -2,16 +2,19 @@ import { Component, Input, OnInit } from "@angular/core";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CalloutTypes } from "@bitwarden/components";
/**
* @deprecated use the CL's `CalloutComponent` instead
*/
@Component({
selector: "app-callout",
templateUrl: "callout.component.html",
})
export class CalloutComponent implements OnInit {
@Input() type = "info";
export class DeprecatedCalloutComponent implements OnInit {
@Input() type: CalloutTypes = "info";
@Input() icon: string;
@Input() title: string;
@Input() clickable: boolean;
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
@Input() enforcedPolicyMessage: string;
@Input() useAlertRole = false;
@@ -26,34 +29,6 @@ export class CalloutComponent implements OnInit {
if (this.enforcedPolicyMessage === undefined) {
this.enforcedPolicyMessage = this.i18nService.t("masterPasswordPolicyInEffect");
}
if (this.type === "warning" || this.type === "danger") {
if (this.type === "danger") {
this.calloutStyle = "danger";
}
if (this.title === undefined) {
this.title = this.i18nService.t("warning");
}
if (this.icon === undefined) {
this.icon = "bwi-exclamation-triangle";
}
} else if (this.type === "error") {
this.calloutStyle = "danger";
if (this.title === undefined) {
this.title = this.i18nService.t("error");
}
if (this.icon === undefined) {
this.icon = "bwi-error";
}
} else if (this.type === "tip") {
this.calloutStyle = "success";
if (this.title === undefined) {
this.title = this.i18nService.t("tip");
}
if (this.icon === undefined) {
this.icon = "bwi-lightbulb";
}
}
}
getPasswordScoreAlertDisplay() {

View File

@@ -14,6 +14,7 @@ import {
AsyncActionsModule,
AutofocusDirective,
ButtonModule,
CalloutModule,
CheckboxModule,
DialogModule,
FormFieldModule,
@@ -29,7 +30,7 @@ import {
} from "@bitwarden/components";
import { TwoFactorIconComponent } from "./auth/components/two-factor-icon.component";
import { CalloutComponent } from "./components/callout.component";
import { DeprecatedCalloutComponent } from "./components/callout.component";
import { A11yInvalidDirective } from "./directives/a11y-invalid.directive";
import { A11yTitleDirective } from "./directives/a11y-title.directive";
import { ApiActionDirective } from "./directives/api-action.directive";
@@ -72,6 +73,7 @@ import { IconComponent } from "./vault/components/icon.component";
FormFieldModule,
SelectModule,
ButtonModule,
CalloutModule,
CheckboxModule,
DialogModule,
TypographyModule,
@@ -88,7 +90,7 @@ import { IconComponent } from "./vault/components/icon.component";
ApiActionDirective,
AutofocusDirective,
BoxRowDirective,
CalloutComponent,
DeprecatedCalloutComponent,
CopyTextDirective,
CreditCardNumberPipe,
EllipsisPipe,
@@ -125,7 +127,7 @@ import { IconComponent } from "./vault/components/icon.component";
AutofocusDirective,
ToastModule,
BoxRowDirective,
CalloutComponent,
DeprecatedCalloutComponent,
CopyTextDirective,
CreditCardNumberPipe,
EllipsisPipe,

View File

@@ -0,0 +1,83 @@
import { Injector, WritableSignal } from "@angular/core";
import type { FormGroup } from "@angular/forms";
import type { Jsonify, JsonValue } from "type-fest";
type Deserializer<T> = {
/**
* A function to use to safely convert your type from json to your expected type.
*
* @param jsonValue The JSON object representation of your state.
* @returns The fully typed version of your state.
*/
readonly deserializer?: (jsonValue: Jsonify<T>) => T;
};
type BaseCacheOptions<T> = {
/** A unique key for saving the cached value to state */
key: string;
/** An optional injector. Required if the method is called outside of an injection context. */
injector?: Injector;
} & (T extends JsonValue ? Deserializer<T> : Required<Deserializer<T>>);
export type SignalCacheOptions<T> = BaseCacheOptions<T> & {
/** The initial value for the signal. */
initialValue: T;
};
/** Extract the value type from a FormGroup */
type FormValue<TFormGroup extends FormGroup> = TFormGroup["value"];
export type FormCacheOptions<TFormGroup extends FormGroup> = BaseCacheOptions<
FormValue<TFormGroup>
> & {
control: TFormGroup;
};
/**
* Cache for temporary component state
*
* #### Implementations
* - browser extension popup: used to persist UI between popup open and close
* - all other clients: noop
*/
export abstract class ViewCacheService {
/**
* Create a signal from a previously cached value. Whenever the signal is updated, the new value is saved to the cache.
*
* Non browser extension implementations are noop and return a normal signal.
*
* @returns the created signal
*
* @example
* ```ts
* const mySignal = this.viewCacheService.signal({
* key: "popup-search-text"
* initialValue: ""
* });
* ```
*/
abstract signal<T>(options: SignalCacheOptions<T>): WritableSignal<T>;
/**
* - Initialize a form from a cached value
* - Save form value to cache when it changes
* - The form is marked dirty if the restored value is not `undefined`.
*
* Non browser extension implementations are noop and return the original form group.
*
* @example
* ```ts
* this.loginDetailsForm = this.viewCacheService.formGroup({
* key: "vault-login-details-form",
* control: this.formBuilder.group({
* username: [""],
* email: [""],
* })
* });
* ```
**/
abstract formGroup<TFormGroup extends FormGroup>(
options: FormCacheOptions<TFormGroup>,
): TFormGroup;
}

View File

@@ -0,0 +1,33 @@
import { Injectable, signal, WritableSignal } from "@angular/core";
import type { FormGroup } from "@angular/forms";
import {
FormCacheOptions,
SignalCacheOptions,
ViewCacheService,
} from "../abstractions/view-cache.service";
/**
* The functionality of the {@link ViewCacheService} is only needed in the browser extension popup,
* yet is provided to all clients to make sharing components easier.
*
* Non-extension clients use this noop implementation.
* */
@Injectable({
providedIn: "root",
})
export class NoopViewCacheService implements ViewCacheService {
/**
* Return a normal signal.
*/
signal<T>(options: SignalCacheOptions<T>): WritableSignal<T> {
return signal(options.initialValue);
}
/**
* Return the original form group.
**/
formGroup<TFormGroup extends FormGroup>(options: FormCacheOptions<TFormGroup>): TFormGroup {
return options.control;
}
}

View File

@@ -268,8 +268,10 @@ import {
} from "@bitwarden/vault-export-core";
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
import { ViewCacheService } from "../platform/abstractions/view-cache.service";
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
import { LoggingErrorHandler } from "../platform/services/logging-error-handler";
import { NoopViewCacheService } from "../platform/services/noop-view-cache.service";
import { AngularThemingService } from "../platform/services/theming/angular-theming.service";
import { AbstractThemingService } from "../platform/services/theming/theming.service.abstraction";
import { safeProvider, SafeProvider } from "../platform/utils/safe-provider";
@@ -1290,6 +1292,11 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultRegistrationFinishService,
deps: [CryptoServiceAbstraction, AccountApiServiceAbstraction],
}),
safeProvider({
provide: ViewCacheService,
useExisting: NoopViewCacheService,
deps: [],
}),
];
@NgModule({