diff --git a/libs/angular/src/auth/device-management/device-management-item-group.component.html b/libs/angular/src/auth/device-management/device-management-item-group.component.html index b47408059a2..b6a3ea2d8f8 100644 --- a/libs/angular/src/auth/device-management/device-management-item-group.component.html +++ b/libs/angular/src/auth/device-management/device-management-item-group.component.html @@ -6,8 +6,7 @@ bit-item-content type="button" [attr.tabindex]="device.pendingAuthRequest != null ? 0 : null" - (click)="approveOrDenyAuthRequest(device.pendingAuthRequest)" - (keydown.enter)="approveOrDenyAuthRequest(device.pendingAuthRequest)" + (click)="answerAuthRequest(device.pendingAuthRequest)" > {{ device.displayName }} @@ -21,7 +20,7 @@ - {{ "needsApproval" | i18n }} +
{{ "firstLogin" | i18n }}: {{ device.firstLogin | date: "medium" }} diff --git a/libs/angular/src/auth/device-management/device-management-item-group.component.ts b/libs/angular/src/auth/device-management/device-management-item-group.component.ts index 864712ceb78..71e343e734f 100644 --- a/libs/angular/src/auth/device-management/device-management-item-group.component.ts +++ b/libs/angular/src/auth/device-management/device-management-item-group.component.ts @@ -1,15 +1,11 @@ import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; import { DevicePendingAuthRequest } from "@bitwarden/common/auth/abstractions/devices/responses/device.response"; -import { BadgeModule, DialogService, ItemModule } from "@bitwarden/components"; +import { BadgeModule, ItemModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; -import { LoginApprovalDialogComponent } from "../login-approval/login-approval-dialog.component"; - import { DeviceDisplayData } from "./device-management.component"; -import { clearAuthRequestAndResortDevices } from "./resort-devices.helper"; /** Displays user devices in an item list view */ @Component({ @@ -20,24 +16,12 @@ import { clearAuthRequestAndResortDevices } from "./resort-devices.helper"; }) export class DeviceManagementItemGroupComponent { @Input() devices: DeviceDisplayData[] = []; + @Output() onAuthRequestAnswered = new EventEmitter(); - constructor(private dialogService: DialogService) {} - - protected async approveOrDenyAuthRequest(pendingAuthRequest: DevicePendingAuthRequest | null) { + protected answerAuthRequest(pendingAuthRequest: DevicePendingAuthRequest | null) { if (pendingAuthRequest == null) { return; } - - const loginApprovalDialog = LoginApprovalDialogComponent.open(this.dialogService, { - notificationId: pendingAuthRequest.id, - }); - - const result = await firstValueFrom(loginApprovalDialog.closed); - - if (result !== undefined && typeof result === "boolean") { - // Auth request was approved or denied, so clear the - // pending auth request and re-sort the device array - this.devices = clearAuthRequestAndResortDevices(this.devices, pendingAuthRequest); - } + this.onAuthRequestAnswered.emit(pendingAuthRequest); } } diff --git a/libs/angular/src/auth/device-management/device-management-table.component.html b/libs/angular/src/auth/device-management/device-management-table.component.html index febb0a96a4e..72187b2a2fc 100644 --- a/libs/angular/src/auth/device-management/device-management-table.component.html +++ b/libs/angular/src/auth/device-management/device-management-table.component.html @@ -1,4 +1,4 @@ - + @@ -17,24 +16,17 @@ - +
@if (device.pendingAuthRequest) { - + {{ device.displayName }} -
- {{ "needsApproval" | i18n }} -
+
} @else { {{ device.displayName }}
diff --git a/libs/angular/src/auth/device-management/device-management-table.component.ts b/libs/angular/src/auth/device-management/device-management-table.component.ts index c3c835f05ed..d663e28b9e4 100644 --- a/libs/angular/src/auth/device-management/device-management-table.component.ts +++ b/libs/angular/src/auth/device-management/device-management-table.component.ts @@ -1,6 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, Input, OnChanges, SimpleChanges } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DevicePendingAuthRequest } from "@bitwarden/common/auth/abstractions/devices/responses/device.response"; @@ -8,16 +7,12 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { BadgeModule, ButtonModule, - DialogService, LinkModule, TableDataSource, TableModule, } from "@bitwarden/components"; -import { LoginApprovalDialogComponent } from "../login-approval/login-approval-dialog.component"; - import { DeviceDisplayData } from "./device-management.component"; -import { clearAuthRequestAndResortDevices } from "./resort-devices.helper"; /** Displays user devices in a sortable table view */ @Component({ @@ -28,6 +23,8 @@ import { clearAuthRequestAndResortDevices } from "./resort-devices.helper"; }) export class DeviceManagementTableComponent implements OnChanges { @Input() devices: DeviceDisplayData[] = []; + @Output() onAuthRequestAnswered = new EventEmitter(); + protected tableDataSource = new TableDataSource(); protected readonly columnConfig = [ @@ -51,10 +48,7 @@ export class DeviceManagementTableComponent implements OnChanges { }, ]; - constructor( - private i18nService: I18nService, - private dialogService: DialogService, - ) {} + constructor(private i18nService: I18nService) {} ngOnChanges(changes: SimpleChanges): void { if (changes.devices) { @@ -62,24 +56,10 @@ export class DeviceManagementTableComponent implements OnChanges { } } - protected async approveOrDenyAuthRequest(pendingAuthRequest: DevicePendingAuthRequest | null) { + protected answerAuthRequest(pendingAuthRequest: DevicePendingAuthRequest | null) { if (pendingAuthRequest == null) { return; } - - const loginApprovalDialog = LoginApprovalDialogComponent.open(this.dialogService, { - notificationId: pendingAuthRequest.id, - }); - - const result = await firstValueFrom(loginApprovalDialog.closed); - - if (result !== undefined && typeof result === "boolean") { - // Auth request was approved or denied, so clear the - // pending auth request and re-sort the device array - this.tableDataSource.data = clearAuthRequestAndResortDevices( - this.devices, - pendingAuthRequest, - ); - } + this.onAuthRequestAnswered.emit(pendingAuthRequest); } } diff --git a/libs/angular/src/auth/device-management/device-management.component.html b/libs/angular/src/auth/device-management/device-management.component.html index 6ee50a32e8e..2a91c2daae2 100644 --- a/libs/angular/src/auth/device-management/device-management.component.html +++ b/libs/angular/src/auth/device-management/device-management.component.html @@ -30,11 +30,13 @@ } diff --git a/libs/angular/src/auth/device-management/device-management.component.ts b/libs/angular/src/auth/device-management/device-management.component.ts index dc7700a9410..3ab9b2146c5 100644 --- a/libs/angular/src/auth/device-management/device-management.component.ts +++ b/libs/angular/src/auth/device-management/device-management.component.ts @@ -16,14 +16,18 @@ import { DeviceType, DeviceTypeMetadata } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { MessageListener } from "@bitwarden/common/platform/messaging"; -import { ButtonModule, PopoverModule } from "@bitwarden/components"; +import { ButtonModule, DialogService, PopoverModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; +import { LoginApprovalDialogComponent } from "../login-approval"; + import { DeviceManagementComponentServiceAbstraction } from "./device-management-component.service.abstraction"; import { DeviceManagementItemGroupComponent } from "./device-management-item-group.component"; import { DeviceManagementTableComponent } from "./device-management-table.component"; +import { clearAuthRequestAndResortDevices, resortDevices } from "./resort-devices.helper"; export interface DeviceDisplayData { + creationDate: string; displayName: string; firstLogin: Date; icon: string; @@ -66,6 +70,7 @@ export class DeviceManagementComponent implements OnInit { private destroyRef: DestroyRef, private deviceManagementComponentService: DeviceManagementComponentServiceAbstraction, private devicesService: DevicesServiceAbstraction, + private dialogService: DialogService, private i18nService: I18nService, private messageListener: MessageListener, private validationService: ValidationService, @@ -130,6 +135,7 @@ export class DeviceManagementComponent implements OnInit { } return { + creationDate: device.creationDate, displayName: this.devicesService.getReadableDeviceTypeName(device.type), firstLogin: device.creationDate ? new Date(device.creationDate) : new Date(), icon: this.getDeviceIcon(device.type), @@ -141,7 +147,8 @@ export class DeviceManagementComponent implements OnInit { pendingAuthRequest: device.response?.devicePendingAuthRequest ?? null, }; }) - .filter((device) => device !== null); + .filter((device) => device !== null) + .sort(resortDevices); } private async upsertDeviceWithPendingAuthRequest(authRequestId: string) { @@ -151,6 +158,7 @@ export class DeviceManagementComponent implements OnInit { } const upsertDevice: DeviceDisplayData = { + creationDate: "", displayName: this.devicesService.getReadableDeviceTypeName( authRequestResponse.requestDeviceTypeValue, ), @@ -174,8 +182,9 @@ export class DeviceManagementComponent implements OnInit { ); if (existingDevice?.id && existingDevice.creationDate) { - upsertDevice.id = existingDevice.id; + upsertDevice.creationDate = existingDevice.creationDate; upsertDevice.firstLogin = new Date(existingDevice.creationDate); + upsertDevice.id = existingDevice.id; } } @@ -186,10 +195,10 @@ export class DeviceManagementComponent implements OnInit { if (existingDeviceIndex >= 0) { // Update existing device in device list this.devices[existingDeviceIndex] = upsertDevice; - this.devices = [...this.devices]; + this.devices = [...this.devices].sort(resortDevices); } else { // Add new device to device list - this.devices = [upsertDevice, ...this.devices]; + this.devices = [upsertDevice, ...this.devices].sort(resortDevices); } } @@ -227,4 +236,18 @@ export class DeviceManagementComponent implements OnInit { const metadata = DeviceTypeMetadata[type]; return metadata ? (categoryIconMap[metadata.category] ?? defaultIcon) : defaultIcon; } + + protected async handleAuthRequestAnswered(pendingAuthRequest: DevicePendingAuthRequest) { + const loginApprovalDialog = LoginApprovalDialogComponent.open(this.dialogService, { + notificationId: pendingAuthRequest.id, + }); + + const result = await firstValueFrom(loginApprovalDialog.closed); + + if (result !== undefined && typeof result === "boolean") { + // Auth request was approved or denied, so clear the + // pending auth request and re-sort the device array + this.devices = clearAuthRequestAndResortDevices(this.devices, pendingAuthRequest); + } + } } diff --git a/libs/angular/src/auth/device-management/resort-devices.helper.ts b/libs/angular/src/auth/device-management/resort-devices.helper.ts index e739e943ee8..01661c18ef3 100644 --- a/libs/angular/src/auth/device-management/resort-devices.helper.ts +++ b/libs/angular/src/auth/device-management/resort-devices.helper.ts @@ -23,7 +23,7 @@ export function clearAuthRequestAndResortDevices( * * This is a helper function that gets passed to the `Array.sort()` method */ -function resortDevices(deviceA: DeviceDisplayData, deviceB: DeviceDisplayData) { +export function resortDevices(deviceA: DeviceDisplayData, deviceB: DeviceDisplayData) { // Devices with a pending auth request should be first if (deviceA.pendingAuthRequest) { return -1; @@ -40,11 +40,11 @@ function resortDevices(deviceA: DeviceDisplayData, deviceB: DeviceDisplayData) { return 1; } - // Then sort the rest by display name (alphabetically) - if (deviceA.displayName < deviceB.displayName) { + // Then sort the rest by creation date (newest to oldest) + if (deviceA.creationDate > deviceB.creationDate) { return -1; } - if (deviceA.displayName > deviceB.displayName) { + if (deviceA.creationDate < deviceB.creationDate) { return 1; }