diff --git a/libs/common/spec/misc/logInStrategies/logIn.strategy.spec.ts b/libs/common/spec/misc/logInStrategies/logIn.strategy.spec.ts index fc10cf1f0c..3ec22a8eec 100644 --- a/libs/common/spec/misc/logInStrategies/logIn.strategy.spec.ts +++ b/libs/common/spec/misc/logInStrategies/logIn.strategy.spec.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line no-restricted-imports -import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; +import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; @@ -67,33 +66,34 @@ export function identityTokenResponseFactory() { } describe("LogInStrategy", () => { - 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 cryptoService: MockProxy; + let apiService: MockProxy; + let tokenService: MockProxy; + let appIdService: MockProxy; + let platformUtilsService: MockProxy; + let messagingService: MockProxy; + let logService: MockProxy; + let stateService: MockProxy; + let twoFactorService: MockProxy; + let authService: MockProxy; 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(); + cryptoService = mock(); + apiService = mock(); + tokenService = mock(); + appIdService = mock(); + platformUtilsService = mock(); + messagingService = mock(); + logService = mock(); + stateService = mock(); + twoFactorService = mock(); + authService = mock(); - appIdService.getAppId().resolves(deviceId); + appIdService.getAppId.mockResolvedValue(deviceId); + tokenService.decodeToken.calledWith(accessToken).mockResolvedValue(decodedToken); // The base class is abstract so we test it via PasswordLogInStrategy passwordLogInStrategy = new PasswordLogInStrategy( @@ -113,12 +113,11 @@ describe("LogInStrategy", () => { describe("base class", () => { it("sets the local environment after a successful login", async () => { - apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory()); - tokenService.decodeToken(accessToken).resolves(decodedToken); + apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); await passwordLogInStrategy.logIn(credentials); - stateService.received(1).addAccount( + expect(stateService.addAccount).toHaveBeenCalledWith( new Account({ profile: { ...new AccountProfile(), @@ -140,10 +139,9 @@ describe("LogInStrategy", () => { }, }) ); - cryptoService.received(1).setEncKey(encKey); - cryptoService.received(1).setEncPrivateKey(privateKey); - - messagingService.received(1).send("loggedIn"); + expect(cryptoService.setEncKey).toHaveBeenCalledWith(encKey); + expect(cryptoService.setEncPrivateKey).toHaveBeenCalledWith(privateKey); + expect(messagingService.send).toHaveBeenCalledWith("loggedIn"); }); it("builds AuthResult", async () => { @@ -151,16 +149,16 @@ describe("LogInStrategy", () => { tokenResponse.forcePasswordReset = true; tokenResponse.resetMasterPassword = true; - apiService.postIdentityToken(Arg.any()).resolves(tokenResponse); + apiService.postIdentityToken.mockResolvedValue(tokenResponse); const result = await passwordLogInStrategy.logIn(credentials); - const expected = new AuthResult(); - expected.forcePasswordReset = true; - expected.resetMasterPassword = true; - expected.twoFactorProviders = null; - expected.captchaSiteKey = ""; - expect(result).toEqual(expected); + expect(result).toEqual({ + forcePasswordReset: true, + resetMasterPassword: true, + twoFactorProviders: null, + captchaSiteKey: "", + } as AuthResult); }); it("rejects login if CAPTCHA is required", async () => { @@ -171,12 +169,12 @@ describe("LogInStrategy", () => { HCaptcha_SiteKey: captchaSiteKey, }); - apiService.postIdentityToken(Arg.any()).resolves(tokenResponse); + apiService.postIdentityToken.mockResolvedValue(tokenResponse); const result = await passwordLogInStrategy.logIn(credentials); - stateService.didNotReceive().addAccount(Arg.any()); - messagingService.didNotReceive().send(Arg.any()); + expect(stateService.addAccount).not.toHaveBeenCalled(); + expect(messagingService.send).not.toHaveBeenCalled(); const expected = new AuthResult(); expected.captchaSiteKey = captchaSiteKey; @@ -186,13 +184,20 @@ describe("LogInStrategy", () => { it("makes a new public and private key for an old account", async () => { const tokenResponse = identityTokenResponseFactory(); tokenResponse.privateKey = null; - cryptoService.makeKeyPair(Arg.any()).resolves(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]); + cryptoService.makeKeyPair.mockResolvedValue(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]); - apiService.postIdentityToken(Arg.any()).resolves(tokenResponse); + apiService.postIdentityToken.mockResolvedValue(tokenResponse); await passwordLogInStrategy.logIn(credentials); - apiService.received(1).postAccountKeys(Arg.any()); + // User key must be set before the new RSA keypair is generated, otherwise we can't decrypt the EncKey + expect(cryptoService.setKey).toHaveBeenCalled(); + expect(cryptoService.makeKeyPair).toHaveBeenCalled(); + expect(cryptoService.setKey.mock.invocationCallOrder[0]).toBeLessThan( + cryptoService.makeKeyPair.mock.invocationCallOrder[0] + ); + + expect(apiService.postAccountKeys).toHaveBeenCalled(); }); }); @@ -206,12 +211,12 @@ describe("LogInStrategy", () => { error_description: "Two factor required.", }); - apiService.postIdentityToken(Arg.any()).resolves(tokenResponse); + apiService.postIdentityToken.mockResolvedValue(tokenResponse); const result = await passwordLogInStrategy.logIn(credentials); - stateService.didNotReceive().addAccount(Arg.any()); - messagingService.didNotReceive().send(Arg.any()); + expect(stateService.addAccount).not.toHaveBeenCalled(); + expect(messagingService.send).not.toHaveBeenCalled(); const expected = new AuthResult(); expected.twoFactorProviders = new Map(); @@ -220,26 +225,25 @@ describe("LogInStrategy", () => { }); it("sends stored 2FA token to server", async () => { - tokenService.getTwoFactorToken().resolves(twoFactorToken); - apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory()); + tokenService.getTwoFactorToken.mockResolvedValue(twoFactorToken); + apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); await passwordLogInStrategy.logIn(credentials); - apiService.received(1).postIdentityToken( - Arg.is((actual) => { - const passwordTokenRequest = actual as any; - return ( - passwordTokenRequest.twoFactor.provider === TwoFactorProviderType.Remember && - passwordTokenRequest.twoFactor.token === twoFactorToken && - passwordTokenRequest.twoFactor.remember === false - ); + expect(apiService.postIdentityToken).toHaveBeenCalledWith( + expect.objectContaining({ + twoFactor: { + provider: TwoFactorProviderType.Remember, + token: twoFactorToken, + remember: false, + } as TokenTwoFactorRequest, }) ); }); it("sends 2FA token provided by user to server (single step)", async () => { // This occurs if the user enters the 2FA code as an argument in the CLI - apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory()); + apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); credentials.twoFactor = new TokenTwoFactorRequest( twoFactorProviderType, twoFactorToken, @@ -248,14 +252,13 @@ describe("LogInStrategy", () => { await passwordLogInStrategy.logIn(credentials); - apiService.received(1).postIdentityToken( - Arg.is((actual) => { - const passwordTokenRequest = actual as any; - return ( - passwordTokenRequest.twoFactor.provider === twoFactorProviderType && - passwordTokenRequest.twoFactor.token === twoFactorToken && - passwordTokenRequest.twoFactor.remember === twoFactorRemember - ); + expect(apiService.postIdentityToken).toHaveBeenCalledWith( + expect.objectContaining({ + twoFactor: { + provider: twoFactorProviderType, + token: twoFactorToken, + remember: twoFactorRemember, + } as TokenTwoFactorRequest, }) ); }); @@ -269,21 +272,20 @@ describe("LogInStrategy", () => { null ); - apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory()); + apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); await passwordLogInStrategy.logInTwoFactor( new TokenTwoFactorRequest(twoFactorProviderType, twoFactorToken, twoFactorRemember), null ); - apiService.received(1).postIdentityToken( - Arg.is((actual) => { - const passwordTokenRequest = actual as any; - return ( - passwordTokenRequest.twoFactor.provider === twoFactorProviderType && - passwordTokenRequest.twoFactor.token === twoFactorToken && - passwordTokenRequest.twoFactor.remember === twoFactorRemember - ); + expect(apiService.postIdentityToken).toHaveBeenCalledWith( + expect.objectContaining({ + twoFactor: { + provider: twoFactorProviderType, + token: twoFactorToken, + remember: twoFactorRemember, + } as TokenTwoFactorRequest, }) ); }); diff --git a/libs/common/spec/misc/logInStrategies/passwordLogIn.strategy.spec.ts b/libs/common/spec/misc/logInStrategies/passwordLogIn.strategy.spec.ts index d0791c8181..dfcb1bb16d 100644 --- a/libs/common/spec/misc/logInStrategies/passwordLogIn.strategy.spec.ts +++ b/libs/common/spec/misc/logInStrategies/passwordLogIn.strategy.spec.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line no-restricted-imports -import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; +import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; @@ -31,41 +30,43 @@ const preloginKey = new SymmetricCryptoKey( 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 cryptoService: MockProxy; + let apiService: MockProxy; + let tokenService: MockProxy; + let appIdService: MockProxy; + let platformUtilsService: MockProxy; + let messagingService: MockProxy; + let logService: MockProxy; + let stateService: MockProxy; + let twoFactorService: MockProxy; + let authService: MockProxy; 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(); + cryptoService = mock(); + apiService = mock(); + tokenService = mock(); + appIdService = mock(); + platformUtilsService = mock(); + messagingService = mock(); + logService = mock(); + stateService = mock(); + twoFactorService = mock(); + authService = mock(); - appIdService.getAppId().resolves(deviceId); - tokenService.getTwoFactorToken().resolves(null); + appIdService.getAppId.mockResolvedValue(deviceId); + tokenService.decodeToken.mockResolvedValue({}); - authService.makePreloginKey(Arg.any(), Arg.any()).resolves(preloginKey); + authService.makePreloginKey.mockResolvedValue(preloginKey); - cryptoService.hashPassword(masterPassword, Arg.any()).resolves(hashedPassword); - cryptoService - .hashPassword(masterPassword, Arg.any(), HashPurpose.LocalAuthorization) - .resolves(localHashedPassword); + cryptoService.hashPassword + .calledWith(masterPassword, expect.anything(), undefined) + .mockResolvedValue(hashedPassword); + cryptoService.hashPassword + .calledWith(masterPassword, expect.anything(), HashPurpose.LocalAuthorization) + .mockResolvedValue(localHashedPassword); passwordLogInStrategy = new PasswordLogInStrategy( cryptoService, @@ -81,23 +82,24 @@ describe("PasswordLogInStrategy", () => { ); credentials = new PasswordLogInCredentials(email, masterPassword); - apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory()); + apiService.postIdentityToken.mockResolvedValue(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 - ); + expect(apiService.postIdentityToken).toHaveBeenCalledWith( + expect.objectContaining({ + email: email, + masterPasswordHash: hashedPassword, + device: expect.objectContaining({ + identifier: deviceId, + }), + twoFactor: expect.objectContaining({ + provider: null, + token: null, + }), + captchaResponse: undefined, }) ); }); @@ -105,7 +107,7 @@ describe("PasswordLogInStrategy", () => { it("sets the local environment after a successful login", async () => { await passwordLogInStrategy.logIn(credentials); - cryptoService.received(1).setKey(preloginKey); - cryptoService.received(1).setKeyHash(localHashedPassword); + expect(cryptoService.setKey).toHaveBeenCalledWith(preloginKey); + expect(cryptoService.setKeyHash).toHaveBeenCalledWith(localHashedPassword); }); }); diff --git a/libs/common/spec/misc/logInStrategies/ssoLogIn.strategy.spec.ts b/libs/common/spec/misc/logInStrategies/ssoLogIn.strategy.spec.ts index 0caa3e9441..d36998d238 100644 --- a/libs/common/spec/misc/logInStrategies/ssoLogIn.strategy.spec.ts +++ b/libs/common/spec/misc/logInStrategies/ssoLogIn.strategy.spec.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line no-restricted-imports -import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; +import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; @@ -18,23 +17,21 @@ import { SsoLogInCredentials } from "@bitwarden/common/models/domain/log-in-cred import { identityTokenResponseFactory } from "./logIn.strategy.spec"; describe("SsoLogInStrategy", () => { - let cryptoService: SubstituteOf; - let apiService: SubstituteOf; - let tokenService: SubstituteOf; - let appIdService: SubstituteOf; - let platformUtilsService: SubstituteOf; - let messagingService: SubstituteOf; - let logService: SubstituteOf; - let keyConnectorService: SubstituteOf; - let stateService: SubstituteOf; - let twoFactorService: SubstituteOf; + let cryptoService: MockProxy; + let apiService: MockProxy; + let tokenService: MockProxy; + let appIdService: MockProxy; + let platformUtilsService: MockProxy; + let messagingService: MockProxy; + let logService: MockProxy; + let stateService: MockProxy; + let twoFactorService: MockProxy; + let keyConnectorService: MockProxy; let ssoLogInStrategy: SsoLogInStrategy; let credentials: SsoLogInCredentials; const deviceId = Utils.newGuid(); - const encKey = "ENC_KEY"; - const privateKey = "PRIVATE_KEY"; const keyConnectorUrl = "KEY_CONNECTOR_URL"; const ssoCode = "SSO_CODE"; @@ -43,19 +40,20 @@ describe("SsoLogInStrategy", () => { const ssoOrgId = "SSO_ORG_ID"; 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(); - keyConnectorService = Substitute.for(); - twoFactorService = Substitute.for(); + cryptoService = mock(); + apiService = mock(); + tokenService = mock(); + appIdService = mock(); + platformUtilsService = mock(); + messagingService = mock(); + logService = mock(); + stateService = mock(); + twoFactorService = mock(); + keyConnectorService = mock(); - tokenService.getTwoFactorToken().resolves(null); - appIdService.getAppId().resolves(deviceId); + tokenService.getTwoFactorToken.mockResolvedValue(null); + appIdService.getAppId.mockResolvedValue(deviceId); + tokenService.decodeToken.mockResolvedValue({}); ssoLogInStrategy = new SsoLogInStrategy( cryptoService, @@ -73,21 +71,22 @@ describe("SsoLogInStrategy", () => { }); it("sends SSO information to server", async () => { - apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory()); + apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); await ssoLogInStrategy.logIn(credentials); - apiService.received(1).postIdentityToken( - Arg.is((actual) => { - const ssoTokenRequest = actual as any; - return ( - ssoTokenRequest.code === ssoCode && - ssoTokenRequest.codeVerifier === ssoCodeVerifier && - ssoTokenRequest.redirectUri === ssoRedirectUrl && - ssoTokenRequest.device.identifier === deviceId && - ssoTokenRequest.twoFactor.provider == null && - ssoTokenRequest.twoFactor.token == null - ); + expect(apiService.postIdentityToken).toHaveBeenCalledWith( + expect.objectContaining({ + code: ssoCode, + codeVerifier: ssoCodeVerifier, + redirectUri: ssoRedirectUrl, + device: expect.objectContaining({ + identifier: deviceId, + }), + twoFactor: expect.objectContaining({ + provider: null, + token: null, + }), }) ); }); @@ -95,23 +94,23 @@ describe("SsoLogInStrategy", () => { it("does not set keys for new SSO user flow", async () => { const tokenResponse = identityTokenResponseFactory(); tokenResponse.key = null; - apiService.postIdentityToken(Arg.any()).resolves(tokenResponse); + apiService.postIdentityToken.mockResolvedValue(tokenResponse); await ssoLogInStrategy.logIn(credentials); - cryptoService.didNotReceive().setEncPrivateKey(privateKey); - cryptoService.didNotReceive().setEncKey(encKey); + expect(cryptoService.setEncPrivateKey).not.toHaveBeenCalled(); + expect(cryptoService.setEncKey).not.toHaveBeenCalled(); }); it("gets and sets KeyConnector key for enrolled user", async () => { const tokenResponse = identityTokenResponseFactory(); tokenResponse.keyConnectorUrl = keyConnectorUrl; - apiService.postIdentityToken(Arg.any()).resolves(tokenResponse); + apiService.postIdentityToken.mockResolvedValue(tokenResponse); await ssoLogInStrategy.logIn(credentials); - keyConnectorService.received(1).getAndSetKey(keyConnectorUrl); + expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl); }); it("converts new SSO user to Key Connector on first login", async () => { @@ -119,10 +118,13 @@ describe("SsoLogInStrategy", () => { tokenResponse.keyConnectorUrl = keyConnectorUrl; tokenResponse.key = null; - apiService.postIdentityToken(Arg.any()).resolves(tokenResponse); + apiService.postIdentityToken.mockResolvedValue(tokenResponse); await ssoLogInStrategy.logIn(credentials); - keyConnectorService.received(1).convertNewSsoUserToKeyConnector(tokenResponse, ssoOrgId); + expect(keyConnectorService.convertNewSsoUserToKeyConnector).toHaveBeenCalledWith( + tokenResponse, + ssoOrgId + ); }); }); diff --git a/libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts b/libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts index bc61d2c7e2..ce9030550c 100644 --- a/libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts +++ b/libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line no-restricted-imports -import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; +import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; @@ -19,17 +18,17 @@ import { UserApiLogInCredentials } from "@bitwarden/common/models/domain/log-in- import { identityTokenResponseFactory } from "./logIn.strategy.spec"; describe("UserApiLogInStrategy", () => { - let cryptoService: SubstituteOf; - let apiService: SubstituteOf; - let tokenService: SubstituteOf; - let appIdService: SubstituteOf; - let platformUtilsService: SubstituteOf; - let messagingService: SubstituteOf; - let logService: SubstituteOf; - let environmentService: SubstituteOf; - let keyConnectorService: SubstituteOf; - let stateService: SubstituteOf; - let twoFactorService: SubstituteOf; + let cryptoService: MockProxy; + let apiService: MockProxy; + let tokenService: MockProxy; + let appIdService: MockProxy; + let platformUtilsService: MockProxy; + let messagingService: MockProxy; + let logService: MockProxy; + let stateService: MockProxy; + let twoFactorService: MockProxy; + let keyConnectorService: MockProxy; + let environmentService: MockProxy; let apiLogInStrategy: UserApiLogInStrategy; let credentials: UserApiLogInCredentials; @@ -40,20 +39,21 @@ describe("UserApiLogInStrategy", () => { const apiClientSecret = "API_CLIENT_SECRET"; beforeEach(async () => { - cryptoService = Substitute.for(); - apiService = Substitute.for(); - tokenService = Substitute.for(); - appIdService = Substitute.for(); - platformUtilsService = Substitute.for(); - messagingService = Substitute.for(); - logService = Substitute.for(); - environmentService = Substitute.for(); - stateService = Substitute.for(); - keyConnectorService = Substitute.for(); - twoFactorService = Substitute.for(); + cryptoService = mock(); + apiService = mock(); + tokenService = mock(); + appIdService = mock(); + platformUtilsService = mock(); + messagingService = mock(); + logService = mock(); + stateService = mock(); + twoFactorService = mock(); + keyConnectorService = mock(); + environmentService = mock(); - appIdService.getAppId().resolves(deviceId); - tokenService.getTwoFactorToken().resolves(null); + appIdService.getAppId.mockResolvedValue(deviceId); + tokenService.getTwoFactorToken.mockResolvedValue(null); + tokenService.decodeToken.mockResolvedValue({}); apiLogInStrategy = new UserApiLogInStrategy( cryptoService, @@ -73,43 +73,43 @@ describe("UserApiLogInStrategy", () => { }); it("sends api key credentials to the server", async () => { - apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory()); + apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); await apiLogInStrategy.logIn(credentials); - apiService.received(1).postIdentityToken( - Arg.is((actual) => { - const apiTokenRequest = actual as any; - return ( - apiTokenRequest.clientId === apiClientId && - apiTokenRequest.clientSecret === apiClientSecret && - apiTokenRequest.device.identifier === deviceId && - apiTokenRequest.twoFactor.provider == null && - apiTokenRequest.twoFactor.token == null && - apiTokenRequest.captchaResponse == null - ); + expect(apiService.postIdentityToken).toHaveBeenCalledWith( + expect.objectContaining({ + clientId: apiClientId, + clientSecret: apiClientSecret, + device: expect.objectContaining({ + identifier: deviceId, + }), + twoFactor: expect.objectContaining({ + provider: null, + token: null, + }), }) ); }); it("sets the local environment after a successful login", async () => { - apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory()); + apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); await apiLogInStrategy.logIn(credentials); - stateService.received(1).setApiKeyClientId(apiClientId); - stateService.received(1).setApiKeyClientSecret(apiClientSecret); - stateService.received(1).addAccount(Arg.any()); + expect(stateService.setApiKeyClientId).toHaveBeenCalledWith(apiClientId); + expect(stateService.setApiKeyClientSecret).toHaveBeenCalledWith(apiClientSecret); + expect(stateService.addAccount).toHaveBeenCalled(); }); it("gets and sets the Key Connector key from environmentUrl", async () => { const tokenResponse = identityTokenResponseFactory(); tokenResponse.apiUseKeyConnector = true; - apiService.postIdentityToken(Arg.any()).resolves(tokenResponse); - environmentService.getKeyConnectorUrl().returns(keyConnectorUrl); + apiService.postIdentityToken.mockResolvedValue(tokenResponse); + environmentService.getKeyConnectorUrl.mockReturnValue(keyConnectorUrl); await apiLogInStrategy.logIn(credentials); - keyConnectorService.received(1).getAndSetKey(keyConnectorUrl); + expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl); }); }); diff --git a/libs/common/src/misc/logInStrategies/logIn.strategy.ts b/libs/common/src/misc/logInStrategies/logIn.strategy.ts index b0cf06e9f9..bf16ff0e5a 100644 --- a/libs/common/src/misc/logInStrategies/logIn.strategy.ts +++ b/libs/common/src/misc/logInStrategies/logIn.strategy.ts @@ -50,6 +50,9 @@ export abstract class LogInStrategy { | PasswordlessLogInCredentials ): Promise; + // The user key comes from different sources depending on the login strategy + protected abstract setUserKey(response: IdentityTokenResponse): Promise; + async logInTwoFactor( twoFactor: TokenTwoFactorRequest, captchaResponse: string = null @@ -74,11 +77,6 @@ export abstract class LogInStrategy { throw new Error("Invalid response object."); } - protected onSuccessfulLogin(response: IdentityTokenResponse): Promise { - // Implemented in subclass if required - return null; - } - protected async buildDeviceRequest() { const appId = await this.appIdService.getAppId(); return new DeviceRequest(appId, this.platformUtilsService); @@ -134,6 +132,9 @@ export abstract class LogInStrategy { await this.tokenService.setTwoFactorToken(response); } + await this.setUserKey(response); + + // Must come after the user Key is set, otherwise createKeyPairForOldAccount will fail const newSsoUser = response.key == null; if (!newSsoUser) { await this.cryptoService.setEncKey(response.key); @@ -142,8 +143,6 @@ export abstract class LogInStrategy { ); } - await this.onSuccessfulLogin(response); - this.messagingService.send("loggedIn"); return result; diff --git a/libs/common/src/misc/logInStrategies/passwordLogin.strategy.ts b/libs/common/src/misc/logInStrategies/passwordLogin.strategy.ts index 2f2a3fdc94..7025b5342e 100644 --- a/libs/common/src/misc/logInStrategies/passwordLogin.strategy.ts +++ b/libs/common/src/misc/logInStrategies/passwordLogin.strategy.ts @@ -56,7 +56,7 @@ export class PasswordLogInStrategy extends LogInStrategy { ); } - async onSuccessfulLogin() { + async setUserKey() { await this.cryptoService.setKey(this.key); await this.cryptoService.setKeyHash(this.localHashedPassword); } diff --git a/libs/common/src/misc/logInStrategies/passwordlessLogin.strategy.ts b/libs/common/src/misc/logInStrategies/passwordlessLogin.strategy.ts index 89d4121a22..e61835f9ae 100644 --- a/libs/common/src/misc/logInStrategies/passwordlessLogin.strategy.ts +++ b/libs/common/src/misc/logInStrategies/passwordlessLogin.strategy.ts @@ -56,7 +56,7 @@ export class PasswordlessLogInStrategy extends LogInStrategy { ); } - async onSuccessfulLogin() { + async setUserKey() { await this.cryptoService.setKey(this.passwordlessCredentials.decKey); await this.cryptoService.setKeyHash(this.passwordlessCredentials.localPasswordHash); } diff --git a/libs/common/src/misc/logInStrategies/ssoLogin.strategy.ts b/libs/common/src/misc/logInStrategies/ssoLogin.strategy.ts index 6a83b3218d..8006dbaea2 100644 --- a/libs/common/src/misc/logInStrategies/ssoLogin.strategy.ts +++ b/libs/common/src/misc/logInStrategies/ssoLogin.strategy.ts @@ -43,7 +43,7 @@ export class SsoLogInStrategy extends LogInStrategy { ); } - async onSuccessfulLogin(tokenResponse: IdentityTokenResponse) { + async setUserKey(tokenResponse: IdentityTokenResponse) { const newSsoUser = tokenResponse.key == null; if (tokenResponse.keyConnectorUrl != null) { diff --git a/libs/common/src/misc/logInStrategies/user-api-login.strategy.ts b/libs/common/src/misc/logInStrategies/user-api-login.strategy.ts index 7b435c74fd..f15f8f7d88 100644 --- a/libs/common/src/misc/logInStrategies/user-api-login.strategy.ts +++ b/libs/common/src/misc/logInStrategies/user-api-login.strategy.ts @@ -44,7 +44,7 @@ export class UserApiLogInStrategy extends LogInStrategy { ); } - async onSuccessfulLogin(tokenResponse: IdentityTokenResponse) { + async setUserKey(tokenResponse: IdentityTokenResponse) { if (tokenResponse.apiUseKeyConnector) { const keyConnectorUrl = this.environmentService.getKeyConnectorUrl(); await this.keyConnectorService.getAndSetKey(keyConnectorUrl);