mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 05:13:29 +00:00
Auth/PM-16947 - Web - Device Management - Add Manage Auth Requests support (#12809)
* PM-16947 - JsLibServices - register default DefaultLoginApprovalComponentService * PM-16947 - DeviceResponse - add interface for DevicePendingAuthRequest * PM-16947 - Web translations - migrate all LoginApprovalComponent translations from desktop to web * PM-16947 - LoginApprovalComp - (1) Add loading state (2) Refactor to return proper boolean results (3) Don't create race condition by trying to respond to the close event in the dialog and re-sending responses upon approve or deny click * PM-16947 - DeviceManagementComponent - added support for approving and denying auth requests. * PM-16947 - LoginApprovalComp - Add validation error * PM-16947 - LoginApprovalComponent - remove validation service for now. * PM-16947 - Re add validation * PM-16947 - Fix LoginApprovalComponent tests
This commit is contained in:
@@ -1,23 +1,31 @@
|
||||
<bit-dialog>
|
||||
<span bitDialogTitle>{{ "areYouTryingtoLogin" | i18n }}</span>
|
||||
<ng-container bitDialogContent>
|
||||
<h4 class="tw-mb-3">{{ "logInAttemptBy" | i18n: email }}</h4>
|
||||
<div>
|
||||
<b>{{ "fingerprintPhraseHeader" | i18n }}</b>
|
||||
<p class="tw-text-code">{{ fingerprintPhrase }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ "deviceType" | i18n }}</b>
|
||||
<p>{{ authRequestResponse?.requestDeviceType }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ "ipAddress" | i18n }}</b>
|
||||
<p>{{ authRequestResponse?.requestIpAddress }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ "time" | i18n }}</b>
|
||||
<p>{{ requestTimeText }}</p>
|
||||
</div>
|
||||
<ng-container *ngIf="loading">
|
||||
<div class="tw-flex tw-items-center tw-justify-center" *ngIf="loading">
|
||||
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!loading">
|
||||
<h4 class="tw-mb-3">{{ "logInAttemptBy" | i18n: email }}</h4>
|
||||
<div>
|
||||
<b>{{ "fingerprintPhraseHeader" | i18n }}</b>
|
||||
<p class="tw-text-code">{{ fingerprintPhrase }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ "deviceType" | i18n }}</b>
|
||||
<p>{{ authRequestResponse?.requestDeviceType }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ "ipAddress" | i18n }}</b>
|
||||
<p>{{ authRequestResponse?.requestIpAddress }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ "time" | i18n }}</b>
|
||||
<p>{{ requestTimeText }}</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button
|
||||
@@ -25,7 +33,7 @@
|
||||
type="button"
|
||||
buttonType="primary"
|
||||
[bitAction]="approveLogin"
|
||||
[bitDialogClose]="true"
|
||||
[disabled]="loading"
|
||||
>
|
||||
{{ "confirmLogIn" | i18n }}
|
||||
</button>
|
||||
@@ -34,7 +42,7 @@
|
||||
type="button"
|
||||
buttonType="secondary"
|
||||
[bitAction]="denyLogin"
|
||||
[bitDialogClose]="true"
|
||||
[disabled]="loading"
|
||||
>
|
||||
{{ "denyLogIn" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
@@ -29,6 +30,7 @@ describe("LoginApprovalComponent", () => {
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let dialogRef: MockProxy<DialogRef>;
|
||||
let toastService: MockProxy<ToastService>;
|
||||
let validationService: MockProxy<ValidationService>;
|
||||
|
||||
const testNotificationId = "test-notification-id";
|
||||
const testEmail = "test@bitwarden.com";
|
||||
@@ -41,6 +43,7 @@ describe("LoginApprovalComponent", () => {
|
||||
i18nService = mock<I18nService>();
|
||||
dialogRef = mock<DialogRef>();
|
||||
toastService = mock<ToastService>();
|
||||
validationService = mock<ValidationService>();
|
||||
|
||||
accountService.activeAccount$ = of({
|
||||
email: testEmail,
|
||||
@@ -62,6 +65,7 @@ describe("LoginApprovalComponent", () => {
|
||||
{ provide: KeyService, useValue: mock<KeyService>() },
|
||||
{ provide: DialogRef, useValue: dialogRef },
|
||||
{ provide: ToastService, useValue: toastService },
|
||||
{ provide: ValidationService, useValue: validationService },
|
||||
{
|
||||
provide: LoginApprovalComponentServiceAbstraction,
|
||||
useValue: mock<LoginApprovalComponentServiceAbstraction>(),
|
||||
|
||||
@@ -16,6 +16,7 @@ import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
@@ -40,6 +41,8 @@ export interface LoginApprovalDialogParams {
|
||||
imports: [CommonModule, AsyncActionsModule, ButtonModule, DialogModule, JslibModule],
|
||||
})
|
||||
export class LoginApprovalComponent implements OnInit, OnDestroy {
|
||||
loading = true;
|
||||
|
||||
notificationId: string;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
@@ -62,25 +65,25 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
||||
private dialogRef: DialogRef,
|
||||
private toastService: ToastService,
|
||||
private loginApprovalComponentService: LoginApprovalComponentService,
|
||||
private validationService: ValidationService,
|
||||
) {
|
||||
this.notificationId = params.notificationId;
|
||||
}
|
||||
|
||||
async ngOnDestroy(): Promise<void> {
|
||||
clearInterval(this.interval);
|
||||
const closedWithButton = await firstValueFrom(this.dialogRef.closed);
|
||||
if (!closedWithButton) {
|
||||
// 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.retrieveAuthRequestAndRespond(false);
|
||||
}
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (this.notificationId != null) {
|
||||
this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId);
|
||||
try {
|
||||
this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId);
|
||||
} catch (error) {
|
||||
this.validationService.showError(error);
|
||||
}
|
||||
|
||||
const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey);
|
||||
this.email = await await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
|
||||
@@ -96,6 +99,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
||||
}, RequestTimeUpdate);
|
||||
|
||||
this.loginApprovalComponentService.showLoginRequestedAlertIfWindowNotVisible(this.email);
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +136,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
this.showResultToast(loginResponse);
|
||||
}
|
||||
|
||||
this.dialogRef.close(approve);
|
||||
}
|
||||
|
||||
showResultToast(loginResponse: AuthRequestResponse) {
|
||||
|
||||
Reference in New Issue
Block a user