From 8e70d5b9232b1f15353dc9f41de1e20109725e89 Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Fri, 31 Jan 2025 11:54:41 -0600 Subject: [PATCH] feat(auth): [PM-3953] generalize copy for login with device flows Updates UI text and translations for the login with device feature to be more consistent and clear across desktop, browser and web clients. Changes include: - Updated titles and content for login via auth request components - Revised translations for device approval modal - Updated notification titles and alert messages - Simplified device management URL handling - Added missing translations across platforms Resolves PM-3953 --- apps/browser/src/_locales/en/messages.json | 15 ++++++-- .../login-via-auth-request-v1.component.html | 15 +++++--- apps/browser/src/popup/app-routing.module.ts | 2 +- apps/desktop/src/app/app-routing.module.ts | 2 +- ...p-login-approval-component.service.spec.ts | 6 ++-- ...esktop-login-approval-component.service.ts | 8 ++--- .../login-via-auth-request-v1.component.html | 16 ++++++--- apps/desktop/src/locales/en/messages.json | 36 ++++++++++++------- .../login-via-auth-request-v1.component.html | 20 ++++------- apps/web/src/app/oss-routing.module.ts | 2 +- apps/web/src/locales/en/messages.json | 36 +++++++++++++++++-- .../login-via-auth-request-v1.component.ts | 9 ++++- .../login-approval.component.html | 8 ++--- .../login-approval.component.ts | 2 +- .../login-via-auth-request.component.html | 16 ++++++++- .../login-via-auth-request.component.ts | 10 ++++++ 16 files changed, 146 insertions(+), 57 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 9a06db4ed73..10efba9422f 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3123,12 +3123,18 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3138,6 +3144,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, diff --git a/apps/browser/src/auth/popup/login-via-auth-request-v1.component.html b/apps/browser/src/auth/popup/login-via-auth-request-v1.component.html index e7fafbb252c..34c0cbe9614 100644 --- a/apps/browser/src/auth/popup/login-via-auth-request-v1.component.html +++ b/apps/browser/src/auth/popup/login-via-auth-request-v1.component.html @@ -7,13 +7,20 @@
-

{{ "loginInitiated" | i18n }}

+

{{ "logInRequestSent" | i18n }}

-

{{ "notificationSentDevice" | i18n }}

-

- {{ "fingerprintMatchInfo" | i18n }} + {{ "notificationSentDevicePart1" | i18n }} + {{ "notificationSentDeviceAnchor" | i18n }}. {{ "notificationSentDevicePart2" | i18n }}

diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 05bd14611da..70a78ce548f 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -437,7 +437,7 @@ const routes: Routes = [ data: { pageIcon: DevicesIcon, pageTitle: { - key: "loginInitiated", + key: "logInRequestSent", }, pageSubtitle: { key: "aNotificationWasSentToYourDevice", diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index 9e3d2b17ca9..9007759775a 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -224,7 +224,7 @@ const routes: Routes = [ data: { pageIcon: DevicesIcon, pageTitle: { - key: "loginInitiated", + key: "logInRequestSent", }, pageSubtitle: { key: "aNotificationWasSentToYourDevice", diff --git a/apps/desktop/src/auth/login/desktop-login-approval-component.service.spec.ts b/apps/desktop/src/auth/login/desktop-login-approval-component.service.spec.ts index d687ae35742..efe17960068 100644 --- a/apps/desktop/src/auth/login/desktop-login-approval-component.service.spec.ts +++ b/apps/desktop/src/auth/login/desktop-login-approval-component.service.spec.ts @@ -51,15 +51,15 @@ describe("DesktopLoginApprovalComponentService", () => { it("calls ipc.auth.loginRequest with correct parameters when window is not visible", async () => { const title = "Log in requested"; const email = "test@bitwarden.com"; - const message = `Confirm login attempt for ${email}`; + const message = `Confirm access attempt for ${email}`; const closeText = "Close"; const loginApprovalComponent = { email } as LoginApprovalComponent; i18nService.t.mockImplementation((key: string) => { switch (key) { - case "logInRequested": + case "accountAccessRequested": return title; - case "confirmLoginAtemptForMail": + case "confirmAccessAttempt": return message; case "close": return closeText; diff --git a/apps/desktop/src/auth/login/desktop-login-approval-component.service.ts b/apps/desktop/src/auth/login/desktop-login-approval-component.service.ts index 9774dbba706..3b4658f34c5 100644 --- a/apps/desktop/src/auth/login/desktop-login-approval-component.service.ts +++ b/apps/desktop/src/auth/login/desktop-login-approval-component.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Injectable } from "@angular/core"; import { DefaultLoginApprovalComponentService } from "@bitwarden/auth/angular"; @@ -15,12 +13,12 @@ export class DesktopLoginApprovalComponentService super(); } - async showLoginRequestedAlertIfWindowNotVisible(email: string): Promise { + async showLoginRequestedAlertIfWindowNotVisible(email?: string): Promise { const isVisible = await ipc.platform.isWindowVisible(); if (!isVisible) { await ipc.auth.loginRequest( - this.i18nService.t("logInRequested"), - this.i18nService.t("confirmLoginAtemptForMail", email), + this.i18nService.t("accountAccessRequested"), + this.i18nService.t("confirmAccessAttempt", email), this.i18nService.t("close"), ); } diff --git a/apps/desktop/src/auth/login/login-via-auth-request-v1.component.html b/apps/desktop/src/auth/login/login-via-auth-request-v1.component.html index 06c1f279c00..9825949f7ec 100644 --- a/apps/desktop/src/auth/login/login-via-auth-request-v1.component.html +++ b/apps/desktop/src/auth/login/login-via-auth-request-v1.component.html @@ -3,15 +3,23 @@ Bitwarden -

{{ "loginInitiated" | i18n }}

+

{{ "logInRequestSent" | i18n }}

-

{{ "notificationSentDevice" | i18n }}

-

- {{ "fingerprintMatchInfo" | i18n }} +

+ {{ "notificationSentDevicePart1" | i18n }} + {{ "notificationSentDeviceAnchor" | i18n }}. {{ "notificationSentDevicePart2" | i18n }}

diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 354ca094a78..e45dceeaa78 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2745,14 +2745,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2782,11 +2791,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2803,11 +2812,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2843,8 +2852,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2855,6 +2864,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, diff --git a/apps/web/src/app/auth/login/login-via-auth-request-v1.component.html b/apps/web/src/app/auth/login/login-via-auth-request-v1.component.html index 69777950a78..ed157eb9cf4 100644 --- a/apps/web/src/app/auth/login/login-via-auth-request-v1.component.html +++ b/apps/web/src/app/auth/login/login-via-auth-request-v1.component.html @@ -1,5 +1,3 @@ - -
@@ -14,15 +12,11 @@
-

{{ "loginInitiated" | i18n }}

+

{{ "logInRequestSent" | i18n }}

-
-

{{ "notificationSentDevice" | i18n }}

- -

- {{ "fingerprintMatchInfo" | i18n }} -

-
+

+ {{ "notificationSentDeviceComplete" | i18n }} +

{{ "fingerprintPhraseHeader" | i18n }}

@@ -39,7 +33,7 @@
-
+
{{ "loginWithDeviceEnabledNote" | i18n }} {{ "viewAllLoginOptions" | i18n }}
@@ -52,7 +46,7 @@ >

{{ "adminApprovalRequested" | i18n }}

-
+

{{ "adminApprovalRequestSentToAdmins" | i18n }}

{{ "youWillBeNotifiedOnceApproved" | i18n }}

@@ -66,7 +60,7 @@
-
+
{{ "troubleLoggingIn" | i18n }} {{ "viewAllLoginOptions" | i18n }}
diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index c43f94b8acf..6863d6721e9 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -187,7 +187,7 @@ const routes: Routes = [ data: { pageIcon: DevicesIcon, pageTitle: { - key: "loginInitiated", + key: "logInRequestSent", }, pageSubtitle: { key: "aNotificationWasSentToYourDevice", diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 42158713271..c6e8b492c1c 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1203,6 +1203,9 @@ "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1392,12 +1395,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { diff --git a/libs/angular/src/auth/components/login-via-auth-request-v1.component.ts b/libs/angular/src/auth/components/login-via-auth-request-v1.component.ts index 386068ff783..7409acf6845 100644 --- a/libs/angular/src/auth/components/login-via-auth-request-v1.component.ts +++ b/libs/angular/src/auth/components/login-via-auth-request-v1.component.ts @@ -64,11 +64,12 @@ export class LoginViaAuthRequestComponentV1 protected StateEnum = State; protected state = State.StandardAuthRequest; - + protected webVaultUrl: string; protected twoFactorRoute = "2fa"; protected successRoute = "vault"; protected forcePasswordResetRoute = "update-temp-password"; private resendTimeout = 12000; + protected deviceManagementUrl: string; private authRequestKeyPair: { publicKey: Uint8Array; privateKey: Uint8Array }; @@ -95,6 +96,12 @@ export class LoginViaAuthRequestComponentV1 ) { super(environmentService, i18nService, platformUtilsService, toastService); + // Get the web vault URL from the environment service + environmentService.environment$.pipe(takeUntil(this.destroy$)).subscribe((env) => { + this.webVaultUrl = env.getWebVaultUrl(); + this.deviceManagementUrl = `${this.webVaultUrl}/#/settings/security/device-management`; + }); + // Gets signalR push notification // Only fires on approval to prevent enumeration this.authRequestService.authRequestPushNotification$ diff --git a/libs/auth/src/angular/login-approval/login-approval.component.html b/libs/auth/src/angular/login-approval/login-approval.component.html index c0cb9b9caf4..2115bdbff11 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.html +++ b/libs/auth/src/angular/login-approval/login-approval.component.html @@ -1,5 +1,5 @@ - {{ "areYouTryingtoLogin" | i18n }} + {{ "areYouTryingToAccessYourAccount" | i18n }}
@@ -8,7 +8,7 @@ -

{{ "logInAttemptBy" | i18n: email }}

+

{{ "accessAttemptBy" | i18n: email }}

{{ "fingerprintPhraseHeader" | i18n }}

{{ fingerprintPhrase }}

@@ -35,7 +35,7 @@ [bitAction]="approveLogin" [disabled]="loading" > - {{ "confirmLogIn" | i18n }} + {{ "confirmAccess" | i18n }} diff --git a/libs/auth/src/angular/login-approval/login-approval.component.ts b/libs/auth/src/angular/login-approval/login-approval.component.ts index 3b44f545abb..54d90306e5c 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.ts @@ -85,7 +85,7 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { } const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey); - this.email = await await firstValueFrom( + this.email = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), ); this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html index a1d0f200c15..ba26ba77cb0 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html @@ -1,6 +1,20 @@
-

{{ "makeSureYourAccountIsUnlockedAndTheFingerprintEtc" | i18n }}

+

+ {{ "notificationSentDevicePart1" | i18n }} + {{ "notificationSentDeviceAnchor" | i18n }}. {{ "notificationSentDevicePart2" | i18n }} +

+

+ {{ "notificationSentDeviceComplete" | i18n }} +

{{ "fingerprintPhraseHeader" | i18n }}
{{ fingerprintPhrase }} diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index b9a5ee4fe73..00e2d621c47 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -29,6 +29,7 @@ import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -71,6 +72,8 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { protected showResendNotification = false; protected Flow = Flow; protected flow = Flow.StandardAuthRequest; + protected webVaultUrl: string; + protected deviceManagementUrl: string; constructor( private accountService: AccountService, @@ -81,6 +84,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private authService: AuthService, private cryptoFunctionService: CryptoFunctionService, private deviceTrustService: DeviceTrustServiceAbstraction, + private environmentService: EnvironmentService, private i18nService: I18nService, private logService: LogService, private loginEmailService: LoginEmailServiceAbstraction, @@ -109,6 +113,12 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { this.logService.error("Failed to use approved auth request: " + e.message); }); }); + + // Get the web vault URL from the environment service + this.environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => { + this.webVaultUrl = env.getWebVaultUrl(); + this.deviceManagementUrl = `${this.webVaultUrl}/#/settings/security/device-management`; + }); } async ngOnInit(): Promise {