mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
refactor(login-approval-component) [Auth/PM-14940] Update LoginApprovalComponent (#15511)
- Renames the `LoginApprovalComponent` to `LoginApprovalDialogComponent` - Renames the property `notificationId` to `authRequestId` for clarity - Updates text content on the component
This commit is contained in:
@@ -3657,25 +3657,6 @@
|
|||||||
"thisRequestIsNoLongerValid": {
|
"thisRequestIsNoLongerValid": {
|
||||||
"message": "This request is no longer valid."
|
"message": "This request is no longer valid."
|
||||||
},
|
},
|
||||||
"areYouTryingToAccessYourAccount": {
|
|
||||||
"message": "Are you trying to access your account?"
|
|
||||||
},
|
|
||||||
"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": {
|
"loginRequestHasAlreadyExpired": {
|
||||||
"message": "Login request has already expired."
|
"message": "Login request has already expired."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ import {
|
|||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
|
import { LoginApprovalDialogComponent } from "@bitwarden/angular/auth/login-approval";
|
||||||
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
|
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
|
||||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||||
import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n";
|
import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { FingerprintDialogComponent, LoginApprovalComponent } from "@bitwarden/auth/angular";
|
import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
|
||||||
import {
|
import {
|
||||||
DESKTOP_SSO_CALLBACK,
|
DESKTOP_SSO_CALLBACK,
|
||||||
LogoutReason,
|
LogoutReason,
|
||||||
@@ -476,7 +477,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
case "openLoginApproval":
|
case "openLoginApproval":
|
||||||
if (message.notificationId != null) {
|
if (message.notificationId != null) {
|
||||||
this.dialogService.closeAll();
|
this.dialogService.closeAll();
|
||||||
const dialogRef = LoginApprovalComponent.open(this.dialogService, {
|
const dialogRef = LoginApprovalDialogComponent.open(this.dialogService, {
|
||||||
notificationId: message.notificationId,
|
notificationId: message.notificationId,
|
||||||
});
|
});
|
||||||
await firstValueFrom(dialogRef.closed);
|
await firstValueFrom(dialogRef.closed);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Router } from "@angular/router";
|
|||||||
import { Subject, merge } from "rxjs";
|
import { Subject, merge } from "rxjs";
|
||||||
|
|
||||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||||
|
import { LoginApprovalDialogComponentServiceAbstraction } from "@bitwarden/angular/auth/login-approval";
|
||||||
import { SetInitialPasswordService } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.service.abstraction";
|
import { SetInitialPasswordService } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.service.abstraction";
|
||||||
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||||
import {
|
import {
|
||||||
@@ -31,7 +32,6 @@ import {
|
|||||||
} from "@bitwarden/auth/angular";
|
} from "@bitwarden/auth/angular";
|
||||||
import {
|
import {
|
||||||
InternalUserDecryptionOptionsServiceAbstraction,
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
LoginApprovalComponentServiceAbstraction,
|
|
||||||
LoginEmailService,
|
LoginEmailService,
|
||||||
SsoUrlService,
|
SsoUrlService,
|
||||||
} from "@bitwarden/auth/common";
|
} from "@bitwarden/auth/common";
|
||||||
@@ -107,7 +107,7 @@ import {
|
|||||||
import { LockComponentService } from "@bitwarden/key-management-ui";
|
import { LockComponentService } from "@bitwarden/key-management-ui";
|
||||||
import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault";
|
import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
import { DesktopLoginApprovalComponentService } from "../../auth/login/desktop-login-approval-component.service";
|
import { DesktopLoginApprovalDialogComponentService } from "../../auth/login/desktop-login-approval-dialog-component.service";
|
||||||
import { DesktopLoginComponentService } from "../../auth/login/desktop-login-component.service";
|
import { DesktopLoginComponentService } from "../../auth/login/desktop-login-component.service";
|
||||||
import { DesktopTwoFactorAuthDuoComponentService } from "../../auth/services/desktop-two-factor-auth-duo-component.service";
|
import { DesktopTwoFactorAuthDuoComponentService } from "../../auth/services/desktop-two-factor-auth-duo-component.service";
|
||||||
import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service";
|
import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service";
|
||||||
@@ -444,8 +444,8 @@ const safeProviders: SafeProvider[] = [
|
|||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: LoginApprovalComponentServiceAbstraction,
|
provide: LoginApprovalDialogComponentServiceAbstraction,
|
||||||
useClass: DesktopLoginApprovalComponentService,
|
useClass: DesktopLoginApprovalDialogComponentService,
|
||||||
deps: [I18nServiceAbstraction],
|
deps: [I18nServiceAbstraction],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { TestBed } from "@angular/core/testing";
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
import { LoginApprovalComponent } from "@bitwarden/auth/angular";
|
import { LoginApprovalDialogComponent } from "@bitwarden/angular/auth/login-approval";
|
||||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
import { DesktopLoginApprovalComponentService } from "./desktop-login-approval-component.service";
|
import { DesktopLoginApprovalDialogComponentService } from "./desktop-login-approval-dialog-component.service";
|
||||||
|
|
||||||
describe("DesktopLoginApprovalComponentService", () => {
|
describe("DesktopLoginApprovalDialogComponentService", () => {
|
||||||
let service: DesktopLoginApprovalComponentService;
|
let service: DesktopLoginApprovalDialogComponentService;
|
||||||
let i18nService: MockProxy<I18nServiceAbstraction>;
|
let i18nService: MockProxy<I18nServiceAbstraction>;
|
||||||
let originalIpc: any;
|
let originalIpc: any;
|
||||||
|
|
||||||
@@ -31,12 +31,12 @@ describe("DesktopLoginApprovalComponentService", () => {
|
|||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
DesktopLoginApprovalComponentService,
|
DesktopLoginApprovalDialogComponentService,
|
||||||
{ provide: I18nServiceAbstraction, useValue: i18nService },
|
{ provide: I18nServiceAbstraction, useValue: i18nService },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
service = TestBed.inject(DesktopLoginApprovalComponentService);
|
service = TestBed.inject(DesktopLoginApprovalDialogComponentService);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -54,7 +54,7 @@ describe("DesktopLoginApprovalComponentService", () => {
|
|||||||
const message = `Confirm access attempt for ${email}`;
|
const message = `Confirm access attempt for ${email}`;
|
||||||
const closeText = "Close";
|
const closeText = "Close";
|
||||||
|
|
||||||
const loginApprovalComponent = { email } as LoginApprovalComponent;
|
const loginApprovalDialogComponent = { email } as LoginApprovalDialogComponent;
|
||||||
i18nService.t.mockImplementation((key: string) => {
|
i18nService.t.mockImplementation((key: string) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "accountAccessRequested":
|
case "accountAccessRequested":
|
||||||
@@ -71,18 +71,20 @@ describe("DesktopLoginApprovalComponentService", () => {
|
|||||||
jest.spyOn(ipc.platform, "isWindowVisible").mockResolvedValue(false);
|
jest.spyOn(ipc.platform, "isWindowVisible").mockResolvedValue(false);
|
||||||
jest.spyOn(ipc.auth, "loginRequest").mockResolvedValue();
|
jest.spyOn(ipc.auth, "loginRequest").mockResolvedValue();
|
||||||
|
|
||||||
await service.showLoginRequestedAlertIfWindowNotVisible(loginApprovalComponent.email);
|
await service.showLoginRequestedAlertIfWindowNotVisible(loginApprovalDialogComponent.email);
|
||||||
|
|
||||||
expect(ipc.auth.loginRequest).toHaveBeenCalledWith(title, message, closeText);
|
expect(ipc.auth.loginRequest).toHaveBeenCalledWith(title, message, closeText);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not call ipc.auth.loginRequest when window is visible", async () => {
|
it("does not call ipc.auth.loginRequest when window is visible", async () => {
|
||||||
const loginApprovalComponent = { email: "test@bitwarden.com" } as LoginApprovalComponent;
|
const loginApprovalDialogComponent = {
|
||||||
|
email: "test@bitwarden.com",
|
||||||
|
} as LoginApprovalDialogComponent;
|
||||||
|
|
||||||
jest.spyOn(ipc.platform, "isWindowVisible").mockResolvedValue(true);
|
jest.spyOn(ipc.platform, "isWindowVisible").mockResolvedValue(true);
|
||||||
jest.spyOn(ipc.auth, "loginRequest");
|
jest.spyOn(ipc.auth, "loginRequest");
|
||||||
|
|
||||||
await service.showLoginRequestedAlertIfWindowNotVisible(loginApprovalComponent.email);
|
await service.showLoginRequestedAlertIfWindowNotVisible(loginApprovalDialogComponent.email);
|
||||||
|
|
||||||
expect(ipc.auth.loginRequest).not.toHaveBeenCalled();
|
expect(ipc.auth.loginRequest).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { DefaultLoginApprovalComponentService } from "@bitwarden/auth/angular";
|
import {
|
||||||
import { LoginApprovalComponentServiceAbstraction } from "@bitwarden/auth/common";
|
DefaultLoginApprovalDialogComponentService,
|
||||||
|
LoginApprovalDialogComponentServiceAbstraction,
|
||||||
|
} from "@bitwarden/angular/auth/login-approval";
|
||||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DesktopLoginApprovalComponentService
|
export class DesktopLoginApprovalDialogComponentService
|
||||||
extends DefaultLoginApprovalComponentService
|
extends DefaultLoginApprovalDialogComponentService
|
||||||
implements LoginApprovalComponentServiceAbstraction
|
implements LoginApprovalDialogComponentServiceAbstraction
|
||||||
{
|
{
|
||||||
constructor(private i18nService: I18nServiceAbstraction) {
|
constructor(private i18nService: I18nServiceAbstraction) {
|
||||||
super();
|
super();
|
||||||
@@ -3027,9 +3027,6 @@
|
|||||||
"message": "Toggle character count",
|
"message": "Toggle character count",
|
||||||
"description": "'Character count' describes a feature that displays a number next to each character of the password."
|
"description": "'Character count' describes a feature that displays a number next to each character of the password."
|
||||||
},
|
},
|
||||||
"areYouTryingToAccessYourAccount": {
|
|
||||||
"message": "Are you trying to access your account?"
|
|
||||||
},
|
|
||||||
"accessAttemptBy": {
|
"accessAttemptBy": {
|
||||||
"message": "Access attempt by $EMAIL$",
|
"message": "Access attempt by $EMAIL$",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -3039,6 +3036,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"loginRequestApprovedForEmailOnDevice": {
|
||||||
|
"message": "Login request approved for $EMAIL$ on $DEVICE$",
|
||||||
|
"placeholders": {
|
||||||
|
"email": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "name@example.com"
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "Web app - Chrome"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"youDeniedLoginAttemptFromAnotherDevice": {
|
||||||
|
"message": "You denied a login attempt from another device. If this was you, try to log in with the device again."
|
||||||
|
},
|
||||||
|
"webApp": {
|
||||||
|
"message": "Web app"
|
||||||
|
},
|
||||||
|
"mobile": {
|
||||||
|
"message": "Mobile",
|
||||||
|
"description": "Mobile app"
|
||||||
|
},
|
||||||
|
"extension": {
|
||||||
|
"message": "Extension",
|
||||||
|
"description": "Browser extension/addon"
|
||||||
|
},
|
||||||
|
"desktop": {
|
||||||
|
"message": "Desktop",
|
||||||
|
"description": "Desktop app"
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"message": "CLI"
|
||||||
|
},
|
||||||
|
"sdk": {
|
||||||
|
"message": "SDK",
|
||||||
|
"description": "Software Development Kit"
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"message": "Server"
|
||||||
|
},
|
||||||
|
"loginRequest": {
|
||||||
|
"message": "Login request"
|
||||||
|
},
|
||||||
"deviceType": {
|
"deviceType": {
|
||||||
"message": "Device Type"
|
"message": "Device Type"
|
||||||
},
|
},
|
||||||
@@ -3054,22 +3095,6 @@
|
|||||||
"denyAccess": {
|
"denyAccess": {
|
||||||
"message": "Deny access"
|
"message": "Deny access"
|
||||||
},
|
},
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"justNow": {
|
"justNow": {
|
||||||
"message": "Just now"
|
"message": "Just now"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Component, DestroyRef } from "@angular/core";
|
|||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { LoginApprovalComponent } from "@bitwarden/auth/angular";
|
import { LoginApprovalDialogComponent } from "@bitwarden/angular/auth/login-approval";
|
||||||
import { AuthRequestApiServiceAbstraction } from "@bitwarden/auth/common";
|
import { AuthRequestApiServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||||
import {
|
import {
|
||||||
@@ -325,7 +325,7 @@ export class DeviceManagementOldComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogRef = LoginApprovalComponent.open(this.dialogService, {
|
const dialogRef = LoginApprovalDialogComponent.open(this.dialogService, {
|
||||||
notificationId: device.devicePendingAuthRequest.id,
|
notificationId: device.devicePendingAuthRequest.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1428,9 +1428,6 @@
|
|||||||
"notificationSentDevicePart1": {
|
"notificationSentDevicePart1": {
|
||||||
"message": "Unlock Bitwarden on your device or on the "
|
"message": "Unlock Bitwarden on your device or on the "
|
||||||
},
|
},
|
||||||
"areYouTryingToAccessYourAccount": {
|
|
||||||
"message": "Are you trying to access your account?"
|
|
||||||
},
|
|
||||||
"accessAttemptBy": {
|
"accessAttemptBy": {
|
||||||
"message": "Access attempt by $EMAIL$",
|
"message": "Access attempt by $EMAIL$",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -3981,22 +3978,6 @@
|
|||||||
"thisRequestIsNoLongerValid": {
|
"thisRequestIsNoLongerValid": {
|
||||||
"message": "This request is no longer valid."
|
"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."
|
|
||||||
},
|
|
||||||
"loginRequestApprovedForEmailOnDevice": {
|
"loginRequestApprovedForEmailOnDevice": {
|
||||||
"message": "Login request approved for $EMAIL$ on $DEVICE$",
|
"message": "Login request approved for $EMAIL$ on $DEVICE$",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ import { CommonModule } from "@angular/common";
|
|||||||
import { Component, Input } from "@angular/core";
|
import { Component, Input } from "@angular/core";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import { LoginApprovalComponent } from "@bitwarden/auth/angular";
|
|
||||||
import { DevicePendingAuthRequest } from "@bitwarden/common/auth/abstractions/devices/responses/device.response";
|
import { DevicePendingAuthRequest } from "@bitwarden/common/auth/abstractions/devices/responses/device.response";
|
||||||
import { BadgeModule, DialogService, ItemModule } from "@bitwarden/components";
|
import { BadgeModule, DialogService, ItemModule } from "@bitwarden/components";
|
||||||
import { I18nPipe } from "@bitwarden/ui-common";
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
|
|
||||||
|
import { LoginApprovalDialogComponent } from "../login-approval/login-approval-dialog.component";
|
||||||
|
|
||||||
import { DeviceDisplayData } from "./device-management.component";
|
import { DeviceDisplayData } from "./device-management.component";
|
||||||
import { clearAuthRequestAndResortDevices } from "./resort-devices.helper";
|
import { clearAuthRequestAndResortDevices } from "./resort-devices.helper";
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ export class DeviceManagementItemGroupComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginApprovalDialog = LoginApprovalComponent.open(this.dialogService, {
|
const loginApprovalDialog = LoginApprovalDialogComponent.open(this.dialogService, {
|
||||||
notificationId: pendingAuthRequest.id,
|
notificationId: pendingAuthRequest.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import { Component, Input, OnChanges, SimpleChanges } from "@angular/core";
|
|||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import { LoginApprovalComponent } from "@bitwarden/auth/angular";
|
|
||||||
import { DevicePendingAuthRequest } from "@bitwarden/common/auth/abstractions/devices/responses/device.response";
|
import { DevicePendingAuthRequest } from "@bitwarden/common/auth/abstractions/devices/responses/device.response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import {
|
import {
|
||||||
@@ -17,6 +14,8 @@ import {
|
|||||||
TableModule,
|
TableModule,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { LoginApprovalDialogComponent } from "../login-approval/login-approval-dialog.component";
|
||||||
|
|
||||||
import { DeviceDisplayData } from "./device-management.component";
|
import { DeviceDisplayData } from "./device-management.component";
|
||||||
import { clearAuthRequestAndResortDevices } from "./resort-devices.helper";
|
import { clearAuthRequestAndResortDevices } from "./resort-devices.helper";
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ export class DeviceManagementTableComponent implements OnChanges {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginApprovalDialog = LoginApprovalComponent.open(this.dialogService, {
|
const loginApprovalDialog = LoginApprovalDialogComponent.open(this.dialogService, {
|
||||||
notificationId: pendingAuthRequest.id,
|
notificationId: pendingAuthRequest.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { TestBed } from "@angular/core/testing";
|
||||||
|
|
||||||
|
import { DefaultLoginApprovalDialogComponentService } from "./default-login-approval-dialog-component.service";
|
||||||
|
import { LoginApprovalDialogComponent } from "./login-approval-dialog.component";
|
||||||
|
|
||||||
|
describe("DefaultLoginApprovalDialogComponentService", () => {
|
||||||
|
let service: DefaultLoginApprovalDialogComponentService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [DefaultLoginApprovalDialogComponentService],
|
||||||
|
});
|
||||||
|
|
||||||
|
service = TestBed.inject(DefaultLoginApprovalDialogComponentService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is created successfully", () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has showLoginRequestedAlertIfWindowNotVisible method that is a no-op", async () => {
|
||||||
|
const loginApprovalDialogComponent = {} as LoginApprovalDialogComponent;
|
||||||
|
|
||||||
|
const result = await service.showLoginRequestedAlertIfWindowNotVisible(
|
||||||
|
loginApprovalDialogComponent.email,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { LoginApprovalDialogComponentServiceAbstraction } from "./login-approval-dialog-component.service.abstraction";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of the LoginApprovalDialogComponentServiceAbstraction.
|
||||||
|
*/
|
||||||
|
export class DefaultLoginApprovalDialogComponentService
|
||||||
|
implements LoginApprovalDialogComponentServiceAbstraction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* No-op implementation of the showLoginRequestedAlertIfWindowNotVisible method.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async showLoginRequestedAlertIfWindowNotVisible(email?: string): Promise<void> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
libs/angular/src/auth/login-approval/index.ts
Normal file
3
libs/angular/src/auth/login-approval/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./login-approval-dialog.component";
|
||||||
|
export * from "./login-approval-dialog-component.service.abstraction";
|
||||||
|
export * from "./default-login-approval-dialog-component.service";
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Abstraction for the LoginApprovalComponent service.
|
* Abstraction for the LoginApprovalDialogComponent service.
|
||||||
*/
|
*/
|
||||||
export abstract class LoginApprovalComponentServiceAbstraction {
|
export abstract class LoginApprovalDialogComponentServiceAbstraction {
|
||||||
/**
|
/**
|
||||||
* Shows a login requested alert if the window is not visible.
|
* Shows a login requested alert if the window is not visible.
|
||||||
*/
|
*/
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<bit-dialog>
|
<bit-dialog>
|
||||||
<span bitDialogTitle>{{ "areYouTryingToAccessYourAccount" | i18n }}</span>
|
<span bitDialogTitle>{{ "loginRequest" | i18n }}</span>
|
||||||
|
|
||||||
<ng-container bitDialogContent>
|
<ng-container bitDialogContent>
|
||||||
<ng-container *ngIf="loading">
|
<ng-container *ngIf="loading">
|
||||||
<div class="tw-flex tw-items-center tw-justify-center" *ngIf="loading">
|
<div class="tw-flex tw-items-center tw-justify-center" *ngIf="loading">
|
||||||
@@ -8,28 +9,29 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="!loading">
|
<ng-container *ngIf="!loading">
|
||||||
<h4 class="tw-mb-3">{{ "accessAttemptBy" | i18n: email }}</h4>
|
<p>{{ "accessAttemptBy" | i18n: email }}</p>
|
||||||
<div>
|
<div>
|
||||||
<b>{{ "fingerprintPhraseHeader" | i18n }}</b>
|
<span class="tw-font-medium">{{ "fingerprintPhraseHeader" | i18n }}</span>
|
||||||
<p class="tw-text-code">{{ fingerprintPhrase }}</p>
|
<p class="tw-text-code">{{ fingerprintPhrase }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>{{ "deviceType" | i18n }}</b>
|
<span class="tw-font-medium">{{ "deviceType" | i18n }}</span>
|
||||||
<p>{{ authRequestResponse?.requestDeviceType }}</p>
|
<p>{{ readableDeviceTypeName }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>{{ "location" | i18n }}</b>
|
<span class="tw-font-medium">{{ "location" | i18n }}</span>
|
||||||
<p>
|
<p>
|
||||||
<span class="tw-capitalize">{{ authRequestResponse?.requestCountryName }} </span>
|
<span class="tw-capitalize">{{ authRequestResponse?.requestCountryName }} </span>
|
||||||
({{ authRequestResponse?.requestIpAddress }})
|
({{ authRequestResponse?.requestIpAddress }})
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>{{ "time" | i18n }}</b>
|
<span class="tw-font-medium">{{ "time" | i18n }}</span>
|
||||||
<p>{{ requestTimeText }}</p>
|
<p>{{ requestTimeText }}</p>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container bitDialogFooter>
|
<ng-container bitDialogFooter>
|
||||||
<button
|
<button
|
||||||
bitButton
|
bitButton
|
||||||
@@ -2,34 +2,33 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { of } from "rxjs";
|
import { of } from "rxjs";
|
||||||
|
|
||||||
import {
|
|
||||||
AuthRequestServiceAbstraction,
|
|
||||||
LoginApprovalComponentServiceAbstraction,
|
|
||||||
} from "@bitwarden/auth/common";
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
|
||||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
|
||||||
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";
|
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||||
|
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { DialogRef, DIALOG_DATA, ToastService } from "@bitwarden/components";
|
import { DialogRef, DIALOG_DATA, ToastService } from "@bitwarden/components";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { LogService } from "@bitwarden/logging";
|
||||||
|
|
||||||
import { LoginApprovalComponent } from "./login-approval.component";
|
import { LoginApprovalDialogComponentServiceAbstraction } from "./login-approval-dialog-component.service.abstraction";
|
||||||
|
import { LoginApprovalDialogComponent } from "./login-approval-dialog.component";
|
||||||
|
|
||||||
describe("LoginApprovalComponent", () => {
|
describe("LoginApprovalDialogComponent", () => {
|
||||||
let component: LoginApprovalComponent;
|
let component: LoginApprovalDialogComponent;
|
||||||
let fixture: ComponentFixture<LoginApprovalComponent>;
|
let fixture: ComponentFixture<LoginApprovalDialogComponent>;
|
||||||
|
|
||||||
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
|
||||||
let accountService: MockProxy<AccountService>;
|
let accountService: MockProxy<AccountService>;
|
||||||
let apiService: MockProxy<ApiService>;
|
let apiService: MockProxy<ApiService>;
|
||||||
let i18nService: MockProxy<I18nService>;
|
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
||||||
|
let devicesService: MockProxy<DevicesServiceAbstraction>;
|
||||||
let dialogRef: MockProxy<DialogRef>;
|
let dialogRef: MockProxy<DialogRef>;
|
||||||
|
let i18nService: MockProxy<I18nService>;
|
||||||
|
let logService: MockProxy<LogService>;
|
||||||
let toastService: MockProxy<ToastService>;
|
let toastService: MockProxy<ToastService>;
|
||||||
let validationService: MockProxy<ValidationService>;
|
let validationService: MockProxy<ValidationService>;
|
||||||
|
|
||||||
@@ -38,11 +37,13 @@ describe("LoginApprovalComponent", () => {
|
|||||||
const testPublicKey = "test-public-key";
|
const testPublicKey = "test-public-key";
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
authRequestService = mock<AuthRequestServiceAbstraction>();
|
|
||||||
accountService = mock<AccountService>();
|
accountService = mock<AccountService>();
|
||||||
apiService = mock<ApiService>();
|
apiService = mock<ApiService>();
|
||||||
i18nService = mock<I18nService>();
|
authRequestService = mock<AuthRequestServiceAbstraction>();
|
||||||
|
devicesService = mock<DevicesServiceAbstraction>();
|
||||||
dialogRef = mock<DialogRef>();
|
dialogRef = mock<DialogRef>();
|
||||||
|
i18nService = mock<I18nService>();
|
||||||
|
logService = mock<LogService>();
|
||||||
toastService = mock<ToastService>();
|
toastService = mock<ToastService>();
|
||||||
validationService = mock<ValidationService>();
|
validationService = mock<ValidationService>();
|
||||||
|
|
||||||
@@ -54,27 +55,26 @@ describe("LoginApprovalComponent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [LoginApprovalComponent],
|
imports: [LoginApprovalDialogComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: DIALOG_DATA, useValue: { notificationId: testNotificationId } },
|
{ provide: DIALOG_DATA, useValue: { notificationId: testNotificationId } },
|
||||||
{ provide: AuthRequestServiceAbstraction, useValue: authRequestService },
|
|
||||||
{ provide: AccountService, useValue: accountService },
|
{ provide: AccountService, useValue: accountService },
|
||||||
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
|
|
||||||
{ provide: I18nService, useValue: i18nService },
|
|
||||||
{ provide: ApiService, useValue: apiService },
|
{ provide: ApiService, useValue: apiService },
|
||||||
{ provide: AppIdService, useValue: mock<AppIdService>() },
|
{ provide: AuthRequestServiceAbstraction, useValue: authRequestService },
|
||||||
{ provide: KeyService, useValue: mock<KeyService>() },
|
{ provide: DevicesServiceAbstraction, useValue: devicesService },
|
||||||
{ provide: DialogRef, useValue: dialogRef },
|
{ provide: DialogRef, useValue: dialogRef },
|
||||||
|
{ provide: I18nService, useValue: i18nService },
|
||||||
|
{ provide: LogService, useValue: logService },
|
||||||
{ provide: ToastService, useValue: toastService },
|
{ provide: ToastService, useValue: toastService },
|
||||||
{ provide: ValidationService, useValue: validationService },
|
{ provide: ValidationService, useValue: validationService },
|
||||||
{
|
{
|
||||||
provide: LoginApprovalComponentServiceAbstraction,
|
provide: LoginApprovalDialogComponentServiceAbstraction,
|
||||||
useValue: mock<LoginApprovalComponentServiceAbstraction>(),
|
useValue: mock<LoginApprovalDialogComponentServiceAbstraction>(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(LoginApprovalComponent);
|
fixture = TestBed.createComponent(LoginApprovalDialogComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,7 +119,6 @@ describe("LoginApprovalComponent", () => {
|
|||||||
expect(authRequestService.approveOrDenyAuthRequest).toHaveBeenCalledWith(false, response);
|
expect(authRequestService.approveOrDenyAuthRequest).toHaveBeenCalledWith(false, response);
|
||||||
expect(toastService.showToast).toHaveBeenCalledWith({
|
expect(toastService.showToast).toHaveBeenCalledWith({
|
||||||
variant: "info",
|
variant: "info",
|
||||||
title: null,
|
|
||||||
message: "denied message",
|
message: "denied message",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,24 +1,18 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, OnInit, OnDestroy, Inject } from "@angular/core";
|
import { Component, OnInit, OnDestroy, Inject } from "@angular/core";
|
||||||
import { Subject, firstValueFrom, map } from "rxjs";
|
import { firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import {
|
|
||||||
AuthRequestServiceAbstraction,
|
|
||||||
LoginApprovalComponentServiceAbstraction as LoginApprovalComponentService,
|
|
||||||
} from "@bitwarden/auth/common";
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
|
||||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
|
||||||
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";
|
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||||
|
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import {
|
import {
|
||||||
DIALOG_DATA,
|
DIALOG_DATA,
|
||||||
DialogRef,
|
DialogRef,
|
||||||
@@ -28,85 +22,101 @@ import {
|
|||||||
DialogService,
|
DialogService,
|
||||||
ToastService,
|
ToastService,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { LogService } from "@bitwarden/logging";
|
||||||
|
|
||||||
const RequestTimeOut = 60000 * 15; //15 Minutes
|
import { LoginApprovalDialogComponentServiceAbstraction } from "./login-approval-dialog-component.service.abstraction";
|
||||||
const RequestTimeUpdate = 60000 * 5; //5 Minutes
|
|
||||||
|
const RequestTimeOut = 60000 * 15; // 15 Minutes
|
||||||
|
const RequestTimeUpdate = 60000 * 5; // 5 Minutes
|
||||||
|
|
||||||
export interface LoginApprovalDialogParams {
|
export interface LoginApprovalDialogParams {
|
||||||
notificationId: string;
|
notificationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "login-approval",
|
templateUrl: "login-approval-dialog.component.html",
|
||||||
templateUrl: "login-approval.component.html",
|
imports: [AsyncActionsModule, ButtonModule, CommonModule, DialogModule, JslibModule],
|
||||||
imports: [CommonModule, AsyncActionsModule, ButtonModule, DialogModule, JslibModule],
|
|
||||||
})
|
})
|
||||||
export class LoginApprovalComponent implements OnInit, OnDestroy {
|
export class LoginApprovalDialogComponent implements OnInit, OnDestroy {
|
||||||
|
authRequestId: string;
|
||||||
|
authRequestResponse?: AuthRequestResponse;
|
||||||
|
email?: string;
|
||||||
|
fingerprintPhrase?: string;
|
||||||
|
interval?: NodeJS.Timeout;
|
||||||
loading = true;
|
loading = true;
|
||||||
|
readableDeviceTypeName?: string;
|
||||||
notificationId: string;
|
requestTimeText?: string;
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
|
||||||
|
|
||||||
email: string;
|
|
||||||
fingerprintPhrase: string;
|
|
||||||
authRequestResponse: AuthRequestResponse;
|
|
||||||
interval: NodeJS.Timeout;
|
|
||||||
requestTimeText: string;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DIALOG_DATA) private params: LoginApprovalDialogParams,
|
@Inject(DIALOG_DATA) private params: LoginApprovalDialogParams,
|
||||||
protected authRequestService: AuthRequestServiceAbstraction,
|
private accountService: AccountService,
|
||||||
protected accountService: AccountService,
|
private apiService: ApiService,
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
private authRequestService: AuthRequestServiceAbstraction,
|
||||||
protected i18nService: I18nService,
|
private devicesService: DevicesServiceAbstraction,
|
||||||
protected apiService: ApiService,
|
|
||||||
protected appIdService: AppIdService,
|
|
||||||
protected keyService: KeyService,
|
|
||||||
private dialogRef: DialogRef,
|
private dialogRef: DialogRef,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private loginApprovalDialogComponentService: LoginApprovalDialogComponentServiceAbstraction,
|
||||||
|
private logService: LogService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private loginApprovalComponentService: LoginApprovalComponentService,
|
|
||||||
private validationService: ValidationService,
|
private validationService: ValidationService,
|
||||||
) {
|
) {
|
||||||
this.notificationId = params.notificationId;
|
this.authRequestId = params.notificationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnDestroy(): Promise<void> {
|
async ngOnDestroy(): Promise<void> {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
this.destroy$.next();
|
|
||||||
this.destroy$.complete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
if (this.notificationId != null) {
|
if (this.authRequestId == null) {
|
||||||
|
this.logService.error("LoginApprovalDialogComponent: authRequestId is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId);
|
this.authRequestResponse = await this.apiService.getAuthRequest(this.authRequestId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.validationService.showError(error);
|
this.validationService.showError(error);
|
||||||
|
this.logService.error("LoginApprovalDialogComponent: getAuthRequest error", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.authRequestResponse == null) {
|
||||||
|
this.logService.error("LoginApprovalDialogComponent: authRequestResponse not found");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey);
|
const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey);
|
||||||
|
|
||||||
this.email = await firstValueFrom(
|
this.email = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
|
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!this.email) {
|
||||||
|
this.logService.error("LoginApprovalDialogComponent: email not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase(
|
this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase(
|
||||||
this.email,
|
this.email,
|
||||||
publicKey,
|
publicKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.readableDeviceTypeName = this.devicesService.getReadableDeviceTypeName(
|
||||||
|
this.authRequestResponse.requestDeviceTypeValue,
|
||||||
|
);
|
||||||
|
|
||||||
this.updateTimeText();
|
this.updateTimeText();
|
||||||
|
|
||||||
this.interval = setInterval(() => {
|
this.interval = setInterval(() => {
|
||||||
this.updateTimeText();
|
this.updateTimeText();
|
||||||
}, RequestTimeUpdate);
|
}, RequestTimeUpdate);
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await this.loginApprovalDialogComponentService.showLoginRequestedAlertIfWindowNotVisible(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
this.email,
|
||||||
this.loginApprovalComponentService.showLoginRequestedAlertIfWindowNotVisible(this.email);
|
);
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strongly-typed helper to open a LoginApprovalDialog
|
* Strongly-typed helper to open a LoginApprovalDialog
|
||||||
@@ -114,7 +124,7 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
|||||||
* @param data Configuration for the dialog
|
* @param data Configuration for the dialog
|
||||||
*/
|
*/
|
||||||
static open(dialogService: DialogService, data: LoginApprovalDialogParams) {
|
static open(dialogService: DialogService, data: LoginApprovalDialogParams) {
|
||||||
return dialogService.open(LoginApprovalComponent, { data });
|
return dialogService.open(LoginApprovalDialogComponent, { data });
|
||||||
}
|
}
|
||||||
|
|
||||||
denyLogin = async () => {
|
denyLogin = async () => {
|
||||||
@@ -126,11 +136,10 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private async retrieveAuthRequestAndRespond(approve: boolean) {
|
private async retrieveAuthRequestAndRespond(approve: boolean) {
|
||||||
this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId);
|
this.authRequestResponse = await this.apiService.getAuthRequest(this.authRequestId);
|
||||||
if (this.authRequestResponse.requestApproved || this.authRequestResponse.responseDate != null) {
|
if (this.authRequestResponse.requestApproved || this.authRequestResponse.responseDate != null) {
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "info",
|
variant: "info",
|
||||||
title: null,
|
|
||||||
message: this.i18nService.t("thisRequestIsNoLongerValid"),
|
message: this.i18nService.t("thisRequestIsNoLongerValid"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -148,23 +157,26 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
|||||||
if (loginResponse.requestApproved) {
|
if (loginResponse.requestApproved) {
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: null,
|
|
||||||
message: this.i18nService.t(
|
message: this.i18nService.t(
|
||||||
"logInConfirmedForEmailOnDevice",
|
"loginRequestApprovedForEmailOnDevice",
|
||||||
this.email,
|
this.email,
|
||||||
loginResponse.requestDeviceType,
|
this.devicesService.getReadableDeviceTypeName(loginResponse.requestDeviceTypeValue),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "info",
|
variant: "info",
|
||||||
title: null,
|
message: this.i18nService.t("youDeniedLoginAttemptFromAnotherDevice"),
|
||||||
message: this.i18nService.t("youDeniedALogInAttemptFromAnotherDevice"),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTimeText() {
|
updateTimeText() {
|
||||||
|
if (this.authRequestResponse == null) {
|
||||||
|
this.logService.error("LoginApprovalDialogComponent: authRequestResponse not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const requestDate = new Date(this.authRequestResponse.creationDate);
|
const requestDate = new Date(this.authRequestResponse.creationDate);
|
||||||
const requestDateUTC = Date.UTC(
|
const requestDateUTC = Date.UTC(
|
||||||
requestDate.getUTCFullYear(),
|
requestDate.getUTCFullYear(),
|
||||||
@@ -201,7 +213,6 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
|||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "info",
|
variant: "info",
|
||||||
title: null,
|
|
||||||
message: this.i18nService.t("loginRequestHasAlreadyExpired"),
|
message: this.i18nService.t("loginRequestHasAlreadyExpired"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import {
|
import {
|
||||||
DefaultLoginApprovalComponentService,
|
|
||||||
DefaultLoginComponentService,
|
DefaultLoginComponentService,
|
||||||
DefaultLoginDecryptionOptionsService,
|
DefaultLoginDecryptionOptionsService,
|
||||||
DefaultRegistrationFinishService,
|
DefaultRegistrationFinishService,
|
||||||
@@ -40,7 +39,6 @@ import {
|
|||||||
DefaultLoginSuccessHandlerService,
|
DefaultLoginSuccessHandlerService,
|
||||||
DefaultLogoutService,
|
DefaultLogoutService,
|
||||||
InternalUserDecryptionOptionsServiceAbstraction,
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
LoginApprovalComponentServiceAbstraction,
|
|
||||||
LoginEmailService,
|
LoginEmailService,
|
||||||
LoginEmailServiceAbstraction,
|
LoginEmailServiceAbstraction,
|
||||||
LoginStrategyService,
|
LoginStrategyService,
|
||||||
@@ -343,6 +341,8 @@ import {
|
|||||||
VaultExportServiceAbstraction,
|
VaultExportServiceAbstraction,
|
||||||
} from "@bitwarden/vault-export-core";
|
} from "@bitwarden/vault-export-core";
|
||||||
|
|
||||||
|
import { DefaultLoginApprovalDialogComponentService } from "../auth/login-approval/default-login-approval-dialog-component.service";
|
||||||
|
import { LoginApprovalDialogComponentServiceAbstraction } from "../auth/login-approval/login-approval-dialog-component.service.abstraction";
|
||||||
import { DefaultSetInitialPasswordService } from "../auth/password-management/set-initial-password/default-set-initial-password.service.implementation";
|
import { DefaultSetInitialPasswordService } from "../auth/password-management/set-initial-password/default-set-initial-password.service.implementation";
|
||||||
import { SetInitialPasswordService } from "../auth/password-management/set-initial-password/set-initial-password.service.abstraction";
|
import { SetInitialPasswordService } from "../auth/password-management/set-initial-password/set-initial-password.service.abstraction";
|
||||||
import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "../auth/services/device-trust-toast.service.abstraction";
|
import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "../auth/services/device-trust-toast.service.abstraction";
|
||||||
@@ -1494,8 +1494,8 @@ const safeProviders: SafeProvider[] = [
|
|||||||
deps: [CryptoFunctionServiceAbstraction],
|
deps: [CryptoFunctionServiceAbstraction],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: LoginApprovalComponentServiceAbstraction,
|
provide: LoginApprovalDialogComponentServiceAbstraction,
|
||||||
useClass: DefaultLoginApprovalComponentService,
|
useClass: DefaultLoginApprovalDialogComponentService,
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
|
|||||||
@@ -57,10 +57,6 @@ export * from "./sso/default-sso-component.service";
|
|||||||
// self hosted environment configuration dialog
|
// self hosted environment configuration dialog
|
||||||
export * from "./self-hosted-env-config-dialog/self-hosted-env-config-dialog.component";
|
export * from "./self-hosted-env-config-dialog/self-hosted-env-config-dialog.component";
|
||||||
|
|
||||||
// login approval
|
|
||||||
export * from "./login-approval/login-approval.component";
|
|
||||||
export * from "./login-approval/default-login-approval-component.service";
|
|
||||||
|
|
||||||
// two factor auth
|
// two factor auth
|
||||||
export * from "./two-factor-auth";
|
export * from "./two-factor-auth";
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { TestBed } from "@angular/core/testing";
|
|
||||||
|
|
||||||
import { DefaultLoginApprovalComponentService } from "./default-login-approval-component.service";
|
|
||||||
import { LoginApprovalComponent } from "./login-approval.component";
|
|
||||||
|
|
||||||
describe("DefaultLoginApprovalComponentService", () => {
|
|
||||||
let service: DefaultLoginApprovalComponentService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [DefaultLoginApprovalComponentService],
|
|
||||||
});
|
|
||||||
|
|
||||||
service = TestBed.inject(DefaultLoginApprovalComponentService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("is created successfully", () => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has showLoginRequestedAlertIfWindowNotVisible method that is a no-op", async () => {
|
|
||||||
const loginApprovalComponent = {} as LoginApprovalComponent;
|
|
||||||
await service.showLoginRequestedAlertIfWindowNotVisible(loginApprovalComponent.email);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { LoginApprovalComponentServiceAbstraction } from "../../common/abstractions/login-approval-component.service.abstraction";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default implementation of the LoginApprovalComponentServiceAbstraction.
|
|
||||||
*/
|
|
||||||
export class DefaultLoginApprovalComponentService
|
|
||||||
implements LoginApprovalComponentServiceAbstraction
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* No-op implementation of the showLoginRequestedAlertIfWindowNotVisible method.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async showLoginRequestedAlertIfWindowNotVisible(email?: string): Promise<void> {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,5 @@ export * from "./login-email.service";
|
|||||||
export * from "./login-strategy.service";
|
export * from "./login-strategy.service";
|
||||||
export * from "./user-decryption-options.service.abstraction";
|
export * from "./user-decryption-options.service.abstraction";
|
||||||
export * from "./auth-request.service.abstraction";
|
export * from "./auth-request.service.abstraction";
|
||||||
export * from "./login-approval-component.service.abstraction";
|
|
||||||
export * from "./login-success-handler.service";
|
export * from "./login-success-handler.service";
|
||||||
export * from "./logout.service";
|
export * from "./logout.service";
|
||||||
|
|||||||
Reference in New Issue
Block a user