mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-23 16:38:45 +01:00
Auth/PM-4596 - Extract PIN and Biometrics unlock method logic into re-useable services for user verification (#7107)
* PM-4596 - PinCryptoService first draft * PM-4596 - PinCryptoService - Refactor pinKeyEncryptedKey retrievals out into own method getPinKeyEncryptedKeys * PM-4596 - npm ci + npm run prettier to fix lint issues * PM-4596 - PinCryptoService - Add kdf types * PM-4596 - PinCryptoService - Refactor pin validation into own helper method. * PM-4596 - Rename pin-crypto.service.ts to pin-crypto.service.implementation.ts * PM-4596 - PinCryptoService - add additional logging for error states. * PM-4596 - JslibServicesModule - register new PinCryptoService and PinCryptoServiceAbstraction * PM-4596 - PinCryptoService - modify decryptUserKeyWithPin signature to not require email to match MP verification process in user verification service. * PM-4596 - Lock components - use new PinCryptoService.decryptUserKeyWithPin(...) to get user key + refactor base comp unlock with pin method to improve * PM-4596 - Lock component - if too many invalid attempts, added toast explaining that we were logging the user out due to excess PIN entry attempts * PM-4596 - UserVerificationService - (1) Refactor verifyUser(...) to use switch + separate methods for a cleaner parent method + better extensibility for PIN & biometrics which are TBD (2) Add PIN support to validateInput(...) * PM-4596 - UserVerificationService - add PIN and biometrics functions to verifyUser(...) * PM-4596 - PinCryptoService Spec - start test file - instantiates properly * PM-4596 - PinCryptoService tests - WIP * PM-4596 - PinCryptoService tests - WIP - got success cases working * PM-4596 - pin-crypto.service.implementation.spec.ts renamed to pin-crypto.service.spec.ts * PM-4596 - PinCryptoService.getPinKeyEncryptedKeys(...) - add comment + var name change for clarity * PM-4596 - PinCryptoService tests - test invalid, null return scenarios * PM-4596 - CLI - bw.ts - update UserVerificationService instantiation to include new pinCryptoService * PM-4596 - PinCryptoService - import VaultTimeoutSettingsServiceAbstraction instead of implementation for factory creation to get browser building * PM-4596 - (1) Create pinCryptoServiceFactory for browser background (2) Add it to the existing userVerificationServiceFactory * PM-4596 - Browser - Main.background.ts - Add pinCryptoService and add to userVerificationService dependencies * PM-4596 - UserVerificationService - per PR feedback simplify returns of verifyUserByPIN(...) and verifyUserByBiometrics(...) * PM-4596 - Messages.json on desktop & browser - per PR feedback, adjust tooManyInvalidPinEntryAttemptsLoggingOut translation text to remove "you" * PM-4596 - VerificationType enum - fix line copy mistake and give BIOMETRICS own, unique value. * PM-4596 - VerificationType - rename BIOMETRICS to Biometrics to match existing MasterPassword value case. * PM-4596 - Update verification type to consider whether or not a secret exists as we have added a new verification which doesn't have a type. Add new server and client side verification types. Update all relevant code to pass compilation checks. * PM-4596 - More verification type tweaking * PM-4596 - Verification - verificationHasSecret - tweak logic to be more dynamic and flexible for future verification types * PM-4596 - UpdateTempPasswordComp - use new MasterPasswordVerification * PM-4596 - Desktop - DeleteAcctComp - use VerificationWithSecret to solve compile error w/ accessing secret * PM-4596 - Per discussions with Andreas & Will, move new Pin Crypto services into libs/auth + added @bitwarden/auth path to CLI tsconfig + added new, required index.ts files for exporting service abstractions & implementations * PM-4596 - Fixed missed import fixes for lock components across clients for pin crypto service after moving into @bitwarden/auth * PM-4596 - More PinCryptoService import fixes to get browser & desktop building * PM-4596 - Update desktop lock comp tests to pass by providing new pin crypto service. * PM-4596 - User verification service -update todo * PM-4596 - PinCryptoService - per PR feedback, fix auto import wrong paths. * PM-4596 - PinCryptoService tests - fix imports per PR feedback * PM-4596 - UserVerificationSvc - rename method to validateSecretInput per PR feedback * Fix imports * PM-4596 - PinCryptoService - Refactor naming for clarity and move test cases into describes per PR feedback * reorg libs/auth; expose only libs/auth/core to cli app * PM-4596 - UserVerification - Resolve import issue with importing from libs/auth. Can't use @bitwarden/auth for whatever reason. * PM-4596 - Fix desktop build by fixing import * PM-4596 - Provide PinCryptoService to UserVerificationService * PM-4596 - PinCryptoServiceFactory - you cannot import services from @bitwarden/auth in the background b/c it brings along the libs/auth/components and introduces angular into the background context which doesn't have access to angular which causes random test failures. So, we must separate out the core services just like the CLI to only bring along the angular agnostic services from core. * PM-4596 - Refactor libs/auth to have angular / common + update all imports per discussion with Matt & Will. Introduced circular dep between PinCryptoService + VaultTimeoutSettingsService + UserVerificationService * PM-4596 - VaultTimeoutSettingsService - Refactor UserVerificationService out of the service and update all service instantiations and tests. The use of the UserVerificationService.hasMasterPassword method no longer needs to be used for backwards compatibility. This resolves the circular dependency between the PinCryptoService, the UserVerificationService, and the VaultTimeoutSettingsService. We will likely refactor the hasMasterPassword method out of the UserVerificationService in the future. * PM-4596 - Update CL tsconfig.libs.json to add new auth/common and auth/angular paths for jslib-services.module imports of pin crypto service to work and for test code coverage to run successfully. * PM-4596 - Address PR feedback * PM-4596 - Update root tsconfig (only used by storybook) to add new libs/auth paths to fix chromatic build pipeline. * PM-4596 - Actually update tsconfig with proper routes to fix storybook * PM-4596 - UserVerificationService - verifyUserByBiometrics - add error handling logic to convert failed or cancelled biometrics verification to a usable boolean * PM-4596 - Add missing await * PM-4596 - (1) Add log service and log to user verification service biometric flow to ensure errors are at least revealed to the console (2) Fix factory missing PinCryptoServiceInitOptions * PM-4596 - Use the correct log service abstraction * PM-4596 - Remove unused types per PR review --------- Co-authored-by: William Martin <contact@willmartian.com>
This commit is contained in:
parent
38c525b2ab
commit
756c02cec2
@ -1493,6 +1493,9 @@
|
||||
"invalidPin": {
|
||||
"message": "Invalid PIN code."
|
||||
},
|
||||
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
||||
"message": "Too many invalid PIN entry attempts. Logging out."
|
||||
},
|
||||
"unlockWithBiometrics": {
|
||||
"message": "Unlock with biometrics"
|
||||
},
|
||||
|
@ -0,0 +1,49 @@
|
||||
import { PinCryptoServiceAbstraction, PinCryptoService } from "@bitwarden/auth/common";
|
||||
|
||||
import {
|
||||
VaultTimeoutSettingsServiceInitOptions,
|
||||
vaultTimeoutSettingsServiceFactory,
|
||||
} from "../../../background/service-factories/vault-timeout-settings-service.factory";
|
||||
import {
|
||||
CryptoServiceInitOptions,
|
||||
cryptoServiceFactory,
|
||||
} from "../../../platform/background/service-factories/crypto-service.factory";
|
||||
import {
|
||||
FactoryOptions,
|
||||
CachedServices,
|
||||
factory,
|
||||
} from "../../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
LogServiceInitOptions,
|
||||
logServiceFactory,
|
||||
} from "../../../platform/background/service-factories/log-service.factory";
|
||||
import {
|
||||
StateServiceInitOptions,
|
||||
stateServiceFactory,
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
|
||||
type PinCryptoServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type PinCryptoServiceInitOptions = PinCryptoServiceFactoryOptions &
|
||||
StateServiceInitOptions &
|
||||
CryptoServiceInitOptions &
|
||||
VaultTimeoutSettingsServiceInitOptions &
|
||||
LogServiceInitOptions;
|
||||
|
||||
export function pinCryptoServiceFactory(
|
||||
cache: { pinCryptoService?: PinCryptoServiceAbstraction } & CachedServices,
|
||||
opts: PinCryptoServiceInitOptions,
|
||||
): Promise<PinCryptoServiceAbstraction> {
|
||||
return factory(
|
||||
cache,
|
||||
"pinCryptoService",
|
||||
opts,
|
||||
async () =>
|
||||
new PinCryptoService(
|
||||
await stateServiceFactory(cache, opts),
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await vaultTimeoutSettingsServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
@ -14,11 +14,16 @@ import {
|
||||
I18nServiceInitOptions,
|
||||
i18nServiceFactory,
|
||||
} from "../../../platform/background/service-factories/i18n-service.factory";
|
||||
import {
|
||||
LogServiceInitOptions,
|
||||
logServiceFactory,
|
||||
} from "../../../platform/background/service-factories/log-service.factory";
|
||||
import {
|
||||
StateServiceInitOptions,
|
||||
stateServiceFactory,
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
|
||||
import { PinCryptoServiceInitOptions, pinCryptoServiceFactory } from "./pin-crypto-service.factory";
|
||||
import {
|
||||
UserVerificationApiServiceInitOptions,
|
||||
userVerificationApiServiceFactory,
|
||||
@ -30,7 +35,9 @@ export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryO
|
||||
StateServiceInitOptions &
|
||||
CryptoServiceInitOptions &
|
||||
I18nServiceInitOptions &
|
||||
UserVerificationApiServiceInitOptions;
|
||||
UserVerificationApiServiceInitOptions &
|
||||
PinCryptoServiceInitOptions &
|
||||
LogServiceInitOptions;
|
||||
|
||||
export function userVerificationServiceFactory(
|
||||
cache: { userVerificationService?: AbstractUserVerificationService } & CachedServices,
|
||||
@ -46,6 +53,8 @@ export function userVerificationServiceFactory(
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await i18nServiceFactory(cache, opts),
|
||||
await userVerificationApiServiceFactory(cache, opts),
|
||||
await pinCryptoServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { Component, NgZone } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
|
||||
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
@ -56,6 +57,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
dialogService: DialogService,
|
||||
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
userVerificationService: UserVerificationService,
|
||||
pinCryptoService: PinCryptoServiceAbstraction,
|
||||
private routerService: BrowserRouterService,
|
||||
) {
|
||||
super(
|
||||
@ -77,6 +79,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
dialogService,
|
||||
deviceTrustCryptoService,
|
||||
userVerificationService,
|
||||
pinCryptoService,
|
||||
);
|
||||
this.successRoute = "/tabs/current";
|
||||
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { PinCryptoServiceAbstraction, PinCryptoService } from "@bitwarden/auth/common";
|
||||
import { AvatarUpdateService as AvatarUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||
@ -246,6 +247,7 @@ export default class MainBackground {
|
||||
authRequestCryptoService: AuthRequestCryptoServiceAbstraction;
|
||||
accountService: AccountServiceAbstraction;
|
||||
globalStateProvider: GlobalStateProvider;
|
||||
pinCryptoService: PinCryptoServiceAbstraction;
|
||||
singleUserStateProvider: SingleUserStateProvider;
|
||||
activeUserStateProvider: ActiveUserStateProvider;
|
||||
derivedStateProvider: DerivedStateProvider;
|
||||
@ -482,11 +484,20 @@ export default class MainBackground {
|
||||
|
||||
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
|
||||
|
||||
this.pinCryptoService = new PinCryptoService(
|
||||
this.stateService,
|
||||
this.cryptoService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
this.userVerificationService = new UserVerificationService(
|
||||
this.stateService,
|
||||
this.cryptoService,
|
||||
this.i18nService,
|
||||
this.userVerificationApiService,
|
||||
this.pinCryptoService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
this.configApiService = new ConfigApiService(this.apiService, this.authService);
|
||||
@ -524,7 +535,6 @@ export default class MainBackground {
|
||||
this.tokenService,
|
||||
this.policyService,
|
||||
this.stateService,
|
||||
this.userVerificationService,
|
||||
);
|
||||
|
||||
this.vaultFilterService = new VaultFilterService(
|
||||
|
@ -9,10 +9,6 @@ import {
|
||||
tokenServiceFactory,
|
||||
TokenServiceInitOptions,
|
||||
} from "../../auth/background/service-factories/token-service.factory";
|
||||
import {
|
||||
userVerificationServiceFactory,
|
||||
UserVerificationServiceInitOptions,
|
||||
} from "../../auth/background/service-factories/user-verification-service.factory";
|
||||
import {
|
||||
CryptoServiceInitOptions,
|
||||
cryptoServiceFactory,
|
||||
@ -33,8 +29,7 @@ export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsService
|
||||
CryptoServiceInitOptions &
|
||||
TokenServiceInitOptions &
|
||||
PolicyServiceInitOptions &
|
||||
StateServiceInitOptions &
|
||||
UserVerificationServiceInitOptions;
|
||||
StateServiceInitOptions;
|
||||
|
||||
export function vaultTimeoutSettingsServiceFactory(
|
||||
cache: { vaultTimeoutSettingsService?: AbstractVaultTimeoutSettingsService } & CachedServices,
|
||||
@ -50,7 +45,6 @@ export function vaultTimeoutSettingsServiceFactory(
|
||||
await tokenServiceFactory(cache, opts),
|
||||
await policyServiceFactory(cache, opts),
|
||||
await stateServiceFactory(cache, opts),
|
||||
await userVerificationServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
} from "rxjs";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { FingerprintDialogComponent } from "@bitwarden/auth";
|
||||
import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
|
@ -12,7 +12,8 @@
|
||||
"paths": {
|
||||
"@bitwarden/admin-console": ["../../libs/admin-console/src"],
|
||||
"@bitwarden/angular/*": ["../../libs/angular/src/*"],
|
||||
"@bitwarden/auth": ["../../libs/auth/src"],
|
||||
"@bitwarden/auth/common": ["../../libs/auth/src/common"],
|
||||
"@bitwarden/auth/angular": ["../../libs/auth/src/angular"],
|
||||
"@bitwarden/billing": ["../../libs/billing/src"],
|
||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||
"@bitwarden/components": ["../../libs/components/src"],
|
||||
|
@ -4,6 +4,7 @@ import * as path from "path";
|
||||
import * as program from "commander";
|
||||
import * as jsdom from "jsdom";
|
||||
|
||||
import { PinCryptoServiceAbstraction, PinCryptoService } from "@bitwarden/auth/common";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
@ -160,6 +161,7 @@ export class Main {
|
||||
cipherFileUploadService: CipherFileUploadService;
|
||||
keyConnectorService: KeyConnectorService;
|
||||
userVerificationService: UserVerificationService;
|
||||
pinCryptoService: PinCryptoServiceAbstraction;
|
||||
stateService: StateService;
|
||||
organizationService: OrganizationService;
|
||||
providerService: ProviderService;
|
||||
@ -433,11 +435,20 @@ export class Main {
|
||||
const lockedCallback = async (userId?: string) =>
|
||||
await this.cryptoService.clearStoredUserKey(KeySuffixOptions.Auto);
|
||||
|
||||
this.pinCryptoService = new PinCryptoService(
|
||||
this.stateService,
|
||||
this.cryptoService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
this.userVerificationService = new UserVerificationService(
|
||||
this.stateService,
|
||||
this.cryptoService,
|
||||
this.i18nService,
|
||||
this.userVerificationApiService,
|
||||
this.pinCryptoService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
||||
@ -445,7 +456,6 @@ export class Main {
|
||||
this.tokenService,
|
||||
this.policyService,
|
||||
this.stateService,
|
||||
this.userVerificationService,
|
||||
);
|
||||
|
||||
this.vaultTimeoutService = new VaultTimeoutService(
|
||||
|
@ -13,6 +13,8 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@bitwarden/common/spec": ["../../libs/common/spec"],
|
||||
"@bitwarden/auth/common": ["../../libs/auth/src/common"],
|
||||
"@bitwarden/auth/angular": ["../../libs/auth/src/angular"],
|
||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||
"@bitwarden/importer/core": ["../../libs/importer/src"],
|
||||
"@bitwarden/exporter/*": ["../../libs/exporter/src/*"],
|
||||
|
@ -15,7 +15,7 @@ import { firstValueFrom, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { FingerprintDialogComponent } from "@bitwarden/auth";
|
||||
import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
|
||||
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
|
@ -4,7 +4,7 @@ import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
|
||||
import { Verification } from "@bitwarden/common/auth/types/verification";
|
||||
import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
@ -33,7 +33,7 @@ import { UserVerificationComponent } from "../app/components/user-verification.c
|
||||
})
|
||||
export class DeleteAccountComponent {
|
||||
deleteForm = this.formBuilder.group({
|
||||
verification: undefined as Verification | undefined,
|
||||
verification: undefined as VerificationWithSecret | undefined,
|
||||
});
|
||||
|
||||
constructor(
|
||||
|
@ -6,6 +6,7 @@ import { of } from "rxjs";
|
||||
|
||||
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
@ -133,6 +134,10 @@ describe("LockComponent", () => {
|
||||
provide: UserVerificationService,
|
||||
useValue: mock<UserVerificationService>(),
|
||||
},
|
||||
{
|
||||
provide: PinCryptoServiceAbstraction,
|
||||
useValue: mock<PinCryptoServiceAbstraction>(),
|
||||
},
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
|
@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { switchMap } from "rxjs";
|
||||
|
||||
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
|
||||
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
@ -56,6 +57,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
dialogService: DialogService,
|
||||
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
userVerificationService: UserVerificationService,
|
||||
pinCryptoService: PinCryptoServiceAbstraction,
|
||||
) {
|
||||
super(
|
||||
router,
|
||||
@ -76,6 +78,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
dialogService,
|
||||
deviceTrustCryptoService,
|
||||
userVerificationService,
|
||||
pinCryptoService,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1392,6 +1392,9 @@
|
||||
"invalidPin": {
|
||||
"message": "Invalid PIN code."
|
||||
},
|
||||
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
||||
"message": "Too many invalid PIN entry attempts. Logging out."
|
||||
},
|
||||
"unlockWithWindowsHello": {
|
||||
"message": "Unlock with Windows Hello"
|
||||
},
|
||||
|
@ -12,7 +12,8 @@
|
||||
"paths": {
|
||||
"@bitwarden/admin-console": ["../../libs/admin-console/src"],
|
||||
"@bitwarden/angular/*": ["../../libs/angular/src/*"],
|
||||
"@bitwarden/auth": ["../../libs/auth/src"],
|
||||
"@bitwarden/auth/common": ["../../libs/auth/src/common"],
|
||||
"@bitwarden/auth/angular": ["../../libs/auth/src/angular"],
|
||||
"@bitwarden/billing": ["../../libs/billing/src"],
|
||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||
"@bitwarden/components": ["../../libs/components/src"],
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth";
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
|
||||
|
||||
import { LooseComponentsModule } from "../../../shared";
|
||||
import { SharedOrganizationModule } from "../shared";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
|
||||
import { RotateableKeySet } from "@bitwarden/auth";
|
||||
import { RotateableKeySet } from "@bitwarden/auth/common";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
@ -2,7 +2,7 @@ import { randomBytes } from "crypto";
|
||||
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { RotateableKeySet } from "@bitwarden/auth";
|
||||
import { RotateableKeySet } from "@bitwarden/auth/common";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { WebAuthnLoginPrfCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-crypto.service.abstraction";
|
||||
import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Injectable, Optional } from "@angular/core";
|
||||
import { BehaviorSubject, filter, from, map, Observable, shareReplay, switchMap, tap } from "rxjs";
|
||||
|
||||
import { PrfKeySet } from "@bitwarden/auth";
|
||||
import { PrfKeySet } from "@bitwarden/auth/common";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { WebAuthnLoginPrfCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-crypto.service.abstraction";
|
||||
import { WebAuthnLoginCredentialAssertionOptionsView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion-options.view";
|
||||
|
@ -2,6 +2,7 @@ import { Component, NgZone } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
|
||||
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
@ -43,6 +44,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
dialogService: DialogService,
|
||||
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
userVerificationService: UserVerificationService,
|
||||
pinCryptoService: PinCryptoServiceAbstraction,
|
||||
) {
|
||||
super(
|
||||
router,
|
||||
@ -63,6 +65,7 @@ export class LockComponent extends BaseLockComponent {
|
||||
dialogService,
|
||||
deviceTrustCryptoService,
|
||||
userVerificationService,
|
||||
pinCryptoService,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth";
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
|
||||
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth";
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
|
||||
|
||||
import { SharedModule } from "../../shared";
|
||||
import { EmergencyAccessModule } from "../emergency-access";
|
||||
|
@ -3,7 +3,7 @@ import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { firstValueFrom, map, Observable } from "rxjs";
|
||||
|
||||
import { PrfKeySet } from "@bitwarden/auth";
|
||||
import { PrfKeySet } from "@bitwarden/auth/common";
|
||||
import { Verification } from "@bitwarden/common/auth/types/verification";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth";
|
||||
import { PasswordCalloutComponent } from "@bitwarden/auth/angular";
|
||||
|
||||
import { OrganizationSwitcherComponent } from "../admin-console/components/organization-switcher.component";
|
||||
import { OrganizationLayoutComponent } from "../admin-console/organizations/layouts/organization-layout.component";
|
||||
|
@ -7,7 +7,8 @@
|
||||
"paths": {
|
||||
"@bitwarden/admin-console": ["../../libs/admin-console/src"],
|
||||
"@bitwarden/angular/*": ["../../libs/angular/src/*"],
|
||||
"@bitwarden/auth": ["../../libs/auth/src"],
|
||||
"@bitwarden/auth/common": ["../../libs/auth/src/common"],
|
||||
"@bitwarden/auth/angular": ["../../libs/auth/src/angular"],
|
||||
"@bitwarden/billing": ["../../libs/billing/src"],
|
||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||
"@bitwarden/components": ["../../libs/components/src"],
|
||||
|
@ -3,6 +3,7 @@ import { Router } from "@angular/router";
|
||||
import { firstValueFrom, Subject } from "rxjs";
|
||||
import { concatMap, take, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
@ -23,7 +24,6 @@ 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 { HashPurpose, KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { PinLockType } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@ -73,6 +73,7 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||
protected dialogService: DialogService,
|
||||
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
protected userVerificationService: UserVerificationService,
|
||||
protected pinCryptoService: PinCryptoServiceAbstraction,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -150,78 +151,41 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private async doUnlockWithPin() {
|
||||
let failed = true;
|
||||
const MAX_INVALID_PIN_ENTRY_ATTEMPTS = 5;
|
||||
|
||||
try {
|
||||
const kdf = await this.stateService.getKdfType();
|
||||
const kdfConfig = await this.stateService.getKdfConfig();
|
||||
let userKeyPin: EncString;
|
||||
let oldPinKey: EncString;
|
||||
switch (this.pinStatus) {
|
||||
case "PERSISTANT": {
|
||||
userKeyPin = await this.stateService.getPinKeyEncryptedUserKey();
|
||||
const oldEncryptedPinKey = await this.stateService.getEncryptedPinProtected();
|
||||
oldPinKey = oldEncryptedPinKey ? new EncString(oldEncryptedPinKey) : undefined;
|
||||
break;
|
||||
}
|
||||
case "TRANSIENT": {
|
||||
userKeyPin = await this.stateService.getPinKeyEncryptedUserKeyEphemeral();
|
||||
oldPinKey = await this.stateService.getDecryptedPinProtected();
|
||||
break;
|
||||
}
|
||||
case "DISABLED": {
|
||||
throw new Error("Pin is disabled");
|
||||
}
|
||||
default: {
|
||||
const _exhaustiveCheck: never = this.pinStatus;
|
||||
return _exhaustiveCheck;
|
||||
}
|
||||
}
|
||||
const userKey = await this.pinCryptoService.decryptUserKeyWithPin(this.pin);
|
||||
|
||||
let userKey: UserKey;
|
||||
if (oldPinKey) {
|
||||
userKey = await this.cryptoService.decryptAndMigrateOldPinKey(
|
||||
this.pinStatus === "TRANSIENT",
|
||||
this.pin,
|
||||
this.email,
|
||||
kdf,
|
||||
kdfConfig,
|
||||
oldPinKey,
|
||||
);
|
||||
} else {
|
||||
userKey = await this.cryptoService.decryptUserKeyWithPin(
|
||||
this.pin,
|
||||
this.email,
|
||||
kdf,
|
||||
kdfConfig,
|
||||
userKeyPin,
|
||||
);
|
||||
}
|
||||
|
||||
const protectedPin = await this.stateService.getProtectedPin();
|
||||
const decryptedPin = await this.cryptoService.decryptToUtf8(
|
||||
new EncString(protectedPin),
|
||||
userKey,
|
||||
);
|
||||
failed = decryptedPin !== this.pin;
|
||||
|
||||
if (!failed) {
|
||||
if (userKey) {
|
||||
await this.setUserKeyAndContinue(userKey);
|
||||
return; // successfully unlocked
|
||||
}
|
||||
} catch {
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
// Failure state: invalid PIN or failed decryption
|
||||
this.invalidPinAttempts++;
|
||||
if (this.invalidPinAttempts >= 5) {
|
||||
|
||||
// Log user out if they have entered an invalid PIN too many times
|
||||
if (this.invalidPinAttempts >= MAX_INVALID_PIN_ENTRY_ATTEMPTS) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("tooManyInvalidPinEntryAttemptsLoggingOut"),
|
||||
);
|
||||
this.messagingService.send("logout");
|
||||
return;
|
||||
}
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("invalidPin"),
|
||||
);
|
||||
} catch {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("unexpectedError"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import { VerificationType } from "@bitwarden/common/auth/enums/verification-type
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
|
||||
import { Verification } from "@bitwarden/common/auth/types/verification";
|
||||
import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@ -31,7 +31,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
showPassword = false;
|
||||
reason: ForceSetPasswordReason = ForceSetPasswordReason.None;
|
||||
verification: Verification = {
|
||||
verification: MasterPasswordVerification = {
|
||||
type: VerificationType.MasterPassword,
|
||||
secret: "",
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { LOCALE_ID, NgModule } from "@angular/core";
|
||||
|
||||
import { PinCryptoServiceAbstraction, PinCryptoService } from "@bitwarden/auth/common";
|
||||
import { AvatarUpdateService as AccountUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||
@ -478,7 +479,6 @@ import { ModalService } from "./modal.service";
|
||||
TokenServiceAbstraction,
|
||||
PolicyServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
UserVerificationServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -628,6 +628,8 @@ import { ModalService } from "./modal.service";
|
||||
CryptoServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
UserVerificationApiServiceAbstraction,
|
||||
PinCryptoServiceAbstraction,
|
||||
LogService,
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -769,6 +771,17 @@ import { ModalService } from "./modal.service";
|
||||
useClass: AuthRequestCryptoServiceImplementation,
|
||||
deps: [CryptoServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: PinCryptoServiceAbstraction,
|
||||
useClass: PinCryptoService,
|
||||
deps: [
|
||||
StateServiceAbstraction,
|
||||
CryptoServiceAbstraction,
|
||||
VaultTimeoutSettingsServiceAbstraction,
|
||||
LogService,
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
provide: WebAuthnLoginPrfCryptoServiceAbstraction,
|
||||
useClass: WebAuthnLoginPrfCryptoService,
|
||||
|
5
libs/auth/src/angular/index.ts
Normal file
5
libs/auth/src/angular/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* This barrel file should only contain Angular exports
|
||||
*/
|
||||
export * from "./fingerprint-dialog/fingerprint-dialog.component";
|
||||
export * from "./password-callout/password-callout.component";
|
1
libs/auth/src/common/abstractions/index.ts
Normal file
1
libs/auth/src/common/abstractions/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./pin-crypto.service.abstraction";
|
@ -0,0 +1,4 @@
|
||||
import { UserKey } from "../../../../common/src/platform/models/domain/symmetric-crypto-key";
|
||||
export abstract class PinCryptoServiceAbstraction {
|
||||
decryptUserKeyWithPin: (pin: string) => Promise<UserKey | null>;
|
||||
}
|
6
libs/auth/src/common/index.ts
Normal file
6
libs/auth/src/common/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* This barrel file should only contain non-Angular exports
|
||||
*/
|
||||
export * from "./abstractions";
|
||||
export * from "./models";
|
||||
export * from "./services";
|
1
libs/auth/src/common/services/index.ts
Normal file
1
libs/auth/src/common/services/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./pin-crypto/pin-crypto.service.implementation";
|
@ -0,0 +1,106 @@
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { KdfType } from "@bitwarden/common/platform/enums";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { PinLockType } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
|
||||
|
||||
import { PinCryptoServiceAbstraction } from "../../abstractions/pin-crypto.service.abstraction";
|
||||
|
||||
export class PinCryptoService implements PinCryptoServiceAbstraction {
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private cryptoService: CryptoService,
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
async decryptUserKeyWithPin(pin: string): Promise<UserKey | null> {
|
||||
try {
|
||||
const pinLockType: PinLockType = await this.vaultTimeoutSettingsService.isPinLockSet();
|
||||
|
||||
const { pinKeyEncryptedUserKey, oldPinKeyEncryptedMasterKey } =
|
||||
await this.getPinKeyEncryptedKeys(pinLockType);
|
||||
|
||||
const kdf: KdfType = await this.stateService.getKdfType();
|
||||
const kdfConfig: KdfConfig = await this.stateService.getKdfConfig();
|
||||
let userKey: UserKey;
|
||||
const email = await this.stateService.getEmail();
|
||||
if (oldPinKeyEncryptedMasterKey) {
|
||||
userKey = await this.cryptoService.decryptAndMigrateOldPinKey(
|
||||
pinLockType === "TRANSIENT",
|
||||
pin,
|
||||
email,
|
||||
kdf,
|
||||
kdfConfig,
|
||||
oldPinKeyEncryptedMasterKey,
|
||||
);
|
||||
} else {
|
||||
userKey = await this.cryptoService.decryptUserKeyWithPin(
|
||||
pin,
|
||||
email,
|
||||
kdf,
|
||||
kdfConfig,
|
||||
pinKeyEncryptedUserKey,
|
||||
);
|
||||
}
|
||||
|
||||
if (!userKey) {
|
||||
this.logService.error(`User key null after pin key decryption.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(await this.validatePin(userKey, pin))) {
|
||||
this.logService.error(`Pin key decryption successful but pin validation failed.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return userKey;
|
||||
} catch (error) {
|
||||
this.logService.error(`Error decrypting user key with pin: ${error}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: oldPinKeyEncryptedMasterKey is only used for migrating old pin keys
|
||||
// and will be null for all migrated accounts
|
||||
private async getPinKeyEncryptedKeys(
|
||||
pinLockType: PinLockType,
|
||||
): Promise<{ pinKeyEncryptedUserKey: EncString; oldPinKeyEncryptedMasterKey?: EncString }> {
|
||||
switch (pinLockType) {
|
||||
case "PERSISTANT": {
|
||||
const pinKeyEncryptedUserKey = await this.stateService.getPinKeyEncryptedUserKey();
|
||||
const oldPinKeyEncryptedMasterKey = await this.stateService.getEncryptedPinProtected();
|
||||
return {
|
||||
pinKeyEncryptedUserKey,
|
||||
oldPinKeyEncryptedMasterKey: oldPinKeyEncryptedMasterKey
|
||||
? new EncString(oldPinKeyEncryptedMasterKey)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
case "TRANSIENT": {
|
||||
const pinKeyEncryptedUserKey = await this.stateService.getPinKeyEncryptedUserKeyEphemeral();
|
||||
const oldPinKeyEncryptedMasterKey = await this.stateService.getDecryptedPinProtected();
|
||||
return { pinKeyEncryptedUserKey, oldPinKeyEncryptedMasterKey };
|
||||
}
|
||||
case "DISABLED":
|
||||
throw new Error("Pin is disabled");
|
||||
default: {
|
||||
// Compile-time check for exhaustive switch
|
||||
const _exhaustiveCheck: never = pinLockType;
|
||||
return _exhaustiveCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async validatePin(userKey: UserKey, pin: string): Promise<boolean> {
|
||||
const protectedPin = await this.stateService.getProtectedPin();
|
||||
const decryptedPin = await this.cryptoService.decryptToUtf8(
|
||||
new EncString(protectedPin),
|
||||
userKey,
|
||||
);
|
||||
return decryptedPin === pin;
|
||||
}
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import {
|
||||
SymmetricCryptoKey,
|
||||
UserKey,
|
||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import {
|
||||
VaultTimeoutSettingsService,
|
||||
PinLockType,
|
||||
} from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
|
||||
|
||||
import { PinCryptoService } from "./pin-crypto.service.implementation";
|
||||
describe("PinCryptoService", () => {
|
||||
let pinCryptoService: PinCryptoService;
|
||||
|
||||
const stateService = mock<StateService>();
|
||||
const cryptoService = mock<CryptoService>();
|
||||
const vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||
const logService = mock<LogService>();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
pinCryptoService = new PinCryptoService(
|
||||
stateService,
|
||||
cryptoService,
|
||||
vaultTimeoutSettingsService,
|
||||
logService,
|
||||
);
|
||||
});
|
||||
|
||||
it("instantiates", () => {
|
||||
expect(pinCryptoService).not.toBeFalsy();
|
||||
});
|
||||
|
||||
describe("decryptUserKeyWithPin(...)", () => {
|
||||
const mockPin = "1234";
|
||||
const mockProtectedPin = "protectedPin";
|
||||
const DEFAULT_PBKDF2_ITERATIONS = 600000;
|
||||
const mockUserEmail = "user@example.com";
|
||||
const mockUserKey = new SymmetricCryptoKey(randomBytes(32)) as UserKey;
|
||||
|
||||
function setupDecryptUserKeyWithPinMocks(
|
||||
pinLockType: PinLockType,
|
||||
migrationStatus: "PRE" | "POST" = "POST",
|
||||
) {
|
||||
vaultTimeoutSettingsService.isPinLockSet.mockResolvedValue(pinLockType);
|
||||
|
||||
stateService.getKdfConfig.mockResolvedValue(new KdfConfig(DEFAULT_PBKDF2_ITERATIONS));
|
||||
stateService.getEmail.mockResolvedValue(mockUserEmail);
|
||||
|
||||
if (migrationStatus === "PRE") {
|
||||
cryptoService.decryptAndMigrateOldPinKey.mockResolvedValue(mockUserKey);
|
||||
} else {
|
||||
cryptoService.decryptUserKeyWithPin.mockResolvedValue(mockUserKey);
|
||||
}
|
||||
|
||||
mockPinEncryptedKeyDataByPinLockType(pinLockType, migrationStatus);
|
||||
|
||||
stateService.getProtectedPin.mockResolvedValue(mockProtectedPin);
|
||||
cryptoService.decryptToUtf8.mockResolvedValue(mockPin);
|
||||
}
|
||||
|
||||
// Note: both pinKeyEncryptedUserKeys use encryptionType: 2 (AesCbc256_HmacSha256_B64)
|
||||
const pinKeyEncryptedUserKeyEphemeral = new EncString(
|
||||
"2.gbauOANURUHqvhLTDnva1A==|nSW+fPumiuTaDB/s12+JO88uemV6rhwRSR+YR1ZzGr5j6Ei3/h+XEli2Unpz652NlZ9NTuRpHxeOqkYYJtp7J+lPMoclgteXuAzUu9kqlRc=|DeUFkhIwgkGdZA08bDnDqMMNmZk21D+H5g8IostPKAY=",
|
||||
);
|
||||
|
||||
const pinKeyEncryptedUserKeyPersistant = new EncString(
|
||||
"2.fb5kOEZvh9zPABbP8WRmSQ==|Yi6ZAJY+UtqCKMUSqp1ahY9Kf8QuneKXs6BMkpNsakLVOzTYkHHlilyGABMF7GzUO8QHyZi7V/Ovjjg+Naf3Sm8qNhxtDhibITv4k8rDnM0=|TFkq3h2VNTT1z5BFbebm37WYuxyEHXuRo0DZJI7TQnw=",
|
||||
);
|
||||
|
||||
const oldPinKeyEncryptedMasterKeyPostMigration: any = null;
|
||||
const oldPinKeyEncryptedMasterKeyPreMigrationPersistent =
|
||||
"2.fb5kOEZvh9zPABbP8WRmSQ==|Yi6ZAJY+UtqCKMUSqp1ahY9Kf8QuneKXs6BMkpNsakLVOzTYkHHlilyGABMF7GzUO8QHyZi7V/Ovjjg+Naf3Sm8qNhxtDhibITv4k8rDnM0=|TFkq3h2VNTT1z5BFbebm37WYuxyEHXuRo0DZJI7TQnw=";
|
||||
const oldPinKeyEncryptedMasterKeyPreMigrationEphemeral = new EncString(
|
||||
"2.fb5kOEZvh9zPABbP8WRmSQ==|Yi6ZAJY+UtqCKMUSqp1ahY9Kf8QuneKXs6BMkpNsakLVOzTYkHHlilyGABMF7GzUO8QHyZi7V/Ovjjg+Naf3Sm8qNhxtDhibITv4k8rDnM0=|TFkq3h2VNTT1z5BFbebm37WYuxyEHXuRo0DZJI7TQnw=",
|
||||
);
|
||||
|
||||
function mockPinEncryptedKeyDataByPinLockType(
|
||||
pinLockType: PinLockType,
|
||||
migrationStatus: "PRE" | "POST" = "POST",
|
||||
) {
|
||||
switch (pinLockType) {
|
||||
case "PERSISTANT":
|
||||
stateService.getPinKeyEncryptedUserKey.mockResolvedValue(
|
||||
pinKeyEncryptedUserKeyPersistant,
|
||||
);
|
||||
if (migrationStatus === "PRE") {
|
||||
stateService.getEncryptedPinProtected.mockResolvedValue(
|
||||
oldPinKeyEncryptedMasterKeyPreMigrationPersistent,
|
||||
);
|
||||
} else {
|
||||
stateService.getEncryptedPinProtected.mockResolvedValue(
|
||||
oldPinKeyEncryptedMasterKeyPostMigration,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "TRANSIENT":
|
||||
stateService.getPinKeyEncryptedUserKeyEphemeral.mockResolvedValue(
|
||||
pinKeyEncryptedUserKeyEphemeral,
|
||||
);
|
||||
|
||||
if (migrationStatus === "PRE") {
|
||||
stateService.getDecryptedPinProtected.mockResolvedValue(
|
||||
oldPinKeyEncryptedMasterKeyPreMigrationEphemeral,
|
||||
);
|
||||
} else {
|
||||
stateService.getDecryptedPinProtected.mockResolvedValue(
|
||||
oldPinKeyEncryptedMasterKeyPostMigration,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "DISABLED":
|
||||
// no mocking required. Error should be thrown
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const testCases: { pinLockType: PinLockType; migrationStatus: "PRE" | "POST" }[] = [
|
||||
{ pinLockType: "PERSISTANT", migrationStatus: "PRE" },
|
||||
{ pinLockType: "PERSISTANT", migrationStatus: "POST" },
|
||||
{ pinLockType: "TRANSIENT", migrationStatus: "PRE" },
|
||||
{ pinLockType: "TRANSIENT", migrationStatus: "POST" },
|
||||
];
|
||||
|
||||
testCases.forEach(({ pinLockType, migrationStatus }) => {
|
||||
describe(`given a ${pinLockType} PIN (${migrationStatus} migration)`, () => {
|
||||
it(`should successfully decrypt and return user key when using a valid PIN`, async () => {
|
||||
// Arrange
|
||||
setupDecryptUserKeyWithPinMocks(pinLockType, migrationStatus);
|
||||
|
||||
// Act
|
||||
const result = await pinCryptoService.decryptUserKeyWithPin(mockPin);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockUserKey);
|
||||
});
|
||||
|
||||
it(`should return null when PIN is incorrect and user key cannot be decrypted`, async () => {
|
||||
// Arrange
|
||||
setupDecryptUserKeyWithPinMocks("PERSISTANT");
|
||||
|
||||
cryptoService.decryptUserKeyWithPin.mockResolvedValue(null);
|
||||
|
||||
// Act
|
||||
const result = await pinCryptoService.decryptUserKeyWithPin(mockPin);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
// not sure if this is a realistic scenario but going to test it anyway
|
||||
it(`should return null when PIN doesn't match after successful user key decryption`, async () => {
|
||||
// Arrange
|
||||
setupDecryptUserKeyWithPinMocks("PERSISTANT");
|
||||
|
||||
// non matching PIN
|
||||
cryptoService.decryptToUtf8.mockResolvedValue("9999");
|
||||
|
||||
// Act
|
||||
const result = await pinCryptoService.decryptUserKeyWithPin(mockPin);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it(`should return null when pin is disabled`, async () => {
|
||||
// Arrange
|
||||
setupDecryptUserKeyWithPinMocks("DISABLED");
|
||||
|
||||
// Act
|
||||
const result = await pinCryptoService.decryptUserKeyWithPin(mockPin);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test helpers
|
||||
function randomBytes(length: number): Uint8Array {
|
||||
return new Uint8Array(Array.from({ length }, (_, k) => k % 255));
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export * from "./components/fingerprint-dialog.component";
|
||||
export * from "./password-callout/password-callout.component";
|
||||
export * from "./models";
|
@ -1,4 +1,6 @@
|
||||
export enum VerificationType {
|
||||
MasterPassword = 0,
|
||||
OTP = 1,
|
||||
PIN = 2,
|
||||
Biometrics = 3,
|
||||
}
|
||||
|
@ -1,12 +1,24 @@
|
||||
import { PinCryptoServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin-crypto.service.abstraction";
|
||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { StateService } from "../../../platform/abstractions/state.service";
|
||||
import { KeySuffixOptions } from "../../../platform/enums/key-suffix-options.enum";
|
||||
import { UserKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "../../enums/verification-type";
|
||||
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
|
||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||
import { Verification } from "../../types/verification";
|
||||
import {
|
||||
MasterPasswordVerification,
|
||||
OtpVerification,
|
||||
PinVerification,
|
||||
ServerSideVerification,
|
||||
Verification,
|
||||
VerificationWithSecret,
|
||||
verificationHasSecret,
|
||||
} from "../../types/verification";
|
||||
|
||||
/**
|
||||
* Used for general-purpose user verification throughout the app.
|
||||
@ -18,6 +30,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
private cryptoService: CryptoService,
|
||||
private i18nService: I18nService,
|
||||
private userVerificationApiService: UserVerificationApiServiceAbstraction,
|
||||
private pinCryptoService: PinCryptoServiceAbstraction,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -27,11 +41,11 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
* @param alreadyHashed Whether the master password is already hashed
|
||||
*/
|
||||
async buildRequest<T extends SecretVerificationRequest>(
|
||||
verification: Verification,
|
||||
verification: ServerSideVerification,
|
||||
requestClass?: new () => T,
|
||||
alreadyHashed?: boolean,
|
||||
) {
|
||||
this.validateInput(verification);
|
||||
this.validateSecretInput(verification);
|
||||
|
||||
const request =
|
||||
requestClass != null ? new requestClass() : (new SecretVerificationRequest() as T);
|
||||
@ -57,42 +71,87 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to verify the Master Password client-side, or send the OTP to the server for verification (with no other data)
|
||||
* Used to verify Master Password, PIN, or biometrics client-side, or send the OTP to the server for verification (with no other data)
|
||||
* Generally used for client-side verification only.
|
||||
* @param verification User-supplied verification data (Master Password or OTP)
|
||||
* @param verification User-supplied verification data (OTP, MP, PIN, or biometrics)
|
||||
*/
|
||||
async verifyUser(verification: Verification): Promise<boolean> {
|
||||
this.validateInput(verification);
|
||||
if (verificationHasSecret(verification)) {
|
||||
this.validateSecretInput(verification);
|
||||
}
|
||||
|
||||
if (verification.type === VerificationType.OTP) {
|
||||
const request = new VerifyOTPRequest(verification.secret);
|
||||
try {
|
||||
await this.userVerificationApiService.postAccountVerifyOTP(request);
|
||||
} catch (e) {
|
||||
throw new Error(this.i18nService.t("invalidVerificationCode"));
|
||||
switch (verification.type) {
|
||||
case VerificationType.OTP:
|
||||
return this.verifyUserByOTP(verification);
|
||||
case VerificationType.MasterPassword:
|
||||
return this.verifyUserByMasterPassword(verification);
|
||||
case VerificationType.PIN:
|
||||
return this.verifyUserByPIN(verification);
|
||||
break;
|
||||
case VerificationType.Biometrics:
|
||||
return this.verifyUserByBiometrics();
|
||||
default: {
|
||||
// Compile-time check for exhaustive switch
|
||||
const _exhaustiveCheck: never = verification;
|
||||
return _exhaustiveCheck;
|
||||
}
|
||||
} else {
|
||||
let masterKey = await this.cryptoService.getMasterKey();
|
||||
if (!masterKey) {
|
||||
masterKey = await this.cryptoService.makeMasterKey(
|
||||
verification.secret,
|
||||
await this.stateService.getEmail(),
|
||||
await this.stateService.getKdfType(),
|
||||
await this.stateService.getKdfConfig(),
|
||||
);
|
||||
}
|
||||
const passwordValid = await this.cryptoService.compareAndUpdateKeyHash(
|
||||
verification.secret,
|
||||
masterKey,
|
||||
);
|
||||
if (!passwordValid) {
|
||||
throw new Error(this.i18nService.t("invalidMasterPassword"));
|
||||
}
|
||||
this.cryptoService.setMasterKey(masterKey);
|
||||
}
|
||||
}
|
||||
|
||||
private async verifyUserByOTP(verification: OtpVerification): Promise<boolean> {
|
||||
const request = new VerifyOTPRequest(verification.secret);
|
||||
try {
|
||||
await this.userVerificationApiService.postAccountVerifyOTP(request);
|
||||
} catch (e) {
|
||||
throw new Error(this.i18nService.t("invalidVerificationCode"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async verifyUserByMasterPassword(
|
||||
verification: MasterPasswordVerification,
|
||||
): Promise<boolean> {
|
||||
let masterKey = await this.cryptoService.getMasterKey();
|
||||
if (!masterKey) {
|
||||
masterKey = await this.cryptoService.makeMasterKey(
|
||||
verification.secret,
|
||||
await this.stateService.getEmail(),
|
||||
await this.stateService.getKdfType(),
|
||||
await this.stateService.getKdfConfig(),
|
||||
);
|
||||
}
|
||||
const passwordValid = await this.cryptoService.compareAndUpdateKeyHash(
|
||||
verification.secret,
|
||||
masterKey,
|
||||
);
|
||||
if (!passwordValid) {
|
||||
throw new Error(this.i18nService.t("invalidMasterPassword"));
|
||||
}
|
||||
// TODO: we should re-evaluate later on if user verification should have the side effect of modifying state. Probably not.
|
||||
await this.cryptoService.setMasterKey(masterKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async verifyUserByPIN(verification: PinVerification): Promise<boolean> {
|
||||
const userKey = await this.pinCryptoService.decryptUserKeyWithPin(verification.secret);
|
||||
|
||||
return userKey != null;
|
||||
}
|
||||
|
||||
private async verifyUserByBiometrics(): Promise<boolean> {
|
||||
let userKey: UserKey;
|
||||
// Biometrics crashes and doesn't return a value if the user cancels the prompt
|
||||
try {
|
||||
userKey = await this.cryptoService.getUserKeyFromStorage(KeySuffixOptions.Biometric);
|
||||
} catch (e) {
|
||||
this.logService.error(`Biometrics User Verification failed: ${e.message}`);
|
||||
// So, any failures should be treated as a failed verification
|
||||
return false;
|
||||
}
|
||||
|
||||
return userKey != null;
|
||||
}
|
||||
|
||||
async requestOTP() {
|
||||
await this.userVerificationApiService.postAccountRequestOTP();
|
||||
}
|
||||
@ -121,12 +180,15 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
);
|
||||
}
|
||||
|
||||
private validateInput(verification: Verification) {
|
||||
private validateSecretInput(verification: VerificationWithSecret) {
|
||||
if (verification?.secret == null || verification.secret === "") {
|
||||
if (verification.type === VerificationType.OTP) {
|
||||
throw new Error(this.i18nService.t("verificationCodeRequired"));
|
||||
} else {
|
||||
throw new Error(this.i18nService.t("masterPasswordRequired"));
|
||||
switch (verification.type) {
|
||||
case VerificationType.OTP:
|
||||
throw new Error(this.i18nService.t("verificationCodeRequired"));
|
||||
case VerificationType.MasterPassword:
|
||||
throw new Error(this.i18nService.t("masterPasswordRequired"));
|
||||
case VerificationType.PIN:
|
||||
throw new Error(this.i18nService.t("pinRequired"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,19 @@
|
||||
import { VerificationType } from "../enums/verification-type";
|
||||
|
||||
export type Verification = {
|
||||
type: VerificationType;
|
||||
secret: string;
|
||||
};
|
||||
export type OtpVerification = { type: VerificationType.OTP; secret: string };
|
||||
export type MasterPasswordVerification = { type: VerificationType.MasterPassword; secret: string };
|
||||
export type PinVerification = { type: VerificationType.PIN; secret: string };
|
||||
export type BiometricsVerification = { type: VerificationType.Biometrics };
|
||||
|
||||
export type VerificationWithSecret = OtpVerification | MasterPasswordVerification | PinVerification;
|
||||
export type VerificationWithoutSecret = BiometricsVerification;
|
||||
|
||||
export type Verification = VerificationWithSecret | VerificationWithoutSecret;
|
||||
|
||||
export function verificationHasSecret(
|
||||
verification: Verification,
|
||||
): verification is VerificationWithSecret {
|
||||
return "secret" in verification;
|
||||
}
|
||||
|
||||
export type ServerSideVerification = OtpVerification | MasterPasswordVerification;
|
||||
|
@ -4,10 +4,10 @@ import { firstValueFrom } from "rxjs";
|
||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { Policy } from "../../admin-console/models/domain/policy";
|
||||
import { TokenService } from "../../auth/abstractions/token.service";
|
||||
import { UserVerificationService } from "../../auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
|
||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||
import { StateService } from "../../platform/abstractions/state.service";
|
||||
import { AccountDecryptionOptions } from "../../platform/models/domain/account";
|
||||
import { EncString } from "../../platform/models/domain/enc-string";
|
||||
|
||||
import { VaultTimeoutSettingsService } from "./vault-timeout-settings.service";
|
||||
@ -17,7 +17,6 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let userVerificationService: MockProxy<UserVerificationService>;
|
||||
let service: VaultTimeoutSettingsService;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -25,13 +24,11 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
tokenService = mock<TokenService>();
|
||||
policyService = mock<PolicyService>();
|
||||
stateService = mock<StateService>();
|
||||
userVerificationService = mock<UserVerificationService>();
|
||||
service = new VaultTimeoutSettingsService(
|
||||
cryptoService,
|
||||
tokenService,
|
||||
policyService,
|
||||
stateService,
|
||||
userVerificationService,
|
||||
);
|
||||
});
|
||||
|
||||
@ -43,7 +40,9 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
});
|
||||
|
||||
it("contains Lock when the user has a master password", async () => {
|
||||
userVerificationService.hasMasterPassword.mockResolvedValue(true);
|
||||
stateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
new AccountDecryptionOptions({ hasMasterPassword: true }),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(service.availableVaultTimeoutActions$());
|
||||
|
||||
@ -75,7 +74,9 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
});
|
||||
|
||||
it("not contains Lock when the user does not have a master password, PIN, or biometrics", async () => {
|
||||
userVerificationService.hasMasterPassword.mockResolvedValue(false);
|
||||
stateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
new AccountDecryptionOptions({ hasMasterPassword: false }),
|
||||
);
|
||||
stateService.getPinKeyEncryptedUserKey.mockResolvedValue(null);
|
||||
stateService.getProtectedPin.mockResolvedValue(null);
|
||||
stateService.getBiometricUnlock.mockResolvedValue(false);
|
||||
@ -97,7 +98,9 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
`(
|
||||
"returns $expected when policy is $policy, and user preference is $userPreference",
|
||||
async ({ policy, userPreference, expected }) => {
|
||||
userVerificationService.hasMasterPassword.mockResolvedValue(true);
|
||||
stateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
new AccountDecryptionOptions({ hasMasterPassword: true }),
|
||||
);
|
||||
policyService.policyAppliesToUser.mockResolvedValue(policy === null ? false : true);
|
||||
policyService.getAll.mockResolvedValue(
|
||||
policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[]),
|
||||
@ -125,7 +128,9 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
"returns $expected when policy is $policy, has unlock method is $unlockMethod, and user preference is $userPreference",
|
||||
async ({ unlockMethod, policy, userPreference, expected }) => {
|
||||
stateService.getBiometricUnlock.mockResolvedValue(unlockMethod);
|
||||
userVerificationService.hasMasterPassword.mockResolvedValue(false);
|
||||
stateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
new AccountDecryptionOptions({ hasMasterPassword: false }),
|
||||
);
|
||||
policyService.policyAppliesToUser.mockResolvedValue(policy === null ? false : true);
|
||||
policyService.getAll.mockResolvedValue(
|
||||
policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[]),
|
||||
|
@ -4,7 +4,6 @@ import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction }
|
||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "../../admin-console/enums";
|
||||
import { TokenService } from "../../auth/abstractions/token.service";
|
||||
import { UserVerificationService } from "../../auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
|
||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||
import { StateService } from "../../platform/abstractions/state.service";
|
||||
@ -22,7 +21,6 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
private tokenService: TokenService,
|
||||
private policyService: PolicyService,
|
||||
private stateService: StateService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
) {}
|
||||
|
||||
async setVaultTimeoutOptions(timeout: number, action: VaultTimeoutAction): Promise<void> {
|
||||
@ -134,7 +132,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
|
||||
if (vaultTimeoutAction == null) {
|
||||
// Depends on whether or not the user has a master password
|
||||
const defaultValue = (await this.userVerificationService.hasMasterPassword())
|
||||
const defaultValue = (await this.userHasMasterPassword(userId))
|
||||
? VaultTimeoutAction.Lock
|
||||
: VaultTimeoutAction.LogOut;
|
||||
// We really shouldn't need to set the value here, but multiple services relies on this value being correct.
|
||||
@ -151,7 +149,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
const availableActions = [VaultTimeoutAction.LogOut];
|
||||
|
||||
const canLock =
|
||||
(await this.userVerificationService.hasMasterPassword(userId)) ||
|
||||
(await this.userHasMasterPassword(userId)) ||
|
||||
(await this.isPinLockSet(userId)) !== "DISABLED" ||
|
||||
(await this.isBiometricLockSet(userId));
|
||||
|
||||
@ -166,4 +164,14 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
await this.stateService.setEverBeenUnlocked(false, { userId: userId });
|
||||
await this.cryptoService.clearPinKeys(userId);
|
||||
}
|
||||
|
||||
private async userHasMasterPassword(userId: string): Promise<boolean> {
|
||||
const acctDecryptionOpts = await this.stateService.getAccountDecryptionOptions({
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
if (acctDecryptionOpts?.hasMasterPassword != undefined) {
|
||||
return acctDecryptionOpts.hasMasterPassword;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@
|
||||
"paths": {
|
||||
"@bitwarden/admin-console": ["../admin-console/src"],
|
||||
"@bitwarden/angular/*": ["../angular/src/*"],
|
||||
"@bitwarden/auth": ["../auth/src"],
|
||||
"@bitwarden/auth/common": ["../auth/src/common"],
|
||||
"@bitwarden/auth/angular": ["../auth/src/angular"],
|
||||
"@bitwarden/billing": ["../billing/src"],
|
||||
"@bitwarden/common/*": ["../common/src/*"],
|
||||
"@bitwarden/components": ["../components/src"],
|
||||
|
@ -17,7 +17,8 @@
|
||||
"paths": {
|
||||
"@bitwarden/admin-console": ["./libs/admin-console/src"],
|
||||
"@bitwarden/angular/*": ["./libs/angular/src/*"],
|
||||
"@bitwarden/auth": ["./libs/auth/src"],
|
||||
"@bitwarden/auth/common": ["./libs/auth/src/common"],
|
||||
"@bitwarden/auth/angular": ["./libs/auth/src/angular"],
|
||||
"@bitwarden/billing": ["./libs/billing/src"],
|
||||
"@bitwarden/common/*": ["./libs/common/src/*"],
|
||||
"@bitwarden/components": ["./libs/components/src"],
|
||||
|
Loading…
Reference in New Issue
Block a user