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:
@@ -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 = [
|
||||
|
||||
@@ -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") &&
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -498,6 +498,7 @@ const safeProviders: SafeProvider[] = [
|
||||
VaultTimeoutSettingsService,
|
||||
KdfConfigService,
|
||||
TaskSchedulerService,
|
||||
ConfigService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
Reference in New Issue
Block a user