diff --git a/angular/src/components/add-edit.component.ts b/angular/src/components/add-edit.component.ts index edb0b4aede..d8d9a8e318 100644 --- a/angular/src/components/add-edit.component.ts +++ b/angular/src/components/add-edit.component.ts @@ -22,11 +22,11 @@ import { FolderService } from 'jslib-common/abstractions/folder.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { LogService } from 'jslib-common/abstractions/log.service'; import { MessagingService } from 'jslib-common/abstractions/messaging.service'; +import { OrganizationService } from 'jslib-common/abstractions/organization.service'; import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { StateService } from 'jslib-common/abstractions/state.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; import { Cipher } from 'jslib-common/models/domain/cipher'; @@ -89,10 +89,10 @@ export class AddEditComponent implements OnInit { constructor(protected cipherService: CipherService, protected folderService: FolderService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected auditService: AuditService, protected stateService: StateService, - protected userService: UserService, protected collectionService: CollectionService, - protected messagingService: MessagingService, protected eventService: EventService, - protected policyService: PolicyService, protected passwordRepromptService: PasswordRepromptService, - private logService: LogService) { + protected collectionService: CollectionService, protected messagingService: MessagingService, + protected eventService: EventService, protected policyService: PolicyService, + private logService: LogService, protected passwordRepromptService: PasswordRepromptService, + private organizationService: OrganizationService) { this.typeOptions = [ { name: i18nService.t('typeLogin'), value: CipherType.Login }, { name: i18nService.t('typeCard'), value: CipherType.Card }, @@ -160,11 +160,11 @@ export class AddEditComponent implements OnInit { if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) { this.allowPersonal = false; } else { - const myEmail = await this.userService.getEmail(); + const myEmail = await this.stateService.getEmail(); this.ownershipOptions.push({ name: myEmail, value: null }); } - const orgs = await this.userService.getAllOrganizations(); + const orgs = await this.organizationService.getAll(); orgs.sort(Utils.getSortFunction(this.i18nService, 'name')).forEach(o => { if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) { this.ownershipOptions.push({ name: o.name, value: o.id }); @@ -193,12 +193,12 @@ export class AddEditComponent implements OnInit { this.title = this.i18nService.t('addItem'); } - const addEditCipherInfo: any = await this.stateService.get('addEditCipherInfo'); + const addEditCipherInfo: any = await this.stateService.getAddEditCipherInfo(); if (addEditCipherInfo != null) { this.cipher = addEditCipherInfo.cipher; this.collectionIds = addEditCipherInfo.collectionIds; } - await this.stateService.remove('addEditCipherInfo'); + await this.stateService.setAddEditCipherInfo(null); if (this.cipher == null) { if (this.editMode) { @@ -442,7 +442,7 @@ export class AddEditComponent implements OnInit { } if (this.cipher.organizationId != null) { this.collections = this.writeableCollections.filter(c => c.organizationId === this.cipher.organizationId); - const org = await this.userService.getOrganization(this.cipher.organizationId); + const org = await this.organizationService.get(this.cipher.organizationId); if (org != null) { this.cipher.organizationUseTotp = org.useTotp; } diff --git a/angular/src/components/attachments.component.ts b/angular/src/components/attachments.component.ts index dcbaf19b3b..6745251b2b 100644 --- a/angular/src/components/attachments.component.ts +++ b/angular/src/components/attachments.component.ts @@ -12,7 +12,7 @@ import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { LogService } from 'jslib-common/abstractions/log.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { Cipher } from 'jslib-common/models/domain/cipher'; import { ErrorResponse } from 'jslib-common/models/response/errorResponse'; @@ -37,9 +37,9 @@ export class AttachmentsComponent implements OnInit { emergencyAccessId?: string = null; constructor(protected cipherService: CipherService, protected i18nService: I18nService, - protected cryptoService: CryptoService, protected userService: UserService, - protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, - protected win: Window, private logService: LogService) { } + protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService, + protected apiService: ApiService, protected win: Window, + protected logService: LogService, protected stateService: StateService) { } async ngOnInit() { await this.init(); @@ -164,7 +164,7 @@ export class AttachmentsComponent implements OnInit { this.cipher = await this.cipherDomain.decrypt(); this.hasUpdatedKey = await this.cryptoService.hasEncKey(); - const canAccessPremium = await this.userService.canAccessPremium(); + const canAccessPremium = await this.stateService.getCanAccessPremium(); this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null; if (!this.canAccessAttachments) { diff --git a/angular/src/components/avatar.component.ts b/angular/src/components/avatar.component.ts index 1760c521f2..a9ba91179d 100644 --- a/angular/src/components/avatar.component.ts +++ b/angular/src/components/avatar.component.ts @@ -45,7 +45,7 @@ export class AvatarComponent implements OnChanges, OnInit { } private async generate() { - const enableGravatars = await this.stateService.get('enableGravatars'); + const enableGravatars = await this.stateService.getEnableGravitars(); if (enableGravatars && this.email != null) { const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5'); const hash = Utils.fromBufferToHex(hashBytes).toLowerCase(); diff --git a/angular/src/components/change-password.component.ts b/angular/src/components/change-password.component.ts index 5af79115d7..7bbb3c513a 100644 --- a/angular/src/components/change-password.component.ts +++ b/angular/src/components/change-password.component.ts @@ -6,7 +6,7 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { EncString } from 'jslib-common/models/domain/encString'; import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions'; @@ -29,12 +29,12 @@ export class ChangePasswordComponent implements OnInit { private masterPasswordStrengthTimeout: any; constructor(protected i18nService: I18nService, protected cryptoService: CryptoService, - protected messagingService: MessagingService, protected userService: UserService, - protected passwordGenerationService: PasswordGenerationService, - protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService) { } + protected messagingService: MessagingService, protected passwordGenerationService: PasswordGenerationService, + protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService, + protected stateService: StateService) { } async ngOnInit() { - this.email = await this.userService.getEmail(); + this.email = await this.stateService.getEmail(); this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); } @@ -47,12 +47,12 @@ export class ChangePasswordComponent implements OnInit { return; } - const email = await this.userService.getEmail(); + const email = await this.stateService.getEmail(); if (this.kdf == null) { - this.kdf = await this.userService.getKdf(); + this.kdf = await this.stateService.getKdfType(); } if (this.kdfIterations == null) { - this.kdfIterations = await this.userService.getKdfIterations(); + this.kdfIterations = await this.stateService.getKdfIterations(); } const key = await this.cryptoService.makeKey(this.masterPassword, email.trim().toLowerCase(), this.kdf, this.kdfIterations); diff --git a/angular/src/components/groupings.component.ts b/angular/src/components/groupings.component.ts index 1c3c27a1d7..98784bb4b3 100644 --- a/angular/src/components/groupings.component.ts +++ b/angular/src/components/groupings.component.ts @@ -14,10 +14,7 @@ import { TreeNode } from 'jslib-common/models/domain/treeNode'; import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { FolderService } from 'jslib-common/abstractions/folder.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; - -import { ConstantsService } from 'jslib-common/services/constants.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; @Directive() export class GroupingsComponent { @@ -50,15 +47,12 @@ export class GroupingsComponent { selectedCollectionId: string = null; private collapsedGroupings: Set; - private collapsedGroupingsKey: string; constructor(protected collectionService: CollectionService, protected folderService: FolderService, - protected storageService: StorageService, protected userService: UserService) { } + protected stateService: StateService) { } async load(setLoaded = true) { - const userId = await this.userService.getUserId(); - this.collapsedGroupingsKey = ConstantsService.collapsedGroupingsKey + '_' + userId; - const collapsedGroupings = await this.storageService.get(this.collapsedGroupingsKey); + const collapsedGroupings = await this.stateService.getCollapsedGroupings(); if (collapsedGroupings == null) { this.collapsedGroupings = new Set(); } else { @@ -149,7 +143,7 @@ export class GroupingsComponent { this.selectedCollectionId = null; } - collapse(grouping: FolderView | CollectionView, idPrefix = '') { + async collapse(grouping: FolderView | CollectionView, idPrefix = '') { if (grouping.id == null) { return; } @@ -159,7 +153,7 @@ export class GroupingsComponent { } else { this.collapsedGroupings.add(id); } - this.storageService.save(this.collapsedGroupingsKey, this.collapsedGroupings); + await this.stateService.setCollapsedGroupings(this.collapsedGroupings); } isCollapsed(grouping: FolderView | CollectionView, idPrefix = '') { diff --git a/angular/src/components/icon.component.ts b/angular/src/components/icon.component.ts index b3a75d5a8f..1ae988c797 100644 --- a/angular/src/components/icon.component.ts +++ b/angular/src/components/icon.component.ts @@ -11,8 +11,6 @@ import { CipherView } from 'jslib-common/models/view/cipherView'; import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { StateService } from 'jslib-common/abstractions/state.service'; -import { ConstantsService } from 'jslib-common/services/constants.service'; - import { Utils } from 'jslib-common/misc/utils'; const IconMap: any = { @@ -37,7 +35,7 @@ export class IconComponent implements OnChanges { private iconsUrl: string; - constructor(environmentService: EnvironmentService, protected stateService: StateService) { + constructor(environmentService: EnvironmentService, private stateService: StateService) { this.iconsUrl = environmentService.getIconsUrl(); } @@ -46,7 +44,7 @@ export class IconComponent implements OnChanges { // to avoid this we reset all state variables. this.image = null; this.fallbackImage = null; - this.imageEnabled = !(await this.stateService.get(ConstantsService.disableFaviconKey)); + this.imageEnabled = !(await this.stateService.getDisableFavicon()); this.load(); } diff --git a/angular/src/components/lock.component.ts b/angular/src/components/lock.component.ts index b192ce6c4a..ae9887c2aa 100644 --- a/angular/src/components/lock.component.ts +++ b/angular/src/components/lock.component.ts @@ -11,12 +11,8 @@ import { LogService } from 'jslib-common/abstractions/log.service'; import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; -import { ConstantsService } from 'jslib-common/services/constants.service'; - import { EncString } from 'jslib-common/models/domain/encString'; import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; @@ -25,6 +21,7 @@ import { SecretVerificationRequest } from 'jslib-common/models/request/secretVer import { Utils } from 'jslib-common/misc/utils'; import { HashPurpose } from 'jslib-common/enums/hashPurpose'; +import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions'; @Directive() export class LockComponent implements OnInit { @@ -41,38 +38,22 @@ export class LockComponent implements OnInit { hideInput: boolean; protected successRoute: string = 'vault'; - protected onSuccessfulSubmit: () => void; + protected onSuccessfulSubmit: () => Promise; private invalidPinAttempts = 0; private pinSet: [boolean, boolean]; constructor(protected router: Router, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService, - protected userService: UserService, protected cryptoService: CryptoService, - protected storageService: StorageService, protected vaultTimeoutService: VaultTimeoutService, + protected cryptoService: CryptoService, protected vaultTimeoutService: VaultTimeoutService, protected environmentService: EnvironmentService, protected stateService: StateService, protected apiService: ApiService, private logService: LogService, private keyConnectorService: KeyConnectorService, protected ngZone: NgZone) { } async ngOnInit() { - this.pinSet = await this.vaultTimeoutService.isPinLockSet(); - this.pinLock = (this.pinSet[0] && this.vaultTimeoutService.pinProtectedKey != null) || this.pinSet[1]; - this.supportsBiometric = await this.platformUtilsService.supportsBiometric(); - this.biometricLock = await this.vaultTimeoutService.isBiometricLockSet() && - (await this.cryptoService.hasKeyStored('biometric') || !this.platformUtilsService.supportsSecureStorage()); - this.biometricText = await this.storageService.get(ConstantsService.biometricText); - this.email = await this.userService.getEmail(); - const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); - this.hideInput = usesKeyConnector && !this.pinLock; - - // Users with key connector and without biometric or pin has no MP to unlock using - if (usesKeyConnector && !(this.biometricLock || this.pinLock)) { - await this.vaultTimeoutService.logOut(); - } - - const webVaultUrl = this.environmentService.getWebVaultUrl(); - const vaultUrl = webVaultUrl === 'https://vault.bitwarden.com' ? 'https://bitwarden.com' : webVaultUrl; - this.webVaultHostname = Utils.getHostname(vaultUrl); + this.stateService.activeAccount.subscribe(async _userId => { + await this.load(); + }); } async submit() { @@ -87,17 +68,17 @@ export class LockComponent implements OnInit { return; } - const kdf = await this.userService.getKdf(); - const kdfIterations = await this.userService.getKdfIterations(); + const kdf = await this.stateService.getKdfType(); + const kdfIterations = await this.stateService.getKdfIterations(); if (this.pinLock) { let failed = true; try { if (this.pinSet[0]) { const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations, - this.vaultTimeoutService.pinProtectedKey); + await this.stateService.getDecryptedPinProtected()); const encKey = await this.cryptoService.getEncKey(key); - const protectedPin = await this.storageService.get(ConstantsService.protectedPin); + const protectedPin = await this.stateService.getProtectedPin(); const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey); failed = decPin !== this.pin; if (!failed) { @@ -148,13 +129,13 @@ export class LockComponent implements OnInit { if (passwordValid) { if (this.pinSet[0]) { - const protectedPin = await this.storageService.get(ConstantsService.protectedPin); + const protectedPin = await this.stateService.getProtectedPin(); const encKey = await this.cryptoService.getEncKey(key); const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey); const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfIterations); - this.vaultTimeoutService.pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey); + await this.stateService.setDecryptedPinProtected(await this.cryptoService.encrypt(key.key, pinKey)); } - this.setKeyAndContinue(key); + await this.setKeyAndContinue(key); } else { this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.i18nService.t('invalidMasterPassword')); @@ -175,7 +156,7 @@ export class LockComponent implements OnInit { return; } - const success = (await this.cryptoService.getKey('biometric')) != null; + const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null; if (success) { await this.doContinue(); @@ -196,19 +177,40 @@ export class LockComponent implements OnInit { private async setKeyAndContinue(key: SymmetricCryptoKey) { await this.cryptoService.setKey(key); - this.doContinue(); + await this.doContinue(); } private async doContinue() { - this.vaultTimeoutService.biometricLocked = false; - this.vaultTimeoutService.everBeenUnlocked = true; - const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); - await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); + await this.stateService.setBiometricLocked(false); + await this.stateService.setEverBeenUnlocked(true); + const disableFavicon = await this.stateService.getDisableFavicon(); + await this.stateService.setDisableFavicon(!!disableFavicon); this.messagingService.send('unlocked'); if (this.onSuccessfulSubmit != null) { - this.onSuccessfulSubmit(); + await this.onSuccessfulSubmit(); } else if (this.router != null) { this.router.navigate([this.successRoute]); } } + + private async load() { + this.pinSet = await this.vaultTimeoutService.isPinLockSet(); + this.pinLock = (this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || this.pinSet[1]; + this.supportsBiometric = await this.platformUtilsService.supportsBiometric(); + this.biometricLock = await this.vaultTimeoutService.isBiometricLockSet() && + (await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric) || !this.platformUtilsService.supportsSecureStorage()); + this.biometricText = await this.stateService.getBiometricText(); + this.email = await this.stateService.getEmail(); + const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); + this.hideInput = usesKeyConnector && !this.pinLock; + + // Users with key connector and without biometric or pin has no MP to unlock using + if (usesKeyConnector && !(this.biometricLock || this.pinLock)) { + await this.vaultTimeoutService.logOut(); + } + + const webVaultUrl = this.environmentService.getWebVaultUrl(); + const vaultUrl = webVaultUrl === 'https://vault.bitwarden.com' ? 'https://bitwarden.com' : webVaultUrl; + this.webVaultHostname = Utils.getHostname(vaultUrl); + } } diff --git a/angular/src/components/login.component.ts b/angular/src/components/login.component.ts index 34c0b8fd00..5dc15c71dd 100644 --- a/angular/src/components/login.component.ts +++ b/angular/src/components/login.component.ts @@ -19,19 +19,11 @@ import { LogService } from 'jslib-common/abstractions/log.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; - -import { ConstantsService } from 'jslib-common/services/constants.service'; import { Utils } from 'jslib-common/misc/utils'; import { CaptchaProtectedComponent } from './captchaProtected.component'; -const Keys = { - rememberedEmail: 'rememberedEmail', - rememberEmail: 'rememberEmail', -}; - @Directive() export class LoginComponent extends CaptchaProtectedComponent implements OnInit { @Input() email: string = ''; @@ -53,22 +45,19 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit platformUtilsService: PlatformUtilsService, i18nService: I18nService, protected stateService: StateService, environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService, - protected cryptoFunctionService: CryptoFunctionService, private storageService: StorageService, - protected logService: LogService, protected ngZone: NgZone) { + protected cryptoFunctionService: CryptoFunctionService, protected logService: LogService, + protected ngZone: NgZone) { super(environmentService, i18nService, platformUtilsService); } async ngOnInit() { if (this.email == null || this.email === '') { - this.email = await this.storageService.get(Keys.rememberedEmail); + this.email = await this.stateService.getRememberedEmail(); if (this.email == null) { this.email = ''; } } - this.rememberEmail = await this.storageService.get(Keys.rememberEmail); - if (this.rememberEmail == null) { - this.rememberEmail = true; - } + this.rememberEmail = await this.stateService.getRememberedEmail() != null; if (Utils.isBrowser && !Utils.isNode) { this.focusInput(); } @@ -96,11 +85,10 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit try { this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken); const response = await this.formPromise; - await this.storageService.save(Keys.rememberEmail, this.rememberEmail); if (this.rememberEmail) { - await this.storageService.save(Keys.rememberedEmail, this.email); + await this.stateService.setRememberedEmail(this.email); } else { - await this.storageService.remove(Keys.rememberedEmail); + await this.stateService.setRememberedEmail(null); } if (this.handleCaptchaRequired(response)) { return; @@ -117,8 +105,8 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit this.router.navigate([this.forcePasswordResetRoute]); } } else { - const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); - await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); + const disableFavicon = await this.stateService.getDisableFavicon(); + await this.stateService.setDisableFavicon(!!disableFavicon); if (this.onSuccessfulLogin != null) { this.onSuccessfulLogin(); } @@ -158,8 +146,8 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); // Save sso params - await this.storageService.save(ConstantsService.ssoStateKey, state); - await this.storageService.save(ConstantsService.ssoCodeVerifierKey, ssoCodeVerifier); + await this.stateService.setSsoState(state); + await this.stateService.setSsoCodeVerifier(ssoCodeVerifier); // Build URI const webUrl = this.environmentService.getWebVaultUrl(); diff --git a/angular/src/components/premium.component.ts b/angular/src/components/premium.component.ts index 5b5b4c89db..13efaa3b53 100644 --- a/angular/src/components/premium.component.ts +++ b/angular/src/components/premium.component.ts @@ -4,7 +4,7 @@ import { ApiService } from 'jslib-common/abstractions/api.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { LogService } from 'jslib-common/abstractions/log.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; @Directive() export class PremiumComponent implements OnInit { @@ -13,10 +13,11 @@ export class PremiumComponent implements OnInit { refreshPromise: Promise; constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, - protected apiService: ApiService, protected userService: UserService, private logService: LogService) { } + protected apiService: ApiService, private logService: LogService, + protected stateService: StateService) { } async ngOnInit() { - this.isPremium = await this.userService.canAccessPremium(); + this.isPremium = await this.stateService.getCanAccessPremium(); } async refresh() { @@ -24,7 +25,7 @@ export class PremiumComponent implements OnInit { this.refreshPromise = this.apiService.refreshIdentityToken(); await this.refreshPromise; this.platformUtilsService.showToast('success', null, this.i18nService.t('refreshComplete')); - this.isPremium = await this.userService.canAccessPremium(); + this.isPremium = await this.stateService.getCanAccessPremium(); } catch (e) { this.logService.error(e); } diff --git a/angular/src/components/register.component.ts b/angular/src/components/register.component.ts index fd91af29c7..df0579bcfa 100644 --- a/angular/src/components/register.component.ts +++ b/angular/src/components/register.component.ts @@ -141,7 +141,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn const request = new RegisterRequest(this.email, this.name, hashedPassword, this.hint, encKey[1].encryptedString, kdf, kdfIterations, this.referenceData, this.captchaToken); request.keys = new KeysRequest(keys[0], keys[1].encryptedString); - const orgInvite = await this.stateService.get('orgInvitation'); + const orgInvite = await this.stateService.getOrganizationInvitation(); if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) { request.token = orgInvite.token; request.organizationUserId = orgInvite.organizationUserId; diff --git a/angular/src/components/remove-password.component.ts b/angular/src/components/remove-password.component.ts index 91c35312b0..56132cbd22 100644 --- a/angular/src/components/remove-password.component.ts +++ b/angular/src/components/remove-password.component.ts @@ -8,11 +8,8 @@ import { ApiService } from 'jslib-common/abstractions/api.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; - -import { ConstantsService } from 'jslib-common/services/constants.service'; import { Organization } from 'jslib-common/models/domain/organization'; @@ -27,14 +24,14 @@ export class RemovePasswordComponent implements OnInit { organization: Organization; email: string; - constructor(private router: Router, private userService: UserService, + constructor(private router: Router, private stateService: StateService, private apiService: ApiService, private syncService: SyncService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, - private keyConnectorService: KeyConnectorService, private storageService: StorageService) { } + private keyConnectorService: KeyConnectorService) { } async ngOnInit() { this.organization = await this.keyConnectorService.getManagingOrganization(); - this.email = await this.userService.getEmail(); + this.email = await this.stateService.getEmail(); await this.syncService.fullSync(false); this.loading = false; } diff --git a/angular/src/components/send/add-edit.component.ts b/angular/src/components/send/add-edit.component.ts index 70bd87ba69..69d5c7d43a 100644 --- a/angular/src/components/send/add-edit.component.ts +++ b/angular/src/components/send/add-edit.component.ts @@ -17,7 +17,7 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { SendService } from 'jslib-common/abstractions/send.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { SendFileView } from 'jslib-common/models/view/sendFileView'; import { SendTextView } from 'jslib-common/models/view/sendTextView'; @@ -57,9 +57,9 @@ export class AddEditComponent implements OnInit { constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService, protected datePipe: DatePipe, - protected sendService: SendService, protected userService: UserService, - protected messagingService: MessagingService, protected policyService: PolicyService, - private logService: LogService) { + protected sendService: SendService, protected messagingService: MessagingService, + protected policyService: PolicyService, private logService: LogService, + protected stateService: StateService) { this.typeOptions = [ { name: i18nService.t('sendTypeFile'), value: SendType.File }, { name: i18nService.t('sendTypeText'), value: SendType.Text }, @@ -108,8 +108,8 @@ export class AddEditComponent implements OnInit { this.disableHideEmail = await this.policyService.policyAppliesToUser(PolicyType.SendOptions, p => p.data.disableHideEmail); - this.canAccessPremium = await this.userService.canAccessPremium(); - this.emailVerified = await this.userService.getEmailVerified(); + this.canAccessPremium = await this.stateService.getCanAccessPremium(); + this.emailVerified = await this.stateService.getEmailVerified(); if (!this.canAccessPremium || !this.emailVerified) { this.type = SendType.Text; } diff --git a/angular/src/components/send/send.component.ts b/angular/src/components/send/send.component.ts index 77f36459a4..c9b83a5cd9 100644 --- a/angular/src/components/send/send.component.ts +++ b/angular/src/components/send/send.component.ts @@ -16,7 +16,6 @@ import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.se import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { SearchService } from 'jslib-common/abstractions/search.service'; import { SendService } from 'jslib-common/abstractions/send.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; @Directive() export class SendComponent implements OnInit { @@ -48,8 +47,7 @@ export class SendComponent implements OnInit { constructor(protected sendService: SendService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService, protected ngZone: NgZone, protected searchService: SearchService, - protected policyService: PolicyService, protected userService: UserService, - private logService: LogService) { } + protected policyService: PolicyService, private logService: LogService) { } async ngOnInit() { this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend); diff --git a/angular/src/components/set-password.component.ts b/angular/src/components/set-password.component.ts index c12e32e6d3..c828a79347 100644 --- a/angular/src/components/set-password.component.ts +++ b/angular/src/components/set-password.component.ts @@ -13,8 +13,8 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; import { EncString } from 'jslib-common/models/domain/encString'; import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; @@ -42,12 +42,13 @@ export class SetPasswordComponent extends BaseChangePasswordComponent { onSuccessfulChangePassword: () => Promise; successRoute = 'vault'; - constructor(i18nService: I18nService, cryptoService: CryptoService, messagingService: MessagingService, - userService: UserService, passwordGenerationService: PasswordGenerationService, - platformUtilsService: PlatformUtilsService, policyService: PolicyService, protected router: Router, - private apiService: ApiService, private syncService: SyncService, private route: ActivatedRoute) { - super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, - platformUtilsService, policyService); + constructor(i18nService: I18nService, cryptoService: CryptoService, + messagingService: MessagingService, passwordGenerationService: PasswordGenerationService, + platformUtilsService: PlatformUtilsService, policyService: PolicyService, + protected router: Router, private apiService: ApiService, + private syncService: SyncService, private route: ActivatedRoute, stateService: StateService) { + super(i18nService, cryptoService, messagingService, passwordGenerationService, + platformUtilsService, policyService, stateService); } async ngOnInit() { @@ -102,7 +103,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent { if (response == null) { throw new Error(this.i18nService.t('resetPasswordOrgKeysError')); } - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); const publicKey = Utils.fromB64ToArray(response.publicKey); // RSA Encrypt user's encKey.key with organization public key @@ -138,8 +139,8 @@ export class SetPasswordComponent extends BaseChangePasswordComponent { } private async onSetPasswordSuccess(key: SymmetricCryptoKey, encKey: [SymmetricCryptoKey, EncString], keys: [string, EncString]) { - await this.userService.setInformation(await this.userService.getUserId(), await this.userService.getEmail(), - this.kdf, this.kdfIterations); + await this.stateService.setKdfType(this.kdf); + await this.stateService.setKdfIterations(this.kdfIterations); await this.cryptoService.setKey(key); await this.cryptoService.setEncKey(encKey[1].encryptedString); await this.cryptoService.setEncPrivateKey(keys[1].encryptedString); diff --git a/angular/src/components/set-pin.component.ts b/angular/src/components/set-pin.component.ts index d2cfae3a03..fc26d127a3 100644 --- a/angular/src/components/set-pin.component.ts +++ b/angular/src/components/set-pin.component.ts @@ -5,11 +5,7 @@ import { import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; -import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; - -import { ConstantsService } from 'jslib-common/services/constants.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { Utils } from 'jslib-common/misc/utils'; @@ -17,15 +13,13 @@ import { ModalRef } from './modal/modal.ref'; @Directive() export class SetPinComponent implements OnInit { - pin = ''; showPin = false; masterPassOnRestart = true; showMasterPassOnRestart = true; - constructor(private modalRef: ModalRef, private cryptoService: CryptoService, private userService: UserService, - private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService, - private keyConnectorService: KeyConnectorService) { } + constructor(private modalRef: ModalRef, private cryptoService: CryptoService, + private keyConnectorService: KeyConnectorService, private stateService: StateService) { } async ngOnInit() { this.showMasterPassOnRestart = this.masterPassOnRestart = !await this.keyConnectorService.getUsesKeyConnector(); @@ -40,18 +34,18 @@ export class SetPinComponent implements OnInit { this.modalRef.close(false); } - const kdf = await this.userService.getKdf(); - const kdfIterations = await this.userService.getKdfIterations(); - const email = await this.userService.getEmail(); + const kdf = await this.stateService.getKdfType(); + const kdfIterations = await this.stateService.getKdfIterations(); + const email = await this.stateService.getEmail(); const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations); const key = await this.cryptoService.getKey(); const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey); if (this.masterPassOnRestart) { const encPin = await this.cryptoService.encrypt(this.pin); - await this.storageService.save(ConstantsService.protectedPin, encPin.encryptedString); - this.vaultTimeoutService.pinProtectedKey = pinProtectedKey; + await this.stateService.setProtectedPin(encPin.encryptedString); + await this.stateService.setDecryptedPinProtected(pinProtectedKey); } else { - await this.storageService.save(ConstantsService.pinProtectedKey, pinProtectedKey.encryptedString); + await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString); } this.modalRef.close(true); diff --git a/angular/src/components/share.component.ts b/angular/src/components/share.component.ts index e5badc3790..1812f427a5 100644 --- a/angular/src/components/share.component.ts +++ b/angular/src/components/share.component.ts @@ -12,8 +12,8 @@ import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { LogService } from 'jslib-common/abstractions/log.service'; +import { OrganizationService } from 'jslib-common/abstractions/organization.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; import { Organization } from 'jslib-common/models/domain/organization'; import { CipherView } from 'jslib-common/models/view/cipherView'; @@ -35,8 +35,8 @@ export class ShareComponent implements OnInit { protected writeableCollections: CollectionView[] = []; constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService, - protected i18nService: I18nService, protected userService: UserService, - protected cipherService: CipherService, private logService: LogService) { } + protected i18nService: I18nService, protected cipherService: CipherService, + private logService: LogService, protected organizationService: OrganizationService) { } async ngOnInit() { await this.load(); @@ -45,7 +45,7 @@ export class ShareComponent implements OnInit { async load() { const allCollections = await this.collectionService.getAllDecrypted(); this.writeableCollections = allCollections.map(c => c).filter(c => !c.readOnly); - const orgs = await this.userService.getAllOrganizations(); + const orgs = await this.organizationService.getAll(); this.organizations = orgs.sort(Utils.getSortFunction(this.i18nService, 'name')) .filter(o => o.enabled && o.status === OrganizationUserStatusType.Confirmed); diff --git a/angular/src/components/sso.component.ts b/angular/src/components/sso.component.ts index 1ab8e2f404..d08559fc1d 100644 --- a/angular/src/components/sso.component.ts +++ b/angular/src/components/sso.component.ts @@ -15,9 +15,6 @@ import { LogService } from 'jslib-common/abstractions/log.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { StateService } from 'jslib-common/abstractions/state.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; - -import { ConstantsService } from 'jslib-common/services/constants.service'; import { Utils } from 'jslib-common/misc/utils'; @@ -47,18 +44,18 @@ export class SsoComponent { constructor(protected authService: AuthService, protected router: Router, protected i18nService: I18nService, protected route: ActivatedRoute, - protected storageService: StorageService, protected stateService: StateService, - protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, - protected cryptoFunctionService: CryptoFunctionService, protected environmentService: EnvironmentService, - protected passwordGenerationService: PasswordGenerationService, protected logService: LogService) { } + protected stateService: StateService, protected platformUtilsService: PlatformUtilsService, + protected apiService: ApiService, protected cryptoFunctionService: CryptoFunctionService, + protected environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService, + protected logService: LogService) { } async ngOnInit() { this.route.queryParams.pipe(first()).subscribe(async qParams => { if (qParams.code != null && qParams.state != null) { - const codeVerifier = await this.storageService.get(ConstantsService.ssoCodeVerifierKey); - const state = await this.storageService.get(ConstantsService.ssoStateKey); - await this.storageService.remove(ConstantsService.ssoCodeVerifierKey); - await this.storageService.remove(ConstantsService.ssoStateKey); + const codeVerifier = await this.stateService.getSsoCodeVerifier(); + const state = await this.stateService.getSsoState(); + await this.stateService.setSsoCodeVerifier(null); + await this.stateService.setSsoState(null); if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) { await this.logIn(qParams.code, codeVerifier, this.getOrgIdentifierFromState(qParams.state)); } @@ -106,7 +103,7 @@ export class SsoComponent { const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256'); codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier); + await this.stateService.setSsoCodeVerifier(codeVerifier); } if (state == null) { @@ -120,7 +117,7 @@ export class SsoComponent { state += `_identifier=${this.identifier}`; // Save state (regardless of new or existing) - await this.storageService.save(ConstantsService.ssoStateKey, state); + await this.stateService.setSsoState(state); let authorizeUrl = this.environmentService.getIdentityUrl() + '/connect/authorize?' + 'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + @@ -170,8 +167,8 @@ export class SsoComponent { this.router.navigate([this.forcePasswordResetRoute]); } } else { - const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); - await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); + const disableFavicon = await this.stateService.getDisableFavicon(); + await this.stateService.setDisableFavicon(!!disableFavicon); if (this.onSuccessfulLogin != null) { this.onSuccessfulLogin(); } diff --git a/angular/src/components/two-factor.component.ts b/angular/src/components/two-factor.component.ts index 5ad45a2525..02f5923d8b 100644 --- a/angular/src/components/two-factor.component.ts +++ b/angular/src/components/two-factor.component.ts @@ -20,10 +20,8 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service'; 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 { StorageService } from 'jslib-common/abstractions/storage.service'; import { TwoFactorProviders } from 'jslib-common/services/auth.service'; -import { ConstantsService } from 'jslib-common/services/constants.service'; import * as DuoWebSDK from 'duo_web_sdk'; import { WebAuthnIFrame } from 'jslib-common/misc/webauthn_iframe'; @@ -58,8 +56,7 @@ 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 storageService: StorageService, protected route: ActivatedRoute, - protected logService: LogService) { + protected route: ActivatedRoute, protected logService: LogService) { this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); } @@ -179,8 +176,8 @@ export class TwoFactorComponent implements OnInit, OnDestroy { async doSubmit() { this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember); const response: AuthResult = await this.formPromise; - const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); - await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); + const disableFavicon = await this.stateService.getDisableFavicon(); + await this.stateService.setDisableFavicon(!!disableFavicon); if (this.onSuccessfulLogin != null) { this.onSuccessfulLogin(); } diff --git a/angular/src/components/update-temp-password.component.ts b/angular/src/components/update-temp-password.component.ts index 29e545219d..ffb27db279 100644 --- a/angular/src/components/update-temp-password.component.ts +++ b/angular/src/components/update-temp-password.component.ts @@ -8,8 +8,8 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component'; @@ -30,11 +30,11 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent { constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, passwordGenerationService: PasswordGenerationService, policyService: PolicyService, - cryptoService: CryptoService, userService: UserService, - messagingService: MessagingService, private apiService: ApiService, + cryptoService: CryptoService, messagingService: MessagingService, + private apiService: ApiService, stateService: StateService, private syncService: SyncService, private logService: LogService) { - super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, - platformUtilsService, policyService); + super(i18nService, cryptoService, messagingService, passwordGenerationService, + platformUtilsService, policyService, stateService); } async ngOnInit() { @@ -49,9 +49,9 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent { async setupSubmitActions(): Promise { this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); - this.email = await this.userService.getEmail(); - this.kdf = await this.userService.getKdf(); - this.kdfIterations = await this.userService.getKdfIterations(); + this.email = await this.stateService.getEmail(); + this.kdf = await this.stateService.getKdfType(); + this.kdfIterations = await this.stateService.getKdfIterations(); return true; } diff --git a/angular/src/components/view.component.ts b/angular/src/components/view.component.ts index 8e12634f3c..6a3da19ee6 100644 --- a/angular/src/components/view.component.ts +++ b/angular/src/components/view.component.ts @@ -24,9 +24,9 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { LogService } from 'jslib-common/abstractions/log.service'; import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { TokenService } from 'jslib-common/abstractions/token.service'; import { TotpService } from 'jslib-common/abstractions/totp.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; import { ErrorResponse } from 'jslib-common/models/response/errorResponse'; @@ -67,9 +67,9 @@ export class ViewComponent implements OnDestroy, OnInit { protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService, protected auditService: AuditService, protected win: Window, protected broadcasterService: BroadcasterService, protected ngZone: NgZone, - protected changeDetectorRef: ChangeDetectorRef, protected userService: UserService, - protected eventService: EventService, protected apiService: ApiService, - protected passwordRepromptService: PasswordRepromptService, private logService: LogService) { } + protected changeDetectorRef: ChangeDetectorRef, protected eventService: EventService, + protected apiService: ApiService, protected passwordRepromptService: PasswordRepromptService, + private logService: LogService, protected stateService: StateService) { } ngOnInit() { this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { @@ -96,7 +96,7 @@ export class ViewComponent implements OnDestroy, OnInit { const cipher = await this.cipherService.get(this.cipherId); this.cipher = await cipher.decrypt(); - this.canAccessPremium = await this.userService.canAccessPremium(); + this.canAccessPremium = await this.stateService.getCanAccessPremium(); if (this.cipher.type === CipherType.Login && this.cipher.login.totp && (cipher.organizationUseTotp || this.canAccessPremium)) { diff --git a/angular/src/services/auth-guard.service.ts b/angular/src/services/auth-guard.service.ts index 5656157756..942e5b7336 100644 --- a/angular/src/services/auth-guard.service.ts +++ b/angular/src/services/auth-guard.service.ts @@ -8,16 +8,17 @@ import { import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; @Injectable() export class AuthGuardService implements CanActivate { - constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService, - private router: Router, private messagingService: MessagingService, private keyConnectorService: KeyConnectorService) { } + constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router, + private messagingService: MessagingService, private keyConnectorService: KeyConnectorService, + private stateService: StateService) { } async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) { - const isAuthed = await this.userService.isAuthenticated(); + const isAuthed = await this.stateService.getIsAuthenticated(); if (!isAuthed) { this.messagingService.send('authBlocked'); return false; diff --git a/angular/src/services/jslib-services.module.ts b/angular/src/services/jslib-services.module.ts index 2040fb1093..a413eb4bed 100644 --- a/angular/src/services/jslib-services.module.ts +++ b/angular/src/services/jslib-services.module.ts @@ -19,16 +19,18 @@ import { FileUploadService } from 'jslib-common/services/fileUpload.service'; import { FolderService } from 'jslib-common/services/folder.service'; import { KeyConnectorService } from 'jslib-common/services/keyConnector.service'; import { NotificationsService } from 'jslib-common/services/notifications.service'; +import { OrganizationService } from 'jslib-common/services/organization.service'; import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service'; import { PolicyService } from 'jslib-common/services/policy.service'; +import { ProviderService } from 'jslib-common/services/provider.service'; import { SearchService } from 'jslib-common/services/search.service'; import { SendService } from 'jslib-common/services/send.service'; import { SettingsService } from 'jslib-common/services/settings.service'; import { StateService } from 'jslib-common/services/state.service'; +import { StateMigrationService } from 'jslib-common/services/stateMigration.service'; 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 { UserService } from 'jslib-common/services/user.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'; @@ -42,7 +44,7 @@ import { CipherService as CipherServiceAbstraction } from 'jslib-common/abstract import { CollectionService as CollectionServiceAbstraction } from 'jslib-common/abstractions/collection.service'; import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service'; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service'; -import { EnvironmentService as EnvironmentServiceAbstraction, Urls } from 'jslib-common/abstractions/environment.service'; +import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service'; import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service'; import { ExportService as ExportServiceAbstraction } from 'jslib-common/abstractions/export.service'; import { FileUploadService as FileUploadServiceAbstraction } from 'jslib-common/abstractions/fileUpload.service'; @@ -52,21 +54,23 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from 'jslib-com import { LogService } from 'jslib-common/abstractions/log.service'; import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service'; import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service'; +import { OrganizationService as OrganizationServiceAbstraction } from 'jslib-common/abstractions/organization.service'; import { PasswordGenerationService as PasswordGenerationServiceAbstraction, } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service'; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service'; import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service'; +import { ProviderService as ProviderServiceAbstraction } from 'jslib-common/abstractions/provider.service'; import { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service'; import { SendService as SendServiceAbstraction } from 'jslib-common/abstractions/send.service'; import { SettingsService as SettingsServiceAbstraction } from 'jslib-common/abstractions/settings.service'; import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service'; +import { StateMigrationService as StateMigrationServiceAbstraction } from 'jslib-common/abstractions/stateMigration.service'; import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service'; 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 { UserService as UserServiceAbstraction } from 'jslib-common/abstractions/user.service'; import { UserVerificationService as UserVerificationServiceAbstraction } from 'jslib-common/abstractions/userVerification.service'; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service'; @@ -108,7 +112,6 @@ import { ValidationService } from './validation.service'; deps: [ CryptoServiceAbstraction, ApiServiceAbstraction, - UserServiceAbstraction, TokenServiceAbstraction, AppIdServiceAbstraction, I18nServiceAbstraction, @@ -117,28 +120,41 @@ import { ValidationService } from './validation.service'; VaultTimeoutServiceAbstraction, LogService, CryptoFunctionServiceAbstraction, - EnvironmentServiceAbstraction, KeyConnectorServiceAbstraction, + EnvironmentServiceAbstraction, + StateServiceAbstraction, ], }, { provide: CipherServiceAbstraction, - useFactory: (cryptoService: CryptoServiceAbstraction, userService: UserServiceAbstraction, - settingsService: SettingsServiceAbstraction, apiService: ApiServiceAbstraction, - fileUploadService: FileUploadServiceAbstraction, storageService: StorageServiceAbstraction, - i18nService: I18nServiceAbstraction, injector: Injector, logService: LogService) => - new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService, - storageService, i18nService, () => injector.get(SearchServiceAbstraction), logService), + useFactory: ( + cryptoService: CryptoServiceAbstraction, + settingsService: SettingsServiceAbstraction, + apiService: ApiServiceAbstraction, + fileUploadService: FileUploadServiceAbstraction, + i18nService: I18nServiceAbstraction, + injector: Injector, + logService: LogService, + stateService: StateServiceAbstraction, + ) => new CipherService( + cryptoService, + settingsService, + apiService, + fileUploadService, + i18nService, + () => injector.get(SearchServiceAbstraction), + logService, + stateService, + ), deps: [ CryptoServiceAbstraction, - UserServiceAbstraction, SettingsServiceAbstraction, ApiServiceAbstraction, FileUploadServiceAbstraction, - StorageServiceAbstraction, I18nServiceAbstraction, Injector, // TODO: Get rid of this circular dependency! LogService, + StateServiceAbstraction, ], }, { @@ -146,11 +162,10 @@ import { ValidationService } from './validation.service'; useClass: FolderService, deps: [ CryptoServiceAbstraction, - UserServiceAbstraction, ApiServiceAbstraction, - StorageServiceAbstraction, I18nServiceAbstraction, CipherServiceAbstraction, + StateServiceAbstraction, ], }, { provide: LogService, useFactory: () => new ConsoleLogService(false) }, @@ -159,35 +174,33 @@ import { ValidationService } from './validation.service'; useClass: CollectionService, deps: [ CryptoServiceAbstraction, - UserServiceAbstraction, - StorageServiceAbstraction, I18nServiceAbstraction, + StateServiceAbstraction, ], }, { provide: EnvironmentServiceAbstraction, useClass: EnvironmentService, - deps: [StorageServiceAbstraction], + deps: [StateServiceAbstraction], }, { provide: TotpServiceAbstraction, useClass: TotpService, deps: [ - StorageServiceAbstraction, CryptoFunctionServiceAbstraction, LogService, + StateServiceAbstraction, ], }, - { provide: TokenServiceAbstraction, useClass: TokenService, deps: [StorageServiceAbstraction] }, + { provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] }, { provide: CryptoServiceAbstraction, useClass: CryptoService, deps: [ - StorageServiceAbstraction, - 'SECURE_STORAGE', CryptoFunctionServiceAbstraction, PlatformUtilsServiceAbstraction, LogService, + StateServiceAbstraction, ], }, { @@ -195,8 +208,8 @@ import { ValidationService } from './validation.service'; useClass: PasswordGenerationService, deps: [ CryptoServiceAbstraction, - StorageServiceAbstraction, PolicyServiceAbstraction, + StateServiceAbstraction, ], }, { @@ -222,71 +235,121 @@ import { ValidationService } from './validation.service'; }, { provide: SyncServiceAbstraction, - useFactory: (userService: UserServiceAbstraction, apiService: ApiServiceAbstraction, - settingsService: SettingsServiceAbstraction, folderService: FolderServiceAbstraction, - cipherService: CipherServiceAbstraction, cryptoService: CryptoServiceAbstraction, - collectionService: CollectionServiceAbstraction, storageService: StorageServiceAbstraction, - messagingService: MessagingServiceAbstraction, policyService: PolicyServiceAbstraction, - sendService: SendServiceAbstraction, logService: LogService, tokenService: TokenService, - keyConnectorService: KeyConnectorServiceAbstraction) => new SyncService(userService, apiService, - settingsService, folderService, cipherService, cryptoService, collectionService, storageService, - messagingService, policyService, sendService, logService, tokenService, keyConnectorService, - async (expired: boolean) => messagingService.send('logout', { expired: expired })), + useFactory: ( + apiService: ApiServiceAbstraction, + settingsService: SettingsServiceAbstraction, + folderService: FolderServiceAbstraction, + cipherService: CipherServiceAbstraction, + cryptoService: CryptoServiceAbstraction, + collectionService: CollectionServiceAbstraction, + messagingService: MessagingServiceAbstraction, + policyService: PolicyServiceAbstraction, + sendService: SendServiceAbstraction, + logService: LogService, + keyConnectorService: KeyConnectorServiceAbstraction, + stateService: StateServiceAbstraction, + organizationService: OrganizationServiceAbstraction, + providerService: ProviderServiceAbstraction, + ) => new SyncService( + apiService, + settingsService, + folderService, + cipherService, + cryptoService, + collectionService, + messagingService, + policyService, + sendService, + logService, + keyConnectorService, + stateService, + organizationService, + providerService, + async (expired: boolean) => messagingService.send('logout', { expired: expired })), deps: [ - UserServiceAbstraction, ApiServiceAbstraction, SettingsServiceAbstraction, FolderServiceAbstraction, CipherServiceAbstraction, CryptoServiceAbstraction, CollectionServiceAbstraction, - StorageServiceAbstraction, MessagingServiceAbstraction, PolicyServiceAbstraction, SendServiceAbstraction, LogService, - TokenServiceAbstraction, KeyConnectorServiceAbstraction, + StateServiceAbstraction, + OrganizationServiceAbstraction, + ProviderServiceAbstraction, ], }, - { - provide: UserServiceAbstraction, - useClass: UserService, - deps: [TokenServiceAbstraction, StorageServiceAbstraction], - }, { provide: BroadcasterServiceAbstraction, useClass: BroadcasterService }, { provide: SettingsServiceAbstraction, useClass: SettingsService, - deps: [UserServiceAbstraction, StorageServiceAbstraction], + deps: [StateServiceAbstraction], }, { provide: VaultTimeoutServiceAbstraction, - useFactory: (cipherService: CipherServiceAbstraction, folderService: FolderServiceAbstraction, - collectionService: CollectionServiceAbstraction, cryptoService: CryptoServiceAbstraction, - platformUtilsService: PlatformUtilsServiceAbstraction, storageService: StorageServiceAbstraction, - messagingService: MessagingServiceAbstraction, searchService: SearchServiceAbstraction, - userService: UserServiceAbstraction, tokenService: TokenServiceAbstraction, - policyService: PolicyServiceAbstraction, keyConnectorService: KeyConnectorServiceAbstraction) => - new VaultTimeoutService(cipherService, folderService, collectionService, cryptoService, - platformUtilsService, storageService, messagingService, searchService, userService, tokenService, - policyService, keyConnectorService, null, - async () => messagingService.send('logout', { expired: false })), + useFactory: ( + cipherService: CipherServiceAbstraction, + folderService: FolderServiceAbstraction, + collectionService: CollectionServiceAbstraction, + cryptoService: CryptoServiceAbstraction, + platformUtilsService: PlatformUtilsServiceAbstraction, + messagingService: MessagingServiceAbstraction, + searchService: SearchServiceAbstraction, + tokenService: TokenServiceAbstraction, + policyService: PolicyServiceAbstraction, + keyConnectorService: KeyConnectorServiceAbstraction, + stateService: StateServiceAbstraction, + ) => new VaultTimeoutService( + cipherService, + folderService, + collectionService, + cryptoService, + platformUtilsService, + messagingService, + searchService, + tokenService, + policyService, + keyConnectorService, + stateService, + null, + async () => messagingService.send('logout', { expired: false }), + ), deps: [ CipherServiceAbstraction, FolderServiceAbstraction, CollectionServiceAbstraction, CryptoServiceAbstraction, PlatformUtilsServiceAbstraction, - StorageServiceAbstraction, MessagingServiceAbstraction, SearchServiceAbstraction, - UserServiceAbstraction, TokenServiceAbstraction, PolicyServiceAbstraction, + KeyConnectorServiceAbstraction, + StateServiceAbstraction, + ], + }, + { + provide: StateServiceAbstraction, + useClass: StateService, + deps: [ + StorageServiceAbstraction, + 'SECURE_STORAGE', + LogService, + StateMigrationServiceAbstraction, + ], + }, + { + provide: StateMigrationServiceAbstraction, + useClass: StateMigrationService, + deps: [ + StorageServiceAbstraction, + 'SECURE_STORAGE', ], }, - { provide: StateServiceAbstraction, useClass: StateService }, { provide: ExportServiceAbstraction, useClass: ExportService, @@ -308,14 +371,26 @@ import { ValidationService } from './validation.service'; }, { provide: NotificationsServiceAbstraction, - useFactory: (userService: UserServiceAbstraction, syncService: SyncServiceAbstraction, - appIdService: AppIdServiceAbstraction, apiService: ApiServiceAbstraction, - vaultTimeoutService: VaultTimeoutServiceAbstraction, environmentService: EnvironmentServiceAbstraction, - messagingService: MessagingServiceAbstraction, logService: LogService) => - new NotificationsService(userService, syncService, appIdService, apiService, vaultTimeoutService, - environmentService, async () => messagingService.send('logout', { expired: true }), logService), + useFactory: ( + syncService: SyncServiceAbstraction, + appIdService: AppIdServiceAbstraction, + apiService: ApiServiceAbstraction, + vaultTimeoutService: VaultTimeoutServiceAbstraction, + environmentService: EnvironmentServiceAbstraction, + messagingService: MessagingServiceAbstraction, + logService: LogService, + stateService: StateServiceAbstraction, + ) => new NotificationsService( + syncService, + appIdService, + apiService, + vaultTimeoutService, + environmentService, + async () => messagingService.send('logout', { expired: true }), + logService, + stateService, + ), deps: [ - UserServiceAbstraction, SyncServiceAbstraction, AppIdServiceAbstraction, ApiServiceAbstraction, @@ -323,6 +398,7 @@ import { ValidationService } from './validation.service'; EnvironmentServiceAbstraction, MessagingServiceAbstraction, LogService, + StateServiceAbstraction, ], }, { @@ -334,19 +410,19 @@ import { ValidationService } from './validation.service'; provide: EventServiceAbstraction, useClass: EventService, deps: [ - StorageServiceAbstraction, ApiServiceAbstraction, - UserServiceAbstraction, CipherServiceAbstraction, + StateServiceAbstraction, LogService, + OrganizationServiceAbstraction, ], }, { provide: PolicyServiceAbstraction, useClass: PolicyService, deps: [ - UserServiceAbstraction, - StorageServiceAbstraction, + StateServiceAbstraction, + OrganizationServiceAbstraction, ApiServiceAbstraction, ], }, @@ -355,24 +431,23 @@ import { ValidationService } from './validation.service'; useClass: SendService, deps: [ CryptoServiceAbstraction, - UserServiceAbstraction, ApiServiceAbstraction, FileUploadServiceAbstraction, - StorageServiceAbstraction, I18nServiceAbstraction, CryptoFunctionServiceAbstraction, + StateServiceAbstraction, ], }, { provide: KeyConnectorServiceAbstraction, useClass: KeyConnectorService, deps: [ - StorageServiceAbstraction, - UserServiceAbstraction, + StateServiceAbstraction, CryptoServiceAbstraction, ApiServiceAbstraction, TokenServiceAbstraction, LogService, + OrganizationServiceAbstraction, ], }, { @@ -385,6 +460,20 @@ import { ValidationService } from './validation.service'; ], }, { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, + { + provide: OrganizationServiceAbstraction, + useClass: OrganizationService, + deps: [ + StateServiceAbstraction, + ], + }, + { + provide: ProviderServiceAbstraction, + useClass: ProviderService, + deps: [ + StateServiceAbstraction, + ], + }, ], }) export class JslibServicesModule { diff --git a/angular/src/services/lock-guard.service.ts b/angular/src/services/lock-guard.service.ts index 400eedc555..bde728e302 100644 --- a/angular/src/services/lock-guard.service.ts +++ b/angular/src/services/lock-guard.service.ts @@ -4,29 +4,26 @@ import { Router, } from '@angular/router'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; @Injectable() export class LockGuardService implements CanActivate { - protected homepage = 'vault'; - constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService, - private router: Router) { } + constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router, + private stateService: StateService) { } async canActivate() { - const isAuthed = await this.userService.isAuthenticated(); - if (isAuthed) { - const locked = await this.vaultTimeoutService.isLocked(); - if (locked) { - return true; - } else { - this.router.navigate([this.homepage]); - return false; - } + if (!await this.stateService.getIsAuthenticated()) { + this.router.navigate(['login']); + return false; } - this.router.navigate(['']); - return false; + if (!await this.vaultTimeoutService.isLocked()) { + this.router.navigate([this.homepage]); + return false; + } + + return true; } } diff --git a/angular/src/services/unauth-guard.service.ts b/angular/src/services/unauth-guard.service.ts index 786241b887..26b0d53cdb 100644 --- a/angular/src/services/unauth-guard.service.ts +++ b/angular/src/services/unauth-guard.service.ts @@ -1,22 +1,21 @@ import { Injectable } from '@angular/core'; import { - ActivatedRouteSnapshot, CanActivate, Router, } from '@angular/router'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; @Injectable() export class UnauthGuardService implements CanActivate { protected homepage = 'vault'; - constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService, - private router: Router) { } + constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router, + private stateService: StateService) { } async canActivate() { - const isAuthed = await this.userService.isAuthenticated(); + const isAuthed = await this.stateService.getIsAuthenticated(); if (isAuthed) { const locked = await this.vaultTimeoutService.isLocked(); if (locked) { @@ -26,7 +25,6 @@ export class UnauthGuardService implements CanActivate { } return false; } - return true; } } diff --git a/common/src/abstractions/apiKey.service.ts b/common/src/abstractions/apiKey.service.ts deleted file mode 100644 index bc4b8a4437..0000000000 --- a/common/src/abstractions/apiKey.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -export abstract class ApiKeyService { - setInformation: (clientId: string, clientSecret: string) => Promise; - clear: () => Promise; - getClientId: () => Promise; - getClientSecret: () => Promise; - getEntityType: () => Promise; - getEntityId: () => Promise; - isAuthenticated: () => Promise; -} diff --git a/common/src/abstractions/cipher.service.ts b/common/src/abstractions/cipher.service.ts index 941c3826ac..d8db4a628c 100644 --- a/common/src/abstractions/cipher.service.ts +++ b/common/src/abstractions/cipher.service.ts @@ -11,9 +11,7 @@ import { CipherView } from '../models/view/cipherView'; import { FieldView } from '../models/view/fieldView'; export abstract class CipherService { - decryptedCipherCache: CipherView[]; - - clearCache: () => void; + clearCache: (userId?: string) => Promise; encrypt: (model: CipherView, key?: SymmetricCryptoKey, originalCipher?: Cipher) => Promise; encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise; encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise; diff --git a/common/src/abstractions/collection.service.ts b/common/src/abstractions/collection.service.ts index 0904553793..987fcdd049 100644 --- a/common/src/abstractions/collection.service.ts +++ b/common/src/abstractions/collection.service.ts @@ -6,9 +6,7 @@ import { TreeNode } from '../models/domain/treeNode'; import { CollectionView } from '../models/view/collectionView'; export abstract class CollectionService { - decryptedCollectionCache: CollectionView[]; - - clearCache: () => void; + clearCache: (userId?: string) => Promise; encrypt: (model: CollectionView) => Promise; decryptMany: (collections: Collection[]) => Promise; get: (id: string) => Promise; diff --git a/common/src/abstractions/crypto.service.ts b/common/src/abstractions/crypto.service.ts index 841e61ab07..34d38108dd 100644 --- a/common/src/abstractions/crypto.service.ts +++ b/common/src/abstractions/crypto.service.ts @@ -8,17 +8,16 @@ import { ProfileProviderResponse } from '../models/response/profileProviderRespo import { HashPurpose } from '../enums/hashPurpose'; import { KdfType } from '../enums/kdfType'; - -import { KeySuffixOptions } from './storage.service'; +import { KeySuffixOptions } from '../enums/keySuffixOptions'; export abstract class CryptoService { setKey: (key: SymmetricCryptoKey) => Promise; - setKeyHash: (keyHash: string) => Promise<{}>; - setEncKey: (encKey: string) => Promise<{}>; - setEncPrivateKey: (encPrivateKey: string) => Promise<{}>; - setOrgKeys: (orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]) => Promise<{}>; - setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<{}>; - getKey: (keySuffix?: KeySuffixOptions) => Promise; + setKeyHash: (keyHash: string) => Promise; + setEncKey: (encKey: string) => Promise; + setEncPrivateKey: (encPrivateKey: string) => Promise; + setOrgKeys: (orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]) => Promise; + setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise; + getKey: (keySuffix?: KeySuffixOptions, userId?: string) => Promise; getKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise; getKeyHash: () => Promise; compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise; @@ -30,17 +29,17 @@ export abstract class CryptoService { getOrgKey: (orgId: string) => Promise; getProviderKey: (providerId: string) => Promise; hasKey: () => Promise; - hasKeyInMemory: () => boolean; - hasKeyStored: (keySuffix?: KeySuffixOptions) => Promise; + hasKeyInMemory: (userId?: string) => Promise; + hasKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise; hasEncKey: () => Promise; - clearKey: (clearSecretStorage?: boolean) => Promise; + clearKey: (clearSecretStorage?: boolean, userId?: string) => Promise; clearKeyHash: () => Promise; - clearEncKey: (memoryOnly?: boolean) => Promise; - clearKeyPair: (memoryOnly?: boolean) => Promise; - clearOrgKeys: (memoryOnly?: boolean) => Promise; + clearEncKey: (memoryOnly?: boolean, userId?: string) => Promise; + clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise; + clearOrgKeys: (memoryOnly?: boolean, userId?: string) => Promise; clearProviderKeys: (memoryOnly?: boolean) => Promise; clearPinProtectedKey: () => Promise; - clearKeys: () => Promise; + clearKeys: (userId?: string) => Promise; toggleKey: () => Promise; makeKey: (password: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise; makeKeyFromPin: (pin: string, salt: string, kdf: KdfType, kdfIterations: number, diff --git a/common/src/abstractions/event.service.ts b/common/src/abstractions/event.service.ts index 40c080275f..ace1027817 100644 --- a/common/src/abstractions/event.service.ts +++ b/common/src/abstractions/event.service.ts @@ -2,6 +2,6 @@ import { EventType } from '../enums/eventType'; export abstract class EventService { collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise; - uploadEvents: () => Promise; - clearEvents: () => Promise; + uploadEvents: (userId?: string) => Promise; + clearEvents: (userId?: string) => Promise; } diff --git a/common/src/abstractions/folder.service.ts b/common/src/abstractions/folder.service.ts index f1997ffef5..339aec892d 100644 --- a/common/src/abstractions/folder.service.ts +++ b/common/src/abstractions/folder.service.ts @@ -7,9 +7,7 @@ import { TreeNode } from '../models/domain/treeNode'; import { FolderView } from '../models/view/folderView'; export abstract class FolderService { - decryptedFolderCache: FolderView[]; - - clearCache: () => void; + clearCache: (userId?: string) => Promise; encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise; get: (id: string) => Promise; getAll: () => Promise; diff --git a/common/src/abstractions/organization.service.ts b/common/src/abstractions/organization.service.ts new file mode 100644 index 0000000000..a9af9faaac --- /dev/null +++ b/common/src/abstractions/organization.service.ts @@ -0,0 +1,11 @@ +import { OrganizationData } from '../models/data/organizationData'; + +import { Organization } from '../models/domain/organization'; + +export abstract class OrganizationService { + get: (id: string) => Promise; + getByIdentifier: (identifier: string) => Promise; + getAll: (userId?: string) => Promise; + save: (orgs: {[id: string]: OrganizationData}) => Promise; + canManageSponsorships: () => Promise; +} diff --git a/common/src/abstractions/passwordGeneration.service.ts b/common/src/abstractions/passwordGeneration.service.ts index 52d18dde7c..163e09c46e 100644 --- a/common/src/abstractions/passwordGeneration.service.ts +++ b/common/src/abstractions/passwordGeneration.service.ts @@ -12,7 +12,7 @@ export abstract class PasswordGenerationService { saveOptions: (options: any) => Promise; getHistory: () => Promise; addHistory: (password: string) => Promise; - clear: () => Promise; + clear: (userId?: string) => Promise; passwordStrength: (password: string, userInputs?: string[]) => zxcvbn.ZXCVBNResult; normalizeOptions: (options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) => void; } diff --git a/common/src/abstractions/policy.service.ts b/common/src/abstractions/policy.service.ts index 6acc8253fc..84ff94ac5b 100644 --- a/common/src/abstractions/policy.service.ts +++ b/common/src/abstractions/policy.service.ts @@ -10,17 +10,15 @@ import { PolicyResponse } from '../models/response/policyResponse'; import { PolicyType } from '../enums/policyType'; export abstract class PolicyService { - policyCache: Policy[]; - clearCache: () => void; - getAll: (type?: PolicyType) => Promise; + getAll: (type?: PolicyType, userId?: string) => Promise; getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise; replace: (policies: { [id: string]: PolicyData; }) => Promise; - clear: (userId: string) => Promise; + clear: (userId?: string) => Promise; getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise; evaluateMasterPassword: (passwordStrength: number, newPassword: string, enforcedPolicyOptions?: MasterPasswordPolicyOptions) => boolean; getResetPasswordPolicyOptions: (policies: Policy[], orgId: string) => [ResetPasswordPolicyOptions, boolean]; mapPoliciesFromToken: (policiesResponse: ListResponse) => Policy[]; - policyAppliesToUser: (policyType: PolicyType, policyFilter?: (policy: Policy) => boolean) => Promise; + policyAppliesToUser: (policyType: PolicyType, policyFilter?: (policy: Policy) => boolean, userId?: string) => Promise; } diff --git a/common/src/abstractions/provider.service.ts b/common/src/abstractions/provider.service.ts new file mode 100644 index 0000000000..e3ca562569 --- /dev/null +++ b/common/src/abstractions/provider.service.ts @@ -0,0 +1,9 @@ +import { ProviderData } from '../models/data/providerData'; + +import { Provider } from '../models/domain/provider'; + +export abstract class ProviderService { + get: (id: string) => Promise; + getAll: () => Promise; + save: (providers: {[id: string]: ProviderData}) => Promise; +} diff --git a/common/src/abstractions/send.service.ts b/common/src/abstractions/send.service.ts index f7ede22376..2763dcda90 100644 --- a/common/src/abstractions/send.service.ts +++ b/common/src/abstractions/send.service.ts @@ -7,9 +7,7 @@ import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; import { SendView } from '../models/view/sendView'; export abstract class SendService { - decryptedSendCache: SendView[]; - - clearCache: () => void; + clearCache: () => Promise; encrypt: (model: SendView, file: File | ArrayBuffer, password: string, key?: SymmetricCryptoKey) => Promise<[Send, EncArrayBuffer]>; get: (id: string) => Promise; getAll: () => Promise; diff --git a/common/src/abstractions/settings.service.ts b/common/src/abstractions/settings.service.ts index 6104c1d1be..1112d557a1 100644 --- a/common/src/abstractions/settings.service.ts +++ b/common/src/abstractions/settings.service.ts @@ -1,6 +1,6 @@ export abstract class SettingsService { - clearCache: () => void; + clearCache: () => Promise; getEquivalentDomains: () => Promise; setEquivalentDomains: (equivalentDomains: string[][]) => Promise; - clear: (userId: string) => Promise; + clear: (userId?: string) => Promise; } diff --git a/common/src/abstractions/state.service.ts b/common/src/abstractions/state.service.ts index 78658882cd..042639fe8c 100644 --- a/common/src/abstractions/state.service.ts +++ b/common/src/abstractions/state.service.ts @@ -1,6 +1,259 @@ +import { BehaviorSubject } from 'rxjs'; + +import { KdfType } from '../enums/kdfType'; +import { UriMatchType } from '../enums/uriMatchType'; + +import { CipherData } from '../models/data/cipherData'; +import { CollectionData } from '../models/data/collectionData'; +import { EventData } from '../models/data/eventData'; +import { FolderData } from '../models/data/folderData'; +import { OrganizationData } from '../models/data/organizationData'; +import { PolicyData } from '../models/data/policyData'; +import { ProviderData } from '../models/data/providerData'; +import { SendData } from '../models/data/sendData'; + +import { Account } from '../models/domain/account'; +import { EncString } from '../models/domain/encString'; +import { GeneratedPasswordHistory } from '../models/domain/generatedPasswordHistory'; +import { Policy } from '../models/domain/policy'; +import { StorageOptions } from '../models/domain/storageOptions'; +import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; + +import { CipherView } from '../models/view/cipherView'; +import { CollectionView } from '../models/view/collectionView'; +import { FolderView } from '../models/view/folderView'; +import { SendView } from '../models/view/sendView'; + export abstract class StateService { - get: (key: string) => Promise; - save: (key: string, obj: any) => Promise; - remove: (key: string) => Promise; - purge: () => Promise; + accounts: BehaviorSubject<{ [userId: string]: Account }>; + activeAccount: BehaviorSubject; + + addAccount: (account: Account) => Promise; + setActiveUser: (userId: string) => Promise; + clean: (options?: StorageOptions) => Promise; + init: () => Promise; + + getAccessToken: (options?: StorageOptions) => Promise; + setAccessToken: (value: string, options?: StorageOptions) => Promise; + getAddEditCipherInfo: (options?: StorageOptions) => Promise; + setAddEditCipherInfo: (value: any, options?: StorageOptions) => Promise; + getAlwaysShowDock: (options?: StorageOptions) => Promise; + setAlwaysShowDock: (value: boolean, options?: StorageOptions) => Promise; + getApiKeyClientId: (options?: StorageOptions) => Promise; + setApiKeyClientId: (value: string, options?: StorageOptions) => Promise; + getApiKeyClientSecret: (options?: StorageOptions) => Promise; + setApiKeyClientSecret: (value: string, options?: StorageOptions) => Promise; + getAutoConfirmFingerPrints: (options?: StorageOptions) => Promise; + setAutoConfirmFingerprints: (value: boolean, options?: StorageOptions) => Promise; + getAutoFillOnPageLoadDefault: (options?: StorageOptions) => Promise; + setAutoFillOnPageLoadDefault: (value: boolean, options?: StorageOptions) => Promise; + getBiometricAwaitingAcceptance: (options?: StorageOptions) => Promise; + setBiometricAwaitingAcceptance: (value: boolean, options?: StorageOptions) => Promise; + getBiometricFingerprintValidated: (options?: StorageOptions) => Promise; + setBiometricFingerprintValidated: (value: boolean, options?: StorageOptions) => Promise; + getBiometricLocked: (options?: StorageOptions) => Promise; + setBiometricLocked: (value: boolean, options?: StorageOptions) => Promise; + getBiometricText: (options?: StorageOptions) => Promise; + setBiometricText: (value: string, options?: StorageOptions) => Promise; + getBiometricUnlock: (options?: StorageOptions) => Promise; + setBiometricUnlock: (value: boolean, options?: StorageOptions) => Promise; + getCanAccessPremium: (options?: StorageOptions) => Promise; + getClearClipboard: (options?: StorageOptions) => Promise; + setClearClipboard: (value: number, options?: StorageOptions) => Promise; + getCollapsedGroupings: (options?: StorageOptions) => Promise>; + setCollapsedGroupings: (value: Set, options?: StorageOptions) => Promise; + getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise; + setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise; + getCryptoMasterKey: (options?: StorageOptions) => Promise; + setCryptoMasterKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise; + getCryptoMasterKeyAuto: (options?: StorageOptions) => Promise; + setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise; + getCryptoMasterKeyB64: (options?: StorageOptions) => Promise; + setCryptoMasterKeyB64: (value: string, options?: StorageOptions) => Promise; + getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise; + hasCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise; + setCryptoMasterKeyBiometric: (value: string, options?: StorageOptions) => Promise; + getDecodedToken: (options?: StorageOptions) => Promise; + setDecodedToken: (value: any, options?: StorageOptions) => Promise; + getDecryptedCiphers: (options?: StorageOptions) => Promise; + setDecryptedCiphers: (value: CipherView[], options?: StorageOptions) => Promise; + getDecryptedCollections: (options?: StorageOptions) => Promise; + setDecryptedCollections: (value: CollectionView[], options?: StorageOptions) => Promise; + getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; + setDecryptedCryptoSymmetricKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise; + getDecryptedFolders: (options?: StorageOptions) => Promise; + setDecryptedFolders: (value: FolderView[], options?: StorageOptions) => Promise; + getDecryptedOrganizationKeys: (options?: StorageOptions) => Promise>; + setDecryptedOrganizationKeys: (value: Map, options?: StorageOptions) => Promise; + getDecryptedPasswordGenerationHistory: (options?: StorageOptions) => Promise; + setDecryptedPasswordGenerationHistory: (value: GeneratedPasswordHistory[], options?: StorageOptions) => Promise; + getDecryptedPinProtected: (options?: StorageOptions) => Promise; + setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise; + getDecryptedPolicies: (options?: StorageOptions) => Promise; + setDecryptedPolicies: (value: Policy[], options?: StorageOptions) => Promise; + getDecryptedPrivateKey: (options?: StorageOptions) => Promise; + setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise; + getDecryptedProviderKeys: (options?: StorageOptions) => Promise>; + setDecryptedProviderKeys: (value: Map, options?: StorageOptions) => Promise; + getDecryptedSends: (options?: StorageOptions) => Promise; + setDecryptedSends: (value: SendView[], options?: StorageOptions) => Promise; + getDefaultUriMatch: (options?: StorageOptions) => Promise; + setDefaultUriMatch: (value: UriMatchType, options?: StorageOptions) => Promise; + getDisableAddLoginNotification: (options?: StorageOptions) => Promise; + setDisableAddLoginNotification: (value: boolean, options?: StorageOptions) => Promise; + getDisableAutoBiometricsPrompt: (options?: StorageOptions) => Promise; + setDisableAutoBiometricsPrompt: (value: boolean, options?: StorageOptions) => Promise; + getDisableAutoTotpCopy: (options?: StorageOptions) => Promise; + setDisableAutoTotpCopy: (value: boolean, options?: StorageOptions) => Promise; + getDisableBadgeCounter: (options?: StorageOptions) => Promise; + setDisableBadgeCounter: (value: boolean, options?: StorageOptions) => Promise; + getDisableChangedPasswordNotification: (options?: StorageOptions) => Promise; + setDisableChangedPasswordNotification: (value: boolean, options?: StorageOptions) => Promise; + getDisableContextMenuItem: (options?: StorageOptions) => Promise; + setDisableContextMenuItem: (value: boolean, options?: StorageOptions) => Promise; + getDisableFavicon: (options?: StorageOptions) => Promise; + setDisableFavicon: (value: boolean, options?: StorageOptions) => Promise; + getDisableGa: (options?: StorageOptions) => Promise; + setDisableGa: (value: boolean, options?: StorageOptions) => Promise; + getDontShowCardsCurrentTab: (options?: StorageOptions) => Promise; + setDontShowCardsCurrentTab: (value: boolean, options?: StorageOptions) => Promise; + getDontShowIdentitiesCurrentTab: (options?: StorageOptions) => Promise; + setDontShowIdentitiesCurrentTab: (value: boolean, options?: StorageOptions) => Promise; + getEmail: (options?: StorageOptions) => Promise; + setEmail: (value: string, options?: StorageOptions) => Promise; + getEmailVerified: (options?: StorageOptions) => Promise; + setEmailVerified: (value: boolean, options?: StorageOptions) => Promise; + getEnableAlwaysOnTop: (options?: StorageOptions) => Promise; + setEnableAlwaysOnTop: (value: boolean, options?: StorageOptions) => Promise; + getEnableAutoFillOnPageLoad: (options?: StorageOptions) => Promise; + setEnableAutoFillOnPageLoad: (value: boolean, options?: StorageOptions) => Promise; + getEnableBiometric: (options?: StorageOptions) => Promise; + setEnableBiometric: (value: boolean, options?: StorageOptions) => Promise; + getEnableBrowserIntegration: (options?: StorageOptions) => Promise; + setEnableBrowserIntegration: (value: boolean, options?: StorageOptions) => Promise; + getEnableBrowserIntegrationFingerprint: (options?: StorageOptions) => Promise; + setEnableBrowserIntegrationFingerprint: (value: boolean, options?: StorageOptions) => Promise; + getEnableCloseToTray: (options?: StorageOptions) => Promise; + setEnableCloseToTray: (value: boolean, options?: StorageOptions) => Promise; + getEnableFullWidth: (options?: StorageOptions) => Promise; + setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise; + getEnableGravitars: (options?: StorageOptions) => Promise; + setEnableGravitars: (value: boolean, options?: StorageOptions) => Promise; + getEnableMinimizeToTray: (options?: StorageOptions) => Promise; + setEnableMinimizeToTray: (value: boolean, options?: StorageOptions) => Promise; + getEnableStartToTray: (options?: StorageOptions) => Promise; + setEnableStartToTray: (value: boolean, options?: StorageOptions) => Promise; + getEnableTray: (options?: StorageOptions) => Promise; + setEnableTray: (value: boolean, options?: StorageOptions) => Promise; + getEncryptedCiphers: (options?: StorageOptions) => Promise<{ [id: string]: CipherData }>; + setEncryptedCiphers: (value: { [id: string]: CipherData }, options?: StorageOptions) => Promise; + getEncryptedCollections: (options?: StorageOptions) => Promise<{ [id: string]: CollectionData }>; + setEncryptedCollections: (value: { [id: string]: CollectionData }, options?: StorageOptions) => Promise; + getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; + setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise; + getEncryptedFolders: (options?: StorageOptions) => Promise<{ [id: string]: FolderData }>; + setEncryptedFolders: (value: { [id: string]: FolderData }, options?: StorageOptions) => Promise; + getEncryptedOrganizationKeys: (options?: StorageOptions) => Promise; + setEncryptedOrganizationKeys: (value: Map, options?: StorageOptions) => Promise; + getEncryptedPasswordGenerationHistory: (options?: StorageOptions) => Promise; + setEncryptedPasswordGenerationHistory: (value: GeneratedPasswordHistory[], options?: StorageOptions) => Promise; + getEncryptedPinProtected: (options?: StorageOptions) => Promise; + setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise; + getEncryptedPolicies: (options?: StorageOptions) => Promise<{ [id: string]: PolicyData }>; + setEncryptedPolicies: (value: { [id: string]: PolicyData }, options?: StorageOptions) => Promise; + getEncryptedPrivateKey: (options?: StorageOptions) => Promise; + setEncryptedPrivateKey: (value: string, options?: StorageOptions) => Promise; + getEncryptedProviderKeys: (options?: StorageOptions) => Promise; + setEncryptedProviderKeys: (value: any, options?: StorageOptions) => Promise; + getEncryptedSends: (options?: StorageOptions) => Promise<{ [id: string]: SendData }>; + setEncryptedSends: (value: { [id: string]: SendData }, options?: StorageOptions) => Promise; + getEntityId: (options?: StorageOptions) => Promise; + setEntityId: (value: string, options?: StorageOptions) => Promise; + getEntityType: (options?: StorageOptions) => Promise; + setEntityType: (value: string, options?: StorageOptions) => Promise; + getEnvironmentUrls: (options?: StorageOptions) => Promise; + setEnvironmentUrls: (value: any, options?: StorageOptions) => Promise; + getEquivalentDomains: (options?: StorageOptions) => Promise; + setEquivalentDomains: (value: string, options?: StorageOptions) => Promise; + getEventCollection: (options?: StorageOptions) => Promise; + setEventCollection: (value: EventData[], options?: StorageOptions) => Promise; + getEverBeenUnlocked: (options?: StorageOptions) => Promise; + setEverBeenUnlocked: (value: boolean, options?: StorageOptions) => Promise; + getForcePasswordReset: (options?: StorageOptions) => Promise; + setForcePasswordReset: (value: boolean, options?: StorageOptions) => Promise; + getInstalledVersion: (options?: StorageOptions) => Promise; + setInstalledVersion: (value: string, options?: StorageOptions) => Promise; + getIsAuthenticated: (options?: StorageOptions) => Promise; + getKdfIterations: (options?: StorageOptions) => Promise; + setKdfIterations: (value: number, options?: StorageOptions) => Promise; + getKdfType: (options?: StorageOptions) => Promise; + setKdfType: (value: KdfType, options?: StorageOptions) => Promise; + getKeyHash: (options?: StorageOptions) => Promise; + setKeyHash: (value: string, options?: StorageOptions) => Promise; + getLastActive: (options?: StorageOptions) => Promise; + setLastActive: (value: number, options?: StorageOptions) => Promise; + getLastSync: (options?: StorageOptions) => Promise; + setLastSync: (value: string, options?: StorageOptions) => Promise; + getLegacyEtmKey: (options?: StorageOptions) => Promise; + setLegacyEtmKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise; + getLocalData: (options?: StorageOptions) => Promise; + setLocalData: (value: string, options?: StorageOptions) => Promise; + getLocale: (options?: StorageOptions) => Promise; + setLocale: (value: string, options?: StorageOptions) => Promise; + getLoginRedirect: (options?: StorageOptions) => Promise; + setLoginRedirect: (value: any, options?: StorageOptions) => Promise; + getMainWindowSize: (options?: StorageOptions) => Promise; + setMainWindowSize: (value: number, options?: StorageOptions) => Promise; + getMinimizeOnCopyToClipboard: (options?: StorageOptions) => Promise; + setMinimizeOnCopyToClipboard: (value: boolean, options?: StorageOptions) => Promise; + getNeverDomains: (options?: StorageOptions) => Promise<{ [id: string]: any }>; + setNeverDomains: (value: { [id: string]: any }, options?: StorageOptions) => Promise; + getNoAutoPromptBiometrics: (options?: StorageOptions) => Promise; + setNoAutoPromptBiometrics: (value: boolean, options?: StorageOptions) => Promise; + getNoAutoPromptBiometricsText: (options?: StorageOptions) => Promise; + setNoAutoPromptBiometricsText: (value: string, options?: StorageOptions) => Promise; + getOpenAtLogin: (options?: StorageOptions) => Promise; + setOpenAtLogin: (value: boolean, options?: StorageOptions) => Promise; + getOrganizationInvitation: (options?: StorageOptions) => Promise; + setOrganizationInvitation: (value: any, options?: StorageOptions) => Promise; + getOrganizations: (options?: StorageOptions) => Promise<{ [id: string]: OrganizationData }>; + setOrganizations: (value: { [id: string]: OrganizationData }, options?: StorageOptions) => Promise; + getPasswordGenerationOptions: (options?: StorageOptions) => Promise; + setPasswordGenerationOptions: (value: any, options?: StorageOptions) => Promise; + getProtectedPin: (options?: StorageOptions) => Promise; + setProtectedPin: (value: string, options?: StorageOptions) => Promise; + getProviders: (options?: StorageOptions) => Promise<{ [id: string]: ProviderData }>; + setProviders: (value: { [id: string]: ProviderData }, options?: StorageOptions) => Promise; + getPublicKey: (options?: StorageOptions) => Promise; + setPublicKey: (value: ArrayBuffer, options?: StorageOptions) => Promise; + getRefreshToken: (options?: StorageOptions) => Promise; + setRefreshToken: (value: string, options?: StorageOptions) => Promise; + getRememberedEmail: (options?: StorageOptions) => Promise; + setRememberedEmail: (value: string, options?: StorageOptions) => Promise; + getSecurityStamp: (options?: StorageOptions) => Promise; + setSecurityStamp: (value: string, options?: StorageOptions) => Promise; + getSettings: (options?: StorageOptions) => Promise; + setSettings: (value: string, options?: StorageOptions) => Promise; + getSsoCodeVerifier: (options?: StorageOptions) => Promise; + setSsoCodeVerifier: (value: string, options?: StorageOptions) => Promise; + getSsoOrgIdentifier: (options?: StorageOptions) => Promise; + setSsoOrganizationIdentifier: (value: string, options?: StorageOptions) => Promise; + getSsoState: (options?: StorageOptions) => Promise; + setSsoState: (value: string, options?: StorageOptions) => Promise; + getTheme: (options?: StorageOptions) => Promise; + setTheme: (value: string, options?: StorageOptions) => Promise; + getTwoFactorToken: (options?: StorageOptions) => Promise; + setTwoFactorToken: (value: string, options?: StorageOptions) => Promise; + getUserId: (options?: StorageOptions) => Promise; + getUsesKeyConnector: (options?: StorageOptions) => Promise; + setUsesKeyConnector: (vaule: boolean, options?: StorageOptions) => Promise; + getVaultTimeout: (options?: StorageOptions) => Promise; + setVaultTimeout: (value: number, options?: StorageOptions) => Promise; + getVaultTimeoutAction: (options?: StorageOptions) => Promise; + setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise; + getStateVersion: () => Promise; + setStateVersion: (value: number) => Promise; + getWindow: () => Promise>; + setWindow: (value: Map) => Promise; } + diff --git a/common/src/abstractions/stateMigration.service.ts b/common/src/abstractions/stateMigration.service.ts new file mode 100644 index 0000000000..f367a9ed24 --- /dev/null +++ b/common/src/abstractions/stateMigration.service.ts @@ -0,0 +1,4 @@ +export abstract class StateMigrationService { + needsMigration: () => Promise; + migrate: () => Promise; +} diff --git a/common/src/abstractions/storage.service.ts b/common/src/abstractions/storage.service.ts index cfedb2d05b..6599267ac1 100644 --- a/common/src/abstractions/storage.service.ts +++ b/common/src/abstractions/storage.service.ts @@ -1,12 +1,9 @@ +import { StorageOptions } from '../models/domain/storageOptions'; + export abstract class StorageService { - get: (key: string, options?: StorageServiceOptions) => Promise; - has: (key: string, options?: StorageServiceOptions) => Promise; - save: (key: string, obj: any, options?: StorageServiceOptions) => Promise; - remove: (key: string, options?: StorageServiceOptions) => Promise; + get: (key: string, options?: StorageOptions) => Promise; + has: (key: string, options?: StorageOptions) => Promise; + save: (key: string, obj: any, options?: StorageOptions) => Promise; + remove: (key: string, options?: StorageOptions) => Promise; } -export interface StorageServiceOptions { - keySuffix: KeySuffixOptions; -} - -export type KeySuffixOptions = 'auto' | 'biometric'; diff --git a/common/src/abstractions/sync.service.ts b/common/src/abstractions/sync.service.ts index 5d35cf20bc..31cb0d259c 100644 --- a/common/src/abstractions/sync.service.ts +++ b/common/src/abstractions/sync.service.ts @@ -8,7 +8,7 @@ export abstract class SyncService { syncInProgress: boolean; getLastSync: () => Promise; - setLastSync: (date: Date) => Promise; + setLastSync: (date: Date, userId?: string) => Promise; fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise; syncUpsertFolder: (notification: SyncFolderNotification, isEdit: boolean) => Promise; syncDeleteFolder: (notification: SyncFolderNotification) => Promise; diff --git a/common/src/abstractions/system.service.ts b/common/src/abstractions/system.service.ts index c8948bc11b..203233f2ea 100644 --- a/common/src/abstractions/system.service.ts +++ b/common/src/abstractions/system.service.ts @@ -1,6 +1,6 @@ export abstract class SystemService { - startProcessReload: () => void; + startProcessReload: () => Promise; cancelProcessReload: () => void; - clearClipboard: (clipboardValue: string, timeoutMs?: number) => void; + clearClipboard: (clipboardValue: string, timeoutMs?: number) => Promise; clearPendingClipboard: () => Promise; } diff --git a/common/src/abstractions/token.service.ts b/common/src/abstractions/token.service.ts index 73c960ec34..e4e2eb0286 100644 --- a/common/src/abstractions/token.service.ts +++ b/common/src/abstractions/token.service.ts @@ -1,7 +1,4 @@ export abstract class TokenService { - token: string; - decodedToken: any; - refreshToken: string; setTokens: (accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]) => Promise; setToken: (token: string) => Promise; getToken: () => Promise; @@ -15,16 +12,16 @@ export abstract class TokenService { setTwoFactorToken: (token: string, email: string) => Promise; getTwoFactorToken: (email: string) => Promise; clearTwoFactorToken: (email: string) => Promise; - clearToken: () => Promise; - decodeToken: () => any; - getTokenExpirationDate: () => Date; - tokenSecondsRemaining: (offsetSeconds?: number) => number; - tokenNeedsRefresh: (minutes?: number) => boolean; - getUserId: () => string; - getEmail: () => string; - getEmailVerified: () => boolean; - getName: () => string; - getPremium: () => boolean; - getIssuer: () => string; - getIsExternal: () => boolean; + clearToken: (userId?: string) => Promise; + decodeToken: (token?: string) => any; + getTokenExpirationDate: () => Promise; + tokenSecondsRemaining: (offsetSeconds?: number) => Promise; + tokenNeedsRefresh: (minutes?: number) => Promise; + getUserId: () => Promise; + getEmail: () => Promise; + getEmailVerified: () => Promise; + getName: () => Promise; + getPremium: () => Promise; + getIssuer: () => Promise; + getIsExternal: () => Promise; } diff --git a/common/src/abstractions/user.service.ts b/common/src/abstractions/user.service.ts deleted file mode 100644 index 8170ae73a7..0000000000 --- a/common/src/abstractions/user.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { OrganizationData } from '../models/data/organizationData'; -import { ProviderData } from '../models/data/providerData'; - -import { Organization } from '../models/domain/organization'; -import { Provider } from '../models/domain/provider'; - -import { KdfType } from '../enums/kdfType'; - -export abstract class UserService { - setInformation: (userId: string, email: string, kdf: KdfType, kdfIterations: number) => Promise; - setEmailVerified: (emailVerified: boolean) => Promise; - setSecurityStamp: (stamp: string) => Promise; - setForcePasswordReset: (forcePasswordReset: boolean) => Promise; - getUserId: () => Promise; - getEmail: () => Promise; - getSecurityStamp: () => Promise; - getKdf: () => Promise; - getKdfIterations: () => Promise; - getEmailVerified: () => Promise; - getForcePasswordReset: () => Promise; - clear: () => Promise; - isAuthenticated: () => Promise; - canAccessPremium: () => Promise; - canManageSponsorships: () => Promise; - getOrganization: (id: string) => Promise; - getOrganizationByIdentifier: (identifier: string) => Promise; - getAllOrganizations: () => Promise; - replaceOrganizations: (organizations: { [id: string]: OrganizationData; }) => Promise; - clearOrganizations: (userId: string) => Promise; - getProvider: (id: string) => Promise; - getAllProviders: () => Promise; - replaceProviders: (providers: { [id: string]: ProviderData; }) => Promise; - clearProviders: (userId: string) => Promise; -} diff --git a/common/src/abstractions/vaultTimeout.service.ts b/common/src/abstractions/vaultTimeout.service.ts index a5944c4813..0ac2ee8630 100644 --- a/common/src/abstractions/vaultTimeout.service.ts +++ b/common/src/abstractions/vaultTimeout.service.ts @@ -1,16 +1,11 @@ -import { EncString } from '../models/domain/encString'; - export abstract class VaultTimeoutService { - biometricLocked: boolean; - everBeenUnlocked: boolean; - pinProtectedKey: EncString; - isLocked: () => Promise; + isLocked: (userId?: string) => Promise; checkVaultTimeout: () => Promise; - lock: (allowSoftLock?: boolean) => Promise; - logOut: () => Promise; + lock: (allowSoftLock?: boolean, userId?: string) => Promise; + logOut: (userId?: string) => Promise; setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise; getVaultTimeout: () => Promise; isPinLockSet: () => Promise<[boolean, boolean]>; isBiometricLockSet: () => Promise; - clear: () => Promise; + clear: (userId?: string) => Promise; } diff --git a/common/src/enums/authenticationStatus.ts b/common/src/enums/authenticationStatus.ts new file mode 100644 index 0000000000..83058b2131 --- /dev/null +++ b/common/src/enums/authenticationStatus.ts @@ -0,0 +1,6 @@ +export enum AuthenticationStatus { + Locked = 'locked', + Unlocked = 'unlocked', + LoggedOut = 'loggedOut', + Active = 'active', +} diff --git a/common/src/enums/htmlStorageLocation.ts b/common/src/enums/htmlStorageLocation.ts new file mode 100644 index 0000000000..59560c36a9 --- /dev/null +++ b/common/src/enums/htmlStorageLocation.ts @@ -0,0 +1,5 @@ +export enum HtmlStorageLocation { + Local = 'local', + Memory = 'memory', + Session = 'session', +} diff --git a/common/src/enums/keySuffixOptions.ts b/common/src/enums/keySuffixOptions.ts new file mode 100644 index 0000000000..40d3e58c60 --- /dev/null +++ b/common/src/enums/keySuffixOptions.ts @@ -0,0 +1,4 @@ +export enum KeySuffixOptions { + Auto = 'auto', + Biometric = 'biometric', +} diff --git a/common/src/enums/storageLocation.ts b/common/src/enums/storageLocation.ts new file mode 100644 index 0000000000..d45e5fa695 --- /dev/null +++ b/common/src/enums/storageLocation.ts @@ -0,0 +1,5 @@ +export enum StorageLocation { + Both = 'both', + Disk = 'disk', + Memory = 'memory', +} diff --git a/common/src/models/domain/account.ts b/common/src/models/domain/account.ts new file mode 100644 index 0000000000..eb53d662b7 --- /dev/null +++ b/common/src/models/domain/account.ts @@ -0,0 +1,168 @@ +import { OrganizationData } from '../data/organizationData'; + +import { AuthenticationStatus } from '../../enums/authenticationStatus'; +import { KdfType } from '../../enums/kdfType'; +import { UriMatchType } from '../../enums/uriMatchType'; + +import { CipherView } from '../view/cipherView'; +import { CollectionView } from '../view/collectionView'; +import { FolderView } from '../view/folderView'; +import { SendView } from '../view/sendView'; + +import { EncString } from './encString'; +import { GeneratedPasswordHistory } from './generatedPasswordHistory'; +import { Policy } from './policy'; +import { SymmetricCryptoKey } from './symmetricCryptoKey'; + +import { CipherData } from '../data/cipherData'; +import { CollectionData } from '../data/collectionData'; +import { EventData } from '../data/eventData'; +import { FolderData } from '../data/folderData'; +import { PolicyData } from '../data/policyData'; +import { ProviderData } from '../data/providerData'; +import { SendData } from '../data/sendData'; + +export class EncryptionPair { + encrypted?: TEncrypted; + decrypted?: TDecrypted; +} + +export class DataEncryptionPair { + encrypted?: { [id: string]: TEncrypted }; + decrypted?: TDecrypted[]; +} + +export class AccountData { + ciphers?: DataEncryptionPair = new DataEncryptionPair(); + folders?: DataEncryptionPair = new DataEncryptionPair(); + localData?: any; + sends?: DataEncryptionPair = new DataEncryptionPair(); + collections?: DataEncryptionPair = new DataEncryptionPair(); + policies?: DataEncryptionPair = new DataEncryptionPair(); + passwordGenerationHistory?: EncryptionPair = new EncryptionPair(); + addEditCipherInfo?: any; + collapsedGroupings?: Set; + eventCollection?: EventData[]; + organizations?: { [id: string]: OrganizationData }; + providers?: { [id: string]: ProviderData }; +} + +export class AccountKeys { + cryptoMasterKey?: SymmetricCryptoKey; + cryptoMasterKeyAuto?: string; + cryptoMasterKeyB64?: string; + cryptoMasterKeyBiometric?: string; + cryptoSymmetricKey?: EncryptionPair = new EncryptionPair(); + organizationKeys?: EncryptionPair> = new EncryptionPair>(); + providerKeys?: EncryptionPair> = new EncryptionPair>(); + privateKey?: EncryptionPair = new EncryptionPair(); + legacyEtmKey?: SymmetricCryptoKey; + publicKey?: ArrayBuffer; + apiKeyClientSecret?: string; +} + +export class AccountProfile { + apiKeyClientId?: string; + authenticationStatus?: AuthenticationStatus; + convertAccountToKeyConnector?: boolean; + email?: string; + emailVerified?: boolean; + entityId?: string; + entityType?: string; + everBeenUnlocked?: boolean; + forcePasswordReset?: boolean; + hasPremiumPersonally?: boolean; + lastActive?: number; + lastSync?: string; + ssoCodeVerifier?: string; + ssoOrganizationIdentifier?: string; + ssoState?: string; + userId?: string; + usesKeyConnector?: boolean; + keyHash?: string; + kdfIterations?: number; + kdfType?: KdfType; +} + +export class AccountSettings { + alwaysShowDock?: boolean; + autoConfirmFingerPrints?: boolean; + autoFillOnPageLoadDefault?: boolean; + biometricLocked?: boolean; + biometricUnlock?: boolean; + clearClipboard?: number; + defaultUriMatch?: UriMatchType; + disableAddLoginNotification?: boolean; + disableAutoBiometricsPrompt?: boolean; + disableAutoTotpCopy?: boolean; + disableBadgeCounter?: boolean; + disableChangedPasswordNotification?: boolean; + disableContextMenuItem?: boolean; + disableGa?: boolean; + dontShowCardsCurrentTab?: boolean; + dontShowIdentitiesCurrentTab?: boolean; + enableAlwaysOnTop?: boolean; + enableAutoFillOnPageLoad?: boolean; + enableBiometric?: boolean; + enableBrowserIntegration?: boolean; + enableBrowserIntegrationFingerprint?: boolean; + enableCloseToTray?: boolean; + enableFullWidth?: boolean; + enableGravitars?: boolean; + enableMinimizeToTray?: boolean; + enableStartToTray?: boolean; + enableTray?: boolean; + environmentUrls?: any = { + server: 'bitwarden.com', + }; + equivalentDomains?: any; + minimizeOnCopyToClipboard?: boolean; + neverDomains?: { [id: string]: any }; + openAtLogin?: boolean; + passwordGenerationOptions?: any; + pinProtected?: EncryptionPair = new EncryptionPair(); + protectedPin?: string; + settings?: any; // TODO: Merge whatever is going on here into the AccountSettings model properly + vaultTimeout?: number; + vaultTimeoutAction?: string; +} + +export class AccountTokens { + accessToken?: string; + decodedToken?: any; + refreshToken?: string; + securityStamp?: string; +} + +export class Account { + data?: AccountData = new AccountData(); + keys?: AccountKeys = new AccountKeys(); + profile?: AccountProfile = new AccountProfile(); + settings?: AccountSettings = new AccountSettings(); + tokens?: AccountTokens = new AccountTokens(); + + constructor(init: Partial) { + Object.assign(this, { + data: { + ...new AccountData(), + ...init?.data, + }, + keys: { + ...new AccountKeys(), + ...init?.keys, + }, + profile: { + ...new AccountProfile(), + ...init?.profile, + }, + settings: { + ...new AccountSettings(), + ...init?.settings, + }, + tokens: { + ...new AccountTokens(), + ...init?.tokens, + }, + }); + } +} diff --git a/common/src/models/domain/globalState.ts b/common/src/models/domain/globalState.ts new file mode 100644 index 0000000000..f0fa78ced5 --- /dev/null +++ b/common/src/models/domain/globalState.ts @@ -0,0 +1,24 @@ +export class GlobalState { + enableAlwaysOnTop?: boolean; + installedVersion?: string; + lastActive?: number; + locale?: string; + openAtLogin?: boolean; + organizationInvitation?: any; + rememberedEmail?: string; + theme?: string; + window?: Map = new Map(); + twoFactorToken?: string; + disableFavicon?: boolean; + biometricAwaitingAcceptance?: boolean; + biometricFingerprintValidated?: boolean; + vaultTimeout?: number; + vaultTimeoutAction?: string; + loginRedirect?: any; + mainWindowSize?: number; + enableBiometrics?: boolean; + biometricText?: string; + noAutoPromptBiometrics?: boolean; + noAutoPromptBiometricsText?: string; + stateVersion: number; +} diff --git a/common/src/models/domain/state.ts b/common/src/models/domain/state.ts new file mode 100644 index 0000000000..a0099e5d24 --- /dev/null +++ b/common/src/models/domain/state.ts @@ -0,0 +1,9 @@ +import { Account } from './account'; +import { GlobalState } from './globalState'; + +export class State { + accounts: { [userId: string]: Account } = {}; + globals: GlobalState = new GlobalState(); + activeUserId: string; +} + diff --git a/common/src/models/domain/storageOptions.ts b/common/src/models/domain/storageOptions.ts new file mode 100644 index 0000000000..d9924795d6 --- /dev/null +++ b/common/src/models/domain/storageOptions.ts @@ -0,0 +1,10 @@ +import { HtmlStorageLocation } from '../../enums/htmlStorageLocation'; +import { StorageLocation } from '../../enums/storageLocation'; + +export type StorageOptions = { + storageLocation?: StorageLocation; + useSecureStorage?: boolean; + userId?: string; + htmlStorageLocation?: HtmlStorageLocation; + keySuffix?: string, +}; diff --git a/common/src/services/api.service.ts b/common/src/services/api.service.ts index 46fdc1391e..f42a760e0b 100644 --- a/common/src/services/api.service.ts +++ b/common/src/services/api.service.ts @@ -1515,7 +1515,7 @@ export class ApiService implements ApiServiceAbstraction { async getActiveBearerToken(): Promise { let accessToken = await this.tokenService.getToken(); - if (this.tokenService.tokenNeedsRefresh()) { + if (await this.tokenService.tokenNeedsRefresh()) { await this.doAuthRefresh(); accessToken = await this.tokenService.getToken(); } @@ -1637,7 +1637,7 @@ export class ApiService implements ApiServiceAbstraction { headers.set('User-Agent', this.customUserAgent); } - const decodedToken = this.tokenService.decodeToken(); + const decodedToken = await this.tokenService.decodeToken(); const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + '/connect/token', { body: this.qsStringify({ grant_type: 'refresh_token', diff --git a/common/src/services/apiKey.service.ts b/common/src/services/apiKey.service.ts deleted file mode 100644 index 0b67781b20..0000000000 --- a/common/src/services/apiKey.service.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { ApiKeyService as ApiKeyServiceAbstraction } from '../abstractions/apiKey.service'; -import { StorageService } from '../abstractions/storage.service'; -import { TokenService } from '../abstractions/token.service'; - -import { Utils } from '../misc/utils'; - -const Keys = { - clientId: 'clientId', - clientSecret: 'clientSecret', - entityType: 'entityType', - entityId: 'entityId', -}; - - -export class ApiKeyService implements ApiKeyServiceAbstraction { - private clientId: string; - private clientSecret: string; - private entityType: string; - private entityId: string; - - constructor(private tokenService: TokenService, private storageService: StorageService) { } - - async setInformation(clientId: string, clientSecret: string) { - this.clientId = clientId; - this.clientSecret = clientSecret; - const idParts = clientId.split('.'); - - if (idParts.length !== 2 || !Utils.isGuid(idParts[1])) { - throw Error('Invalid clientId'); - } - this.entityType = idParts[0]; - this.entityId = idParts[1]; - - await this.storageService.save(Keys.clientId, this.clientId); - await this.storageService.save(Keys.entityId, this.entityId); - await this.storageService.save(Keys.entityType, this.entityType); - await this.storageService.save(Keys.clientSecret, this.clientSecret); - } - - async getClientId(): Promise { - if (this.clientId == null) { - this.clientId = await this.storageService.get(Keys.clientId); - } - return this.clientId; - } - - async getClientSecret(): Promise { - if (this.clientSecret == null) { - this.clientSecret = await this.storageService.get(Keys.clientSecret); - } - return this.clientSecret; - } - - async getEntityType(): Promise { - if (this.entityType == null) { - this.entityType = await this.storageService.get(Keys.entityType); - } - return this.entityType; - } - - async getEntityId(): Promise { - if (this.entityId == null) { - this.entityId = await this.storageService.get(Keys.entityId); - } - return this.entityId; - } - - async clear(): Promise { - await this.storageService.remove(Keys.clientId); - await this.storageService.remove(Keys.clientSecret); - await this.storageService.remove(Keys.entityId); - await this.storageService.remove(Keys.entityType); - - this.clientId = this.clientSecret = this.entityId = this.entityType = null; - } - - async isAuthenticated(): Promise { - const token = await this.tokenService.getToken(); - if (token == null) { - return false; - } - - const entityId = await this.getEntityId(); - return entityId != null; - } -} diff --git a/common/src/services/auth.service.ts b/common/src/services/auth.service.ts index e4f670d7b7..540d7e5c28 100644 --- a/common/src/services/auth.service.ts +++ b/common/src/services/auth.service.ts @@ -2,6 +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 { AuthResult } from '../models/domain/authResult'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; @@ -26,8 +27,8 @@ 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 { UserService } from '../abstractions/user.service'; import { VaultTimeoutService } from '../abstractions/vaultTimeout.service'; import { Utils } from '../misc/utils'; @@ -99,12 +100,12 @@ export class AuthService implements AuthServiceAbstraction { private key: SymmetricCryptoKey; constructor(private cryptoService: CryptoService, protected apiService: ApiService, - private userService: UserService, protected tokenService: TokenService, - protected appIdService: AppIdService, private i18nService: I18nService, - protected platformUtilsService: PlatformUtilsService, private messagingService: MessagingService, - private vaultTimeoutService: VaultTimeoutService, private logService: LogService, - private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService, - private keyConnectorService: KeyConnectorService, private setCryptoKeys = true) { + protected tokenService: TokenService, protected appIdService: AppIdService, + private i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, + private messagingService: MessagingService, private vaultTimeoutService: VaultTimeoutService, + private logService: LogService, protected cryptoFunctionService: CryptoFunctionService, + private keyConnectorService: KeyConnectorService, protected environmentService: EnvironmentService, + protected stateService: StateService, private setCryptoKeys = true) { } init() { @@ -234,7 +235,7 @@ export class AuthService implements AuthServiceAbstraction { let providerType: TwoFactorProviderType = null; let providerPriority = -1; - this.twoFactorProvidersData.forEach((value, type) => { + this.twoFactorProvidersData.forEach((_value, type) => { const provider = (TwoFactorProviders as any)[type]; if (provider != null && provider.priority > providerPriority) { if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { @@ -350,13 +351,34 @@ export class AuthService implements AuthServiceAbstraction { const tokenResponse = response as IdentityTokenResponse; result.resetMasterPassword = tokenResponse.resetMasterPassword; result.forcePasswordReset = tokenResponse.forcePasswordReset; + + const accountInformation = await this.tokenService.decodeToken(tokenResponse.accessToken); + await this.stateService.addAccount({ + profile: { + ...new AccountProfile(), + ...{ + userId: accountInformation.sub, + email: accountInformation.email, + apiKeyClientId: clientId, + apiKeyClientSecret: clientSecret, + hasPremiumPersonally: accountInformation.premium, + kdfIterations: tokenResponse.kdfIterations, + kdfType: tokenResponse.kdf, + }, + }, + tokens: { + ...new AccountTokens(), + ...{ + accessToken: tokenResponse.accessToken, + refreshToken: tokenResponse.refreshToken, + }, + }, + }); + if (tokenResponse.twoFactorToken != null) { await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken, email); } - await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken, clientIdClientSecret); - await this.userService.setInformation(this.tokenService.getUserId(), this.tokenService.getEmail(), - tokenResponse.kdf, tokenResponse.kdfIterations); if (this.setCryptoKeys) { if (key != null) { await this.cryptoService.setKey(key); @@ -392,7 +414,7 @@ export class AuthService implements AuthServiceAbstraction { } else if (tokenResponse.keyConnectorUrl != null) { const password = await this.cryptoFunctionService.randomBytes(64); - const k = await this.cryptoService.makeKey(Utils.fromBufferToB64(password), this.tokenService.getEmail(), tokenResponse.kdf, tokenResponse.kdfIterations); + const k = await this.cryptoService.makeKey(Utils.fromBufferToB64(password), await this.tokenService.getEmail(), tokenResponse.kdf, tokenResponse.kdfIterations); const keyConnectorRequest = new KeyConnectorUserKeyRequest(k.encKeyB64); await this.cryptoService.setKey(k); @@ -416,7 +438,7 @@ export class AuthService implements AuthServiceAbstraction { } if (this.vaultTimeoutService != null) { - this.vaultTimeoutService.biometricLocked = false; + await this.stateService.setBiometricLocked(false); } this.messagingService.send('loggedIn'); return result; diff --git a/common/src/services/cipher.service.ts b/common/src/services/cipher.service.ts index 421f381ac6..9ee245dd31 100644 --- a/common/src/services/cipher.service.ts +++ b/common/src/services/cipher.service.ts @@ -46,43 +46,32 @@ import { FileUploadService } from '../abstractions/fileUpload.service'; import { I18nService } from '../abstractions/i18n.service'; import { SearchService } from '../abstractions/search.service'; import { SettingsService } from '../abstractions/settings.service'; -import { StorageService } from '../abstractions/storage.service'; -import { UserService } from '../abstractions/user.service'; - -import { ConstantsService } from './constants.service'; +import { StateService } from '../abstractions/state.service'; import { LogService } from '../abstractions/log.service'; import { sequentialize } from '../misc/sequentialize'; import { Utils } from '../misc/utils'; -const Keys = { - ciphersPrefix: 'ciphers_', - localData: 'sitesLocalData', - neverDomains: 'neverDomains', -}; - const DomainMatchBlacklist = new Map>([ ['google.com', new Set(['script.google.com'])], ]); export class CipherService implements CipherServiceAbstraction { - // tslint:disable-next-line - _decryptedCipherCache: CipherView[]; - private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache(this.sortCiphersByLastUsed); - constructor(private cryptoService: CryptoService, private userService: UserService, - private settingsService: SettingsService, private apiService: ApiService, - private fileUploadService: FileUploadService, private storageService: StorageService, + constructor(private cryptoService: CryptoService, private settingsService: SettingsService, + private apiService: ApiService, private fileUploadService: FileUploadService, private i18nService: I18nService, private searchService: () => SearchService, - private logService: LogService) { + private logService: LogService, private stateService: StateService) { } - get decryptedCipherCache() { - return this._decryptedCipherCache; + async getDecryptedCipherCache(): Promise { + const decryptedCiphers = await this.stateService.getDecryptedCiphers(); + return decryptedCiphers; } - set decryptedCipherCache(value: CipherView[]) { - this._decryptedCipherCache = value; + + async setDecryptedCipherCache(value: CipherView[]) { + await this.stateService.setDecryptedCiphers(value); if (this.searchService != null) { if (value == null) { this.searchService().clearIndex(); @@ -92,9 +81,8 @@ export class CipherService implements CipherServiceAbstraction { } } - clearCache(): void { - this.decryptedCipherCache = null; - this.sortedCiphersCache.clear(); + async clearCache(userId?: string): Promise { + await this.clearDecryptedCiphersState(userId); } async encrypt(model: CipherView, key?: SymmetricCryptoKey, originalCipher: Cipher = null): Promise { @@ -212,12 +200,10 @@ export class CipherService implements CipherServiceAbstraction { const self = this; const encFields: Field[] = []; - await fieldsModel.reduce((promise, field) => { - return promise.then(() => { - return self.encryptField(field, key); - }).then((encField: Field) => { - encFields.push(encField); - }); + await fieldsModel.reduce(async (promise, field) => { + await promise; + const encField = await self.encryptField(field, key); + encFields.push(encField); }, Promise.resolve()); return encFields; @@ -247,12 +233,10 @@ export class CipherService implements CipherServiceAbstraction { const self = this; const encPhs: Password[] = []; - await phModels.reduce((promise, ph) => { - return promise.then(() => { - return self.encryptPasswordHistory(ph, key); - }).then((encPh: Password) => { - encPhs.push(encPh); - }); + await phModels.reduce(async (promise, ph) => { + await promise; + const encPh = await self.encryptPasswordHistory(ph, key); + encPhs.push(encPh); }, Promise.resolve()); return encPhs; @@ -270,22 +254,18 @@ export class CipherService implements CipherServiceAbstraction { } async get(id: string): Promise { - const userId = await this.userService.getUserId(); - const localData = await this.storageService.get(Keys.localData); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); + const ciphers = await this.stateService.getEncryptedCiphers(); if (ciphers == null || !ciphers.hasOwnProperty(id)) { return null; } + const localData = await this.stateService.getLocalData(); return new Cipher(ciphers[id], false, localData ? localData[id] : null); } async getAll(): Promise { - const userId = await this.userService.getUserId(); - const localData = await this.storageService.get(Keys.localData); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); + const localData = await this.stateService.getLocalData(); + const ciphers = await this.stateService.getEncryptedCiphers(); const response: Cipher[] = []; for (const id in ciphers) { if (ciphers.hasOwnProperty(id)) { @@ -297,13 +277,13 @@ export class CipherService implements CipherServiceAbstraction { @sequentialize(() => 'getAllDecrypted') async getAllDecrypted(): Promise { - if (this.decryptedCipherCache != null) { - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); + if (await this.getDecryptedCipherCache() != null) { if (this.searchService != null && (this.searchService().indexedEntityId ?? userId) !== userId) { - await this.searchService().indexCiphers(userId, this.decryptedCipherCache); + await this.searchService().indexCiphers(userId, await this.getDecryptedCipherCache()); } - return this.decryptedCipherCache; + return await this.getDecryptedCipherCache(); } const decCiphers: CipherView[] = []; @@ -314,14 +294,14 @@ export class CipherService implements CipherServiceAbstraction { const promises: any[] = []; const ciphers = await this.getAll(); - ciphers.forEach(cipher => { + ciphers.forEach(async cipher => { promises.push(cipher.decrypt().then(c => decCiphers.push(c))); }); await Promise.all(promises); decCiphers.sort(this.getLocaleSortingFunction()); - this.decryptedCipherCache = decCiphers; - return this.decryptedCipherCache; + await this.stateService.setDecryptedCiphers(decCiphers); + return decCiphers; } async getAllDecryptedForGrouping(groupingId: string, folder: boolean = true): Promise { @@ -369,7 +349,7 @@ export class CipherService implements CipherServiceAbstraction { const ciphers = result[1]; if (defaultMatch == null) { - defaultMatch = await this.storageService.get(ConstantsService.defaultUriMatch); + defaultMatch = await this.stateService.getDefaultUriMatch(); if (defaultMatch == null) { defaultMatch = UriMatchType.Domain; } @@ -476,7 +456,7 @@ export class CipherService implements CipherServiceAbstraction { } async updateLastUsedDate(id: string): Promise { - let ciphersLocalData = await this.storageService.get(Keys.localData); + let ciphersLocalData = await this.stateService.getLocalData(); if (!ciphersLocalData) { ciphersLocalData = {}; } @@ -489,23 +469,25 @@ export class CipherService implements CipherServiceAbstraction { }; } - await this.storageService.save(Keys.localData, ciphersLocalData); + await this.stateService.setLocalData(ciphersLocalData); - if (this.decryptedCipherCache == null) { + const decryptedCipherCache = await this.stateService.getDecryptedCiphers(); + if (!decryptedCipherCache) { return; } - for (let i = 0; i < this.decryptedCipherCache.length; i++) { - const cached = this.decryptedCipherCache[i]; + for (let i = 0; i < decryptedCipherCache.length; i++) { + const cached = decryptedCipherCache[i]; if (cached.id === id) { cached.localData = ciphersLocalData[id]; break; } } + await this.stateService.setDecryptedCiphers(decryptedCipherCache); } async updateLastLaunchedDate(id: string): Promise { - let ciphersLocalData = await this.storageService.get(Keys.localData); + let ciphersLocalData = await this.stateService.getLocalData(); if (!ciphersLocalData) { ciphersLocalData = {}; } @@ -518,19 +500,21 @@ export class CipherService implements CipherServiceAbstraction { }; } - await this.storageService.save(Keys.localData, ciphersLocalData); + await this.stateService.setLocalData(ciphersLocalData); - if (this.decryptedCipherCache == null) { + const decryptedCipherCache = await this.stateService.getDecryptedCiphers(); + if (!decryptedCipherCache) { return; } - for (let i = 0; i < this.decryptedCipherCache.length; i++) { - const cached = this.decryptedCipherCache[i]; + for (let i = 0; i < decryptedCipherCache.length; i++) { + const cached = decryptedCipherCache[i]; if (cached.id === id) { cached.localData = ciphersLocalData[id]; break; } } + await this.stateService.setDecryptedCiphers(decryptedCipherCache); } async saveNeverDomain(domain: string): Promise { @@ -538,12 +522,12 @@ export class CipherService implements CipherServiceAbstraction { return; } - let domains = await this.storageService.get<{ [id: string]: any; }>(Keys.neverDomains); + let domains = await this.stateService.getNeverDomains(); if (!domains) { domains = {}; } domains[domain] = null; - await this.storageService.save(Keys.neverDomains, domains); + await this.stateService.setNeverDomains(domains); } async saveWithServer(cipher: Cipher): Promise { @@ -562,8 +546,7 @@ export class CipherService implements CipherServiceAbstraction { response = await this.apiService.putCipher(cipher.id, request); } - const userId = await this.userService.getUserId(); - const data = new CipherData(response, userId, cipher.collectionIds); + const data = new CipherData(response, await this.stateService.getUserId(), cipher.collectionIds); await this.upsert(data); } @@ -583,8 +566,7 @@ export class CipherService implements CipherServiceAbstraction { const encCipher = await this.encrypt(cipher); const request = new CipherShareRequest(encCipher); const response = await this.apiService.putShareCipher(cipher.id, request); - const userId = await this.userService.getUserId(); - const data = new CipherData(response, userId, collectionIds); + const data = new CipherData(response, await this.stateService.getUserId(), collectionIds); await this.upsert(data); } @@ -601,7 +583,7 @@ export class CipherService implements CipherServiceAbstraction { await Promise.all(promises); const request = new CipherBulkShareRequest(encCiphers, collectionIds); await this.apiService.putShareCiphers(request); - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); await this.upsert(encCiphers.map(c => c.toCipherData(userId))); } @@ -618,7 +600,7 @@ export class CipherService implements CipherServiceAbstraction { reject(e); } }; - reader.onerror = evt => { + reader.onerror = _evt => { reject('Error reading file.'); }; }); @@ -654,8 +636,7 @@ export class CipherService implements CipherServiceAbstraction { } } - const userId = await this.userService.getUserId(); - const cData = new CipherData(response, userId, cipher.collectionIds); + const cData = new CipherData(response, await this.stateService.getUserId(), cipher.collectionIds); if (!admin) { await this.upsert(cData); } @@ -702,15 +683,12 @@ export class CipherService implements CipherServiceAbstraction { async saveCollectionsWithServer(cipher: Cipher): Promise { const request = new CipherCollectionsRequest(cipher.collectionIds); await this.apiService.putCipherCollections(cipher.id, request); - const userId = await this.userService.getUserId(); - const data = cipher.toCipherData(userId); + const data = cipher.toCipherData(await this.stateService.getUserId()); await this.upsert(data); } async upsert(cipher: CipherData | CipherData[]): Promise { - const userId = await this.userService.getUserId(); - let ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); + let ciphers = await this.stateService.getEncryptedCiphers(); if (ciphers == null) { ciphers = {}; } @@ -724,27 +702,23 @@ export class CipherService implements CipherServiceAbstraction { }); } - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; + await this.replace(ciphers); } async replace(ciphers: { [id: string]: CipherData; }): Promise { - const userId = await this.userService.getUserId(); - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; + await this.clearDecryptedCiphersState(); + await this.stateService.setEncryptedCiphers(ciphers); } - async clear(userId: string): Promise { - await this.storageService.remove(Keys.ciphersPrefix + userId); - this.clearCache(); + async clear(userId?: string): Promise { + await this.clearEncryptedCiphersState(userId); + await this.clearCache(userId); } async moveManyWithServer(ids: string[], folderId: string): Promise { await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId)); - const userId = await this.userService.getUserId(); - let ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); + let ciphers = await this.stateService.getEncryptedCiphers(); if (ciphers == null) { ciphers = {}; } @@ -755,14 +729,12 @@ export class CipherService implements CipherServiceAbstraction { } }); - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; + await this.clearCache(); + await this.stateService.setEncryptedCiphers(ciphers); } async delete(id: string | string[]): Promise { - const userId = await this.userService.getUserId(); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); + const ciphers = await this.stateService.getEncryptedCiphers(); if (ciphers == null) { return; } @@ -778,8 +750,8 @@ export class CipherService implements CipherServiceAbstraction { }); } - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; + await this.clearCache(); + await this.stateService.setEncryptedCiphers(ciphers); } async deleteWithServer(id: string): Promise { @@ -793,9 +765,7 @@ export class CipherService implements CipherServiceAbstraction { } async deleteAttachment(id: string, attachmentId: string): Promise { - const userId = await this.userService.getUserId(); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); + const ciphers = await this.stateService.getEncryptedCiphers(); if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[id].attachments == null) { return; @@ -807,8 +777,8 @@ export class CipherService implements CipherServiceAbstraction { } } - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; + await this.clearCache(); + await this.stateService.setEncryptedCiphers(ciphers); } async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { @@ -887,9 +857,7 @@ export class CipherService implements CipherServiceAbstraction { } async softDelete(id: string | string[]): Promise { - const userId = await this.userService.getUserId(); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); + const ciphers = await this.stateService.getEncryptedCiphers(); if (ciphers == null) { return; } @@ -907,8 +875,8 @@ export class CipherService implements CipherServiceAbstraction { (id as string[]).forEach(setDeletedDate); } - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; + await this.clearCache(); + await this.stateService.setEncryptedCiphers(ciphers); } async softDeleteWithServer(id: string): Promise { @@ -922,9 +890,7 @@ export class CipherService implements CipherServiceAbstraction { } async restore(cipher: { id: string, revisionDate: string; } | { id: string, revisionDate: string; }[]) { - const userId = await this.userService.getUserId(); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); + const ciphers = await this.stateService.getEncryptedCiphers(); if (ciphers == null) { return; } @@ -944,8 +910,8 @@ export class CipherService implements CipherServiceAbstraction { clearDeletedDate(cipher as { id: string, revisionDate: string; }); } - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; + await this.clearCache(); + await this.stateService.setEncryptedCiphers(ciphers); } async restoreWithServer(id: string): Promise { @@ -1109,7 +1075,7 @@ export class CipherService implements CipherServiceAbstraction { } if (autofillOnPageLoad) { - const autofillOnPageLoadDefault = await this.storageService.get(ConstantsService.autoFillOnPageLoadDefaultKey); + const autofillOnPageLoadDefault = await this.stateService.getAutoFillOnPageLoadDefault(); ciphers = ciphers.filter(cipher => cipher.login.autofillOnPageLoad || (cipher.login.autofillOnPageLoad == null && autofillOnPageLoadDefault !== false)); if (ciphers.length === 0) { @@ -1128,4 +1094,17 @@ export class CipherService implements CipherServiceAbstraction { return this.sortedCiphersCache.getNext(cacheKey); } } + + private async clearEncryptedCiphersState(userId?: string) { + await this.stateService.setEncryptedCiphers(null, { userId: userId }); + } + + private async clearDecryptedCiphersState(userId?: string) { + await this.stateService.setDecryptedCiphers(null, { userId: userId }); + this.clearSortedCiphers(); + } + + private clearSortedCiphers() { + this.sortedCiphersCache.clear(); + } } diff --git a/common/src/services/collection.service.ts b/common/src/services/collection.service.ts index 3c594080f8..3affccd66e 100644 --- a/common/src/services/collection.service.ts +++ b/common/src/services/collection.service.ts @@ -8,26 +8,20 @@ import { CollectionView } from '../models/view/collectionView'; import { CollectionService as CollectionServiceAbstraction } from '../abstractions/collection.service'; import { CryptoService } from '../abstractions/crypto.service'; import { I18nService } from '../abstractions/i18n.service'; -import { StorageService } from '../abstractions/storage.service'; -import { UserService } from '../abstractions/user.service'; +import { StateService } from '../abstractions/state.service'; import { ServiceUtils } from '../misc/serviceUtils'; import { Utils } from '../misc/utils'; -const Keys = { - collectionsPrefix: 'collections_', -}; const NestingDelimiter = '/'; export class CollectionService implements CollectionServiceAbstraction { - decryptedCollectionCache: CollectionView[]; - - constructor(private cryptoService: CryptoService, private userService: UserService, - private storageService: StorageService, private i18nService: I18nService) { + constructor(private cryptoService: CryptoService, private i18nService: I18nService, + private stateService: StateService) { } - clearCache(): void { - this.decryptedCollectionCache = null; + async clearCache(userId?: string): Promise { + await this.stateService.setDecryptedCollections(null, { userId: userId }); } async encrypt(model: CollectionView): Promise { @@ -60,9 +54,7 @@ export class CollectionService implements CollectionServiceAbstraction { } async get(id: string): Promise { - const userId = await this.userService.getUserId(); - const collections = await this.storageService.get<{ [id: string]: CollectionData; }>( - Keys.collectionsPrefix + userId); + const collections = await this.stateService.getEncryptedCollections(); if (collections == null || !collections.hasOwnProperty(id)) { return null; } @@ -71,9 +63,7 @@ export class CollectionService implements CollectionServiceAbstraction { } async getAll(): Promise { - const userId = await this.userService.getUserId(); - const collections = await this.storageService.get<{ [id: string]: CollectionData; }>( - Keys.collectionsPrefix + userId); + const collections = await this.stateService.getEncryptedCollections(); const response: Collection[] = []; for (const id in collections) { if (collections.hasOwnProperty(id)) { @@ -84,8 +74,9 @@ export class CollectionService implements CollectionServiceAbstraction { } async getAllDecrypted(): Promise { - if (this.decryptedCollectionCache != null) { - return this.decryptedCollectionCache; + let decryptedCollections = await this.stateService.getDecryptedCollections(); + if (decryptedCollections != null) { + return decryptedCollections; } const hasKey = await this.cryptoService.hasKey(); @@ -94,8 +85,9 @@ export class CollectionService implements CollectionServiceAbstraction { } const collections = await this.getAll(); - this.decryptedCollectionCache = await this.decryptMany(collections); - return this.decryptedCollectionCache; + decryptedCollections = await this.decryptMany(collections); + await this.stateService.setDecryptedCollections(decryptedCollections); + return decryptedCollections; } async getAllNested(collections: CollectionView[] = null): Promise[]> { @@ -119,9 +111,7 @@ export class CollectionService implements CollectionServiceAbstraction { } async upsert(collection: CollectionData | CollectionData[]): Promise { - const userId = await this.userService.getUserId(); - let collections = await this.storageService.get<{ [id: string]: CollectionData; }>( - Keys.collectionsPrefix + userId); + let collections = await this.stateService.getEncryptedCollections(); if (collections == null) { collections = {}; } @@ -135,31 +125,26 @@ export class CollectionService implements CollectionServiceAbstraction { }); } - await this.storageService.save(Keys.collectionsPrefix + userId, collections); - this.decryptedCollectionCache = null; + await this.replace(collections); } async replace(collections: { [id: string]: CollectionData; }): Promise { - const userId = await this.userService.getUserId(); - await this.storageService.save(Keys.collectionsPrefix + userId, collections); - this.decryptedCollectionCache = null; + await this.clearCache(); + await this.stateService.setEncryptedCollections(collections); } - async clear(userId: string): Promise { - await this.storageService.remove(Keys.collectionsPrefix + userId); - this.decryptedCollectionCache = null; + async clear(userId?: string): Promise { + await this.clearCache(userId); + await this.stateService.setEncryptedCollections(null, { userId: userId }); } async delete(id: string | string[]): Promise { - const userId = await this.userService.getUserId(); - const collections = await this.storageService.get<{ [id: string]: CollectionData; }>( - Keys.collectionsPrefix + userId); + const collections = await this.stateService.getEncryptedCollections(); if (collections == null) { return; } if (typeof id === 'string') { - const i = id as string; delete collections[id]; } else { (id as string[]).forEach(i => { @@ -167,7 +152,6 @@ export class CollectionService implements CollectionServiceAbstraction { }); } - await this.storageService.save(Keys.collectionsPrefix + userId, collections); - this.decryptedCollectionCache = null; + await this.replace(collections); } } diff --git a/common/src/services/constants.service.ts b/common/src/services/constants.service.ts deleted file mode 100644 index 9202f8620b..0000000000 --- a/common/src/services/constants.service.ts +++ /dev/null @@ -1,68 +0,0 @@ -export class ConstantsService { - static readonly environmentUrlsKey: string = 'environmentUrls'; - static readonly disableGaKey: string = 'disableGa'; - static readonly disableAddLoginNotificationKey: string = 'disableAddLoginNotification'; - static readonly disableChangedPasswordNotificationKey: string = 'disableChangedPasswordNotification'; - static readonly disableContextMenuItemKey: string = 'disableContextMenuItem'; - static readonly disableFaviconKey: string = 'disableFavicon'; - static readonly disableBadgeCounterKey: string = 'disableBadgeCounter'; - static readonly disableAutoTotpCopyKey: string = 'disableAutoTotpCopy'; - static readonly disableAutoBiometricsPromptKey: string = 'noAutoPromptBiometrics'; - static readonly enableAutoFillOnPageLoadKey: string = 'enableAutoFillOnPageLoad'; - static readonly autoFillOnPageLoadDefaultKey: string = 'autoFillOnPageLoadDefault'; - static readonly vaultTimeoutKey: string = 'lockOption'; - static readonly vaultTimeoutActionKey: string = 'vaultTimeoutAction'; - static readonly lastActiveKey: string = 'lastActive'; - static readonly neverDomainsKey: string = 'neverDomains'; - static readonly installedVersionKey: string = 'installedVersion'; - static readonly localeKey: string = 'locale'; - static readonly themeKey: string = 'theme'; - static readonly collapsedGroupingsKey: string = 'collapsedGroupings'; - static readonly autoConfirmFingerprints: string = 'autoConfirmFingerprints'; - static readonly dontShowCardsCurrentTab: string = 'dontShowCardsCurrentTab'; - static readonly dontShowIdentitiesCurrentTab: string = 'dontShowIdentitiesCurrentTab'; - static readonly defaultUriMatch: string = 'defaultUriMatch'; - static readonly pinProtectedKey: string = 'pinProtectedKey'; - static readonly protectedPin: string = 'protectedPin'; - static readonly clearClipboardKey: string = 'clearClipboardKey'; - static readonly eventCollectionKey: string = 'eventCollection'; - static readonly ssoCodeVerifierKey: string = 'ssoCodeVerifier'; - static readonly ssoStateKey: string = 'ssoState'; - static readonly biometricUnlockKey: string = 'biometric'; - static readonly biometricText: string = 'biometricText'; - static readonly biometricAwaitingAcceptance: string = 'biometricAwaitingAcceptance'; - static readonly biometricFingerprintValidated: string = 'biometricFingerprintValidated'; - - readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey; - readonly disableGaKey: string = ConstantsService.disableGaKey; - readonly disableAddLoginNotificationKey: string = ConstantsService.disableAddLoginNotificationKey; - readonly disableContextMenuItemKey: string = ConstantsService.disableContextMenuItemKey; - readonly disableFaviconKey: string = ConstantsService.disableFaviconKey; - readonly disableBadgeCounterKey: string = ConstantsService.disableBadgeCounterKey; - readonly disableAutoTotpCopyKey: string = ConstantsService.disableAutoTotpCopyKey; - readonly disableAutoBiometricsPromptKey: string = ConstantsService.disableAutoBiometricsPromptKey; - readonly enableAutoFillOnPageLoadKey: string = ConstantsService.enableAutoFillOnPageLoadKey; - readonly autoFillOnPageLoadDefaultKey: string = ConstantsService.autoFillOnPageLoadDefaultKey; - readonly vaultTimeoutKey: string = ConstantsService.vaultTimeoutKey; - readonly vaultTimeoutActionKey: string = ConstantsService.vaultTimeoutActionKey; - readonly lastActiveKey: string = ConstantsService.lastActiveKey; - readonly neverDomainsKey: string = ConstantsService.neverDomainsKey; - readonly installedVersionKey: string = ConstantsService.installedVersionKey; - readonly localeKey: string = ConstantsService.localeKey; - readonly themeKey: string = ConstantsService.themeKey; - readonly collapsedGroupingsKey: string = ConstantsService.collapsedGroupingsKey; - readonly autoConfirmFingerprints: string = ConstantsService.autoConfirmFingerprints; - readonly dontShowCardsCurrentTab: string = ConstantsService.dontShowCardsCurrentTab; - readonly dontShowIdentitiesCurrentTab: string = ConstantsService.dontShowIdentitiesCurrentTab; - readonly defaultUriMatch: string = ConstantsService.defaultUriMatch; - readonly pinProtectedKey: string = ConstantsService.pinProtectedKey; - readonly protectedPin: string = ConstantsService.protectedPin; - readonly clearClipboardKey: string = ConstantsService.clearClipboardKey; - readonly eventCollectionKey: string = ConstantsService.eventCollectionKey; - readonly ssoCodeVerifierKey: string = ConstantsService.ssoCodeVerifierKey; - readonly ssoStateKey: string = ConstantsService.ssoStateKey; - readonly biometricUnlockKey: string = ConstantsService.biometricUnlockKey; - readonly biometricText: string = ConstantsService.biometricText; - readonly biometricAwaitingAcceptance: string = ConstantsService.biometricAwaitingAcceptance; - readonly biometricFingerprintValidated: string = ConstantsService.biometricFingerprintValidated; -} diff --git a/common/src/services/crypto.service.ts b/common/src/services/crypto.service.ts index 60ef0ea055..25989d178f 100644 --- a/common/src/services/crypto.service.ts +++ b/common/src/services/crypto.service.ts @@ -3,84 +3,60 @@ import * as bigInt from 'big-integer'; import { EncryptionType } from '../enums/encryptionType'; import { HashPurpose } from '../enums/hashPurpose'; import { KdfType } from '../enums/kdfType'; +import { KeySuffixOptions } from '../enums/keySuffixOptions'; import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; import { EncryptedObject } from '../models/domain/encryptedObject'; import { EncString } from '../models/domain/encString'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse'; import { CryptoService as CryptoServiceAbstraction } from '../abstractions/crypto.service'; import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; import { LogService } from '../abstractions/log.service'; import { PlatformUtilsService } from '../abstractions/platformUtils.service'; -import { - KeySuffixOptions, - StorageService, -} from '../abstractions/storage.service'; - -import { ConstantsService } from './constants.service'; +import { StateService } from '../abstractions/state.service'; import { sequentialize } from '../misc/sequentialize'; import { Utils } from '../misc/utils'; import { EEFLongWordList } from '../misc/wordlist'; + +import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse'; import { ProfileProviderOrganizationResponse } from '../models/response/profileProviderOrganizationResponse'; import { ProfileProviderResponse } from '../models/response/profileProviderResponse'; -export const Keys = { - key: 'key', // Master Key - encOrgKeys: 'encOrgKeys', - encProviderKeys: 'encProviderKeys', - encPrivateKey: 'encPrivateKey', - encKey: 'encKey', // Generated Symmetric Key - keyHash: 'keyHash', -}; - export class CryptoService implements CryptoServiceAbstraction { - private key: SymmetricCryptoKey; - private encKey: SymmetricCryptoKey; - private legacyEtmKey: SymmetricCryptoKey; - private keyHash: string; - private publicKey: ArrayBuffer; - private privateKey: ArrayBuffer; - private orgKeys: Map; - private providerKeys: Map; - - constructor(private storageService: StorageService, protected secureStorageService: StorageService, - private cryptoFunctionService: CryptoFunctionService, protected platformUtilService: PlatformUtilsService, - protected logService: LogService) { + constructor(private cryptoFunctionService: CryptoFunctionService, protected platformUtilService: PlatformUtilsService, + protected logService: LogService, protected stateService: StateService) { } - async setKey(key: SymmetricCryptoKey): Promise { - this.key = key; - - await this.storeKey(key); + async setKey(key: SymmetricCryptoKey, userId?: string): Promise { + await this.stateService.setCryptoMasterKey(key, { userId: userId }); + await this.storeKey(key, userId); } - setKeyHash(keyHash: string): Promise<{}> { - this.keyHash = keyHash; - return this.storageService.save(Keys.keyHash, keyHash); + async setKeyHash(keyHash: string): Promise { + await this.stateService.setKeyHash(keyHash); } - async setEncKey(encKey: string): Promise<{}> { + async setEncKey(encKey: string): Promise { if (encKey == null) { return; } - await this.storageService.save(Keys.encKey, encKey); - this.encKey = null; + await this.stateService.setDecryptedCryptoSymmetricKey(null); + await this.stateService.setEncryptedCryptoSymmetricKey(encKey); } - async setEncPrivateKey(encPrivateKey: string): Promise<{}> { + async setEncPrivateKey(encPrivateKey: string): Promise { if (encPrivateKey == null) { return; } - await this.storageService.save(Keys.encPrivateKey, encPrivateKey); - this.privateKey = null; + await this.stateService.setDecryptedPrivateKey(null); + await this.stateService.setEncryptedPrivateKey(encPrivateKey); } - async setOrgKeys(orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]): Promise<{}> { + async setOrgKeys(orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]): Promise { const orgKeys: any = {}; orgs.forEach(org => { orgKeys[org.id] = org.key; @@ -90,47 +66,49 @@ export class CryptoService implements CryptoServiceAbstraction { // Convert provider encrypted keys to user encrypted. const providerKey = await this.getProviderKey(providerOrg.providerId); const decValue = await this.decryptToBytes(new EncString(providerOrg.key), providerKey); - orgKeys[providerOrg.id] = await (await this.rsaEncrypt(decValue)).encryptedString; + orgKeys[providerOrg.id] = (await this.rsaEncrypt(decValue)).encryptedString; } - this.orgKeys = null; - return this.storageService.save(Keys.encOrgKeys, orgKeys); + await this.stateService.setDecryptedOrganizationKeys(null); + return await this.stateService.setEncryptedOrganizationKeys(orgKeys); } - setProviderKeys(providers: ProfileProviderResponse[]): Promise<{}> { + async setProviderKeys(providers: ProfileProviderResponse[]): Promise { const providerKeys: any = {}; providers.forEach(provider => { providerKeys[provider.id] = provider.key; }); - this.providerKeys = null; - return this.storageService.save(Keys.encProviderKeys, providerKeys); + await this.stateService.setDecryptedProviderKeys(null); + return await this.stateService.setEncryptedProviderKeys(providerKeys); } - async getKey(keySuffix?: KeySuffixOptions): Promise { - if (this.key != null) { - return this.key; + async getKey(keySuffix?: KeySuffixOptions, userId?: string): Promise { + const inMemoryKey = await this.stateService.getCryptoMasterKey({ userId: userId }); + + if (inMemoryKey != null) { + return inMemoryKey; } - keySuffix ||= 'auto'; - const symmetricKey = await this.getKeyFromStorage(keySuffix); + keySuffix ||= KeySuffixOptions.Auto; + const symmetricKey = await this.getKeyFromStorage(keySuffix, userId); if (symmetricKey != null) { - this.setKey(symmetricKey); + // TODO: Refactor here so get key doesn't also set key + this.setKey(symmetricKey, userId); } return symmetricKey; } - async getKeyFromStorage(keySuffix: KeySuffixOptions): Promise { - const key = await this.retrieveKeyFromStorage(keySuffix); + async getKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string): Promise { + const key = await this.retrieveKeyFromStorage(keySuffix, userId); if (key != null) { - const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer); if (!await this.validateKey(symmetricKey)) { this.logService.warning('Wrong key, throwing away stored key'); - this.secureStorageService.remove(Keys.key, { keySuffix: keySuffix }); + await this.clearSecretKeyStore(userId); return null; } @@ -140,16 +118,7 @@ export class CryptoService implements CryptoServiceAbstraction { } async getKeyHash(): Promise { - if (this.keyHash != null) { - return this.keyHash; - } - - const keyHash = await this.storageService.get(Keys.keyHash); - if (keyHash != null) { - this.keyHash = keyHash; - } - - return keyHash == null ? null : this.keyHash; + return await this.stateService.getKeyHash(); } async compareAndUpdateKeyHash(masterPassword: string, key: SymmetricCryptoKey): Promise { @@ -173,11 +142,12 @@ export class CryptoService implements CryptoServiceAbstraction { @sequentialize(() => 'getEncKey') async getEncKey(key: SymmetricCryptoKey = null): Promise { - if (this.encKey != null) { - return this.encKey; + const inMemoryKey = await this.stateService.getDecryptedCryptoSymmetricKey(); + if (inMemoryKey != null) { + return inMemoryKey; } - const encKey = await this.storageService.get(Keys.encKey); + const encKey = await this.stateService.getEncryptedCryptoSymmetricKey(); if (encKey == null) { return null; } @@ -203,13 +173,15 @@ export class CryptoService implements CryptoServiceAbstraction { if (decEncKey == null) { return null; } - this.encKey = new SymmetricCryptoKey(decEncKey); - return this.encKey; + const symmetricCryptoKey = new SymmetricCryptoKey(decEncKey); + await this.stateService.setDecryptedCryptoSymmetricKey(symmetricCryptoKey); + return symmetricCryptoKey; } async getPublicKey(): Promise { - if (this.publicKey != null) { - return this.publicKey; + const inMemoryPublicKey = await this.stateService.getPublicKey(); + if (inMemoryPublicKey != null) { + return inMemoryPublicKey; } const privateKey = await this.getPrivateKey(); @@ -217,22 +189,25 @@ export class CryptoService implements CryptoServiceAbstraction { return null; } - this.publicKey = await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); - return this.publicKey; + const publicKey = await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); + await this.stateService.setPublicKey(publicKey); + return publicKey; } async getPrivateKey(): Promise { - if (this.privateKey != null) { - return this.privateKey; + const decryptedPrivateKey = await this.stateService.getDecryptedPrivateKey(); + if (decryptedPrivateKey != null) { + return decryptedPrivateKey; } - const encPrivateKey = await this.storageService.get(Keys.encPrivateKey); + const encPrivateKey = await this.stateService.getEncryptedPrivateKey(); if (encPrivateKey == null) { return null; } - this.privateKey = await this.decryptToBytes(new EncString(encPrivateKey), null); - return this.privateKey; + const privateKey = await this.decryptToBytes(new EncString(encPrivateKey), null); + await this.stateService.setDecryptedPrivateKey(privateKey); + return privateKey; } async getFingerprint(userId: string, publicKey?: ArrayBuffer): Promise { @@ -249,16 +224,17 @@ export class CryptoService implements CryptoServiceAbstraction { @sequentialize(() => 'getOrgKeys') async getOrgKeys(): Promise> { - if (this.orgKeys != null && this.orgKeys.size > 0) { - return this.orgKeys; + const orgKeys: Map = new Map(); + const decryptedOrganizationKeys = await this.stateService.getDecryptedOrganizationKeys(); + if (decryptedOrganizationKeys != null && decryptedOrganizationKeys.size > 0) { + return decryptedOrganizationKeys; } - const encOrgKeys = await this.storageService.get(Keys.encOrgKeys); + const encOrgKeys = await this.stateService.getEncryptedOrganizationKeys(); if (encOrgKeys == null) { return null; } - const orgKeys: Map = new Map(); let setKey = false; for (const orgId in encOrgKeys) { @@ -272,10 +248,10 @@ export class CryptoService implements CryptoServiceAbstraction { } if (setKey) { - this.orgKeys = orgKeys; + await this.stateService.setDecryptedOrganizationKeys(orgKeys); } - return this.orgKeys; + return orgKeys; } async getOrgKey(orgId: string): Promise { @@ -293,16 +269,17 @@ export class CryptoService implements CryptoServiceAbstraction { @sequentialize(() => 'getProviderKeys') async getProviderKeys(): Promise> { - if (this.providerKeys != null && this.providerKeys.size > 0) { - return this.providerKeys; + const providerKeys: Map = new Map(); + const decryptedProviderKeys = await this.stateService.getDecryptedProviderKeys(); + if (decryptedProviderKeys != null && decryptedProviderKeys.size > 0) { + return decryptedProviderKeys; } - const encProviderKeys = await this.storageService.get(Keys.encProviderKeys); + const encProviderKeys = await this.stateService.getEncryptedProviderKeys(); if (encProviderKeys == null) { return null; } - const providerKeys: Map = new Map(); let setKey = false; for (const orgId in encProviderKeys) { @@ -316,10 +293,10 @@ export class CryptoService implements CryptoServiceAbstraction { } if (setKey) { - this.providerKeys = providerKeys; + await this.stateService.setDecryptedProviderKeys(providerKeys); } - return this.providerKeys; + return providerKeys; } async getProviderKey(providerId: string): Promise { @@ -336,84 +313,87 @@ export class CryptoService implements CryptoServiceAbstraction { } async hasKey(): Promise { - return this.hasKeyInMemory() || await this.hasKeyStored('auto') || await this.hasKeyStored('biometric'); + return await this.hasKeyInMemory() || await this.hasKeyStored(KeySuffixOptions.Auto) || await this.hasKeyStored(KeySuffixOptions.Biometric); } - hasKeyInMemory(): boolean { - return this.key != null; + async hasKeyInMemory(userId?: string): Promise { + return await this.stateService.getCryptoMasterKey({ userId: userId }) != null; } - hasKeyStored(keySuffix: KeySuffixOptions): Promise { - return this.secureStorageService.has(Keys.key, { keySuffix: keySuffix }); + async hasKeyStored(keySuffix: KeySuffixOptions, userId?: string): Promise { + const key = keySuffix === KeySuffixOptions.Auto ? + await this.stateService.getCryptoMasterKeyAuto({ userId: userId }) : + await this.stateService.hasCryptoMasterKeyBiometric({ userId: userId }); + + return key != null; } async hasEncKey(): Promise { - const encKey = await this.storageService.get(Keys.encKey); - return encKey != null; + return await this.stateService.getEncryptedCryptoSymmetricKey() != null; } - async clearKey(clearSecretStorage: boolean = true): Promise { - this.key = this.legacyEtmKey = null; + async clearKey(clearSecretStorage: boolean = true, userId?: string): Promise { + await this.stateService.setCryptoMasterKey(null, { userId: userId }); + await this.stateService.setLegacyEtmKey(null, { userId: userId }); if (clearSecretStorage) { - this.clearStoredKey('auto'); - this.clearStoredKey('biometric'); + await this.clearSecretKeyStore(userId); } } async clearStoredKey(keySuffix: KeySuffixOptions) { - await this.secureStorageService.remove(Keys.key, { keySuffix: keySuffix }); + keySuffix === KeySuffixOptions.Auto ? + await this.stateService.setCryptoMasterKeyAuto(null) : + await this.stateService.setCryptoMasterKeyBiometric(null); } - clearKeyHash(): Promise { - this.keyHash = null; - return this.storageService.remove(Keys.keyHash); + async clearKeyHash(userId?: string): Promise { + return await this.stateService.setKeyHash(null, { userId: userId }); } - clearEncKey(memoryOnly?: boolean): Promise { - this.encKey = null; - if (memoryOnly) { - return Promise.resolve(); + async clearEncKey(memoryOnly?: boolean, userId?: string): Promise { + await this.stateService.setDecryptedCryptoSymmetricKey(null, { userId: userId }); + if (!memoryOnly) { + await this.stateService.setEncryptedCryptoSymmetricKey(null, { userId: userId }); } - return this.storageService.remove(Keys.encKey); } - clearKeyPair(memoryOnly?: boolean): Promise { - this.privateKey = null; - this.publicKey = null; - if (memoryOnly) { - return Promise.resolve(); + async clearKeyPair(memoryOnly?: boolean, userId?: string): Promise { + const keysToClear: Promise[] = [ + this.stateService.setDecryptedPrivateKey(null, { userId: userId }), + this.stateService.setPublicKey(null, { userId: userId }), + ]; + if (!memoryOnly) { + keysToClear.push(this.stateService.setEncryptedPrivateKey(null, { userId: userId })); } - return this.storageService.remove(Keys.encPrivateKey); + return Promise.all(keysToClear); } - clearOrgKeys(memoryOnly?: boolean): Promise { - this.orgKeys = null; - if (memoryOnly) { - return Promise.resolve(); + async clearOrgKeys(memoryOnly?: boolean, userId?: string): Promise { + await this.stateService.setDecryptedOrganizationKeys(null, { userId: userId }); + if (!memoryOnly) { + await this.stateService.setEncryptedOrganizationKeys(null, { userId: userId }); + } + } + + async clearProviderKeys(memoryOnly?: boolean, userId?: string): Promise { + await this.stateService.setDecryptedProviderKeys(null, { userId: userId }); + if (!memoryOnly) { + await this.stateService.setEncryptedProviderKeys(null, { userId: userId }); } - return this.storageService.remove(Keys.encOrgKeys); } - clearProviderKeys(memoryOnly?: boolean): Promise { - this.providerKeys = null; - if (memoryOnly) { - return Promise.resolve(); - } - return this.storageService.remove(Keys.encOrgKeys); + async clearPinProtectedKey(userId?: string): Promise { + return await this.stateService.setEncryptedPinProtected(null, { userId: userId }); } - clearPinProtectedKey(): Promise { - return this.storageService.remove(ConstantsService.pinProtectedKey); - } - - async clearKeys(): Promise { - await this.clearKey(); - await this.clearKeyHash(); - await this.clearOrgKeys(); - await this.clearProviderKeys(); - await this.clearEncKey(); - await this.clearKeyPair(); - await this.clearPinProtectedKey(); + async clearKeys(userId?: string): Promise { + await this.clearKey(true, userId); + await this.clearKeyHash(userId); + await this.clearOrgKeys(false, userId); + await this.clearProviderKeys(false, userId); + await this.clearEncKey(false, userId); + await this.clearKeyPair(false, userId); + await this.clearPinProtectedKey(userId); } async toggleKey(): Promise { @@ -442,7 +422,7 @@ export class CryptoService implements CryptoServiceAbstraction { protectedKeyCs: EncString = null): Promise { if (protectedKeyCs == null) { - const pinProtectedKey = await this.storageService.get(ConstantsService.pinProtectedKey); + const pinProtectedKey = await this.stateService.getEncryptedPinProtected(); if (pinProtectedKey == null) { throw new Error('No PIN protected key found.'); } @@ -699,7 +679,7 @@ export class CryptoService implements CryptoServiceAbstraction { async validateKey(key: SymmetricCryptoKey) { try { - const encPrivateKey = await this.storageService.get(Keys.encPrivateKey); + const encPrivateKey = await this.stateService.getEncryptedPrivateKey(); const encKey = await this.getEncKey(key); if (encPrivateKey == null || encKey == null) { return false; @@ -715,29 +695,30 @@ export class CryptoService implements CryptoServiceAbstraction { } // Helpers - - protected async storeKey(key: SymmetricCryptoKey) { - if (await this.shouldStoreKey('auto') || await this.shouldStoreKey('biometric')) { - this.secureStorageService.save(Keys.key, key.keyB64); + protected async storeKey(key: SymmetricCryptoKey, userId?: string) { + if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId) || await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) { + await this.stateService.setCryptoMasterKeyB64(key.keyB64, { userId: userId }); } else { - this.secureStorageService.remove(Keys.key); + await this.stateService.setCryptoMasterKeyB64(null, { userId: userId }); } } - protected async shouldStoreKey(keySuffix: KeySuffixOptions) { + protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: string) { let shouldStoreKey = false; - if (keySuffix === 'auto') { - const vaultTimeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); + if (keySuffix === KeySuffixOptions.Auto) { + const vaultTimeout = await this.stateService.getVaultTimeout({ userId: userId }); shouldStoreKey = vaultTimeout == null; - } else if (keySuffix === 'biometric') { - const biometricUnlock = await this.storageService.get(ConstantsService.biometricUnlockKey); + } else if (keySuffix === KeySuffixOptions.Biometric) { + const biometricUnlock = await this.stateService.getBiometricUnlock({ userId: userId }); shouldStoreKey = biometricUnlock && this.platformUtilService.supportsSecureStorage(); } return shouldStoreKey; } - protected retrieveKeyFromStorage(keySuffix: KeySuffixOptions) { - return this.secureStorageService.get(Keys.key, { keySuffix: keySuffix }); + protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string) { + return keySuffix === KeySuffixOptions.Auto ? + await this.stateService.getCryptoMasterKeyAuto({ userId: userId }) : + await this.stateService.getCryptoMasterKeyBiometric({ userId: userId }); } private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise { @@ -759,7 +740,7 @@ export class CryptoService implements CryptoServiceAbstraction { private async aesDecryptToUtf8(encType: EncryptionType, data: string, iv: string, mac: string, key: SymmetricCryptoKey): Promise { const keyForEnc = await this.getKeyForEncryption(key); - const theKey = this.resolveLegacyKey(encType, keyForEnc); + const theKey = await this.resolveLegacyKey(encType, keyForEnc); if (theKey.macKey != null && mac == null) { this.logService.error('mac required.'); @@ -788,7 +769,7 @@ export class CryptoService implements CryptoServiceAbstraction { private async aesDecryptToBytes(encType: EncryptionType, data: ArrayBuffer, iv: ArrayBuffer, mac: ArrayBuffer, key: SymmetricCryptoKey): Promise { const keyForEnc = await this.getKeyForEncryption(key); - const theKey = this.resolveLegacyKey(encType, keyForEnc); + const theKey = await this.resolveLegacyKey(encType, keyForEnc); if (theKey.macKey != null && mac == null) { return null; @@ -830,14 +811,16 @@ export class CryptoService implements CryptoServiceAbstraction { return await this.getKey(); } - private resolveLegacyKey(encType: EncryptionType, key: SymmetricCryptoKey): SymmetricCryptoKey { + private async resolveLegacyKey(encType: EncryptionType, key: SymmetricCryptoKey): Promise { if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && key.encType === EncryptionType.AesCbc256_B64) { // Old encrypt-then-mac scheme, make a new key - if (this.legacyEtmKey == null) { - this.legacyEtmKey = new SymmetricCryptoKey(key.key, EncryptionType.AesCbc128_HmacSha256_B64); + let legacyKey = await this.stateService.getLegacyEtmKey(); + if (legacyKey == null) { + legacyKey = new SymmetricCryptoKey(key.key, EncryptionType.AesCbc128_HmacSha256_B64); + await this.stateService.setLegacyEtmKey(legacyKey); } - return this.legacyEtmKey; + return legacyKey; } return key; @@ -885,4 +868,9 @@ export class CryptoService implements CryptoServiceAbstraction { } return [new SymmetricCryptoKey(encKey), encKeyEnc]; } + + private async clearSecretKeyStore(userId?: string): Promise { + await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); + await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId }); + } } diff --git a/common/src/services/environment.service.ts b/common/src/services/environment.service.ts index 8d10995a00..0cbe1a7d54 100644 --- a/common/src/services/environment.service.ts +++ b/common/src/services/environment.service.ts @@ -2,10 +2,8 @@ import { Observable, Subject } from 'rxjs'; import { EnvironmentUrls } from '../models/domain/environmentUrls'; -import { ConstantsService } from './constants.service'; - import { EnvironmentService as EnvironmentServiceAbstraction, Urls } from '../abstractions/environment.service'; -import { StorageService } from '../abstractions/storage.service'; +import { StateService } from '../abstractions/state.service'; export class EnvironmentService implements EnvironmentServiceAbstraction { @@ -21,7 +19,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { private eventsUrl: string; private keyConnectorUrl: string; - constructor(private storageService: StorageService) {} + constructor(private stateService: StateService) {} hasBaseUrl() { return this.baseUrl != null; @@ -109,7 +107,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { } async setUrlsFromStorage(): Promise { - const urlsObj: any = await this.storageService.get(ConstantsService.environmentUrlsKey); + const urlsObj: any = await this.stateService.getEnvironmentUrls(); const urls = urlsObj || { base: null, api: null, @@ -148,7 +146,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { urls.keyConnector = this.formatUrl(urls.keyConnector); if (saveSettings) { - await this.storageService.save(ConstantsService.environmentUrlsKey, { + await this.stateService.setEnvironmentUrls({ base: urls.base, api: urls.api, identity: urls.identity, diff --git a/common/src/services/event.service.ts b/common/src/services/event.service.ts index 452133fa6b..7f1de12ab5 100644 --- a/common/src/services/event.service.ts +++ b/common/src/services/event.service.ts @@ -7,18 +7,16 @@ import { EventRequest } from '../models/request/eventRequest'; import { ApiService } from '../abstractions/api.service'; import { CipherService } from '../abstractions/cipher.service'; import { EventService as EventServiceAbstraction } from '../abstractions/event.service'; -import { StorageService } from '../abstractions/storage.service'; -import { UserService } from '../abstractions/user.service'; - import { LogService } from '../abstractions/log.service'; -import { ConstantsService } from './constants.service'; +import { OrganizationService } from '../abstractions/organization.service'; +import { StateService } from '../abstractions/state.service'; export class EventService implements EventServiceAbstraction { private inited = false; - constructor(private storageService: StorageService, private apiService: ApiService, - private userService: UserService, private cipherService: CipherService, - private logService: LogService) { } + constructor(private apiService: ApiService, private cipherService: CipherService, + private stateService: StateService, private logService: LogService, + private organizationService: OrganizationService) { } init(checkOnInterval: boolean) { if (this.inited) { @@ -33,11 +31,11 @@ export class EventService implements EventServiceAbstraction { } async collect(eventType: EventType, cipherId: string = null, uploadImmediately = false): Promise { - const authed = await this.userService.isAuthenticated(); + const authed = await this.stateService.getIsAuthenticated(); if (!authed) { return; } - const organizations = await this.userService.getAllOrganizations(); + const organizations = await this.organizationService.getAll(); if (organizations == null) { return; } @@ -51,7 +49,7 @@ export class EventService implements EventServiceAbstraction { return; } } - let eventCollection = await this.storageService.get(ConstantsService.eventCollectionKey); + let eventCollection = await this.stateService.getEventCollection(); if (eventCollection == null) { eventCollection = []; } @@ -60,18 +58,18 @@ export class EventService implements EventServiceAbstraction { event.cipherId = cipherId; event.date = new Date().toISOString(); eventCollection.push(event); - await this.storageService.save(ConstantsService.eventCollectionKey, eventCollection); + await this.stateService.setEventCollection(eventCollection); if (uploadImmediately) { await this.uploadEvents(); } } - async uploadEvents(): Promise { - const authed = await this.userService.isAuthenticated(); + async uploadEvents(userId?: string): Promise { + const authed = await this.stateService.getIsAuthenticated({ userId: userId }); if (!authed) { return; } - const eventCollection = await this.storageService.get(ConstantsService.eventCollectionKey); + const eventCollection = await this.stateService.getEventCollection({ userId: userId }); if (eventCollection == null || eventCollection.length === 0) { return; } @@ -84,13 +82,13 @@ export class EventService implements EventServiceAbstraction { }); try { await this.apiService.postEventsCollect(request); - this.clearEvents(); + this.clearEvents(userId); } catch (e) { this.logService.error(e); } } - async clearEvents(): Promise { - await this.storageService.remove(ConstantsService.eventCollectionKey); + async clearEvents(userId?: string): Promise { + await this.stateService.setEventCollection(null, { userId: userId }); } } diff --git a/common/src/services/folder.service.ts b/common/src/services/folder.service.ts index 0f3ae50e23..d8b9543081 100644 --- a/common/src/services/folder.service.ts +++ b/common/src/services/folder.service.ts @@ -15,28 +15,22 @@ import { CipherService } from '../abstractions/cipher.service'; import { CryptoService } from '../abstractions/crypto.service'; import { FolderService as FolderServiceAbstraction } from '../abstractions/folder.service'; import { I18nService } from '../abstractions/i18n.service'; -import { StorageService } from '../abstractions/storage.service'; -import { UserService } from '../abstractions/user.service'; +import { StateService } from '../abstractions/state.service'; + import { CipherData } from '../models/data/cipherData'; import { ServiceUtils } from '../misc/serviceUtils'; import { Utils } from '../misc/utils'; -const Keys = { - foldersPrefix: 'folders_', - ciphersPrefix: 'ciphers_', -}; const NestingDelimiter = '/'; export class FolderService implements FolderServiceAbstraction { - decryptedFolderCache: FolderView[]; + constructor(private cryptoService: CryptoService, private apiService: ApiService, + private i18nService: I18nService, private cipherService: CipherService, + private stateService: StateService) { } - constructor(private cryptoService: CryptoService, private userService: UserService, - private apiService: ApiService, private storageService: StorageService, - private i18nService: I18nService, private cipherService: CipherService) { } - - clearCache(): void { - this.decryptedFolderCache = null; + async clearCache(userId?: string): Promise { + await this.stateService.setDecryptedFolders(null, { userId: userId }); } async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise { @@ -47,9 +41,7 @@ export class FolderService implements FolderServiceAbstraction { } async get(id: string): Promise { - const userId = await this.userService.getUserId(); - const folders = await this.storageService.get<{ [id: string]: FolderData; }>( - Keys.foldersPrefix + userId); + const folders = await this.stateService.getEncryptedFolders(); if (folders == null || !folders.hasOwnProperty(id)) { return null; } @@ -58,9 +50,7 @@ export class FolderService implements FolderServiceAbstraction { } async getAll(): Promise { - const userId = await this.userService.getUserId(); - const folders = await this.storageService.get<{ [id: string]: FolderData; }>( - Keys.foldersPrefix + userId); + const folders = await this.stateService.getEncryptedFolders(); const response: Folder[] = []; for (const id in folders) { if (folders.hasOwnProperty(id)) { @@ -71,8 +61,9 @@ export class FolderService implements FolderServiceAbstraction { } async getAllDecrypted(): Promise { - if (this.decryptedFolderCache != null) { - return this.decryptedFolderCache; + const decryptedFolders = await this.stateService.getDecryptedFolders(); + if (decryptedFolders != null) { + return decryptedFolders; } const hasKey = await this.cryptoService.hasKey(); @@ -94,8 +85,8 @@ export class FolderService implements FolderServiceAbstraction { noneFolder.name = this.i18nService.t('noneFolder'); decFolders.push(noneFolder); - this.decryptedFolderCache = decFolders; - return this.decryptedFolderCache; + await this.stateService.setDecryptedFolders(decFolders); + return decFolders; } async getAllNested(): Promise[]> { @@ -127,15 +118,13 @@ export class FolderService implements FolderServiceAbstraction { response = await this.apiService.putFolder(folder.id, request); } - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); const data = new FolderData(response, userId); await this.upsert(data); } async upsert(folder: FolderData | FolderData[]): Promise { - const userId = await this.userService.getUserId(); - let folders = await this.storageService.get<{ [id: string]: FolderData; }>( - Keys.foldersPrefix + userId); + let folders = await this.stateService.getEncryptedFolders(); if (folders == null) { folders = {}; } @@ -149,25 +138,22 @@ export class FolderService implements FolderServiceAbstraction { }); } - await this.storageService.save(Keys.foldersPrefix + userId, folders); - this.decryptedFolderCache = null; + await this.stateService.setDecryptedFolders(null); + await this.stateService.setEncryptedFolders(folders); } async replace(folders: { [id: string]: FolderData; }): Promise { - const userId = await this.userService.getUserId(); - await this.storageService.save(Keys.foldersPrefix + userId, folders); - this.decryptedFolderCache = null; + await this.stateService.setDecryptedFolders(null); + await this.stateService.setEncryptedFolders(folders); } - async clear(userId: string): Promise { - await this.storageService.remove(Keys.foldersPrefix + userId); - this.decryptedFolderCache = null; + async clear(userId?: string): Promise { + await this.stateService.setDecryptedFolders(null, { userId: userId }); + await this.stateService.setEncryptedFolders(null, { userId: userId }); } async delete(id: string | string[]): Promise { - const userId = await this.userService.getUserId(); - const folders = await this.storageService.get<{ [id: string]: FolderData; }>( - Keys.foldersPrefix + userId); + const folders = await this.stateService.getEncryptedFolders(); if (folders == null) { return; } @@ -183,11 +169,11 @@ export class FolderService implements FolderServiceAbstraction { }); } - await this.storageService.save(Keys.foldersPrefix + userId, folders); - this.decryptedFolderCache = null; + await this.stateService.setDecryptedFolders(null); + await this.stateService.setEncryptedFolders(folders); // Items in a deleted folder are re-assigned to "No Folder" - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(Keys.ciphersPrefix + userId); + const ciphers = await this.stateService.getEncryptedCiphers(); if (ciphers != null) { const updates: CipherData[] = []; for (const cId in ciphers) { diff --git a/common/src/services/keyConnector.service.ts b/common/src/services/keyConnector.service.ts index d597469f39..1e6894f222 100644 --- a/common/src/services/keyConnector.service.ts +++ b/common/src/services/keyConnector.service.ts @@ -2,9 +2,9 @@ import { ApiService } from '../abstractions/api.service'; import { CryptoService } from '../abstractions/crypto.service'; import { KeyConnectorService as KeyConnectorServiceAbstraction } from '../abstractions/keyConnector.service'; import { LogService } from '../abstractions/log.service'; -import { StorageService } from '../abstractions/storage.service'; +import { OrganizationService } from '../abstractions/organization.service'; +import { StateService } from '../abstractions/state.service'; import { TokenService } from '../abstractions/token.service'; -import { UserService } from '../abstractions/user.service'; import { OrganizationUserType } from '../enums/organizationUserType'; @@ -14,25 +14,17 @@ import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKeyRequest'; -const Keys = { - usesKeyConnector: 'usesKeyConnector', - convertAccountToKeyConnector: 'convertAccountToKeyConnector', -}; - export class KeyConnectorService implements KeyConnectorServiceAbstraction { - private usesKeyConnector?: boolean = null; - - constructor(private storageService: StorageService, private userService: UserService, - private cryptoService: CryptoService, private apiService: ApiService, - private tokenService: TokenService, private logService: LogService) { } + constructor(private stateService: StateService, private cryptoService: CryptoService, + private apiService: ApiService, private tokenService: TokenService, + private logService: LogService, private organizationService: OrganizationService) { } setUsesKeyConnector(usesKeyConnector: boolean) { - this.usesKeyConnector = usesKeyConnector; - return this.storageService.save(Keys.usesKeyConnector, usesKeyConnector); + return this.stateService.setUsesKeyConnector(usesKeyConnector); } async getUsesKeyConnector(): Promise { - return this.usesKeyConnector ??= await this.storageService.get(Keys.usesKeyConnector); + return await this.stateService.getUsesKeyConnector(); } async userNeedsMigration() { @@ -70,7 +62,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { } async getManagingOrganization() { - const orgs = await this.userService.getAllOrganizations(); + const orgs = await this.organizationService.getAll(); return orgs.find(o => o.keyConnectorEnabled && o.type !== OrganizationUserType.Admin && @@ -79,15 +71,15 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { } async setConvertAccountRequired(status: boolean) { - await this.storageService.save(Keys.convertAccountToKeyConnector, status); + await this.stateService.setConvertAccountToKeyConnector(status); } async getConvertAccountRequired(): Promise { - return await this.storageService.get(Keys.convertAccountToKeyConnector); + return await this.stateService.getConvertAccountToKeyConnector(); } async removeConvertAccountRequired() { - await this.storageService.remove(Keys.convertAccountToKeyConnector); + await this.stateService.setConvertAccountToKeyConnector(null); } async clear() { diff --git a/common/src/services/notifications.service.ts b/common/src/services/notifications.service.ts index b1d5723b2d..b10b89bb81 100644 --- a/common/src/services/notifications.service.ts +++ b/common/src/services/notifications.service.ts @@ -8,8 +8,8 @@ import { AppIdService } from '../abstractions/appId.service'; import { EnvironmentService } from '../abstractions/environment.service'; import { LogService } from '../abstractions/log.service'; import { NotificationsService as NotificationsServiceAbstraction } from '../abstractions/notifications.service'; +import { StateService } from '../abstractions/state.service'; import { SyncService } from '../abstractions/sync.service'; -import { UserService } from '../abstractions/user.service'; import { VaultTimeoutService } from '../abstractions/vaultTimeout.service'; import { @@ -27,10 +27,10 @@ export class NotificationsService implements NotificationsServiceAbstraction { private inactive = false; private reconnectTimer: any = null; - constructor(private userService: UserService, private syncService: SyncService, - private appIdService: AppIdService, private apiService: ApiService, - private vaultTimeoutService: VaultTimeoutService, private environmentService: EnvironmentService, - private logoutCallback: () => Promise, private logService: LogService) { + constructor(private syncService: SyncService, private appIdService: AppIdService, + private apiService: ApiService, private vaultTimeoutService: VaultTimeoutService, + private environmentService: EnvironmentService, private logoutCallback: () => Promise, + private logService: LogService, private stateService: StateService) { this.environmentService.urls.subscribe(() => { if (!this.inited) { return; @@ -117,9 +117,9 @@ export class NotificationsService implements NotificationsServiceAbstraction { return; } - const isAuthenticated = await this.userService.isAuthenticated(); + const isAuthenticated = await this.stateService.getIsAuthenticated(); const payloadUserId = notification.payload.userId || notification.payload.UserId; - const myUserId = await this.userService.getUserId(); + const myUserId = await this.stateService.getUserId(); if (isAuthenticated && payloadUserId != null && payloadUserId !== myUserId) { return; } @@ -202,7 +202,7 @@ export class NotificationsService implements NotificationsServiceAbstraction { } private async isAuthedAndUnlocked() { - if (await this.userService.isAuthenticated()) { + if (await this.stateService.getIsAuthenticated()) { const locked = await this.vaultTimeoutService.isLocked(); return !locked; } diff --git a/common/src/services/organization.service.ts b/common/src/services/organization.service.ts new file mode 100644 index 0000000000..cdfe2981f3 --- /dev/null +++ b/common/src/services/organization.service.ts @@ -0,0 +1,49 @@ +import { OrganizationService as OrganizationServiceAbstraction } from '../abstractions/organization.service'; +import { StateService } from '../abstractions/state.service'; + +import { OrganizationData } from '../models/data/organizationData'; + +import { Organization } from '../models/domain/organization'; + +export class OrganizationService implements OrganizationServiceAbstraction { + constructor(private stateService: StateService) { + } + + async get(id: string): Promise { + const organizations = await this.stateService.getOrganizations(); + if (organizations == null || !organizations.hasOwnProperty(id)) { + return null; + } + + return new Organization(organizations[id]); + } + + async getByIdentifier(identifier: string): Promise { + const organizations = await this.getAll(); + if (organizations == null || organizations.length === 0) { + return null; + } + + return organizations.find(o => o.identifier === identifier); + } + + async getAll(userId?: string): Promise { + const organizations = await this.stateService.getOrganizations({ userId: userId }); + const response: Organization[] = []; + for (const id in organizations) { + if (organizations.hasOwnProperty(id) && !organizations[id].isProviderUser) { + response.push(new Organization(organizations[id])); + } + } + return response; + } + + async save(organizations: {[id: string]: OrganizationData}) { + return await this.stateService.setOrganizations(organizations); + } + + async canManageSponsorships(): Promise { + const orgs = await this.getAll(); + return orgs.some(o => o.familySponsorshipAvailable || o.familySponsorshipFriendlyName !== null); + } +} diff --git a/common/src/services/passwordGeneration.service.ts b/common/src/services/passwordGeneration.service.ts index ab4af5b950..c6d0d7cc68 100644 --- a/common/src/services/passwordGeneration.service.ts +++ b/common/src/services/passwordGeneration.service.ts @@ -10,7 +10,7 @@ import { PasswordGenerationService as PasswordGenerationServiceAbstraction, } from '../abstractions/passwordGeneration.service'; import { PolicyService } from '../abstractions/policy.service'; -import { StorageService } from '../abstractions/storage.service'; +import { StateService } from '../abstractions/state.service'; import { EEFLongWordList } from '../misc/wordlist'; @@ -34,19 +34,11 @@ const DefaultOptions = { includeNumber: false, }; -const Keys = { - options: 'passwordGenerationOptions', - history: 'generatedPasswordHistory', -}; - const MaxPasswordsInHistory = 100; export class PasswordGenerationService implements PasswordGenerationServiceAbstraction { - private optionsCache: any; - private history: GeneratedPasswordHistory[]; - - constructor(private cryptoService: CryptoService, private storageService: StorageService, - private policyService: PolicyService) { } + constructor(private cryptoService: CryptoService, private policyService: PolicyService, + private stateService: StateService) { } async generatePassword(options: any): Promise { // overload defaults with given options @@ -188,17 +180,16 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr } async getOptions(): Promise<[any, PasswordGeneratorPolicyOptions]> { - if (this.optionsCache == null) { - const options = await this.storageService.get(Keys.options); - if (options == null) { - this.optionsCache = DefaultOptions; - } else { - this.optionsCache = Object.assign({}, DefaultOptions, options); - } + let options = await this.stateService.getPasswordGenerationOptions(); + if (options == null) { + options = DefaultOptions; + } else { + options = Object.assign({}, DefaultOptions, options); } - const enforcedOptions = await this.enforcePasswordGeneratorPoliciesOnOptions(this.optionsCache); - this.optionsCache = enforcedOptions[0]; - return [this.optionsCache, enforcedOptions[1]]; + await this.stateService.setPasswordGenerationOptions(options); + const enforcedOptions = await this.enforcePasswordGeneratorPoliciesOnOptions(options); + options = enforcedOptions[0]; + return [options, enforcedOptions[1]]; } async enforcePasswordGeneratorPoliciesOnOptions(options: any): Promise<[any, PasswordGeneratorPolicyOptions]> { @@ -332,8 +323,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr } async saveOptions(options: any) { - await this.storageService.save(Keys.options, options); - this.optionsCache = options; + await this.stateService.setPasswordGenerationOptions(options); } async getHistory(): Promise { @@ -342,12 +332,16 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr return new Array(); } - if (!this.history) { - const encrypted = await this.storageService.get(Keys.history); - this.history = await this.decryptHistory(encrypted); + if (await this.stateService.getDecryptedPasswordGenerationHistory() != null) { + const encrypted = await this.stateService.getEncryptedPasswordGenerationHistory(); + const decrypted = await this.decryptHistory(encrypted); + await this.stateService.setDecryptedPasswordGenerationHistory(decrypted); } - return this.history || new Array(); + const passwordGenerationHistory = await this.stateService.getDecryptedPasswordGenerationHistory(); + return passwordGenerationHistory != null ? + passwordGenerationHistory : + new Array(); } async addHistory(password: string): Promise { @@ -372,12 +366,12 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr } const newHistory = await this.encryptHistory(currentHistory); - return await this.storageService.save(Keys.history, newHistory); + return await this.stateService.setEncryptedPasswordGenerationHistory(newHistory); } - async clear(): Promise { - this.history = []; - return await this.storageService.remove(Keys.history); + async clear(userId?: string): Promise { + await this.stateService.setEncryptedPasswordGenerationHistory(null, { userId: userId }); + await this.stateService.setDecryptedPasswordGenerationHistory(null, { userId: userId }); } passwordStrength(password: string, userInputs: string[] = null): zxcvbn.ZXCVBNResult { diff --git a/common/src/services/policy.service.ts b/common/src/services/policy.service.ts index 4d23448af8..042d01b0c6 100644 --- a/common/src/services/policy.service.ts +++ b/common/src/services/policy.service.ts @@ -1,6 +1,6 @@ +import { OrganizationService } from '../abstractions/organization.service'; import { PolicyService as PolicyServiceAbstraction } from '../abstractions/policy.service'; -import { StorageService } from '../abstractions/storage.service'; -import { UserService } from '../abstractions/user.service'; +import { StateService } from '../abstractions/state.service'; import { PolicyData } from '../models/data/policyData'; @@ -17,43 +17,40 @@ import { ApiService } from '../abstractions/api.service'; import { ListResponse } from '../models/response/listResponse'; import { PolicyResponse } from '../models/response/policyResponse'; -const Keys = { - policiesPrefix: 'policies_', -}; - export class PolicyService implements PolicyServiceAbstraction { policyCache: Policy[]; - constructor(private userService: UserService, private storageService: StorageService, + constructor(private stateService: StateService, private organizationService: OrganizationService, private apiService: ApiService) { } - clearCache(): void { - this.policyCache = null; + async clearCache(): Promise { + await this.stateService.setDecryptedPolicies(null); } - async getAll(type?: PolicyType): Promise { - if (this.policyCache == null) { - const userId = await this.userService.getUserId(); - const policies = await this.storageService.get<{ [id: string]: PolicyData; }>( - Keys.policiesPrefix + userId); - const response: Policy[] = []; - for (const id in policies) { - if (policies.hasOwnProperty(id)) { - response.push(new Policy(policies[id])); + async getAll(type?: PolicyType, userId?: string): Promise { + let response: Policy[] = []; + const decryptedPolicies = await this.stateService.getDecryptedPolicies({ userId: userId }); + if (decryptedPolicies != null) { + response = decryptedPolicies; + } else { + const diskPolicies = await this.stateService.getEncryptedPolicies({ userId: userId }); + for (const id in diskPolicies) { + if (diskPolicies.hasOwnProperty(id)) { + response.push(new Policy(diskPolicies[id])); } } - this.policyCache = response; + await this.stateService.setDecryptedPolicies(response, { userId: userId }); } if (type != null) { - return this.policyCache.filter(p => p.type === type); + return response.filter(policy => policy.type === type); } else { - return this.policyCache; + return response; } } async getPolicyForOrganization(policyType: PolicyType, organizationId: string): Promise { - const org = await this.userService.getOrganization(organizationId); + const org = await this.organizationService.get(organizationId); if (org?.isProviderUser) { const orgPolicies = await this.apiService.getPolicies(organizationId); const policy = orgPolicies.data.find(p => p.organizationId === organizationId); @@ -70,14 +67,13 @@ export class PolicyService implements PolicyServiceAbstraction { } async replace(policies: { [id: string]: PolicyData; }): Promise { - const userId = await this.userService.getUserId(); - await this.storageService.save(Keys.policiesPrefix + userId, policies); - this.policyCache = null; + await this.stateService.setDecryptedPolicies(null); + await this.stateService.setEncryptedPolicies(policies); } - async clear(userId: string): Promise { - await this.storageService.remove(Keys.policiesPrefix + userId); - this.policyCache = null; + async clear(userId?: string): Promise { + await this.stateService.setDecryptedPolicies(null, { userId: userId }); + await this.stateService.setEncryptedPolicies(null, { userId: userId }); } async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise { @@ -187,9 +183,9 @@ export class PolicyService implements PolicyServiceAbstraction { return policiesData.map(p => new Policy(p)); } - async policyAppliesToUser(policyType: PolicyType, policyFilter?: (policy: Policy) => boolean) { - const policies = await this.getAll(policyType); - const organizations = await this.userService.getAllOrganizations(); + async policyAppliesToUser(policyType: PolicyType, policyFilter?: (policy: Policy) => boolean, userId?: string) { + const policies = await this.getAll(policyType, userId); + const organizations = await this.organizationService.getAll(userId); let filteredPolicies; if (policyFilter != null) { diff --git a/common/src/services/provider.service.ts b/common/src/services/provider.service.ts new file mode 100644 index 0000000000..9e1f560245 --- /dev/null +++ b/common/src/services/provider.service.ts @@ -0,0 +1,35 @@ +import { ProviderService as ProviderServiceAbstraction } from '../abstractions/provider.service'; +import { StateService } from '../abstractions/state.service'; + +import { ProviderData } from '../models/data/providerData'; + +import { Provider } from '../models/domain/provider'; + +export class ProviderService implements ProviderServiceAbstraction { + constructor(private stateService: StateService) { + } + + async get(id: string): Promise { + const providers = await this.stateService.getProviders(); + if (providers == null || !providers.hasOwnProperty(id)) { + return null; + } + + return new Provider(providers[id]); + } + + async getAll(): Promise { + const providers = await this.stateService.getProviders(); + const response: Provider[] = []; + for (const id in providers) { + if (providers.hasOwnProperty(id)) { + response.push(new Provider(providers[id])); + } + } + return response; + } + + async save(providers: { [id: string]: ProviderData; }) { + await this.stateService.setProviders(providers); + } +} diff --git a/common/src/services/send.service.ts b/common/src/services/send.service.ts index a5a9a69994..ff0d09ae11 100644 --- a/common/src/services/send.service.ts +++ b/common/src/services/send.service.ts @@ -12,7 +12,6 @@ import { SendFile } from '../models/domain/sendFile'; import { SendText } from '../models/domain/sendText'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -import { FileUploadType } from '../enums/fileUploadType'; import { SendType } from '../enums/sendType'; import { SendView } from '../models/view/sendView'; @@ -23,25 +22,17 @@ import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; import { FileUploadService } from '../abstractions/fileUpload.service'; import { I18nService } from '../abstractions/i18n.service'; import { SendService as SendServiceAbstraction } from '../abstractions/send.service'; -import { StorageService } from '../abstractions/storage.service'; -import { UserService } from '../abstractions/user.service'; +import { StateService } from '../abstractions/state.service'; import { Utils } from '../misc/utils'; -const Keys = { - sendsPrefix: 'sends_', -}; - export class SendService implements SendServiceAbstraction { - decryptedSendCache: SendView[]; + constructor(private cryptoService: CryptoService, private apiService: ApiService, + private fileUploadService: FileUploadService, private i18nService: I18nService, + private cryptoFunctionService: CryptoFunctionService, private stateService: StateService) { } - constructor(private cryptoService: CryptoService, private userService: UserService, - private apiService: ApiService, private fileUploadService: FileUploadService, - private storageService: StorageService, private i18nService: I18nService, - private cryptoFunctionService: CryptoFunctionService) { } - - clearCache(): void { - this.decryptedSendCache = null; + async clearCache(): Promise { + await this.stateService.setDecryptedSends(null); } async encrypt(model: SendView, file: File | ArrayBuffer, password: string, @@ -85,9 +76,7 @@ export class SendService implements SendServiceAbstraction { } async get(id: string): Promise { - const userId = await this.userService.getUserId(); - const sends = await this.storageService.get<{ [id: string]: SendData; }>( - Keys.sendsPrefix + userId); + const sends = await this.stateService.getEncryptedSends(); if (sends == null || !sends.hasOwnProperty(id)) { return null; } @@ -96,9 +85,7 @@ export class SendService implements SendServiceAbstraction { } async getAll(): Promise { - const userId = await this.userService.getUserId(); - const sends = await this.storageService.get<{ [id: string]: SendData; }>( - Keys.sendsPrefix + userId); + const sends = await this.stateService.getEncryptedSends(); const response: Send[] = []; for (const id in sends) { if (sends.hasOwnProperty(id)) { @@ -109,16 +96,17 @@ export class SendService implements SendServiceAbstraction { } async getAllDecrypted(): Promise { - if (this.decryptedSendCache != null) { - return this.decryptedSendCache; + let decSends = await this.stateService.getDecryptedSends(); + if (decSends != null) { + return decSends; } + decSends = []; const hasKey = await this.cryptoService.hasKey(); if (!hasKey) { throw new Error('No key.'); } - const decSends: SendView[] = []; const promises: Promise[] = []; const sends = await this.getAll(); sends.forEach(send => { @@ -128,8 +116,8 @@ export class SendService implements SendServiceAbstraction { await Promise.all(promises); decSends.sort(Utils.getSortFunction(this.i18nService, 'name')); - this.decryptedSendCache = decSends; - return this.decryptedSendCache; + await this.stateService.setDecryptedSends(decSends); + return decSends; } async saveWithServer(sendData: [Send, EncArrayBuffer]): Promise { @@ -160,7 +148,7 @@ export class SendService implements SendServiceAbstraction { response = await this.apiService.putSend(sendData[0].id, request); } - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); const data = new SendData(response, userId); await this.upsert(data); } @@ -191,9 +179,7 @@ export class SendService implements SendServiceAbstraction { } async upsert(send: SendData | SendData[]): Promise { - const userId = await this.userService.getUserId(); - let sends = await this.storageService.get<{ [id: string]: SendData; }>( - Keys.sendsPrefix + userId); + let sends = await this.stateService.getEncryptedSends(); if (sends == null) { sends = {}; } @@ -207,25 +193,21 @@ export class SendService implements SendServiceAbstraction { }); } - await this.storageService.save(Keys.sendsPrefix + userId, sends); - this.decryptedSendCache = null; + await this.replace(sends); } async replace(sends: { [id: string]: SendData; }): Promise { - const userId = await this.userService.getUserId(); - await this.storageService.save(Keys.sendsPrefix + userId, sends); - this.decryptedSendCache = null; + await this.stateService.setDecryptedSends(null); + await this.stateService.setEncryptedSends(sends); } - async clear(userId: string): Promise { - await this.storageService.remove(Keys.sendsPrefix + userId); - this.decryptedSendCache = null; + async clear(): Promise { + await this.stateService.setDecryptedSends(null); + await this.stateService.setEncryptedSends(null); } async delete(id: string | string[]): Promise { - const userId = await this.userService.getUserId(); - const sends = await this.storageService.get<{ [id: string]: SendData; }>( - Keys.sendsPrefix + userId); + const sends = await this.stateService.getEncryptedSends(); if (sends == null) { return; } @@ -241,8 +223,7 @@ export class SendService implements SendServiceAbstraction { }); } - await this.storageService.save(Keys.sendsPrefix + userId, sends); - this.decryptedSendCache = null; + await this.replace(sends); } async deleteWithServer(id: string): Promise { @@ -252,7 +233,7 @@ export class SendService implements SendServiceAbstraction { async removePasswordWithServer(id: string): Promise { const response = await this.apiService.putSendRemovePassword(id); - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); const data = new SendData(response, userId); await this.upsert(data); } @@ -270,7 +251,7 @@ export class SendService implements SendServiceAbstraction { reject(e); } }; - reader.onerror = evt => { + reader.onerror = () => { reject('Error reading file.'); }; }); diff --git a/common/src/services/settings.service.ts b/common/src/services/settings.service.ts index 51d970f3c1..a8c7ed5ba5 100644 --- a/common/src/services/settings.service.ts +++ b/common/src/services/settings.service.ts @@ -1,6 +1,5 @@ import { SettingsService as SettingsServiceAbstraction } from '../abstractions/settings.service'; -import { StorageService } from '../abstractions/storage.service'; -import { UserService } from '../abstractions/user.service'; +import { StateService } from '../abstractions/state.service'; const Keys = { settingsPrefix: 'settings_', @@ -8,13 +7,11 @@ const Keys = { }; export class SettingsService implements SettingsServiceAbstraction { - private settingsCache: any; - - constructor(private userService: UserService, private storageService: StorageService) { + constructor(private stateService: StateService) { } - clearCache(): void { - this.settingsCache = null; + async clearCache(): Promise { + await this.stateService.setSettings(null); } getEquivalentDomains(): Promise { @@ -25,19 +22,18 @@ export class SettingsService implements SettingsServiceAbstraction { await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains); } - async clear(userId: string): Promise { - await this.storageService.remove(Keys.settingsPrefix + userId); - this.clearCache(); + async clear(userId?: string): Promise { + await this.stateService.setSettings(null, { userId: userId }); } // Helpers private async getSettings(): Promise { - if (this.settingsCache == null) { - const userId = await this.userService.getUserId(); - this.settingsCache = this.storageService.get(Keys.settingsPrefix + userId); + const settings = await this.stateService.getSettings(); + if (settings == null) { + const userId = await this.stateService.getUserId(); } - return this.settingsCache; + return settings; } private async getSettingsKey(key: string): Promise { @@ -49,14 +45,12 @@ export class SettingsService implements SettingsServiceAbstraction { } private async setSettingsKey(key: string, value: any): Promise { - const userId = await this.userService.getUserId(); let settings = await this.getSettings(); if (!settings) { settings = {}; } settings[key] = value; - await this.storageService.save(Keys.settingsPrefix + userId, settings); - this.settingsCache = settings; + await this.stateService.setSettings(settings); } } diff --git a/common/src/services/state.service.ts b/common/src/services/state.service.ts index 03a09e30fc..6a6c71ec2a 100644 --- a/common/src/services/state.service.ts +++ b/common/src/services/state.service.ts @@ -1,27 +1,1579 @@ import { StateService as StateServiceAbstraction } from '../abstractions/state.service'; +import { Account } from '../models/domain/account'; + +import { LogService } from '../abstractions/log.service'; +import { StorageService } from '../abstractions/storage.service'; + +import { HtmlStorageLocation } from '../enums/htmlStorageLocation'; +import { KdfType } from '../enums/kdfType'; +import { StorageLocation } from '../enums/storageLocation'; +import { UriMatchType } from '../enums/uriMatchType'; + +import { CipherView } from '../models/view/cipherView'; +import { CollectionView } from '../models/view/collectionView'; +import { FolderView } from '../models/view/folderView'; +import { SendView } from '../models/view/sendView'; + +import { EncString } from '../models/domain/encString'; +import { GeneratedPasswordHistory } from '../models/domain/generatedPasswordHistory'; +import { GlobalState } from '../models/domain/globalState'; +import { Policy } from '../models/domain/policy'; +import { State } from '../models/domain/state'; +import { StorageOptions } from '../models/domain/storageOptions'; +import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; + +import { CipherData } from '../models/data/cipherData'; +import { CollectionData } from '../models/data/collectionData'; +import { EventData } from '../models/data/eventData'; +import { FolderData } from '../models/data/folderData'; +import { OrganizationData } from '../models/data/organizationData'; +import { PolicyData } from '../models/data/policyData'; +import { ProviderData } from '../models/data/providerData'; +import { SendData } from '../models/data/sendData'; + +import { BehaviorSubject } from 'rxjs'; + +import { StateMigrationService } from './stateMigration.service'; + export class StateService implements StateServiceAbstraction { - private state: any = {}; + accounts = new BehaviorSubject<{ [userId: string]: Account }>({}); + activeAccount = new BehaviorSubject(null); - get(key: string): Promise { - if (this.state.hasOwnProperty(key)) { - return Promise.resolve(this.state[key]); + private state: State = new State(); + + constructor( + private storageService: StorageService, + private secureStorageService: StorageService, + private logService: LogService, + private stateMigrationService: StateMigrationService + ) { } + + async init(): Promise { + if (await this.stateMigrationService.needsMigration()) { + await this.stateMigrationService.migrate(); + } + if (this.state.activeUserId == null) { + await this.loadStateFromDisk(); } - return Promise.resolve(null); } - save(key: string, obj: any): Promise { - this.state[key] = obj; - return Promise.resolve(); + async loadStateFromDisk() { + if (await this.getActiveUserIdFromStorage() != null) { + const diskState = await this.storageService.get('state', await this.defaultOnDiskOptions()); + this.state = diskState; + await this.pruneInMemoryAccounts(); + await this.saveStateToStorage(this.state, await this.defaultOnDiskMemoryOptions()); + await this.pushAccounts(); + } } - remove(key: string): Promise { - delete this.state[key]; - return Promise.resolve(); + async addAccount(account: Account) { + if (account?.profile?.userId == null) { + return; + } + this.state.accounts[account.profile.userId] = account; + await this.scaffoldNewAccountStorage(account); + await this.setActiveUser(account.profile.userId); + this.activeAccount.next(account.profile.userId); } - purge(): Promise { - this.state = {}; - return Promise.resolve(); + async setActiveUser(userId: string): Promise { + this.state.activeUserId = userId; + const storedState = await this.storageService.get('state', await this.defaultOnDiskOptions()); + storedState.activeUserId = userId; + await this.saveStateToStorage(storedState, await this.defaultOnDiskOptions()); + await this.pushAccounts(); + this.activeAccount.next(this.state.activeUserId); + } + + async clean(options?: StorageOptions): Promise { + // Find and set the next active user if any exists + if (options?.userId == null || options.userId === await this.getUserId()) { + for (const userId in this.state.accounts) { + if (userId == null) { + continue; + } + if (await this.getIsAuthenticated({ userId: userId })) { + await this.setActiveUser(userId); + break; + } + await this.setActiveUser(null); + } + } + + await this.removeAccountFromSessionStorage(options?.userId); + await this.removeAccountFromLocalStorage(options?.userId); + await this.removeAccountFromSecureStorage(options?.userId); + this.removeAccountFromMemory(options?.userId); + await this.pushAccounts(); + } + + async getAccessToken(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.tokens?.accessToken; + } + + async setAccessToken(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.tokens.accessToken = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getAddEditCipherInfo(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.addEditCipherInfo; + } + + async setAddEditCipherInfo(value: any, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.addEditCipherInfo = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getAlwaysShowDock(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.alwaysShowDock ?? false; + } + + async setAlwaysShowDock(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.alwaysShowDock = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getApiKeyClientId(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.profile?.apiKeyClientId; + } + + async setApiKeyClientId(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.profile.apiKeyClientId = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getApiKeyClientSecret(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.keys?.apiKeyClientSecret; + } + + async setApiKeyClientSecret(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.keys.apiKeyClientSecret = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getAutoConfirmFingerPrints(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.autoConfirmFingerPrints ?? true; + } + + async setAutoConfirmFingerprints(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.autoConfirmFingerPrints = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getAutoFillOnPageLoadDefault(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.autoFillOnPageLoadDefault ?? false; + } + + async setAutoFillOnPageLoadDefault(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.autoFillOnPageLoadDefault = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getBiometricAwaitingAcceptance(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.biometricAwaitingAcceptance ?? false; + } + + async setBiometricAwaitingAcceptance(value: boolean, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.biometricAwaitingAcceptance = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getBiometricFingerprintValidated(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.biometricFingerprintValidated ?? false; + } + + async setBiometricFingerprintValidated(value: boolean, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.biometricFingerprintValidated = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getBiometricLocked(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.settings?.biometricLocked ?? false; + } + + async setBiometricLocked(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.settings.biometricLocked = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getBiometricText(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.biometricText; + } + + async setBiometricText(value: string, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.biometricText = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getBiometricUnlock(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.biometricUnlock ?? false; + } + + async setBiometricUnlock(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.biometricUnlock = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getCanAccessPremium(options?: StorageOptions): Promise { + if (!await this.getIsAuthenticated(options)) { + return false; + } + + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + if (account.profile.hasPremiumPersonally) { + return true; + } + + const organizations = await this.getOrganizations(options); + if (organizations == null) { + return false; + } + + for (const id of Object.keys(organizations)) { + const o = organizations[id]; + if (o.enabled && o.usersGetPremium && !o.isProviderUser) { + return true; + } + } + + return false; + } + + async getClearClipboard(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.settings?.clearClipboard; + } + + async setClearClipboard(value: number, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + account.settings.clearClipboard = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getCollapsedGroupings(options?: StorageOptions): Promise> { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.collapsedGroupings; + } + + async setCollapsedGroupings(value: Set, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.collapsedGroupings = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getConvertAccountToKeyConnector(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.profile?.convertAccountToKeyConnector; + } + + async setConvertAccountToKeyConnector(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.profile.convertAccountToKeyConnector = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getCryptoMasterKey(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.keys?.cryptoMasterKey; + } + + async setCryptoMasterKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.keys.cryptoMasterKey = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getCryptoMasterKeyAuto(options?: StorageOptions): Promise { + options = this.reconcileOptions(this.reconcileOptions(options, { keySuffix: 'auto' }), await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get(`${options.userId}_masterkey_auto`, options); + } + + async setCryptoMasterKeyAuto(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(this.reconcileOptions(options, { keySuffix: 'auto' }), await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save(`${options.userId}_masterkey_auto`, value, options); + } + + async getCryptoMasterKeyB64(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get(`${options?.userId}_masterkey`, options); + } + + async setCryptoMasterKeyB64(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save(`${options.userId}_masterkey`, value, options); + } + + async getCryptoMasterKeyBiometric(options?: StorageOptions): Promise { + options = this.reconcileOptions(this.reconcileOptions(options, { keySuffix: 'biometric' }), await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + + return null; + } + return await this.secureStorageService.get(`${options.userId}_masterkey_biometric`, options); + } + + async hasCryptoMasterKeyBiometric(options?: StorageOptions): Promise { + options = this.reconcileOptions(this.reconcileOptions(options, { keySuffix: 'biometric' }), await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return false; + } + return await this.secureStorageService.has(`${options.userId}_masterkey_biometric`, options); + } + + async setCryptoMasterKeyBiometric(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(this.reconcileOptions(options, { keySuffix: 'biometric' }), await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save(`${options.userId}_masterkey_biometric`, value, options); + } + + async getDecodedToken(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.tokens?.decodedToken; + } + + async setDecodedToken(value: any, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.tokens.decodedToken = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedCiphers(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.ciphers?.decrypted; + } + + async setDecryptedCiphers(value: CipherView[], options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.ciphers.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedCollections(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.collections?.decrypted; + } + + async setDecryptedCollections(value: CollectionView[], options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.collections.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedCryptoSymmetricKey(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.keys?.cryptoSymmetricKey?.decrypted; + } + + async setDecryptedCryptoSymmetricKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.keys.cryptoSymmetricKey.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedFolders(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.folders?.decrypted; + } + + async setDecryptedFolders(value: FolderView[], options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.folders.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedOrganizationKeys(options?: StorageOptions): Promise> { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.keys?.organizationKeys?.decrypted; + } + + async setDecryptedOrganizationKeys(value: Map, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.keys.organizationKeys.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedPasswordGenerationHistory(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.passwordGenerationHistory?.decrypted; + } + + async setDecryptedPasswordGenerationHistory(value: GeneratedPasswordHistory[], options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.passwordGenerationHistory.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedPinProtected(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.settings?.pinProtected?.decrypted; + } + + async setDecryptedPinProtected(value: EncString, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.settings.pinProtected.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedPolicies(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.policies?.decrypted; + } + + async setDecryptedPolicies(value: Policy[], options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.policies.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedPrivateKey(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.keys?.privateKey?.decrypted; + } + + async setDecryptedPrivateKey(value: ArrayBuffer, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.keys.privateKey.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedProviderKeys(options?: StorageOptions): Promise> { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.keys?.providerKeys?.decrypted; + } + + async setDecryptedProviderKeys(value: Map, options?: StorageOptions): Promise { + const account = (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))); + account.keys.providerKeys.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDecryptedSends(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.sends?.decrypted; + } + + async setDecryptedSends(value: SendView[], options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.sends.decrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getDefaultUriMatch(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.defaultUriMatch; + } + + async setDefaultUriMatch(value: UriMatchType, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.defaultUriMatch = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getDisableAddLoginNotification(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.disableAddLoginNotification ?? false; + } + + async setDisableAddLoginNotification(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.disableAddLoginNotification = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getDisableAutoBiometricsPrompt(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.disableAutoBiometricsPrompt ?? false; + } + + async setDisableAutoBiometricsPrompt(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.disableAutoBiometricsPrompt = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getDisableAutoTotpCopy(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.disableAutoTotpCopy ?? false; + } + + async setDisableAutoTotpCopy(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.disableAutoTotpCopy = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getDisableBadgeCounter(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.disableBadgeCounter ?? false; + } + + async setDisableBadgeCounter(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.disableBadgeCounter = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getDisableChangedPasswordNotification(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.disableChangedPasswordNotification ?? false; + } + + async setDisableChangedPasswordNotification(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.disableChangedPasswordNotification = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getDisableContextMenuItem(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.disableContextMenuItem ?? false; + } + + async setDisableContextMenuItem(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.disableContextMenuItem = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getDisableFavicon(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.disableFavicon ?? false; + } + + async setDisableFavicon(value: boolean, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + globals.disableFavicon = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getDisableGa(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.disableGa ?? false; + } + + async setDisableGa(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.disableGa = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getDontShowCardsCurrentTab(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.dontShowCardsCurrentTab ?? false; + } + + async setDontShowCardsCurrentTab(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.dontShowCardsCurrentTab = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getDontShowIdentitiesCurrentTab(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.dontShowIdentitiesCurrentTab ?? false; + } + + async setDontShowIdentitiesCurrentTab(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.dontShowIdentitiesCurrentTab = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEmail(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.profile?.email; + } + + async setEmail(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.profile.email = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEmailVerified(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.profile.emailVerified ?? false; + } + + async setEmailVerified(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.profile.emailVerified = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEnableAlwaysOnTop(options?: StorageOptions): Promise { + const accountPreference = (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.enableAlwaysOnTop; + const globalPreference = (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.enableAlwaysOnTop; + return accountPreference ?? globalPreference ?? false; + } + + async setEnableAlwaysOnTop(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.enableAlwaysOnTop = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.enableAlwaysOnTop = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEnableAutoFillOnPageLoad(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.enableAutoFillOnPageLoad ?? false; + } + + async setEnableAutoFillOnPageLoad(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.enableAutoFillOnPageLoad = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEnableBiometric(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.enableBiometrics ?? false; + } + + async setEnableBiometric(value: boolean, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.enableBiometrics = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEnableBrowserIntegration(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.enableBrowserIntegration ?? false; + } + + async setEnableBrowserIntegration(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.enableBrowserIntegration = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEnableBrowserIntegrationFingerprint(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.enableBrowserIntegrationFingerprint ?? false; + } + + async setEnableBrowserIntegrationFingerprint(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.enableBrowserIntegrationFingerprint = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEnableCloseToTray(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.enableCloseToTray ?? false; + } + + async setEnableCloseToTray(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.enableCloseToTray = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEnableFullWidth(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.settings?.enableFullWidth ?? false; + } + + async setEnableFullWidth(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + account.settings.enableFullWidth = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getEnableGravitars(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.settings?.enableGravitars ?? false; + } + + async setEnableGravitars(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + account.settings.enableGravitars = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getEnableMinimizeToTray(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.enableMinimizeToTray ?? false; + } + + async setEnableMinimizeToTray(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.enableMinimizeToTray = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEnableStartToTray(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings.enableStartToTray ?? false; + } + + async setEnableStartToTray(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.enableStartToTray = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEnableTray(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.enableTray ?? false; + } + + async setEnableTray(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.enableTray = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData; }> { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())))?.data?.ciphers?.encrypted; + } + + async setEncryptedCiphers(value: { [id: string]: CipherData; }, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + account.data.ciphers.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + } + + async getEncryptedCollections(options?: StorageOptions): Promise<{ [id: string]: CollectionData; }> { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())))?.data?.collections?.encrypted; + } + + async setEncryptedCollections(value: { [id: string]: CollectionData; }, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + account.data.collections.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + } + + async getEncryptedCryptoSymmetricKey(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.keys.cryptoSymmetricKey.encrypted; + } + + async setEncryptedCryptoSymmetricKey(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.keys.cryptoSymmetricKey.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEncryptedFolders(options?: StorageOptions): Promise<{ [id: string]: FolderData; }> { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())))?.data?.folders?.encrypted; + } + + async setEncryptedFolders(value: { [id: string]: FolderData; }, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + account.data.folders.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + } + + async getEncryptedOrganizationKeys(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.keys?.organizationKeys.encrypted; + } + + async setEncryptedOrganizationKeys(value: Map, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.keys.organizationKeys.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEncryptedPasswordGenerationHistory(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.data?.passwordGenerationHistory?.encrypted; + } + + async setEncryptedPasswordGenerationHistory(value: GeneratedPasswordHistory[], options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.data.passwordGenerationHistory.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEncryptedPinProtected(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.pinProtected?.encrypted; + } + + async setEncryptedPinProtected(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.pinProtected.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEncryptedPolicies(options?: StorageOptions): Promise<{ [id: string]: PolicyData; }> { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.data?.policies?.encrypted; + } + + async setEncryptedPolicies(value: { [id: string]: PolicyData; }, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.data.policies.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEncryptedPrivateKey(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.keys?.privateKey?.encrypted; + } + + async setEncryptedPrivateKey(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.keys.privateKey.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEncryptedProviderKeys(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.keys?.providerKeys?.encrypted; + } + + async setEncryptedProviderKeys(value: any, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.keys.providerKeys.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEncryptedSends(options?: StorageOptions): Promise<{ [id: string]: SendData; }> { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())))?.data?.sends.encrypted; + } + + async setEncryptedSends(value: { [id: string]: SendData; }, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + account.data.sends.encrypted = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + } + + async getEntityId(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile?.entityId; + } + + async setEntityId(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.profile.entityId = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getEntityType(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile?.entityType; + } + + async setEntityType(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.profile.entityType = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getEnvironmentUrls(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.environmentUrls ?? { + server: 'bitwarden.com', + }; + } + + async setEnvironmentUrls(value: any, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.environmentUrls = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEquivalentDomains(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.equivalentDomains; + } + + async setEquivalentDomains(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.equivalentDomains = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEventCollection(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.data?.eventCollection; + } + + async setEventCollection(value: EventData[], options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.data.eventCollection = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getEverBeenUnlocked(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile?.everBeenUnlocked ?? false; + } + + async setEverBeenUnlocked(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.profile.everBeenUnlocked = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getForcePasswordReset(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile?.forcePasswordReset ?? false; + } + + async setForcePasswordReset(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.profile.forcePasswordReset = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getInstalledVersion(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.installedVersion; + } + + async setInstalledVersion(value: string, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.installedVersion = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getIsAuthenticated(options?: StorageOptions): Promise { + return await this.getAccessToken(options) != null && await this.getUserId(options) != null; + } + + async getKdfIterations(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.profile?.kdfIterations; + } + + async setKdfIterations(value: number, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.profile.kdfIterations = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getKdfType(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.profile?.kdfType; + } + + async setKdfType(value: KdfType, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.profile.kdfType = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getKeyHash(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.profile?.keyHash; + } + + async setKeyHash(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.profile.keyHash = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getLastActive(options?: StorageOptions): Promise { + const lastActive = (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.profile?.lastActive; + return lastActive ?? (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.lastActive; + } + + async setLastActive(value: number, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + if (account != null) { + account.profile.lastActive = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.lastActive = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getLastSync(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())))?.profile?.lastSync; + } + + async setLastSync(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + account.profile.lastSync = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + } + + async getLegacyEtmKey(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.keys?.legacyEtmKey; + } + + async setLegacyEtmKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.keys.legacyEtmKey = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getLocalData(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.localData; + } + + async setLocalData(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.localData = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getLocale(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.locale; + } + + async setLocale(value: string, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + globals.locale = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getLoginRedirect(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.loginRedirect; + } + + async setLoginRedirect(value: any, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, this.defaultInMemoryOptions)); + globals.loginRedirect = value; + await this.saveGlobals(globals, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getMainWindowSize(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.mainWindowSize; + } + + async setMainWindowSize(value: number, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, this.defaultInMemoryOptions)); + globals.mainWindowSize = value; + await this.saveGlobals(globals, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getMinimizeOnCopyToClipboard(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.minimizeOnCopyToClipboard ?? false; + } + + async setMinimizeOnCopyToClipboard(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.minimizeOnCopyToClipboard = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getNeverDomains(options?: StorageOptions): Promise<{ [id: string]: any; }> { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.neverDomains; + } + + async setNeverDomains(value: { [id: string]: any; }, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.neverDomains = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getNoAutoPromptBiometrics(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.noAutoPromptBiometrics ?? false; + } + + async setNoAutoPromptBiometrics(value: boolean, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.noAutoPromptBiometrics = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getNoAutoPromptBiometricsText(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.noAutoPromptBiometricsText; + } + + async setNoAutoPromptBiometricsText(value: string, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.noAutoPromptBiometricsText = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getOpenAtLogin(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.openAtLogin ?? false; + } + + async setOpenAtLogin(value: boolean, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.openAtLogin = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getOrganizationInvitation(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.organizationInvitation; + } + + async setOrganizationInvitation(value: any, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, this.defaultInMemoryOptions)); + globals.organizationInvitation = value; + await this.saveGlobals(globals, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getOrganizations(options?: StorageOptions): Promise<{ [id: string]: OrganizationData; }> { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.organizations; + } + + async setOrganizations(value: { [id: string]: OrganizationData; }, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.organizations = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getPasswordGenerationOptions(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.passwordGenerationOptions; + } + + async setPasswordGenerationOptions(value: any, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.passwordGenerationOptions = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getProtectedPin(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.settings?.protectedPin; + } + + async setProtectedPin(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.settings.protectedPin = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getProviders(options?: StorageOptions): Promise<{ [id: string]: ProviderData; }> { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.data?.providers; + } + + async setProviders(value: { [id: string]: ProviderData; }, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.data.providers = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getPublicKey(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.keys?.publicKey; + } + + async setPublicKey(value: ArrayBuffer, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.keys.publicKey = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getRefreshToken(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.tokens?.refreshToken; + } + + async setRefreshToken(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + account.tokens.refreshToken = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + async getRememberedEmail(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.rememberedEmail; + } + + async setRememberedEmail(value: string, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + globals.rememberedEmail = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getSecurityStamp(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.tokens?.securityStamp; + } + + async setSecurityStamp(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.tokens.securityStamp = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getSettings(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())))?.settings?.settings; + } + + async setSettings(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + account.settings.settings = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())); + } + + async getSsoCodeVerifier(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile?.ssoCodeVerifier; + } + + async setSsoCodeVerifier(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.profile.ssoCodeVerifier = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getSsoOrgIdentifier(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.profile?.ssoOrganizationIdentifier; + } + + async setSsoOrganizationIdentifier(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + account.profile.ssoOrganizationIdentifier = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getSsoState(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile?.ssoState; + } + + async setSsoState(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.profile.ssoState = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getTheme(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.theme; + } + + async setTheme(value: string, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + globals.theme = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getTwoFactorToken(options?: StorageOptions): Promise { + return (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.twoFactorToken; + } + + async setTwoFactorToken(value: string, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + globals.twoFactorToken = value; + await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getUserId(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))?.profile?.userId; + } + + async getUsesKeyConnector(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile?.usesKeyConnector; + } + + async setUsesKeyConnector(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); + account.profile.usesKeyConnector = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async getVaultTimeout(options?: StorageOptions): Promise { + const accountVaultTimeout = (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.settings?.vaultTimeout; + const globalVaultTimeout = (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.vaultTimeout; + return accountVaultTimeout ?? globalVaultTimeout ?? 15; + } + + async setVaultTimeout(value: number, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + account.settings.vaultTimeout = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getVaultTimeoutAction(options?: StorageOptions): Promise { + const accountVaultTimeoutAction = (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.settings?.vaultTimeoutAction; + const globalVaultTimeoutAction = (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())))?.vaultTimeoutAction; + return accountVaultTimeoutAction ?? globalVaultTimeoutAction; + } + + async setVaultTimeoutAction(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + account.settings.vaultTimeoutAction = value; + await this.saveAccount(account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())); + } + + async getStateVersion(): Promise { + return (await this.getGlobals(await this.defaultOnDiskLocalOptions())).stateVersion ?? 1; + } + + async setStateVersion(value: number): Promise { + const globals = await this.getGlobals(await this.defaultOnDiskOptions()); + globals.stateVersion = value; + await this.saveGlobals(globals, await this.defaultOnDiskOptions()); + } + + async getWindow(): Promise> { + const globals = await this.getGlobals(await this.defaultOnDiskOptions()); + return globals?.window != null && Object.keys(globals.window).length > 0 ? + globals.window : + new Map(); + } + + async setWindow(value: Map, options?: StorageOptions): Promise { + const globals = await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())); + globals.window = value; + return await this.saveGlobals(globals, this.reconcileOptions(options, await this.defaultOnDiskOptions())); + } + + private async getGlobals(options: StorageOptions): Promise { + let globals: GlobalState; + if (this.useMemory(options.storageLocation)) { + globals = this.getGlobalsFromMemory(); + } + + if (this.useDisk && globals == null) { + globals = await this.getGlobalsFromDisk(options); + } + + return globals ?? new GlobalState(); + } + + private async saveGlobals(globals: GlobalState, options: StorageOptions) { + return this.useMemory(options.storageLocation) ? + this.saveGlobalsToMemory(globals) : + await this.saveGlobalsToDisk(globals, options); + } + + private getGlobalsFromMemory(): GlobalState { + return this.state.globals; + } + + private async getGlobalsFromDisk(options: StorageOptions): Promise { + return (await this.storageService.get('state', options))?.globals; + } + + private saveGlobalsToMemory(globals: GlobalState): void { + this.state.globals = globals; + } + + private async saveGlobalsToDisk(globals: GlobalState, options: StorageOptions): Promise { + if (options.useSecureStorage) { + const state = await this.secureStorageService.get('state', options) ?? new State(); + state.globals = globals; + await this.secureStorageService.save('state', state, options); + } else { + const state = await this.storageService.get('state', options) ?? new State(); + state.globals = globals; + await this.saveStateToStorage(state, options); + } + } + + private async getAccount(options: StorageOptions): Promise { + try { + let account: Account; + if (this.useMemory(options.storageLocation)) { + account = this.getAccountFromMemory(options); + } + + if (this.useDisk(options.storageLocation) && account == null) { + account = await this.getAccountFromDisk(options); + } + + return account != null ? + new Account(account) : + null; + } + catch (e) { + this.logService.error(e); + } + } + + private getAccountFromMemory(options: StorageOptions): Account { + if (this.state.accounts == null) { + return null; + } + return this.state.accounts[this.getUserIdFromMemory(options)]; + } + + private getUserIdFromMemory(options: StorageOptions): string { + return options?.userId != null ? + this.state.accounts[options.userId]?.profile?.userId : + this.state.activeUserId; + } + + private async getAccountFromDisk(options: StorageOptions): Promise { + if (options?.userId == null && this.state.activeUserId == null) { + return null; + } + + const state = options?.useSecureStorage ? + await this.secureStorageService.get('state', options) ?? + await this.storageService.get('state', this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local })) : + await this.storageService.get('state', options); + + return state?.accounts[options?.userId ?? this.state.activeUserId]; + } + + private useMemory(storageLocation: StorageLocation) { + return storageLocation === StorageLocation.Memory || + storageLocation === StorageLocation.Both; + } + + private useDisk(storageLocation: StorageLocation) { + return storageLocation === StorageLocation.Disk || + storageLocation === StorageLocation.Both; + } + + private async saveAccount(account: Account, options: StorageOptions = { + storageLocation: StorageLocation.Both, + useSecureStorage: false, + }) { + return this.useMemory(options.storageLocation) ? + await this.saveAccountToMemory(account) : + await this.saveAccountToDisk(account, options); + } + + private async saveAccountToDisk(account: Account, options: StorageOptions): Promise { + const storageLocation = options.useSecureStorage ? + this.secureStorageService : + this.storageService; + + const state = await storageLocation.get('state', options) ?? new State(); + state.accounts[account.profile.userId] = account; + + await storageLocation.save('state', state, options); + await this.pushAccounts(); + } + + private async saveAccountToMemory(account: Account): Promise { + if (this.getAccountFromMemory({ userId: account.profile.userId }) !== null) { + this.state.accounts[account.profile.userId] = account; + } + await this.pushAccounts(); + } + + private async scaffoldNewAccountStorage(account: Account): Promise { + await this.scaffoldNewAccountLocalStorage(account); + await this.scaffoldNewAccountSessionStorage(account); + await this.scaffoldNewAccountMemoryStorage(account); + } + + private async scaffoldNewAccountLocalStorage(account: Account): Promise { + const storedState = await this.storageService.get('state', await this.defaultOnDiskLocalOptions()) ?? new State(); + const storedAccount = storedState.accounts[account.profile.userId]; + if (storedAccount != null) { + account = { + settings: storedAccount.settings, + profile: account.profile, + tokens: account.tokens, + keys: account.keys, + data: account.data, + }; + } + storedState.accounts[account.profile.userId] = account; + await this.saveStateToStorage(storedState, await this.defaultOnDiskLocalOptions()); + } + + private async scaffoldNewAccountMemoryStorage(account: Account): Promise { + const storedState = await this.storageService.get('state', await this.defaultOnDiskMemoryOptions()) ?? new State(); + const storedAccount = storedState.accounts[account.profile.userId]; + if (storedAccount != null) { + account = { + settings: storedAccount.settings, + profile: account.profile, + tokens: account.tokens, + keys: account.keys, + data: account.data, + }; + } + storedState.accounts[account.profile.userId] = account; + await this.saveStateToStorage(storedState, await this.defaultOnDiskMemoryOptions()); + } + + private async scaffoldNewAccountSessionStorage(account: Account): Promise { + const storedState = await this.storageService.get('state', await this.defaultOnDiskOptions()) ?? new State(); + const storedAccount = storedState.accounts[account.profile.userId]; + if (storedAccount != null) { + account = { + settings: storedAccount.settings, + profile: account.profile, + tokens: account.tokens, + keys: account.keys, + data: account.data, + }; + } + storedState.accounts[account.profile.userId] = account; + await this.saveStateToStorage(storedState, await this.defaultOnDiskOptions()); + } + + private async pushAccounts(): Promise { + await this.pruneInMemoryAccounts(); + if (this.state?.accounts == null || Object.keys(this.state.accounts).length < 1) { + this.accounts.next(null); + return; + } + + this.accounts.next(this.state.accounts); + } + + private reconcileOptions(requestedOptions: StorageOptions, defaultOptions: StorageOptions): StorageOptions { + if (requestedOptions == null) { + return defaultOptions; + } + requestedOptions.userId = requestedOptions?.userId ?? defaultOptions.userId; + requestedOptions.storageLocation = requestedOptions?.storageLocation ?? defaultOptions.storageLocation; + requestedOptions.useSecureStorage = requestedOptions?.useSecureStorage ?? defaultOptions.useSecureStorage; + requestedOptions.htmlStorageLocation = requestedOptions?.htmlStorageLocation ?? defaultOptions.htmlStorageLocation; + requestedOptions.keySuffix = requestedOptions?.keySuffix ?? defaultOptions.keySuffix; + return requestedOptions; + } + + private get defaultInMemoryOptions(): StorageOptions { + return { storageLocation: StorageLocation.Memory, userId: this.state.activeUserId }; + } + + private async defaultOnDiskOptions(): Promise { + return { + storageLocation: StorageLocation.Disk, + htmlStorageLocation: HtmlStorageLocation.Session, + userId: await this.getActiveUserIdFromStorage(), + useSecureStorage: false, + }; + } + + private async defaultOnDiskLocalOptions(): Promise { + return { + storageLocation: StorageLocation.Disk, + htmlStorageLocation: HtmlStorageLocation.Local, + userId: await this.getActiveUserIdFromStorage(), + useSecureStorage: false, + }; + } + + private async defaultOnDiskMemoryOptions(): Promise { + return { + storageLocation: StorageLocation.Disk, + htmlStorageLocation: HtmlStorageLocation.Memory, + userId: await this.getUserId(), + useSecureStorage: false, + }; + } + + private async defaultSecureStorageOptions(): Promise { + return { + storageLocation: StorageLocation.Disk, + useSecureStorage: true, + userId: await this.getActiveUserIdFromStorage(), + }; + } + + private async getActiveUserIdFromStorage(): Promise { + const state = await this.storageService.get('state'); + return state?.activeUserId; + } + + private async removeAccountFromLocalStorage(userId: string = this.state.activeUserId): Promise { + const state = await this.storageService.get('state', { htmlStorageLocation: HtmlStorageLocation.Local }); + if (state?.accounts[userId] == null) { + return; + } + + state.accounts[userId] = new Account({ + settings: state.accounts[userId].settings, + }); + + await this.saveStateToStorage(state, await this.defaultOnDiskLocalOptions()); + } + + private async removeAccountFromSessionStorage(userId: string = this.state.activeUserId): Promise { + const state = await this.storageService.get('state', { htmlStorageLocation: HtmlStorageLocation.Session }); + if (state?.accounts[userId] == null) { + return; + } + + state.accounts[userId] = new Account({ + settings: state.accounts[userId].settings, + }); + + await this.saveStateToStorage(state, await this.defaultOnDiskOptions()); + } + + private async removeAccountFromSecureStorage(userId: string = this.state.activeUserId): Promise { + await this.setCryptoMasterKeyAuto(null, { userId: userId }); + await this.setCryptoMasterKeyBiometric(null, { userId: userId }); + await this.setCryptoMasterKeyB64(null, { userId: userId }); + } + + private removeAccountFromMemory(userId: string = this.state.activeUserId): void { + delete this.state.accounts[userId]; + } + + private async saveStateToStorage(state: State, options: StorageOptions): Promise { + await this.storageService.save('state', state, options); + } + + private async pruneInMemoryAccounts() { + // We preserve settings for logged out accounts, but we don't want to consider them when thinking about active account state + for (const userId in this.state.accounts) { + if (!await this.getIsAuthenticated({ userId: userId })) { + delete this.state.accounts[userId]; + } + } } } diff --git a/common/src/services/stateMigration.service.ts b/common/src/services/stateMigration.service.ts new file mode 100644 index 0000000000..4b6ceef3ab --- /dev/null +++ b/common/src/services/stateMigration.service.ts @@ -0,0 +1,335 @@ +import { StorageService } from '../abstractions/storage.service'; + +import { Account } from '../models/domain/account'; +import { GeneratedPasswordHistory } from '../models/domain/generatedPasswordHistory'; +import { State } from '../models/domain/state'; +import { StorageOptions } from '../models/domain/storageOptions'; + +import { CipherData } from '../models/data/cipherData'; +import { CollectionData } from '../models/data/collectionData'; +import { EventData } from '../models/data/eventData'; +import { FolderData } from '../models/data/folderData'; +import { OrganizationData } from '../models/data/organizationData'; +import { PolicyData } from '../models/data/policyData'; +import { ProviderData } from '../models/data/providerData'; +import { SendData } from '../models/data/sendData'; + +import { HtmlStorageLocation } from '../enums/htmlStorageLocation'; +import { KdfType } from '../enums/kdfType'; + +// Originally (before January 2022) storage was handled as a flat key/value pair store. +// With the move to a typed object for state storage these keys should no longer be in use anywhere outside of this migration. +const v1Keys = { + accessToken: 'accessToken', + alwaysShowDock: 'alwaysShowDock', + autoConfirmFingerprints: 'autoConfirmFingerprints', + autoFillOnPageLoadDefault: 'autoFillOnPageLoadDefault', + biometricAwaitingAcceptance: 'biometricAwaitingAcceptance', + biometricFingerprintValidated: 'biometricFingerprintValidated', + biometricText: 'biometricText', + biometricUnlock: 'biometric', + clearClipboard: 'clearClipboardKey', + clientId: 'clientId', + clientSecret: 'clientSecret', + collapsedGroupings: 'collapsedGroupings', + convertAccountToKeyConnector: 'convertAccountToKeyConnector', + defaultUriMatch: 'defaultUriMatch', + disableAddLoginNotification: 'disableAddLoginNotification', + disableAutoBiometricsPrompt: 'noAutoPromptBiometrics', + disableAutoTotpCopy: 'disableAutoTotpCopy', + disableBadgeCounter: 'disableBadgeCounter', + disableChangedPasswordNotification: 'disableChangedPasswordNotification', + disableContextMenuItem: 'disableContextMenuItem', + disableFavicon: 'disableFavicon', + disableGa: 'disableGa', + dontShowCardsCurrentTab: 'dontShowCardsCurrentTab', + dontShowIdentitiesCurrentTab: 'dontShowIdentitiesCurrentTab', + emailVerified: 'emailVerified', + enableAlwaysOnTop: 'enableAlwaysOnTopKey', + enableAutoFillOnPageLoad: 'enableAutoFillOnPageLoad', + enableBiometric: 'enabledBiometric', + enableBrowserIntegration: 'enableBrowserIntegration', + enableBrowserIntegrationFingerprint: 'enableBrowserIntegrationFingerprint', + enableCloseToTray: 'enableCloseToTray', + enableFullWidth: 'enableFullWidth', + enableGravatars: 'enableGravatars', + enableMinimizeToTray: 'enableMinimizeToTray', + enableStartToTray: 'enableStartToTrayKey', + enableTray: 'enableTray', + encKey: 'encKey', // Generated Symmetric Key + encOrgKeys: 'encOrgKeys', + encPrivate: 'encPrivateKey', + encProviderKeys: 'encProviderKeys', + entityId: 'entityId', + entityType: 'entityType', + environmentUrls: 'environmentUrls', + equivalentDomains: 'equivalentDomains', + eventCollection: 'eventCollection', + forcePasswordReset: 'forcePasswordReset', + history: 'generatedPasswordHistory', + installedVersion: 'installedVersion', + kdf: 'kdf', + kdfIterations: 'kdfIterations', + key: 'key', // Master Key + keyHash: 'keyHash', + lastActive: 'lastActive', + localData: 'sitesLocalData', + locale: 'locale', + mainWindowSize: 'mainWindowSize', + minimizeOnCopyToClipboard: 'minimizeOnCopyToClipboardKey', + neverDomains: 'neverDomains', + noAutoPromptBiometricsText: 'noAutoPromptBiometricsText', + openAtLogin: 'openAtLogin', + passwordGenerationOptions: 'passwordGenerationOptions', + pinProtected: 'pinProtectedKey', + protectedPin: 'protectedPin', + refreshToken: 'refreshToken', + ssoCodeVerifier: 'ssoCodeVerifier', + ssoIdentifier: 'ssoOrgIdentifier', + ssoState: 'ssoState', + stamp: 'securityStamp', + theme: 'theme', + userEmail: 'userEmail', + userId: 'userId', + usesConnector: 'usesKeyConnector', + vaultTimeoutAction: 'vaultTimeoutAction', + vaultTimeout: 'lockOption', + rememberedEmail: 'rememberedEmail', +}; + +const v1KeyPrefixes = { + ciphers: 'ciphers_', + collections: 'collections_', + folders: 'folders_', + lastSync: 'lastSync_', + policies: 'policies_', + twoFactorToken: 'twoFactorToken_', + organizations: 'organizations_', + providers: 'providers_', + sends: 'sends_', + settings: 'settings_', +}; + + +export class StateMigrationService { + readonly latestVersion: number = 2; + + constructor( + private storageService: StorageService, + private secureStorageService: StorageService + ) {} + + async needsMigration(): Promise { + const currentStateVersion = (await this.storageService.get('state'))?.globals?.stateVersion; + return currentStateVersion == null || currentStateVersion < this.latestVersion; + } + + async migrate(): Promise { + let currentStateVersion = (await this.storageService.get('state'))?.globals?.stateVersion ?? 1; + while (currentStateVersion < this.latestVersion) { + switch (currentStateVersion) { + case 1: + await this.migrateStateFrom1To2(); + break; + } + + currentStateVersion += 1; + } + } + + private async migrateStateFrom1To2(): Promise { + const options: StorageOptions = { htmlStorageLocation: HtmlStorageLocation.Local }; + const userId = await this.storageService.get('userId'); + const initialState: State = userId == null ? + { + globals: { + stateVersion: 2, + }, + accounts: {}, + activeUserId: null, + } : + { + activeUserId: userId, + globals: { + biometricAwaitingAcceptance: await this.storageService.get(v1Keys.biometricAwaitingAcceptance, options), + biometricFingerprintValidated: await this.storageService.get(v1Keys.biometricFingerprintValidated, options), + biometricText: await this.storageService.get(v1Keys.biometricText, options), + disableFavicon: await this.storageService.get(v1Keys.disableFavicon, options), + enableAlwaysOnTop: await this.storageService.get(v1Keys.enableAlwaysOnTop, options), + enableBiometrics: await this.storageService.get(v1Keys.enableBiometric, options), + installedVersion: await this.storageService.get(v1Keys.installedVersion, options), + lastActive: await this.storageService.get(v1Keys.lastActive, options), + locale: await this.storageService.get(v1Keys.locale, options), + loginRedirect: null, + mainWindowSize: null, + noAutoPromptBiometrics: await this.storageService.get(v1Keys.disableAutoBiometricsPrompt, options), + noAutoPromptBiometricsText: await this.storageService.get(v1Keys.noAutoPromptBiometricsText, options), + openAtLogin: await this.storageService.get(v1Keys.openAtLogin, options), + organizationInvitation: await this.storageService.get('', options), + rememberedEmail: await this.storageService.get(v1Keys.rememberedEmail, options), + stateVersion: 2, + theme: await this.storageService.get(v1Keys.theme, options), + twoFactorToken: await this.storageService.get(v1KeyPrefixes.twoFactorToken + userId, options), + vaultTimeout: await this.storageService.get(v1Keys.vaultTimeout, options), + vaultTimeoutAction: await this.storageService.get(v1Keys.vaultTimeoutAction, options), + window: null, + }, + accounts: { + [userId]: new Account({ + data: { + addEditCipherInfo: null, + ciphers: { + decrypted: null, + encrypted: await this.storageService.get<{ [id: string]: CipherData }>(v1KeyPrefixes.ciphers + userId, options), + }, + collapsedGroupings: null, + collections: { + decrypted: null, + encrypted: await this.storageService.get<{ [id: string]: CollectionData }>(v1KeyPrefixes.collections + userId, options), + }, + eventCollection: await this.storageService.get(v1Keys.eventCollection, options), + folders: { + decrypted: null, + encrypted: await this.storageService.get<{ [id: string]: FolderData }>(v1KeyPrefixes.folders + userId, options), + }, + localData: null, + organizations: await this.storageService.get<{ [id: string]: OrganizationData }>(v1KeyPrefixes.organizations + userId), + passwordGenerationHistory: { + decrypted: null, + encrypted: await this.storageService.get('TODO', options), // TODO: Whats up here? + }, + policies: { + decrypted: null, + encrypted: await this.storageService.get<{ [id: string]: PolicyData }>(v1KeyPrefixes.policies + userId, options), + }, + providers: await this.storageService.get<{ [id: string]: ProviderData }>(v1KeyPrefixes.providers + userId), + sends: { + decrypted: null, + encrypted: await this.storageService.get<{ [id: string]: SendData }>(v1KeyPrefixes.sends, options), + }, + }, + keys: { + apiKeyClientSecret: await this.storageService.get(v1Keys.clientSecret, options), + cryptoMasterKey: null, + cryptoMasterKeyAuto: null, + cryptoMasterKeyB64: null, + cryptoMasterKeyBiometric: null, + cryptoSymmetricKey: { + encrypted: await this.storageService.get(v1Keys.encKey, options), + decrypted: null, + }, + legacyEtmKey: null, + organizationKeys: { + decrypted: null, + encrypted: await this.storageService.get(v1Keys.encOrgKeys + userId, options), + }, + privateKey: { + decrypted: null, + encrypted: await this.storageService.get(v1Keys.encPrivate, options), + }, + providerKeys: { + decrypted: null, + encrypted: await this.storageService.get(v1Keys.encProviderKeys + userId, options), + }, + publicKey: null, + }, + profile: { + apiKeyClientId: await this.storageService.get(v1Keys.clientId, options), + authenticationStatus: null, + convertAccountToKeyConnector: await this.storageService.get(v1Keys.convertAccountToKeyConnector, options), + email: await this.storageService.get(v1Keys.userEmail, options), + emailVerified: await this.storageService.get(v1Keys.emailVerified, options), + entityId: null, + entityType: null, + everBeenUnlocked: null, + forcePasswordReset: null, + hasPremiumPersonally: null, + kdfIterations: await this.storageService.get(v1Keys.kdfIterations, options), + kdfType: await this.storageService.get(v1Keys.kdf, options), + keyHash: await this.storageService.get(v1Keys.keyHash, options), + lastActive: await this.storageService.get(v1Keys.lastActive, options), + lastSync: null, + ssoCodeVerifier: await this.storageService.get(v1Keys.ssoCodeVerifier, options), + ssoOrganizationIdentifier: await this.storageService.get(v1Keys.ssoIdentifier, options), + ssoState: null, + userId: userId, + usesKeyConnector: null, + }, + settings: { + alwaysShowDock: await this.storageService.get(v1Keys.alwaysShowDock, options), + autoConfirmFingerPrints: await this.storageService.get(v1Keys.autoConfirmFingerprints, options), + autoFillOnPageLoadDefault: await this.storageService.get(v1Keys.autoFillOnPageLoadDefault, options), + biometricLocked: null, + biometricUnlock: await this.storageService.get(v1Keys.biometricUnlock, options), + clearClipboard: await this.storageService.get(v1Keys.clearClipboard, options), + defaultUriMatch: await this.storageService.get(v1Keys.defaultUriMatch, options), + disableAddLoginNotification: await this.storageService.get(v1Keys.disableAddLoginNotification, options), + disableAutoBiometricsPrompt: await this.storageService.get(v1Keys.disableAutoBiometricsPrompt, options), + disableAutoTotpCopy: await this.storageService.get(v1Keys.disableAutoTotpCopy, options), + disableBadgeCounter: await this.storageService.get(v1Keys.disableBadgeCounter, options), + disableChangedPasswordNotification: await this.storageService.get(v1Keys.disableChangedPasswordNotification, options), + disableContextMenuItem: await this.storageService.get(v1Keys.disableContextMenuItem, options), + disableGa: await this.storageService.get(v1Keys.disableGa, options), + dontShowCardsCurrentTab: await this.storageService.get(v1Keys.dontShowCardsCurrentTab, options), + dontShowIdentitiesCurrentTab: await this.storageService.get(v1Keys.dontShowIdentitiesCurrentTab, options), + enableAlwaysOnTop: await this.storageService.get(v1Keys.enableAlwaysOnTop, options), + enableAutoFillOnPageLoad: await this.storageService.get(v1Keys.enableAutoFillOnPageLoad, options), + enableBiometric: await this.storageService.get(v1Keys.enableBiometric, options), + enableBrowserIntegration: await this.storageService.get(v1Keys.enableBrowserIntegration, options), + enableBrowserIntegrationFingerprint: await this.storageService.get(v1Keys.enableBrowserIntegrationFingerprint, options), + enableCloseToTray: await this.storageService.get(v1Keys.enableCloseToTray, options), + enableFullWidth: await this.storageService.get(v1Keys.enableFullWidth, options), + enableGravitars: await this.storageService.get(v1Keys.enableGravatars, options), + enableMinimizeToTray: await this.storageService.get(v1Keys.enableMinimizeToTray, options), + enableStartToTray: await this.storageService.get(v1Keys.enableStartToTray, options), + enableTray: await this.storageService.get(v1Keys.enableTray, options), + environmentUrls: await this.storageService.get(v1Keys.environmentUrls, options), + equivalentDomains: await this.storageService.get(v1Keys.equivalentDomains, options), + minimizeOnCopyToClipboard: await this.storageService.get(v1Keys.minimizeOnCopyToClipboard, options), + neverDomains: await this.storageService.get(v1Keys.neverDomains, options), + openAtLogin: await this.storageService.get(v1Keys.openAtLogin, options), + passwordGenerationOptions: await this.storageService.get(v1Keys.passwordGenerationOptions, options), + pinProtected: { + decrypted: null, + encrypted: await this.storageService.get(v1Keys.pinProtected, options), + }, + protectedPin: await this.storageService.get(v1Keys.protectedPin, options), + settings: await this.storageService.get(v1KeyPrefixes.settings + userId, options), + vaultTimeout: await this.storageService.get(v1Keys.vaultTimeout, options), + vaultTimeoutAction: await this.storageService.get(v1Keys.vaultTimeoutAction, options), + }, + tokens: { + accessToken: await this.storageService.get(v1Keys.accessToken, options), + decodedToken: null, + refreshToken: await this.storageService.get(v1Keys.refreshToken, options), + securityStamp: null, + }, + }), + }, + }; + + await this.storageService.save('state', initialState, options); + + if (await this.secureStorageService.has(v1Keys.key, { keySuffix: 'biometric' })) { + await this.secureStorageService.save( + `${userId}_masterkey_biometric`, + await this.secureStorageService.get(v1Keys.key, { keySuffix: 'biometric' }), + { keySuffix: 'biometric' }); + await this.secureStorageService.remove(v1Keys.key, { keySuffix: 'biometric' }); + } + + if (await this.secureStorageService.has(v1Keys.key, { keySuffix: 'auto' })) { + await this.secureStorageService.save( + `${userId}_masterkey_auto`, + await this.secureStorageService.get(v1Keys.key, { keySuffix: 'auto' }), + { keySuffix: 'auto' } + ); + await this.secureStorageService.remove(v1Keys.key, { keySuffix: 'auto' }); + } + + if (await this.secureStorageService.has(v1Keys.key)) { + await this.secureStorageService.save(`${userId}_masterkey`, await this.secureStorageService.get(v1Keys.key)); + await this.secureStorageService.remove(v1Keys.key); + } + } +} diff --git a/common/src/services/sync.service.ts b/common/src/services/sync.service.ts index 8395ddcc87..9ecfc74db4 100644 --- a/common/src/services/sync.service.ts +++ b/common/src/services/sync.service.ts @@ -6,13 +6,13 @@ import { FolderService } from '../abstractions/folder.service'; import { KeyConnectorService } from '../abstractions/keyConnector.service'; import { LogService } from '../abstractions/log.service'; import { MessagingService } from '../abstractions/messaging.service'; +import { OrganizationService } from '../abstractions/organization.service'; import { PolicyService } from '../abstractions/policy.service'; +import { ProviderService } from '../abstractions/provider.service'; import { SendService } from '../abstractions/send.service'; import { SettingsService } from '../abstractions/settings.service'; -import { StorageService } from '../abstractions/storage.service'; +import { StateService } from '../abstractions/state.service'; import { SyncService as SyncServiceAbstraction } from '../abstractions/sync.service'; -import { TokenService } from '../abstractions/token.service'; -import { UserService } from '../abstractions/user.service'; import { CipherData } from '../models/data/cipherData'; import { CollectionData } from '../models/data/collectionData'; @@ -35,30 +35,24 @@ import { PolicyResponse } from '../models/response/policyResponse'; import { ProfileResponse } from '../models/response/profileResponse'; import { SendResponse } from '../models/response/sendResponse'; -const Keys = { - lastSyncPrefix: 'lastSync_', -}; - export class SyncService implements SyncServiceAbstraction { syncInProgress: boolean = false; - constructor(private userService: UserService, private apiService: ApiService, - private settingsService: SettingsService, private folderService: FolderService, - private cipherService: CipherService, private cryptoService: CryptoService, - private collectionService: CollectionService, private storageService: StorageService, - private messagingService: MessagingService, private policyService: PolicyService, + constructor(private apiService: ApiService, private settingsService: SettingsService, + private folderService: FolderService, private cipherService: CipherService, + private cryptoService: CryptoService, private collectionService: CollectionService, + private messagingService: MessagingService, private policyService: PolicyService, private sendService: SendService, private logService: LogService, - private tokenService: TokenService, private keyConnectorService: KeyConnectorService, - private logoutCallback: (expired: boolean) => Promise) { - } + private keyConnectorService: KeyConnectorService, private stateService: StateService, + private organizationService: OrganizationService, private providerService: ProviderService, + private logoutCallback: (expired: boolean) => Promise) { } async getLastSync(): Promise { - const userId = await this.userService.getUserId(); - if (userId == null) { + if (await this.stateService.getUserId() == null) { return null; } - const lastSync = await this.storageService.get(Keys.lastSyncPrefix + userId); + const lastSync = await this.stateService.getLastSync(); if (lastSync) { return new Date(lastSync); } @@ -66,18 +60,13 @@ export class SyncService implements SyncServiceAbstraction { return null; } - async setLastSync(date: Date): Promise { - const userId = await this.userService.getUserId(); - if (userId == null) { - return; - } - - await this.storageService.save(Keys.lastSyncPrefix + userId, date.toJSON()); + async setLastSync(date: Date, userId?: string): Promise { + await this.stateService.setLastSync(date.toJSON(), { userId: userId }); } async fullSync(forceSync: boolean, allowThrowOnError = false): Promise { this.syncStarted(); - const isAuthenticated = await this.userService.isAuthenticated(); + const isAuthenticated = await this.stateService.getIsAuthenticated(); if (!isAuthenticated) { return this.syncCompleted(false); } @@ -97,7 +86,7 @@ export class SyncService implements SyncServiceAbstraction { return this.syncCompleted(false); } - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); try { await this.apiService.refreshIdentityToken(); const response = await this.apiService.getSync(); @@ -107,7 +96,7 @@ export class SyncService implements SyncServiceAbstraction { await this.syncCollections(response.collections); await this.syncCiphers(userId, response.ciphers); await this.syncSends(userId, response.sends); - await this.syncSettings(userId, response.domains); + await this.syncSettings(response.domains); await this.syncPolicies(response.policies); await this.setLastSync(now); @@ -123,14 +112,14 @@ export class SyncService implements SyncServiceAbstraction { async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise { this.syncStarted(); - if (await this.userService.isAuthenticated()) { + if (await this.stateService.getIsAuthenticated()) { try { const localFolder = await this.folderService.get(notification.id); if ((!isEdit && localFolder == null) || (isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate)) { const remoteFolder = await this.apiService.getFolder(notification.id); if (remoteFolder != null) { - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); await this.folderService.upsert(new FolderData(remoteFolder, userId)); this.messagingService.send('syncedUpsertedFolder', { folderId: notification.id }); return this.syncCompleted(true); @@ -145,7 +134,7 @@ export class SyncService implements SyncServiceAbstraction { async syncDeleteFolder(notification: SyncFolderNotification): Promise { this.syncStarted(); - if (await this.userService.isAuthenticated()) { + if (await this.stateService.getIsAuthenticated()) { await this.folderService.delete(notification.id); this.messagingService.send('syncedDeletedFolder', { folderId: notification.id }); this.syncCompleted(true); @@ -156,7 +145,7 @@ export class SyncService implements SyncServiceAbstraction { async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise { this.syncStarted(); - if (await this.userService.isAuthenticated()) { + if (await this.stateService.getIsAuthenticated()) { try { let shouldUpdate = true; const localCipher = await this.cipherService.get(notification.id); @@ -195,7 +184,7 @@ export class SyncService implements SyncServiceAbstraction { if (shouldUpdate) { const remoteCipher = await this.apiService.getCipher(notification.id); if (remoteCipher != null) { - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); await this.cipherService.upsert(new CipherData(remoteCipher, userId)); this.messagingService.send('syncedUpsertedCipher', { cipherId: notification.id }); return this.syncCompleted(true); @@ -214,7 +203,7 @@ export class SyncService implements SyncServiceAbstraction { async syncDeleteCipher(notification: SyncCipherNotification): Promise { this.syncStarted(); - if (await this.userService.isAuthenticated()) { + if (await this.stateService.getIsAuthenticated()) { await this.cipherService.delete(notification.id); this.messagingService.send('syncedDeletedCipher', { cipherId: notification.id }); return this.syncCompleted(true); @@ -224,14 +213,14 @@ export class SyncService implements SyncServiceAbstraction { async syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise { this.syncStarted(); - if (await this.userService.isAuthenticated()) { + if (await this.stateService.getIsAuthenticated()) { try { const localSend = await this.sendService.get(notification.id); if ((!isEdit && localSend == null) || (isEdit && localSend != null && localSend.revisionDate < notification.revisionDate)) { const remoteSend = await this.apiService.getSend(notification.id); if (remoteSend != null) { - const userId = await this.userService.getUserId(); + const userId = await this.stateService.getUserId(); await this.sendService.upsert(new SendData(remoteSend, userId)); this.messagingService.send('syncedUpsertedSend', { sendId: notification.id }); return this.syncCompleted(true); @@ -246,7 +235,7 @@ export class SyncService implements SyncServiceAbstraction { async syncDeleteSend(notification: SyncSendNotification): Promise { this.syncStarted(); - if (await this.userService.isAuthenticated()) { + if (await this.stateService.getIsAuthenticated()) { await this.sendService.delete(notification.id); this.messagingService.send('syncedDeletedSend', { sendId: notification.id }); this.syncCompleted(true); @@ -286,7 +275,7 @@ export class SyncService implements SyncServiceAbstraction { } private async syncProfile(response: ProfileResponse) { - const stamp = await this.userService.getSecurityStamp(); + const stamp = await this.stateService.getSecurityStamp(); if (stamp != null && stamp !== response.securityStamp) { if (this.logoutCallback != null) { await this.logoutCallback(true); @@ -299,9 +288,9 @@ export class SyncService implements SyncServiceAbstraction { await this.cryptoService.setEncPrivateKey(response.privateKey); await this.cryptoService.setProviderKeys(response.providers); await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); - await this.userService.setSecurityStamp(response.securityStamp); - await this.userService.setEmailVerified(response.emailVerified); - await this.userService.setForcePasswordReset(response.forcePasswordReset); + await this.stateService.setSecurityStamp(response.securityStamp); + await this.stateService.setEmailVerified(response.emailVerified); + await this.stateService.setForcePasswordReset(response.forcePasswordReset); await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector); const organizations: { [id: string]: OrganizationData; } = {}; @@ -322,8 +311,8 @@ export class SyncService implements SyncServiceAbstraction { }); await Promise.all([ - this.userService.replaceOrganizations(organizations), - this.userService.replaceProviders(providers), + this.organizationService.save(organizations), + this.providerService.save(providers), ]); if (await this.keyConnectorService.userNeedsMigration()) { @@ -365,7 +354,7 @@ export class SyncService implements SyncServiceAbstraction { return await this.sendService.replace(sends); } - private async syncSettings(userId: string, response: DomainsResponse) { + private async syncSettings(response: DomainsResponse) { let eqDomains: string[][] = []; if (response != null && response.equivalentDomains != null) { eqDomains = eqDomains.concat(response.equivalentDomains); diff --git a/common/src/services/system.service.ts b/common/src/services/system.service.ts index ada918132a..afcb7f8b4f 100644 --- a/common/src/services/system.service.ts +++ b/common/src/services/system.service.ts @@ -1,10 +1,7 @@ import { MessagingService } from '../abstractions/messaging.service'; import { PlatformUtilsService } from '../abstractions/platformUtils.service'; -import { StorageService } from '../abstractions/storage.service'; +import { StateService } from '../abstractions/state.service'; import { SystemService as SystemServiceAbstraction } from '../abstractions/system.service'; -import { VaultTimeoutService } from '../abstractions/vaultTimeout.service'; - -import { ConstantsService } from './constants.service'; import { Utils } from '../misc/utils'; @@ -13,28 +10,27 @@ export class SystemService implements SystemServiceAbstraction { private clearClipboardTimeout: any = null; private clearClipboardTimeoutFunction: () => Promise = null; - constructor(private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService, - private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService, - private reloadCallback: () => Promise = null) { + constructor(private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService, + private reloadCallback: () => Promise = null, private stateService: StateService) { } - startProcessReload(): void { - if (this.vaultTimeoutService.pinProtectedKey != null || - this.vaultTimeoutService.biometricLocked || + async startProcessReload(): Promise { + if (await this.stateService.getDecryptedPinProtected() != null || + await this.stateService.getBiometricLocked() || this.reloadInterval != null) { return; } this.cancelProcessReload(); this.reloadInterval = setInterval(async () => { let doRefresh = false; - const lastActive = await this.storageService.get(ConstantsService.lastActiveKey); + const lastActive = await this.stateService.getLastActive(); if (lastActive != null) { const diffSeconds = (new Date()).getTime() - lastActive; // Don't refresh if they are still active in the window doRefresh = diffSeconds >= 5000; } const biometricLockedFingerprintValidated = - await this.storageService.get(ConstantsService.biometricFingerprintValidated) && this.vaultTimeoutService.biometricLocked; + await this.stateService.getBiometricFingerprintValidated() && await this.stateService.getBiometricLocked(); if (doRefresh && !biometricLockedFingerprintValidated) { clearInterval(this.reloadInterval); this.reloadInterval = null; @@ -53,7 +49,7 @@ export class SystemService implements SystemServiceAbstraction { } } - clearClipboard(clipboardValue: string, timeoutMs: number = null): void { + async clearClipboard(clipboardValue: string, timeoutMs: number = null): Promise { if (this.clearClipboardTimeout != null) { clearTimeout(this.clearClipboardTimeout); this.clearClipboardTimeout = null; @@ -61,7 +57,7 @@ export class SystemService implements SystemServiceAbstraction { if (Utils.isNullOrWhitespace(clipboardValue)) { return; } - this.storageService.get(ConstantsService.clearClipboardKey).then(clearSeconds => { + await this.stateService.getClearClipboard().then(clearSeconds => { if (clearSeconds == null) { return; } diff --git a/common/src/services/token.service.ts b/common/src/services/token.service.ts index 58f42e7d4a..c541c030a0 100644 --- a/common/src/services/token.service.ts +++ b/common/src/services/token.service.ts @@ -1,26 +1,10 @@ -import { ConstantsService } from './constants.service'; - -import { StorageService } from '../abstractions/storage.service'; +import { StateService } from '../abstractions/state.service'; import { TokenService as TokenServiceAbstraction } from '../abstractions/token.service'; import { Utils } from '../misc/utils'; -const Keys = { - accessToken: 'accessToken', - refreshToken: 'refreshToken', - twoFactorTokenPrefix: 'twoFactorToken_', - clientId: 'apikey_clientId', - clientSecret: 'apikey_clientSecret', -}; - export class TokenService implements TokenServiceAbstraction { - token: string; - decodedToken: any; - refreshToken: string; - clientId: string; - clientSecret: string; - - constructor(private storageService: StorageService) { + constructor(private stateService: StateService) { } async setTokens(accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]): Promise { @@ -33,60 +17,44 @@ export class TokenService implements TokenServiceAbstraction { } async setClientId(clientId: string): Promise { - this.clientId = clientId; - return this.storeTokenValue(Keys.clientId, clientId); + if (await this.skipTokenStorage() || clientId == null) { + return; + } + return await this.stateService.setApiKeyClientId(clientId); } async getClientId(): Promise { - if (this.clientId != null) { - return this.clientId; - } - - this.clientId = await this.storageService.get(Keys.clientId); - return this.clientId; + return await this.stateService.getApiKeyClientId(); } async setClientSecret(clientSecret: string): Promise { - this.clientSecret = clientSecret; - return this.storeTokenValue(Keys.clientSecret, clientSecret); + if (await this.skipTokenStorage() || clientSecret == null) { + return; + } + return await this.stateService.setApiKeyClientSecret(clientSecret); } async getClientSecret(): Promise { - if (this.clientSecret != null) { - return this.clientSecret; - } - - this.clientSecret = await this.storageService.get(Keys.clientSecret); - return this.clientSecret; + return await this.stateService.getApiKeyClientSecret(); } - async setToken(token: string): Promise { - this.token = token; - this.decodedToken = null; - return this.storeTokenValue(Keys.accessToken, token); + async setToken(token: string): Promise { + await this.stateService.setAccessToken(token); } async getToken(): Promise { - if (this.token != null) { - return this.token; - } - - this.token = await this.storageService.get(Keys.accessToken); - return this.token; + return await this.stateService.getAccessToken(); } async setRefreshToken(refreshToken: string): Promise { - this.refreshToken = refreshToken; - return this.storeTokenValue(Keys.refreshToken, refreshToken); + if (await this.skipTokenStorage()) { + return; + } + return await this.stateService.setRefreshToken(refreshToken); } async getRefreshToken(): Promise { - if (this.refreshToken != null) { - return this.refreshToken; - } - - this.refreshToken = await this.storageService.get(Keys.refreshToken); - return this.refreshToken; + return await this.stateService.getRefreshToken(); } async toggleTokens(): Promise { @@ -94,16 +62,12 @@ export class TokenService implements TokenServiceAbstraction { const refreshToken = await this.getRefreshToken(); const clientId = await this.getClientId(); const clientSecret = await this.getClientSecret(); - const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); - const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); + const timeout = await this.stateService.getVaultTimeout(); + const action = await this.stateService.getVaultTimeoutAction(); + if ((timeout != null || timeout === 0) && action === 'logOut') { // if we have a vault timeout and the action is log out, reset tokens await this.clearToken(); - this.token = token; - this.refreshToken = refreshToken; - this.clientId = clientId; - this.clientSecret = clientSecret; - return; } await this.setToken(token); @@ -112,44 +76,41 @@ export class TokenService implements TokenServiceAbstraction { await this.setClientSecret(clientSecret); } - setTwoFactorToken(token: string, email: string): Promise { - return this.storageService.save(Keys.twoFactorTokenPrefix + email, token); + async setTwoFactorToken(token: string): Promise { + return await this.stateService.setTwoFactorToken(token); } - getTwoFactorToken(email: string): Promise { - return this.storageService.get(Keys.twoFactorTokenPrefix + email); + async getTwoFactorToken(): Promise { + return await this.stateService.getTwoFactorToken(); } - clearTwoFactorToken(email: string): Promise { - return this.storageService.remove(Keys.twoFactorTokenPrefix + email); + async clearTwoFactorToken(): Promise { + return await this.stateService.setTwoFactorToken(null); } - async clearToken(): Promise { - this.token = null; - this.decodedToken = null; - this.refreshToken = null; - this.clientId = null; - this.clientSecret = null; - - await this.storageService.remove(Keys.accessToken); - await this.storageService.remove(Keys.refreshToken); - await this.storageService.remove(Keys.clientId); - await this.storageService.remove(Keys.clientSecret); + async clearToken(userId?: string): Promise { + await this.stateService.setAccessToken(null, { userId: userId }); + await this.stateService.setRefreshToken(null, { userId: userId }); + await this.stateService.setApiKeyClientId(null, { userId: userId }); + await this.stateService.setApiKeyClientSecret(null, { userId: userId }); } // jwthelper methods // ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js - decodeToken(): any { - if (this.decodedToken) { - return this.decodedToken; + async decodeToken(token?: string): Promise { + const storedToken = await this.stateService.getDecodedToken(); + if (token === null && storedToken != null) { + return storedToken; } - if (this.token == null) { + token = token ?? await this.stateService.getAccessToken(); + + if (token == null) { throw new Error('Token not found.'); } - const parts = this.token.split('.'); + const parts = token.split('.'); if (parts.length !== 3) { throw new Error('JWT must have 3 parts'); } @@ -159,12 +120,12 @@ export class TokenService implements TokenServiceAbstraction { throw new Error('Cannot decode the token'); } - this.decodedToken = JSON.parse(decoded); - return this.decodedToken; + const decodedToken = JSON.parse(decoded); + return decodedToken; } - getTokenExpirationDate(): Date { - const decoded = this.decodeToken(); + async getTokenExpirationDate(): Promise { + const decoded = await this.decodeToken(); if (typeof decoded.exp === 'undefined') { return null; } @@ -174,8 +135,8 @@ export class TokenService implements TokenServiceAbstraction { return d; } - tokenSecondsRemaining(offsetSeconds: number = 0): number { - const d = this.getTokenExpirationDate(); + async tokenSecondsRemaining(offsetSeconds: number = 0): Promise { + const d = await this.getTokenExpirationDate(); if (d == null) { return 0; } @@ -184,13 +145,13 @@ export class TokenService implements TokenServiceAbstraction { return Math.round(msRemaining / 1000); } - tokenNeedsRefresh(minutes: number = 5): boolean { - const sRemaining = this.tokenSecondsRemaining(); + async tokenNeedsRefresh(minutes: number = 5): Promise { + const sRemaining = await this.tokenSecondsRemaining(); return sRemaining < (60 * minutes); } - getUserId(): string { - const decoded = this.decodeToken(); + async getUserId(): Promise { + const decoded = await this.decodeToken(); if (typeof decoded.sub === 'undefined') { throw new Error('No user id found'); } @@ -198,8 +159,8 @@ export class TokenService implements TokenServiceAbstraction { return decoded.sub as string; } - getEmail(): string { - const decoded = this.decodeToken(); + async getEmail(): Promise { + const decoded = await this.decodeToken(); if (typeof decoded.email === 'undefined') { throw new Error('No email found'); } @@ -207,8 +168,8 @@ export class TokenService implements TokenServiceAbstraction { return decoded.email as string; } - getEmailVerified(): boolean { - const decoded = this.decodeToken(); + async getEmailVerified(): Promise { + const decoded = await this.decodeToken(); if (typeof decoded.email_verified === 'undefined') { throw new Error('No email verification found'); } @@ -216,8 +177,8 @@ export class TokenService implements TokenServiceAbstraction { return decoded.email_verified as boolean; } - getName(): string { - const decoded = this.decodeToken(); + async getName(): Promise { + const decoded = await this.decodeToken(); if (typeof decoded.name === 'undefined') { return null; } @@ -225,8 +186,8 @@ export class TokenService implements TokenServiceAbstraction { return decoded.name as string; } - getPremium(): boolean { - const decoded = this.decodeToken(); + async getPremium(): Promise { + const decoded = await this.decodeToken(); if (typeof decoded.premium === 'undefined') { return false; } @@ -234,8 +195,8 @@ export class TokenService implements TokenServiceAbstraction { return decoded.premium as boolean; } - getIssuer(): string { - const decoded = this.decodeToken(); + async getIssuer(): Promise { + const decoded = await this.decodeToken(); if (typeof decoded.iss === 'undefined') { throw new Error('No issuer found'); } @@ -243,8 +204,8 @@ export class TokenService implements TokenServiceAbstraction { return decoded.iss as string; } - getIsExternal(): boolean { - const decoded = this.decodeToken(); + async getIsExternal(): Promise { + const decoded = await this.decodeToken(); if (!Array.isArray(decoded.amr)) { throw new Error('No amr found'); } @@ -252,18 +213,9 @@ export class TokenService implements TokenServiceAbstraction { return decoded.amr.includes('external'); } - private async storeTokenValue(key: string, value: string) { - if (await this.skipTokenStorage()) { - // if we have a vault timeout and the action is log out, don't store token - return; - } - - return this.storageService.save(key, value); - } - private async skipTokenStorage(): Promise { - const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); - const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); + const timeout = await this.stateService.getVaultTimeout(); + const action = await this.stateService.getVaultTimeoutAction(); return timeout != null && action === 'logOut'; } } diff --git a/common/src/services/totp.service.ts b/common/src/services/totp.service.ts index 50c8c4eb51..a16c49f614 100644 --- a/common/src/services/totp.service.ts +++ b/common/src/services/totp.service.ts @@ -1,8 +1,6 @@ -import { ConstantsService } from './constants.service'; - import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; import { LogService } from '../abstractions/log.service'; -import { StorageService } from '../abstractions/storage.service'; +import { StateService } from '../abstractions/state.service'; import { TotpService as TotpServiceAbstraction } from '../abstractions/totp.service'; import { Utils } from '../misc/utils'; @@ -11,8 +9,8 @@ const B32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; const SteamChars = '23456789BCDFGHJKMNPQRTVWXY'; export class TotpService implements TotpServiceAbstraction { - constructor(private storageService: StorageService, private cryptoFunctionService: CryptoFunctionService, - private logService: LogService) { } + constructor(private cryptoFunctionService: CryptoFunctionService, private logService: LogService, + private stateService: StateService) { } async getCode(key: string): Promise { if (key == null) { @@ -114,7 +112,7 @@ export class TotpService implements TotpServiceAbstraction { } async isAutoCopyEnabled(): Promise { - return !(await this.storageService.get(ConstantsService.disableAutoTotpCopyKey)); + return !(await this.stateService.getDisableAutoTotpCopy()); } // Helpers diff --git a/common/src/services/user.service.ts b/common/src/services/user.service.ts deleted file mode 100644 index a572691afd..0000000000 --- a/common/src/services/user.service.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { StorageService } from '../abstractions/storage.service'; -import { TokenService } from '../abstractions/token.service'; -import { UserService as UserServiceAbstraction } from '../abstractions/user.service'; - -import { OrganizationData } from '../models/data/organizationData'; -import { Organization } from '../models/domain/organization'; - -import { KdfType } from '../enums/kdfType'; - -import { ProviderData } from '../models/data/providerData'; -import { Provider } from '../models/domain/provider'; - -const Keys = { - userId: 'userId', - userEmail: 'userEmail', - stamp: 'securityStamp', - kdf: 'kdf', - kdfIterations: 'kdfIterations', - organizationsPrefix: 'organizations_', - providersPrefix: 'providers_', - emailVerified: 'emailVerified', - forcePasswordReset: 'forcePasswordReset', -}; - -export class UserService implements UserServiceAbstraction { - private userId: string; - private email: string; - private stamp: string; - private kdf: KdfType; - private kdfIterations: number; - private emailVerified: boolean; - private forcePasswordReset: boolean; - - constructor(private tokenService: TokenService, private storageService: StorageService) { } - - async setInformation(userId: string, email: string, kdf: KdfType, kdfIterations: number): Promise { - this.email = email; - this.userId = userId; - this.kdf = kdf; - this.kdfIterations = kdfIterations; - - await this.storageService.save(Keys.userEmail, email); - await this.storageService.save(Keys.userId, userId); - await this.storageService.save(Keys.kdf, kdf); - await this.storageService.save(Keys.kdfIterations, kdfIterations); - } - - setSecurityStamp(stamp: string): Promise { - this.stamp = stamp; - return this.storageService.save(Keys.stamp, stamp); - } - - setEmailVerified(emailVerified: boolean) { - this.emailVerified = emailVerified; - return this.storageService.save(Keys.emailVerified, emailVerified); - } - - setForcePasswordReset(forcePasswordReset: boolean) { - this.forcePasswordReset = forcePasswordReset; - return this.storageService.save(Keys.forcePasswordReset, forcePasswordReset); - } - - async getUserId(): Promise { - if (this.userId == null) { - this.userId = await this.storageService.get(Keys.userId); - } - return this.userId; - } - - async getEmail(): Promise { - if (this.email == null) { - this.email = await this.storageService.get(Keys.userEmail); - } - return this.email; - } - - async getSecurityStamp(): Promise { - if (this.stamp == null) { - this.stamp = await this.storageService.get(Keys.stamp); - } - return this.stamp; - } - - async getKdf(): Promise { - if (this.kdf == null) { - this.kdf = await this.storageService.get(Keys.kdf); - } - return this.kdf; - } - - async getKdfIterations(): Promise { - if (this.kdfIterations == null) { - this.kdfIterations = await this.storageService.get(Keys.kdfIterations); - } - return this.kdfIterations; - } - - async getEmailVerified(): Promise { - if (this.emailVerified == null) { - this.emailVerified = await this.storageService.get(Keys.emailVerified); - } - return this.emailVerified; - } - - async getForcePasswordReset(): Promise { - if (this.forcePasswordReset == null) { - this.forcePasswordReset = await this.storageService.get(Keys.forcePasswordReset); - } - return this.forcePasswordReset; - } - - async clear(): Promise { - const userId = await this.getUserId(); - - await this.storageService.remove(Keys.userId); - await this.storageService.remove(Keys.userEmail); - await this.storageService.remove(Keys.stamp); - await this.storageService.remove(Keys.kdf); - await this.storageService.remove(Keys.kdfIterations); - await this.storageService.remove(Keys.forcePasswordReset); - await this.clearOrganizations(userId); - await this.clearProviders(userId); - - this.userId = this.email = this.stamp = null; - this.kdf = null; - this.kdfIterations = null; - } - - async isAuthenticated(): Promise { - const token = await this.tokenService.getToken(); - if (token == null) { - return false; - } - - const userId = await this.getUserId(); - return userId != null; - } - - async canAccessPremium(): Promise { - const authed = await this.isAuthenticated(); - if (!authed) { - return false; - } - - const tokenPremium = this.tokenService.getPremium(); - if (tokenPremium) { - return true; - } - - const orgs = await this.getAllOrganizations(); - for (let i = 0; i < orgs.length; i++) { - if (orgs[i].usersGetPremium && orgs[i].enabled) { - return true; - } - } - return false; - } - - async canManageSponsorships(): Promise { - const orgs = await this.getAllOrganizations(); - return orgs.some(o => o.familySponsorshipAvailable || o.familySponsorshipFriendlyName !== null); - } - - async getOrganization(id: string): Promise { - const userId = await this.getUserId(); - const organizations = await this.storageService.get<{ [id: string]: OrganizationData; }>( - Keys.organizationsPrefix + userId); - if (organizations == null || !organizations.hasOwnProperty(id)) { - return null; - } - - return new Organization(organizations[id]); - } - - async getOrganizationByIdentifier(identifier: string): Promise { - const organizations = await this.getAllOrganizations(); - if (organizations == null || organizations.length === 0) { - return null; - } - - return organizations.find(o => o.identifier === identifier); - } - - async getAllOrganizations(): Promise { - const userId = await this.getUserId(); - const organizations = await this.storageService.get<{ [id: string]: OrganizationData; }>( - Keys.organizationsPrefix + userId); - const response: Organization[] = []; - for (const id in organizations) { - if (organizations.hasOwnProperty(id) && !organizations[id].isProviderUser) { - response.push(new Organization(organizations[id])); - } - } - return response; - } - - async replaceOrganizations(organizations: { [id: string]: OrganizationData; }): Promise { - const userId = await this.getUserId(); - await this.storageService.save(Keys.organizationsPrefix + userId, organizations); - } - - async clearOrganizations(userId: string): Promise { - await this.storageService.remove(Keys.organizationsPrefix + userId); - } - - async getProvider(id: string): Promise { - const userId = await this.getUserId(); - const providers = await this.storageService.get<{ [id: string]: ProviderData; }>( - Keys.providersPrefix + userId); - if (providers == null || !providers.hasOwnProperty(id)) { - return null; - } - - return new Provider(providers[id]); - } - - async getAllProviders(): Promise { - const userId = await this.getUserId(); - const providers = await this.storageService.get<{ [id: string]: ProviderData; }>( - Keys.providersPrefix + userId); - const response: Provider[] = []; - for (const id in providers) { - if (providers.hasOwnProperty(id)) { - response.push(new Provider(providers[id])); - } - } - return response; - } - - async replaceProviders(providers: { [id: string]: ProviderData; }): Promise { - const userId = await this.getUserId(); - await this.storageService.save(Keys.providersPrefix + userId, providers); - } - - async clearProviders(userId: string): Promise { - await this.storageService.remove(Keys.providersPrefix + userId); - } -} diff --git a/common/src/services/vaultTimeout.service.ts b/common/src/services/vaultTimeout.service.ts index 54d288b155..e88e8293e3 100644 --- a/common/src/services/vaultTimeout.service.ts +++ b/common/src/services/vaultTimeout.service.ts @@ -1,5 +1,3 @@ -import { ConstantsService } from './constants.service'; - import { CipherService } from '../abstractions/cipher.service'; import { CollectionService } from '../abstractions/collection.service'; import { CryptoService } from '../abstractions/crypto.service'; @@ -9,29 +7,31 @@ import { MessagingService } from '../abstractions/messaging.service'; import { PlatformUtilsService } from '../abstractions/platformUtils.service'; import { PolicyService } from '../abstractions/policy.service'; import { SearchService } from '../abstractions/search.service'; -import { StorageService } from '../abstractions/storage.service'; +import { StateService } from '../abstractions/state.service'; import { TokenService } from '../abstractions/token.service'; -import { UserService } from '../abstractions/user.service'; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from '../abstractions/vaultTimeout.service'; +import { KeySuffixOptions } from '../enums/keySuffixOptions'; import { PolicyType } from '../enums/policyType'; -import { EncString } from '../models/domain/encString'; export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { - pinProtectedKey: EncString = null; - biometricLocked: boolean = true; - everBeenUnlocked: boolean = false; - private inited = false; - constructor(private cipherService: CipherService, private folderService: FolderService, - private collectionService: CollectionService, private cryptoService: CryptoService, - protected platformUtilsService: PlatformUtilsService, private storageService: StorageService, - private messagingService: MessagingService, private searchService: SearchService, - private userService: UserService, private tokenService: TokenService, private policyService: PolicyService, + constructor( + private cipherService: CipherService, + private folderService: FolderService, + private collectionService: CollectionService, + private cryptoService: CryptoService, + protected platformUtilsService: PlatformUtilsService, + private messagingService: MessagingService, + private searchService: SearchService, + private tokenService: TokenService, + private policyService: PolicyService, private keyConnectorService: KeyConnectorService, - private lockedCallback: () => Promise = null, private loggedOutCallback: () => Promise = null) { - } + private stateService: StateService, + private lockedCallback: () => Promise = null, + private loggedOutCallback: (userId?: string) => Promise = null + ) {} init(checkOnInterval: boolean) { if (this.inited) { @@ -50,110 +50,96 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { } // Keys aren't stored for a device that is locked or logged out. - async isLocked(): Promise { - // Handle never lock startup situation - if (await this.cryptoService.hasKeyStored('auto') && !this.everBeenUnlocked) { - await this.cryptoService.getKey('auto'); + async isLocked(userId?: string): Promise { + const neverLock = await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId) && + !(await this.stateService.getEverBeenUnlocked({ userId: userId })); + if (neverLock) { + // TODO: This also _sets_ the key so when we check memory in the next line it finds a key. + // We should refactor here. + await this.cryptoService.getKey(KeySuffixOptions.Auto, userId); } - return !this.cryptoService.hasKeyInMemory(); + return !(await this.cryptoService.hasKeyInMemory(userId)); } async checkVaultTimeout(): Promise { if (await this.platformUtilsService.isViewOpen()) { - // Do not lock return; } - // "is logged out check" - similar to isLocked, below - const authed = await this.userService.isAuthenticated(); - if (!authed) { - return; - } - - if (await this.isLocked()) { - return; - } - - const vaultTimeout = await this.getVaultTimeout(); - if (vaultTimeout == null || vaultTimeout < 0) { - return; - } - - const lastActive = await this.storageService.get(ConstantsService.lastActiveKey); - if (lastActive == null) { - return; - } - - const vaultTimeoutSeconds = vaultTimeout * 60; - const diffSeconds = ((new Date()).getTime() - lastActive) / 1000; - if (diffSeconds >= vaultTimeoutSeconds) { - // Pivot based on the saved vault timeout action - const timeoutAction = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); - timeoutAction === 'logOut' ? await this.logOut() : await this.lock(true); + for (const userId in this.stateService.accounts.getValue()) { + if (userId != null && await this.shouldLock(userId)) { + await this.executeTimeoutAction(userId); + } } } - async lock(allowSoftLock = false): Promise { - const authed = await this.userService.isAuthenticated(); + async lock(allowSoftLock = false, userId?: string): Promise { + const authed = await this.stateService.getIsAuthenticated({ userId: userId }); if (!authed) { return; } if (await this.keyConnectorService.getUsesKeyConnector()) { const pinSet = await this.isPinLockSet(); - const pinLock = (pinSet[0] && this.pinProtectedKey != null) || pinSet[1]; + const pinLock = (pinSet[0] && await this.stateService.getDecryptedPinProtected() != null) || pinSet[1]; if (!pinLock && !await this.isBiometricLockSet()) { await this.logOut(); } } - this.biometricLocked = true; - this.everBeenUnlocked = true; - await this.cryptoService.clearKey(false); - await this.cryptoService.clearOrgKeys(true); - await this.cryptoService.clearKeyPair(true); - await this.cryptoService.clearEncKey(true); + if (userId == null || userId === await this.stateService.getUserId()) { + this.searchService.clearIndex(); + } + + await this.stateService.setEverBeenUnlocked(true, { userId: userId }); + await this.stateService.setBiometricLocked(true, { userId: userId }); + + await this.cryptoService.clearKey(false, userId); + await this.cryptoService.clearOrgKeys(true, userId); + await this.cryptoService.clearKeyPair(true, userId); + await this.cryptoService.clearEncKey(true, userId); + + await this.folderService.clearCache(userId); + await this.cipherService.clearCache(userId); + await this.collectionService.clearCache(userId); + + this.messagingService.send('locked', { userId: userId }); - this.folderService.clearCache(); - this.cipherService.clearCache(); - this.collectionService.clearCache(); - this.searchService.clearIndex(); - this.messagingService.send('locked'); if (this.lockedCallback != null) { await this.lockedCallback(); } } - async logOut(): Promise { + async logOut(userId?: string): Promise { if (this.loggedOutCallback != null) { - await this.loggedOutCallback(); + await this.loggedOutCallback(userId); } } async setVaultTimeoutOptions(timeout: number, action: string): Promise { - await this.storageService.save(ConstantsService.vaultTimeoutKey, timeout); - await this.storageService.save(ConstantsService.vaultTimeoutActionKey, action); + await this.stateService.setVaultTimeout(timeout); + await this.stateService.setVaultTimeoutAction(action); await this.cryptoService.toggleKey(); await this.tokenService.toggleTokens(); } async isPinLockSet(): Promise<[boolean, boolean]> { - const protectedPin = await this.storageService.get(ConstantsService.protectedPin); - const pinProtectedKey = await this.storageService.get(ConstantsService.pinProtectedKey); + const protectedPin = await this.stateService.getProtectedPin(); + const pinProtectedKey = await this.stateService.getEncryptedPinProtected(); return [protectedPin != null, pinProtectedKey != null]; } async isBiometricLockSet(): Promise { - return await this.storageService.get(ConstantsService.biometricUnlockKey); + return await this.stateService.getBiometricUnlock(); } - async getVaultTimeout(): Promise { - const vaultTimeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); + async getVaultTimeout(userId?: string): Promise { + const vaultTimeout = await this.stateService.getVaultTimeout( { userId: userId } ); - if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) { - const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout); + if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId)) { + const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout, userId); // Remove negative values, and ensure it's smaller than maximum allowed value according to policy let timeout = Math.min(vaultTimeout, policy[0].data.minutes); @@ -163,7 +149,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { // We really shouldn't need to set the value here, but multiple services relies on this value being correct. if (vaultTimeout !== timeout) { - await this.storageService.save(ConstantsService.vaultTimeoutKey, timeout); + await this.stateService.setVaultTimeout(timeout, { userId: userId }); } return timeout; @@ -172,9 +158,42 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { return vaultTimeout; } - clear(): Promise { - this.everBeenUnlocked = false; - this.pinProtectedKey = null; - return this.storageService.remove(ConstantsService.protectedPin); + async clear(userId?: string): Promise { + await this.stateService.setEverBeenUnlocked(false, { userId: userId }); + await this.stateService.setDecryptedPinProtected(null, { userId: userId }); + await this.stateService.setProtectedPin(null, { userId: userId }); + } + + private async isLoggedOut(userId?: string): Promise { + return !(await this.stateService.getIsAuthenticated({ userId: userId })); + } + + private async shouldLock(userId: string): Promise { + if (await this.isLoggedOut(userId)) { + return false; + } + + if (await this.isLocked(userId)) { + return false; + } + + const vaultTimeout = await this.getVaultTimeout(userId); + if (vaultTimeout == null || vaultTimeout < 0) { + return false; + } + + const lastActive = await this.stateService.getLastActive({ userId: userId }); + if (lastActive == null) { + return false; + } + + const vaultTimeoutSeconds = vaultTimeout * 60; + const diffSeconds = ((new Date()).getTime() - lastActive) / 1000; + return diffSeconds >= vaultTimeoutSeconds; + } + + private async executeTimeoutAction(userId: string): Promise { + const timeoutAction = await this.stateService.getVaultTimeoutAction({ userId: userId }); + timeoutAction === 'logOut' ? await this.logOut() : await this.lock(true, userId); } } diff --git a/electron/src/biometric.darwin.main.ts b/electron/src/biometric.darwin.main.ts index 26d3bd74ac..162c772230 100644 --- a/electron/src/biometric.darwin.main.ts +++ b/electron/src/biometric.darwin.main.ts @@ -1,21 +1,18 @@ import { ipcMain, systemPreferences } from 'electron'; -import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { ConstantsService } from 'jslib-common/services/constants.service'; - import { BiometricMain } from 'jslib-common/abstractions/biometric.main'; -import { ElectronConstants } from './electronConstants'; +import { I18nService } from 'jslib-common/abstractions/i18n.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; export default class BiometricDarwinMain implements BiometricMain { isError: boolean = false; - constructor(private storageService: StorageService, private i18nservice: I18nService) {} + constructor(private i18nservice: I18nService, private stateService: StateService) {} async init() { - this.storageService.save(ElectronConstants.enableBiometric, await this.supportsBiometric()); - this.storageService.save(ConstantsService.biometricText, 'unlockWithTouchId'); - this.storageService.save(ElectronConstants.noAutoPromptBiometricsText, 'noAutoPromptTouchId'); + await this.stateService.setEnableBiometric(await this.supportsBiometric()); + await this.stateService.setBiometricText('unlockWithTouchId'); + await this.stateService.setNoAutoPromptBiometricsText('noAutoPromptTouchId'); ipcMain.on('biometric', async (event: any, message: any) => { event.returnValue = await this.authenticateBiometric(); diff --git a/electron/src/biometric.windows.main.ts b/electron/src/biometric.windows.main.ts index d33875b757..2bf323883f 100644 --- a/electron/src/biometric.windows.main.ts +++ b/electron/src/biometric.windows.main.ts @@ -1,22 +1,20 @@ import { ipcMain } from 'electron'; import forceFocus from 'forcefocus'; -import { ElectronConstants } from './electronConstants'; import { WindowMain } from './window.main'; import { BiometricMain } from 'jslib-common/abstractions/biometric.main'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { LogService } from 'jslib-common/abstractions/log.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { ConstantsService } from 'jslib-common/services/constants.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; export default class BiometricWindowsMain implements BiometricMain { isError: boolean = false; private windowsSecurityCredentialsUiModule: any; - constructor(private storageService: StorageService, private i18nservice: I18nService, private windowMain: WindowMain, - private logService: LogService) { } + constructor(private i18nservice: I18nService, private windowMain: WindowMain, + private stateService: StateService, private logService: LogService) { } async init() { this.windowsSecurityCredentialsUiModule = this.getWindowsSecurityCredentialsUiModule(); @@ -27,9 +25,9 @@ export default class BiometricWindowsMain implements BiometricMain { // store error state so we can let the user know on the settings page this.isError = true; } - this.storageService.save(ElectronConstants.enableBiometric, supportsBiometric); - this.storageService.save(ConstantsService.biometricText, 'unlockWithWindowsHello'); - this.storageService.save(ElectronConstants.noAutoPromptBiometricsText, 'noAutoPromptWindowsHello'); + await this.stateService.setEnableBiometric(supportsBiometric); + await this.stateService.setBiometricText('unlockWithWindowsHello'); + await this.stateService.setNoAutoPromptBiometricsText('noAutoPromptWindowsHello'); ipcMain.on('biometric', async (event: any, message: any) => { event.returnValue = await this.authenticateBiometric(); diff --git a/electron/src/electronConstants.ts b/electron/src/electronConstants.ts deleted file mode 100644 index 68be19562a..0000000000 --- a/electron/src/electronConstants.ts +++ /dev/null @@ -1,14 +0,0 @@ -export class ElectronConstants { - static readonly enableMinimizeToTrayKey: string = 'enableMinimizeToTray'; - static readonly enableCloseToTrayKey: string = 'enableCloseToTray'; - static readonly enableTrayKey: string = 'enableTray'; - static readonly enableStartToTrayKey: string = 'enableStartToTrayKey'; - static readonly enableAlwaysOnTopKey: string = 'enableAlwaysOnTopKey'; - static readonly minimizeOnCopyToClipboardKey: string = 'minimizeOnCopyToClipboardKey'; - static readonly enableBiometric: string = 'enabledBiometric'; - static readonly enableBrowserIntegration: string = 'enableBrowserIntegration'; - static readonly enableBrowserIntegrationFingerprint: string = 'enableBrowserIntegrationFingerprint'; - static readonly alwaysShowDock: string = 'alwaysShowDock'; - static readonly openAtLogin: string = 'openAtLogin'; - static readonly noAutoPromptBiometricsText: string = 'noAutoPromptBiometricsText'; -} diff --git a/electron/src/services/electronCrypto.service.ts b/electron/src/services/electronCrypto.service.ts index dc602e0fbc..36834549ad 100644 --- a/electron/src/services/electronCrypto.service.ts +++ b/electron/src/services/electronCrypto.service.ts @@ -1,16 +1,19 @@ import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { LogService } from 'jslib-common/abstractions/log.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { KeySuffixOptions, StorageService } from 'jslib-common/abstractions/storage.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; + +import { CryptoService } from 'jslib-common/services/crypto.service'; + +import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions'; +import { StorageLocation } from 'jslib-common/enums/storageLocation'; import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; -import { CryptoService, Keys } from 'jslib-common/services/crypto.service'; export class ElectronCryptoService extends CryptoService { - constructor(storageService: StorageService, secureStorageService: StorageService, - cryptoFunctionService: CryptoFunctionService, platformUtilService: PlatformUtilsService, - logService: LogService) { - super(storageService, secureStorageService, cryptoFunctionService, platformUtilService, logService); + constructor(cryptoFunctionService: CryptoFunctionService, platformUtilService: PlatformUtilsService, + logService: LogService, stateService: StateService) { + super(cryptoFunctionService, platformUtilService, logService, stateService); } async hasKeyStored(keySuffix: KeySuffixOptions): Promise { @@ -18,17 +21,17 @@ export class ElectronCryptoService extends CryptoService { return super.hasKeyStored(keySuffix); } - protected async storeKey(key: SymmetricCryptoKey) { - if (await this.shouldStoreKey('auto')) { - await this.secureStorageService.save(Keys.key, key.keyB64, { keySuffix: 'auto' }); + protected async storeKey(key: SymmetricCryptoKey, userId?: string) { + if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) { + await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId }); } else { - this.clearStoredKey('auto'); + this.clearStoredKey(KeySuffixOptions.Auto); } - if (await this.shouldStoreKey('biometric')) { - await this.secureStorageService.save(Keys.key, key.keyB64, { keySuffix: 'biometric' }); + if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) { + await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId }); } else { - this.clearStoredKey('biometric'); + this.clearStoredKey(KeySuffixOptions.Biometric); } } @@ -43,24 +46,24 @@ export class ElectronCryptoService extends CryptoService { */ private async upgradeSecurelyStoredKey() { // attempt key upgrade, but if we fail just delete it. Keys will be stored property upon unlock anyway. - const key = await this.secureStorageService.get(Keys.key); + const key = await this.stateService.getCryptoMasterKeyB64(); if (key == null) { return; } try { - if (await this.shouldStoreKey('auto')) { - await this.secureStorageService.save(Keys.key, key, { keySuffix: 'auto' }); + if (await this.shouldStoreKey(KeySuffixOptions.Auto)) { + await this.stateService.setCryptoMasterKeyAuto(key); } - if (await this.shouldStoreKey('biometric')) { - await this.secureStorageService.save(Keys.key, key, { keySuffix: 'biometric' }); + if (await this.shouldStoreKey(KeySuffixOptions.Biometric)) { + await this.stateService.setCryptoMasterKeyBiometric(key); } } catch (e) { this.logService.error(`Encountered error while upgrading obsolete Bitwarden secure storage item:`); this.logService.error(e); } - await this.secureStorageService.remove(Keys.key); + await this.stateService.setCryptoMasterKeyB64(null); } } diff --git a/electron/src/services/electronPlatformUtils.service.ts b/electron/src/services/electronPlatformUtils.service.ts index 7ffe3e3f94..acd910c2f7 100644 --- a/electron/src/services/electronPlatformUtils.service.ts +++ b/electron/src/services/electronPlatformUtils.service.ts @@ -15,11 +15,7 @@ import { ThemeType } from 'jslib-common/enums/themeType'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; - -import { ConstantsService } from 'jslib-common/services/constants.service'; - -import { ElectronConstants } from '../electronConstants'; +import { StateService } from 'jslib-common/abstractions/state.service'; export class ElectronPlatformUtilsService implements PlatformUtilsService { identityClientId: string; @@ -27,7 +23,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { private deviceCache: DeviceType = null; constructor(protected i18nService: I18nService, private messagingService: MessagingService, - private isDesktopApp: boolean, private storageService: StorageService) { + private isDesktopApp: boolean, private stateService: StateService) { this.identityClientId = isDesktopApp ? 'desktop' : 'connector'; } @@ -178,8 +174,8 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { return Promise.resolve(clipboard.readText(type)); } - supportsBiometric(): Promise { - return this.storageService.get(ElectronConstants.enableBiometric); + async supportsBiometric(): Promise { + return await this.stateService.getEnableBiometric(); } authenticateBiometric(): Promise { @@ -200,7 +196,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { } async getEffectiveTheme() { - const theme = await this.storageService.get(ConstantsService.themeKey); + const theme = await this.stateService.getTheme(); if (theme == null || theme === ThemeType.System) { return this.getDefaultSystemTheme(); } else { diff --git a/electron/src/services/electronRendererSecureStorage.service.ts b/electron/src/services/electronRendererSecureStorage.service.ts index fb71805811..13a7459a27 100644 --- a/electron/src/services/electronRendererSecureStorage.service.ts +++ b/electron/src/services/electronRendererSecureStorage.service.ts @@ -1,9 +1,11 @@ import { ipcRenderer } from 'electron'; -import { StorageService, StorageServiceOptions } from 'jslib-common/abstractions/storage.service'; +import { StorageService } from 'jslib-common/abstractions/storage.service'; + +import { StorageOptions } from 'jslib-common/models/domain/storageOptions'; export class ElectronRendererSecureStorageService implements StorageService { - async get(key: string, options?: StorageServiceOptions): Promise { + async get(key: string, options?: StorageOptions): Promise { const val = ipcRenderer.sendSync('keytar', { action: 'getPassword', key: key, @@ -12,7 +14,7 @@ export class ElectronRendererSecureStorageService implements StorageService { return Promise.resolve(val != null ? JSON.parse(val) as T : null); } - async has(key: string, options?: StorageServiceOptions): Promise { + async has(key: string, options?: StorageOptions): Promise { const val = ipcRenderer.sendSync('keytar', { action: 'hasPassword', key: key, @@ -21,7 +23,7 @@ export class ElectronRendererSecureStorageService implements StorageService { return Promise.resolve(!!val); } - async save(key: string, obj: any, options?: StorageServiceOptions): Promise { + async save(key: string, obj: any, options?: StorageOptions): Promise { ipcRenderer.sendSync('keytar', { action: 'setPassword', key: key, @@ -31,7 +33,7 @@ export class ElectronRendererSecureStorageService implements StorageService { return Promise.resolve(); } - async remove(key: string, options?: StorageServiceOptions): Promise { + async remove(key: string, options?: StorageOptions): Promise { ipcRenderer.sendSync('keytar', { action: 'deletePassword', key: key, diff --git a/electron/src/tray.main.ts b/electron/src/tray.main.ts index 30df8410db..c75089044d 100644 --- a/electron/src/tray.main.ts +++ b/electron/src/tray.main.ts @@ -2,7 +2,6 @@ import { app, BrowserWindow, Menu, - MenuItem, MenuItemConstructorOptions, nativeImage, Tray, @@ -10,9 +9,8 @@ import { import * as path from 'path'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; -import { ElectronConstants } from './electronConstants'; import { WindowMain } from './window.main'; export class TrayMain { @@ -24,7 +22,7 @@ export class TrayMain { private pressedIcon: Electron.NativeImage; constructor(private windowMain: WindowMain, private i18nService: I18nService, - private storageService: StorageService) { + private stateService: StateService) { if (process.platform === 'win32') { this.icon = path.join(__dirname, '/images/icon.ico'); } else if (process.platform === 'darwin') { @@ -55,21 +53,21 @@ export class TrayMain { } this.contextMenu = Menu.buildFromTemplate(menuItemOptions); - if (await this.storageService.get(ElectronConstants.enableTrayKey)) { + if (await this.stateService.getEnableTray()) { this.showTray(); } } setupWindowListeners(win: BrowserWindow) { win.on('minimize', async (e: Event) => { - if (await this.storageService.get(ElectronConstants.enableMinimizeToTrayKey)) { + if (await this.stateService.getEnableMinimizeToTray()) { e.preventDefault(); this.hideToTray(); } }); win.on('close', async (e: Event) => { - if (await this.storageService.get(ElectronConstants.enableCloseToTrayKey)) { + if (await this.stateService.getEnableCloseToTray()) { if (!this.windowMain.isQuitting) { e.preventDefault(); this.hideToTray(); @@ -78,7 +76,7 @@ export class TrayMain { }); win.on('show', async (e: Event) => { - const enableTray = await this.storageService.get(ElectronConstants.enableTrayKey); + const enableTray = await this.stateService.getEnableTray(); if (!enableTray) { setTimeout(() => this.removeTray(false), 100); } @@ -103,7 +101,7 @@ export class TrayMain { if (this.windowMain.win != null) { this.windowMain.win.hide(); } - if (this.isDarwin() && !await this.storageService.get(ElectronConstants.alwaysShowDock)) { + if (this.isDarwin() && !await this.stateService.getAlwaysShowDock()) { this.hideDock(); } } @@ -167,7 +165,7 @@ export class TrayMain { } if (this.windowMain.win.isVisible()) { this.windowMain.win.hide(); - if (this.isDarwin() && !await this.storageService.get(ElectronConstants.alwaysShowDock)) { + if (this.isDarwin() && !await this.stateService.getAlwaysShowDock()) { this.hideDock(); } } else { diff --git a/electron/src/utils.ts b/electron/src/utils.ts index ce4de6058d..7a33142f80 100644 --- a/electron/src/utils.ts +++ b/electron/src/utils.ts @@ -25,8 +25,12 @@ export function isAppImage() { return process.platform === 'linux' && 'APPIMAGE' in process.env; } +export function isMac() { + return process.platform === 'darwin'; +} + export function isMacAppStore() { - return process.platform === 'darwin' && process.mas && process.mas === true; + return isMac() && process.mas && process.mas === true; } export function isWindowsStore() { diff --git a/electron/src/window.main.ts b/electron/src/window.main.ts index f51dc11410..07cc5166b8 100644 --- a/electron/src/window.main.ts +++ b/electron/src/window.main.ts @@ -7,9 +7,8 @@ import * as path from 'path'; import * as url from 'url'; import { LogService } from 'jslib-common/abstractions/log.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; -import { ElectronConstants } from './electronConstants'; import { cleanUserAgent, isDev, @@ -17,11 +16,8 @@ import { isSnapStore, } from './utils'; +const mainWindowSizeKey = 'mainWindowSize'; const WindowEventHandlingDelay = 100; -const Keys = { - mainWindowSize: 'mainWindowSize', -}; - export class WindowMain { win: BrowserWindow; isQuitting: boolean = false; @@ -30,7 +26,7 @@ export class WindowMain { private windowStates: { [key: string]: any; } = {}; private enableAlwaysOnTop: boolean = false; - constructor(private storageService: StorageService, private logService: LogService, + constructor(private stateService: StateService, private logService: LogService, private hideTitleBar = false, private defaultWidth = 950, private defaultHeight = 600, private argvCallback: (argv: string[]) => void = null, private createWindowCallback: (win: BrowserWindow) => void) { } @@ -107,18 +103,18 @@ export class WindowMain { } async createWindow(): Promise { - this.windowStates[Keys.mainWindowSize] = await this.getWindowState(Keys.mainWindowSize, this.defaultWidth, + this.windowStates[mainWindowSizeKey] = await this.getWindowState(mainWindowSizeKey, this.defaultWidth, this.defaultHeight); - this.enableAlwaysOnTop = await this.storageService.get(ElectronConstants.enableAlwaysOnTopKey); + this.enableAlwaysOnTop = await this.stateService.getEnableAlwaysOnTop(); // Create the browser window. this.win = new BrowserWindow({ - width: this.windowStates[Keys.mainWindowSize].width, - height: this.windowStates[Keys.mainWindowSize].height, + width: this.windowStates[mainWindowSizeKey].width, + height: this.windowStates[mainWindowSizeKey].height, minWidth: 680, minHeight: 500, - x: this.windowStates[Keys.mainWindowSize].x, - y: this.windowStates[Keys.mainWindowSize].y, + x: this.windowStates[mainWindowSizeKey].x, + y: this.windowStates[mainWindowSizeKey].y, title: app.name, icon: process.platform === 'linux' ? path.join(__dirname, '/images/icon.png') : undefined, titleBarStyle: this.hideTitleBar && process.platform === 'darwin' ? 'hiddenInset' : undefined, @@ -132,7 +128,7 @@ export class WindowMain { }, }); - if (this.windowStates[Keys.mainWindowSize].isMaximized) { + if (this.windowStates[mainWindowSizeKey].isMaximized) { this.win.maximize(); } @@ -158,7 +154,7 @@ export class WindowMain { // Emitted when the window is closed. this.win.on('closed', async () => { - await this.updateWindowState(Keys.mainWindowSize, this.win); + await this.updateWindowState(mainWindowSizeKey, this.win); // Dereference the window object, usually you would store window // in an array if your app supports multi windows, this is the time @@ -167,23 +163,23 @@ export class WindowMain { }); this.win.on('close', async () => { - await this.updateWindowState(Keys.mainWindowSize, this.win); + await this.updateWindowState(mainWindowSizeKey, this.win); }); this.win.on('maximize', async () => { - await this.updateWindowState(Keys.mainWindowSize, this.win); + await this.updateWindowState(mainWindowSizeKey, this.win); }); this.win.on('unmaximize', async () => { - await this.updateWindowState(Keys.mainWindowSize, this.win); + await this.updateWindowState(mainWindowSizeKey, this.win); }); this.win.on('resize', () => { - this.windowStateChangeHandler(Keys.mainWindowSize, this.win); + this.windowStateChangeHandler(mainWindowSizeKey, this.win); }); this.win.on('move', () => { - this.windowStateChangeHandler(Keys.mainWindowSize, this.win); + this.windowStateChangeHandler(mainWindowSizeKey, this.win); }); this.win.on('focus', () => { this.win.webContents.send('messagingService', { @@ -200,7 +196,7 @@ export class WindowMain { async toggleAlwaysOnTop() { this.enableAlwaysOnTop = !this.win.isAlwaysOnTop(); this.win.setAlwaysOnTop(this.enableAlwaysOnTop); - await this.storageService.save(ElectronConstants.enableAlwaysOnTopKey, this.enableAlwaysOnTop); + await this.stateService.setEnableAlwaysOnTop(this.enableAlwaysOnTop); } private windowStateChangeHandler(configKey: string, win: BrowserWindow) { @@ -219,7 +215,7 @@ export class WindowMain { const bounds = win.getBounds(); if (this.windowStates[configKey] == null) { - this.windowStates[configKey] = await this.storageService.get(configKey); + this.windowStates[configKey] = (await this.stateService.getWindow()).get(configKey); if (this.windowStates[configKey] == null) { this.windowStates[configKey] = {}; } @@ -235,14 +231,19 @@ export class WindowMain { this.windowStates[configKey].height = bounds.height; } - await this.storageService.save(configKey, this.windowStates[configKey]); + const cachedWindow = await this.stateService.getWindow() ?? new Map(); + cachedWindow.set(configKey, this.windowStates[configKey]); + await this.stateService.setWindow(cachedWindow); } catch (e) { this.logService.error(e); } } private async getWindowState(configKey: string, defaultWidth: number, defaultHeight: number) { - let state = await this.storageService.get(configKey); + const windowState = await this.stateService.getWindow() ?? new Map(); + let state = windowState.has(configKey) ? + windowState.get(configKey) : + null; const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized); let displayBounds: Electron.Rectangle = null; diff --git a/node/src/cli/baseProgram.ts b/node/src/cli/baseProgram.ts index 7772ba805b..99447db8cd 100644 --- a/node/src/cli/baseProgram.ts +++ b/node/src/cli/baseProgram.ts @@ -1,15 +1,15 @@ import * as chalk from 'chalk'; +import { StateService } from 'jslib-common/abstractions/state.service'; + import { Response } from './models/response'; import { ListResponse } from './models/response/listResponse'; import { MessageResponse } from './models/response/messageResponse'; import { StringResponse } from './models/response/stringResponse'; -import { UserService } from 'jslib-common/abstractions/user.service'; - export abstract class BaseProgram { constructor( - private userService: UserService, + private stateService: StateService, private writeLn: (s: string, finalLine: boolean, error: boolean) => void) { } protected processResponse(response: Response, exitImmediately = false, dataProcessor: () => string = null) { @@ -92,15 +92,15 @@ export abstract class BaseProgram { } protected async exitIfAuthed() { - const authed = await this.userService.isAuthenticated(); + const authed = await this.stateService.getIsAuthenticated(); if (authed) { - const email = await this.userService.getEmail(); + const email = await this.stateService.getEmail(); this.processResponse(Response.error('You are already logged in as ' + email + '.'), true); } } protected async exitIfNotAuthed() { - const authed = await this.userService.isAuthenticated(); + const authed = await this.stateService.getIsAuthenticated(); if (!authed) { this.processResponse(Response.error('You are not logged in.'), true); } diff --git a/node/src/cli/commands/login.command.ts b/node/src/cli/commands/login.command.ts index 7cdb607eb0..0bb744fdd6 100644 --- a/node/src/cli/commands/login.command.ts +++ b/node/src/cli/commands/login.command.ts @@ -18,8 +18,8 @@ import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.serv import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; import { SyncService } from 'jslib-common/abstractions/sync.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; import { Response } from '../models/response'; @@ -49,7 +49,7 @@ export class LoginCommand { protected i18nService: I18nService, protected environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService, protected cryptoFunctionService: CryptoFunctionService, protected platformUtilsService: PlatformUtilsService, - protected userService: UserService, protected cryptoService: CryptoService, + protected stateService: StateService, protected cryptoService: CryptoService, protected policyService: PolicyService, clientId: string, private syncService: SyncService, protected keyConnectorService: KeyConnectorService) { this.clientId = clientId; @@ -300,7 +300,7 @@ export class LoginCommand { } if (this.email == null || this.email === 'undefined') { - this.email = await this.userService.getEmail(); + this.email = await this.stateService.getEmail(); } // Get New Master Password @@ -350,8 +350,8 @@ export class LoginCommand { // Retrieve details for key generation const enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); - const kdf = await this.userService.getKdf(); - const kdfIterations = await this.userService.getKdfIterations(); + const kdf = await this.stateService.getKdfType(); + const kdfIterations = await this.stateService.getKdfIterations(); if (enforcedPolicyOptions != null && !this.policyService.evaluateMasterPassword( diff --git a/spec/common/services/cipher.service.spec.ts b/spec/common/services/cipher.service.spec.ts index 83dce24c02..2fe54c61a8 100644 --- a/spec/common/services/cipher.service.spec.ts +++ b/spec/common/services/cipher.service.spec.ts @@ -7,8 +7,8 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { LogService } from 'jslib-common/abstractions/log.service'; import { SearchService } from 'jslib-common/abstractions/search.service'; import { SettingsService } from 'jslib-common/abstractions/settings.service'; -import { StorageService } from 'jslib-common/abstractions/storage.service'; -import { UserService } from 'jslib-common/abstractions/user.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; + import { Utils } from 'jslib-common/misc/utils'; import { Cipher } from 'jslib-common/models/domain/cipher'; import { EncArrayBuffer } from 'jslib-common/models/domain/encArrayBuffer'; @@ -22,11 +22,10 @@ const ENCRYPTED_BYTES = new EncArrayBuffer(Utils.fromUtf8ToArray(ENCRYPTED_TEXT) describe('Cipher Service', () => { let cryptoService: SubstituteOf; - let userService: SubstituteOf; + let stateService: SubstituteOf; let settingsService: SubstituteOf; let apiService: SubstituteOf; let fileUploadService: SubstituteOf; - let storageService: SubstituteOf; let i18nService: SubstituteOf; let searchService: SubstituteOf; let logService: SubstituteOf; @@ -35,11 +34,10 @@ describe('Cipher Service', () => { beforeEach(() => { cryptoService = Substitute.for(); - userService = Substitute.for(); + stateService = Substitute.for(); settingsService = Substitute.for(); apiService = Substitute.for(); fileUploadService = Substitute.for(); - storageService = Substitute.for(); i18nService = Substitute.for(); searchService = Substitute.for(); logService = Substitute.for(); @@ -47,12 +45,11 @@ describe('Cipher Service', () => { cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES); cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT)); - cipherService = new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService, - storageService, i18nService, () => searchService, logService); + cipherService = new CipherService(cryptoService, settingsService, apiService, fileUploadService, + i18nService, () => searchService, logService, stateService); }); it('attachments upload encrypted file contents', async () => { - const key = new SymmetricCryptoKey(new Uint8Array(32).buffer); const fileName = 'filename'; const fileData = new Uint8Array(10).buffer; cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer));