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:
@@ -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)) ||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
83
libs/angular/src/platform/abstractions/view-cache.service.ts
Normal file
83
libs/angular/src/platform/abstractions/view-cache.service.ts
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user