mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 08:43:33 +00:00
[PM-19212] Consolidate password set routing to AuthGuard using ForceSetPasswordReason (#14356)
* Consolidates component routing, removing routing to update-temp-password from components. All routing to update-temp-password should happen in the AuthGuard now. --------- Co-authored-by: Jared Snider <jsnider@bitwarden.com> Co-authored-by: Todd Martin <tmartin@bitwarden.com>
This commit is contained in:
@@ -4,8 +4,6 @@ import { Utils } from "../../../platform/misc/utils";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
||||
|
||||
import { ForceSetPasswordReason } from "./force-set-password-reason";
|
||||
|
||||
export class AuthResult {
|
||||
userId: UserId;
|
||||
captchaSiteKey = "";
|
||||
@@ -17,7 +15,6 @@ export class AuthResult {
|
||||
* */
|
||||
resetMasterPassword = false;
|
||||
|
||||
forcePasswordReset: ForceSetPasswordReason = ForceSetPasswordReason.None;
|
||||
twoFactorProviders: Partial<Record<TwoFactorProviderType, Record<string, string>>> = null;
|
||||
ssoEmail2FaSessionToken?: string;
|
||||
email: string;
|
||||
|
||||
@@ -31,4 +31,9 @@ export enum ForceSetPasswordReason {
|
||||
* Occurs when TDE is disabled and master password has to be set.
|
||||
*/
|
||||
TdeOffboarding,
|
||||
|
||||
/**
|
||||
* Occurs when a new SSO user is JIT provisioned and needs to set their master password.
|
||||
*/
|
||||
SsoNewJitProvisionedUser,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
import * as rxjs from "rxjs";
|
||||
|
||||
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
|
||||
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { StateService } from "../../../platform/abstractions/state.service";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { EncryptService } from "../../crypto/abstractions/encrypt.service";
|
||||
|
||||
import { MasterPasswordService } from "./master-password.service";
|
||||
|
||||
describe("MasterPasswordService", () => {
|
||||
let sut: MasterPasswordService;
|
||||
|
||||
let stateProvider: MockProxy<StateProvider>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let keyGenerationService: MockProxy<KeyGenerationService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
|
||||
const userId = "user-id" as UserId;
|
||||
const mockUserState = {
|
||||
state$: of(null),
|
||||
update: jest.fn().mockResolvedValue(null),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
stateProvider = mock<StateProvider>();
|
||||
stateService = mock<StateService>();
|
||||
keyGenerationService = mock<KeyGenerationService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
logService = mock<LogService>();
|
||||
|
||||
stateProvider.getUser.mockReturnValue(mockUserState as any);
|
||||
|
||||
mockUserState.update.mockReset();
|
||||
|
||||
sut = new MasterPasswordService(
|
||||
stateProvider,
|
||||
stateService,
|
||||
keyGenerationService,
|
||||
encryptService,
|
||||
logService,
|
||||
);
|
||||
});
|
||||
|
||||
describe("setForceSetPasswordReason", () => {
|
||||
it("calls stateProvider with the provided reason and user ID", async () => {
|
||||
const reason = ForceSetPasswordReason.WeakMasterPassword;
|
||||
|
||||
await sut.setForceSetPasswordReason(reason, userId);
|
||||
|
||||
expect(stateProvider.getUser).toHaveBeenCalled();
|
||||
expect(mockUserState.update).toHaveBeenCalled();
|
||||
|
||||
// Call the update function to verify it returns the correct reason
|
||||
const updateFn = mockUserState.update.mock.calls[0][0];
|
||||
expect(updateFn(null)).toBe(reason);
|
||||
});
|
||||
|
||||
it("throws an error if reason is null", async () => {
|
||||
await expect(
|
||||
sut.setForceSetPasswordReason(null as unknown as ForceSetPasswordReason, userId),
|
||||
).rejects.toThrow("Reason is required.");
|
||||
});
|
||||
|
||||
it("throws an error if user ID is null", async () => {
|
||||
await expect(
|
||||
sut.setForceSetPasswordReason(ForceSetPasswordReason.None, null as unknown as UserId),
|
||||
).rejects.toThrow("User ID is required.");
|
||||
});
|
||||
|
||||
it("does not overwrite AdminForcePasswordReset with other reasons except None", async () => {
|
||||
jest
|
||||
.spyOn(sut, "forceSetPasswordReason$")
|
||||
.mockReturnValue(of(ForceSetPasswordReason.AdminForcePasswordReset));
|
||||
|
||||
jest
|
||||
.spyOn(rxjs, "firstValueFrom")
|
||||
.mockResolvedValue(ForceSetPasswordReason.AdminForcePasswordReset);
|
||||
|
||||
await sut.setForceSetPasswordReason(ForceSetPasswordReason.WeakMasterPassword, userId);
|
||||
|
||||
expect(mockUserState.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows overwriting AdminForcePasswordReset with None", async () => {
|
||||
jest
|
||||
.spyOn(sut, "forceSetPasswordReason$")
|
||||
.mockReturnValue(of(ForceSetPasswordReason.AdminForcePasswordReset));
|
||||
|
||||
jest
|
||||
.spyOn(rxjs, "firstValueFrom")
|
||||
.mockResolvedValue(ForceSetPasswordReason.AdminForcePasswordReset);
|
||||
|
||||
await sut.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
|
||||
|
||||
expect(mockUserState.update).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -148,6 +148,17 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
|
||||
if (userId == null) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
|
||||
// Don't overwrite AdminForcePasswordReset with any other reasons other than None
|
||||
// as we must allow a reset when the user has completed admin account recovery
|
||||
const currentReason = await firstValueFrom(this.forceSetPasswordReason$(userId));
|
||||
if (
|
||||
currentReason === ForceSetPasswordReason.AdminForcePasswordReset &&
|
||||
reason !== ForceSetPasswordReason.None
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.stateProvider.getUser(userId, FORCE_SET_PASSWORD_REASON).update((_) => reason);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user