1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

feat(auth): [PM-8221] implement device verification for unknown devices

Add device verification flow that requires users to enter an OTP when logging in from an unrecognized device. This includes:

- New device verification route and guard
- Email OTP verification component
- Authentication timeout handling

PM-8221
This commit is contained in:
Alec Rippberger
2025-01-23 12:57:48 -06:00
committed by GitHub
parent f50f5ef70b
commit aa1c0ca0ee
35 changed files with 852 additions and 86 deletions

View File

@@ -647,6 +647,12 @@
"verifyIdentity": {
"message": "Verify identity"
},
"weDontRecognizeThisDevice": {
"message": "We don't recognize this device. Enter the code sent to your email to verify your identity."
},
"continueLoggingIn": {
"message": "Continue logging in"
},
"yourVaultIsLocked": {
"message": "Your vault is locked. Verify your identity to continue."
},

View File

@@ -1,21 +1,23 @@
import { Injectable, NgModule } from "@angular/core";
import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
import { AuthenticationTimeoutComponent } from "@bitwarden/angular/auth/components/authentication-timeout.component";
import {
EnvironmentSelectorComponent,
EnvironmentSelectorRouteData,
ExtensionDefaultOverlayPosition,
} from "@bitwarden/angular/auth/components/environment-selector.component";
import { TwoFactorTimeoutComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-expired.component";
import { unauthUiRefreshRedirect } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-redirect";
import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
authGuard,
lockGuard,
activeAuthGuard,
redirectGuard,
tdeDecryptionRequiredGuard,
unauthGuardFn,
} from "@bitwarden/angular/auth/guards";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap";
import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards";
import {
@@ -39,7 +41,10 @@ import {
DevicesIcon,
SsoComponent,
TwoFactorTimeoutIcon,
NewDeviceVerificationComponent,
DeviceVerificationIcon,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { LockComponent } from "@bitwarden/key-management/angular";
import {
NewDeviceVerificationNoticePageOneComponent,
@@ -172,12 +177,12 @@ const routes: Routes = [
component: ExtensionAnonLayoutWrapperComponent,
children: [
{
path: "2fa-timeout",
path: "authentication-timeout",
canActivate: [unauthGuardFn(unauthRouteOverrides)],
children: [
{
path: "",
component: TwoFactorTimeoutComponent,
component: AuthenticationTimeoutComponent,
},
],
data: {
@@ -230,6 +235,27 @@ const routes: Routes = [
],
},
),
{
path: "device-verification",
component: ExtensionAnonLayoutWrapperComponent,
canActivate: [
canAccessFeature(FeatureFlag.NewDeviceVerification),
unauthGuardFn(),
activeAuthGuard(),
],
children: [{ path: "", component: NewDeviceVerificationComponent }],
data: {
pageIcon: DeviceVerificationIcon,
pageTitle: {
key: "verifyIdentity",
},
pageSubtitle: {
key: "weDontRecognizeThisDevice",
},
showBackButton: true,
elevation: 1,
} satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData,
},
{
path: "set-password",
component: SetPasswordComponent,

View File

@@ -1,19 +1,21 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthenticationTimeoutComponent } from "@bitwarden/angular/auth/components/authentication-timeout.component";
import {
DesktopDefaultOverlayPosition,
EnvironmentSelectorComponent,
} from "@bitwarden/angular/auth/components/environment-selector.component";
import { TwoFactorTimeoutComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-expired.component";
import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
authGuard,
lockGuard,
activeAuthGuard,
redirectGuard,
tdeDecryptionRequiredGuard,
unauthGuardFn,
} from "@bitwarden/angular/auth/guards";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap";
import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards";
import {
@@ -37,7 +39,10 @@ import {
DevicesIcon,
SsoComponent,
TwoFactorTimeoutIcon,
NewDeviceVerificationComponent,
DeviceVerificationIcon,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { LockComponent } from "@bitwarden/key-management/angular";
import {
NewDeviceVerificationNoticePageOneComponent,
@@ -97,12 +102,12 @@ const routes: Routes = [
},
),
{
path: "2fa-timeout",
path: "authentication-timeout",
component: AnonLayoutWrapperComponent,
children: [
{
path: "",
component: TwoFactorTimeoutComponent,
component: AuthenticationTimeoutComponent,
},
],
data: {
@@ -112,6 +117,25 @@ const routes: Routes = [
},
} satisfies RouteDataProperties & AnonLayoutWrapperData,
},
{
path: "device-verification",
component: AnonLayoutWrapperComponent,
canActivate: [
canAccessFeature(FeatureFlag.NewDeviceVerification),
unauthGuardFn(),
activeAuthGuard(),
],
children: [{ path: "", component: NewDeviceVerificationComponent }],
data: {
pageIcon: DeviceVerificationIcon,
pageTitle: {
key: "verifyIdentity",
},
pageSubtitle: {
key: "weDontRecognizeThisDevice",
},
} satisfies RouteDataProperties & AnonLayoutWrapperData,
},
{ path: "register", component: RegisterComponent },
{
path: "new-device-notice",

View File

@@ -885,6 +885,15 @@
"message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.",
"description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated."
},
"verifyIdentity": {
"message": "Verify your Identity"
},
"weDontRecognizeThisDevice": {
"message": "We don't recognize this device. Enter the code sent to your email to verify your identity."
},
"continueLoggingIn": {
"message": "Continue logging in"
},
"webAuthnTitle": {
"message": "FIDO2 WebAuthn"
},

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { Route, RouterModule, Routes } from "@angular/router";
import { TwoFactorTimeoutComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-expired.component";
import { AuthenticationTimeoutComponent } from "@bitwarden/angular/auth/components/authentication-timeout.component";
import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
authGuard,
@@ -9,7 +9,9 @@ import {
redirectGuard,
tdeDecryptionRequiredGuard,
unauthGuardFn,
activeAuthGuard,
} from "@bitwarden/angular/auth/guards";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import { generatorSwap } from "@bitwarden/angular/tools/generator/generator-swap";
import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap";
import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards";
@@ -37,7 +39,10 @@ import {
SsoComponent,
VaultIcon,
LoginDecryptionOptionsComponent,
NewDeviceVerificationComponent,
DeviceVerificationIcon,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { LockComponent } from "@bitwarden/key-management/angular";
import {
NewDeviceVerificationNoticePageOneComponent,
@@ -538,12 +543,12 @@ const routes: Routes = [
} satisfies RouteDataProperties & AnonLayoutWrapperData,
},
{
path: "2fa-timeout",
path: "authentication-timeout",
canActivate: [unauthGuardFn()],
children: [
{
path: "",
component: TwoFactorTimeoutComponent,
component: AuthenticationTimeoutComponent,
},
{
path: "",
@@ -580,6 +585,29 @@ const routes: Routes = [
titleId: "recoverAccountTwoStep",
} satisfies RouteDataProperties & AnonLayoutWrapperData,
},
{
path: "device-verification",
canActivate: [
canAccessFeature(FeatureFlag.NewDeviceVerification),
unauthGuardFn(),
activeAuthGuard(),
],
children: [
{
path: "",
component: NewDeviceVerificationComponent,
},
],
data: {
pageIcon: DeviceVerificationIcon,
pageTitle: {
key: "verifyIdentity",
},
pageSubtitle: {
key: "weDontRecognizeThisDevice",
},
} satisfies RouteDataProperties & AnonLayoutWrapperData,
},
{
path: "accept-emergency",
canActivate: [deepLinkGuard()],

View File

@@ -1182,6 +1182,12 @@
"verifyIdentity": {
"message": "Verify your Identity"
},
"weDontRecognizeThisDevice": {
"message": "We don't recognize this device. Enter the code sent to your email to verify your identity."
},
"continueLoggingIn": {
"message": "Continue logging in"
},
"whatIsADevice": {
"message": "What is a device?"
},