diff --git a/angular/src/components/two-factor.component.ts b/angular/src/components/two-factor.component.ts index 02f5923d..71859d4b 100644 --- a/angular/src/components/two-factor.component.ts +++ b/angular/src/components/two-factor.component.ts @@ -21,9 +21,10 @@ import { LogService } from 'jslib-common/abstractions/log.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { StateService } from 'jslib-common/abstractions/state.service'; -import { TwoFactorProviders } from 'jslib-common/services/auth.service'; +import { TwoFactorProviders } from 'jslib-common/services/twoFactor.service'; import * as DuoWebSDK from 'duo_web_sdk'; +import { TwoFactorService } from 'jslib-common/abstractions/twoFactor.service'; import { WebAuthnIFrame } from 'jslib-common/misc/webauthn_iframe'; @Directive() @@ -56,12 +57,13 @@ export class TwoFactorComponent implements OnInit, OnDestroy { protected i18nService: I18nService, protected apiService: ApiService, protected platformUtilsService: PlatformUtilsService, protected win: Window, protected environmentService: EnvironmentService, protected stateService: StateService, - protected route: ActivatedRoute, protected logService: LogService) { + protected route: ActivatedRoute, protected logService: LogService, + protected twoFactorService: TwoFactorService) { this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); } async ngOnInit() { - if (!this.authing || this.authService.twoFactorProvidersData == null) { + if (!this.authing || this.twoFactorService.providers == null) { this.router.navigate([this.loginRoute]); return; } @@ -92,7 +94,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy { ); } - this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(this.webAuthnSupported); + this.selectedProviderType = this.twoFactorService.getDefaultTwoFactorProvider(this.webAuthnSupported); await this.init(); } @@ -109,7 +111,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy { this.cleanupWebAuthn(); this.title = (TwoFactorProviders as any)[this.selectedProviderType].name; - const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType); + const providerData = this.twoFactorService.providers.get(this.selectedProviderType); switch (this.selectedProviderType) { case TwoFactorProviderType.WebAuthn: if (!this.webAuthnNewTab) { @@ -137,7 +139,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy { break; case TwoFactorProviderType.Email: this.twoFactorEmail = providerData.Email; - if (this.authService.twoFactorProvidersData.size > 1) { + if (this.twoFactorService.providers.size > 1) { await this.sendEmail(false); } break; @@ -225,7 +227,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy { } authWebAuthn() { - const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType); + const providerData = this.twoFactorService.providers.get(this.selectedProviderType); if (!this.webAuthnSupported || this.webAuthn == null) { return; diff --git a/angular/src/services/jslib-services.module.ts b/angular/src/services/jslib-services.module.ts index a413eb4b..f17f133c 100644 --- a/angular/src/services/jslib-services.module.ts +++ b/angular/src/services/jslib-services.module.ts @@ -31,6 +31,7 @@ import { StateMigrationService } from 'jslib-common/services/stateMigration.serv import { SyncService } from 'jslib-common/services/sync.service'; import { TokenService } from 'jslib-common/services/token.service'; import { TotpService } from 'jslib-common/services/totp.service'; +import { TwoFactorService } from 'jslib-common/services/twoFactor.service'; import { UserVerificationService } from 'jslib-common/services/userVerification.service'; import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service'; import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunction.service'; @@ -71,6 +72,7 @@ import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstra import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service'; import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service'; import { TotpService as TotpServiceAbstraction } from 'jslib-common/abstractions/totp.service'; +import { TwoFactorService as TwoFactorServiceAbstraction } from 'jslib-common/abstractions/twoFactor.service'; import { UserVerificationService as UserVerificationServiceAbstraction } from 'jslib-common/abstractions/userVerification.service'; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service'; @@ -114,14 +116,13 @@ import { ValidationService } from './validation.service'; ApiServiceAbstraction, TokenServiceAbstraction, AppIdServiceAbstraction, - I18nServiceAbstraction, PlatformUtilsServiceAbstraction, MessagingServiceAbstraction, - VaultTimeoutServiceAbstraction, LogService, CryptoFunctionServiceAbstraction, KeyConnectorServiceAbstraction, EnvironmentServiceAbstraction, + TwoFactorServiceAbstraction, StateServiceAbstraction, ], }, @@ -474,6 +475,14 @@ import { ValidationService } from './validation.service'; StateServiceAbstraction, ], }, + { + provide: TwoFactorServiceAbstraction, + useClass: TwoFactorService, + deps: [ + I18nServiceAbstraction, + PlatformUtilsServiceAbstraction, + ], + }, ], }) export class JslibServicesModule { diff --git a/common/src/abstractions/auth.service.ts b/common/src/abstractions/auth.service.ts index 58f72fc7..be3507b8 100644 --- a/common/src/abstractions/auth.service.ts +++ b/common/src/abstractions/auth.service.ts @@ -11,8 +11,6 @@ export abstract class AuthService { ssoRedirectUrl: string; clientId: string; clientSecret: string; - twoFactorProvidersData: Map; - selectedTwoFactorProviderType: TwoFactorProviderType; logIn: (email: string, masterPassword: string, captchaToken?: string) => Promise; logInSso: (code: string, codeVerifier: string, redirectUrl: string, orgId: string) => Promise; @@ -26,8 +24,6 @@ export abstract class AuthService { logInApiKeyComplete: (clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean) => Promise; logOut: (callback: Function) => void; - getSupportedTwoFactorProviders: (win: Window) => any[]; - getDefaultTwoFactorProvider: (webAuthnSupported: boolean) => TwoFactorProviderType; makePreloginKey: (masterPassword: string, email: string) => Promise; authingWithApiKey: () => boolean; authingWithSso: () => boolean; diff --git a/common/src/abstractions/twoFactor.service.ts b/common/src/abstractions/twoFactor.service.ts new file mode 100644 index 00000000..40df40fb --- /dev/null +++ b/common/src/abstractions/twoFactor.service.ts @@ -0,0 +1,10 @@ +import { TwoFactorProviderType } from '../enums/twoFactorProviderType'; + +export abstract class TwoFactorService { + getSupportedTwoFactorProviders: (win: Window) => any[]; + getDefaultTwoFactorProvider: (webAuthnSupported: boolean) => TwoFactorProviderType; + clearSelectedProvider: () => void; + setProviders: (data: any) => void; + clearProviders: () => void; + providers: Map; +} diff --git a/common/src/services/auth.service.ts b/common/src/services/auth.service.ts index a0c098a1..5377e2f8 100644 --- a/common/src/services/auth.service.ts +++ b/common/src/services/auth.service.ts @@ -2,7 +2,7 @@ import { HashPurpose } from '../enums/hashPurpose'; import { KdfType } from '../enums/kdfType'; import { TwoFactorProviderType } from '../enums/twoFactorProviderType'; -import { Account, AccountData, AccountProfile, AccountTokens } from '../models/domain/account'; +import { AccountProfile, AccountTokens } from '../models/domain/account'; import { AuthResult } from '../models/domain/authResult'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; @@ -22,68 +22,16 @@ import { AuthService as AuthServiceAbstraction } from '../abstractions/auth.serv import { CryptoService } from '../abstractions/crypto.service'; import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; import { EnvironmentService } from '../abstractions/environment.service'; -import { I18nService } from '../abstractions/i18n.service'; import { KeyConnectorService } from '../abstractions/keyConnector.service'; import { LogService } from '../abstractions/log.service'; import { MessagingService } from '../abstractions/messaging.service'; import { PlatformUtilsService } from '../abstractions/platformUtils.service'; import { StateService } from '../abstractions/state.service'; import { TokenService } from '../abstractions/token.service'; -import { VaultTimeoutService } from '../abstractions/vaultTimeout.service'; +import { TwoFactorService } from '../abstractions/twoFactor.service'; import { Utils } from '../misc/utils'; -export const TwoFactorProviders = { - [TwoFactorProviderType.Authenticator]: { - type: TwoFactorProviderType.Authenticator, - name: null as string, - description: null as string, - priority: 1, - sort: 1, - premium: false, - }, - [TwoFactorProviderType.Yubikey]: { - type: TwoFactorProviderType.Yubikey, - name: null as string, - description: null as string, - priority: 3, - sort: 2, - premium: true, - }, - [TwoFactorProviderType.Duo]: { - type: TwoFactorProviderType.Duo, - name: 'Duo', - description: null as string, - priority: 2, - sort: 3, - premium: true, - }, - [TwoFactorProviderType.OrganizationDuo]: { - type: TwoFactorProviderType.OrganizationDuo, - name: 'Duo (Organization)', - description: null as string, - priority: 10, - sort: 4, - premium: false, - }, - [TwoFactorProviderType.Email]: { - type: TwoFactorProviderType.Email, - name: null as string, - description: null as string, - priority: 0, - sort: 6, - premium: false, - }, - [TwoFactorProviderType.WebAuthn]: { - type: TwoFactorProviderType.WebAuthn, - name: null as string, - description: null as string, - priority: 4, - sort: 5, - premium: true, - }, -}; - export class AuthService implements AuthServiceAbstraction { email: string; masterPasswordHash: string; @@ -93,45 +41,21 @@ export class AuthService implements AuthServiceAbstraction { ssoRedirectUrl: string; clientId: string; clientSecret: string; - twoFactorProvidersData: Map; - selectedTwoFactorProviderType: TwoFactorProviderType = null; captchaToken: string; private key: SymmetricCryptoKey; constructor(private cryptoService: CryptoService, protected apiService: ApiService, protected tokenService: TokenService, protected appIdService: AppIdService, - private i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, - private messagingService: MessagingService, private vaultTimeoutService: VaultTimeoutService, + protected platformUtilsService: PlatformUtilsService, private messagingService: MessagingService, private logService: LogService, protected cryptoFunctionService: CryptoFunctionService, private keyConnectorService: KeyConnectorService, protected environmentService: EnvironmentService, - protected stateService: StateService, private setCryptoKeys = true) { - } - - init() { - TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t('emailTitle'); - TwoFactorProviders[TwoFactorProviderType.Email].description = this.i18nService.t('emailDesc'); - - TwoFactorProviders[TwoFactorProviderType.Authenticator].name = this.i18nService.t('authenticatorAppTitle'); - TwoFactorProviders[TwoFactorProviderType.Authenticator].description = - this.i18nService.t('authenticatorAppDesc'); - - TwoFactorProviders[TwoFactorProviderType.Duo].description = this.i18nService.t('duoDesc'); - - TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].name = - 'Duo (' + this.i18nService.t('organization') + ')'; - TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = - this.i18nService.t('duoOrganizationDesc'); - - TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t('webAuthnTitle'); - TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = this.i18nService.t('webAuthnDesc'); - - TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t('yubiKeyTitle'); - TwoFactorProviders[TwoFactorProviderType.Yubikey].description = this.i18nService.t('yubiKeyDesc'); + protected stateService: StateService, private twoFactorService: TwoFactorService, + private setCryptoKeys = true) { } async logIn(email: string, masterPassword: string, captchaToken?: string): Promise { - this.selectedTwoFactorProviderType = null; + this.twoFactorService.clearSelectedProvider(); const key = await this.makePreloginKey(masterPassword, email); const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key); const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key, @@ -141,13 +65,13 @@ export class AuthService implements AuthServiceAbstraction { } async logInSso(code: string, codeVerifier: string, redirectUrl: string, orgId: string): Promise { - this.selectedTwoFactorProviderType = null; + this.twoFactorService.clearSelectedProvider(); return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null, null, null, null, null, null, null, orgId); } async logInApiKey(clientId: string, clientSecret: string): Promise { - this.selectedTwoFactorProviderType = null; + this.twoFactorService.clearSelectedProvider(); return await this.logInHelper(null, null, null, null, null, null, clientId, clientSecret, null, null, null, null, null, null); } @@ -161,26 +85,26 @@ export class AuthService implements AuthServiceAbstraction { async logInComplete(email: string, masterPassword: string, twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean, captchaToken?: string): Promise { - this.selectedTwoFactorProviderType = null; - const key = await this.makePreloginKey(masterPassword, email); - const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key); - const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key, + this.twoFactorService.clearSelectedProvider(); + const key = await this.makePreloginKey(masterPassword, email); + const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key); + const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key, HashPurpose.LocalAuthorization); - return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null, key, + return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null, key, twoFactorProvider, twoFactorToken, remember, captchaToken, null); } async logInSsoComplete(code: string, codeVerifier: string, redirectUrl: string, twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean): Promise { - this.selectedTwoFactorProviderType = null; - return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null, + this.twoFactorService.clearSelectedProvider(); + return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null, null, null, twoFactorProvider, twoFactorToken, remember, null, null); } async logInApiKeyComplete(clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean): Promise { - this.selectedTwoFactorProviderType = null; - return await this.logInHelper(null, null, null, null, null, null, clientId, clientSecret, null, + this.twoFactorService.clearSelectedProvider(); + return await this.logInHelper(null, null, null, null, null, null, clientId, clientSecret, null, twoFactorProvider, twoFactorToken, remember, null, null); } @@ -189,65 +113,16 @@ export class AuthService implements AuthServiceAbstraction { this.messagingService.send('loggedOut'); } - getSupportedTwoFactorProviders(win: Window): any[] { - const providers: any[] = []; - if (this.twoFactorProvidersData == null) { - return providers; - } - - if (this.twoFactorProvidersData.has(TwoFactorProviderType.OrganizationDuo) && - this.platformUtilsService.supportsDuo()) { - providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]); - } - - if (this.twoFactorProvidersData.has(TwoFactorProviderType.Authenticator)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]); - } - - if (this.twoFactorProvidersData.has(TwoFactorProviderType.Yubikey)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]); - } - - if (this.twoFactorProvidersData.has(TwoFactorProviderType.Duo) && this.platformUtilsService.supportsDuo()) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); - } - - if (this.twoFactorProvidersData.has(TwoFactorProviderType.WebAuthn) && this.platformUtilsService.supportsWebAuthn(win)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); - } - - if (this.twoFactorProvidersData.has(TwoFactorProviderType.Email)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Email]); - } - - return providers; + authingWithApiKey(): boolean { + return this.clientId != null && this.clientSecret != null; } - getDefaultTwoFactorProvider(webAuthnSupported: boolean): TwoFactorProviderType { - if (this.twoFactorProvidersData == null) { - return null; - } + authingWithSso(): boolean { + return this.code != null && this.codeVerifier != null && this.ssoRedirectUrl != null; + } - if (this.selectedTwoFactorProviderType != null && - this.twoFactorProvidersData.has(this.selectedTwoFactorProviderType)) { - return this.selectedTwoFactorProviderType; - } - - let providerType: TwoFactorProviderType = null; - let providerPriority = -1; - this.twoFactorProvidersData.forEach((_value, type) => { - const provider = (TwoFactorProviders as any)[type]; - if (provider != null && provider.priority > providerPriority) { - if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { - return; - } - - providerType = type; - providerPriority = provider.priority; - } - }); - - return providerType; + authingWithPassword(): boolean { + return this.email != null && this.masterPasswordHash != null; } async makePreloginKey(masterPassword: string, email: string): Promise { @@ -267,18 +142,6 @@ export class AuthService implements AuthServiceAbstraction { } return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations); } - - authingWithApiKey(): boolean { - return this.clientId != null && this.clientSecret != null; - } - - authingWithSso(): boolean { - return this.code != null && this.codeVerifier != null && this.ssoRedirectUrl != null; - } - - authingWithPassword(): boolean { - return this.email != null && this.masterPasswordHash != null; - } private async createTokenRequest(email: string, hashedPassword: string, code: string, codeVerifier: string, redirectUrl: string, clientId: string, clientSecret: string, twoFactorToken: string, twoFactorProvider: TwoFactorProviderType, remember: boolean, captchaToken: string) { @@ -309,10 +172,8 @@ export class AuthService implements AuthServiceAbstraction { let request: TokenRequest; if (twoFactorToken != null && twoFactorProvider != null) { - console.log('creating 2FA request'); request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, twoFactorProvider, twoFactorToken, remember, captchaToken, deviceRequest); - console.log(request); } else if (storedTwoFactorToken != null) { request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, TwoFactorProviderType.Remember, storedTwoFactorToken, false, captchaToken, deviceRequest); @@ -337,7 +198,7 @@ export class AuthService implements AuthServiceAbstraction { this.clientId = clientId; this.clientSecret = clientSecret; this.key = this.setCryptoKeys ? key : null; - this.twoFactorProvidersData = twoFactorProviders; + this.twoFactorService.setProviders(twoFactorProviders); } private async convertNewUserToKeyConnector(tokenResponse: IdentityTokenResponse, orgId: string) { @@ -401,7 +262,7 @@ export class AuthService implements AuthServiceAbstraction { } private isNewSsoUser(code: string, key: string) { - return code != null && key == null + return code != null && key == null; } private async logInHelper(email: string, hashedPassword: string, localHashedPassword: string, code: string, @@ -426,7 +287,7 @@ export class AuthService implements AuthServiceAbstraction { 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; } @@ -487,7 +348,7 @@ export class AuthService implements AuthServiceAbstraction { this.ssoRedirectUrl = null; this.clientId = null; this.clientSecret = null; - this.twoFactorProvidersData = null; - this.selectedTwoFactorProviderType = null; + this.twoFactorService.clearProviders(); + this.twoFactorService.clearSelectedProvider(); } } diff --git a/common/src/services/twoFactor.service.ts b/common/src/services/twoFactor.service.ts new file mode 100644 index 00000000..2daef55b --- /dev/null +++ b/common/src/services/twoFactor.service.ts @@ -0,0 +1,161 @@ +import { I18nService } from '../abstractions/i18n.service'; +import { PlatformUtilsService } from '../abstractions/platformUtils.service'; +import { TwoFactorService as TwoFactorServiceAbstraction } from '../abstractions/twoFactor.service'; +import { TwoFactorProviderType } from '../enums/twoFactorProviderType'; + +export const TwoFactorProviders = { + [TwoFactorProviderType.Authenticator]: { + type: TwoFactorProviderType.Authenticator, + name: null as string, + description: null as string, + priority: 1, + sort: 1, + premium: false, + }, + [TwoFactorProviderType.Yubikey]: { + type: TwoFactorProviderType.Yubikey, + name: null as string, + description: null as string, + priority: 3, + sort: 2, + premium: true, + }, + [TwoFactorProviderType.Duo]: { + type: TwoFactorProviderType.Duo, + name: 'Duo', + description: null as string, + priority: 2, + sort: 3, + premium: true, + }, + [TwoFactorProviderType.OrganizationDuo]: { + type: TwoFactorProviderType.OrganizationDuo, + name: 'Duo (Organization)', + description: null as string, + priority: 10, + sort: 4, + premium: false, + }, + [TwoFactorProviderType.Email]: { + type: TwoFactorProviderType.Email, + name: null as string, + description: null as string, + priority: 0, + sort: 6, + premium: false, + }, + [TwoFactorProviderType.WebAuthn]: { + type: TwoFactorProviderType.WebAuthn, + name: null as string, + description: null as string, + priority: 4, + sort: 5, + premium: true, + }, +}; + +export class TwoFactorService implements TwoFactorServiceAbstraction { + twoFactorProvidersData: Map; + selectedTwoFactorProviderType: TwoFactorProviderType = null; + + constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService) { } + + init() { + TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t('emailTitle'); + TwoFactorProviders[TwoFactorProviderType.Email].description = this.i18nService.t('emailDesc'); + + TwoFactorProviders[TwoFactorProviderType.Authenticator].name = this.i18nService.t('authenticatorAppTitle'); + TwoFactorProviders[TwoFactorProviderType.Authenticator].description = + this.i18nService.t('authenticatorAppDesc'); + + TwoFactorProviders[TwoFactorProviderType.Duo].description = this.i18nService.t('duoDesc'); + + TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].name = + 'Duo (' + this.i18nService.t('organization') + ')'; + TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = + this.i18nService.t('duoOrganizationDesc'); + + TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t('webAuthnTitle'); + TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = this.i18nService.t('webAuthnDesc'); + + TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t('yubiKeyTitle'); + TwoFactorProviders[TwoFactorProviderType.Yubikey].description = this.i18nService.t('yubiKeyDesc'); + } + + getSupportedTwoFactorProviders(win: Window): any[] { + const providers: any[] = []; + if (this.twoFactorProvidersData == null) { + return providers; + } + + if (this.twoFactorProvidersData.has(TwoFactorProviderType.OrganizationDuo) && + this.platformUtilsService.supportsDuo()) { + providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]); + } + + if (this.twoFactorProvidersData.has(TwoFactorProviderType.Authenticator)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]); + } + + if (this.twoFactorProvidersData.has(TwoFactorProviderType.Yubikey)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]); + } + + if (this.twoFactorProvidersData.has(TwoFactorProviderType.Duo) && this.platformUtilsService.supportsDuo()) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); + } + + if (this.twoFactorProvidersData.has(TwoFactorProviderType.WebAuthn) && this.platformUtilsService.supportsWebAuthn(win)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); + } + + if (this.twoFactorProvidersData.has(TwoFactorProviderType.Email)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Email]); + } + + return providers; + } + + getDefaultTwoFactorProvider(webAuthnSupported: boolean): TwoFactorProviderType { + if (this.twoFactorProvidersData == null) { + return null; + } + + if (this.selectedTwoFactorProviderType != null && + this.twoFactorProvidersData.has(this.selectedTwoFactorProviderType)) { + return this.selectedTwoFactorProviderType; + } + + let providerType: TwoFactorProviderType = null; + let providerPriority = -1; + this.twoFactorProvidersData.forEach((_value, type) => { + const provider = (TwoFactorProviders as any)[type]; + if (provider != null && provider.priority > providerPriority) { + if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { + return; + } + + providerType = type; + providerPriority = provider.priority; + } + }); + + return providerType; + } + + clearSelectedProvider() { + this.selectedTwoFactorProviderType = null; + } + + setProviders(data: Map) { + this.twoFactorProvidersData = data; + } + + clearProviders() { + this.twoFactorProvidersData = null; + } + + get providers() { + return this.twoFactorProvidersData; + } +} diff --git a/spec/common/services/auth.service.spec.ts b/spec/common/services/auth.service.spec.ts index 0c714db1..13ef8173 100644 --- a/spec/common/services/auth.service.spec.ts +++ b/spec/common/services/auth.service.spec.ts @@ -25,6 +25,7 @@ import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKe import { IdentityTokenResponse } from 'jslib-common/models/response/identityTokenResponse'; +import { TwoFactorService } from 'jslib-common/abstractions/twoFactor.service'; import { HashPurpose } from 'jslib-common/enums/hashPurpose'; import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType'; @@ -33,15 +34,14 @@ describe('Cipher Service', () => { let apiService: SubstituteOf; let tokenService: SubstituteOf; let appIdService: SubstituteOf; - let i18nService: SubstituteOf; let platformUtilsService: SubstituteOf; let messagingService: SubstituteOf; - let vaultTimeoutService: SubstituteOf; let logService: SubstituteOf; let cryptoFunctionService: SubstituteOf; let environmentService: SubstituteOf; let keyConnectorService: SubstituteOf; let stateService: SubstituteOf; + let twoFactorService: SubstituteOf; const setCryptoKeys = true; const email = 'hello@world.com'; @@ -84,20 +84,18 @@ describe('Cipher Service', () => { apiService = Substitute.for(); tokenService = Substitute.for(); appIdService = Substitute.for(); - i18nService = Substitute.for(); platformUtilsService = Substitute.for(); messagingService = Substitute.for(); - vaultTimeoutService = Substitute.for(); logService = Substitute.for(); cryptoFunctionService = Substitute.for(); environmentService = Substitute.for(); stateService = Substitute.for(); keyConnectorService = Substitute.for(); + twoFactorService = Substitute.for(); - authService = new AuthService(cryptoService, apiService, tokenService, appIdService, i18nService, - platformUtilsService, messagingService, vaultTimeoutService, logService, cryptoFunctionService, - keyConnectorService, environmentService, stateService, setCryptoKeys); - authService.init(); + authService = new AuthService(cryptoService, apiService, tokenService, appIdService, + platformUtilsService, messagingService, logService, cryptoFunctionService, + keyConnectorService, environmentService, stateService, twoFactorService, setCryptoKeys); }); function logInSetup() { @@ -237,9 +235,9 @@ describe('Cipher Service', () => { apiService.postIdentityToken(Arg.any()).resolves(tokenResponse); // Re-init authService with setCryptoKeys = false - authService = new AuthService(cryptoService, apiService, tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService, logService, cryptoFunctionService, - keyConnectorService, environmentService, stateService, false); - authService.init(); + authService = new AuthService(cryptoService, apiService, tokenService, appIdService, platformUtilsService, + messagingService, logService, cryptoFunctionService, keyConnectorService, environmentService, stateService, + twoFactorService, false); // Act const result = await authService.logIn(email, masterPassword);