1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 23:03:32 +00:00

fix(tde-offboarding): Auth/PM-19165 - Handle TDE offboarding on an untrusted device with warning message (#15430)

When a user logs in via SSO after their org has offboarded from TDE, we now show them a helpful error message stating that they must either login on a Trusted device, or ask their admin to assign them a password.

Feature flag: `PM16117_SetInitialPasswordRefactor`
This commit is contained in:
Jared Snider
2025-07-08 12:58:03 -04:00
committed by GitHub
parent 3da58e1752
commit b9f930a609
17 changed files with 257 additions and 41 deletions

View File

@@ -70,10 +70,10 @@ describe("AuthGuard", () => {
{ path: "lock", component: EmptyComponent },
{ path: "set-password", component: EmptyComponent },
{ path: "set-password-jit", component: EmptyComponent },
{ path: "set-initial-password", component: EmptyComponent },
{ path: "update-temp-password", component: EmptyComponent },
{ path: "set-initial-password", component: EmptyComponent, canActivate: [authGuard] },
{ path: "update-temp-password", component: EmptyComponent, canActivate: [authGuard] },
{ path: "change-password", component: EmptyComponent },
{ path: "remove-password", component: EmptyComponent },
{ path: "remove-password", component: EmptyComponent, canActivate: [authGuard] },
]),
],
providers: [
@@ -124,6 +124,34 @@ describe("AuthGuard", () => {
expect(router.url).toBe("/remove-password");
});
describe("given user is Locked", () => {
describe("given the PM16117_SetInitialPasswordRefactor feature flag is ON", () => {
it("should redirect to /set-initial-password when the user has ForceSetPasswordReaason.TdeOffboardingUntrustedDevice", async () => {
const { router } = setup(
AuthenticationStatus.Locked,
ForceSetPasswordReason.TdeOffboardingUntrustedDevice,
false,
FeatureFlag.PM16117_SetInitialPasswordRefactor,
);
await router.navigate(["guarded-route"]);
expect(router.url).toBe("/set-initial-password");
});
it("should allow navigation to continue to /set-initial-password when the user has ForceSetPasswordReason.TdeOffboardingUntrustedDevice", async () => {
const { router } = setup(
AuthenticationStatus.Unlocked,
ForceSetPasswordReason.TdeOffboardingUntrustedDevice,
false,
FeatureFlag.PM16117_SetInitialPasswordRefactor,
);
await router.navigate(["/set-initial-password"]);
expect(router.url).toContain("/set-initial-password");
});
});
});
describe("given user is Unlocked", () => {
describe("given the PM16117_SetInitialPasswordRefactor feature flag is ON", () => {
const tests = [

View File

@@ -61,9 +61,22 @@ export const authGuard: CanActivateFn = async (
return router.createUrlTree(["/set-initial-password"]);
}
// TDE Offboarding on untrusted device
if (
authStatus === AuthenticationStatus.Locked &&
forceSetPasswordReason !== ForceSetPasswordReason.SsoNewJitProvisionedUser
forceSetPasswordReason === ForceSetPasswordReason.TdeOffboardingUntrustedDevice &&
!routerState.url.includes("set-initial-password") &&
isSetInitialPasswordFlagOn
) {
return router.createUrlTree(["/set-initial-password"]);
}
// We must add exemptions for the SsoNewJitProvisionedUser and TdeOffboardingUntrustedDevice scenarios as
// the "set-initial-password" route is guarded by the authGuard.
if (
authStatus === AuthenticationStatus.Locked &&
forceSetPasswordReason !== ForceSetPasswordReason.SsoNewJitProvisionedUser &&
forceSetPasswordReason !== ForceSetPasswordReason.TdeOffboardingUntrustedDevice
) {
if (routerState != null) {
messagingService.send("lockedUrl", { url: routerState.url });
@@ -91,7 +104,7 @@ export const authGuard: CanActivateFn = async (
return router.createUrlTree([route]);
}
// TDE Offboarding
// TDE Offboarding on trusted device
if (
forceSetPasswordReason === ForceSetPasswordReason.TdeOffboarding &&
!routerState.url.includes("update-temp-password") &&

View File

@@ -7,28 +7,38 @@
></i>
</div>
} @else {
<bit-callout
*ngIf="resetPasswordAutoEnroll"
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</bit-callout>
@if (userType === SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER_UNTRUSTED_DEVICE) {
<div class="tw-mt-4"></div>
<bit-callout type="warning">
{{ "loginOnTrustedDeviceOrAskAdminToAssignPassword" | i18n }}
</bit-callout>
<button type="button" bitButton block buttonType="secondary" (click)="logout()">
{{ "logOut" | i18n }}
</button>
} @else {
<bit-callout
*ngIf="resetPasswordAutoEnroll"
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</bit-callout>
<auth-input-password
[flow]="inputPasswordFlow"
[email]="email"
[userId]="userId"
[loading]="submitting"
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
[primaryButtonText]="{
key:
userType === SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER
? 'setPassword'
: 'createAccount',
}"
[secondaryButtonText]="{ key: 'logOut' }"
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
(onSecondaryButtonClick)="logout()"
></auth-input-password>
<auth-input-password
[flow]="inputPasswordFlow"
[email]="email"
[userId]="userId"
[loading]="submitting"
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
[primaryButtonText]="{
key:
userType === SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER
? 'setPassword'
: 'createAccount',
}"
[secondaryButtonText]="{ key: 'logOut' }"
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
(onSecondaryButtonClick)="logout()"
></auth-input-password>
}
}

View File

@@ -1,6 +1,7 @@
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
// import { NoAccess } from "libs/components/src/icon/icons";
import { firstValueFrom } from "rxjs";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
@@ -30,9 +31,11 @@ import { SyncService } from "@bitwarden/common/platform/sync";
import { UserId } from "@bitwarden/common/types/guid";
import {
AnonLayoutWrapperDataService,
ButtonModule,
CalloutComponent,
DialogService,
ToastService,
Icons,
} from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
@@ -46,7 +49,7 @@ import {
@Component({
standalone: true,
templateUrl: "set-initial-password.component.html",
imports: [CalloutComponent, CommonModule, InputPasswordComponent, I18nPipe],
imports: [ButtonModule, CalloutComponent, CommonModule, InputPasswordComponent, I18nPipe],
})
export class SetInitialPasswordComponent implements OnInit {
protected inputPasswordFlow = InputPasswordFlow.SetInitialPasswordAuthedUser;
@@ -106,6 +109,14 @@ export class SetInitialPasswordComponent implements OnInit {
this.masterPasswordService.forceSetPasswordReason$(this.userId),
);
if (this.forceSetPasswordReason === ForceSetPasswordReason.TdeOffboardingUntrustedDevice) {
this.userType = SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER_UNTRUSTED_DEVICE;
this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
pageTitle: { key: "unableToCompleteLogin" },
pageIcon: Icons.NoAccess,
});
}
if (this.forceSetPasswordReason === ForceSetPasswordReason.SsoNewJitProvisionedUser) {
this.userType = SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER;
this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({

View File

@@ -22,9 +22,15 @@ export const _SetInitialPasswordUserType = {
/**
* A user in an org that offboarded from trusted device encryption and is now a
* master-password-encryption org
* master-password-encryption org. User is on a trusted device.
*/
OFFBOARDED_TDE_ORG_USER: "offboarded_tde_org_user",
/**
* A user in an org that offboarded from trusted device encryption and is now a
* master-password-encryption org. User is on an untrusted device.
*/
OFFBOARDED_TDE_ORG_USER_UNTRUSTED_DEVICE: "offboarded_tde_org_user_untrusted_device",
} as const;
type _SetInitialPasswordUserType = typeof _SetInitialPasswordUserType;

View File

@@ -498,6 +498,7 @@ const safeProviders: SafeProvider[] = [
VaultTimeoutSettingsService,
KdfConfigService,
TaskSchedulerService,
ConfigService,
],
}),
safeProvider({