mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-4016] Address feedback on [PM-2014] (#6532)
* [PM-4016] feat: use dialog `loading` attribute
* [PM-4016] chore: move constant to service
* [PM-4016] chore: simplify paddings
* [PM-4016] chore: rename to `AuthSettingsModule`
* [PM-4016] fix: move request creation to service
* [PM-4016] feat: simplify module structure
Remove core.module and use `@Injectable({ providedIn: "root" })` instead.
This commit is contained in:
@@ -1,12 +1,11 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { CoreAuthModule } from "./core";
|
import { AuthSettingsModule } from "./settings/settings.module";
|
||||||
import { SettingsModule } from "./settings/settings.module";
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CoreAuthModule, SettingsModule],
|
imports: [AuthSettingsModule],
|
||||||
declarations: [],
|
declarations: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
exports: [SettingsModule],
|
exports: [AuthSettingsModule],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { NgModule, Optional, SkipSelf } from "@angular/core";
|
|
||||||
|
|
||||||
import { WebauthnLoginApiService } from "./services/webauthn-login/webauthn-login-api.service";
|
|
||||||
import { WebauthnLoginService } from "./services/webauthn-login/webauthn-login.service";
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
providers: [WebauthnLoginService, WebauthnLoginApiService],
|
|
||||||
})
|
|
||||||
export class CoreAuthModule {
|
|
||||||
constructor(@Optional() @SkipSelf() parentModule?: CoreAuthModule) {
|
|
||||||
if (parentModule) {
|
|
||||||
throw new Error("CoreAuthModule is already loaded. Import it in AuthModule only");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
export * from "./services";
|
export * from "./services";
|
||||||
export * from "./core.module";
|
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
import { Verification } from "@bitwarden/common/types/verification";
|
|
||||||
|
|
||||||
import { SaveCredentialRequest } from "./request/save-credential.request";
|
import { SaveCredentialRequest } from "./request/save-credential.request";
|
||||||
import { WebauthnLoginCredentialCreateOptionsResponse } from "./response/webauthn-login-credential-create-options.response";
|
import { WebauthnLoginCredentialCreateOptionsResponse } from "./response/webauthn-login-credential-create-options.response";
|
||||||
import { WebauthnLoginCredentialResponse } from "./response/webauthn-login-credential.response";
|
import { WebauthnLoginCredentialResponse } from "./response/webauthn-login-credential.response";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: "root" })
|
||||||
export class WebauthnLoginApiService {
|
export class WebauthnLoginApiService {
|
||||||
constructor(
|
constructor(private apiService: ApiService) {}
|
||||||
private apiService: ApiService,
|
|
||||||
private userVerificationService: UserVerificationService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async getCredentialCreateOptions(
|
async getCredentialCreateOptions(
|
||||||
verification: Verification
|
request: SecretVerificationRequest
|
||||||
): Promise<WebauthnLoginCredentialCreateOptionsResponse> {
|
): Promise<WebauthnLoginCredentialCreateOptionsResponse> {
|
||||||
const request = await this.userVerificationService.buildRequest(verification);
|
|
||||||
const response = await this.apiService.send("POST", "/webauthn/options", request, true, true);
|
const response = await this.apiService.send("POST", "/webauthn/options", request, true, true);
|
||||||
return new WebauthnLoginCredentialCreateOptionsResponse(response);
|
return new WebauthnLoginCredentialCreateOptionsResponse(response);
|
||||||
}
|
}
|
||||||
@@ -33,8 +28,7 @@ export class WebauthnLoginApiService {
|
|||||||
return this.apiService.send("GET", "/webauthn", null, true, true);
|
return this.apiService.send("GET", "/webauthn", null, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteCredential(credentialId: string, verification: Verification): Promise<void> {
|
async deleteCredential(credentialId: string, request: SecretVerificationRequest): Promise<void> {
|
||||||
const request = await this.userVerificationService.buildRequest(verification);
|
|
||||||
await this.apiService.send("POST", `/webauthn/${credentialId}/delete`, request, true, true);
|
await this.apiService.send("POST", `/webauthn/${credentialId}/delete`, request, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
|
|
||||||
import { CredentialCreateOptionsView } from "../../views/credential-create-options.view";
|
import { CredentialCreateOptionsView } from "../../views/credential-create-options.view";
|
||||||
|
|
||||||
import { WebauthnLoginApiService } from "./webauthn-login-api.service";
|
import { WebauthnLoginApiService } from "./webauthn-login-api.service";
|
||||||
@@ -7,6 +9,7 @@ import { WebauthnLoginService } from "./webauthn-login.service";
|
|||||||
|
|
||||||
describe("WebauthnService", () => {
|
describe("WebauthnService", () => {
|
||||||
let apiService!: MockProxy<WebauthnLoginApiService>;
|
let apiService!: MockProxy<WebauthnLoginApiService>;
|
||||||
|
let userVerificationService!: MockProxy<UserVerificationService>;
|
||||||
let credentials: MockProxy<CredentialsContainer>;
|
let credentials: MockProxy<CredentialsContainer>;
|
||||||
let webauthnService!: WebauthnLoginService;
|
let webauthnService!: WebauthnLoginService;
|
||||||
|
|
||||||
@@ -15,8 +18,9 @@ describe("WebauthnService", () => {
|
|||||||
window.PublicKeyCredential = class {} as any;
|
window.PublicKeyCredential = class {} as any;
|
||||||
window.AuthenticatorAttestationResponse = class {} as any;
|
window.AuthenticatorAttestationResponse = class {} as any;
|
||||||
apiService = mock<WebauthnLoginApiService>();
|
apiService = mock<WebauthnLoginApiService>();
|
||||||
|
userVerificationService = mock<UserVerificationService>();
|
||||||
credentials = mock<CredentialsContainer>();
|
credentials = mock<CredentialsContainer>();
|
||||||
webauthnService = new WebauthnLoginService(apiService, credentials);
|
webauthnService = new WebauthnLoginService(apiService, userVerificationService, credentials);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("createCredential", () => {
|
describe("createCredential", () => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable, Optional } from "@angular/core";
|
import { Injectable, Optional } from "@angular/core";
|
||||||
import { BehaviorSubject, filter, from, map, Observable, shareReplay, switchMap, tap } from "rxjs";
|
import { BehaviorSubject, filter, from, map, Observable, shareReplay, switchMap, tap } from "rxjs";
|
||||||
|
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { Verification } from "@bitwarden/common/types/verification";
|
import { Verification } from "@bitwarden/common/types/verification";
|
||||||
|
|
||||||
@@ -11,8 +12,10 @@ import { SaveCredentialRequest } from "./request/save-credential.request";
|
|||||||
import { WebauthnLoginAttestationResponseRequest } from "./request/webauthn-login-attestation-response.request";
|
import { WebauthnLoginAttestationResponseRequest } from "./request/webauthn-login-attestation-response.request";
|
||||||
import { WebauthnLoginApiService } from "./webauthn-login-api.service";
|
import { WebauthnLoginApiService } from "./webauthn-login-api.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: "root" })
|
||||||
export class WebauthnLoginService {
|
export class WebauthnLoginService {
|
||||||
|
static readonly MaxCredentialCount = 5;
|
||||||
|
|
||||||
private navigatorCredentials: CredentialsContainer;
|
private navigatorCredentials: CredentialsContainer;
|
||||||
private _refresh$ = new BehaviorSubject<void>(undefined);
|
private _refresh$ = new BehaviorSubject<void>(undefined);
|
||||||
private _loading$ = new BehaviorSubject<boolean>(true);
|
private _loading$ = new BehaviorSubject<boolean>(true);
|
||||||
@@ -27,6 +30,7 @@ export class WebauthnLoginService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: WebauthnLoginApiService,
|
private apiService: WebauthnLoginApiService,
|
||||||
|
private userVerificationService: UserVerificationService,
|
||||||
@Optional() navigatorCredentials?: CredentialsContainer,
|
@Optional() navigatorCredentials?: CredentialsContainer,
|
||||||
@Optional() private logService?: LogService
|
@Optional() private logService?: LogService
|
||||||
) {
|
) {
|
||||||
@@ -37,7 +41,8 @@ export class WebauthnLoginService {
|
|||||||
async getCredentialCreateOptions(
|
async getCredentialCreateOptions(
|
||||||
verification: Verification
|
verification: Verification
|
||||||
): Promise<CredentialCreateOptionsView> {
|
): Promise<CredentialCreateOptionsView> {
|
||||||
const response = await this.apiService.getCredentialCreateOptions(verification);
|
const request = await this.userVerificationService.buildRequest(verification);
|
||||||
|
const response = await this.apiService.getCredentialCreateOptions(request);
|
||||||
return new CredentialCreateOptionsView(response.options, response.token);
|
return new CredentialCreateOptionsView(response.options, response.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +100,8 @@ export class WebauthnLoginService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteCredential(credentialId: string, verification: Verification): Promise<void> {
|
async deleteCredential(credentialId: string, verification: Verification): Promise<void> {
|
||||||
await this.apiService.deleteCredential(credentialId, verification);
|
const request = await this.userVerificationService.buildRequest(verification);
|
||||||
|
await this.apiService.deleteCredential(credentialId, request);
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ import { WebauthnLoginSettingsModule } from "./webauthn-login-settings";
|
|||||||
imports: [SharedModule, WebauthnLoginSettingsModule, PasswordCalloutComponent],
|
imports: [SharedModule, WebauthnLoginSettingsModule, PasswordCalloutComponent],
|
||||||
declarations: [ChangePasswordComponent],
|
declarations: [ChangePasswordComponent],
|
||||||
providers: [],
|
providers: [],
|
||||||
exports: [WebauthnLoginSettingsModule, ChangePasswordComponent],
|
exports: [ChangePasswordComponent],
|
||||||
})
|
})
|
||||||
export class SettingsModule {}
|
export class AuthSettingsModule {}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||||
<bit-dialog dialogSize="large">
|
<bit-dialog dialogSize="large" [loading]="loading$ | async">
|
||||||
<span bitDialogTitle
|
<span bitDialogTitle
|
||||||
>{{ "loginWithPasskey" | i18n }}
|
>{{ "loginWithPasskey" | i18n }}
|
||||||
<span class="tw-text-sm tw-normal-case tw-text-muted">{{ "newPasskey" | i18n }}</span>
|
<span class="tw-text-sm tw-normal-case tw-text-muted">{{ "newPasskey" | i18n }}</span>
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export class CreateCredentialDialogComponent implements OnInit {
|
|||||||
protected credentialOptions?: CredentialCreateOptionsView;
|
protected credentialOptions?: CredentialCreateOptionsView;
|
||||||
protected deviceResponse?: PublicKeyCredential;
|
protected deviceResponse?: PublicKeyCredential;
|
||||||
protected hasPasskeys$?: Observable<boolean>;
|
protected hasPasskeys$?: Observable<boolean>;
|
||||||
|
protected loading$ = this.webauthnService.loading$;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||||
<bit-dialog dialogSize="large">
|
<bit-dialog dialogSize="large" [loading]="loading$ | async">
|
||||||
<span bitDialogTitle
|
<span bitDialogTitle
|
||||||
>{{ "removePasskey" | i18n }}
|
>{{ "removePasskey" | i18n }}
|
||||||
<span *ngIf="credential" class="tw-text-sm tw-normal-case tw-text-muted">{{
|
<span *ngIf="credential" class="tw-text-sm tw-normal-case tw-text-muted">{{
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export class DeleteCredentialDialogComponent implements OnInit, OnDestroy {
|
|||||||
masterPassword: ["", [Validators.required]],
|
masterPassword: ["", [Validators.required]],
|
||||||
});
|
});
|
||||||
protected credential?: WebauthnCredentialView;
|
protected credential?: WebauthnCredentialView;
|
||||||
|
protected loading$ = this.webauthnService.loading$;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DIALOG_DATA) private params: DeleteCredentialDialogParams,
|
@Inject(DIALOG_DATA) private params: DeleteCredentialDialogParams,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<table *ngIf="hasCredentials" class="tw-mb-5">
|
<table *ngIf="hasCredentials" class="tw-mb-5">
|
||||||
<tr *ngFor="let credential of credentials">
|
<tr *ngFor="let credential of credentials">
|
||||||
<td class="tw-p-2 tw-pl-0 tw-font-semibold">{{ credential.name }}</td>
|
<td class="tw-p-2 tw-pl-0 tw-font-semibold">{{ credential.name }}</td>
|
||||||
<td class="tw-p-2 tw-pr-0">
|
<td class="tw-p-2 tw-pr-10">
|
||||||
<ng-container *ngIf="credential.prfSupport">
|
<ng-container *ngIf="credential.prfSupport">
|
||||||
<i class="bwi bwi-lock-encrypted"></i>
|
<i class="bwi bwi-lock-encrypted"></i>
|
||||||
{{ "supportsEncryption" | i18n }}
|
{{ "supportsEncryption" | i18n }}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
{{ "encryptionNotSupported" | i18n }}
|
{{ "encryptionNotSupported" | i18n }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="tw-py-2 tw-pl-10 tw-pr-0">
|
<td class="tw-py-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
bitLink
|
bitLink
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { openDeleteCredentialDialogComponent } from "./delete-credential-dialog/
|
|||||||
export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy {
|
export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy {
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
protected readonly MaxCredentialCount = 5;
|
protected readonly MaxCredentialCount = WebauthnLoginService.MaxCredentialCount;
|
||||||
|
|
||||||
protected credentials?: WebauthnCredentialView[];
|
protected credentials?: WebauthnCredentialView[];
|
||||||
protected loading = true;
|
protected loading = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user