mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +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:
@@ -48,17 +48,31 @@
|
||||
<i [class]="getDeviceIcon(row.type)" class="bwi-lg" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div>
|
||||
{{ row.displayName }}
|
||||
<span *ngIf="row.trusted" class="tw-text-sm tw-text-muted tw-block">
|
||||
{{ "trusted" | i18n }}
|
||||
</span>
|
||||
<ng-container *ngIf="row.hasPendingAuthRequest">
|
||||
<a bitLink href="#" appStopClick (click)="managePendingAuthRequest(row)">
|
||||
{{ row.displayName }}
|
||||
</a>
|
||||
|
||||
<span class="tw-text-sm tw-text-muted tw-block">
|
||||
{{ "needsApproval" | i18n }}
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!row.hasPendingAuthRequest">
|
||||
{{ row.displayName }}
|
||||
<span
|
||||
*ngIf="row.trusted && !row.hasPendingAuthRequest"
|
||||
class="tw-text-sm tw-text-muted tw-block"
|
||||
>
|
||||
{{ "trusted" | i18n }}
|
||||
</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<span *ngIf="isCurrentDevice(row)" bitBadge variant="primary">{{
|
||||
"currentSession" | i18n
|
||||
}}</span>
|
||||
<span *ngIf="hasPendingAuthRequest(row)" bitBadge variant="warning">{{
|
||||
<span *ngIf="row.hasPendingAuthRequest" bitBadge variant="warning">{{
|
||||
"requestPending" | i18n
|
||||
}}</span>
|
||||
</td>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { switchMap } from "rxjs/operators";
|
||||
import { combineLatest, firstValueFrom } from "rxjs";
|
||||
|
||||
import { LoginApprovalComponent } from "@bitwarden/auth/angular";
|
||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||
import {
|
||||
DevicePendingAuthRequest,
|
||||
DeviceResponse,
|
||||
} from "@bitwarden/common/auth/abstractions/devices/responses/device.response";
|
||||
import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view";
|
||||
import { DeviceType, DeviceTypeMetadata } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -26,7 +30,8 @@ interface DeviceTableData {
|
||||
loginStatus: string;
|
||||
firstLogin: Date;
|
||||
trusted: boolean;
|
||||
devicePendingAuthRequest: object | null;
|
||||
devicePendingAuthRequest: DevicePendingAuthRequest | null;
|
||||
hasPendingAuthRequest: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,28 +57,25 @@ export class DeviceManagementComponent {
|
||||
private toastService: ToastService,
|
||||
private validationService: ValidationService,
|
||||
) {
|
||||
this.devicesService
|
||||
.getCurrentDevice$()
|
||||
.pipe(
|
||||
takeUntilDestroyed(),
|
||||
switchMap((currentDevice) => {
|
||||
this.currentDevice = new DeviceView(currentDevice);
|
||||
return this.devicesService.getDevices$();
|
||||
}),
|
||||
)
|
||||
combineLatest([this.devicesService.getCurrentDevice$(), this.devicesService.getDevices$()])
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe({
|
||||
next: (devices) => {
|
||||
this.dataSource.data = devices.map((device) => {
|
||||
next: ([currentDevice, devices]: [DeviceResponse, Array<DeviceView>]) => {
|
||||
this.currentDevice = new DeviceView(currentDevice);
|
||||
|
||||
this.dataSource.data = devices.map((device: DeviceView): DeviceTableData => {
|
||||
return {
|
||||
id: device.id,
|
||||
type: device.type,
|
||||
displayName: this.getHumanReadableDeviceType(device.type),
|
||||
loginStatus: this.getLoginStatus(device),
|
||||
devicePendingAuthRequest: device.response.devicePendingAuthRequest,
|
||||
firstLogin: new Date(device.creationDate),
|
||||
trusted: device.response.isTrusted,
|
||||
devicePendingAuthRequest: device.response.devicePendingAuthRequest,
|
||||
hasPendingAuthRequest: this.hasPendingAuthRequest(device.response),
|
||||
};
|
||||
});
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
error: () => {
|
||||
@@ -176,15 +178,36 @@ export class DeviceManagementComponent {
|
||||
|
||||
/**
|
||||
* Check if a device has a pending auth request
|
||||
* @param device - The device
|
||||
* @param device - The device response
|
||||
* @returns True if the device has a pending auth request, false otherwise
|
||||
*/
|
||||
protected hasPendingAuthRequest(device: DeviceTableData): boolean {
|
||||
private hasPendingAuthRequest(device: DeviceResponse): boolean {
|
||||
return (
|
||||
device.devicePendingAuthRequest !== undefined && device.devicePendingAuthRequest !== null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a dialog to approve or deny a pending auth request for a device
|
||||
*/
|
||||
async managePendingAuthRequest(device: DeviceTableData) {
|
||||
if (device.devicePendingAuthRequest === undefined || device.devicePendingAuthRequest === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dialogRef = LoginApprovalComponent.open(this.dialogService, {
|
||||
notificationId: device.devicePendingAuthRequest.id,
|
||||
});
|
||||
|
||||
const result = await firstValueFrom(dialogRef.closed);
|
||||
|
||||
if (result !== undefined && typeof result === "boolean") {
|
||||
// auth request approved or denied so reset
|
||||
device.devicePendingAuthRequest = null;
|
||||
device.hasPendingAuthRequest = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a device
|
||||
* @param device - The device
|
||||
|
||||
@@ -3813,6 +3813,67 @@
|
||||
"trusted": {
|
||||
"message": "Trusted"
|
||||
},
|
||||
"needsApproval": {
|
||||
"message": "Needs approval"
|
||||
},
|
||||
"areYouTryingtoLogin": {
|
||||
"message": "Are you trying to log in?"
|
||||
},
|
||||
"logInAttemptBy": {
|
||||
"message": "Login attempt by $EMAIL$",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"content": "$1",
|
||||
"example": "name@example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deviceType": {
|
||||
"message": "Device Type"
|
||||
},
|
||||
"ipAddress": {
|
||||
"message": "IP Address"
|
||||
},
|
||||
"confirmLogIn": {
|
||||
"message": "Confirm login"
|
||||
},
|
||||
"denyLogIn": {
|
||||
"message": "Deny login"
|
||||
},
|
||||
"thisRequestIsNoLongerValid": {
|
||||
"message": "This request is no longer valid."
|
||||
},
|
||||
"logInConfirmedForEmailOnDevice": {
|
||||
"message": "Login confirmed for $EMAIL$ on $DEVICE$",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"content": "$1",
|
||||
"example": "name@example.com"
|
||||
},
|
||||
"device": {
|
||||
"content": "$2",
|
||||
"example": "iOS"
|
||||
}
|
||||
}
|
||||
},
|
||||
"youDeniedALogInAttemptFromAnotherDevice": {
|
||||
"message": "You denied a login attempt from another device. If this really was you, try to log in with the device again."
|
||||
},
|
||||
"loginRequestHasAlreadyExpired": {
|
||||
"message": "Login request has already expired."
|
||||
},
|
||||
"justNow": {
|
||||
"message": "Just now"
|
||||
},
|
||||
"requestedXMinutesAgo": {
|
||||
"message": "Requested $MINUTES$ minutes ago",
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"creatingAccountOn": {
|
||||
"message": "Creating account on"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user