diff --git a/common/src/abstractions/auth.service.ts b/common/src/abstractions/auth.service.ts index 81b90793..cd1db92b 100644 --- a/common/src/abstractions/auth.service.ts +++ b/common/src/abstractions/auth.service.ts @@ -3,14 +3,6 @@ import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; import { TwoFactorData } from '../models/request/identityToken/tokenRequest'; export abstract class AuthService { - email: string; - masterPasswordHash: string; - code: string; - codeVerifier: string; - ssoRedirectUrl: string; - clientId: string; - clientSecret: string; - logIn: (email: string, masterPassword: string, twoFactor?: TwoFactorData, captchaToken?: string) => Promise; logInSso: ( code: string, diff --git a/common/src/models/request/identityToken/apiTokenRequest.ts b/common/src/models/request/identityToken/apiTokenRequest.ts index 6a1b4059..00352175 100644 --- a/common/src/models/request/identityToken/apiTokenRequest.ts +++ b/common/src/models/request/identityToken/apiTokenRequest.ts @@ -4,8 +4,8 @@ import { DeviceRequest } from "../deviceRequest"; export class ApiTokenRequest extends TokenRequest { constructor( - private clientId: string, - private clientSecret: string, + public clientId: string, + public clientSecret: string, protected twoFactor: TwoFactorData, captchaResponse: string, device?: DeviceRequest diff --git a/common/src/models/request/identityToken/passwordTokenRequest.ts b/common/src/models/request/identityToken/passwordTokenRequest.ts index d362a95b..2047ebb9 100644 --- a/common/src/models/request/identityToken/passwordTokenRequest.ts +++ b/common/src/models/request/identityToken/passwordTokenRequest.ts @@ -6,16 +6,13 @@ import { Utils } from "../../../misc/utils"; export class PasswordTokenRequest extends TokenRequest { constructor( - private email: string, - private masterPasswordHash: string, + public email: string, + public masterPasswordHash: string, protected twoFactor: TwoFactorData, captchaResponse: string, device?: DeviceRequest ) { super(twoFactor, captchaResponse, device); - - this.email = email; - this.masterPasswordHash = masterPasswordHash; } toIdentityToken(clientId: string) { diff --git a/common/src/models/request/identityToken/ssoTokenRequest.ts b/common/src/models/request/identityToken/ssoTokenRequest.ts index e07022f8..0d455ed9 100644 --- a/common/src/models/request/identityToken/ssoTokenRequest.ts +++ b/common/src/models/request/identityToken/ssoTokenRequest.ts @@ -4,18 +4,14 @@ import { DeviceRequest } from "../deviceRequest"; export class SsoTokenRequest extends TokenRequest { constructor( - private code: string, - private codeVerifier: string, - private redirectUri: string, + public code: string, + public codeVerifier: string, + public redirectUri: string, protected twoFactor: TwoFactorData, captchaResponse: string, device?: DeviceRequest ) { super(twoFactor, captchaResponse, device); - - this.code = code; - this.codeVerifier = codeVerifier; - this.redirectUri = redirectUri; } toIdentityToken(clientId: string) { diff --git a/common/src/models/request/identityToken/tokenRequest.ts b/common/src/models/request/identityToken/tokenRequest.ts index 25639379..92255c0d 100644 --- a/common/src/models/request/identityToken/tokenRequest.ts +++ b/common/src/models/request/identityToken/tokenRequest.ts @@ -50,4 +50,8 @@ export abstract class TokenRequest implements CaptchaProtectedRequest { alterIdentityTokenHeaders(headers: Headers) { // Implemented in subclass if required } + + setTwoFactor(twoFactor: TwoFactorData) { + this.twoFactor = twoFactor; + } } diff --git a/common/src/services/auth.service.ts b/common/src/services/auth.service.ts index 7f0d87ab..d60fc514 100644 --- a/common/src/services/auth.service.ts +++ b/common/src/services/auth.service.ts @@ -35,17 +35,12 @@ import { TokenService } from "../abstractions/token.service"; import { TwoFactorService } from "../abstractions/twoFactor.service"; import { Utils } from "../misc/utils"; +import { IdentityCaptchaResponse } from "../models/response/identityCaptchaResponse"; export class AuthService implements AuthServiceAbstraction { - email: string; - masterPasswordHash: string; - localMasterPasswordHash: string; - code: string; - codeVerifier: string; - ssoRedirectUrl: string; - clientId: string; - clientSecret: string; - captchaToken: string; + private localMasterPasswordHash: string; + + private savedTokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest; private key: SymmetricCryptoKey; @@ -65,7 +60,12 @@ export class AuthService implements AuthServiceAbstraction { private setCryptoKeys = true ) {} - async logIn(email: string, masterPassword: string, twoFactor?: TwoFactorData, captchaToken?: string): Promise { + async logIn( + email: string, + masterPassword: string, + twoFactor?: TwoFactorData, + captchaToken?: string + ): Promise { this.twoFactorService.clearSelectedProvider(); const key = await this.makePreloginKey(masterPassword, email); const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key); @@ -74,20 +74,32 @@ export class AuthService implements AuthServiceAbstraction { key, HashPurpose.LocalAuthorization ); - return await this.logInHelper( + + const tokenRequest = new PasswordTokenRequest( email, hashedPassword, + await this.createTwoFactorData(twoFactor, email), + captchaToken, + await this.createDeviceRequest() + ); + const response = await this.apiService.postIdentityToken(tokenRequest); + + const result = await this.processTokenResponse( + response, + email, localHashedPassword, null, null, null, - null, - null, key, - twoFactor, - captchaToken, null ); + + if (result.twoFactor) { + this.saveState(tokenRequest, key, localHashedPassword, result.twoFactorProviders); + } + + return result; } async logInSso( @@ -95,59 +107,84 @@ export class AuthService implements AuthServiceAbstraction { codeVerifier: string, redirectUrl: string, orgId: string, - twoFactor?: TwoFactorData, + twoFactor?: TwoFactorData ): Promise { this.twoFactorService.clearSelectedProvider(); - return await this.logInHelper( - null, - null, - null, + + const tokenRequest = new SsoTokenRequest( code, codeVerifier, redirectUrl, + await this.createTwoFactorData(twoFactor, null), + null, + await this.createDeviceRequest() + ); + const response = await this.apiService.postIdentityToken(tokenRequest); + + const result = await this.processTokenResponse( + response, null, null, + code, + null, null, - twoFactor, null, orgId ); + + if (result.twoFactor) { + this.saveState(tokenRequest, null, null, result.twoFactorProviders); + } + + return result; } - async logInApiKey(clientId: string, clientSecret: string, twoFactor?: TwoFactorData): Promise { + async logInApiKey( + clientId: string, + clientSecret: string, + twoFactor?: TwoFactorData + ): Promise { this.twoFactorService.clearSelectedProvider(); - return await this.logInHelper( - null, - null, + + const tokenRequest = new ApiTokenRequest( + clientId, + clientSecret, + await this.createTwoFactorData(twoFactor, null), null, + await this.createDeviceRequest() + ); + const response = await this.apiService.postIdentityToken(tokenRequest); + + const result = await this.processTokenResponse( + response, null, null, null, clientId, clientSecret, null, - twoFactor, - null, null ); + + if (result.twoFactor) { + this.saveState(tokenRequest, null, null, result.twoFactorProviders); + } + + return result; } - async logInTwoFactor( - twoFactor: TwoFactorData, - ): Promise { - return await this.logInHelper( - this.email, - this.masterPasswordHash, + async logInTwoFactor(twoFactor: TwoFactorData): Promise { + this.savedTokenRequest.setTwoFactor(twoFactor); + const response = await this.apiService.postIdentityToken(this.savedTokenRequest); + + return await this.processTokenResponse( + response, + (this.savedTokenRequest as PasswordTokenRequest).email, this.localMasterPasswordHash, - this.code, - this.codeVerifier, - this.ssoRedirectUrl, - this.clientId, - this.clientSecret, - this.key, - twoFactor, - this.captchaToken, - null + (this.savedTokenRequest as SsoTokenRequest).code, + (this.savedTokenRequest as ApiTokenRequest).clientId, + (this.savedTokenRequest as ApiTokenRequest).clientSecret, + this.key ); } @@ -156,16 +193,20 @@ export class AuthService implements AuthServiceAbstraction { this.messagingService.send("loggedOut"); } + // TODO authingWithApiKey(): boolean { - return this.clientId != null && this.clientSecret != null; + return null; + // return this.clientId != null && this.clientSecret != null; } authingWithSso(): boolean { - return this.code != null && this.codeVerifier != null && this.ssoRedirectUrl != null; + return null; + // return this.code != null && this.codeVerifier != null && this.ssoRedirectUrl != null; } authingWithPassword(): boolean { - return this.email != null && this.masterPasswordHash != null; + return null; + // return this.email != null && this.masterPasswordHash != null; } async makePreloginKey(masterPassword: string, email: string): Promise { @@ -186,34 +227,16 @@ export class AuthService implements AuthServiceAbstraction { return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations); } - private async logInHelper( + private async processTokenResponse( + response: IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse, email: string, - hashedPassword: string, localHashedPassword: string, code: string, - codeVerifier: string, - redirectUrl: string, clientId: string, clientSecret: string, key: SymmetricCryptoKey, - twoFactor: TwoFactorData, - captchaToken?: string, orgId?: string ): Promise { - const request = await this.createTokenRequest( - email, - hashedPassword, - code, - codeVerifier, - redirectUrl, - clientId, - clientSecret, - twoFactor, - captchaToken - ); - - const response = await this.apiService.postIdentityToken(request); - this.clearState(); const result = new AuthResult(); @@ -224,19 +247,6 @@ export class AuthService implements AuthServiceAbstraction { result.twoFactor = !!(response as any).twoFactorProviders2; if (result.twoFactor) { - this.saveState( - email, - hashedPassword, - localHashedPassword, - code, - codeVerifier, - redirectUrl, - clientId, - clientSecret, - key, - (response as IdentityTwoFactorResponse).twoFactorProviders2 - ); - result.twoFactorProviders = (response as IdentityTwoFactorResponse).twoFactorProviders2; return result; } @@ -292,19 +302,7 @@ export class AuthService implements AuthServiceAbstraction { return new DeviceRequest(appId, this.platformUtilsService); } - private async createTokenRequest( - email: string, - hashedPassword: string, - code: string, - codeVerifier: string, - redirectUrl: string, - clientId: string, - clientSecret: string, - twoFactor: TwoFactorData, - captchaToken: string - ) { - const deviceRequest = await this.createDeviceRequest(); - + private async createTwoFactorData(twoFactor: TwoFactorData, email: string) { if (twoFactor == null) { const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email); if (storedTwoFactorToken != null) { @@ -312,38 +310,16 @@ export class AuthService implements AuthServiceAbstraction { token: storedTwoFactorToken, provider: TwoFactorProviderType.Remember, remember: false, - } + }; } else { - twoFactor = { - token: null, - provider: null, - remember: false, - }; - } + twoFactor = { + token: null, + provider: null, + remember: false, + }; } - - if (email != null && hashedPassword != null) { - return new PasswordTokenRequest( - email, - hashedPassword, - twoFactor, - captchaToken, - deviceRequest - ); - } else if (code != null && codeVerifier != null && redirectUrl != null) { - return new SsoTokenRequest( - code, - codeVerifier, - redirectUrl, - twoFactor, - captchaToken, - deviceRequest - ); - } else if (clientId != null && clientSecret != null) { - return new ApiTokenRequest(clientId, clientSecret, twoFactor, captchaToken, deviceRequest); - } else { - throw new Error("No credentials provided."); } + return twoFactor; } private async saveAccountInformation( @@ -425,39 +401,21 @@ export class AuthService implements AuthServiceAbstraction { } private saveState( - email: string, - hashedPassword: string, - localHashedPassword: string, - code: string, - codeVerifier: string, - redirectUrl: string, - clientId: string, - clientSecret: string, + tokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest, key: SymmetricCryptoKey, + localMasterPasswordHash: string, twoFactorProviders: Map ) { - this.email = email; - this.masterPasswordHash = hashedPassword; - this.localMasterPasswordHash = localHashedPassword; - this.code = code; - this.codeVerifier = codeVerifier; - this.ssoRedirectUrl = redirectUrl; - this.clientId = clientId; - this.clientSecret = clientSecret; + this.savedTokenRequest = tokenRequest; + this.localMasterPasswordHash = localMasterPasswordHash; this.key = this.setCryptoKeys ? key : null; this.twoFactorService.setProviders(twoFactorProviders); } private clearState(): void { + this.savedTokenRequest = null; this.key = null; - this.email = null; - this.masterPasswordHash = null; this.localMasterPasswordHash = null; - this.code = null; - this.codeVerifier = null; - this.ssoRedirectUrl = null; - this.clientId = null; - this.clientSecret = null; this.twoFactorService.clearProviders(); this.twoFactorService.clearSelectedProvider(); } diff --git a/spec/common/services/auth.service.spec.ts b/spec/common/services/auth.service.spec.ts index e2bb749b..a218e872 100644 --- a/spec/common/services/auth.service.spec.ts +++ b/spec/common/services/auth.service.spec.ts @@ -26,6 +26,8 @@ import { IdentityTokenResponse } from "jslib-common/models/response/identityToke import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service"; import { HashPurpose } from "jslib-common/enums/hashPurpose"; import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType"; +import { PasswordTokenRequest } from "jslib-common/models/request/identityToken/passwordTokenRequest"; +import { DeviceRequest } from "jslib-common/models/request/deviceRequest"; describe("Cipher Service", () => { let cryptoService: SubstituteOf; @@ -327,9 +329,9 @@ describe("Cipher Service", () => { it("logIn: sends stored 2FA token to server", async () => { commonSetup(); - logInSetup(); + logInSetup(); - tokenService.getTwoFactorToken(email).resolves(twoFactorToken); + tokenService.getTwoFactorToken(email).resolves(twoFactorToken); await authService.logIn(email, masterPassword); @@ -351,9 +353,13 @@ describe("Cipher Service", () => { it("logIn: sends 2FA token entered by user to server", async () => { commonSetup(); - logInSetup(); + logInSetup(); - await authService.logIn(email, masterPassword, { provider: twoFactorProviderType, token: twoFactorToken, remember: twoFactorRemember }); + await authService.logIn(email, masterPassword, { + provider: twoFactorProviderType, + token: twoFactorToken, + remember: twoFactorRemember, + }); apiService.received(1).postIdentityToken( Arg.is((actual) => { @@ -371,15 +377,22 @@ describe("Cipher Service", () => { ); }); - it("logInTwoFactor: sends 2FA token to server when using Master Password", async () => { commonSetup(); - authService.email = email; - authService.masterPasswordHash = hashedPassword; - authService.localMasterPasswordHash = localHashedPassword; + const tokenRequest = new PasswordTokenRequest(email, hashedPassword, null, null, { + identifier: deviceId, + } as DeviceRequest); - await authService.logInTwoFactor({ provider: twoFactorProviderType, token: twoFactorToken, remember: twoFactorRemember }); + (authService as any).localMasterPasswordHash = localHashedPassword; + (authService as any).email = email; + (authService as any).savedTokenRequest = tokenRequest; + + await authService.logInTwoFactor({ + provider: twoFactorProviderType, + token: twoFactorToken, + remember: twoFactorRemember, + }); apiService.received(1).postIdentityToken( Arg.is((actual) => {