mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-2417] Update LoginApprovalComponent on Desktop (#6751)
* [PM-2417] convert modal to dialog service * code format * [PM-2417] Fix title * [PM-2417] Remove unnecessary class * Updated to use a local reference for the dialog. * Changes to clarify the method naming * More cleanup with Will. * Removed unused style --------- Co-authored-by: Todd Martin <tmartin@bitwarden.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com>
This commit is contained in:
@@ -399,7 +399,11 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
break;
|
break;
|
||||||
case "openLoginApproval":
|
case "openLoginApproval":
|
||||||
if (message.notificationId != null) {
|
if (message.notificationId != null) {
|
||||||
await this.openLoginApproval(message.notificationId);
|
this.dialogService.closeAll();
|
||||||
|
const dialogRef = LoginApprovalComponent.open(this.dialogService, {
|
||||||
|
notificationId: message.notificationId,
|
||||||
|
});
|
||||||
|
await firstValueFrom(dialogRef.closed);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "redrawMenu":
|
case "redrawMenu":
|
||||||
@@ -473,19 +477,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async openLoginApproval(notificationId: string) {
|
|
||||||
this.modalService.closeAll();
|
|
||||||
|
|
||||||
this.modal = await this.modalService.open(LoginApprovalComponent, {
|
|
||||||
data: { notificationId: notificationId },
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
|
||||||
this.modal.onClosed.subscribe(() => {
|
|
||||||
this.modal = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateAppMenu() {
|
private async updateAppMenu() {
|
||||||
let updateRequest: MenuUpdateRequest;
|
let updateRequest: MenuUpdateRequest;
|
||||||
const stateAccounts = await firstValueFrom(this.stateService.accounts$);
|
const stateAccounts = await firstValueFrom(this.stateService.accounts$);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { DeleteAccountComponent } from "../auth/delete-account.component";
|
|||||||
import { EnvironmentComponent } from "../auth/environment.component";
|
import { EnvironmentComponent } from "../auth/environment.component";
|
||||||
import { HintComponent } from "../auth/hint.component";
|
import { HintComponent } from "../auth/hint.component";
|
||||||
import { LockComponent } from "../auth/lock.component";
|
import { LockComponent } from "../auth/lock.component";
|
||||||
import { LoginApprovalComponent } from "../auth/login/login-approval.component";
|
|
||||||
import { LoginModule } from "../auth/login/login.module";
|
import { LoginModule } from "../auth/login/login.module";
|
||||||
import { RegisterComponent } from "../auth/register.component";
|
import { RegisterComponent } from "../auth/register.component";
|
||||||
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
||||||
@@ -101,7 +100,6 @@ import { SendComponent } from "./tools/send/send.component";
|
|||||||
VaultTimeoutInputComponent,
|
VaultTimeoutInputComponent,
|
||||||
ViewComponent,
|
ViewComponent,
|
||||||
ViewCustomFieldsComponent,
|
ViewCustomFieldsComponent,
|
||||||
LoginApprovalComponent,
|
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,43 +1,42 @@
|
|||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="loginApprovalTitle">
|
<bit-dialog>
|
||||||
<div class="modal-dialog modal-md" role="document">
|
<span bitDialogTitle>{{ "areYouTryingtoLogin" | i18n }}</span>
|
||||||
<div id="login-approval-page" class="modal-content">
|
<ng-container bitDialogContent>
|
||||||
<div class="section-title">
|
<h4>{{ "logInAttemptBy" | i18n: email }}</h4>
|
||||||
<p style="text-transform: uppercase">{{ "areYouTryingtoLogin" | i18n }}</p>
|
<div>
|
||||||
</div>
|
<b>{{ "fingerprintPhraseHeader" | i18n }}</b>
|
||||||
<div class="content">
|
<p class="tw-text-code">{{ fingerprintPhrase }}</p>
|
||||||
<div class="section">
|
|
||||||
<h4>{{ "logInAttemptBy" | i18n: email }}</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h4 class="label">{{ "fingerprintPhraseHeader" | i18n }}</h4>
|
|
||||||
<code>{{ fingerprintPhrase }}</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h4 class="label">{{ "deviceType" | i18n }}</h4>
|
|
||||||
<p>{{ authRequestResponse?.requestDeviceType }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h4 class="label">{{ "ipAddress" | i18n }}</h4>
|
|
||||||
<p>{{ authRequestResponse?.requestIpAddress }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h4 class="label">{{ "time" | i18n }}</h4>
|
|
||||||
<p>{{ requestTimeText }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="primary" (click)="approveLogin(true, true)">
|
|
||||||
{{ "confirmLogIn" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button type="button" (click)="approveLogin(false, true)">
|
|
||||||
{{ "denyLogIn" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<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 bitDialogFooter>
|
||||||
|
<button
|
||||||
|
bitButton
|
||||||
|
type="button"
|
||||||
|
buttonType="primary"
|
||||||
|
[bitAction]="approveLogin"
|
||||||
|
[bitDialogClose]="true"
|
||||||
|
>
|
||||||
|
{{ "confirmLogIn" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
bitButton
|
||||||
|
type="button"
|
||||||
|
buttonType="secondary"
|
||||||
|
[bitAction]="denyLogin"
|
||||||
|
[bitDialogClose]="true"
|
||||||
|
>
|
||||||
|
{{ "denyLogIn" | i18n }}
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</bit-dialog>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Component, OnInit, OnDestroy } from "@angular/core";
|
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
|
||||||
import { Subject } from "rxjs";
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { Component, OnInit, OnDestroy, Inject } from "@angular/core";
|
||||||
|
import { Subject, firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||||
@@ -12,13 +13,25 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import {
|
||||||
|
AsyncActionsModule,
|
||||||
|
ButtonModule,
|
||||||
|
DialogModule,
|
||||||
|
DialogService,
|
||||||
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
const RequestTimeOut = 60000 * 15; //15 Minutes
|
const RequestTimeOut = 60000 * 15; //15 Minutes
|
||||||
const RequestTimeUpdate = 60000 * 5; //5 Minutes
|
const RequestTimeUpdate = 60000 * 5; //5 Minutes
|
||||||
|
|
||||||
|
export interface LoginApprovalDialogParams {
|
||||||
|
notificationId: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "login-approval",
|
selector: "login-approval",
|
||||||
templateUrl: "login-approval.component.html",
|
templateUrl: "login-approval.component.html",
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, AsyncActionsModule, ButtonModule, DialogModule, JslibModule],
|
||||||
})
|
})
|
||||||
export class LoginApprovalComponent implements OnInit, OnDestroy {
|
export class LoginApprovalComponent implements OnInit, OnDestroy {
|
||||||
notificationId: string;
|
notificationId: string;
|
||||||
@@ -30,9 +43,9 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
|||||||
authRequestResponse: AuthRequestResponse;
|
authRequestResponse: AuthRequestResponse;
|
||||||
interval: NodeJS.Timeout;
|
interval: NodeJS.Timeout;
|
||||||
requestTimeText: string;
|
requestTimeText: string;
|
||||||
dismissModal: boolean;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) private params: LoginApprovalDialogParams,
|
||||||
protected stateService: StateService,
|
protected stateService: StateService,
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
@@ -40,23 +53,17 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
|||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected appIdService: AppIdService,
|
protected appIdService: AppIdService,
|
||||||
protected cryptoService: CryptoService,
|
protected cryptoService: CryptoService,
|
||||||
private modalRef: ModalRef,
|
private dialogRef: DialogRef,
|
||||||
config: ModalConfig,
|
|
||||||
) {
|
) {
|
||||||
this.notificationId = config.data.notificationId;
|
this.notificationId = params.notificationId;
|
||||||
|
|
||||||
this.dismissModal = true;
|
|
||||||
this.modalRef.onClosed
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
|
||||||
.subscribe(() => {
|
|
||||||
if (this.dismissModal) {
|
|
||||||
this.approveLogin(false, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
async ngOnDestroy(): Promise<void> {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
|
const closedWithButton = await firstValueFrom(this.dialogRef.closed);
|
||||||
|
if (!closedWithButton) {
|
||||||
|
this.retrieveAuthRequestAndRespond(false);
|
||||||
|
}
|
||||||
this.destroy$.next();
|
this.destroy$.next();
|
||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
@@ -86,14 +93,24 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async approveLogin(approveLogin: boolean, approveDenyButtonClicked: boolean) {
|
/**
|
||||||
clearInterval(this.interval);
|
* Strongly-typed helper to open a LoginApprovalDialog
|
||||||
|
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||||
|
* @param data Configuration for the dialog
|
||||||
|
*/
|
||||||
|
static open(dialogService: DialogService, data: LoginApprovalDialogParams) {
|
||||||
|
return dialogService.open(LoginApprovalComponent, { data });
|
||||||
|
}
|
||||||
|
|
||||||
this.dismissModal = !approveDenyButtonClicked;
|
denyLogin = async () => {
|
||||||
if (approveDenyButtonClicked) {
|
await this.retrieveAuthRequestAndRespond(false);
|
||||||
this.modalRef.close();
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
approveLogin = async () => {
|
||||||
|
await this.retrieveAuthRequestAndRespond(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
private async retrieveAuthRequestAndRespond(approve: boolean) {
|
||||||
this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId);
|
this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId);
|
||||||
if (this.authRequestResponse.requestApproved || this.authRequestResponse.responseDate != null) {
|
if (this.authRequestResponse.requestApproved || this.authRequestResponse.responseDate != null) {
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
@@ -105,7 +122,7 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
|||||||
const loginResponse = await this.authService.passwordlessLogin(
|
const loginResponse = await this.authService.passwordlessLogin(
|
||||||
this.authRequestResponse.id,
|
this.authRequestResponse.id,
|
||||||
this.authRequestResponse.publicKey,
|
this.authRequestResponse.publicKey,
|
||||||
approveLogin,
|
approve,
|
||||||
);
|
);
|
||||||
this.showResultToast(loginResponse);
|
this.showResultToast(loginResponse);
|
||||||
}
|
}
|
||||||
@@ -165,7 +182,7 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
this.modalRef.close();
|
this.dialogRef.close();
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"info",
|
"info",
|
||||||
null,
|
null,
|
||||||
|
|||||||
Reference in New Issue
Block a user