mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[PM-5362]Create MP Service for state provider migration (#7623)
* create mp and kdf service * update mp service interface to not rely on active user * rename observable methods * update crypto service with new MP service * add master password service to login strategies - make fake service for easier testing - fix crypto service tests * update auth service and finish strategies * auth request refactors * more service refactors and constructor updates * setMasterKey refactors * remove master key methods from crypto service * remove master key and hash from state service * missed fixes * create migrations and fix references * fix master key imports * default force set password reason to none * add password reset reason observable factory to service * remove kdf changes and migrate only disk data * update migration number * fix sync service deps * use disk for force set password state * fix desktop migration * fix sso test * fix tests * fix more tests * fix even more tests * fix even more tests * fix cli * remove kdf service abstraction * add missing deps for browser * fix merge conflicts * clear reset password reason on lock or logout * fix tests * fix other tests * add jsdocs to abstraction * use state provider in crypto service * inverse master password service factory * add clearOn to master password service * add parameter validation to master password service * add component level userId * add missed userId * migrate key hash * fix login strategy service * delete crypto master key from account * migrate master key encrypted user key * rename key hash to master key hash * use mp service for getMasterKeyEncryptedUserKey * fix tests
This commit is contained in:
@@ -5,6 +5,7 @@ import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abst
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
@@ -14,7 +15,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||
@@ -42,6 +45,10 @@ describe("AuthRequestLoginStrategy", () => {
|
||||
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
let accountService: FakeAccountService;
|
||||
let masterPasswordService: FakeMasterPasswordService;
|
||||
|
||||
let authRequestLoginStrategy: AuthRequestLoginStrategy;
|
||||
let credentials: AuthRequestLoginCredentials;
|
||||
let tokenResponse: IdentityTokenResponse;
|
||||
@@ -71,12 +78,17 @@ describe("AuthRequestLoginStrategy", () => {
|
||||
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
masterPasswordService = new FakeMasterPasswordService();
|
||||
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.decodeAccessToken.mockResolvedValue({});
|
||||
|
||||
authRequestLoginStrategy = new AuthRequestLoginStrategy(
|
||||
cache,
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -108,13 +120,16 @@ describe("AuthRequestLoginStrategy", () => {
|
||||
const masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
|
||||
const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
|
||||
|
||||
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
|
||||
|
||||
await authRequestLoginStrategy.logIn(credentials);
|
||||
|
||||
expect(cryptoService.setMasterKey).toHaveBeenCalledWith(masterKey);
|
||||
expect(cryptoService.setMasterKeyHash).toHaveBeenCalledWith(decMasterKeyHash);
|
||||
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(masterKey, mockUserId);
|
||||
expect(masterPasswordService.mock.setMasterKeyHash).toHaveBeenCalledWith(
|
||||
decMasterKeyHash,
|
||||
mockUserId,
|
||||
);
|
||||
expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key);
|
||||
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey);
|
||||
expect(deviceTrustCryptoService.trustDeviceIfRequired).toHaveBeenCalled();
|
||||
@@ -136,8 +151,8 @@ describe("AuthRequestLoginStrategy", () => {
|
||||
await authRequestLoginStrategy.logIn(credentials);
|
||||
|
||||
// setMasterKey and setMasterKeyHash should not be called
|
||||
expect(cryptoService.setMasterKey).not.toHaveBeenCalled();
|
||||
expect(cryptoService.setMasterKeyHash).not.toHaveBeenCalled();
|
||||
expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled();
|
||||
expect(masterPasswordService.mock.setMasterKeyHash).not.toHaveBeenCalled();
|
||||
|
||||
// setMasterKeyEncryptedUserKey, setUserKey, and setPrivateKey should still be called
|
||||
expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Observable, map, BehaviorSubject } from "rxjs";
|
||||
import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
@@ -47,6 +49,8 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||
|
||||
constructor(
|
||||
data: AuthRequestLoginStrategyData,
|
||||
accountService: AccountService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
@@ -61,6 +65,8 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -114,8 +120,15 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||
authRequestCredentials.decryptedMasterKey &&
|
||||
authRequestCredentials.decryptedMasterKeyHash
|
||||
) {
|
||||
await this.cryptoService.setMasterKey(authRequestCredentials.decryptedMasterKey);
|
||||
await this.cryptoService.setMasterKeyHash(authRequestCredentials.decryptedMasterKeyHash);
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
await this.masterPasswordService.setMasterKey(
|
||||
authRequestCredentials.decryptedMasterKey,
|
||||
userId,
|
||||
);
|
||||
await this.masterPasswordService.setMasterKeyHash(
|
||||
authRequestCredentials.decryptedMasterKeyHash,
|
||||
userId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +150,8 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||
}
|
||||
|
||||
private async trySetUserKeyWithMasterKey(): Promise<void> {
|
||||
const masterKey = await this.cryptoService.getMasterKey();
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
if (masterKey) {
|
||||
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
|
||||
await this.cryptoService.setUserKey(userKey);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id
|
||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
|
||||
import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
|
||||
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
@@ -31,11 +32,13 @@ import {
|
||||
} from "@bitwarden/common/platform/models/domain/account";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import {
|
||||
PasswordStrengthServiceAbstraction,
|
||||
PasswordStrengthService,
|
||||
} from "@bitwarden/common/tools/password-strength";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { UserKey, MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
||||
@@ -56,7 +59,7 @@ const privateKey = "PRIVATE_KEY";
|
||||
const captchaSiteKey = "CAPTCHA_SITE_KEY";
|
||||
const kdf = 0;
|
||||
const kdfIterations = 10000;
|
||||
const userId = Utils.newGuid();
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
const masterPasswordHash = "MASTER_PASSWORD_HASH";
|
||||
const name = "NAME";
|
||||
const defaultUserDecryptionOptionsServerResponse: IUserDecryptionOptionsServerResponse = {
|
||||
@@ -98,6 +101,8 @@ export function identityTokenResponseFactory(
|
||||
// TODO: add tests for latest changes to base class for TDE
|
||||
describe("LoginStrategy", () => {
|
||||
let cache: PasswordLoginStrategyData;
|
||||
let accountService: FakeAccountService;
|
||||
let masterPasswordService: FakeMasterPasswordService;
|
||||
|
||||
let loginStrategyService: MockProxy<LoginStrategyServiceAbstraction>;
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
@@ -118,6 +123,9 @@ describe("LoginStrategy", () => {
|
||||
let credentials: PasswordLoginCredentials;
|
||||
|
||||
beforeEach(async () => {
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
masterPasswordService = new FakeMasterPasswordService();
|
||||
|
||||
loginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
@@ -139,6 +147,8 @@ describe("LoginStrategy", () => {
|
||||
// The base class is abstract so we test it via PasswordLoginStrategy
|
||||
passwordLoginStrategy = new PasswordLoginStrategy(
|
||||
cache,
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -241,7 +251,7 @@ describe("LoginStrategy", () => {
|
||||
});
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
|
||||
|
||||
const result = await passwordLoginStrategy.logIn(credentials);
|
||||
@@ -260,7 +270,7 @@ describe("LoginStrategy", () => {
|
||||
cryptoService.makeKeyPair.mockResolvedValue(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]);
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
|
||||
|
||||
await passwordLoginStrategy.logIn(credentials);
|
||||
@@ -382,6 +392,8 @@ describe("LoginStrategy", () => {
|
||||
|
||||
passwordLoginStrategy = new PasswordLoginStrategy(
|
||||
cache,
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
@@ -60,6 +62,8 @@ export abstract class LoginStrategy {
|
||||
protected abstract cache: BehaviorSubject<LoginStrategyData>;
|
||||
|
||||
constructor(
|
||||
protected accountService: AccountService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
protected cryptoService: CryptoService,
|
||||
protected apiService: ApiService,
|
||||
protected tokenService: TokenService,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for
|
||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
|
||||
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
@@ -19,11 +20,13 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
||||
import { HashPurpose } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import {
|
||||
PasswordStrengthServiceAbstraction,
|
||||
PasswordStrengthService,
|
||||
} from "@bitwarden/common/tools/password-strength";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
||||
@@ -42,6 +45,7 @@ const masterKey = new SymmetricCryptoKey(
|
||||
"N2KWjlLpfi5uHjv+YcfUKIpZ1l+W+6HRensmIqD+BFYBf6N/dvFpJfWwYnVBdgFCK2tJTAIMLhqzIQQEUmGFgg==",
|
||||
),
|
||||
) as MasterKey;
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
const deviceId = Utils.newGuid();
|
||||
const masterPasswordPolicy = new MasterPasswordPolicyResponse({
|
||||
EnforceOnLogin: true,
|
||||
@@ -50,6 +54,8 @@ const masterPasswordPolicy = new MasterPasswordPolicyResponse({
|
||||
|
||||
describe("PasswordLoginStrategy", () => {
|
||||
let cache: PasswordLoginStrategyData;
|
||||
let accountService: FakeAccountService;
|
||||
let masterPasswordService: FakeMasterPasswordService;
|
||||
|
||||
let loginStrategyService: MockProxy<LoginStrategyServiceAbstraction>;
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
@@ -71,6 +77,9 @@ describe("PasswordLoginStrategy", () => {
|
||||
let tokenResponse: IdentityTokenResponse;
|
||||
|
||||
beforeEach(async () => {
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
masterPasswordService = new FakeMasterPasswordService();
|
||||
|
||||
loginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
@@ -102,6 +111,8 @@ describe("PasswordLoginStrategy", () => {
|
||||
|
||||
passwordLoginStrategy = new PasswordLoginStrategy(
|
||||
cache,
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -145,13 +156,16 @@ describe("PasswordLoginStrategy", () => {
|
||||
it("sets keys after a successful authentication", async () => {
|
||||
const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
|
||||
|
||||
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
|
||||
|
||||
await passwordLoginStrategy.logIn(credentials);
|
||||
|
||||
expect(cryptoService.setMasterKey).toHaveBeenCalledWith(masterKey);
|
||||
expect(cryptoService.setMasterKeyHash).toHaveBeenCalledWith(localHashedPassword);
|
||||
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(masterKey, userId);
|
||||
expect(masterPasswordService.mock.setMasterKeyHash).toHaveBeenCalledWith(
|
||||
localHashedPassword,
|
||||
userId,
|
||||
);
|
||||
expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key);
|
||||
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey);
|
||||
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey);
|
||||
@@ -183,8 +197,9 @@ describe("PasswordLoginStrategy", () => {
|
||||
const result = await passwordLoginStrategy.logIn(credentials);
|
||||
|
||||
expect(policyService.evaluateMasterPassword).toHaveBeenCalled();
|
||||
expect(stateService.setForceSetPasswordReason).toHaveBeenCalledWith(
|
||||
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
|
||||
ForceSetPasswordReason.WeakMasterPassword,
|
||||
userId,
|
||||
);
|
||||
expect(result.forcePasswordReset).toEqual(ForceSetPasswordReason.WeakMasterPassword);
|
||||
});
|
||||
@@ -222,8 +237,9 @@ describe("PasswordLoginStrategy", () => {
|
||||
expect(firstResult.forcePasswordReset).toEqual(ForceSetPasswordReason.None);
|
||||
|
||||
// Second login attempt should save the force password reset options and return in result
|
||||
expect(stateService.setForceSetPasswordReason).toHaveBeenCalledWith(
|
||||
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
|
||||
ForceSetPasswordReason.WeakMasterPassword,
|
||||
userId,
|
||||
);
|
||||
expect(secondResult.forcePasswordReset).toEqual(ForceSetPasswordReason.WeakMasterPassword);
|
||||
});
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { BehaviorSubject, map, Observable } from "rxjs";
|
||||
import { BehaviorSubject, firstValueFrom, map, Observable } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
@@ -70,6 +72,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
|
||||
constructor(
|
||||
data: PasswordLoginStrategyData,
|
||||
accountService: AccountService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
@@ -86,6 +90,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -157,8 +163,10 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
});
|
||||
} else {
|
||||
// Authentication was successful, save the force update password options with the state service
|
||||
await this.stateService.setForceSetPasswordReason(
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
await this.masterPasswordService.setForceSetPasswordReason(
|
||||
ForceSetPasswordReason.WeakMasterPassword,
|
||||
userId,
|
||||
);
|
||||
authResult.forcePasswordReset = ForceSetPasswordReason.WeakMasterPassword;
|
||||
}
|
||||
@@ -184,7 +192,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
!result.requiresCaptcha &&
|
||||
forcePasswordResetReason != ForceSetPasswordReason.None
|
||||
) {
|
||||
await this.stateService.setForceSetPasswordReason(forcePasswordResetReason);
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
await this.masterPasswordService.setForceSetPasswordReason(forcePasswordResetReason, userId);
|
||||
result.forcePasswordReset = forcePasswordResetReason;
|
||||
}
|
||||
|
||||
@@ -193,8 +202,9 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
|
||||
protected override async setMasterKey(response: IdentityTokenResponse) {
|
||||
const { masterKey, localMasterKeyHash } = this.cache.value;
|
||||
await this.cryptoService.setMasterKey(masterKey);
|
||||
await this.cryptoService.setMasterKeyHash(localMasterKeyHash);
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
await this.masterPasswordService.setMasterKey(masterKey, userId);
|
||||
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, userId);
|
||||
}
|
||||
|
||||
protected override async setUserKey(response: IdentityTokenResponse): Promise<void> {
|
||||
@@ -204,7 +214,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
}
|
||||
await this.cryptoService.setMasterKeyEncryptedUserKey(response.key);
|
||||
|
||||
const masterKey = await this.cryptoService.getMasterKey();
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
if (masterKey) {
|
||||
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
|
||||
await this.cryptoService.setUserKey(userKey);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/a
|
||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||
import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
|
||||
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
@@ -20,7 +21,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { DeviceKey, UserKey, MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import {
|
||||
@@ -33,6 +36,9 @@ import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
import { SsoLoginStrategy } from "./sso-login.strategy";
|
||||
|
||||
describe("SsoLoginStrategy", () => {
|
||||
let accountService: FakeAccountService;
|
||||
let masterPasswordService: FakeMasterPasswordService;
|
||||
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
@@ -52,6 +58,7 @@ describe("SsoLoginStrategy", () => {
|
||||
let ssoLoginStrategy: SsoLoginStrategy;
|
||||
let credentials: SsoLoginCredentials;
|
||||
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
const deviceId = Utils.newGuid();
|
||||
const keyConnectorUrl = "KEY_CONNECTOR_URL";
|
||||
|
||||
@@ -61,6 +68,9 @@ describe("SsoLoginStrategy", () => {
|
||||
const ssoOrgId = "SSO_ORG_ID";
|
||||
|
||||
beforeEach(async () => {
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
masterPasswordService = new FakeMasterPasswordService();
|
||||
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
@@ -83,6 +93,8 @@ describe("SsoLoginStrategy", () => {
|
||||
|
||||
ssoLoginStrategy = new SsoLoginStrategy(
|
||||
null,
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -130,7 +142,7 @@ describe("SsoLoginStrategy", () => {
|
||||
|
||||
await ssoLoginStrategy.logIn(credentials);
|
||||
|
||||
expect(cryptoService.setMasterKey).not.toHaveBeenCalled();
|
||||
expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled();
|
||||
expect(cryptoService.setUserKey).not.toHaveBeenCalled();
|
||||
expect(cryptoService.setPrivateKey).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -395,7 +407,7 @@ describe("SsoLoginStrategy", () => {
|
||||
) as MasterKey;
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
|
||||
await ssoLoginStrategy.logIn(credentials);
|
||||
|
||||
@@ -422,7 +434,7 @@ describe("SsoLoginStrategy", () => {
|
||||
) as MasterKey;
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
|
||||
|
||||
await ssoLoginStrategy.logIn(credentials);
|
||||
@@ -446,7 +458,7 @@ describe("SsoLoginStrategy", () => {
|
||||
) as MasterKey;
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
|
||||
await ssoLoginStrategy.logIn(credentials);
|
||||
|
||||
@@ -473,7 +485,7 @@ describe("SsoLoginStrategy", () => {
|
||||
) as MasterKey;
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
|
||||
|
||||
await ssoLoginStrategy.logIn(credentials);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Observable, map, BehaviorSubject } from "rxjs";
|
||||
import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
@@ -79,6 +81,8 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
|
||||
constructor(
|
||||
data: SsoLoginStrategyData,
|
||||
accountService: AccountService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
@@ -96,6 +100,8 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -138,7 +144,11 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
|
||||
// Auth guard currently handles redirects for this.
|
||||
if (ssoAuthResult.forcePasswordReset == ForceSetPasswordReason.AdminForcePasswordReset) {
|
||||
await this.stateService.setForceSetPasswordReason(ssoAuthResult.forcePasswordReset);
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
await this.masterPasswordService.setForceSetPasswordReason(
|
||||
ssoAuthResult.forcePasswordReset,
|
||||
userId,
|
||||
);
|
||||
}
|
||||
|
||||
this.cache.next({
|
||||
@@ -323,7 +333,8 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
}
|
||||
|
||||
private async trySetUserKeyWithMasterKey(): Promise<void> {
|
||||
const masterKey = await this.cryptoService.getMasterKey();
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
|
||||
// There is a scenario in which the master key is not set here. That will occur if the user
|
||||
// has a master password and is using Key Connector. In that case, we cannot set the master key
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
@@ -19,7 +20,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { UserKey, MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||
@@ -30,6 +33,8 @@ import { UserApiLoginStrategy, UserApiLoginStrategyData } from "./user-api-login
|
||||
|
||||
describe("UserApiLoginStrategy", () => {
|
||||
let cache: UserApiLoginStrategyData;
|
||||
let accountService: FakeAccountService;
|
||||
let masterPasswordService: FakeMasterPasswordService;
|
||||
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
@@ -48,12 +53,16 @@ describe("UserApiLoginStrategy", () => {
|
||||
let apiLogInStrategy: UserApiLoginStrategy;
|
||||
let credentials: UserApiLoginCredentials;
|
||||
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
const deviceId = Utils.newGuid();
|
||||
const keyConnectorUrl = "KEY_CONNECTOR_URL";
|
||||
const apiClientId = "API_CLIENT_ID";
|
||||
const apiClientSecret = "API_CLIENT_SECRET";
|
||||
|
||||
beforeEach(async () => {
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
masterPasswordService = new FakeMasterPasswordService();
|
||||
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
@@ -74,6 +83,8 @@ describe("UserApiLoginStrategy", () => {
|
||||
|
||||
apiLogInStrategy = new UserApiLoginStrategy(
|
||||
cache,
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -172,7 +183,7 @@ describe("UserApiLoginStrategy", () => {
|
||||
environmentService.environment$ = new BehaviorSubject(env);
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||
masterPasswordService.masterKeySubject.next(masterKey);
|
||||
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
|
||||
|
||||
await apiLogInStrategy.logIn(credentials);
|
||||
|
||||
@@ -2,7 +2,9 @@ import { firstValueFrom, BehaviorSubject } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { UserApiTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/user-api-token.request";
|
||||
@@ -39,6 +41,8 @@ export class UserApiLoginStrategy extends LoginStrategy {
|
||||
|
||||
constructor(
|
||||
data: UserApiLoginStrategyData,
|
||||
accountService: AccountService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
@@ -54,6 +58,8 @@ export class UserApiLoginStrategy extends LoginStrategy {
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -95,7 +101,8 @@ export class UserApiLoginStrategy extends LoginStrategy {
|
||||
await this.cryptoService.setMasterKeyEncryptedUserKey(response.key);
|
||||
|
||||
if (response.apiUseKeyConnector) {
|
||||
const masterKey = await this.cryptoService.getMasterKey();
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
if (masterKey) {
|
||||
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
|
||||
await this.cryptoService.setUserKey(userKey);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||
import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
|
||||
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
|
||||
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
@@ -16,6 +17,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService } from "@bitwarden/common/spec";
|
||||
import { PrfKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||
@@ -26,6 +28,8 @@ import { WebAuthnLoginStrategy, WebAuthnLoginStrategyData } from "./webauthn-log
|
||||
|
||||
describe("WebAuthnLoginStrategy", () => {
|
||||
let cache: WebAuthnLoginStrategyData;
|
||||
let accountService: FakeAccountService;
|
||||
let masterPasswordService: FakeMasterPasswordService;
|
||||
|
||||
let cryptoService!: MockProxy<CryptoService>;
|
||||
let apiService!: MockProxy<ApiService>;
|
||||
@@ -63,6 +67,9 @@ describe("WebAuthnLoginStrategy", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
accountService = new FakeAccountService(null);
|
||||
masterPasswordService = new FakeMasterPasswordService();
|
||||
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
@@ -81,6 +88,8 @@ describe("WebAuthnLoginStrategy", () => {
|
||||
|
||||
webAuthnLoginStrategy = new WebAuthnLoginStrategy(
|
||||
cache,
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
@@ -207,7 +216,7 @@ describe("WebAuthnLoginStrategy", () => {
|
||||
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(idTokenResponse.privateKey);
|
||||
|
||||
// Master key and private key should not be set
|
||||
expect(cryptoService.setMasterKey).not.toHaveBeenCalled();
|
||||
expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not try to set the user key when prfKey is missing", async () => {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { BehaviorSubject } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
@@ -41,6 +43,8 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
|
||||
|
||||
constructor(
|
||||
data: WebAuthnLoginStrategyData,
|
||||
accountService: AccountService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
@@ -54,6 +58,8 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
|
||||
@@ -2,13 +2,15 @@ import { mock } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
|
||||
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { AuthRequestService } from "./auth-request.service";
|
||||
@@ -16,17 +18,27 @@ import { AuthRequestService } from "./auth-request.service";
|
||||
describe("AuthRequestService", () => {
|
||||
let sut: AuthRequestService;
|
||||
|
||||
let accountService: FakeAccountService;
|
||||
let masterPasswordService: FakeMasterPasswordService;
|
||||
const appIdService = mock<AppIdService>();
|
||||
const cryptoService = mock<CryptoService>();
|
||||
const apiService = mock<ApiService>();
|
||||
const stateService = mock<StateService>();
|
||||
|
||||
let mockPrivateKey: Uint8Array;
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
masterPasswordService = new FakeMasterPasswordService();
|
||||
|
||||
sut = new AuthRequestService(appIdService, cryptoService, apiService, stateService);
|
||||
sut = new AuthRequestService(
|
||||
appIdService,
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
);
|
||||
|
||||
mockPrivateKey = new Uint8Array(64);
|
||||
});
|
||||
@@ -67,8 +79,8 @@ describe("AuthRequestService", () => {
|
||||
});
|
||||
|
||||
it("should use the master key and hash if they exist", async () => {
|
||||
cryptoService.getMasterKey.mockResolvedValueOnce({ encKey: new Uint8Array(64) } as MasterKey);
|
||||
stateService.getKeyHash.mockResolvedValueOnce("KEY_HASH");
|
||||
masterPasswordService.masterKeySubject.next({ encKey: new Uint8Array(64) } as MasterKey);
|
||||
masterPasswordService.masterKeyHashSubject.next("MASTER_KEY_HASH");
|
||||
|
||||
await sut.approveOrDenyAuthRequest(
|
||||
true,
|
||||
@@ -130,8 +142,8 @@ describe("AuthRequestService", () => {
|
||||
masterKeyHash: mockDecryptedMasterKeyHash,
|
||||
});
|
||||
|
||||
cryptoService.setMasterKey.mockResolvedValueOnce(undefined);
|
||||
cryptoService.setMasterKeyHash.mockResolvedValueOnce(undefined);
|
||||
masterPasswordService.masterKeySubject.next(undefined);
|
||||
masterPasswordService.masterKeyHashSubject.next(undefined);
|
||||
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValueOnce(mockDecryptedUserKey);
|
||||
cryptoService.setUserKey.mockResolvedValueOnce(undefined);
|
||||
|
||||
@@ -144,10 +156,18 @@ describe("AuthRequestService", () => {
|
||||
mockAuthReqResponse.masterPasswordHash,
|
||||
mockPrivateKey,
|
||||
);
|
||||
expect(cryptoService.setMasterKey).toBeCalledWith(mockDecryptedMasterKey);
|
||||
expect(cryptoService.setMasterKeyHash).toBeCalledWith(mockDecryptedMasterKeyHash);
|
||||
expect(cryptoService.decryptUserKeyWithMasterKey).toBeCalledWith(mockDecryptedMasterKey);
|
||||
expect(cryptoService.setUserKey).toBeCalledWith(mockDecryptedUserKey);
|
||||
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
|
||||
mockDecryptedMasterKey,
|
||||
mockUserId,
|
||||
);
|
||||
expect(masterPasswordService.mock.setMasterKeyHash).toHaveBeenCalledWith(
|
||||
mockDecryptedMasterKeyHash,
|
||||
mockUserId,
|
||||
);
|
||||
expect(cryptoService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(
|
||||
mockDecryptedMasterKey,
|
||||
);
|
||||
expect(cryptoService.setUserKey).toHaveBeenCalledWith(mockDecryptedUserKey);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { firstValueFrom, Observable, Subject } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
|
||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
@@ -19,9 +20,10 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
|
||||
|
||||
constructor(
|
||||
private appIdService: AppIdService,
|
||||
private accountService: AccountService,
|
||||
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
private cryptoService: CryptoService,
|
||||
private apiService: ApiService,
|
||||
private stateService: StateService,
|
||||
) {
|
||||
this.authRequestPushNotification$ = this.authRequestPushNotificationSubject.asObservable();
|
||||
}
|
||||
@@ -38,8 +40,9 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
|
||||
}
|
||||
const pubKey = Utils.fromB64ToArray(authRequest.publicKey);
|
||||
|
||||
const masterKey = await this.cryptoService.getMasterKey();
|
||||
const masterKeyHash = await this.stateService.getKeyHash();
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId));
|
||||
let encryptedMasterKeyHash;
|
||||
let keyToEncrypt;
|
||||
|
||||
@@ -92,8 +95,9 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
|
||||
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
|
||||
|
||||
// Set masterKey + masterKeyHash in state after decryption (in case decryption fails)
|
||||
await this.cryptoService.setMasterKey(masterKey);
|
||||
await this.cryptoService.setMasterKeyHash(masterKeyHash);
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
|
||||
await this.masterPasswordService.setMasterKey(masterKey, userId);
|
||||
await this.masterPasswordService.setMasterKeyHash(masterKeyHash, userId);
|
||||
|
||||
await this.cryptoService.setUserKey(userKey);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
@@ -22,8 +23,14 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { KdfType } from "@bitwarden/common/platform/enums";
|
||||
import { FakeGlobalState, FakeGlobalStateProvider } from "@bitwarden/common/spec";
|
||||
import {
|
||||
FakeAccountService,
|
||||
FakeGlobalState,
|
||||
FakeGlobalStateProvider,
|
||||
mockAccountServiceWith,
|
||||
} from "@bitwarden/common/spec";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
AuthRequestServiceAbstraction,
|
||||
@@ -38,6 +45,8 @@ import { CACHE_EXPIRATION_KEY } from "./login-strategy.state";
|
||||
describe("LoginStrategyService", () => {
|
||||
let sut: LoginStrategyService;
|
||||
|
||||
let accountService: FakeAccountService;
|
||||
let masterPasswordService: FakeMasterPasswordService;
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
@@ -61,7 +70,11 @@ describe("LoginStrategyService", () => {
|
||||
let stateProvider: FakeGlobalStateProvider;
|
||||
let loginStrategyCacheExpirationState: FakeGlobalState<Date | null>;
|
||||
|
||||
const userId = "USER_ID" as UserId;
|
||||
|
||||
beforeEach(() => {
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
masterPasswordService = new FakeMasterPasswordService();
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
@@ -84,6 +97,8 @@ describe("LoginStrategyService", () => {
|
||||
stateProvider = new FakeGlobalStateProvider();
|
||||
|
||||
sut = new LoginStrategyService(
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
|
||||
@@ -9,8 +9,10 @@ import {
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
|
||||
@@ -81,6 +83,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
currentAuthType$: Observable<AuthenticationType | null>;
|
||||
|
||||
constructor(
|
||||
protected accountService: AccountService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
protected cryptoService: CryptoService,
|
||||
protected apiService: ApiService,
|
||||
protected tokenService: TokenService,
|
||||
@@ -257,7 +261,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
): Promise<AuthRequestResponse> {
|
||||
const pubKey = Utils.fromB64ToArray(key);
|
||||
|
||||
const masterKey = await this.cryptoService.getMasterKey();
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
let keyToEncrypt;
|
||||
let encryptedMasterKeyHash = null;
|
||||
|
||||
@@ -266,7 +271,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
|
||||
// Only encrypt the master password hash if masterKey exists as
|
||||
// we won't have a masterKeyHash without a masterKey
|
||||
const masterKeyHash = await this.stateService.getKeyHash();
|
||||
const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId));
|
||||
if (masterKeyHash != null) {
|
||||
encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt(
|
||||
Utils.fromUtf8ToArray(masterKeyHash),
|
||||
@@ -333,6 +338,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
case AuthenticationType.Password:
|
||||
return new PasswordLoginStrategy(
|
||||
data?.password,
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
@@ -351,6 +358,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
case AuthenticationType.Sso:
|
||||
return new SsoLoginStrategy(
|
||||
data?.sso,
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
@@ -370,6 +379,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
case AuthenticationType.UserApiKey:
|
||||
return new UserApiLoginStrategy(
|
||||
data?.userApiKey,
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
@@ -387,6 +398,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
case AuthenticationType.AuthRequest:
|
||||
return new AuthRequestLoginStrategy(
|
||||
data?.authRequest,
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
@@ -403,6 +416,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
case AuthenticationType.WebAuthn:
|
||||
return new WebAuthnLoginStrategy(
|
||||
data?.webAuthn,
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
|
||||
Reference in New Issue
Block a user