import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { TokenService } from "@bitwarden/common/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; import { HashPurpose } from "@bitwarden/common/enums/hashPurpose"; import { PasswordLogInStrategy } from "@bitwarden/common/misc/logInStrategies/passwordLogin.strategy"; import { Utils } from "@bitwarden/common/misc/utils"; import { PasswordLogInCredentials } from "@bitwarden/common/models/domain/logInCredentials"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey"; import { identityTokenResponseFactory } from "./logIn.strategy.spec"; const email = "hello@world.com"; const masterPassword = "password"; const hashedPassword = "HASHED_PASSWORD"; const localHashedPassword = "LOCAL_HASHED_PASSWORD"; const preloginKey = new SymmetricCryptoKey( Utils.fromB64ToArray( "N2KWjlLpfi5uHjv+YcfUKIpZ1l+W+6HRensmIqD+BFYBf6N/dvFpJfWwYnVBdgFCK2tJTAIMLhqzIQQEUmGFgg==" ) ); const deviceId = Utils.newGuid(); describe("PasswordLogInStrategy", () => { let cryptoService: SubstituteOf; let apiService: SubstituteOf; let tokenService: SubstituteOf; let appIdService: SubstituteOf; let platformUtilsService: SubstituteOf; let messagingService: SubstituteOf; let logService: SubstituteOf; let stateService: SubstituteOf; let twoFactorService: SubstituteOf; let authService: SubstituteOf; let passwordLogInStrategy: PasswordLogInStrategy; let credentials: PasswordLogInCredentials; beforeEach(async () => { cryptoService = Substitute.for(); apiService = Substitute.for(); tokenService = Substitute.for(); appIdService = Substitute.for(); platformUtilsService = Substitute.for(); messagingService = Substitute.for(); logService = Substitute.for(); stateService = Substitute.for(); twoFactorService = Substitute.for(); authService = Substitute.for(); appIdService.getAppId().resolves(deviceId); tokenService.getTwoFactorToken().resolves(null); authService.makePreloginKey(Arg.any(), Arg.any()).resolves(preloginKey); cryptoService.hashPassword(masterPassword, Arg.any()).resolves(hashedPassword); cryptoService .hashPassword(masterPassword, Arg.any(), HashPurpose.LocalAuthorization) .resolves(localHashedPassword); passwordLogInStrategy = new PasswordLogInStrategy( cryptoService, apiService, tokenService, appIdService, platformUtilsService, messagingService, logService, stateService, twoFactorService, authService ); credentials = new PasswordLogInCredentials(email, masterPassword); apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory()); }); it("sends master password credentials to the server", async () => { await passwordLogInStrategy.logIn(credentials); apiService.received(1).postIdentityToken( Arg.is((actual) => { const passwordTokenRequest = actual as any; // Need to access private fields return ( passwordTokenRequest.email === email && passwordTokenRequest.masterPasswordHash === hashedPassword && passwordTokenRequest.device.identifier === deviceId && passwordTokenRequest.twoFactor.provider == null && passwordTokenRequest.twoFactor.token == null && passwordTokenRequest.captchaResponse == null ); }) ); }); it("sets the local environment after a successful login", async () => { await passwordLogInStrategy.logIn(credentials); cryptoService.received(1).setKey(preloginKey); cryptoService.received(1).setKeyHash(localHashedPassword); }); });