mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
[PM-5800] Remove passwordless-login feature flag (#7626)
* Removed passwordless-login feature flag * Removed conditional on login component. * Added back reference accidentally deleted. * Fixed initialization of the service in tests. * Removed unused private variable. * Updated DI to remove configService * Undid changes to workspace file. * Undid all changes to workspace file * Undid merge changes to collection dialog * Linting
This commit is contained in:
@@ -51,10 +51,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="tw-mb-3 tw-flex tw-flex-col tw-items-center tw-justify-center">
|
||||||
class="tw-mb-3 tw-flex tw-flex-col tw-items-center tw-justify-center"
|
|
||||||
*ngIf="showWebauthnLogin$ | async"
|
|
||||||
>
|
|
||||||
<p class="tw-mb-3">{{ "or" | i18n }}</p>
|
<p class="tw-mb-3">{{ "or" | i18n }}</p>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -126,6 +126,4 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<app-webauthn-login-settings
|
<app-webauthn-login-settings></app-webauthn-login-settings>
|
||||||
*ngIf="showWebauthnLoginSettings$ | async"
|
|
||||||
></app-webauthn-login-settings>
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
import { Observable } from "rxjs";
|
|
||||||
|
|
||||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
|
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
@@ -8,7 +7,6 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
|||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -35,8 +33,6 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
|||||||
checkForBreaches = true;
|
checkForBreaches = true;
|
||||||
characterMinimumMessage = "";
|
characterMinimumMessage = "";
|
||||||
|
|
||||||
protected showWebauthnLoginSettings$: Observable<boolean>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
cryptoService: CryptoService,
|
cryptoService: CryptoService,
|
||||||
@@ -68,10 +64,6 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.showWebauthnLoginSettings$ = this.configService.getFeatureFlag$(
|
|
||||||
FeatureFlag.PasswordlessLogin,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!(await this.userVerificationService.hasMasterPassword())) {
|
if (!(await this.userVerificationService.hasMasterPassword())) {
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// 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
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Directive, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
import { Directive, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { Observable, Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import { take, takeUntil } from "rxjs/operators";
|
import { take, takeUntil } from "rxjs/operators";
|
||||||
|
|
||||||
import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common";
|
import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common";
|
||||||
@@ -54,7 +54,6 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
|
|||||||
protected twoFactorRoute = "2fa";
|
protected twoFactorRoute = "2fa";
|
||||||
protected successRoute = "vault";
|
protected successRoute = "vault";
|
||||||
protected forcePasswordResetRoute = "update-temp-password";
|
protected forcePasswordResetRoute = "update-temp-password";
|
||||||
protected showWebauthnLogin$: Observable<boolean>;
|
|
||||||
|
|
||||||
protected destroy$ = new Subject<void>();
|
protected destroy$ = new Subject<void>();
|
||||||
|
|
||||||
@@ -90,8 +89,6 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.showWebauthnLogin$ = this.webAuthnLoginService.enabled$;
|
|
||||||
|
|
||||||
this.route?.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
|
this.route?.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
|
||||||
if (!params) {
|
if (!params) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -864,7 +864,6 @@ import { ModalService } from "./modal.service";
|
|||||||
deps: [
|
deps: [
|
||||||
WebAuthnLoginApiServiceAbstraction,
|
WebAuthnLoginApiServiceAbstraction,
|
||||||
LoginStrategyServiceAbstraction,
|
LoginStrategyServiceAbstraction,
|
||||||
ConfigServiceAbstraction,
|
|
||||||
WebAuthnLoginPrfCryptoServiceAbstraction,
|
WebAuthnLoginPrfCryptoServiceAbstraction,
|
||||||
WINDOW,
|
WINDOW,
|
||||||
LogService,
|
LogService,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Observable } from "rxjs";
|
|
||||||
|
|
||||||
import { AuthResult } from "../../models/domain/auth-result";
|
import { AuthResult } from "../../models/domain/auth-result";
|
||||||
import { WebAuthnLoginCredentialAssertionOptionsView } from "../../models/view/webauthn-login/webauthn-login-credential-assertion-options.view";
|
import { WebAuthnLoginCredentialAssertionOptionsView } from "../../models/view/webauthn-login/webauthn-login-credential-assertion-options.view";
|
||||||
import { WebAuthnLoginCredentialAssertionView } from "../../models/view/webauthn-login/webauthn-login-credential-assertion.view";
|
import { WebAuthnLoginCredentialAssertionView } from "../../models/view/webauthn-login/webauthn-login-credential-assertion.view";
|
||||||
@@ -8,11 +6,6 @@ import { WebAuthnLoginCredentialAssertionView } from "../../models/view/webauthn
|
|||||||
* Service for logging in with WebAuthnLogin credentials.
|
* Service for logging in with WebAuthnLogin credentials.
|
||||||
*/
|
*/
|
||||||
export abstract class WebAuthnLoginServiceAbstraction {
|
export abstract class WebAuthnLoginServiceAbstraction {
|
||||||
/**
|
|
||||||
* An Observable that emits a boolean indicating whether the WebAuthn login feature is enabled.
|
|
||||||
*/
|
|
||||||
readonly enabled$: Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the credential assertion options needed for initiating the WebAuthn
|
* Gets the credential assertion options needed for initiating the WebAuthn
|
||||||
* authentication process. It should provide the challenge and other data
|
* authentication process. It should provide the challenge and other data
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
import { firstValueFrom, of } from "rxjs";
|
|
||||||
|
|
||||||
import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common";
|
import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common";
|
||||||
|
|
||||||
import { ConfigServiceAbstraction } from "../../../platform/abstractions/config/config.service.abstraction";
|
|
||||||
import { LogService } from "../../../platform/abstractions/log.service";
|
import { LogService } from "../../../platform/abstractions/log.service";
|
||||||
import { Utils } from "../../../platform/misc/utils";
|
import { Utils } from "../../../platform/misc/utils";
|
||||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||||
@@ -23,7 +21,6 @@ describe("WebAuthnLoginService", () => {
|
|||||||
|
|
||||||
const webAuthnLoginApiService = mock<WebAuthnLoginApiServiceAbstraction>();
|
const webAuthnLoginApiService = mock<WebAuthnLoginApiServiceAbstraction>();
|
||||||
const loginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
const loginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
||||||
const configService = mock<ConfigServiceAbstraction>();
|
|
||||||
const webAuthnLoginPrfCryptoService = mock<WebAuthnLoginPrfCryptoServiceAbstraction>();
|
const webAuthnLoginPrfCryptoService = mock<WebAuthnLoginPrfCryptoServiceAbstraction>();
|
||||||
const navigatorCredentials = mock<CredentialsContainer>();
|
const navigatorCredentials = mock<CredentialsContainer>();
|
||||||
const logService = mock<LogService>();
|
const logService = mock<LogService>();
|
||||||
@@ -71,12 +68,10 @@ describe("WebAuthnLoginService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createWebAuthnLoginService(config: { featureEnabled: boolean }): WebAuthnLoginService {
|
function createWebAuthnLoginService(): WebAuthnLoginService {
|
||||||
configService.getFeatureFlag$.mockReturnValue(of(config.featureEnabled));
|
|
||||||
return new WebAuthnLoginService(
|
return new WebAuthnLoginService(
|
||||||
webAuthnLoginApiService,
|
webAuthnLoginApiService,
|
||||||
loginStrategyService,
|
loginStrategyService,
|
||||||
configService,
|
|
||||||
webAuthnLoginPrfCryptoService,
|
webAuthnLoginPrfCryptoService,
|
||||||
window,
|
window,
|
||||||
logService,
|
logService,
|
||||||
@@ -84,34 +79,14 @@ describe("WebAuthnLoginService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
it("instantiates", () => {
|
it("instantiates", () => {
|
||||||
webAuthnLoginService = createWebAuthnLoginService({ featureEnabled: true });
|
webAuthnLoginService = createWebAuthnLoginService();
|
||||||
expect(webAuthnLoginService).not.toBeFalsy();
|
expect(webAuthnLoginService).not.toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("enabled$", () => {
|
|
||||||
it("should emit true when feature flag for PasswordlessLogin is enabled", async () => {
|
|
||||||
// Arrange
|
|
||||||
const webAuthnLoginService = createWebAuthnLoginService({ featureEnabled: true });
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
const result = await firstValueFrom(webAuthnLoginService.enabled$);
|
|
||||||
expect(result).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should emit false when feature flag for PasswordlessLogin is disabled", async () => {
|
|
||||||
// Arrange
|
|
||||||
const webAuthnLoginService = createWebAuthnLoginService({ featureEnabled: false });
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
const result = await firstValueFrom(webAuthnLoginService.enabled$);
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getCredentialAssertionOptions()", () => {
|
describe("getCredentialAssertionOptions()", () => {
|
||||||
it("webAuthnLoginService returns WebAuthnLoginCredentialAssertionOptionsView when getCredentialAssertionOptions is called with the feature enabled", async () => {
|
it("webAuthnLoginService returns WebAuthnLoginCredentialAssertionOptionsView when getCredentialAssertionOptions is called with the feature enabled", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const webAuthnLoginService = createWebAuthnLoginService({ featureEnabled: true });
|
const webAuthnLoginService = createWebAuthnLoginService();
|
||||||
|
|
||||||
const challenge = "6CG3jqMCVASJVXySMi9KWw";
|
const challenge = "6CG3jqMCVASJVXySMi9KWw";
|
||||||
const token = "BWWebAuthnLoginAssertionOptions_CfDJ_2KBN892w";
|
const token = "BWWebAuthnLoginAssertionOptions_CfDJ_2KBN892w";
|
||||||
@@ -154,7 +129,7 @@ describe("WebAuthnLoginService", () => {
|
|||||||
describe("assertCredential(...)", () => {
|
describe("assertCredential(...)", () => {
|
||||||
it("should assert the credential and return WebAuthnLoginAssertionView on success", async () => {
|
it("should assert the credential and return WebAuthnLoginAssertionView on success", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const webAuthnLoginService = createWebAuthnLoginService({ featureEnabled: true });
|
const webAuthnLoginService = createWebAuthnLoginService();
|
||||||
const credentialAssertionOptions = buildCredentialAssertionOptions();
|
const credentialAssertionOptions = buildCredentialAssertionOptions();
|
||||||
|
|
||||||
// Mock webAuthnUtils functions
|
// Mock webAuthnUtils functions
|
||||||
@@ -222,7 +197,7 @@ describe("WebAuthnLoginService", () => {
|
|||||||
|
|
||||||
it("should return undefined on non-PublicKeyCredential browser response", async () => {
|
it("should return undefined on non-PublicKeyCredential browser response", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const webAuthnLoginService = createWebAuthnLoginService({ featureEnabled: true });
|
const webAuthnLoginService = createWebAuthnLoginService();
|
||||||
const credentialAssertionOptions = buildCredentialAssertionOptions();
|
const credentialAssertionOptions = buildCredentialAssertionOptions();
|
||||||
|
|
||||||
// Mock the navigatorCredentials.get to return null
|
// Mock the navigatorCredentials.get to return null
|
||||||
@@ -237,7 +212,7 @@ describe("WebAuthnLoginService", () => {
|
|||||||
|
|
||||||
it("should log an error and return undefined when navigatorCredentials.get throws an error", async () => {
|
it("should log an error and return undefined when navigatorCredentials.get throws an error", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const webAuthnLoginService = createWebAuthnLoginService({ featureEnabled: true });
|
const webAuthnLoginService = createWebAuthnLoginService();
|
||||||
const credentialAssertionOptions = buildCredentialAssertionOptions();
|
const credentialAssertionOptions = buildCredentialAssertionOptions();
|
||||||
|
|
||||||
// Mock navigatorCredentials.get to throw an error
|
// Mock navigatorCredentials.get to throw an error
|
||||||
@@ -269,7 +244,7 @@ describe("WebAuthnLoginService", () => {
|
|||||||
|
|
||||||
it("should accept an assertion with a signed challenge and use it to try and login", async () => {
|
it("should accept an assertion with a signed challenge and use it to try and login", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const webAuthnLoginService = createWebAuthnLoginService({ featureEnabled: true });
|
const webAuthnLoginService = createWebAuthnLoginService();
|
||||||
const assertion = buildWebAuthnLoginCredentialAssertionView();
|
const assertion = buildWebAuthnLoginCredentialAssertionView();
|
||||||
const mockAuthResult: AuthResult = new AuthResult();
|
const mockAuthResult: AuthResult = new AuthResult();
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { Observable } from "rxjs";
|
|
||||||
|
|
||||||
import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common";
|
import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common";
|
||||||
|
|
||||||
import { FeatureFlag } from "../../../enums/feature-flag.enum";
|
|
||||||
import { ConfigServiceAbstraction } from "../../../platform/abstractions/config/config.service.abstraction";
|
|
||||||
import { LogService } from "../../../platform/abstractions/log.service";
|
import { LogService } from "../../../platform/abstractions/log.service";
|
||||||
import { PrfKey } from "../../../types/key";
|
import { PrfKey } from "../../../types/key";
|
||||||
import { WebAuthnLoginApiServiceAbstraction } from "../../abstractions/webauthn/webauthn-login-api.service.abstraction";
|
import { WebAuthnLoginApiServiceAbstraction } from "../../abstractions/webauthn/webauthn-login-api.service.abstraction";
|
||||||
@@ -16,19 +12,15 @@ import { WebAuthnLoginCredentialAssertionView } from "../../models/view/webauthn
|
|||||||
import { WebAuthnLoginAssertionResponseRequest } from "./request/webauthn-login-assertion-response.request";
|
import { WebAuthnLoginAssertionResponseRequest } from "./request/webauthn-login-assertion-response.request";
|
||||||
|
|
||||||
export class WebAuthnLoginService implements WebAuthnLoginServiceAbstraction {
|
export class WebAuthnLoginService implements WebAuthnLoginServiceAbstraction {
|
||||||
readonly enabled$: Observable<boolean>;
|
|
||||||
|
|
||||||
private navigatorCredentials: CredentialsContainer;
|
private navigatorCredentials: CredentialsContainer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private webAuthnLoginApiService: WebAuthnLoginApiServiceAbstraction,
|
private webAuthnLoginApiService: WebAuthnLoginApiServiceAbstraction,
|
||||||
private loginStrategyService: LoginStrategyServiceAbstraction,
|
private loginStrategyService: LoginStrategyServiceAbstraction,
|
||||||
private configService: ConfigServiceAbstraction,
|
|
||||||
private webAuthnLoginPrfCryptoService: WebAuthnLoginPrfCryptoServiceAbstraction,
|
private webAuthnLoginPrfCryptoService: WebAuthnLoginPrfCryptoServiceAbstraction,
|
||||||
private window: Window,
|
private window: Window,
|
||||||
private logService?: LogService,
|
private logService?: LogService,
|
||||||
) {
|
) {
|
||||||
this.enabled$ = this.configService.getFeatureFlag$(FeatureFlag.PasswordlessLogin, false);
|
|
||||||
this.navigatorCredentials = this.window.navigator.credentials;
|
this.navigatorCredentials = this.window.navigator.credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export enum FeatureFlag {
|
export enum FeatureFlag {
|
||||||
PasswordlessLogin = "passwordless-login",
|
|
||||||
BrowserFilelessImport = "browser-fileless-import",
|
BrowserFilelessImport = "browser-fileless-import",
|
||||||
ItemShare = "item-share",
|
ItemShare = "item-share",
|
||||||
FlexibleCollectionsV1 = "flexible-collections-v-1", // v-1 is intentional
|
FlexibleCollectionsV1 = "flexible-collections-v-1", // v-1 is intentional
|
||||||
|
|||||||
Reference in New Issue
Block a user