diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 2e1dc7952d..4e3f7b7abc 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2327,6 +2327,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "resetPasswordPolicyAutoEnroll": { "message": "Automatic enrollment" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 868367cf18..afd5cdc4cb 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2015,6 +2015,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + }, "tryAgain": { "message": "Try again" }, diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index 1abaa309f4..0bfe1d0dc8 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -86,6 +86,9 @@ export class EventService { case EventType.User_RequestedDeviceApproval: msg = humanReadableMsg = this.i18nService.t("requestedDeviceApproval"); break; + case EventType.User_TdeOffboardingPasswordSet: + msg = humanReadableMsg = this.i18nService.t("tdeOffboardingPasswordSet"); + break; // Cipher case EventType.Cipher_Created: msg = this.i18nService.t("createdItemId", this.formatCipherId(ev, options)); diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index b4922df885..2f4350718a 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5322,6 +5322,9 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "tdeDisabledMasterPasswordRequired": { + "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + }, "maximumVaultTimeout": { "message": "Vault timeout" }, @@ -7674,6 +7677,9 @@ "requestedDeviceApproval": { "message": "Requested device approval." }, + "tdeOffboardingPasswordSet": { + "message": "User set a master password during TDE offboarding." + }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Start your 7-Day free trial of Bitwarden for $ORG$", "placeholders": { diff --git a/libs/angular/src/auth/components/sso.component.spec.ts b/libs/angular/src/auth/components/sso.component.spec.ts index dff6268f39..8584abeeb8 100644 --- a/libs/angular/src/auth/components/sso.component.spec.ts +++ b/libs/angular/src/auth/components/sso.component.spec.ts @@ -154,12 +154,12 @@ describe("SsoComponent", () => { }), withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({ hasMasterPassword: true, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false), keyConnectorOption: undefined, }), withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({ hasMasterPassword: true, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false), keyConnectorOption: undefined, }), withMasterPasswordAndKeyConnector: new UserDecryptionOptions({ @@ -169,12 +169,12 @@ describe("SsoComponent", () => { }), noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({ hasMasterPassword: false, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false), keyConnectorOption: undefined, }), noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({ hasMasterPassword: false, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false), keyConnectorOption: undefined, }), noMasterPasswordWithKeyConnector: new UserDecryptionOptions({ diff --git a/libs/angular/src/auth/components/sso.component.ts b/libs/angular/src/auth/components/sso.component.ts index 42119be360..bb4a4902d5 100644 --- a/libs/angular/src/auth/components/sso.component.ts +++ b/libs/angular/src/auth/components/sso.component.ts @@ -287,8 +287,18 @@ export class SsoComponent { orgIdentifier: string, userDecryptionOpts: UserDecryptionOptions, ): Promise { - // If user doesn't have a MP, but has reset password permission, they must set a MP + // Tde offboarding takes precedence if ( + !userDecryptionOpts.hasMasterPassword && + userDecryptionOpts.trustedDeviceOption.isTdeOffboarding + ) { + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + await this.masterPasswordService.setForceSetPasswordReason( + ForceSetPasswordReason.TdeOffboarding, + userId, + ); + } else if ( + // If user doesn't have a MP, but has reset password permission, they must set a MP !userDecryptionOpts.hasMasterPassword && userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission ) { diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts index 04532f7fcb..489ca5c178 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts @@ -127,12 +127,12 @@ describe("TwoFactorComponent", () => { }), withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({ hasMasterPassword: true, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false), keyConnectorOption: undefined, }), withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({ hasMasterPassword: true, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false), keyConnectorOption: undefined, }), withMasterPasswordAndKeyConnector: new UserDecryptionOptions({ @@ -142,12 +142,12 @@ describe("TwoFactorComponent", () => { }), noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({ hasMasterPassword: false, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false), keyConnectorOption: undefined, }), noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({ hasMasterPassword: false, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false), keyConnectorOption: undefined, }), noMasterPasswordWithKeyConnector: new UserDecryptionOptions({ diff --git a/libs/angular/src/auth/components/two-factor.component.spec.ts b/libs/angular/src/auth/components/two-factor.component.spec.ts index 3325f3bc32..e21d119adf 100644 --- a/libs/angular/src/auth/components/two-factor.component.spec.ts +++ b/libs/angular/src/auth/components/two-factor.component.spec.ts @@ -120,12 +120,12 @@ describe("TwoFactorComponent", () => { }), withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({ hasMasterPassword: true, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false), keyConnectorOption: undefined, }), withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({ hasMasterPassword: true, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false), keyConnectorOption: undefined, }), withMasterPasswordAndKeyConnector: new UserDecryptionOptions({ @@ -135,12 +135,12 @@ describe("TwoFactorComponent", () => { }), noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({ hasMasterPassword: false, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false), keyConnectorOption: undefined, }), noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({ hasMasterPassword: false, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true), + trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false), keyConnectorOption: undefined, }), noMasterPasswordWithKeyConnector: new UserDecryptionOptions({ diff --git a/libs/angular/src/auth/components/update-temp-password.component.ts b/libs/angular/src/auth/components/update-temp-password.component.ts index bb9c23b1cc..ec275c876c 100644 --- a/libs/angular/src/auth/components/update-temp-password.component.ts +++ b/libs/angular/src/auth/components/update-temp-password.component.ts @@ -12,6 +12,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; +import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request"; import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -97,9 +98,13 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent { } get masterPasswordWarningText(): string { - return this.reason == ForceSetPasswordReason.WeakMasterPassword - ? this.i18nService.t("updateWeakMasterPasswordWarning") - : this.i18nService.t("updateMasterPasswordWarning"); + if (this.reason == ForceSetPasswordReason.WeakMasterPassword) { + return this.i18nService.t("weakMasterPasswordWarning"); + } else if (this.reason == ForceSetPasswordReason.TdeOffboarding) { + return this.i18nService.t("tdeDisabledMasterPasswordRequired"); + } else { + return this.i18nService.t("masterPasswordWarning"); + } } togglePassword(confirmField: boolean) { @@ -165,6 +170,9 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent { case ForceSetPasswordReason.WeakMasterPassword: this.formPromise = this.updatePassword(masterPasswordHash, userKey); break; + case ForceSetPasswordReason.TdeOffboarding: + this.formPromise = this.updateTdeOffboardingPassword(masterPasswordHash, userKey); + break; } await this.formPromise; @@ -211,4 +219,16 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent { return this.apiService.postPassword(request); } + + private async updateTdeOffboardingPassword( + masterPasswordHash: string, + userKey: [UserKey, EncString], + ) { + const request = new UpdateTdeOffboardingPasswordRequest(); + request.key = userKey[1].encryptedString; + request.newMasterPasswordHash = masterPasswordHash; + request.masterPasswordHint = this.hint; + + return this.apiService.putUpdateTdeOffboardingPassword(request); + } } diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index 885cad014c..82169a19d1 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -211,6 +211,7 @@ describe("SsoLoginStrategy", () => { HasAdminApproval: true, HasLoginApprovingDevice: true, HasManageResetPasswordPermission: false, + IsTdeOffboarding: false, EncryptedPrivateKey: mockEncDevicePrivateKey, EncryptedUserKey: mockEncUserKey, }, @@ -343,6 +344,7 @@ describe("SsoLoginStrategy", () => { HasAdminApproval: true, HasLoginApprovingDevice: false, HasManageResetPasswordPermission: false, + IsTdeOffboarding: false, EncryptedPrivateKey: mockEncDevicePrivateKey, EncryptedUserKey: mockEncUserKey, }, diff --git a/libs/auth/src/common/models/domain/user-decryption-options.ts b/libs/auth/src/common/models/domain/user-decryption-options.ts index ca4046f36e..a4e61f5dac 100644 --- a/libs/auth/src/common/models/domain/user-decryption-options.ts +++ b/libs/auth/src/common/models/domain/user-decryption-options.ts @@ -54,6 +54,8 @@ export class TrustedDeviceUserDecryptionOption { hasLoginApprovingDevice: boolean; /** True if the user has manage reset password permission, as these users must be forced to have a master password. */ hasManageResetPasswordPermission: boolean; + /** True if tde is disabled but user has not set a master password yet. */ + isTdeOffboarding: boolean; /** * Initializes a new instance of the TrustedDeviceUserDecryptionOption from a response object. @@ -70,6 +72,7 @@ export class TrustedDeviceUserDecryptionOption { options.hasAdminApproval = response?.hasAdminApproval ?? false; options.hasLoginApprovingDevice = response?.hasLoginApprovingDevice ?? false; options.hasManageResetPasswordPermission = response?.hasManageResetPasswordPermission ?? false; + options.isTdeOffboarding = response?.isTdeOffboarding ?? false; return options; } diff --git a/libs/auth/src/common/models/spec/fake-user-decryption-options.ts b/libs/auth/src/common/models/spec/fake-user-decryption-options.ts index fe4a1203c6..4b43d12ffa 100644 --- a/libs/auth/src/common/models/spec/fake-user-decryption-options.ts +++ b/libs/auth/src/common/models/spec/fake-user-decryption-options.ts @@ -29,10 +29,12 @@ export class FakeTrustedDeviceUserDecryptionOption extends TrustedDeviceUserDecr hasAdminApproval: boolean, hasLoginApprovingDevice: boolean, hasManageResetPasswordPermission: boolean, + isTdeOffboarding: boolean, ) { super(); this.hasAdminApproval = hasAdminApproval; this.hasLoginApprovingDevice = hasLoginApprovingDevice; this.hasManageResetPasswordPermission = hasManageResetPasswordPermission; + this.isTdeOffboarding = isTdeOffboarding; } } diff --git a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.spec.ts b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.spec.ts index ae1813d3d7..c2fafc1a2f 100644 --- a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.spec.ts +++ b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.spec.ts @@ -8,6 +8,8 @@ import { } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; +import { UserDecryptionOptions } from "../../models/domain/user-decryption-options"; + import { USER_DECRYPTION_OPTIONS, UserDecryptionOptionsService, @@ -27,12 +29,13 @@ describe("UserDecryptionOptionsService", () => { sut = new UserDecryptionOptionsService(fakeStateProvider); }); - const userDecryptionOptions = { + const userDecryptionOptions: UserDecryptionOptions = { hasMasterPassword: true, trustedDeviceOption: { hasAdminApproval: false, hasLoginApprovingDevice: false, hasManageResetPasswordPermission: true, + isTdeOffboarding: false, }, keyConnectorOption: { keyConnectorUrl: "https://keyconnector.bitwarden.com", diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 465f3f4348..dc68a9f79a 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -48,6 +48,7 @@ import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.r import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request"; import { TwoFactorRecoveryRequest } from "../auth/models/request/two-factor-recovery.request"; import { UpdateProfileRequest } from "../auth/models/request/update-profile.request"; +import { UpdateTdeOffboardingPasswordRequest } from "../auth/models/request/update-tde-offboarding-password.request"; import { UpdateTempPasswordRequest } from "../auth/models/request/update-temp-password.request"; import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request"; import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request"; @@ -181,6 +182,7 @@ export abstract class ApiService { postUserApiKey: (id: string, request: SecretVerificationRequest) => Promise; postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise; putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise; + putUpdateTdeOffboardingPassword: (request: UpdateTdeOffboardingPasswordRequest) => Promise; postConvertToKeyConnector: () => Promise; //passwordless postAuthRequest: (request: CreateAuthRequest) => Promise; diff --git a/libs/common/src/auth/models/domain/force-set-password-reason.ts b/libs/common/src/auth/models/domain/force-set-password-reason.ts index a6b407d2f1..011ef7fff8 100644 --- a/libs/common/src/auth/models/domain/force-set-password-reason.ts +++ b/libs/common/src/auth/models/domain/force-set-password-reason.ts @@ -26,4 +26,9 @@ export enum ForceSetPasswordReason { * Set post login & decryption client side and by server in sync (to catch logged in users). */ TdeUserWithoutPasswordHasPasswordResetPermission, + + /** + * Occurs when TDE is disabled and master password has to be set. + */ + TdeOffboarding, } diff --git a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts new file mode 100644 index 0000000000..0f6a8c3911 --- /dev/null +++ b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts @@ -0,0 +1,5 @@ +import { OrganizationUserResetPasswordRequest } from "../../../admin-console/abstractions/organization-user/requests"; + +export class UpdateTdeOffboardingPasswordRequest extends OrganizationUserResetPasswordRequest { + masterPasswordHint: string; +} diff --git a/libs/common/src/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response.ts b/libs/common/src/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response.ts index b42d3eb970..88e1f3a716 100644 --- a/libs/common/src/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response.ts +++ b/libs/common/src/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response.ts @@ -5,6 +5,7 @@ export interface ITrustedDeviceUserDecryptionOptionServerResponse { HasAdminApproval: boolean; HasLoginApprovingDevice: boolean; HasManageResetPasswordPermission: boolean; + IsTdeOffboarding: boolean; EncryptedPrivateKey?: string; EncryptedUserKey?: string; } @@ -13,6 +14,7 @@ export class TrustedDeviceUserDecryptionOptionResponse extends BaseResponse { hasAdminApproval: boolean; hasLoginApprovingDevice: boolean; hasManageResetPasswordPermission: boolean; + isTdeOffboarding: boolean; encryptedPrivateKey: EncString; encryptedUserKey: EncString; @@ -25,6 +27,8 @@ export class TrustedDeviceUserDecryptionOptionResponse extends BaseResponse { "HasManageResetPasswordPermission", ); + this.isTdeOffboarding = this.getResponseProperty("IsTdeOffboarding"); + if (response.EncryptedPrivateKey) { this.encryptedPrivateKey = new EncString(this.getResponseProperty("EncryptedPrivateKey")); } diff --git a/libs/common/src/enums/event-type.enum.ts b/libs/common/src/enums/event-type.enum.ts index 853b07ab25..c72fb80de4 100644 --- a/libs/common/src/enums/event-type.enum.ts +++ b/libs/common/src/enums/event-type.enum.ts @@ -11,6 +11,7 @@ export enum EventType { User_UpdatedTempPassword = 1008, User_MigratedKeyToKeyConnector = 1009, User_RequestedDeviceApproval = 1010, + User_TdeOffboardingPasswordSet = 1011, Cipher_Created = 1100, Cipher_Updated = 1101, diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 28be70221f..03ea402363 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -57,6 +57,7 @@ import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.r import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request"; import { TwoFactorRecoveryRequest } from "../auth/models/request/two-factor-recovery.request"; import { UpdateProfileRequest } from "../auth/models/request/update-profile.request"; +import { UpdateTdeOffboardingPasswordRequest } from "../auth/models/request/update-tde-offboarding-password.request"; import { UpdateTempPasswordRequest } from "../auth/models/request/update-temp-password.request"; import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request"; import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request"; @@ -461,6 +462,10 @@ export class ApiService implements ApiServiceAbstraction { return this.send("PUT", "/accounts/update-temp-password", request, true, false); } + putUpdateTdeOffboardingPassword(request: UpdateTdeOffboardingPasswordRequest): Promise { + return this.send("PUT", "/accounts/update-tde-offboarding-password", request, true, false); + } + postConvertToKeyConnector(): Promise { return this.send("POST", "/accounts/convert-to-key-connector", null, true, false); }