1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-26 22:01:32 +01:00

[Account Switching] [Feature] Allow clients to store data for more than one user (#491)

* [refactor] Extract, rename, and expand StorageServiceOptions

* Pulled StorageServiceOptions into its own file
* Renamed StorageServiceOptions to StorageOptions
* Pulled KeySuffixOpptions into its own file
* Converted KeySuffixOptions into an enum from a union type

* [refactor] Expand StateService into a full coverage storage proxy

* Expand StateService to allow it to manage all data points of the application state regardless of memory.
* Expand StateService to allow for storing and managing multiple accounts

* [refactor] Create helper services for managing organization and provider state data

* [refactor] Implement StateService across service layer

* Remove service level variables used for in memory data storage and replaced with calls to StateService
* Remove direct calls to StorageService in favor of using StateService as a proxy

* [feature] Implement account switching capable services across components and processes

* Replace calls to StorageService and deprecated services with calls to a StateService

* [chore] Remove unused services

Several services are no longer in use because of the expanded state service. These have simply been removed.

* [bug] Add loginRedirect to the account model

* [bug] Add awaits to newly async calls in TokenService

* [bug] Add several missing awaits

* [bug] Add state service handlers for AutoConfirmFingerprint

* [bug] Move TwoFactorToken to global state

* Update unauth-guard.service.ts

Add back return true

* [refactor] Slim down the boilerplate needed to manage options on StateService calls

* [bug] Allow the lock message handler to manipulate a specific acount

* [bug] Add missing await to auth guard

* [bug] Adjust state scope of several biometric data points

* [bug] Ensure vault locking logic can operate over non-active accounts

* [style] Fix lint complaints

* [bug] Move disableFavicon to global state

* [refactor] Remove an unecassary parameter from a StorageOptions instance

* [bug] Ensure HtmlStorageService paths are accounted for in StateService

* [feature] Add a server url helper to the account model for the account switcher

* [refactor] Remove some unused getters from the account model

* [bug] Ensure locking and logging out can function over any user

* Fix account getting set to null in getAccountFromDisk

* [bug] Ensure lock component is always working with the latest active account in state

* [chore] Update recent KeyConnector changes to use stateService

* [style] Fix lint complaints

* [chore] Resolve TokenService merge issues from KeyConnector

* [bug] Add missing service arguement

* [bug] Correct several default storage option types

* [bug] Check for the right key in hasEncKey

* [bug] Add enableFullWidth to the account model

* [style] Fix lint complaints

* [review] Revist remember email

* [refactor] Remove RememberEmail from state

* setDisableFavicon to correct storage location

* [bug] Convert vault lock loop returns into continues to not skip secondary accounts

* [review] Sorted state service methods

* [bug] Correct neverDomains type on the account model

* [review] Rename stateService.purge to stateService.clean

* [review] [refactor] Extract lock refresh logic to a load function

* [review] [refactor] Extract some timeout logic to dedicated functions

* [review] [refactor] Move AuthenticationStatus to a dedicated file

* [review] [refactor] Rename Globals to GlobalState

* [style] Fix lint complaints

* [review] Remove unused global state property for decodedToken

* [review] [bug] Adjust state scope for OrganizationInvitation

* [review] [bug] Put back the homepage variable in lock guard

* [review] Un-try-catch the window creation function

* Revert "[review] [bug] Adjust state scope for OrganizationInvitation"

This reverts commit caa4574a65d9d0c3573a7529ed2221764fd55497.

* [bug] Change || to && in recent vault timeout refactor

* [bug] Keep up with entire state in storage instead of just accounts and globals

Not having access to the last active user was creating issues across clients when restarting the process.
For example: when refreshing the page on web we no longer maintain an understanding of who is logged in.

To resolve this I converted all storage save operations to get and save an entire state object, instead of specifying accounts and globals.
This allows for more flexible saving, like saving activeUserId as a top level storage item.

* [style] Fix lint complaints

* Revert "[bug] Keep up with entire state in storage instead of just accounts and globals"

This reverts commit e8970725be472386358c1e2f06f53663c4979e0e.

* [bug] Initialize GlobalState by default

* [bug] Only get key hash from storage

* [bug] Remove settings storage location overrides

* [bug] Only save accessToken to storage

* [refactor] Remove unecassary argements from electron crypto state calls

* [bug] Ensure keys and tokens load and save to the right locations for web

* [style] Fix lint complaints

* [bug] Remove keySuffix storage option and split uses into unique methods

The keySuffix options don't work with saving serialized json as a storage object - use cases simply overwrite each other in state.
This commit breaks Auto and Biometric keys into distinct storage items and adjusts logic accordingly.

* [bug] Add default vault timeouts to new accounts

* [bug] Save appId as a top level storage item

* [bug] Add missing await to timeout logic

* [bug] Adjust state scope for everBeenUnlocked

* [bug] Clear access tokens when loading account state from disk

* [bug] Adjust theme to be a global state item

* [bug] Adjust null checking for window in state

* [bug] Correct getGlobals not pulling from the stored state item

* [bug] Null check in memory account before claiming it has a userId

* [bug] Scaffold secure storage service when building storage objects on init

* [bug] Adjusted state scope of event collection

* [bug] Adjusted state scope of vault timeout and action

* [bug] Grab account from normal storage if secure storage is requested but does not exist

* [bug] Create a State if one is requested from memory before it exists

* [bug] Ensure all storage locations are cleared on state clean

* [style] Fix lint complaints

* [bug] Remove uneeded clearing of access token

* [bug] Reset tokens when toggling

* [refactor] Split up the Account model

Until this point the account model has been very flat, holding many kinds of data.

In order to be able to prune data at appropriate times, for example clearing keys at logout without clearing QoL settings like locale,
the Account model has been divided into logical chunks.

* [bug] Correct the serverUrl helpers return

* Fix sends always coming back as empty in browser

* Get settings properly (I think)

* [bug] Fix lint error

* [bug] Add missing await to identity token refresh

This was causing weird behavior in web that was creating a lot of 429s

* [bug] Scaffold memory storage for web

Not properly creating storage objects on signin was creating weird behavior when logging out, locking, and logging back in.
Namely, encrypted data that was recently synced had nowhere to save to and was lost.

* [bug] Implement better null handling in a few places for retrieving state

* [bug] Update correct storage locations on account removal

* [bug] Added missing awaits to lock component

* [bug] Reload lock component on account switching vs. account update

* [bug] Store master keys correctly

* [bug] Move some biometrics storage items to global state

* [feature] Add platform helper isMac()

* [refactor] Comment emphasis and call order refresh

* [refactor] Remove unecassary using

* [bug] Relocate authenticationStatus check logic to component

* [bug] Stop not clearing everything on state clean

* [style] Fix lint complaints

* [bug] Correct mismatched uses of encrypted and decrypted pin states

* Add browser specific state classes and methods

* lint fixes

* [bug] Migrate existing persistant data to new schema

* [style] Fix lint complaints

* [bug] Dont clear settings on state clean

* [bug] Maintain the right storage items on logout

* [chore] resolve issues from merge

* [bug] Resolve settings clearing on lock

* [chore] Added a comment

* [review] fromatting for code review

* Revert browser state items

Co-authored-by: Robyn MacCallum <nickersthecat@gmail.com>
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
This commit is contained in:
Addison Beck 2021-12-13 11:15:16 -05:00 committed by GitHub
parent 8fc3cf50d2
commit f90b3456d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 3644 additions and 1722 deletions

View File

@ -22,11 +22,11 @@ import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.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 { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.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'; import { Cipher } from 'jslib-common/models/domain/cipher';
@ -89,10 +89,10 @@ export class AddEditComponent implements OnInit {
constructor(protected cipherService: CipherService, protected folderService: FolderService, constructor(protected cipherService: CipherService, protected folderService: FolderService,
protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected auditService: AuditService, protected stateService: StateService, protected auditService: AuditService, protected stateService: StateService,
protected userService: UserService, protected collectionService: CollectionService, protected collectionService: CollectionService, protected messagingService: MessagingService,
protected messagingService: MessagingService, protected eventService: EventService, protected eventService: EventService, protected policyService: PolicyService,
protected policyService: PolicyService, protected passwordRepromptService: PasswordRepromptService, private logService: LogService, protected passwordRepromptService: PasswordRepromptService,
private logService: LogService) { private organizationService: OrganizationService) {
this.typeOptions = [ this.typeOptions = [
{ name: i18nService.t('typeLogin'), value: CipherType.Login }, { name: i18nService.t('typeLogin'), value: CipherType.Login },
{ name: i18nService.t('typeCard'), value: CipherType.Card }, { name: i18nService.t('typeCard'), value: CipherType.Card },
@ -160,11 +160,11 @@ export class AddEditComponent implements OnInit {
if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) { if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) {
this.allowPersonal = false; this.allowPersonal = false;
} else { } else {
const myEmail = await this.userService.getEmail(); const myEmail = await this.stateService.getEmail();
this.ownershipOptions.push({ name: myEmail, value: null }); 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 => { orgs.sort(Utils.getSortFunction(this.i18nService, 'name')).forEach(o => {
if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) { if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) {
this.ownershipOptions.push({ name: o.name, value: o.id }); this.ownershipOptions.push({ name: o.name, value: o.id });
@ -193,12 +193,12 @@ export class AddEditComponent implements OnInit {
this.title = this.i18nService.t('addItem'); this.title = this.i18nService.t('addItem');
} }
const addEditCipherInfo: any = await this.stateService.get<any>('addEditCipherInfo'); const addEditCipherInfo: any = await this.stateService.getAddEditCipherInfo();
if (addEditCipherInfo != null) { if (addEditCipherInfo != null) {
this.cipher = addEditCipherInfo.cipher; this.cipher = addEditCipherInfo.cipher;
this.collectionIds = addEditCipherInfo.collectionIds; this.collectionIds = addEditCipherInfo.collectionIds;
} }
await this.stateService.remove('addEditCipherInfo'); await this.stateService.setAddEditCipherInfo(null);
if (this.cipher == null) { if (this.cipher == null) {
if (this.editMode) { if (this.editMode) {
@ -442,7 +442,7 @@ export class AddEditComponent implements OnInit {
} }
if (this.cipher.organizationId != null) { if (this.cipher.organizationId != null) {
this.collections = this.writeableCollections.filter(c => c.organizationId === this.cipher.organizationId); 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) { if (org != null) {
this.cipher.organizationUseTotp = org.useTotp; this.cipher.organizationUseTotp = org.useTotp;
} }

View File

@ -12,7 +12,7 @@ import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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 { Cipher } from 'jslib-common/models/domain/cipher';
import { ErrorResponse } from 'jslib-common/models/response/errorResponse'; import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
@ -37,9 +37,9 @@ export class AttachmentsComponent implements OnInit {
emergencyAccessId?: string = null; emergencyAccessId?: string = null;
constructor(protected cipherService: CipherService, protected i18nService: I18nService, constructor(protected cipherService: CipherService, protected i18nService: I18nService,
protected cryptoService: CryptoService, protected userService: UserService, protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService,
protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, protected apiService: ApiService, protected win: Window,
protected win: Window, private logService: LogService) { } protected logService: LogService, protected stateService: StateService) { }
async ngOnInit() { async ngOnInit() {
await this.init(); await this.init();
@ -164,7 +164,7 @@ export class AttachmentsComponent implements OnInit {
this.cipher = await this.cipherDomain.decrypt(); this.cipher = await this.cipherDomain.decrypt();
this.hasUpdatedKey = await this.cryptoService.hasEncKey(); 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; this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null;
if (!this.canAccessAttachments) { if (!this.canAccessAttachments) {

View File

@ -45,7 +45,7 @@ export class AvatarComponent implements OnChanges, OnInit {
} }
private async generate() { private async generate() {
const enableGravatars = await this.stateService.get<boolean>('enableGravatars'); const enableGravatars = await this.stateService.getEnableGravitars();
if (enableGravatars && this.email != null) { if (enableGravatars && this.email != null) {
const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5'); const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5');
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase(); const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();

View File

@ -6,7 +6,7 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.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 { EncString } from 'jslib-common/models/domain/encString';
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions'; import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
@ -29,12 +29,12 @@ export class ChangePasswordComponent implements OnInit {
private masterPasswordStrengthTimeout: any; private masterPasswordStrengthTimeout: any;
constructor(protected i18nService: I18nService, protected cryptoService: CryptoService, constructor(protected i18nService: I18nService, protected cryptoService: CryptoService,
protected messagingService: MessagingService, protected userService: UserService, protected messagingService: MessagingService, protected passwordGenerationService: PasswordGenerationService,
protected passwordGenerationService: PasswordGenerationService, protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService,
protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService) { } protected stateService: StateService) { }
async ngOnInit() { async ngOnInit() {
this.email = await this.userService.getEmail(); this.email = await this.stateService.getEmail();
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
} }
@ -47,12 +47,12 @@ export class ChangePasswordComponent implements OnInit {
return; return;
} }
const email = await this.userService.getEmail(); const email = await this.stateService.getEmail();
if (this.kdf == null) { if (this.kdf == null) {
this.kdf = await this.userService.getKdf(); this.kdf = await this.stateService.getKdfType();
} }
if (this.kdfIterations == null) { 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(), const key = await this.cryptoService.makeKey(this.masterPassword, email.trim().toLowerCase(),
this.kdf, this.kdfIterations); this.kdf, this.kdfIterations);

View File

@ -14,10 +14,7 @@ import { TreeNode } from 'jslib-common/models/domain/treeNode';
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from 'jslib-common/abstractions/folder.service';
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StateService } from 'jslib-common/abstractions/state.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
@Directive() @Directive()
export class GroupingsComponent { export class GroupingsComponent {
@ -50,15 +47,12 @@ export class GroupingsComponent {
selectedCollectionId: string = null; selectedCollectionId: string = null;
private collapsedGroupings: Set<string>; private collapsedGroupings: Set<string>;
private collapsedGroupingsKey: string;
constructor(protected collectionService: CollectionService, protected folderService: FolderService, constructor(protected collectionService: CollectionService, protected folderService: FolderService,
protected storageService: StorageService, protected userService: UserService) { } protected stateService: StateService) { }
async load(setLoaded = true) { async load(setLoaded = true) {
const userId = await this.userService.getUserId(); const collapsedGroupings = await this.stateService.getCollapsedGroupings();
this.collapsedGroupingsKey = ConstantsService.collapsedGroupingsKey + '_' + userId;
const collapsedGroupings = await this.storageService.get<string[]>(this.collapsedGroupingsKey);
if (collapsedGroupings == null) { if (collapsedGroupings == null) {
this.collapsedGroupings = new Set<string>(); this.collapsedGroupings = new Set<string>();
} else { } else {
@ -149,7 +143,7 @@ export class GroupingsComponent {
this.selectedCollectionId = null; this.selectedCollectionId = null;
} }
collapse(grouping: FolderView | CollectionView, idPrefix = '') { async collapse(grouping: FolderView | CollectionView, idPrefix = '') {
if (grouping.id == null) { if (grouping.id == null) {
return; return;
} }
@ -159,7 +153,7 @@ export class GroupingsComponent {
} else { } else {
this.collapsedGroupings.add(id); this.collapsedGroupings.add(id);
} }
this.storageService.save(this.collapsedGroupingsKey, this.collapsedGroupings); await this.stateService.setCollapsedGroupings(this.collapsedGroupings);
} }
isCollapsed(grouping: FolderView | CollectionView, idPrefix = '') { isCollapsed(grouping: FolderView | CollectionView, idPrefix = '') {

View File

@ -11,8 +11,6 @@ import { CipherView } from 'jslib-common/models/view/cipherView';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from 'jslib-common/abstractions/state.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from 'jslib-common/misc/utils';
const IconMap: any = { const IconMap: any = {
@ -37,7 +35,7 @@ export class IconComponent implements OnChanges {
private iconsUrl: string; private iconsUrl: string;
constructor(environmentService: EnvironmentService, protected stateService: StateService) { constructor(environmentService: EnvironmentService, private stateService: StateService) {
this.iconsUrl = environmentService.getIconsUrl(); this.iconsUrl = environmentService.getIconsUrl();
} }
@ -46,7 +44,7 @@ export class IconComponent implements OnChanges {
// to avoid this we reset all state variables. // to avoid this we reset all state variables.
this.image = null; this.image = null;
this.fallbackImage = null; this.fallbackImage = null;
this.imageEnabled = !(await this.stateService.get<boolean>(ConstantsService.disableFaviconKey)); this.imageEnabled = !(await this.stateService.getDisableFavicon());
this.load(); this.load();
} }

View File

@ -11,12 +11,8 @@ import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.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 { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { EncString } from 'jslib-common/models/domain/encString'; import { EncString } from 'jslib-common/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; 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 { Utils } from 'jslib-common/misc/utils';
import { HashPurpose } from 'jslib-common/enums/hashPurpose'; import { HashPurpose } from 'jslib-common/enums/hashPurpose';
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
@Directive() @Directive()
export class LockComponent implements OnInit { export class LockComponent implements OnInit {
@ -41,38 +38,22 @@ export class LockComponent implements OnInit {
hideInput: boolean; hideInput: boolean;
protected successRoute: string = 'vault'; protected successRoute: string = 'vault';
protected onSuccessfulSubmit: () => void; protected onSuccessfulSubmit: () => Promise<void>;
private invalidPinAttempts = 0; private invalidPinAttempts = 0;
private pinSet: [boolean, boolean]; private pinSet: [boolean, boolean];
constructor(protected router: Router, protected i18nService: I18nService, constructor(protected router: Router, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService, protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService,
protected userService: UserService, protected cryptoService: CryptoService, protected cryptoService: CryptoService, protected vaultTimeoutService: VaultTimeoutService,
protected storageService: StorageService, protected vaultTimeoutService: VaultTimeoutService,
protected environmentService: EnvironmentService, protected stateService: StateService, protected environmentService: EnvironmentService, protected stateService: StateService,
protected apiService: ApiService, private logService: LogService, protected apiService: ApiService, private logService: LogService,
private keyConnectorService: KeyConnectorService, protected ngZone: NgZone) { } private keyConnectorService: KeyConnectorService, protected ngZone: NgZone) { }
async ngOnInit() { async ngOnInit() {
this.pinSet = await this.vaultTimeoutService.isPinLockSet(); this.stateService.activeAccount.subscribe(async _userId => {
this.pinLock = (this.pinSet[0] && this.vaultTimeoutService.pinProtectedKey != null) || this.pinSet[1]; await this.load();
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);
} }
async submit() { async submit() {
@ -87,17 +68,17 @@ export class LockComponent implements OnInit {
return; return;
} }
const kdf = await this.userService.getKdf(); const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.userService.getKdfIterations(); const kdfIterations = await this.stateService.getKdfIterations();
if (this.pinLock) { if (this.pinLock) {
let failed = true; let failed = true;
try { try {
if (this.pinSet[0]) { if (this.pinSet[0]) {
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations, 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 encKey = await this.cryptoService.getEncKey(key);
const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin); const protectedPin = await this.stateService.getProtectedPin();
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey); const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
failed = decPin !== this.pin; failed = decPin !== this.pin;
if (!failed) { if (!failed) {
@ -148,13 +129,13 @@ export class LockComponent implements OnInit {
if (passwordValid) { if (passwordValid) {
if (this.pinSet[0]) { if (this.pinSet[0]) {
const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin); const protectedPin = await this.stateService.getProtectedPin();
const encKey = await this.cryptoService.getEncKey(key); const encKey = await this.cryptoService.getEncKey(key);
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey); const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfIterations); 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 { } else {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidMasterPassword')); this.i18nService.t('invalidMasterPassword'));
@ -175,7 +156,7 @@ export class LockComponent implements OnInit {
return; return;
} }
const success = (await this.cryptoService.getKey('biometric')) != null; const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
if (success) { if (success) {
await this.doContinue(); await this.doContinue();
@ -196,19 +177,40 @@ export class LockComponent implements OnInit {
private async setKeyAndContinue(key: SymmetricCryptoKey) { private async setKeyAndContinue(key: SymmetricCryptoKey) {
await this.cryptoService.setKey(key); await this.cryptoService.setKey(key);
this.doContinue(); await this.doContinue();
} }
private async doContinue() { private async doContinue() {
this.vaultTimeoutService.biometricLocked = false; await this.stateService.setBiometricLocked(false);
this.vaultTimeoutService.everBeenUnlocked = true; await this.stateService.setEverBeenUnlocked(true);
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey); const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); await this.stateService.setDisableFavicon(!!disableFavicon);
this.messagingService.send('unlocked'); this.messagingService.send('unlocked');
if (this.onSuccessfulSubmit != null) { if (this.onSuccessfulSubmit != null) {
this.onSuccessfulSubmit(); await this.onSuccessfulSubmit();
} else if (this.router != null) { } else if (this.router != null) {
this.router.navigate([this.successRoute]); 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);
}
} }

View File

@ -19,19 +19,11 @@ import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.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 { Utils } from 'jslib-common/misc/utils';
import { CaptchaProtectedComponent } from './captchaProtected.component'; import { CaptchaProtectedComponent } from './captchaProtected.component';
const Keys = {
rememberedEmail: 'rememberedEmail',
rememberEmail: 'rememberEmail',
};
@Directive() @Directive()
export class LoginComponent extends CaptchaProtectedComponent implements OnInit { export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
@Input() email: string = ''; @Input() email: string = '';
@ -53,22 +45,19 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
platformUtilsService: PlatformUtilsService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, i18nService: I18nService,
protected stateService: StateService, environmentService: EnvironmentService, protected stateService: StateService, environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService, protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService, private storageService: StorageService, protected cryptoFunctionService: CryptoFunctionService, protected logService: LogService,
protected logService: LogService, protected ngZone: NgZone) { protected ngZone: NgZone) {
super(environmentService, i18nService, platformUtilsService); super(environmentService, i18nService, platformUtilsService);
} }
async ngOnInit() { async ngOnInit() {
if (this.email == null || this.email === '') { if (this.email == null || this.email === '') {
this.email = await this.storageService.get<string>(Keys.rememberedEmail); this.email = await this.stateService.getRememberedEmail();
if (this.email == null) { if (this.email == null) {
this.email = ''; this.email = '';
} }
} }
this.rememberEmail = await this.storageService.get<boolean>(Keys.rememberEmail); this.rememberEmail = await this.stateService.getRememberedEmail() != null;
if (this.rememberEmail == null) {
this.rememberEmail = true;
}
if (Utils.isBrowser && !Utils.isNode) { if (Utils.isBrowser && !Utils.isNode) {
this.focusInput(); this.focusInput();
} }
@ -96,11 +85,10 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
try { try {
this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken); this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken);
const response = await this.formPromise; const response = await this.formPromise;
await this.storageService.save(Keys.rememberEmail, this.rememberEmail);
if (this.rememberEmail) { if (this.rememberEmail) {
await this.storageService.save(Keys.rememberedEmail, this.email); await this.stateService.setRememberedEmail(this.email);
} else { } else {
await this.storageService.remove(Keys.rememberedEmail); await this.stateService.setRememberedEmail(null);
} }
if (this.handleCaptchaRequired(response)) { if (this.handleCaptchaRequired(response)) {
return; return;
@ -117,8 +105,8 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
this.router.navigate([this.forcePasswordResetRoute]); this.router.navigate([this.forcePasswordResetRoute]);
} }
} else { } else {
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey); const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) { if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin(); this.onSuccessfulLogin();
} }
@ -158,8 +146,8 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
// Save sso params // Save sso params
await this.storageService.save(ConstantsService.ssoStateKey, state); await this.stateService.setSsoState(state);
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, ssoCodeVerifier); await this.stateService.setSsoCodeVerifier(ssoCodeVerifier);
// Build URI // Build URI
const webUrl = this.environmentService.getWebVaultUrl(); const webUrl = this.environmentService.getWebVaultUrl();

View File

@ -4,7 +4,7 @@ import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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() @Directive()
export class PremiumComponent implements OnInit { export class PremiumComponent implements OnInit {
@ -13,10 +13,11 @@ export class PremiumComponent implements OnInit {
refreshPromise: Promise<any>; refreshPromise: Promise<any>;
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, 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() { async ngOnInit() {
this.isPremium = await this.userService.canAccessPremium(); this.isPremium = await this.stateService.getCanAccessPremium();
} }
async refresh() { async refresh() {
@ -24,7 +25,7 @@ export class PremiumComponent implements OnInit {
this.refreshPromise = this.apiService.refreshIdentityToken(); this.refreshPromise = this.apiService.refreshIdentityToken();
await this.refreshPromise; await this.refreshPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('refreshComplete')); this.platformUtilsService.showToast('success', null, this.i18nService.t('refreshComplete'));
this.isPremium = await this.userService.canAccessPremium(); this.isPremium = await this.stateService.getCanAccessPremium();
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }

View File

@ -141,7 +141,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
const request = new RegisterRequest(this.email, this.name, hashedPassword, const request = new RegisterRequest(this.email, this.name, hashedPassword,
this.hint, encKey[1].encryptedString, kdf, kdfIterations, this.referenceData, this.captchaToken); this.hint, encKey[1].encryptedString, kdf, kdfIterations, this.referenceData, this.captchaToken);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString); request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.get<any>('orgInvitation'); const orgInvite = await this.stateService.getOrganizationInvitation();
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) { if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
request.token = orgInvite.token; request.token = orgInvite.token;
request.organizationUserId = orgInvite.organizationUserId; request.organizationUserId = orgInvite.organizationUserId;

View File

@ -8,11 +8,8 @@ import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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 { 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'; import { Organization } from 'jslib-common/models/domain/organization';
@ -27,14 +24,14 @@ export class RemovePasswordComponent implements OnInit {
organization: Organization; organization: Organization;
email: string; email: string;
constructor(private router: Router, private userService: UserService, constructor(private router: Router, private stateService: StateService,
private apiService: ApiService, private syncService: SyncService, private apiService: ApiService, private syncService: SyncService,
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private keyConnectorService: KeyConnectorService, private storageService: StorageService) { } private keyConnectorService: KeyConnectorService) { }
async ngOnInit() { async ngOnInit() {
this.organization = await this.keyConnectorService.getManagingOrganization(); this.organization = await this.keyConnectorService.getManagingOrganization();
this.email = await this.userService.getEmail(); this.email = await this.stateService.getEmail();
await this.syncService.fullSync(false); await this.syncService.fullSync(false);
this.loading = false; this.loading = false;
} }

View File

@ -17,7 +17,7 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SendService } from 'jslib-common/abstractions/send.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 { SendFileView } from 'jslib-common/models/view/sendFileView';
import { SendTextView } from 'jslib-common/models/view/sendTextView'; import { SendTextView } from 'jslib-common/models/view/sendTextView';
@ -57,9 +57,9 @@ export class AddEditComponent implements OnInit {
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService, protected datePipe: DatePipe, protected environmentService: EnvironmentService, protected datePipe: DatePipe,
protected sendService: SendService, protected userService: UserService, protected sendService: SendService, protected messagingService: MessagingService,
protected messagingService: MessagingService, protected policyService: PolicyService, protected policyService: PolicyService, private logService: LogService,
private logService: LogService) { protected stateService: StateService) {
this.typeOptions = [ this.typeOptions = [
{ name: i18nService.t('sendTypeFile'), value: SendType.File }, { name: i18nService.t('sendTypeFile'), value: SendType.File },
{ name: i18nService.t('sendTypeText'), value: SendType.Text }, { name: i18nService.t('sendTypeText'), value: SendType.Text },
@ -108,8 +108,8 @@ export class AddEditComponent implements OnInit {
this.disableHideEmail = await this.policyService.policyAppliesToUser(PolicyType.SendOptions, this.disableHideEmail = await this.policyService.policyAppliesToUser(PolicyType.SendOptions,
p => p.data.disableHideEmail); p => p.data.disableHideEmail);
this.canAccessPremium = await this.userService.canAccessPremium(); this.canAccessPremium = await this.stateService.getCanAccessPremium();
this.emailVerified = await this.userService.getEmailVerified(); this.emailVerified = await this.stateService.getEmailVerified();
if (!this.canAccessPremium || !this.emailVerified) { if (!this.canAccessPremium || !this.emailVerified) {
this.type = SendType.Text; this.type = SendType.Text;
} }

View File

@ -16,7 +16,6 @@ import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.se
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from 'jslib-common/abstractions/search.service';
import { SendService } from 'jslib-common/abstractions/send.service'; import { SendService } from 'jslib-common/abstractions/send.service';
import { UserService } from 'jslib-common/abstractions/user.service';
@Directive() @Directive()
export class SendComponent implements OnInit { export class SendComponent implements OnInit {
@ -48,8 +47,7 @@ export class SendComponent implements OnInit {
constructor(protected sendService: SendService, protected i18nService: I18nService, constructor(protected sendService: SendService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService, protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService,
protected ngZone: NgZone, protected searchService: SearchService, protected ngZone: NgZone, protected searchService: SearchService,
protected policyService: PolicyService, protected userService: UserService, protected policyService: PolicyService, private logService: LogService) { }
private logService: LogService) { }
async ngOnInit() { async ngOnInit() {
this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend); this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend);

View File

@ -13,8 +13,8 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.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 { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { EncString } from 'jslib-common/models/domain/encString'; import { EncString } from 'jslib-common/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
@ -42,12 +42,13 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
onSuccessfulChangePassword: () => Promise<any>; onSuccessfulChangePassword: () => Promise<any>;
successRoute = 'vault'; successRoute = 'vault';
constructor(i18nService: I18nService, cryptoService: CryptoService, messagingService: MessagingService, constructor(i18nService: I18nService, cryptoService: CryptoService,
userService: UserService, passwordGenerationService: PasswordGenerationService, messagingService: MessagingService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService, protected router: Router, platformUtilsService: PlatformUtilsService, policyService: PolicyService,
private apiService: ApiService, private syncService: SyncService, private route: ActivatedRoute) { protected router: Router, private apiService: ApiService,
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, private syncService: SyncService, private route: ActivatedRoute, stateService: StateService) {
platformUtilsService, policyService); super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, stateService);
} }
async ngOnInit() { async ngOnInit() {
@ -102,7 +103,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
if (response == null) { if (response == null) {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError')); 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); const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key // 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]) { private async onSetPasswordSuccess(key: SymmetricCryptoKey, encKey: [SymmetricCryptoKey, EncString], keys: [string, EncString]) {
await this.userService.setInformation(await this.userService.getUserId(), await this.userService.getEmail(), await this.stateService.setKdfType(this.kdf);
this.kdf, this.kdfIterations); await this.stateService.setKdfIterations(this.kdfIterations);
await this.cryptoService.setKey(key); await this.cryptoService.setKey(key);
await this.cryptoService.setEncKey(encKey[1].encryptedString); await this.cryptoService.setEncKey(encKey[1].encryptedString);
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString); await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);

View File

@ -5,11 +5,7 @@ import {
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StateService } from 'jslib-common/abstractions/state.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 { Utils } from 'jslib-common/misc/utils'; import { Utils } from 'jslib-common/misc/utils';
@ -17,15 +13,13 @@ import { ModalRef } from './modal/modal.ref';
@Directive() @Directive()
export class SetPinComponent implements OnInit { export class SetPinComponent implements OnInit {
pin = ''; pin = '';
showPin = false; showPin = false;
masterPassOnRestart = true; masterPassOnRestart = true;
showMasterPassOnRestart = true; showMasterPassOnRestart = true;
constructor(private modalRef: ModalRef, private cryptoService: CryptoService, private userService: UserService, constructor(private modalRef: ModalRef, private cryptoService: CryptoService,
private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService, private keyConnectorService: KeyConnectorService, private stateService: StateService) { }
private keyConnectorService: KeyConnectorService) { }
async ngOnInit() { async ngOnInit() {
this.showMasterPassOnRestart = this.masterPassOnRestart = !await this.keyConnectorService.getUsesKeyConnector(); this.showMasterPassOnRestart = this.masterPassOnRestart = !await this.keyConnectorService.getUsesKeyConnector();
@ -40,18 +34,18 @@ export class SetPinComponent implements OnInit {
this.modalRef.close(false); this.modalRef.close(false);
} }
const kdf = await this.userService.getKdf(); const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.userService.getKdfIterations(); const kdfIterations = await this.stateService.getKdfIterations();
const email = await this.userService.getEmail(); const email = await this.stateService.getEmail();
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations); const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations);
const key = await this.cryptoService.getKey(); const key = await this.cryptoService.getKey();
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey); const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
if (this.masterPassOnRestart) { if (this.masterPassOnRestart) {
const encPin = await this.cryptoService.encrypt(this.pin); const encPin = await this.cryptoService.encrypt(this.pin);
await this.storageService.save(ConstantsService.protectedPin, encPin.encryptedString); await this.stateService.setProtectedPin(encPin.encryptedString);
this.vaultTimeoutService.pinProtectedKey = pinProtectedKey; await this.stateService.setDecryptedPinProtected(pinProtectedKey);
} else { } else {
await this.storageService.save(ConstantsService.pinProtectedKey, pinProtectedKey.encryptedString); await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString);
} }
this.modalRef.close(true); this.modalRef.close(true);

View File

@ -12,8 +12,8 @@ import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.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 { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { Organization } from 'jslib-common/models/domain/organization'; import { Organization } from 'jslib-common/models/domain/organization';
import { CipherView } from 'jslib-common/models/view/cipherView'; import { CipherView } from 'jslib-common/models/view/cipherView';
@ -35,8 +35,8 @@ export class ShareComponent implements OnInit {
protected writeableCollections: CollectionView[] = []; protected writeableCollections: CollectionView[] = [];
constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService, constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService, protected userService: UserService, protected i18nService: I18nService, protected cipherService: CipherService,
protected cipherService: CipherService, private logService: LogService) { } private logService: LogService, protected organizationService: OrganizationService) { }
async ngOnInit() { async ngOnInit() {
await this.load(); await this.load();
@ -45,7 +45,7 @@ export class ShareComponent implements OnInit {
async load() { async load() {
const allCollections = await this.collectionService.getAllDecrypted(); const allCollections = await this.collectionService.getAllDecrypted();
this.writeableCollections = allCollections.map(c => c).filter(c => !c.readOnly); 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')) this.organizations = orgs.sort(Utils.getSortFunction(this.i18nService, 'name'))
.filter(o => o.enabled && o.status === OrganizationUserStatusType.Confirmed); .filter(o => o.enabled && o.status === OrganizationUserStatusType.Confirmed);

View File

@ -15,9 +15,6 @@ import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.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 { Utils } from 'jslib-common/misc/utils';
@ -47,18 +44,18 @@ export class SsoComponent {
constructor(protected authService: AuthService, protected router: Router, constructor(protected authService: AuthService, protected router: Router,
protected i18nService: I18nService, protected route: ActivatedRoute, protected i18nService: I18nService, protected route: ActivatedRoute,
protected storageService: StorageService, protected stateService: StateService, protected stateService: StateService, protected platformUtilsService: PlatformUtilsService,
protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, protected apiService: ApiService, protected cryptoFunctionService: CryptoFunctionService,
protected cryptoFunctionService: CryptoFunctionService, protected environmentService: EnvironmentService, protected environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService,
protected passwordGenerationService: PasswordGenerationService, protected logService: LogService) { } protected logService: LogService) { }
async ngOnInit() { async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.code != null && qParams.state != null) { if (qParams.code != null && qParams.state != null) {
const codeVerifier = await this.storageService.get<string>(ConstantsService.ssoCodeVerifierKey); const codeVerifier = await this.stateService.getSsoCodeVerifier();
const state = await this.storageService.get<string>(ConstantsService.ssoStateKey); const state = await this.stateService.getSsoState();
await this.storageService.remove(ConstantsService.ssoCodeVerifierKey); await this.stateService.setSsoCodeVerifier(null);
await this.storageService.remove(ConstantsService.ssoStateKey); await this.stateService.setSsoState(null);
if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) { if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) {
await this.logIn(qParams.code, codeVerifier, this.getOrgIdentifierFromState(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 codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256'); const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier); await this.stateService.setSsoCodeVerifier(codeVerifier);
} }
if (state == null) { if (state == null) {
@ -120,7 +117,7 @@ export class SsoComponent {
state += `_identifier=${this.identifier}`; state += `_identifier=${this.identifier}`;
// Save state (regardless of new or existing) // 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?' + let authorizeUrl = this.environmentService.getIdentityUrl() + '/connect/authorize?' +
'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + 'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' +
@ -170,8 +167,8 @@ export class SsoComponent {
this.router.navigate([this.forcePasswordResetRoute]); this.router.navigate([this.forcePasswordResetRoute]);
} }
} else { } else {
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey); const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) { if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin(); this.onSuccessfulLogin();
} }

View File

@ -20,10 +20,8 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.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 { TwoFactorProviders } from 'jslib-common/services/auth.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import * as DuoWebSDK from 'duo_web_sdk'; import * as DuoWebSDK from 'duo_web_sdk';
import { WebAuthnIFrame } from 'jslib-common/misc/webauthn_iframe'; 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 i18nService: I18nService, protected apiService: ApiService,
protected platformUtilsService: PlatformUtilsService, protected win: Window, protected platformUtilsService: PlatformUtilsService, protected win: Window,
protected environmentService: EnvironmentService, protected stateService: StateService, protected environmentService: EnvironmentService, protected stateService: StateService,
protected storageService: StorageService, protected route: ActivatedRoute, protected route: ActivatedRoute, protected logService: LogService) {
protected logService: LogService) {
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
} }
@ -179,8 +176,8 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
async doSubmit() { async doSubmit() {
this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember); this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember);
const response: AuthResult = await this.formPromise; const response: AuthResult = await this.formPromise;
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey); const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); await this.stateService.setDisableFavicon(!!disableFavicon);
if (this.onSuccessfulLogin != null) { if (this.onSuccessfulLogin != null) {
this.onSuccessfulLogin(); this.onSuccessfulLogin();
} }

View File

@ -8,8 +8,8 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.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 { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component'; import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component';
@ -30,11 +30,11 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService, passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, userService: UserService, cryptoService: CryptoService, messagingService: MessagingService,
messagingService: MessagingService, private apiService: ApiService, private apiService: ApiService, stateService: StateService,
private syncService: SyncService, private logService: LogService) { private syncService: SyncService, private logService: LogService) {
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService); platformUtilsService, policyService, stateService);
} }
async ngOnInit() { async ngOnInit() {
@ -49,9 +49,9 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
async setupSubmitActions(): Promise<boolean> { async setupSubmitActions(): Promise<boolean> {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
this.email = await this.userService.getEmail(); this.email = await this.stateService.getEmail();
this.kdf = await this.userService.getKdf(); this.kdf = await this.stateService.getKdfType();
this.kdfIterations = await this.userService.getKdfIterations(); this.kdfIterations = await this.stateService.getKdfIterations();
return true; return true;
} }

View File

@ -24,9 +24,9 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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 { TokenService } from 'jslib-common/abstractions/token.service';
import { TotpService } from 'jslib-common/abstractions/totp.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'; 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 cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService,
protected auditService: AuditService, protected win: Window, protected auditService: AuditService, protected win: Window,
protected broadcasterService: BroadcasterService, protected ngZone: NgZone, protected broadcasterService: BroadcasterService, protected ngZone: NgZone,
protected changeDetectorRef: ChangeDetectorRef, protected userService: UserService, protected changeDetectorRef: ChangeDetectorRef, protected eventService: EventService,
protected eventService: EventService, protected apiService: ApiService, protected apiService: ApiService, protected passwordRepromptService: PasswordRepromptService,
protected passwordRepromptService: PasswordRepromptService, private logService: LogService) { } private logService: LogService, protected stateService: StateService) { }
ngOnInit() { ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
@ -96,7 +96,7 @@ export class ViewComponent implements OnDestroy, OnInit {
const cipher = await this.cipherService.get(this.cipherId); const cipher = await this.cipherService.get(this.cipherId);
this.cipher = await cipher.decrypt(); 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 && if (this.cipher.type === CipherType.Login && this.cipher.login.totp &&
(cipher.organizationUseTotp || this.canAccessPremium)) { (cipher.organizationUseTotp || this.canAccessPremium)) {

View File

@ -8,16 +8,17 @@ import {
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.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'; import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
@Injectable() @Injectable()
export class AuthGuardService implements CanActivate { export class AuthGuardService implements CanActivate {
constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService, constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
private router: Router, private messagingService: MessagingService, private keyConnectorService: KeyConnectorService) { } private messagingService: MessagingService, private keyConnectorService: KeyConnectorService,
private stateService: StateService) { }
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) { async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
const isAuthed = await this.userService.isAuthenticated(); const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) { if (!isAuthed) {
this.messagingService.send('authBlocked'); this.messagingService.send('authBlocked');
return false; return false;

View File

@ -19,16 +19,18 @@ import { FileUploadService } from 'jslib-common/services/fileUpload.service';
import { FolderService } from 'jslib-common/services/folder.service'; import { FolderService } from 'jslib-common/services/folder.service';
import { KeyConnectorService } from 'jslib-common/services/keyConnector.service'; import { KeyConnectorService } from 'jslib-common/services/keyConnector.service';
import { NotificationsService } from 'jslib-common/services/notifications.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 { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
import { PolicyService } from 'jslib-common/services/policy.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 { SearchService } from 'jslib-common/services/search.service';
import { SendService } from 'jslib-common/services/send.service'; import { SendService } from 'jslib-common/services/send.service';
import { SettingsService } from 'jslib-common/services/settings.service'; import { SettingsService } from 'jslib-common/services/settings.service';
import { StateService } from 'jslib-common/services/state.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 { SyncService } from 'jslib-common/services/sync.service';
import { TokenService } from 'jslib-common/services/token.service'; import { TokenService } from 'jslib-common/services/token.service';
import { TotpService } from 'jslib-common/services/totp.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 { UserVerificationService } from 'jslib-common/services/userVerification.service';
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service'; import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service';
import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunction.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 { CollectionService as CollectionServiceAbstraction } from 'jslib-common/abstractions/collection.service';
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service'; import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.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 { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service';
import { ExportService as ExportServiceAbstraction } from 'jslib-common/abstractions/export.service'; import { ExportService as ExportServiceAbstraction } from 'jslib-common/abstractions/export.service';
import { FileUploadService as FileUploadServiceAbstraction } from 'jslib-common/abstractions/fileUpload.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 { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service'; import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service';
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service'; import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service';
import { OrganizationService as OrganizationServiceAbstraction } from 'jslib-common/abstractions/organization.service';
import { import {
PasswordGenerationService as PasswordGenerationServiceAbstraction, PasswordGenerationService as PasswordGenerationServiceAbstraction,
} from 'jslib-common/abstractions/passwordGeneration.service'; } from 'jslib-common/abstractions/passwordGeneration.service';
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service'; import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.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 { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service';
import { SendService as SendServiceAbstraction } from 'jslib-common/abstractions/send.service'; import { SendService as SendServiceAbstraction } from 'jslib-common/abstractions/send.service';
import { SettingsService as SettingsServiceAbstraction } from 'jslib-common/abstractions/settings.service'; import { SettingsService as SettingsServiceAbstraction } from 'jslib-common/abstractions/settings.service';
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.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 { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service'; import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service';
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service'; import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service';
import { TotpService as TotpServiceAbstraction } from 'jslib-common/abstractions/totp.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 { UserVerificationService as UserVerificationServiceAbstraction } from 'jslib-common/abstractions/userVerification.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
@ -108,7 +112,6 @@ import { ValidationService } from './validation.service';
deps: [ deps: [
CryptoServiceAbstraction, CryptoServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
UserServiceAbstraction,
TokenServiceAbstraction, TokenServiceAbstraction,
AppIdServiceAbstraction, AppIdServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
@ -117,28 +120,41 @@ import { ValidationService } from './validation.service';
VaultTimeoutServiceAbstraction, VaultTimeoutServiceAbstraction,
LogService, LogService,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
EnvironmentServiceAbstraction,
KeyConnectorServiceAbstraction, KeyConnectorServiceAbstraction,
EnvironmentServiceAbstraction,
StateServiceAbstraction,
], ],
}, },
{ {
provide: CipherServiceAbstraction, provide: CipherServiceAbstraction,
useFactory: (cryptoService: CryptoServiceAbstraction, userService: UserServiceAbstraction, useFactory: (
settingsService: SettingsServiceAbstraction, apiService: ApiServiceAbstraction, cryptoService: CryptoServiceAbstraction,
fileUploadService: FileUploadServiceAbstraction, storageService: StorageServiceAbstraction, settingsService: SettingsServiceAbstraction,
i18nService: I18nServiceAbstraction, injector: Injector, logService: LogService) => apiService: ApiServiceAbstraction,
new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService, fileUploadService: FileUploadServiceAbstraction,
storageService, i18nService, () => injector.get(SearchServiceAbstraction), logService), i18nService: I18nServiceAbstraction,
injector: Injector,
logService: LogService,
stateService: StateServiceAbstraction,
) => new CipherService(
cryptoService,
settingsService,
apiService,
fileUploadService,
i18nService,
() => injector.get(SearchServiceAbstraction),
logService,
stateService,
),
deps: [ deps: [
CryptoServiceAbstraction, CryptoServiceAbstraction,
UserServiceAbstraction,
SettingsServiceAbstraction, SettingsServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
FileUploadServiceAbstraction, FileUploadServiceAbstraction,
StorageServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
Injector, // TODO: Get rid of this circular dependency! Injector, // TODO: Get rid of this circular dependency!
LogService, LogService,
StateServiceAbstraction,
], ],
}, },
{ {
@ -146,11 +162,10 @@ import { ValidationService } from './validation.service';
useClass: FolderService, useClass: FolderService,
deps: [ deps: [
CryptoServiceAbstraction, CryptoServiceAbstraction,
UserServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
StorageServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
CipherServiceAbstraction, CipherServiceAbstraction,
StateServiceAbstraction,
], ],
}, },
{ provide: LogService, useFactory: () => new ConsoleLogService(false) }, { provide: LogService, useFactory: () => new ConsoleLogService(false) },
@ -159,35 +174,33 @@ import { ValidationService } from './validation.service';
useClass: CollectionService, useClass: CollectionService,
deps: [ deps: [
CryptoServiceAbstraction, CryptoServiceAbstraction,
UserServiceAbstraction,
StorageServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
StateServiceAbstraction,
], ],
}, },
{ {
provide: EnvironmentServiceAbstraction, provide: EnvironmentServiceAbstraction,
useClass: EnvironmentService, useClass: EnvironmentService,
deps: [StorageServiceAbstraction], deps: [StateServiceAbstraction],
}, },
{ {
provide: TotpServiceAbstraction, provide: TotpServiceAbstraction,
useClass: TotpService, useClass: TotpService,
deps: [ deps: [
StorageServiceAbstraction,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
LogService, LogService,
StateServiceAbstraction,
], ],
}, },
{ provide: TokenServiceAbstraction, useClass: TokenService, deps: [StorageServiceAbstraction] }, { provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] },
{ {
provide: CryptoServiceAbstraction, provide: CryptoServiceAbstraction,
useClass: CryptoService, useClass: CryptoService,
deps: [ deps: [
StorageServiceAbstraction,
'SECURE_STORAGE',
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction, PlatformUtilsServiceAbstraction,
LogService, LogService,
StateServiceAbstraction,
], ],
}, },
{ {
@ -195,8 +208,8 @@ import { ValidationService } from './validation.service';
useClass: PasswordGenerationService, useClass: PasswordGenerationService,
deps: [ deps: [
CryptoServiceAbstraction, CryptoServiceAbstraction,
StorageServiceAbstraction,
PolicyServiceAbstraction, PolicyServiceAbstraction,
StateServiceAbstraction,
], ],
}, },
{ {
@ -222,71 +235,121 @@ import { ValidationService } from './validation.service';
}, },
{ {
provide: SyncServiceAbstraction, provide: SyncServiceAbstraction,
useFactory: (userService: UserServiceAbstraction, apiService: ApiServiceAbstraction, useFactory: (
settingsService: SettingsServiceAbstraction, folderService: FolderServiceAbstraction, apiService: ApiServiceAbstraction,
cipherService: CipherServiceAbstraction, cryptoService: CryptoServiceAbstraction, settingsService: SettingsServiceAbstraction,
collectionService: CollectionServiceAbstraction, storageService: StorageServiceAbstraction, folderService: FolderServiceAbstraction,
messagingService: MessagingServiceAbstraction, policyService: PolicyServiceAbstraction, cipherService: CipherServiceAbstraction,
sendService: SendServiceAbstraction, logService: LogService, tokenService: TokenService, cryptoService: CryptoServiceAbstraction,
keyConnectorService: KeyConnectorServiceAbstraction) => new SyncService(userService, apiService, collectionService: CollectionServiceAbstraction,
settingsService, folderService, cipherService, cryptoService, collectionService, storageService, messagingService: MessagingServiceAbstraction,
messagingService, policyService, sendService, logService, tokenService, keyConnectorService, policyService: PolicyServiceAbstraction,
async (expired: boolean) => messagingService.send('logout', { expired: expired })), 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: [ deps: [
UserServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
SettingsServiceAbstraction, SettingsServiceAbstraction,
FolderServiceAbstraction, FolderServiceAbstraction,
CipherServiceAbstraction, CipherServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
CollectionServiceAbstraction, CollectionServiceAbstraction,
StorageServiceAbstraction,
MessagingServiceAbstraction, MessagingServiceAbstraction,
PolicyServiceAbstraction, PolicyServiceAbstraction,
SendServiceAbstraction, SendServiceAbstraction,
LogService, LogService,
TokenServiceAbstraction,
KeyConnectorServiceAbstraction, KeyConnectorServiceAbstraction,
StateServiceAbstraction,
OrganizationServiceAbstraction,
ProviderServiceAbstraction,
], ],
}, },
{
provide: UserServiceAbstraction,
useClass: UserService,
deps: [TokenServiceAbstraction, StorageServiceAbstraction],
},
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService }, { provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
{ {
provide: SettingsServiceAbstraction, provide: SettingsServiceAbstraction,
useClass: SettingsService, useClass: SettingsService,
deps: [UserServiceAbstraction, StorageServiceAbstraction], deps: [StateServiceAbstraction],
}, },
{ {
provide: VaultTimeoutServiceAbstraction, provide: VaultTimeoutServiceAbstraction,
useFactory: (cipherService: CipherServiceAbstraction, folderService: FolderServiceAbstraction, useFactory: (
collectionService: CollectionServiceAbstraction, cryptoService: CryptoServiceAbstraction, cipherService: CipherServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction, storageService: StorageServiceAbstraction, folderService: FolderServiceAbstraction,
messagingService: MessagingServiceAbstraction, searchService: SearchServiceAbstraction, collectionService: CollectionServiceAbstraction,
userService: UserServiceAbstraction, tokenService: TokenServiceAbstraction, cryptoService: CryptoServiceAbstraction,
policyService: PolicyServiceAbstraction, keyConnectorService: KeyConnectorServiceAbstraction) => platformUtilsService: PlatformUtilsServiceAbstraction,
new VaultTimeoutService(cipherService, folderService, collectionService, cryptoService, messagingService: MessagingServiceAbstraction,
platformUtilsService, storageService, messagingService, searchService, userService, tokenService, searchService: SearchServiceAbstraction,
policyService, keyConnectorService, null, tokenService: TokenServiceAbstraction,
async () => messagingService.send('logout', { expired: false })), 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: [ deps: [
CipherServiceAbstraction, CipherServiceAbstraction,
FolderServiceAbstraction, FolderServiceAbstraction,
CollectionServiceAbstraction, CollectionServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
PlatformUtilsServiceAbstraction, PlatformUtilsServiceAbstraction,
StorageServiceAbstraction,
MessagingServiceAbstraction, MessagingServiceAbstraction,
SearchServiceAbstraction, SearchServiceAbstraction,
UserServiceAbstraction,
TokenServiceAbstraction, TokenServiceAbstraction,
PolicyServiceAbstraction, 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, provide: ExportServiceAbstraction,
useClass: ExportService, useClass: ExportService,
@ -308,14 +371,26 @@ import { ValidationService } from './validation.service';
}, },
{ {
provide: NotificationsServiceAbstraction, provide: NotificationsServiceAbstraction,
useFactory: (userService: UserServiceAbstraction, syncService: SyncServiceAbstraction, useFactory: (
appIdService: AppIdServiceAbstraction, apiService: ApiServiceAbstraction, syncService: SyncServiceAbstraction,
vaultTimeoutService: VaultTimeoutServiceAbstraction, environmentService: EnvironmentServiceAbstraction, appIdService: AppIdServiceAbstraction,
messagingService: MessagingServiceAbstraction, logService: LogService) => apiService: ApiServiceAbstraction,
new NotificationsService(userService, syncService, appIdService, apiService, vaultTimeoutService, vaultTimeoutService: VaultTimeoutServiceAbstraction,
environmentService, async () => messagingService.send('logout', { expired: true }), logService), environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
logService: LogService,
stateService: StateServiceAbstraction,
) => new NotificationsService(
syncService,
appIdService,
apiService,
vaultTimeoutService,
environmentService,
async () => messagingService.send('logout', { expired: true }),
logService,
stateService,
),
deps: [ deps: [
UserServiceAbstraction,
SyncServiceAbstraction, SyncServiceAbstraction,
AppIdServiceAbstraction, AppIdServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
@ -323,6 +398,7 @@ import { ValidationService } from './validation.service';
EnvironmentServiceAbstraction, EnvironmentServiceAbstraction,
MessagingServiceAbstraction, MessagingServiceAbstraction,
LogService, LogService,
StateServiceAbstraction,
], ],
}, },
{ {
@ -334,19 +410,19 @@ import { ValidationService } from './validation.service';
provide: EventServiceAbstraction, provide: EventServiceAbstraction,
useClass: EventService, useClass: EventService,
deps: [ deps: [
StorageServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
UserServiceAbstraction,
CipherServiceAbstraction, CipherServiceAbstraction,
StateServiceAbstraction,
LogService, LogService,
OrganizationServiceAbstraction,
], ],
}, },
{ {
provide: PolicyServiceAbstraction, provide: PolicyServiceAbstraction,
useClass: PolicyService, useClass: PolicyService,
deps: [ deps: [
UserServiceAbstraction, StateServiceAbstraction,
StorageServiceAbstraction, OrganizationServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
], ],
}, },
@ -355,24 +431,23 @@ import { ValidationService } from './validation.service';
useClass: SendService, useClass: SendService,
deps: [ deps: [
CryptoServiceAbstraction, CryptoServiceAbstraction,
UserServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
FileUploadServiceAbstraction, FileUploadServiceAbstraction,
StorageServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
StateServiceAbstraction,
], ],
}, },
{ {
provide: KeyConnectorServiceAbstraction, provide: KeyConnectorServiceAbstraction,
useClass: KeyConnectorService, useClass: KeyConnectorService,
deps: [ deps: [
StorageServiceAbstraction, StateServiceAbstraction,
UserServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
TokenServiceAbstraction, TokenServiceAbstraction,
LogService, LogService,
OrganizationServiceAbstraction,
], ],
}, },
{ {
@ -385,6 +460,20 @@ import { ValidationService } from './validation.service';
], ],
}, },
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
{
provide: OrganizationServiceAbstraction,
useClass: OrganizationService,
deps: [
StateServiceAbstraction,
],
},
{
provide: ProviderServiceAbstraction,
useClass: ProviderService,
deps: [
StateServiceAbstraction,
],
},
], ],
}) })
export class JslibServicesModule { export class JslibServicesModule {

View File

@ -4,29 +4,26 @@ import {
Router, Router,
} from '@angular/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'; import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
@Injectable() @Injectable()
export class LockGuardService implements CanActivate { export class LockGuardService implements CanActivate {
protected homepage = 'vault'; protected homepage = 'vault';
constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService, constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
private router: Router) { } private stateService: StateService) { }
async canActivate() { async canActivate() {
const isAuthed = await this.userService.isAuthenticated(); if (!await this.stateService.getIsAuthenticated()) {
if (isAuthed) { this.router.navigate(['login']);
const locked = await this.vaultTimeoutService.isLocked(); return false;
if (locked) {
return true;
} else {
this.router.navigate([this.homepage]);
return false;
}
} }
this.router.navigate(['']); if (!await this.vaultTimeoutService.isLocked()) {
return false; this.router.navigate([this.homepage]);
return false;
}
return true;
} }
} }

View File

@ -1,22 +1,21 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { import {
ActivatedRouteSnapshot,
CanActivate, CanActivate,
Router, Router,
} from '@angular/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'; import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
@Injectable() @Injectable()
export class UnauthGuardService implements CanActivate { export class UnauthGuardService implements CanActivate {
protected homepage = 'vault'; protected homepage = 'vault';
constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService, constructor(private vaultTimeoutService: VaultTimeoutService, private router: Router,
private router: Router) { } private stateService: StateService) { }
async canActivate() { async canActivate() {
const isAuthed = await this.userService.isAuthenticated(); const isAuthed = await this.stateService.getIsAuthenticated();
if (isAuthed) { if (isAuthed) {
const locked = await this.vaultTimeoutService.isLocked(); const locked = await this.vaultTimeoutService.isLocked();
if (locked) { if (locked) {
@ -26,7 +25,6 @@ export class UnauthGuardService implements CanActivate {
} }
return false; return false;
} }
return true; return true;
} }
} }

View File

@ -1,9 +0,0 @@
export abstract class ApiKeyService {
setInformation: (clientId: string, clientSecret: string) => Promise<any>;
clear: () => Promise<any>;
getClientId: () => Promise<string>;
getClientSecret: () => Promise<string>;
getEntityType: () => Promise<string>;
getEntityId: () => Promise<string>;
isAuthenticated: () => Promise<boolean>;
}

View File

@ -11,9 +11,7 @@ import { CipherView } from '../models/view/cipherView';
import { FieldView } from '../models/view/fieldView'; import { FieldView } from '../models/view/fieldView';
export abstract class CipherService { export abstract class CipherService {
decryptedCipherCache: CipherView[]; clearCache: (userId?: string) => Promise<void>;
clearCache: () => void;
encrypt: (model: CipherView, key?: SymmetricCryptoKey, originalCipher?: Cipher) => Promise<Cipher>; encrypt: (model: CipherView, key?: SymmetricCryptoKey, originalCipher?: Cipher) => Promise<Cipher>;
encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise<Field[]>; encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise<Field[]>;
encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise<Field>; encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise<Field>;

View File

@ -6,9 +6,7 @@ import { TreeNode } from '../models/domain/treeNode';
import { CollectionView } from '../models/view/collectionView'; import { CollectionView } from '../models/view/collectionView';
export abstract class CollectionService { export abstract class CollectionService {
decryptedCollectionCache: CollectionView[]; clearCache: (userId?: string) => Promise<void>;
clearCache: () => void;
encrypt: (model: CollectionView) => Promise<Collection>; encrypt: (model: CollectionView) => Promise<Collection>;
decryptMany: (collections: Collection[]) => Promise<CollectionView[]>; decryptMany: (collections: Collection[]) => Promise<CollectionView[]>;
get: (id: string) => Promise<Collection>; get: (id: string) => Promise<Collection>;

View File

@ -8,17 +8,16 @@ import { ProfileProviderResponse } from '../models/response/profileProviderRespo
import { HashPurpose } from '../enums/hashPurpose'; import { HashPurpose } from '../enums/hashPurpose';
import { KdfType } from '../enums/kdfType'; import { KdfType } from '../enums/kdfType';
import { KeySuffixOptions } from '../enums/keySuffixOptions';
import { KeySuffixOptions } from './storage.service';
export abstract class CryptoService { export abstract class CryptoService {
setKey: (key: SymmetricCryptoKey) => Promise<any>; setKey: (key: SymmetricCryptoKey) => Promise<any>;
setKeyHash: (keyHash: string) => Promise<{}>; setKeyHash: (keyHash: string) => Promise<void>;
setEncKey: (encKey: string) => Promise<{}>; setEncKey: (encKey: string) => Promise<void>;
setEncPrivateKey: (encPrivateKey: string) => Promise<{}>; setEncPrivateKey: (encPrivateKey: string) => Promise<void>;
setOrgKeys: (orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]) => Promise<{}>; setOrgKeys: (orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]) => Promise<void>;
setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<{}>; setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<void>;
getKey: (keySuffix?: KeySuffixOptions) => Promise<SymmetricCryptoKey>; getKey: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<SymmetricCryptoKey>;
getKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise<SymmetricCryptoKey>; getKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise<SymmetricCryptoKey>;
getKeyHash: () => Promise<string>; getKeyHash: () => Promise<string>;
compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>; compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>;
@ -30,17 +29,17 @@ export abstract class CryptoService {
getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>; getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>;
getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>; getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>;
hasKey: () => Promise<boolean>; hasKey: () => Promise<boolean>;
hasKeyInMemory: () => boolean; hasKeyInMemory: (userId?: string) => Promise<boolean>;
hasKeyStored: (keySuffix?: KeySuffixOptions) => Promise<boolean>; hasKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
hasEncKey: () => Promise<boolean>; hasEncKey: () => Promise<boolean>;
clearKey: (clearSecretStorage?: boolean) => Promise<any>; clearKey: (clearSecretStorage?: boolean, userId?: string) => Promise<any>;
clearKeyHash: () => Promise<any>; clearKeyHash: () => Promise<any>;
clearEncKey: (memoryOnly?: boolean) => Promise<any>; clearEncKey: (memoryOnly?: boolean, userId?: string) => Promise<any>;
clearKeyPair: (memoryOnly?: boolean) => Promise<any>; clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise<any>;
clearOrgKeys: (memoryOnly?: boolean) => Promise<any>; clearOrgKeys: (memoryOnly?: boolean, userId?: string) => Promise<any>;
clearProviderKeys: (memoryOnly?: boolean) => Promise<any>; clearProviderKeys: (memoryOnly?: boolean) => Promise<any>;
clearPinProtectedKey: () => Promise<any>; clearPinProtectedKey: () => Promise<any>;
clearKeys: () => Promise<any>; clearKeys: (userId?: string) => Promise<any>;
toggleKey: () => Promise<any>; toggleKey: () => Promise<any>;
makeKey: (password: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise<SymmetricCryptoKey>; makeKey: (password: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise<SymmetricCryptoKey>;
makeKeyFromPin: (pin: string, salt: string, kdf: KdfType, kdfIterations: number, makeKeyFromPin: (pin: string, salt: string, kdf: KdfType, kdfIterations: number,

View File

@ -2,6 +2,6 @@ import { EventType } from '../enums/eventType';
export abstract class EventService { export abstract class EventService {
collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise<any>; collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise<any>;
uploadEvents: () => Promise<any>; uploadEvents: (userId?: string) => Promise<any>;
clearEvents: () => Promise<any>; clearEvents: (userId?: string) => Promise<any>;
} }

View File

@ -7,9 +7,7 @@ import { TreeNode } from '../models/domain/treeNode';
import { FolderView } from '../models/view/folderView'; import { FolderView } from '../models/view/folderView';
export abstract class FolderService { export abstract class FolderService {
decryptedFolderCache: FolderView[]; clearCache: (userId?: string) => Promise<void>;
clearCache: () => void;
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>; encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
get: (id: string) => Promise<Folder>; get: (id: string) => Promise<Folder>;
getAll: () => Promise<Folder[]>; getAll: () => Promise<Folder[]>;

View File

@ -0,0 +1,11 @@
import { OrganizationData } from '../models/data/organizationData';
import { Organization } from '../models/domain/organization';
export abstract class OrganizationService {
get: (id: string) => Promise<Organization>;
getByIdentifier: (identifier: string) => Promise<Organization>;
getAll: (userId?: string) => Promise<Organization[]>;
save: (orgs: {[id: string]: OrganizationData}) => Promise<any>;
canManageSponsorships: () => Promise<boolean>;
}

View File

@ -12,7 +12,7 @@ export abstract class PasswordGenerationService {
saveOptions: (options: any) => Promise<any>; saveOptions: (options: any) => Promise<any>;
getHistory: () => Promise<GeneratedPasswordHistory[]>; getHistory: () => Promise<GeneratedPasswordHistory[]>;
addHistory: (password: string) => Promise<any>; addHistory: (password: string) => Promise<any>;
clear: () => Promise<any>; clear: (userId?: string) => Promise<any>;
passwordStrength: (password: string, userInputs?: string[]) => zxcvbn.ZXCVBNResult; passwordStrength: (password: string, userInputs?: string[]) => zxcvbn.ZXCVBNResult;
normalizeOptions: (options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) => void; normalizeOptions: (options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) => void;
} }

View File

@ -10,17 +10,15 @@ import { PolicyResponse } from '../models/response/policyResponse';
import { PolicyType } from '../enums/policyType'; import { PolicyType } from '../enums/policyType';
export abstract class PolicyService { export abstract class PolicyService {
policyCache: Policy[];
clearCache: () => void; clearCache: () => void;
getAll: (type?: PolicyType) => Promise<Policy[]>; getAll: (type?: PolicyType, userId?: string) => Promise<Policy[]>;
getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise<Policy>; getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise<Policy>;
replace: (policies: { [id: string]: PolicyData; }) => Promise<any>; replace: (policies: { [id: string]: PolicyData; }) => Promise<any>;
clear: (userId: string) => Promise<any>; clear: (userId?: string) => Promise<any>;
getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise<MasterPasswordPolicyOptions>; getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise<MasterPasswordPolicyOptions>;
evaluateMasterPassword: (passwordStrength: number, newPassword: string, evaluateMasterPassword: (passwordStrength: number, newPassword: string,
enforcedPolicyOptions?: MasterPasswordPolicyOptions) => boolean; enforcedPolicyOptions?: MasterPasswordPolicyOptions) => boolean;
getResetPasswordPolicyOptions: (policies: Policy[], orgId: string) => [ResetPasswordPolicyOptions, boolean]; getResetPasswordPolicyOptions: (policies: Policy[], orgId: string) => [ResetPasswordPolicyOptions, boolean];
mapPoliciesFromToken: (policiesResponse: ListResponse<PolicyResponse>) => Policy[]; mapPoliciesFromToken: (policiesResponse: ListResponse<PolicyResponse>) => Policy[];
policyAppliesToUser: (policyType: PolicyType, policyFilter?: (policy: Policy) => boolean) => Promise<boolean>; policyAppliesToUser: (policyType: PolicyType, policyFilter?: (policy: Policy) => boolean, userId?: string) => Promise<boolean>;
} }

View File

@ -0,0 +1,9 @@
import { ProviderData } from '../models/data/providerData';
import { Provider } from '../models/domain/provider';
export abstract class ProviderService {
get: (id: string) => Promise<Provider>;
getAll: () => Promise<Provider[]>;
save: (providers: {[id: string]: ProviderData}) => Promise<any>;
}

View File

@ -7,9 +7,7 @@ import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
import { SendView } from '../models/view/sendView'; import { SendView } from '../models/view/sendView';
export abstract class SendService { export abstract class SendService {
decryptedSendCache: SendView[]; clearCache: () => Promise<void>;
clearCache: () => void;
encrypt: (model: SendView, file: File | ArrayBuffer, password: string, key?: SymmetricCryptoKey) => Promise<[Send, EncArrayBuffer]>; encrypt: (model: SendView, file: File | ArrayBuffer, password: string, key?: SymmetricCryptoKey) => Promise<[Send, EncArrayBuffer]>;
get: (id: string) => Promise<Send>; get: (id: string) => Promise<Send>;
getAll: () => Promise<Send[]>; getAll: () => Promise<Send[]>;

View File

@ -1,6 +1,6 @@
export abstract class SettingsService { export abstract class SettingsService {
clearCache: () => void; clearCache: () => Promise<void>;
getEquivalentDomains: () => Promise<any>; getEquivalentDomains: () => Promise<any>;
setEquivalentDomains: (equivalentDomains: string[][]) => Promise<any>; setEquivalentDomains: (equivalentDomains: string[][]) => Promise<any>;
clear: (userId: string) => Promise<void>; clear: (userId?: string) => Promise<void>;
} }

View File

@ -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 { export abstract class StateService {
get: <T>(key: string) => Promise<T>; accounts: BehaviorSubject<{ [userId: string]: Account }>;
save: (key: string, obj: any) => Promise<any>; activeAccount: BehaviorSubject<string>;
remove: (key: string) => Promise<any>;
purge: () => Promise<any>; addAccount: (account: Account) => Promise<void>;
setActiveUser: (userId: string) => Promise<void>;
clean: (options?: StorageOptions) => Promise<void>;
init: () => Promise<void>;
getAccessToken: (options?: StorageOptions) => Promise<string>;
setAccessToken: (value: string, options?: StorageOptions) => Promise<void>;
getAddEditCipherInfo: (options?: StorageOptions) => Promise<any>;
setAddEditCipherInfo: (value: any, options?: StorageOptions) => Promise<void>;
getAlwaysShowDock: (options?: StorageOptions) => Promise<boolean>;
setAlwaysShowDock: (value: boolean, options?: StorageOptions) => Promise<void>;
getApiKeyClientId: (options?: StorageOptions) => Promise<string>;
setApiKeyClientId: (value: string, options?: StorageOptions) => Promise<void>;
getApiKeyClientSecret: (options?: StorageOptions) => Promise<string>;
setApiKeyClientSecret: (value: string, options?: StorageOptions) => Promise<void>;
getAutoConfirmFingerPrints: (options?: StorageOptions) => Promise<boolean>;
setAutoConfirmFingerprints: (value: boolean, options?: StorageOptions) => Promise<void>;
getAutoFillOnPageLoadDefault: (options?: StorageOptions) => Promise<boolean>;
setAutoFillOnPageLoadDefault: (value: boolean, options?: StorageOptions) => Promise<void>;
getBiometricAwaitingAcceptance: (options?: StorageOptions) => Promise<boolean>;
setBiometricAwaitingAcceptance: (value: boolean, options?: StorageOptions) => Promise<void>;
getBiometricFingerprintValidated: (options?: StorageOptions) => Promise<boolean>;
setBiometricFingerprintValidated: (value: boolean, options?: StorageOptions) => Promise<void>;
getBiometricLocked: (options?: StorageOptions) => Promise<boolean>;
setBiometricLocked: (value: boolean, options?: StorageOptions) => Promise<void>;
getBiometricText: (options?: StorageOptions) => Promise<string>;
setBiometricText: (value: string, options?: StorageOptions) => Promise<void>;
getBiometricUnlock: (options?: StorageOptions) => Promise<boolean>;
setBiometricUnlock: (value: boolean, options?: StorageOptions) => Promise<void>;
getCanAccessPremium: (options?: StorageOptions) => Promise<boolean>;
getClearClipboard: (options?: StorageOptions) => Promise<number>;
setClearClipboard: (value: number, options?: StorageOptions) => Promise<void>;
getCollapsedGroupings: (options?: StorageOptions) => Promise<Set<string>>;
setCollapsedGroupings: (value: Set<string>, options?: StorageOptions) => Promise<void>;
getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise<boolean>;
setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise<void>;
getCryptoMasterKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
setCryptoMasterKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise<void>;
getCryptoMasterKeyAuto: (options?: StorageOptions) => Promise<string>;
setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise<void>;
getCryptoMasterKeyB64: (options?: StorageOptions) => Promise<string>;
setCryptoMasterKeyB64: (value: string, options?: StorageOptions) => Promise<void>;
getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise<string>;
hasCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise<boolean>;
setCryptoMasterKeyBiometric: (value: string, options?: StorageOptions) => Promise<void>;
getDecodedToken: (options?: StorageOptions) => Promise<any>;
setDecodedToken: (value: any, options?: StorageOptions) => Promise<void>;
getDecryptedCiphers: (options?: StorageOptions) => Promise<CipherView[]>;
setDecryptedCiphers: (value: CipherView[], options?: StorageOptions) => Promise<void>;
getDecryptedCollections: (options?: StorageOptions) => Promise<CollectionView[]>;
setDecryptedCollections: (value: CollectionView[], options?: StorageOptions) => Promise<void>;
getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
setDecryptedCryptoSymmetricKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise<void>;
getDecryptedFolders: (options?: StorageOptions) => Promise<FolderView[]>;
setDecryptedFolders: (value: FolderView[], options?: StorageOptions) => Promise<void>;
getDecryptedOrganizationKeys: (options?: StorageOptions) => Promise<Map<string, SymmetricCryptoKey>>;
setDecryptedOrganizationKeys: (value: Map<string, SymmetricCryptoKey>, options?: StorageOptions) => Promise<void>;
getDecryptedPasswordGenerationHistory: (options?: StorageOptions) => Promise<GeneratedPasswordHistory[]>;
setDecryptedPasswordGenerationHistory: (value: GeneratedPasswordHistory[], options?: StorageOptions) => Promise<void>;
getDecryptedPinProtected: (options?: StorageOptions) => Promise<EncString>;
setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise<void>;
getDecryptedPolicies: (options?: StorageOptions) => Promise<Policy[]>;
setDecryptedPolicies: (value: Policy[], options?: StorageOptions) => Promise<void>;
getDecryptedPrivateKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
getDecryptedProviderKeys: (options?: StorageOptions) => Promise<Map<string, SymmetricCryptoKey>>;
setDecryptedProviderKeys: (value: Map<string, SymmetricCryptoKey>, options?: StorageOptions) => Promise<void>;
getDecryptedSends: (options?: StorageOptions) => Promise<SendView[]>;
setDecryptedSends: (value: SendView[], options?: StorageOptions) => Promise<void>;
getDefaultUriMatch: (options?: StorageOptions) => Promise<UriMatchType>;
setDefaultUriMatch: (value: UriMatchType, options?: StorageOptions) => Promise<void>;
getDisableAddLoginNotification: (options?: StorageOptions) => Promise<boolean>;
setDisableAddLoginNotification: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableAutoBiometricsPrompt: (options?: StorageOptions) => Promise<boolean>;
setDisableAutoBiometricsPrompt: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableAutoTotpCopy: (options?: StorageOptions) => Promise<boolean>;
setDisableAutoTotpCopy: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableBadgeCounter: (options?: StorageOptions) => Promise<boolean>;
setDisableBadgeCounter: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableChangedPasswordNotification: (options?: StorageOptions) => Promise<boolean>;
setDisableChangedPasswordNotification: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableContextMenuItem: (options?: StorageOptions) => Promise<boolean>;
setDisableContextMenuItem: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableFavicon: (options?: StorageOptions) => Promise<boolean>;
setDisableFavicon: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableGa: (options?: StorageOptions) => Promise<boolean>;
setDisableGa: (value: boolean, options?: StorageOptions) => Promise<void>;
getDontShowCardsCurrentTab: (options?: StorageOptions) => Promise<boolean>;
setDontShowCardsCurrentTab: (value: boolean, options?: StorageOptions) => Promise<void>;
getDontShowIdentitiesCurrentTab: (options?: StorageOptions) => Promise<boolean>;
setDontShowIdentitiesCurrentTab: (value: boolean, options?: StorageOptions) => Promise<void>;
getEmail: (options?: StorageOptions) => Promise<string>;
setEmail: (value: string, options?: StorageOptions) => Promise<void>;
getEmailVerified: (options?: StorageOptions) => Promise<boolean>;
setEmailVerified: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableAlwaysOnTop: (options?: StorageOptions) => Promise<boolean>;
setEnableAlwaysOnTop: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableAutoFillOnPageLoad: (options?: StorageOptions) => Promise<boolean>;
setEnableAutoFillOnPageLoad: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableBiometric: (options?: StorageOptions) => Promise<boolean>;
setEnableBiometric: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableBrowserIntegration: (options?: StorageOptions) => Promise<boolean>;
setEnableBrowserIntegration: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableBrowserIntegrationFingerprint: (options?: StorageOptions) => Promise<boolean>;
setEnableBrowserIntegrationFingerprint: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableCloseToTray: (options?: StorageOptions) => Promise<boolean>;
setEnableCloseToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableFullWidth: (options?: StorageOptions) => Promise<boolean>;
setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableGravitars: (options?: StorageOptions) => Promise<boolean>;
setEnableGravitars: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableMinimizeToTray: (options?: StorageOptions) => Promise<boolean>;
setEnableMinimizeToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableStartToTray: (options?: StorageOptions) => Promise<boolean>;
setEnableStartToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
getEnableTray: (options?: StorageOptions) => Promise<boolean>;
setEnableTray: (value: boolean, options?: StorageOptions) => Promise<void>;
getEncryptedCiphers: (options?: StorageOptions) => Promise<{ [id: string]: CipherData }>;
setEncryptedCiphers: (value: { [id: string]: CipherData }, options?: StorageOptions) => Promise<void>;
getEncryptedCollections: (options?: StorageOptions) => Promise<{ [id: string]: CollectionData }>;
setEncryptedCollections: (value: { [id: string]: CollectionData }, options?: StorageOptions) => Promise<void>;
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>;
getEncryptedFolders: (options?: StorageOptions) => Promise<{ [id: string]: FolderData }>;
setEncryptedFolders: (value: { [id: string]: FolderData }, options?: StorageOptions) => Promise<void>;
getEncryptedOrganizationKeys: (options?: StorageOptions) => Promise<any>;
setEncryptedOrganizationKeys: (value: Map<string, SymmetricCryptoKey>, options?: StorageOptions) => Promise<void>;
getEncryptedPasswordGenerationHistory: (options?: StorageOptions) => Promise<GeneratedPasswordHistory[]>;
setEncryptedPasswordGenerationHistory: (value: GeneratedPasswordHistory[], options?: StorageOptions) => Promise<void>;
getEncryptedPinProtected: (options?: StorageOptions) => Promise<string>;
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
getEncryptedPolicies: (options?: StorageOptions) => Promise<{ [id: string]: PolicyData }>;
setEncryptedPolicies: (value: { [id: string]: PolicyData }, options?: StorageOptions) => Promise<void>;
getEncryptedPrivateKey: (options?: StorageOptions) => Promise<string>;
setEncryptedPrivateKey: (value: string, options?: StorageOptions) => Promise<void>;
getEncryptedProviderKeys: (options?: StorageOptions) => Promise<any>;
setEncryptedProviderKeys: (value: any, options?: StorageOptions) => Promise<void>;
getEncryptedSends: (options?: StorageOptions) => Promise<{ [id: string]: SendData }>;
setEncryptedSends: (value: { [id: string]: SendData }, options?: StorageOptions) => Promise<void>;
getEntityId: (options?: StorageOptions) => Promise<string>;
setEntityId: (value: string, options?: StorageOptions) => Promise<void>;
getEntityType: (options?: StorageOptions) => Promise<any>;
setEntityType: (value: string, options?: StorageOptions) => Promise<void>;
getEnvironmentUrls: (options?: StorageOptions) => Promise<any>;
setEnvironmentUrls: (value: any, options?: StorageOptions) => Promise<void>;
getEquivalentDomains: (options?: StorageOptions) => Promise<any>;
setEquivalentDomains: (value: string, options?: StorageOptions) => Promise<void>;
getEventCollection: (options?: StorageOptions) => Promise<EventData[]>;
setEventCollection: (value: EventData[], options?: StorageOptions) => Promise<void>;
getEverBeenUnlocked: (options?: StorageOptions) => Promise<boolean>;
setEverBeenUnlocked: (value: boolean, options?: StorageOptions) => Promise<void>;
getForcePasswordReset: (options?: StorageOptions) => Promise<boolean>;
setForcePasswordReset: (value: boolean, options?: StorageOptions) => Promise<void>;
getInstalledVersion: (options?: StorageOptions) => Promise<string>;
setInstalledVersion: (value: string, options?: StorageOptions) => Promise<void>;
getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>;
getKdfIterations: (options?: StorageOptions) => Promise<number>;
setKdfIterations: (value: number, options?: StorageOptions) => Promise<void>;
getKdfType: (options?: StorageOptions) => Promise<KdfType>;
setKdfType: (value: KdfType, options?: StorageOptions) => Promise<void>;
getKeyHash: (options?: StorageOptions) => Promise<string>;
setKeyHash: (value: string, options?: StorageOptions) => Promise<void>;
getLastActive: (options?: StorageOptions) => Promise<number>;
setLastActive: (value: number, options?: StorageOptions) => Promise<void>;
getLastSync: (options?: StorageOptions) => Promise<string>;
setLastSync: (value: string, options?: StorageOptions) => Promise<void>;
getLegacyEtmKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
setLegacyEtmKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise<void>;
getLocalData: (options?: StorageOptions) => Promise<any>;
setLocalData: (value: string, options?: StorageOptions) => Promise<void>;
getLocale: (options?: StorageOptions) => Promise<string>;
setLocale: (value: string, options?: StorageOptions) => Promise<void>;
getLoginRedirect: (options?: StorageOptions) => Promise<any>;
setLoginRedirect: (value: any, options?: StorageOptions) => Promise<void>;
getMainWindowSize: (options?: StorageOptions) => Promise<number>;
setMainWindowSize: (value: number, options?: StorageOptions) => Promise<void>;
getMinimizeOnCopyToClipboard: (options?: StorageOptions) => Promise<boolean>;
setMinimizeOnCopyToClipboard: (value: boolean, options?: StorageOptions) => Promise<void>;
getNeverDomains: (options?: StorageOptions) => Promise<{ [id: string]: any }>;
setNeverDomains: (value: { [id: string]: any }, options?: StorageOptions) => Promise<void>;
getNoAutoPromptBiometrics: (options?: StorageOptions) => Promise<boolean>;
setNoAutoPromptBiometrics: (value: boolean, options?: StorageOptions) => Promise<void>;
getNoAutoPromptBiometricsText: (options?: StorageOptions) => Promise<string>;
setNoAutoPromptBiometricsText: (value: string, options?: StorageOptions) => Promise<void>;
getOpenAtLogin: (options?: StorageOptions) => Promise<boolean>;
setOpenAtLogin: (value: boolean, options?: StorageOptions) => Promise<void>;
getOrganizationInvitation: (options?: StorageOptions) => Promise<any>;
setOrganizationInvitation: (value: any, options?: StorageOptions) => Promise<void>;
getOrganizations: (options?: StorageOptions) => Promise<{ [id: string]: OrganizationData }>;
setOrganizations: (value: { [id: string]: OrganizationData }, options?: StorageOptions) => Promise<void>;
getPasswordGenerationOptions: (options?: StorageOptions) => Promise<any>;
setPasswordGenerationOptions: (value: any, options?: StorageOptions) => Promise<void>;
getProtectedPin: (options?: StorageOptions) => Promise<string>;
setProtectedPin: (value: string, options?: StorageOptions) => Promise<void>;
getProviders: (options?: StorageOptions) => Promise<{ [id: string]: ProviderData }>;
setProviders: (value: { [id: string]: ProviderData }, options?: StorageOptions) => Promise<void>;
getPublicKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
setPublicKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
getRefreshToken: (options?: StorageOptions) => Promise<string>;
setRefreshToken: (value: string, options?: StorageOptions) => Promise<void>;
getRememberedEmail: (options?: StorageOptions) => Promise<string>;
setRememberedEmail: (value: string, options?: StorageOptions) => Promise<void>;
getSecurityStamp: (options?: StorageOptions) => Promise<string>;
setSecurityStamp: (value: string, options?: StorageOptions) => Promise<void>;
getSettings: (options?: StorageOptions) => Promise<any>;
setSettings: (value: string, options?: StorageOptions) => Promise<void>;
getSsoCodeVerifier: (options?: StorageOptions) => Promise<string>;
setSsoCodeVerifier: (value: string, options?: StorageOptions) => Promise<void>;
getSsoOrgIdentifier: (options?: StorageOptions) => Promise<string>;
setSsoOrganizationIdentifier: (value: string, options?: StorageOptions) => Promise<void>;
getSsoState: (options?: StorageOptions) => Promise<string>;
setSsoState: (value: string, options?: StorageOptions) => Promise<void>;
getTheme: (options?: StorageOptions) => Promise<string>;
setTheme: (value: string, options?: StorageOptions) => Promise<void>;
getTwoFactorToken: (options?: StorageOptions) => Promise<string>;
setTwoFactorToken: (value: string, options?: StorageOptions) => Promise<void>;
getUserId: (options?: StorageOptions) => Promise<string>;
getUsesKeyConnector: (options?: StorageOptions) => Promise<boolean>;
setUsesKeyConnector: (vaule: boolean, options?: StorageOptions) => Promise<void>;
getVaultTimeout: (options?: StorageOptions) => Promise<number>;
setVaultTimeout: (value: number, options?: StorageOptions) => Promise<void>;
getVaultTimeoutAction: (options?: StorageOptions) => Promise<string>;
setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise<void>;
getStateVersion: () => Promise<number>;
setStateVersion: (value: number) => Promise<void>;
getWindow: () => Promise<Map<string, any>>;
setWindow: (value: Map<string, any>) => Promise<void>;
} }

View File

@ -0,0 +1,4 @@
export abstract class StateMigrationService {
needsMigration: () => Promise<boolean>;
migrate: () => Promise<void>;
}

View File

@ -1,12 +1,9 @@
import { StorageOptions } from '../models/domain/storageOptions';
export abstract class StorageService { export abstract class StorageService {
get: <T>(key: string, options?: StorageServiceOptions) => Promise<T>; get: <T>(key: string, options?: StorageOptions) => Promise<T>;
has: (key: string, options?: StorageServiceOptions) => Promise<boolean>; has: (key: string, options?: StorageOptions) => Promise<boolean>;
save: (key: string, obj: any, options?: StorageServiceOptions) => Promise<any>; save: (key: string, obj: any, options?: StorageOptions) => Promise<any>;
remove: (key: string, options?: StorageServiceOptions) => Promise<any>; remove: (key: string, options?: StorageOptions) => Promise<any>;
} }
export interface StorageServiceOptions {
keySuffix: KeySuffixOptions;
}
export type KeySuffixOptions = 'auto' | 'biometric';

View File

@ -8,7 +8,7 @@ export abstract class SyncService {
syncInProgress: boolean; syncInProgress: boolean;
getLastSync: () => Promise<Date>; getLastSync: () => Promise<Date>;
setLastSync: (date: Date) => Promise<any>; setLastSync: (date: Date, userId?: string) => Promise<any>;
fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise<boolean>; fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise<boolean>;
syncUpsertFolder: (notification: SyncFolderNotification, isEdit: boolean) => Promise<boolean>; syncUpsertFolder: (notification: SyncFolderNotification, isEdit: boolean) => Promise<boolean>;
syncDeleteFolder: (notification: SyncFolderNotification) => Promise<boolean>; syncDeleteFolder: (notification: SyncFolderNotification) => Promise<boolean>;

View File

@ -1,6 +1,6 @@
export abstract class SystemService { export abstract class SystemService {
startProcessReload: () => void; startProcessReload: () => Promise<void>;
cancelProcessReload: () => void; cancelProcessReload: () => void;
clearClipboard: (clipboardValue: string, timeoutMs?: number) => void; clearClipboard: (clipboardValue: string, timeoutMs?: number) => Promise<void>;
clearPendingClipboard: () => Promise<any>; clearPendingClipboard: () => Promise<any>;
} }

View File

@ -1,7 +1,4 @@
export abstract class TokenService { export abstract class TokenService {
token: string;
decodedToken: any;
refreshToken: string;
setTokens: (accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]) => Promise<any>; setTokens: (accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]) => Promise<any>;
setToken: (token: string) => Promise<any>; setToken: (token: string) => Promise<any>;
getToken: () => Promise<string>; getToken: () => Promise<string>;
@ -15,16 +12,16 @@ export abstract class TokenService {
setTwoFactorToken: (token: string, email: string) => Promise<any>; setTwoFactorToken: (token: string, email: string) => Promise<any>;
getTwoFactorToken: (email: string) => Promise<string>; getTwoFactorToken: (email: string) => Promise<string>;
clearTwoFactorToken: (email: string) => Promise<any>; clearTwoFactorToken: (email: string) => Promise<any>;
clearToken: () => Promise<any>; clearToken: (userId?: string) => Promise<any>;
decodeToken: () => any; decodeToken: (token?: string) => any;
getTokenExpirationDate: () => Date; getTokenExpirationDate: () => Promise<Date>;
tokenSecondsRemaining: (offsetSeconds?: number) => number; tokenSecondsRemaining: (offsetSeconds?: number) => Promise<number>;
tokenNeedsRefresh: (minutes?: number) => boolean; tokenNeedsRefresh: (minutes?: number) => Promise<boolean>;
getUserId: () => string; getUserId: () => Promise<string>;
getEmail: () => string; getEmail: () => Promise<string>;
getEmailVerified: () => boolean; getEmailVerified: () => Promise<boolean>;
getName: () => string; getName: () => Promise<string>;
getPremium: () => boolean; getPremium: () => Promise<boolean>;
getIssuer: () => string; getIssuer: () => Promise<string>;
getIsExternal: () => boolean; getIsExternal: () => Promise<boolean>;
} }

View File

@ -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<any>;
setEmailVerified: (emailVerified: boolean) => Promise<any>;
setSecurityStamp: (stamp: string) => Promise<any>;
setForcePasswordReset: (forcePasswordReset: boolean) => Promise<any>;
getUserId: () => Promise<string>;
getEmail: () => Promise<string>;
getSecurityStamp: () => Promise<string>;
getKdf: () => Promise<KdfType>;
getKdfIterations: () => Promise<number>;
getEmailVerified: () => Promise<boolean>;
getForcePasswordReset: () => Promise<boolean>;
clear: () => Promise<any>;
isAuthenticated: () => Promise<boolean>;
canAccessPremium: () => Promise<boolean>;
canManageSponsorships: () => Promise<boolean>;
getOrganization: (id: string) => Promise<Organization>;
getOrganizationByIdentifier: (identifier: string) => Promise<Organization>;
getAllOrganizations: () => Promise<Organization[]>;
replaceOrganizations: (organizations: { [id: string]: OrganizationData; }) => Promise<any>;
clearOrganizations: (userId: string) => Promise<any>;
getProvider: (id: string) => Promise<Provider>;
getAllProviders: () => Promise<Provider[]>;
replaceProviders: (providers: { [id: string]: ProviderData; }) => Promise<any>;
clearProviders: (userId: string) => Promise<any>;
}

View File

@ -1,16 +1,11 @@
import { EncString } from '../models/domain/encString';
export abstract class VaultTimeoutService { export abstract class VaultTimeoutService {
biometricLocked: boolean; isLocked: (userId?: string) => Promise<boolean>;
everBeenUnlocked: boolean;
pinProtectedKey: EncString;
isLocked: () => Promise<boolean>;
checkVaultTimeout: () => Promise<void>; checkVaultTimeout: () => Promise<void>;
lock: (allowSoftLock?: boolean) => Promise<void>; lock: (allowSoftLock?: boolean, userId?: string) => Promise<void>;
logOut: () => Promise<void>; logOut: (userId?: string) => Promise<void>;
setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise<void>; setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise<void>;
getVaultTimeout: () => Promise<number>; getVaultTimeout: () => Promise<number>;
isPinLockSet: () => Promise<[boolean, boolean]>; isPinLockSet: () => Promise<[boolean, boolean]>;
isBiometricLockSet: () => Promise<boolean>; isBiometricLockSet: () => Promise<boolean>;
clear: () => Promise<any>; clear: (userId?: string) => Promise<any>;
} }

View File

@ -0,0 +1,6 @@
export enum AuthenticationStatus {
Locked = 'locked',
Unlocked = 'unlocked',
LoggedOut = 'loggedOut',
Active = 'active',
}

View File

@ -0,0 +1,5 @@
export enum HtmlStorageLocation {
Local = 'local',
Memory = 'memory',
Session = 'session',
}

View File

@ -0,0 +1,4 @@
export enum KeySuffixOptions {
Auto = 'auto',
Biometric = 'biometric',
}

View File

@ -0,0 +1,5 @@
export enum StorageLocation {
Both = 'both',
Disk = 'disk',
Memory = 'memory',
}

View File

@ -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<TEncrypted, TDecrypted> {
encrypted?: TEncrypted;
decrypted?: TDecrypted;
}
export class DataEncryptionPair<TEncrypted, TDecrypted> {
encrypted?: { [id: string]: TEncrypted };
decrypted?: TDecrypted[];
}
export class AccountData {
ciphers?: DataEncryptionPair<CipherData, CipherView> = new DataEncryptionPair<CipherData, CipherView>();
folders?: DataEncryptionPair<FolderData, FolderView> = new DataEncryptionPair<FolderData, FolderView>();
localData?: any;
sends?: DataEncryptionPair<SendData, SendView> = new DataEncryptionPair<SendData, SendView>();
collections?: DataEncryptionPair<CollectionData, CollectionView> = new DataEncryptionPair<CollectionData, CollectionView>();
policies?: DataEncryptionPair<PolicyData, Policy> = new DataEncryptionPair<PolicyData, Policy>();
passwordGenerationHistory?: EncryptionPair<GeneratedPasswordHistory[], GeneratedPasswordHistory[]> = new EncryptionPair<GeneratedPasswordHistory[], GeneratedPasswordHistory[]>();
addEditCipherInfo?: any;
collapsedGroupings?: Set<string>;
eventCollection?: EventData[];
organizations?: { [id: string]: OrganizationData };
providers?: { [id: string]: ProviderData };
}
export class AccountKeys {
cryptoMasterKey?: SymmetricCryptoKey;
cryptoMasterKeyAuto?: string;
cryptoMasterKeyB64?: string;
cryptoMasterKeyBiometric?: string;
cryptoSymmetricKey?: EncryptionPair<string, SymmetricCryptoKey> = new EncryptionPair<string, SymmetricCryptoKey>();
organizationKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<any, Map<string, SymmetricCryptoKey>>();
providerKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<any, Map<string, SymmetricCryptoKey>>();
privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>();
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<string, EncString> = new EncryptionPair<string, EncString>();
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<Account>) {
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,
},
});
}
}

View File

@ -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<string, any> = new Map<string, any>();
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;
}

View File

@ -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;
}

View File

@ -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,
};

View File

@ -1515,7 +1515,7 @@ export class ApiService implements ApiServiceAbstraction {
async getActiveBearerToken(): Promise<string> { async getActiveBearerToken(): Promise<string> {
let accessToken = await this.tokenService.getToken(); let accessToken = await this.tokenService.getToken();
if (this.tokenService.tokenNeedsRefresh()) { if (await this.tokenService.tokenNeedsRefresh()) {
await this.doAuthRefresh(); await this.doAuthRefresh();
accessToken = await this.tokenService.getToken(); accessToken = await this.tokenService.getToken();
} }
@ -1637,7 +1637,7 @@ export class ApiService implements ApiServiceAbstraction {
headers.set('User-Agent', this.customUserAgent); 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', { const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + '/connect/token', {
body: this.qsStringify({ body: this.qsStringify({
grant_type: 'refresh_token', grant_type: 'refresh_token',

View File

@ -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<string> {
if (this.clientId == null) {
this.clientId = await this.storageService.get<string>(Keys.clientId);
}
return this.clientId;
}
async getClientSecret(): Promise<string> {
if (this.clientSecret == null) {
this.clientSecret = await this.storageService.get<string>(Keys.clientSecret);
}
return this.clientSecret;
}
async getEntityType(): Promise<string> {
if (this.entityType == null) {
this.entityType = await this.storageService.get<string>(Keys.entityType);
}
return this.entityType;
}
async getEntityId(): Promise<string> {
if (this.entityId == null) {
this.entityId = await this.storageService.get<string>(Keys.entityId);
}
return this.entityId;
}
async clear(): Promise<any> {
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<boolean> {
const token = await this.tokenService.getToken();
if (token == null) {
return false;
}
const entityId = await this.getEntityId();
return entityId != null;
}
}

View File

@ -2,6 +2,7 @@ import { HashPurpose } from '../enums/hashPurpose';
import { KdfType } from '../enums/kdfType'; import { KdfType } from '../enums/kdfType';
import { TwoFactorProviderType } from '../enums/twoFactorProviderType'; import { TwoFactorProviderType } from '../enums/twoFactorProviderType';
import { Account, AccountData, AccountProfile, AccountTokens } from '../models/domain/account';
import { AuthResult } from '../models/domain/authResult'; import { AuthResult } from '../models/domain/authResult';
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
@ -26,8 +27,8 @@ import { KeyConnectorService } from '../abstractions/keyConnector.service';
import { LogService } from '../abstractions/log.service'; import { LogService } from '../abstractions/log.service';
import { MessagingService } from '../abstractions/messaging.service'; import { MessagingService } from '../abstractions/messaging.service';
import { PlatformUtilsService } from '../abstractions/platformUtils.service'; import { PlatformUtilsService } from '../abstractions/platformUtils.service';
import { StateService } from '../abstractions/state.service';
import { TokenService } from '../abstractions/token.service'; import { TokenService } from '../abstractions/token.service';
import { UserService } from '../abstractions/user.service';
import { VaultTimeoutService } from '../abstractions/vaultTimeout.service'; import { VaultTimeoutService } from '../abstractions/vaultTimeout.service';
import { Utils } from '../misc/utils'; import { Utils } from '../misc/utils';
@ -99,12 +100,12 @@ export class AuthService implements AuthServiceAbstraction {
private key: SymmetricCryptoKey; private key: SymmetricCryptoKey;
constructor(private cryptoService: CryptoService, protected apiService: ApiService, constructor(private cryptoService: CryptoService, protected apiService: ApiService,
private userService: UserService, protected tokenService: TokenService, protected tokenService: TokenService, protected appIdService: AppIdService,
protected appIdService: AppIdService, private i18nService: I18nService, private i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
protected platformUtilsService: PlatformUtilsService, private messagingService: MessagingService, private messagingService: MessagingService, private vaultTimeoutService: VaultTimeoutService,
private vaultTimeoutService: VaultTimeoutService, private logService: LogService, private logService: LogService, protected cryptoFunctionService: CryptoFunctionService,
private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService, private keyConnectorService: KeyConnectorService, protected environmentService: EnvironmentService,
private keyConnectorService: KeyConnectorService, private setCryptoKeys = true) { protected stateService: StateService, private setCryptoKeys = true) {
} }
init() { init() {
@ -234,7 +235,7 @@ export class AuthService implements AuthServiceAbstraction {
let providerType: TwoFactorProviderType = null; let providerType: TwoFactorProviderType = null;
let providerPriority = -1; let providerPriority = -1;
this.twoFactorProvidersData.forEach((value, type) => { this.twoFactorProvidersData.forEach((_value, type) => {
const provider = (TwoFactorProviders as any)[type]; const provider = (TwoFactorProviders as any)[type];
if (provider != null && provider.priority > providerPriority) { if (provider != null && provider.priority > providerPriority) {
if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) {
@ -350,13 +351,34 @@ export class AuthService implements AuthServiceAbstraction {
const tokenResponse = response as IdentityTokenResponse; const tokenResponse = response as IdentityTokenResponse;
result.resetMasterPassword = tokenResponse.resetMasterPassword; result.resetMasterPassword = tokenResponse.resetMasterPassword;
result.forcePasswordReset = tokenResponse.forcePasswordReset; 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) { if (tokenResponse.twoFactorToken != null) {
await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken, email); 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 (this.setCryptoKeys) {
if (key != null) { if (key != null) {
await this.cryptoService.setKey(key); await this.cryptoService.setKey(key);
@ -392,7 +414,7 @@ export class AuthService implements AuthServiceAbstraction {
} else if (tokenResponse.keyConnectorUrl != null) { } else if (tokenResponse.keyConnectorUrl != null) {
const password = await this.cryptoFunctionService.randomBytes(64); 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); const keyConnectorRequest = new KeyConnectorUserKeyRequest(k.encKeyB64);
await this.cryptoService.setKey(k); await this.cryptoService.setKey(k);
@ -416,7 +438,7 @@ export class AuthService implements AuthServiceAbstraction {
} }
if (this.vaultTimeoutService != null) { if (this.vaultTimeoutService != null) {
this.vaultTimeoutService.biometricLocked = false; await this.stateService.setBiometricLocked(false);
} }
this.messagingService.send('loggedIn'); this.messagingService.send('loggedIn');
return result; return result;

View File

@ -46,43 +46,32 @@ import { FileUploadService } from '../abstractions/fileUpload.service';
import { I18nService } from '../abstractions/i18n.service'; import { I18nService } from '../abstractions/i18n.service';
import { SearchService } from '../abstractions/search.service'; import { SearchService } from '../abstractions/search.service';
import { SettingsService } from '../abstractions/settings.service'; import { SettingsService } from '../abstractions/settings.service';
import { StorageService } from '../abstractions/storage.service'; import { StateService } from '../abstractions/state.service';
import { UserService } from '../abstractions/user.service';
import { ConstantsService } from './constants.service';
import { LogService } from '../abstractions/log.service'; import { LogService } from '../abstractions/log.service';
import { sequentialize } from '../misc/sequentialize'; import { sequentialize } from '../misc/sequentialize';
import { Utils } from '../misc/utils'; import { Utils } from '../misc/utils';
const Keys = {
ciphersPrefix: 'ciphers_',
localData: 'sitesLocalData',
neverDomains: 'neverDomains',
};
const DomainMatchBlacklist = new Map<string, Set<string>>([ const DomainMatchBlacklist = new Map<string, Set<string>>([
['google.com', new Set(['script.google.com'])], ['google.com', new Set(['script.google.com'])],
]); ]);
export class CipherService implements CipherServiceAbstraction { export class CipherService implements CipherServiceAbstraction {
// tslint:disable-next-line
_decryptedCipherCache: CipherView[];
private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache(this.sortCiphersByLastUsed); private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache(this.sortCiphersByLastUsed);
constructor(private cryptoService: CryptoService, private userService: UserService, constructor(private cryptoService: CryptoService, private settingsService: SettingsService,
private settingsService: SettingsService, private apiService: ApiService, private apiService: ApiService, private fileUploadService: FileUploadService,
private fileUploadService: FileUploadService, private storageService: StorageService,
private i18nService: I18nService, private searchService: () => SearchService, private i18nService: I18nService, private searchService: () => SearchService,
private logService: LogService) { private logService: LogService, private stateService: StateService) {
} }
get decryptedCipherCache() { async getDecryptedCipherCache(): Promise<CipherView[]> {
return this._decryptedCipherCache; 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 (this.searchService != null) {
if (value == null) { if (value == null) {
this.searchService().clearIndex(); this.searchService().clearIndex();
@ -92,9 +81,8 @@ export class CipherService implements CipherServiceAbstraction {
} }
} }
clearCache(): void { async clearCache(userId?: string): Promise<void> {
this.decryptedCipherCache = null; await this.clearDecryptedCiphersState(userId);
this.sortedCiphersCache.clear();
} }
async encrypt(model: CipherView, key?: SymmetricCryptoKey, originalCipher: Cipher = null): Promise<Cipher> { async encrypt(model: CipherView, key?: SymmetricCryptoKey, originalCipher: Cipher = null): Promise<Cipher> {
@ -212,12 +200,10 @@ export class CipherService implements CipherServiceAbstraction {
const self = this; const self = this;
const encFields: Field[] = []; const encFields: Field[] = [];
await fieldsModel.reduce((promise, field) => { await fieldsModel.reduce(async (promise, field) => {
return promise.then(() => { await promise;
return self.encryptField(field, key); const encField = await self.encryptField(field, key);
}).then((encField: Field) => { encFields.push(encField);
encFields.push(encField);
});
}, Promise.resolve()); }, Promise.resolve());
return encFields; return encFields;
@ -247,12 +233,10 @@ export class CipherService implements CipherServiceAbstraction {
const self = this; const self = this;
const encPhs: Password[] = []; const encPhs: Password[] = [];
await phModels.reduce((promise, ph) => { await phModels.reduce(async (promise, ph) => {
return promise.then(() => { await promise;
return self.encryptPasswordHistory(ph, key); const encPh = await self.encryptPasswordHistory(ph, key);
}).then((encPh: Password) => { encPhs.push(encPh);
encPhs.push(encPh);
});
}, Promise.resolve()); }, Promise.resolve());
return encPhs; return encPhs;
@ -270,22 +254,18 @@ export class CipherService implements CipherServiceAbstraction {
} }
async get(id: string): Promise<Cipher> { async get(id: string): Promise<Cipher> {
const userId = await this.userService.getUserId(); const ciphers = await this.stateService.getEncryptedCiphers();
const localData = await this.storageService.get<any>(Keys.localData);
const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(
Keys.ciphersPrefix + userId);
if (ciphers == null || !ciphers.hasOwnProperty(id)) { if (ciphers == null || !ciphers.hasOwnProperty(id)) {
return null; return null;
} }
const localData = await this.stateService.getLocalData();
return new Cipher(ciphers[id], false, localData ? localData[id] : null); return new Cipher(ciphers[id], false, localData ? localData[id] : null);
} }
async getAll(): Promise<Cipher[]> { async getAll(): Promise<Cipher[]> {
const userId = await this.userService.getUserId(); const localData = await this.stateService.getLocalData();
const localData = await this.storageService.get<any>(Keys.localData); const ciphers = await this.stateService.getEncryptedCiphers();
const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(
Keys.ciphersPrefix + userId);
const response: Cipher[] = []; const response: Cipher[] = [];
for (const id in ciphers) { for (const id in ciphers) {
if (ciphers.hasOwnProperty(id)) { if (ciphers.hasOwnProperty(id)) {
@ -297,13 +277,13 @@ export class CipherService implements CipherServiceAbstraction {
@sequentialize(() => 'getAllDecrypted') @sequentialize(() => 'getAllDecrypted')
async getAllDecrypted(): Promise<CipherView[]> { async getAllDecrypted(): Promise<CipherView[]> {
if (this.decryptedCipherCache != null) { const userId = await this.stateService.getUserId();
const userId = await this.userService.getUserId(); if (await this.getDecryptedCipherCache() != null) {
if (this.searchService != null && (this.searchService().indexedEntityId ?? userId) !== userId) 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[] = []; const decCiphers: CipherView[] = [];
@ -314,14 +294,14 @@ export class CipherService implements CipherServiceAbstraction {
const promises: any[] = []; const promises: any[] = [];
const ciphers = await this.getAll(); const ciphers = await this.getAll();
ciphers.forEach(cipher => { ciphers.forEach(async cipher => {
promises.push(cipher.decrypt().then(c => decCiphers.push(c))); promises.push(cipher.decrypt().then(c => decCiphers.push(c)));
}); });
await Promise.all(promises); await Promise.all(promises);
decCiphers.sort(this.getLocaleSortingFunction()); decCiphers.sort(this.getLocaleSortingFunction());
this.decryptedCipherCache = decCiphers; await this.stateService.setDecryptedCiphers(decCiphers);
return this.decryptedCipherCache; return decCiphers;
} }
async getAllDecryptedForGrouping(groupingId: string, folder: boolean = true): Promise<CipherView[]> { async getAllDecryptedForGrouping(groupingId: string, folder: boolean = true): Promise<CipherView[]> {
@ -369,7 +349,7 @@ export class CipherService implements CipherServiceAbstraction {
const ciphers = result[1]; const ciphers = result[1];
if (defaultMatch == null) { if (defaultMatch == null) {
defaultMatch = await this.storageService.get<UriMatchType>(ConstantsService.defaultUriMatch); defaultMatch = await this.stateService.getDefaultUriMatch();
if (defaultMatch == null) { if (defaultMatch == null) {
defaultMatch = UriMatchType.Domain; defaultMatch = UriMatchType.Domain;
} }
@ -476,7 +456,7 @@ export class CipherService implements CipherServiceAbstraction {
} }
async updateLastUsedDate(id: string): Promise<void> { async updateLastUsedDate(id: string): Promise<void> {
let ciphersLocalData = await this.storageService.get<any>(Keys.localData); let ciphersLocalData = await this.stateService.getLocalData();
if (!ciphersLocalData) { if (!ciphersLocalData) {
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; return;
} }
for (let i = 0; i < this.decryptedCipherCache.length; i++) { for (let i = 0; i < decryptedCipherCache.length; i++) {
const cached = this.decryptedCipherCache[i]; const cached = decryptedCipherCache[i];
if (cached.id === id) { if (cached.id === id) {
cached.localData = ciphersLocalData[id]; cached.localData = ciphersLocalData[id];
break; break;
} }
} }
await this.stateService.setDecryptedCiphers(decryptedCipherCache);
} }
async updateLastLaunchedDate(id: string): Promise<void> { async updateLastLaunchedDate(id: string): Promise<void> {
let ciphersLocalData = await this.storageService.get<any>(Keys.localData); let ciphersLocalData = await this.stateService.getLocalData();
if (!ciphersLocalData) { if (!ciphersLocalData) {
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; return;
} }
for (let i = 0; i < this.decryptedCipherCache.length; i++) { for (let i = 0; i < decryptedCipherCache.length; i++) {
const cached = this.decryptedCipherCache[i]; const cached = decryptedCipherCache[i];
if (cached.id === id) { if (cached.id === id) {
cached.localData = ciphersLocalData[id]; cached.localData = ciphersLocalData[id];
break; break;
} }
} }
await this.stateService.setDecryptedCiphers(decryptedCipherCache);
} }
async saveNeverDomain(domain: string): Promise<void> { async saveNeverDomain(domain: string): Promise<void> {
@ -538,12 +522,12 @@ export class CipherService implements CipherServiceAbstraction {
return; return;
} }
let domains = await this.storageService.get<{ [id: string]: any; }>(Keys.neverDomains); let domains = await this.stateService.getNeverDomains();
if (!domains) { if (!domains) {
domains = {}; domains = {};
} }
domains[domain] = null; domains[domain] = null;
await this.storageService.save(Keys.neverDomains, domains); await this.stateService.setNeverDomains(domains);
} }
async saveWithServer(cipher: Cipher): Promise<any> { async saveWithServer(cipher: Cipher): Promise<any> {
@ -562,8 +546,7 @@ export class CipherService implements CipherServiceAbstraction {
response = await this.apiService.putCipher(cipher.id, request); response = await this.apiService.putCipher(cipher.id, request);
} }
const userId = await this.userService.getUserId(); const data = new CipherData(response, await this.stateService.getUserId(), cipher.collectionIds);
const data = new CipherData(response, userId, cipher.collectionIds);
await this.upsert(data); await this.upsert(data);
} }
@ -583,8 +566,7 @@ export class CipherService implements CipherServiceAbstraction {
const encCipher = await this.encrypt(cipher); const encCipher = await this.encrypt(cipher);
const request = new CipherShareRequest(encCipher); const request = new CipherShareRequest(encCipher);
const response = await this.apiService.putShareCipher(cipher.id, request); const response = await this.apiService.putShareCipher(cipher.id, request);
const userId = await this.userService.getUserId(); const data = new CipherData(response, await this.stateService.getUserId(), collectionIds);
const data = new CipherData(response, userId, collectionIds);
await this.upsert(data); await this.upsert(data);
} }
@ -601,7 +583,7 @@ export class CipherService implements CipherServiceAbstraction {
await Promise.all(promises); await Promise.all(promises);
const request = new CipherBulkShareRequest(encCiphers, collectionIds); const request = new CipherBulkShareRequest(encCiphers, collectionIds);
await this.apiService.putShareCiphers(request); 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))); await this.upsert(encCiphers.map(c => c.toCipherData(userId)));
} }
@ -618,7 +600,7 @@ export class CipherService implements CipherServiceAbstraction {
reject(e); reject(e);
} }
}; };
reader.onerror = evt => { reader.onerror = _evt => {
reject('Error reading file.'); reject('Error reading file.');
}; };
}); });
@ -654,8 +636,7 @@ export class CipherService implements CipherServiceAbstraction {
} }
} }
const userId = await this.userService.getUserId(); const cData = new CipherData(response, await this.stateService.getUserId(), cipher.collectionIds);
const cData = new CipherData(response, userId, cipher.collectionIds);
if (!admin) { if (!admin) {
await this.upsert(cData); await this.upsert(cData);
} }
@ -702,15 +683,12 @@ export class CipherService implements CipherServiceAbstraction {
async saveCollectionsWithServer(cipher: Cipher): Promise<any> { async saveCollectionsWithServer(cipher: Cipher): Promise<any> {
const request = new CipherCollectionsRequest(cipher.collectionIds); const request = new CipherCollectionsRequest(cipher.collectionIds);
await this.apiService.putCipherCollections(cipher.id, request); await this.apiService.putCipherCollections(cipher.id, request);
const userId = await this.userService.getUserId(); const data = cipher.toCipherData(await this.stateService.getUserId());
const data = cipher.toCipherData(userId);
await this.upsert(data); await this.upsert(data);
} }
async upsert(cipher: CipherData | CipherData[]): Promise<any> { async upsert(cipher: CipherData | CipherData[]): Promise<any> {
const userId = await this.userService.getUserId(); let ciphers = await this.stateService.getEncryptedCiphers();
let ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(
Keys.ciphersPrefix + userId);
if (ciphers == null) { if (ciphers == null) {
ciphers = {}; ciphers = {};
} }
@ -724,27 +702,23 @@ export class CipherService implements CipherServiceAbstraction {
}); });
} }
await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); await this.replace(ciphers);
this.decryptedCipherCache = null;
} }
async replace(ciphers: { [id: string]: CipherData; }): Promise<any> { async replace(ciphers: { [id: string]: CipherData; }): Promise<any> {
const userId = await this.userService.getUserId(); await this.clearDecryptedCiphersState();
await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); await this.stateService.setEncryptedCiphers(ciphers);
this.decryptedCipherCache = null;
} }
async clear(userId: string): Promise<any> { async clear(userId?: string): Promise<any> {
await this.storageService.remove(Keys.ciphersPrefix + userId); await this.clearEncryptedCiphersState(userId);
this.clearCache(); await this.clearCache(userId);
} }
async moveManyWithServer(ids: string[], folderId: string): Promise<any> { async moveManyWithServer(ids: string[], folderId: string): Promise<any> {
await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId)); await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId));
const userId = await this.userService.getUserId(); let ciphers = await this.stateService.getEncryptedCiphers();
let ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(
Keys.ciphersPrefix + userId);
if (ciphers == null) { if (ciphers == null) {
ciphers = {}; ciphers = {};
} }
@ -755,14 +729,12 @@ export class CipherService implements CipherServiceAbstraction {
} }
}); });
await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); await this.clearCache();
this.decryptedCipherCache = null; await this.stateService.setEncryptedCiphers(ciphers);
} }
async delete(id: string | string[]): Promise<any> { async delete(id: string | string[]): Promise<any> {
const userId = await this.userService.getUserId(); const ciphers = await this.stateService.getEncryptedCiphers();
const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(
Keys.ciphersPrefix + userId);
if (ciphers == null) { if (ciphers == null) {
return; return;
} }
@ -778,8 +750,8 @@ export class CipherService implements CipherServiceAbstraction {
}); });
} }
await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); await this.clearCache();
this.decryptedCipherCache = null; await this.stateService.setEncryptedCiphers(ciphers);
} }
async deleteWithServer(id: string): Promise<any> { async deleteWithServer(id: string): Promise<any> {
@ -793,9 +765,7 @@ export class CipherService implements CipherServiceAbstraction {
} }
async deleteAttachment(id: string, attachmentId: string): Promise<void> { async deleteAttachment(id: string, attachmentId: string): Promise<void> {
const userId = await this.userService.getUserId(); const ciphers = await this.stateService.getEncryptedCiphers();
const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(
Keys.ciphersPrefix + userId);
if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[id].attachments == null) { if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[id].attachments == null) {
return; return;
@ -807,8 +777,8 @@ export class CipherService implements CipherServiceAbstraction {
} }
} }
await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); await this.clearCache();
this.decryptedCipherCache = null; await this.stateService.setEncryptedCiphers(ciphers);
} }
async deleteAttachmentWithServer(id: string, attachmentId: string): Promise<void> { async deleteAttachmentWithServer(id: string, attachmentId: string): Promise<void> {
@ -887,9 +857,7 @@ export class CipherService implements CipherServiceAbstraction {
} }
async softDelete(id: string | string[]): Promise<any> { async softDelete(id: string | string[]): Promise<any> {
const userId = await this.userService.getUserId(); const ciphers = await this.stateService.getEncryptedCiphers();
const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(
Keys.ciphersPrefix + userId);
if (ciphers == null) { if (ciphers == null) {
return; return;
} }
@ -907,8 +875,8 @@ export class CipherService implements CipherServiceAbstraction {
(id as string[]).forEach(setDeletedDate); (id as string[]).forEach(setDeletedDate);
} }
await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); await this.clearCache();
this.decryptedCipherCache = null; await this.stateService.setEncryptedCiphers(ciphers);
} }
async softDeleteWithServer(id: string): Promise<any> { async softDeleteWithServer(id: string): Promise<any> {
@ -922,9 +890,7 @@ export class CipherService implements CipherServiceAbstraction {
} }
async restore(cipher: { id: string, revisionDate: string; } | { id: string, revisionDate: string; }[]) { async restore(cipher: { id: string, revisionDate: string; } | { id: string, revisionDate: string; }[]) {
const userId = await this.userService.getUserId(); const ciphers = await this.stateService.getEncryptedCiphers();
const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(
Keys.ciphersPrefix + userId);
if (ciphers == null) { if (ciphers == null) {
return; return;
} }
@ -944,8 +910,8 @@ export class CipherService implements CipherServiceAbstraction {
clearDeletedDate(cipher as { id: string, revisionDate: string; }); clearDeletedDate(cipher as { id: string, revisionDate: string; });
} }
await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); await this.clearCache();
this.decryptedCipherCache = null; await this.stateService.setEncryptedCiphers(ciphers);
} }
async restoreWithServer(id: string): Promise<any> { async restoreWithServer(id: string): Promise<any> {
@ -1109,7 +1075,7 @@ export class CipherService implements CipherServiceAbstraction {
} }
if (autofillOnPageLoad) { if (autofillOnPageLoad) {
const autofillOnPageLoadDefault = await this.storageService.get(ConstantsService.autoFillOnPageLoadDefaultKey); const autofillOnPageLoadDefault = await this.stateService.getAutoFillOnPageLoadDefault();
ciphers = ciphers.filter(cipher => cipher.login.autofillOnPageLoad || ciphers = ciphers.filter(cipher => cipher.login.autofillOnPageLoad ||
(cipher.login.autofillOnPageLoad == null && autofillOnPageLoadDefault !== false)); (cipher.login.autofillOnPageLoad == null && autofillOnPageLoadDefault !== false));
if (ciphers.length === 0) { if (ciphers.length === 0) {
@ -1128,4 +1094,17 @@ export class CipherService implements CipherServiceAbstraction {
return this.sortedCiphersCache.getNext(cacheKey); 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();
}
} }

View File

@ -8,26 +8,20 @@ import { CollectionView } from '../models/view/collectionView';
import { CollectionService as CollectionServiceAbstraction } from '../abstractions/collection.service'; import { CollectionService as CollectionServiceAbstraction } from '../abstractions/collection.service';
import { CryptoService } from '../abstractions/crypto.service'; import { CryptoService } from '../abstractions/crypto.service';
import { I18nService } from '../abstractions/i18n.service'; import { I18nService } from '../abstractions/i18n.service';
import { StorageService } from '../abstractions/storage.service'; import { StateService } from '../abstractions/state.service';
import { UserService } from '../abstractions/user.service';
import { ServiceUtils } from '../misc/serviceUtils'; import { ServiceUtils } from '../misc/serviceUtils';
import { Utils } from '../misc/utils'; import { Utils } from '../misc/utils';
const Keys = {
collectionsPrefix: 'collections_',
};
const NestingDelimiter = '/'; const NestingDelimiter = '/';
export class CollectionService implements CollectionServiceAbstraction { export class CollectionService implements CollectionServiceAbstraction {
decryptedCollectionCache: CollectionView[]; constructor(private cryptoService: CryptoService, private i18nService: I18nService,
private stateService: StateService) {
constructor(private cryptoService: CryptoService, private userService: UserService,
private storageService: StorageService, private i18nService: I18nService) {
} }
clearCache(): void { async clearCache(userId?: string): Promise<void> {
this.decryptedCollectionCache = null; await this.stateService.setDecryptedCollections(null, { userId: userId });
} }
async encrypt(model: CollectionView): Promise<Collection> { async encrypt(model: CollectionView): Promise<Collection> {
@ -60,9 +54,7 @@ export class CollectionService implements CollectionServiceAbstraction {
} }
async get(id: string): Promise<Collection> { async get(id: string): Promise<Collection> {
const userId = await this.userService.getUserId(); const collections = await this.stateService.getEncryptedCollections();
const collections = await this.storageService.get<{ [id: string]: CollectionData; }>(
Keys.collectionsPrefix + userId);
if (collections == null || !collections.hasOwnProperty(id)) { if (collections == null || !collections.hasOwnProperty(id)) {
return null; return null;
} }
@ -71,9 +63,7 @@ export class CollectionService implements CollectionServiceAbstraction {
} }
async getAll(): Promise<Collection[]> { async getAll(): Promise<Collection[]> {
const userId = await this.userService.getUserId(); const collections = await this.stateService.getEncryptedCollections();
const collections = await this.storageService.get<{ [id: string]: CollectionData; }>(
Keys.collectionsPrefix + userId);
const response: Collection[] = []; const response: Collection[] = [];
for (const id in collections) { for (const id in collections) {
if (collections.hasOwnProperty(id)) { if (collections.hasOwnProperty(id)) {
@ -84,8 +74,9 @@ export class CollectionService implements CollectionServiceAbstraction {
} }
async getAllDecrypted(): Promise<CollectionView[]> { async getAllDecrypted(): Promise<CollectionView[]> {
if (this.decryptedCollectionCache != null) { let decryptedCollections = await this.stateService.getDecryptedCollections();
return this.decryptedCollectionCache; if (decryptedCollections != null) {
return decryptedCollections;
} }
const hasKey = await this.cryptoService.hasKey(); const hasKey = await this.cryptoService.hasKey();
@ -94,8 +85,9 @@ export class CollectionService implements CollectionServiceAbstraction {
} }
const collections = await this.getAll(); const collections = await this.getAll();
this.decryptedCollectionCache = await this.decryptMany(collections); decryptedCollections = await this.decryptMany(collections);
return this.decryptedCollectionCache; await this.stateService.setDecryptedCollections(decryptedCollections);
return decryptedCollections;
} }
async getAllNested(collections: CollectionView[] = null): Promise<TreeNode<CollectionView>[]> { async getAllNested(collections: CollectionView[] = null): Promise<TreeNode<CollectionView>[]> {
@ -119,9 +111,7 @@ export class CollectionService implements CollectionServiceAbstraction {
} }
async upsert(collection: CollectionData | CollectionData[]): Promise<any> { async upsert(collection: CollectionData | CollectionData[]): Promise<any> {
const userId = await this.userService.getUserId(); let collections = await this.stateService.getEncryptedCollections();
let collections = await this.storageService.get<{ [id: string]: CollectionData; }>(
Keys.collectionsPrefix + userId);
if (collections == null) { if (collections == null) {
collections = {}; collections = {};
} }
@ -135,31 +125,26 @@ export class CollectionService implements CollectionServiceAbstraction {
}); });
} }
await this.storageService.save(Keys.collectionsPrefix + userId, collections); await this.replace(collections);
this.decryptedCollectionCache = null;
} }
async replace(collections: { [id: string]: CollectionData; }): Promise<any> { async replace(collections: { [id: string]: CollectionData; }): Promise<any> {
const userId = await this.userService.getUserId(); await this.clearCache();
await this.storageService.save(Keys.collectionsPrefix + userId, collections); await this.stateService.setEncryptedCollections(collections);
this.decryptedCollectionCache = null;
} }
async clear(userId: string): Promise<any> { async clear(userId?: string): Promise<any> {
await this.storageService.remove(Keys.collectionsPrefix + userId); await this.clearCache(userId);
this.decryptedCollectionCache = null; await this.stateService.setEncryptedCollections(null, { userId: userId });
} }
async delete(id: string | string[]): Promise<any> { async delete(id: string | string[]): Promise<any> {
const userId = await this.userService.getUserId(); const collections = await this.stateService.getEncryptedCollections();
const collections = await this.storageService.get<{ [id: string]: CollectionData; }>(
Keys.collectionsPrefix + userId);
if (collections == null) { if (collections == null) {
return; return;
} }
if (typeof id === 'string') { if (typeof id === 'string') {
const i = id as string;
delete collections[id]; delete collections[id];
} else { } else {
(id as string[]).forEach(i => { (id as string[]).forEach(i => {
@ -167,7 +152,6 @@ export class CollectionService implements CollectionServiceAbstraction {
}); });
} }
await this.storageService.save(Keys.collectionsPrefix + userId, collections); await this.replace(collections);
this.decryptedCollectionCache = null;
} }
} }

View File

@ -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;
}

View File

@ -3,84 +3,60 @@ import * as bigInt from 'big-integer';
import { EncryptionType } from '../enums/encryptionType'; import { EncryptionType } from '../enums/encryptionType';
import { HashPurpose } from '../enums/hashPurpose'; import { HashPurpose } from '../enums/hashPurpose';
import { KdfType } from '../enums/kdfType'; import { KdfType } from '../enums/kdfType';
import { KeySuffixOptions } from '../enums/keySuffixOptions';
import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; import { EncArrayBuffer } from '../models/domain/encArrayBuffer';
import { EncryptedObject } from '../models/domain/encryptedObject'; import { EncryptedObject } from '../models/domain/encryptedObject';
import { EncString } from '../models/domain/encString'; import { EncString } from '../models/domain/encString';
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse';
import { CryptoService as CryptoServiceAbstraction } from '../abstractions/crypto.service'; import { CryptoService as CryptoServiceAbstraction } from '../abstractions/crypto.service';
import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; import { CryptoFunctionService } from '../abstractions/cryptoFunction.service';
import { LogService } from '../abstractions/log.service'; import { LogService } from '../abstractions/log.service';
import { PlatformUtilsService } from '../abstractions/platformUtils.service'; import { PlatformUtilsService } from '../abstractions/platformUtils.service';
import { import { StateService } from '../abstractions/state.service';
KeySuffixOptions,
StorageService,
} from '../abstractions/storage.service';
import { ConstantsService } from './constants.service';
import { sequentialize } from '../misc/sequentialize'; import { sequentialize } from '../misc/sequentialize';
import { Utils } from '../misc/utils'; import { Utils } from '../misc/utils';
import { EEFLongWordList } from '../misc/wordlist'; import { EEFLongWordList } from '../misc/wordlist';
import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse';
import { ProfileProviderOrganizationResponse } from '../models/response/profileProviderOrganizationResponse'; import { ProfileProviderOrganizationResponse } from '../models/response/profileProviderOrganizationResponse';
import { ProfileProviderResponse } from '../models/response/profileProviderResponse'; 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 { export class CryptoService implements CryptoServiceAbstraction {
private key: SymmetricCryptoKey; constructor(private cryptoFunctionService: CryptoFunctionService, protected platformUtilService: PlatformUtilsService,
private encKey: SymmetricCryptoKey; protected logService: LogService, protected stateService: StateService) {
private legacyEtmKey: SymmetricCryptoKey;
private keyHash: string;
private publicKey: ArrayBuffer;
private privateKey: ArrayBuffer;
private orgKeys: Map<string, SymmetricCryptoKey>;
private providerKeys: Map<string, SymmetricCryptoKey>;
constructor(private storageService: StorageService, protected secureStorageService: StorageService,
private cryptoFunctionService: CryptoFunctionService, protected platformUtilService: PlatformUtilsService,
protected logService: LogService) {
} }
async setKey(key: SymmetricCryptoKey): Promise<any> { async setKey(key: SymmetricCryptoKey, userId?: string): Promise<any> {
this.key = key; await this.stateService.setCryptoMasterKey(key, { userId: userId });
await this.storeKey(key, userId);
await this.storeKey(key);
} }
setKeyHash(keyHash: string): Promise<{}> { async setKeyHash(keyHash: string): Promise<void> {
this.keyHash = keyHash; await this.stateService.setKeyHash(keyHash);
return this.storageService.save(Keys.keyHash, keyHash);
} }
async setEncKey(encKey: string): Promise<{}> { async setEncKey(encKey: string): Promise<void> {
if (encKey == null) { if (encKey == null) {
return; return;
} }
await this.storageService.save(Keys.encKey, encKey); await this.stateService.setDecryptedCryptoSymmetricKey(null);
this.encKey = null; await this.stateService.setEncryptedCryptoSymmetricKey(encKey);
} }
async setEncPrivateKey(encPrivateKey: string): Promise<{}> { async setEncPrivateKey(encPrivateKey: string): Promise<void> {
if (encPrivateKey == null) { if (encPrivateKey == null) {
return; return;
} }
await this.storageService.save(Keys.encPrivateKey, encPrivateKey); await this.stateService.setDecryptedPrivateKey(null);
this.privateKey = null; await this.stateService.setEncryptedPrivateKey(encPrivateKey);
} }
async setOrgKeys(orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]): Promise<{}> { async setOrgKeys(orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]): Promise<void> {
const orgKeys: any = {}; const orgKeys: any = {};
orgs.forEach(org => { orgs.forEach(org => {
orgKeys[org.id] = org.key; orgKeys[org.id] = org.key;
@ -90,47 +66,49 @@ export class CryptoService implements CryptoServiceAbstraction {
// Convert provider encrypted keys to user encrypted. // Convert provider encrypted keys to user encrypted.
const providerKey = await this.getProviderKey(providerOrg.providerId); const providerKey = await this.getProviderKey(providerOrg.providerId);
const decValue = await this.decryptToBytes(new EncString(providerOrg.key), providerKey); 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; await this.stateService.setDecryptedOrganizationKeys(null);
return this.storageService.save(Keys.encOrgKeys, orgKeys); return await this.stateService.setEncryptedOrganizationKeys(orgKeys);
} }
setProviderKeys(providers: ProfileProviderResponse[]): Promise<{}> { async setProviderKeys(providers: ProfileProviderResponse[]): Promise<void> {
const providerKeys: any = {}; const providerKeys: any = {};
providers.forEach(provider => { providers.forEach(provider => {
providerKeys[provider.id] = provider.key; providerKeys[provider.id] = provider.key;
}); });
this.providerKeys = null; await this.stateService.setDecryptedProviderKeys(null);
return this.storageService.save(Keys.encProviderKeys, providerKeys); return await this.stateService.setEncryptedProviderKeys(providerKeys);
} }
async getKey(keySuffix?: KeySuffixOptions): Promise<SymmetricCryptoKey> { async getKey(keySuffix?: KeySuffixOptions, userId?: string): Promise<SymmetricCryptoKey> {
if (this.key != null) { const inMemoryKey = await this.stateService.getCryptoMasterKey({ userId: userId });
return this.key;
if (inMemoryKey != null) {
return inMemoryKey;
} }
keySuffix ||= 'auto'; keySuffix ||= KeySuffixOptions.Auto;
const symmetricKey = await this.getKeyFromStorage(keySuffix); const symmetricKey = await this.getKeyFromStorage(keySuffix, userId);
if (symmetricKey != null) { if (symmetricKey != null) {
this.setKey(symmetricKey); // TODO: Refactor here so get key doesn't also set key
this.setKey(symmetricKey, userId);
} }
return symmetricKey; return symmetricKey;
} }
async getKeyFromStorage(keySuffix: KeySuffixOptions): Promise<SymmetricCryptoKey> { async getKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string): Promise<SymmetricCryptoKey> {
const key = await this.retrieveKeyFromStorage(keySuffix); const key = await this.retrieveKeyFromStorage(keySuffix, userId);
if (key != null) { if (key != null) {
const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer); const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer);
if (!await this.validateKey(symmetricKey)) { if (!await this.validateKey(symmetricKey)) {
this.logService.warning('Wrong key, throwing away stored key'); this.logService.warning('Wrong key, throwing away stored key');
this.secureStorageService.remove(Keys.key, { keySuffix: keySuffix }); await this.clearSecretKeyStore(userId);
return null; return null;
} }
@ -140,16 +118,7 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
async getKeyHash(): Promise<string> { async getKeyHash(): Promise<string> {
if (this.keyHash != null) { return await this.stateService.getKeyHash();
return this.keyHash;
}
const keyHash = await this.storageService.get<string>(Keys.keyHash);
if (keyHash != null) {
this.keyHash = keyHash;
}
return keyHash == null ? null : this.keyHash;
} }
async compareAndUpdateKeyHash(masterPassword: string, key: SymmetricCryptoKey): Promise<boolean> { async compareAndUpdateKeyHash(masterPassword: string, key: SymmetricCryptoKey): Promise<boolean> {
@ -173,11 +142,12 @@ export class CryptoService implements CryptoServiceAbstraction {
@sequentialize(() => 'getEncKey') @sequentialize(() => 'getEncKey')
async getEncKey(key: SymmetricCryptoKey = null): Promise<SymmetricCryptoKey> { async getEncKey(key: SymmetricCryptoKey = null): Promise<SymmetricCryptoKey> {
if (this.encKey != null) { const inMemoryKey = await this.stateService.getDecryptedCryptoSymmetricKey();
return this.encKey; if (inMemoryKey != null) {
return inMemoryKey;
} }
const encKey = await this.storageService.get<string>(Keys.encKey); const encKey = await this.stateService.getEncryptedCryptoSymmetricKey();
if (encKey == null) { if (encKey == null) {
return null; return null;
} }
@ -203,13 +173,15 @@ export class CryptoService implements CryptoServiceAbstraction {
if (decEncKey == null) { if (decEncKey == null) {
return null; return null;
} }
this.encKey = new SymmetricCryptoKey(decEncKey); const symmetricCryptoKey = new SymmetricCryptoKey(decEncKey);
return this.encKey; await this.stateService.setDecryptedCryptoSymmetricKey(symmetricCryptoKey);
return symmetricCryptoKey;
} }
async getPublicKey(): Promise<ArrayBuffer> { async getPublicKey(): Promise<ArrayBuffer> {
if (this.publicKey != null) { const inMemoryPublicKey = await this.stateService.getPublicKey();
return this.publicKey; if (inMemoryPublicKey != null) {
return inMemoryPublicKey;
} }
const privateKey = await this.getPrivateKey(); const privateKey = await this.getPrivateKey();
@ -217,22 +189,25 @@ export class CryptoService implements CryptoServiceAbstraction {
return null; return null;
} }
this.publicKey = await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); const publicKey = await this.cryptoFunctionService.rsaExtractPublicKey(privateKey);
return this.publicKey; await this.stateService.setPublicKey(publicKey);
return publicKey;
} }
async getPrivateKey(): Promise<ArrayBuffer> { async getPrivateKey(): Promise<ArrayBuffer> {
if (this.privateKey != null) { const decryptedPrivateKey = await this.stateService.getDecryptedPrivateKey();
return this.privateKey; if (decryptedPrivateKey != null) {
return decryptedPrivateKey;
} }
const encPrivateKey = await this.storageService.get<string>(Keys.encPrivateKey); const encPrivateKey = await this.stateService.getEncryptedPrivateKey();
if (encPrivateKey == null) { if (encPrivateKey == null) {
return null; return null;
} }
this.privateKey = await this.decryptToBytes(new EncString(encPrivateKey), null); const privateKey = await this.decryptToBytes(new EncString(encPrivateKey), null);
return this.privateKey; await this.stateService.setDecryptedPrivateKey(privateKey);
return privateKey;
} }
async getFingerprint(userId: string, publicKey?: ArrayBuffer): Promise<string[]> { async getFingerprint(userId: string, publicKey?: ArrayBuffer): Promise<string[]> {
@ -249,16 +224,17 @@ export class CryptoService implements CryptoServiceAbstraction {
@sequentialize(() => 'getOrgKeys') @sequentialize(() => 'getOrgKeys')
async getOrgKeys(): Promise<Map<string, SymmetricCryptoKey>> { async getOrgKeys(): Promise<Map<string, SymmetricCryptoKey>> {
if (this.orgKeys != null && this.orgKeys.size > 0) { const orgKeys: Map<string, SymmetricCryptoKey> = new Map<string, SymmetricCryptoKey>();
return this.orgKeys; const decryptedOrganizationKeys = await this.stateService.getDecryptedOrganizationKeys();
if (decryptedOrganizationKeys != null && decryptedOrganizationKeys.size > 0) {
return decryptedOrganizationKeys;
} }
const encOrgKeys = await this.storageService.get<any>(Keys.encOrgKeys); const encOrgKeys = await this.stateService.getEncryptedOrganizationKeys();
if (encOrgKeys == null) { if (encOrgKeys == null) {
return null; return null;
} }
const orgKeys: Map<string, SymmetricCryptoKey> = new Map<string, SymmetricCryptoKey>();
let setKey = false; let setKey = false;
for (const orgId in encOrgKeys) { for (const orgId in encOrgKeys) {
@ -272,10 +248,10 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
if (setKey) { if (setKey) {
this.orgKeys = orgKeys; await this.stateService.setDecryptedOrganizationKeys(orgKeys);
} }
return this.orgKeys; return orgKeys;
} }
async getOrgKey(orgId: string): Promise<SymmetricCryptoKey> { async getOrgKey(orgId: string): Promise<SymmetricCryptoKey> {
@ -293,16 +269,17 @@ export class CryptoService implements CryptoServiceAbstraction {
@sequentialize(() => 'getProviderKeys') @sequentialize(() => 'getProviderKeys')
async getProviderKeys(): Promise<Map<string, SymmetricCryptoKey>> { async getProviderKeys(): Promise<Map<string, SymmetricCryptoKey>> {
if (this.providerKeys != null && this.providerKeys.size > 0) { const providerKeys: Map<string, SymmetricCryptoKey> = new Map<string, SymmetricCryptoKey>();
return this.providerKeys; const decryptedProviderKeys = await this.stateService.getDecryptedProviderKeys();
if (decryptedProviderKeys != null && decryptedProviderKeys.size > 0) {
return decryptedProviderKeys;
} }
const encProviderKeys = await this.storageService.get<any>(Keys.encProviderKeys); const encProviderKeys = await this.stateService.getEncryptedProviderKeys();
if (encProviderKeys == null) { if (encProviderKeys == null) {
return null; return null;
} }
const providerKeys: Map<string, SymmetricCryptoKey> = new Map<string, SymmetricCryptoKey>();
let setKey = false; let setKey = false;
for (const orgId in encProviderKeys) { for (const orgId in encProviderKeys) {
@ -316,10 +293,10 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
if (setKey) { if (setKey) {
this.providerKeys = providerKeys; await this.stateService.setDecryptedProviderKeys(providerKeys);
} }
return this.providerKeys; return providerKeys;
} }
async getProviderKey(providerId: string): Promise<SymmetricCryptoKey> { async getProviderKey(providerId: string): Promise<SymmetricCryptoKey> {
@ -336,84 +313,87 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
async hasKey(): Promise<boolean> { async hasKey(): Promise<boolean> {
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 { async hasKeyInMemory(userId?: string): Promise<boolean> {
return this.key != null; return await this.stateService.getCryptoMasterKey({ userId: userId }) != null;
} }
hasKeyStored(keySuffix: KeySuffixOptions): Promise<boolean> { async hasKeyStored(keySuffix: KeySuffixOptions, userId?: string): Promise<boolean> {
return this.secureStorageService.has(Keys.key, { keySuffix: keySuffix }); const key = keySuffix === KeySuffixOptions.Auto ?
await this.stateService.getCryptoMasterKeyAuto({ userId: userId }) :
await this.stateService.hasCryptoMasterKeyBiometric({ userId: userId });
return key != null;
} }
async hasEncKey(): Promise<boolean> { async hasEncKey(): Promise<boolean> {
const encKey = await this.storageService.get<string>(Keys.encKey); return await this.stateService.getEncryptedCryptoSymmetricKey() != null;
return encKey != null;
} }
async clearKey(clearSecretStorage: boolean = true): Promise<any> { async clearKey(clearSecretStorage: boolean = true, userId?: string): Promise<any> {
this.key = this.legacyEtmKey = null; await this.stateService.setCryptoMasterKey(null, { userId: userId });
await this.stateService.setLegacyEtmKey(null, { userId: userId });
if (clearSecretStorage) { if (clearSecretStorage) {
this.clearStoredKey('auto'); await this.clearSecretKeyStore(userId);
this.clearStoredKey('biometric');
} }
} }
async clearStoredKey(keySuffix: KeySuffixOptions) { 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<any> { async clearKeyHash(userId?: string): Promise<any> {
this.keyHash = null; return await this.stateService.setKeyHash(null, { userId: userId });
return this.storageService.remove(Keys.keyHash);
} }
clearEncKey(memoryOnly?: boolean): Promise<any> { async clearEncKey(memoryOnly?: boolean, userId?: string): Promise<void> {
this.encKey = null; await this.stateService.setDecryptedCryptoSymmetricKey(null, { userId: userId });
if (memoryOnly) { if (!memoryOnly) {
return Promise.resolve(); await this.stateService.setEncryptedCryptoSymmetricKey(null, { userId: userId });
} }
return this.storageService.remove(Keys.encKey);
} }
clearKeyPair(memoryOnly?: boolean): Promise<any> { async clearKeyPair(memoryOnly?: boolean, userId?: string): Promise<any> {
this.privateKey = null; const keysToClear: Promise<void>[] = [
this.publicKey = null; this.stateService.setDecryptedPrivateKey(null, { userId: userId }),
if (memoryOnly) { this.stateService.setPublicKey(null, { userId: userId }),
return Promise.resolve(); ];
if (!memoryOnly) {
keysToClear.push(this.stateService.setEncryptedPrivateKey(null, { userId: userId }));
} }
return this.storageService.remove(Keys.encPrivateKey); return Promise.all(keysToClear);
} }
clearOrgKeys(memoryOnly?: boolean): Promise<any> { async clearOrgKeys(memoryOnly?: boolean, userId?: string): Promise<void> {
this.orgKeys = null; await this.stateService.setDecryptedOrganizationKeys(null, { userId: userId });
if (memoryOnly) { if (!memoryOnly) {
return Promise.resolve(); await this.stateService.setEncryptedOrganizationKeys(null, { userId: userId });
}
}
async clearProviderKeys(memoryOnly?: boolean, userId?: string): Promise<void> {
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<any> { async clearPinProtectedKey(userId?: string): Promise<any> {
this.providerKeys = null; return await this.stateService.setEncryptedPinProtected(null, { userId: userId });
if (memoryOnly) {
return Promise.resolve();
}
return this.storageService.remove(Keys.encOrgKeys);
} }
clearPinProtectedKey(): Promise<any> { async clearKeys(userId?: string): Promise<any> {
return this.storageService.remove(ConstantsService.pinProtectedKey); await this.clearKey(true, userId);
} await this.clearKeyHash(userId);
await this.clearOrgKeys(false, userId);
async clearKeys(): Promise<any> { await this.clearProviderKeys(false, userId);
await this.clearKey(); await this.clearEncKey(false, userId);
await this.clearKeyHash(); await this.clearKeyPair(false, userId);
await this.clearOrgKeys(); await this.clearPinProtectedKey(userId);
await this.clearProviderKeys();
await this.clearEncKey();
await this.clearKeyPair();
await this.clearPinProtectedKey();
} }
async toggleKey(): Promise<any> { async toggleKey(): Promise<any> {
@ -442,7 +422,7 @@ export class CryptoService implements CryptoServiceAbstraction {
protectedKeyCs: EncString = null): protectedKeyCs: EncString = null):
Promise<SymmetricCryptoKey> { Promise<SymmetricCryptoKey> {
if (protectedKeyCs == null) { if (protectedKeyCs == null) {
const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey); const pinProtectedKey = await this.stateService.getEncryptedPinProtected();
if (pinProtectedKey == null) { if (pinProtectedKey == null) {
throw new Error('No PIN protected key found.'); throw new Error('No PIN protected key found.');
} }
@ -699,7 +679,7 @@ export class CryptoService implements CryptoServiceAbstraction {
async validateKey(key: SymmetricCryptoKey) { async validateKey(key: SymmetricCryptoKey) {
try { try {
const encPrivateKey = await this.storageService.get<string>(Keys.encPrivateKey); const encPrivateKey = await this.stateService.getEncryptedPrivateKey();
const encKey = await this.getEncKey(key); const encKey = await this.getEncKey(key);
if (encPrivateKey == null || encKey == null) { if (encPrivateKey == null || encKey == null) {
return false; return false;
@ -715,29 +695,30 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
// Helpers // Helpers
protected async storeKey(key: SymmetricCryptoKey, userId?: string) {
protected async storeKey(key: SymmetricCryptoKey) { if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId) || await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) {
if (await this.shouldStoreKey('auto') || await this.shouldStoreKey('biometric')) { await this.stateService.setCryptoMasterKeyB64(key.keyB64, { userId: userId });
this.secureStorageService.save(Keys.key, key.keyB64);
} else { } 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; let shouldStoreKey = false;
if (keySuffix === 'auto') { if (keySuffix === KeySuffixOptions.Auto) {
const vaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey); const vaultTimeout = await this.stateService.getVaultTimeout({ userId: userId });
shouldStoreKey = vaultTimeout == null; shouldStoreKey = vaultTimeout == null;
} else if (keySuffix === 'biometric') { } else if (keySuffix === KeySuffixOptions.Biometric) {
const biometricUnlock = await this.storageService.get<boolean>(ConstantsService.biometricUnlockKey); const biometricUnlock = await this.stateService.getBiometricUnlock({ userId: userId });
shouldStoreKey = biometricUnlock && this.platformUtilService.supportsSecureStorage(); shouldStoreKey = biometricUnlock && this.platformUtilService.supportsSecureStorage();
} }
return shouldStoreKey; return shouldStoreKey;
} }
protected retrieveKeyFromStorage(keySuffix: KeySuffixOptions) { protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string) {
return this.secureStorageService.get<string>(Keys.key, { keySuffix: keySuffix }); return keySuffix === KeySuffixOptions.Auto ?
await this.stateService.getCryptoMasterKeyAuto({ userId: userId }) :
await this.stateService.getCryptoMasterKeyBiometric({ userId: userId });
} }
private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> { private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
@ -759,7 +740,7 @@ export class CryptoService implements CryptoServiceAbstraction {
private async aesDecryptToUtf8(encType: EncryptionType, data: string, iv: string, mac: string, private async aesDecryptToUtf8(encType: EncryptionType, data: string, iv: string, mac: string,
key: SymmetricCryptoKey): Promise<string> { key: SymmetricCryptoKey): Promise<string> {
const keyForEnc = await this.getKeyForEncryption(key); 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) { if (theKey.macKey != null && mac == null) {
this.logService.error('mac required.'); this.logService.error('mac required.');
@ -788,7 +769,7 @@ export class CryptoService implements CryptoServiceAbstraction {
private async aesDecryptToBytes(encType: EncryptionType, data: ArrayBuffer, iv: ArrayBuffer, private async aesDecryptToBytes(encType: EncryptionType, data: ArrayBuffer, iv: ArrayBuffer,
mac: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> { mac: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
const keyForEnc = await this.getKeyForEncryption(key); 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) { if (theKey.macKey != null && mac == null) {
return null; return null;
@ -830,14 +811,16 @@ export class CryptoService implements CryptoServiceAbstraction {
return await this.getKey(); return await this.getKey();
} }
private resolveLegacyKey(encType: EncryptionType, key: SymmetricCryptoKey): SymmetricCryptoKey { private async resolveLegacyKey(encType: EncryptionType, key: SymmetricCryptoKey): Promise<SymmetricCryptoKey> {
if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && if (encType === EncryptionType.AesCbc128_HmacSha256_B64 &&
key.encType === EncryptionType.AesCbc256_B64) { key.encType === EncryptionType.AesCbc256_B64) {
// Old encrypt-then-mac scheme, make a new key // Old encrypt-then-mac scheme, make a new key
if (this.legacyEtmKey == null) { let legacyKey = await this.stateService.getLegacyEtmKey();
this.legacyEtmKey = new SymmetricCryptoKey(key.key, EncryptionType.AesCbc128_HmacSha256_B64); if (legacyKey == null) {
legacyKey = new SymmetricCryptoKey(key.key, EncryptionType.AesCbc128_HmacSha256_B64);
await this.stateService.setLegacyEtmKey(legacyKey);
} }
return this.legacyEtmKey; return legacyKey;
} }
return key; return key;
@ -885,4 +868,9 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
return [new SymmetricCryptoKey(encKey), encKeyEnc]; return [new SymmetricCryptoKey(encKey), encKeyEnc];
} }
private async clearSecretKeyStore(userId?: string): Promise<void> {
await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId });
await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId });
}
} }

View File

@ -2,10 +2,8 @@ import { Observable, Subject } from 'rxjs';
import { EnvironmentUrls } from '../models/domain/environmentUrls'; import { EnvironmentUrls } from '../models/domain/environmentUrls';
import { ConstantsService } from './constants.service';
import { EnvironmentService as EnvironmentServiceAbstraction, Urls } from '../abstractions/environment.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 { export class EnvironmentService implements EnvironmentServiceAbstraction {
@ -21,7 +19,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
private eventsUrl: string; private eventsUrl: string;
private keyConnectorUrl: string; private keyConnectorUrl: string;
constructor(private storageService: StorageService) {} constructor(private stateService: StateService) {}
hasBaseUrl() { hasBaseUrl() {
return this.baseUrl != null; return this.baseUrl != null;
@ -109,7 +107,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
} }
async setUrlsFromStorage(): Promise<void> { async setUrlsFromStorage(): Promise<void> {
const urlsObj: any = await this.storageService.get(ConstantsService.environmentUrlsKey); const urlsObj: any = await this.stateService.getEnvironmentUrls();
const urls = urlsObj || { const urls = urlsObj || {
base: null, base: null,
api: null, api: null,
@ -148,7 +146,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
urls.keyConnector = this.formatUrl(urls.keyConnector); urls.keyConnector = this.formatUrl(urls.keyConnector);
if (saveSettings) { if (saveSettings) {
await this.storageService.save(ConstantsService.environmentUrlsKey, { await this.stateService.setEnvironmentUrls({
base: urls.base, base: urls.base,
api: urls.api, api: urls.api,
identity: urls.identity, identity: urls.identity,

View File

@ -7,18 +7,16 @@ import { EventRequest } from '../models/request/eventRequest';
import { ApiService } from '../abstractions/api.service'; import { ApiService } from '../abstractions/api.service';
import { CipherService } from '../abstractions/cipher.service'; import { CipherService } from '../abstractions/cipher.service';
import { EventService as EventServiceAbstraction } from '../abstractions/event.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 { 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 { export class EventService implements EventServiceAbstraction {
private inited = false; private inited = false;
constructor(private storageService: StorageService, private apiService: ApiService, constructor(private apiService: ApiService, private cipherService: CipherService,
private userService: UserService, private cipherService: CipherService, private stateService: StateService, private logService: LogService,
private logService: LogService) { } private organizationService: OrganizationService) { }
init(checkOnInterval: boolean) { init(checkOnInterval: boolean) {
if (this.inited) { if (this.inited) {
@ -33,11 +31,11 @@ export class EventService implements EventServiceAbstraction {
} }
async collect(eventType: EventType, cipherId: string = null, uploadImmediately = false): Promise<any> { async collect(eventType: EventType, cipherId: string = null, uploadImmediately = false): Promise<any> {
const authed = await this.userService.isAuthenticated(); const authed = await this.stateService.getIsAuthenticated();
if (!authed) { if (!authed) {
return; return;
} }
const organizations = await this.userService.getAllOrganizations(); const organizations = await this.organizationService.getAll();
if (organizations == null) { if (organizations == null) {
return; return;
} }
@ -51,7 +49,7 @@ export class EventService implements EventServiceAbstraction {
return; return;
} }
} }
let eventCollection = await this.storageService.get<EventData[]>(ConstantsService.eventCollectionKey); let eventCollection = await this.stateService.getEventCollection();
if (eventCollection == null) { if (eventCollection == null) {
eventCollection = []; eventCollection = [];
} }
@ -60,18 +58,18 @@ export class EventService implements EventServiceAbstraction {
event.cipherId = cipherId; event.cipherId = cipherId;
event.date = new Date().toISOString(); event.date = new Date().toISOString();
eventCollection.push(event); eventCollection.push(event);
await this.storageService.save(ConstantsService.eventCollectionKey, eventCollection); await this.stateService.setEventCollection(eventCollection);
if (uploadImmediately) { if (uploadImmediately) {
await this.uploadEvents(); await this.uploadEvents();
} }
} }
async uploadEvents(): Promise<any> { async uploadEvents(userId?: string): Promise<any> {
const authed = await this.userService.isAuthenticated(); const authed = await this.stateService.getIsAuthenticated({ userId: userId });
if (!authed) { if (!authed) {
return; return;
} }
const eventCollection = await this.storageService.get<EventData[]>(ConstantsService.eventCollectionKey); const eventCollection = await this.stateService.getEventCollection({ userId: userId });
if (eventCollection == null || eventCollection.length === 0) { if (eventCollection == null || eventCollection.length === 0) {
return; return;
} }
@ -84,13 +82,13 @@ export class EventService implements EventServiceAbstraction {
}); });
try { try {
await this.apiService.postEventsCollect(request); await this.apiService.postEventsCollect(request);
this.clearEvents(); this.clearEvents(userId);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
} }
async clearEvents(): Promise<any> { async clearEvents(userId?: string): Promise<any> {
await this.storageService.remove(ConstantsService.eventCollectionKey); await this.stateService.setEventCollection(null, { userId: userId });
} }
} }

View File

@ -15,28 +15,22 @@ import { CipherService } from '../abstractions/cipher.service';
import { CryptoService } from '../abstractions/crypto.service'; import { CryptoService } from '../abstractions/crypto.service';
import { FolderService as FolderServiceAbstraction } from '../abstractions/folder.service'; import { FolderService as FolderServiceAbstraction } from '../abstractions/folder.service';
import { I18nService } from '../abstractions/i18n.service'; import { I18nService } from '../abstractions/i18n.service';
import { StorageService } from '../abstractions/storage.service'; import { StateService } from '../abstractions/state.service';
import { UserService } from '../abstractions/user.service';
import { CipherData } from '../models/data/cipherData'; import { CipherData } from '../models/data/cipherData';
import { ServiceUtils } from '../misc/serviceUtils'; import { ServiceUtils } from '../misc/serviceUtils';
import { Utils } from '../misc/utils'; import { Utils } from '../misc/utils';
const Keys = {
foldersPrefix: 'folders_',
ciphersPrefix: 'ciphers_',
};
const NestingDelimiter = '/'; const NestingDelimiter = '/';
export class FolderService implements FolderServiceAbstraction { 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, async clearCache(userId?: string): Promise<void> {
private apiService: ApiService, private storageService: StorageService, await this.stateService.setDecryptedFolders(null, { userId: userId });
private i18nService: I18nService, private cipherService: CipherService) { }
clearCache(): void {
this.decryptedFolderCache = null;
} }
async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise<Folder> { async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise<Folder> {
@ -47,9 +41,7 @@ export class FolderService implements FolderServiceAbstraction {
} }
async get(id: string): Promise<Folder> { async get(id: string): Promise<Folder> {
const userId = await this.userService.getUserId(); const folders = await this.stateService.getEncryptedFolders();
const folders = await this.storageService.get<{ [id: string]: FolderData; }>(
Keys.foldersPrefix + userId);
if (folders == null || !folders.hasOwnProperty(id)) { if (folders == null || !folders.hasOwnProperty(id)) {
return null; return null;
} }
@ -58,9 +50,7 @@ export class FolderService implements FolderServiceAbstraction {
} }
async getAll(): Promise<Folder[]> { async getAll(): Promise<Folder[]> {
const userId = await this.userService.getUserId(); const folders = await this.stateService.getEncryptedFolders();
const folders = await this.storageService.get<{ [id: string]: FolderData; }>(
Keys.foldersPrefix + userId);
const response: Folder[] = []; const response: Folder[] = [];
for (const id in folders) { for (const id in folders) {
if (folders.hasOwnProperty(id)) { if (folders.hasOwnProperty(id)) {
@ -71,8 +61,9 @@ export class FolderService implements FolderServiceAbstraction {
} }
async getAllDecrypted(): Promise<FolderView[]> { async getAllDecrypted(): Promise<FolderView[]> {
if (this.decryptedFolderCache != null) { const decryptedFolders = await this.stateService.getDecryptedFolders();
return this.decryptedFolderCache; if (decryptedFolders != null) {
return decryptedFolders;
} }
const hasKey = await this.cryptoService.hasKey(); const hasKey = await this.cryptoService.hasKey();
@ -94,8 +85,8 @@ export class FolderService implements FolderServiceAbstraction {
noneFolder.name = this.i18nService.t('noneFolder'); noneFolder.name = this.i18nService.t('noneFolder');
decFolders.push(noneFolder); decFolders.push(noneFolder);
this.decryptedFolderCache = decFolders; await this.stateService.setDecryptedFolders(decFolders);
return this.decryptedFolderCache; return decFolders;
} }
async getAllNested(): Promise<TreeNode<FolderView>[]> { async getAllNested(): Promise<TreeNode<FolderView>[]> {
@ -127,15 +118,13 @@ export class FolderService implements FolderServiceAbstraction {
response = await this.apiService.putFolder(folder.id, request); 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); const data = new FolderData(response, userId);
await this.upsert(data); await this.upsert(data);
} }
async upsert(folder: FolderData | FolderData[]): Promise<any> { async upsert(folder: FolderData | FolderData[]): Promise<any> {
const userId = await this.userService.getUserId(); let folders = await this.stateService.getEncryptedFolders();
let folders = await this.storageService.get<{ [id: string]: FolderData; }>(
Keys.foldersPrefix + userId);
if (folders == null) { if (folders == null) {
folders = {}; folders = {};
} }
@ -149,25 +138,22 @@ export class FolderService implements FolderServiceAbstraction {
}); });
} }
await this.storageService.save(Keys.foldersPrefix + userId, folders); await this.stateService.setDecryptedFolders(null);
this.decryptedFolderCache = null; await this.stateService.setEncryptedFolders(folders);
} }
async replace(folders: { [id: string]: FolderData; }): Promise<any> { async replace(folders: { [id: string]: FolderData; }): Promise<any> {
const userId = await this.userService.getUserId(); await this.stateService.setDecryptedFolders(null);
await this.storageService.save(Keys.foldersPrefix + userId, folders); await this.stateService.setEncryptedFolders(folders);
this.decryptedFolderCache = null;
} }
async clear(userId: string): Promise<any> { async clear(userId?: string): Promise<any> {
await this.storageService.remove(Keys.foldersPrefix + userId); await this.stateService.setDecryptedFolders(null, { userId: userId });
this.decryptedFolderCache = null; await this.stateService.setEncryptedFolders(null, { userId: userId });
} }
async delete(id: string | string[]): Promise<any> { async delete(id: string | string[]): Promise<any> {
const userId = await this.userService.getUserId(); const folders = await this.stateService.getEncryptedFolders();
const folders = await this.storageService.get<{ [id: string]: FolderData; }>(
Keys.foldersPrefix + userId);
if (folders == null) { if (folders == null) {
return; return;
} }
@ -183,11 +169,11 @@ export class FolderService implements FolderServiceAbstraction {
}); });
} }
await this.storageService.save(Keys.foldersPrefix + userId, folders); await this.stateService.setDecryptedFolders(null);
this.decryptedFolderCache = null; await this.stateService.setEncryptedFolders(folders);
// Items in a deleted folder are re-assigned to "No Folder" // 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) { if (ciphers != null) {
const updates: CipherData[] = []; const updates: CipherData[] = [];
for (const cId in ciphers) { for (const cId in ciphers) {

View File

@ -2,9 +2,9 @@ import { ApiService } from '../abstractions/api.service';
import { CryptoService } from '../abstractions/crypto.service'; import { CryptoService } from '../abstractions/crypto.service';
import { KeyConnectorService as KeyConnectorServiceAbstraction } from '../abstractions/keyConnector.service'; import { KeyConnectorService as KeyConnectorServiceAbstraction } from '../abstractions/keyConnector.service';
import { LogService } from '../abstractions/log.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 { TokenService } from '../abstractions/token.service';
import { UserService } from '../abstractions/user.service';
import { OrganizationUserType } from '../enums/organizationUserType'; import { OrganizationUserType } from '../enums/organizationUserType';
@ -14,25 +14,17 @@ import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKeyRequest'; import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKeyRequest';
const Keys = {
usesKeyConnector: 'usesKeyConnector',
convertAccountToKeyConnector: 'convertAccountToKeyConnector',
};
export class KeyConnectorService implements KeyConnectorServiceAbstraction { export class KeyConnectorService implements KeyConnectorServiceAbstraction {
private usesKeyConnector?: boolean = null; constructor(private stateService: StateService, private cryptoService: CryptoService,
private apiService: ApiService, private tokenService: TokenService,
constructor(private storageService: StorageService, private userService: UserService, private logService: LogService, private organizationService: OrganizationService) { }
private cryptoService: CryptoService, private apiService: ApiService,
private tokenService: TokenService, private logService: LogService) { }
setUsesKeyConnector(usesKeyConnector: boolean) { setUsesKeyConnector(usesKeyConnector: boolean) {
this.usesKeyConnector = usesKeyConnector; return this.stateService.setUsesKeyConnector(usesKeyConnector);
return this.storageService.save(Keys.usesKeyConnector, usesKeyConnector);
} }
async getUsesKeyConnector(): Promise<boolean> { async getUsesKeyConnector(): Promise<boolean> {
return this.usesKeyConnector ??= await this.storageService.get<boolean>(Keys.usesKeyConnector); return await this.stateService.getUsesKeyConnector();
} }
async userNeedsMigration() { async userNeedsMigration() {
@ -70,7 +62,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
} }
async getManagingOrganization() { async getManagingOrganization() {
const orgs = await this.userService.getAllOrganizations(); const orgs = await this.organizationService.getAll();
return orgs.find(o => return orgs.find(o =>
o.keyConnectorEnabled && o.keyConnectorEnabled &&
o.type !== OrganizationUserType.Admin && o.type !== OrganizationUserType.Admin &&
@ -79,15 +71,15 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
} }
async setConvertAccountRequired(status: boolean) { async setConvertAccountRequired(status: boolean) {
await this.storageService.save(Keys.convertAccountToKeyConnector, status); await this.stateService.setConvertAccountToKeyConnector(status);
} }
async getConvertAccountRequired(): Promise<boolean> { async getConvertAccountRequired(): Promise<boolean> {
return await this.storageService.get(Keys.convertAccountToKeyConnector); return await this.stateService.getConvertAccountToKeyConnector();
} }
async removeConvertAccountRequired() { async removeConvertAccountRequired() {
await this.storageService.remove(Keys.convertAccountToKeyConnector); await this.stateService.setConvertAccountToKeyConnector(null);
} }
async clear() { async clear() {

View File

@ -8,8 +8,8 @@ import { AppIdService } from '../abstractions/appId.service';
import { EnvironmentService } from '../abstractions/environment.service'; import { EnvironmentService } from '../abstractions/environment.service';
import { LogService } from '../abstractions/log.service'; import { LogService } from '../abstractions/log.service';
import { NotificationsService as NotificationsServiceAbstraction } from '../abstractions/notifications.service'; import { NotificationsService as NotificationsServiceAbstraction } from '../abstractions/notifications.service';
import { StateService } from '../abstractions/state.service';
import { SyncService } from '../abstractions/sync.service'; import { SyncService } from '../abstractions/sync.service';
import { UserService } from '../abstractions/user.service';
import { VaultTimeoutService } from '../abstractions/vaultTimeout.service'; import { VaultTimeoutService } from '../abstractions/vaultTimeout.service';
import { import {
@ -27,10 +27,10 @@ export class NotificationsService implements NotificationsServiceAbstraction {
private inactive = false; private inactive = false;
private reconnectTimer: any = null; private reconnectTimer: any = null;
constructor(private userService: UserService, private syncService: SyncService, constructor(private syncService: SyncService, private appIdService: AppIdService,
private appIdService: AppIdService, private apiService: ApiService, private apiService: ApiService, private vaultTimeoutService: VaultTimeoutService,
private vaultTimeoutService: VaultTimeoutService, private environmentService: EnvironmentService, private environmentService: EnvironmentService, private logoutCallback: () => Promise<void>,
private logoutCallback: () => Promise<void>, private logService: LogService) { private logService: LogService, private stateService: StateService) {
this.environmentService.urls.subscribe(() => { this.environmentService.urls.subscribe(() => {
if (!this.inited) { if (!this.inited) {
return; return;
@ -117,9 +117,9 @@ export class NotificationsService implements NotificationsServiceAbstraction {
return; return;
} }
const isAuthenticated = await this.userService.isAuthenticated(); const isAuthenticated = await this.stateService.getIsAuthenticated();
const payloadUserId = notification.payload.userId || notification.payload.UserId; 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) { if (isAuthenticated && payloadUserId != null && payloadUserId !== myUserId) {
return; return;
} }
@ -202,7 +202,7 @@ export class NotificationsService implements NotificationsServiceAbstraction {
} }
private async isAuthedAndUnlocked() { private async isAuthedAndUnlocked() {
if (await this.userService.isAuthenticated()) { if (await this.stateService.getIsAuthenticated()) {
const locked = await this.vaultTimeoutService.isLocked(); const locked = await this.vaultTimeoutService.isLocked();
return !locked; return !locked;
} }

View File

@ -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<Organization> {
const organizations = await this.stateService.getOrganizations();
if (organizations == null || !organizations.hasOwnProperty(id)) {
return null;
}
return new Organization(organizations[id]);
}
async getByIdentifier(identifier: string): Promise<Organization> {
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<Organization[]> {
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<boolean> {
const orgs = await this.getAll();
return orgs.some(o => o.familySponsorshipAvailable || o.familySponsorshipFriendlyName !== null);
}
}

View File

@ -10,7 +10,7 @@ import {
PasswordGenerationService as PasswordGenerationServiceAbstraction, PasswordGenerationService as PasswordGenerationServiceAbstraction,
} from '../abstractions/passwordGeneration.service'; } from '../abstractions/passwordGeneration.service';
import { PolicyService } from '../abstractions/policy.service'; import { PolicyService } from '../abstractions/policy.service';
import { StorageService } from '../abstractions/storage.service'; import { StateService } from '../abstractions/state.service';
import { EEFLongWordList } from '../misc/wordlist'; import { EEFLongWordList } from '../misc/wordlist';
@ -34,19 +34,11 @@ const DefaultOptions = {
includeNumber: false, includeNumber: false,
}; };
const Keys = {
options: 'passwordGenerationOptions',
history: 'generatedPasswordHistory',
};
const MaxPasswordsInHistory = 100; const MaxPasswordsInHistory = 100;
export class PasswordGenerationService implements PasswordGenerationServiceAbstraction { export class PasswordGenerationService implements PasswordGenerationServiceAbstraction {
private optionsCache: any; constructor(private cryptoService: CryptoService, private policyService: PolicyService,
private history: GeneratedPasswordHistory[]; private stateService: StateService) { }
constructor(private cryptoService: CryptoService, private storageService: StorageService,
private policyService: PolicyService) { }
async generatePassword(options: any): Promise<string> { async generatePassword(options: any): Promise<string> {
// overload defaults with given options // overload defaults with given options
@ -188,17 +180,16 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
} }
async getOptions(): Promise<[any, PasswordGeneratorPolicyOptions]> { async getOptions(): Promise<[any, PasswordGeneratorPolicyOptions]> {
if (this.optionsCache == null) { let options = await this.stateService.getPasswordGenerationOptions();
const options = await this.storageService.get(Keys.options); if (options == null) {
if (options == null) { options = DefaultOptions;
this.optionsCache = DefaultOptions; } else {
} else { options = Object.assign({}, DefaultOptions, options);
this.optionsCache = Object.assign({}, DefaultOptions, options);
}
} }
const enforcedOptions = await this.enforcePasswordGeneratorPoliciesOnOptions(this.optionsCache); await this.stateService.setPasswordGenerationOptions(options);
this.optionsCache = enforcedOptions[0]; const enforcedOptions = await this.enforcePasswordGeneratorPoliciesOnOptions(options);
return [this.optionsCache, enforcedOptions[1]]; options = enforcedOptions[0];
return [options, enforcedOptions[1]];
} }
async enforcePasswordGeneratorPoliciesOnOptions(options: any): Promise<[any, PasswordGeneratorPolicyOptions]> { async enforcePasswordGeneratorPoliciesOnOptions(options: any): Promise<[any, PasswordGeneratorPolicyOptions]> {
@ -332,8 +323,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
} }
async saveOptions(options: any) { async saveOptions(options: any) {
await this.storageService.save(Keys.options, options); await this.stateService.setPasswordGenerationOptions(options);
this.optionsCache = options;
} }
async getHistory(): Promise<GeneratedPasswordHistory[]> { async getHistory(): Promise<GeneratedPasswordHistory[]> {
@ -342,12 +332,16 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
return new Array<GeneratedPasswordHistory>(); return new Array<GeneratedPasswordHistory>();
} }
if (!this.history) { if (await this.stateService.getDecryptedPasswordGenerationHistory() != null) {
const encrypted = await this.storageService.get<GeneratedPasswordHistory[]>(Keys.history); const encrypted = await this.stateService.getEncryptedPasswordGenerationHistory();
this.history = await this.decryptHistory(encrypted); const decrypted = await this.decryptHistory(encrypted);
await this.stateService.setDecryptedPasswordGenerationHistory(decrypted);
} }
return this.history || new Array<GeneratedPasswordHistory>(); const passwordGenerationHistory = await this.stateService.getDecryptedPasswordGenerationHistory();
return passwordGenerationHistory != null ?
passwordGenerationHistory :
new Array<GeneratedPasswordHistory>();
} }
async addHistory(password: string): Promise<any> { async addHistory(password: string): Promise<any> {
@ -372,12 +366,12 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
} }
const newHistory = await this.encryptHistory(currentHistory); const newHistory = await this.encryptHistory(currentHistory);
return await this.storageService.save(Keys.history, newHistory); return await this.stateService.setEncryptedPasswordGenerationHistory(newHistory);
} }
async clear(): Promise<any> { async clear(userId?: string): Promise<any> {
this.history = []; await this.stateService.setEncryptedPasswordGenerationHistory(null, { userId: userId });
return await this.storageService.remove(Keys.history); await this.stateService.setDecryptedPasswordGenerationHistory(null, { userId: userId });
} }
passwordStrength(password: string, userInputs: string[] = null): zxcvbn.ZXCVBNResult { passwordStrength(password: string, userInputs: string[] = null): zxcvbn.ZXCVBNResult {

View File

@ -1,6 +1,6 @@
import { OrganizationService } from '../abstractions/organization.service';
import { PolicyService as PolicyServiceAbstraction } from '../abstractions/policy.service'; import { PolicyService as PolicyServiceAbstraction } from '../abstractions/policy.service';
import { StorageService } from '../abstractions/storage.service'; import { StateService } from '../abstractions/state.service';
import { UserService } from '../abstractions/user.service';
import { PolicyData } from '../models/data/policyData'; import { PolicyData } from '../models/data/policyData';
@ -17,43 +17,40 @@ import { ApiService } from '../abstractions/api.service';
import { ListResponse } from '../models/response/listResponse'; import { ListResponse } from '../models/response/listResponse';
import { PolicyResponse } from '../models/response/policyResponse'; import { PolicyResponse } from '../models/response/policyResponse';
const Keys = {
policiesPrefix: 'policies_',
};
export class PolicyService implements PolicyServiceAbstraction { export class PolicyService implements PolicyServiceAbstraction {
policyCache: Policy[]; policyCache: Policy[];
constructor(private userService: UserService, private storageService: StorageService, constructor(private stateService: StateService, private organizationService: OrganizationService,
private apiService: ApiService) { private apiService: ApiService) {
} }
clearCache(): void { async clearCache(): Promise<void> {
this.policyCache = null; await this.stateService.setDecryptedPolicies(null);
} }
async getAll(type?: PolicyType): Promise<Policy[]> { async getAll(type?: PolicyType, userId?: string): Promise<Policy[]> {
if (this.policyCache == null) { let response: Policy[] = [];
const userId = await this.userService.getUserId(); const decryptedPolicies = await this.stateService.getDecryptedPolicies({ userId: userId });
const policies = await this.storageService.get<{ [id: string]: PolicyData; }>( if (decryptedPolicies != null) {
Keys.policiesPrefix + userId); response = decryptedPolicies;
const response: Policy[] = []; } else {
for (const id in policies) { const diskPolicies = await this.stateService.getEncryptedPolicies({ userId: userId });
if (policies.hasOwnProperty(id)) { for (const id in diskPolicies) {
response.push(new Policy(policies[id])); if (diskPolicies.hasOwnProperty(id)) {
response.push(new Policy(diskPolicies[id]));
} }
} }
this.policyCache = response; await this.stateService.setDecryptedPolicies(response, { userId: userId });
} }
if (type != null) { if (type != null) {
return this.policyCache.filter(p => p.type === type); return response.filter(policy => policy.type === type);
} else { } else {
return this.policyCache; return response;
} }
} }
async getPolicyForOrganization(policyType: PolicyType, organizationId: string): Promise<Policy> { async getPolicyForOrganization(policyType: PolicyType, organizationId: string): Promise<Policy> {
const org = await this.userService.getOrganization(organizationId); const org = await this.organizationService.get(organizationId);
if (org?.isProviderUser) { if (org?.isProviderUser) {
const orgPolicies = await this.apiService.getPolicies(organizationId); const orgPolicies = await this.apiService.getPolicies(organizationId);
const policy = orgPolicies.data.find(p => p.organizationId === 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<any> { async replace(policies: { [id: string]: PolicyData; }): Promise<any> {
const userId = await this.userService.getUserId(); await this.stateService.setDecryptedPolicies(null);
await this.storageService.save(Keys.policiesPrefix + userId, policies); await this.stateService.setEncryptedPolicies(policies);
this.policyCache = null;
} }
async clear(userId: string): Promise<any> { async clear(userId?: string): Promise<any> {
await this.storageService.remove(Keys.policiesPrefix + userId); await this.stateService.setDecryptedPolicies(null, { userId: userId });
this.policyCache = null; await this.stateService.setEncryptedPolicies(null, { userId: userId });
} }
async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise<MasterPasswordPolicyOptions> { async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise<MasterPasswordPolicyOptions> {
@ -187,9 +183,9 @@ export class PolicyService implements PolicyServiceAbstraction {
return policiesData.map(p => new Policy(p)); return policiesData.map(p => new Policy(p));
} }
async policyAppliesToUser(policyType: PolicyType, policyFilter?: (policy: Policy) => boolean) { async policyAppliesToUser(policyType: PolicyType, policyFilter?: (policy: Policy) => boolean, userId?: string) {
const policies = await this.getAll(policyType); const policies = await this.getAll(policyType, userId);
const organizations = await this.userService.getAllOrganizations(); const organizations = await this.organizationService.getAll(userId);
let filteredPolicies; let filteredPolicies;
if (policyFilter != null) { if (policyFilter != null) {

View File

@ -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<Provider> {
const providers = await this.stateService.getProviders();
if (providers == null || !providers.hasOwnProperty(id)) {
return null;
}
return new Provider(providers[id]);
}
async getAll(): Promise<Provider[]> {
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);
}
}

View File

@ -12,7 +12,6 @@ import { SendFile } from '../models/domain/sendFile';
import { SendText } from '../models/domain/sendText'; import { SendText } from '../models/domain/sendText';
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
import { FileUploadType } from '../enums/fileUploadType';
import { SendType } from '../enums/sendType'; import { SendType } from '../enums/sendType';
import { SendView } from '../models/view/sendView'; import { SendView } from '../models/view/sendView';
@ -23,25 +22,17 @@ import { CryptoFunctionService } from '../abstractions/cryptoFunction.service';
import { FileUploadService } from '../abstractions/fileUpload.service'; import { FileUploadService } from '../abstractions/fileUpload.service';
import { I18nService } from '../abstractions/i18n.service'; import { I18nService } from '../abstractions/i18n.service';
import { SendService as SendServiceAbstraction } from '../abstractions/send.service'; import { SendService as SendServiceAbstraction } from '../abstractions/send.service';
import { StorageService } from '../abstractions/storage.service'; import { StateService } from '../abstractions/state.service';
import { UserService } from '../abstractions/user.service';
import { Utils } from '../misc/utils'; import { Utils } from '../misc/utils';
const Keys = {
sendsPrefix: 'sends_',
};
export class SendService implements SendServiceAbstraction { 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, async clearCache(): Promise<void> {
private apiService: ApiService, private fileUploadService: FileUploadService, await this.stateService.setDecryptedSends(null);
private storageService: StorageService, private i18nService: I18nService,
private cryptoFunctionService: CryptoFunctionService) { }
clearCache(): void {
this.decryptedSendCache = null;
} }
async encrypt(model: SendView, file: File | ArrayBuffer, password: string, async encrypt(model: SendView, file: File | ArrayBuffer, password: string,
@ -85,9 +76,7 @@ export class SendService implements SendServiceAbstraction {
} }
async get(id: string): Promise<Send> { async get(id: string): Promise<Send> {
const userId = await this.userService.getUserId(); const sends = await this.stateService.getEncryptedSends();
const sends = await this.storageService.get<{ [id: string]: SendData; }>(
Keys.sendsPrefix + userId);
if (sends == null || !sends.hasOwnProperty(id)) { if (sends == null || !sends.hasOwnProperty(id)) {
return null; return null;
} }
@ -96,9 +85,7 @@ export class SendService implements SendServiceAbstraction {
} }
async getAll(): Promise<Send[]> { async getAll(): Promise<Send[]> {
const userId = await this.userService.getUserId(); const sends = await this.stateService.getEncryptedSends();
const sends = await this.storageService.get<{ [id: string]: SendData; }>(
Keys.sendsPrefix + userId);
const response: Send[] = []; const response: Send[] = [];
for (const id in sends) { for (const id in sends) {
if (sends.hasOwnProperty(id)) { if (sends.hasOwnProperty(id)) {
@ -109,16 +96,17 @@ export class SendService implements SendServiceAbstraction {
} }
async getAllDecrypted(): Promise<SendView[]> { async getAllDecrypted(): Promise<SendView[]> {
if (this.decryptedSendCache != null) { let decSends = await this.stateService.getDecryptedSends();
return this.decryptedSendCache; if (decSends != null) {
return decSends;
} }
decSends = [];
const hasKey = await this.cryptoService.hasKey(); const hasKey = await this.cryptoService.hasKey();
if (!hasKey) { if (!hasKey) {
throw new Error('No key.'); throw new Error('No key.');
} }
const decSends: SendView[] = [];
const promises: Promise<any>[] = []; const promises: Promise<any>[] = [];
const sends = await this.getAll(); const sends = await this.getAll();
sends.forEach(send => { sends.forEach(send => {
@ -128,8 +116,8 @@ export class SendService implements SendServiceAbstraction {
await Promise.all(promises); await Promise.all(promises);
decSends.sort(Utils.getSortFunction(this.i18nService, 'name')); decSends.sort(Utils.getSortFunction(this.i18nService, 'name'));
this.decryptedSendCache = decSends; await this.stateService.setDecryptedSends(decSends);
return this.decryptedSendCache; return decSends;
} }
async saveWithServer(sendData: [Send, EncArrayBuffer]): Promise<any> { async saveWithServer(sendData: [Send, EncArrayBuffer]): Promise<any> {
@ -160,7 +148,7 @@ export class SendService implements SendServiceAbstraction {
response = await this.apiService.putSend(sendData[0].id, request); 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); const data = new SendData(response, userId);
await this.upsert(data); await this.upsert(data);
} }
@ -191,9 +179,7 @@ export class SendService implements SendServiceAbstraction {
} }
async upsert(send: SendData | SendData[]): Promise<any> { async upsert(send: SendData | SendData[]): Promise<any> {
const userId = await this.userService.getUserId(); let sends = await this.stateService.getEncryptedSends();
let sends = await this.storageService.get<{ [id: string]: SendData; }>(
Keys.sendsPrefix + userId);
if (sends == null) { if (sends == null) {
sends = {}; sends = {};
} }
@ -207,25 +193,21 @@ export class SendService implements SendServiceAbstraction {
}); });
} }
await this.storageService.save(Keys.sendsPrefix + userId, sends); await this.replace(sends);
this.decryptedSendCache = null;
} }
async replace(sends: { [id: string]: SendData; }): Promise<any> { async replace(sends: { [id: string]: SendData; }): Promise<any> {
const userId = await this.userService.getUserId(); await this.stateService.setDecryptedSends(null);
await this.storageService.save(Keys.sendsPrefix + userId, sends); await this.stateService.setEncryptedSends(sends);
this.decryptedSendCache = null;
} }
async clear(userId: string): Promise<any> { async clear(): Promise<any> {
await this.storageService.remove(Keys.sendsPrefix + userId); await this.stateService.setDecryptedSends(null);
this.decryptedSendCache = null; await this.stateService.setEncryptedSends(null);
} }
async delete(id: string | string[]): Promise<any> { async delete(id: string | string[]): Promise<any> {
const userId = await this.userService.getUserId(); const sends = await this.stateService.getEncryptedSends();
const sends = await this.storageService.get<{ [id: string]: SendData; }>(
Keys.sendsPrefix + userId);
if (sends == null) { if (sends == null) {
return; return;
} }
@ -241,8 +223,7 @@ export class SendService implements SendServiceAbstraction {
}); });
} }
await this.storageService.save(Keys.sendsPrefix + userId, sends); await this.replace(sends);
this.decryptedSendCache = null;
} }
async deleteWithServer(id: string): Promise<any> { async deleteWithServer(id: string): Promise<any> {
@ -252,7 +233,7 @@ export class SendService implements SendServiceAbstraction {
async removePasswordWithServer(id: string): Promise<any> { async removePasswordWithServer(id: string): Promise<any> {
const response = await this.apiService.putSendRemovePassword(id); 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); const data = new SendData(response, userId);
await this.upsert(data); await this.upsert(data);
} }
@ -270,7 +251,7 @@ export class SendService implements SendServiceAbstraction {
reject(e); reject(e);
} }
}; };
reader.onerror = evt => { reader.onerror = () => {
reject('Error reading file.'); reject('Error reading file.');
}; };
}); });

View File

@ -1,6 +1,5 @@
import { SettingsService as SettingsServiceAbstraction } from '../abstractions/settings.service'; import { SettingsService as SettingsServiceAbstraction } from '../abstractions/settings.service';
import { StorageService } from '../abstractions/storage.service'; import { StateService } from '../abstractions/state.service';
import { UserService } from '../abstractions/user.service';
const Keys = { const Keys = {
settingsPrefix: 'settings_', settingsPrefix: 'settings_',
@ -8,13 +7,11 @@ const Keys = {
}; };
export class SettingsService implements SettingsServiceAbstraction { export class SettingsService implements SettingsServiceAbstraction {
private settingsCache: any; constructor(private stateService: StateService) {
constructor(private userService: UserService, private storageService: StorageService) {
} }
clearCache(): void { async clearCache(): Promise<void> {
this.settingsCache = null; await this.stateService.setSettings(null);
} }
getEquivalentDomains(): Promise<any> { getEquivalentDomains(): Promise<any> {
@ -25,19 +22,18 @@ export class SettingsService implements SettingsServiceAbstraction {
await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains); await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains);
} }
async clear(userId: string): Promise<void> { async clear(userId?: string): Promise<void> {
await this.storageService.remove(Keys.settingsPrefix + userId); await this.stateService.setSettings(null, { userId: userId });
this.clearCache();
} }
// Helpers // Helpers
private async getSettings(): Promise<any> { private async getSettings(): Promise<any> {
if (this.settingsCache == null) { const settings = await this.stateService.getSettings();
const userId = await this.userService.getUserId(); if (settings == null) {
this.settingsCache = this.storageService.get(Keys.settingsPrefix + userId); const userId = await this.stateService.getUserId();
} }
return this.settingsCache; return settings;
} }
private async getSettingsKey(key: string): Promise<any> { private async getSettingsKey(key: string): Promise<any> {
@ -49,14 +45,12 @@ export class SettingsService implements SettingsServiceAbstraction {
} }
private async setSettingsKey(key: string, value: any): Promise<void> { private async setSettingsKey(key: string, value: any): Promise<void> {
const userId = await this.userService.getUserId();
let settings = await this.getSettings(); let settings = await this.getSettings();
if (!settings) { if (!settings) {
settings = {}; settings = {};
} }
settings[key] = value; settings[key] = value;
await this.storageService.save(Keys.settingsPrefix + userId, settings); await this.stateService.setSettings(settings);
this.settingsCache = settings;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -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<boolean> {
const currentStateVersion = (await this.storageService.get<State>('state'))?.globals?.stateVersion;
return currentStateVersion == null || currentStateVersion < this.latestVersion;
}
async migrate(): Promise<void> {
let currentStateVersion = (await this.storageService.get<State>('state'))?.globals?.stateVersion ?? 1;
while (currentStateVersion < this.latestVersion) {
switch (currentStateVersion) {
case 1:
await this.migrateStateFrom1To2();
break;
}
currentStateVersion += 1;
}
}
private async migrateStateFrom1To2(): Promise<void> {
const options: StorageOptions = { htmlStorageLocation: HtmlStorageLocation.Local };
const userId = await this.storageService.get<string>('userId');
const initialState: State = userId == null ?
{
globals: {
stateVersion: 2,
},
accounts: {},
activeUserId: null,
} :
{
activeUserId: userId,
globals: {
biometricAwaitingAcceptance: await this.storageService.get<boolean>(v1Keys.biometricAwaitingAcceptance, options),
biometricFingerprintValidated: await this.storageService.get<boolean>(v1Keys.biometricFingerprintValidated, options),
biometricText: await this.storageService.get<string>(v1Keys.biometricText, options),
disableFavicon: await this.storageService.get<boolean>(v1Keys.disableFavicon, options),
enableAlwaysOnTop: await this.storageService.get<boolean>(v1Keys.enableAlwaysOnTop, options),
enableBiometrics: await this.storageService.get<boolean>(v1Keys.enableBiometric, options),
installedVersion: await this.storageService.get<string>(v1Keys.installedVersion, options),
lastActive: await this.storageService.get<number>(v1Keys.lastActive, options),
locale: await this.storageService.get<string>(v1Keys.locale, options),
loginRedirect: null,
mainWindowSize: null,
noAutoPromptBiometrics: await this.storageService.get<boolean>(v1Keys.disableAutoBiometricsPrompt, options),
noAutoPromptBiometricsText: await this.storageService.get<string>(v1Keys.noAutoPromptBiometricsText, options),
openAtLogin: await this.storageService.get<boolean>(v1Keys.openAtLogin, options),
organizationInvitation: await this.storageService.get<string>('', options),
rememberedEmail: await this.storageService.get<string>(v1Keys.rememberedEmail, options),
stateVersion: 2,
theme: await this.storageService.get<string>(v1Keys.theme, options),
twoFactorToken: await this.storageService.get<string>(v1KeyPrefixes.twoFactorToken + userId, options),
vaultTimeout: await this.storageService.get<number>(v1Keys.vaultTimeout, options),
vaultTimeoutAction: await this.storageService.get<string>(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<EventData[]>(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<GeneratedPasswordHistory[]>('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<string>(v1Keys.clientSecret, options),
cryptoMasterKey: null,
cryptoMasterKeyAuto: null,
cryptoMasterKeyB64: null,
cryptoMasterKeyBiometric: null,
cryptoSymmetricKey: {
encrypted: await this.storageService.get<string>(v1Keys.encKey, options),
decrypted: null,
},
legacyEtmKey: null,
organizationKeys: {
decrypted: null,
encrypted: await this.storageService.get<any>(v1Keys.encOrgKeys + userId, options),
},
privateKey: {
decrypted: null,
encrypted: await this.storageService.get<string>(v1Keys.encPrivate, options),
},
providerKeys: {
decrypted: null,
encrypted: await this.storageService.get<any>(v1Keys.encProviderKeys + userId, options),
},
publicKey: null,
},
profile: {
apiKeyClientId: await this.storageService.get<string>(v1Keys.clientId, options),
authenticationStatus: null,
convertAccountToKeyConnector: await this.storageService.get<boolean>(v1Keys.convertAccountToKeyConnector, options),
email: await this.storageService.get<string>(v1Keys.userEmail, options),
emailVerified: await this.storageService.get<boolean>(v1Keys.emailVerified, options),
entityId: null,
entityType: null,
everBeenUnlocked: null,
forcePasswordReset: null,
hasPremiumPersonally: null,
kdfIterations: await this.storageService.get<number>(v1Keys.kdfIterations, options),
kdfType: await this.storageService.get<KdfType>(v1Keys.kdf, options),
keyHash: await this.storageService.get<string>(v1Keys.keyHash, options),
lastActive: await this.storageService.get<number>(v1Keys.lastActive, options),
lastSync: null,
ssoCodeVerifier: await this.storageService.get<string>(v1Keys.ssoCodeVerifier, options),
ssoOrganizationIdentifier: await this.storageService.get<string>(v1Keys.ssoIdentifier, options),
ssoState: null,
userId: userId,
usesKeyConnector: null,
},
settings: {
alwaysShowDock: await this.storageService.get<boolean>(v1Keys.alwaysShowDock, options),
autoConfirmFingerPrints: await this.storageService.get<boolean>(v1Keys.autoConfirmFingerprints, options),
autoFillOnPageLoadDefault: await this.storageService.get<boolean>(v1Keys.autoFillOnPageLoadDefault, options),
biometricLocked: null,
biometricUnlock: await this.storageService.get<boolean>(v1Keys.biometricUnlock, options),
clearClipboard: await this.storageService.get<number>(v1Keys.clearClipboard, options),
defaultUriMatch: await this.storageService.get<any>(v1Keys.defaultUriMatch, options),
disableAddLoginNotification: await this.storageService.get<boolean>(v1Keys.disableAddLoginNotification, options),
disableAutoBiometricsPrompt: await this.storageService.get<boolean>(v1Keys.disableAutoBiometricsPrompt, options),
disableAutoTotpCopy: await this.storageService.get<boolean>(v1Keys.disableAutoTotpCopy, options),
disableBadgeCounter: await this.storageService.get<boolean>(v1Keys.disableBadgeCounter, options),
disableChangedPasswordNotification: await this.storageService.get<boolean>(v1Keys.disableChangedPasswordNotification, options),
disableContextMenuItem: await this.storageService.get<boolean>(v1Keys.disableContextMenuItem, options),
disableGa: await this.storageService.get<boolean>(v1Keys.disableGa, options),
dontShowCardsCurrentTab: await this.storageService.get<boolean>(v1Keys.dontShowCardsCurrentTab, options),
dontShowIdentitiesCurrentTab: await this.storageService.get<boolean>(v1Keys.dontShowIdentitiesCurrentTab, options),
enableAlwaysOnTop: await this.storageService.get<boolean>(v1Keys.enableAlwaysOnTop, options),
enableAutoFillOnPageLoad: await this.storageService.get<boolean>(v1Keys.enableAutoFillOnPageLoad, options),
enableBiometric: await this.storageService.get<boolean>(v1Keys.enableBiometric, options),
enableBrowserIntegration: await this.storageService.get<boolean>(v1Keys.enableBrowserIntegration, options),
enableBrowserIntegrationFingerprint: await this.storageService.get<boolean>(v1Keys.enableBrowserIntegrationFingerprint, options),
enableCloseToTray: await this.storageService.get<boolean>(v1Keys.enableCloseToTray, options),
enableFullWidth: await this.storageService.get<boolean>(v1Keys.enableFullWidth, options),
enableGravitars: await this.storageService.get<boolean>(v1Keys.enableGravatars, options),
enableMinimizeToTray: await this.storageService.get<boolean>(v1Keys.enableMinimizeToTray, options),
enableStartToTray: await this.storageService.get<boolean>(v1Keys.enableStartToTray, options),
enableTray: await this.storageService.get<boolean>(v1Keys.enableTray, options),
environmentUrls: await this.storageService.get<any>(v1Keys.environmentUrls, options),
equivalentDomains: await this.storageService.get<any>(v1Keys.equivalentDomains, options),
minimizeOnCopyToClipboard: await this.storageService.get<boolean>(v1Keys.minimizeOnCopyToClipboard, options),
neverDomains: await this.storageService.get<any>(v1Keys.neverDomains, options),
openAtLogin: await this.storageService.get<boolean>(v1Keys.openAtLogin, options),
passwordGenerationOptions: await this.storageService.get<any>(v1Keys.passwordGenerationOptions, options),
pinProtected: {
decrypted: null,
encrypted: await this.storageService.get<string>(v1Keys.pinProtected, options),
},
protectedPin: await this.storageService.get<string>(v1Keys.protectedPin, options),
settings: await this.storageService.get<any>(v1KeyPrefixes.settings + userId, options),
vaultTimeout: await this.storageService.get<number>(v1Keys.vaultTimeout, options),
vaultTimeoutAction: await this.storageService.get<string>(v1Keys.vaultTimeoutAction, options),
},
tokens: {
accessToken: await this.storageService.get<string>(v1Keys.accessToken, options),
decodedToken: null,
refreshToken: await this.storageService.get<string>(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);
}
}
}

View File

@ -6,13 +6,13 @@ import { FolderService } from '../abstractions/folder.service';
import { KeyConnectorService } from '../abstractions/keyConnector.service'; import { KeyConnectorService } from '../abstractions/keyConnector.service';
import { LogService } from '../abstractions/log.service'; import { LogService } from '../abstractions/log.service';
import { MessagingService } from '../abstractions/messaging.service'; import { MessagingService } from '../abstractions/messaging.service';
import { OrganizationService } from '../abstractions/organization.service';
import { PolicyService } from '../abstractions/policy.service'; import { PolicyService } from '../abstractions/policy.service';
import { ProviderService } from '../abstractions/provider.service';
import { SendService } from '../abstractions/send.service'; import { SendService } from '../abstractions/send.service';
import { SettingsService } from '../abstractions/settings.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 { 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 { CipherData } from '../models/data/cipherData';
import { CollectionData } from '../models/data/collectionData'; import { CollectionData } from '../models/data/collectionData';
@ -35,30 +35,24 @@ import { PolicyResponse } from '../models/response/policyResponse';
import { ProfileResponse } from '../models/response/profileResponse'; import { ProfileResponse } from '../models/response/profileResponse';
import { SendResponse } from '../models/response/sendResponse'; import { SendResponse } from '../models/response/sendResponse';
const Keys = {
lastSyncPrefix: 'lastSync_',
};
export class SyncService implements SyncServiceAbstraction { export class SyncService implements SyncServiceAbstraction {
syncInProgress: boolean = false; syncInProgress: boolean = false;
constructor(private userService: UserService, private apiService: ApiService, constructor(private apiService: ApiService, private settingsService: SettingsService,
private settingsService: SettingsService, private folderService: FolderService, private folderService: FolderService, private cipherService: CipherService,
private cipherService: CipherService, private cryptoService: CryptoService, private cryptoService: CryptoService, private collectionService: CollectionService,
private collectionService: CollectionService, private storageService: StorageService, private messagingService: MessagingService, private policyService: PolicyService,
private messagingService: MessagingService, private policyService: PolicyService,
private sendService: SendService, private logService: LogService, private sendService: SendService, private logService: LogService,
private tokenService: TokenService, private keyConnectorService: KeyConnectorService, private keyConnectorService: KeyConnectorService, private stateService: StateService,
private logoutCallback: (expired: boolean) => Promise<void>) { private organizationService: OrganizationService, private providerService: ProviderService,
} private logoutCallback: (expired: boolean) => Promise<void>) { }
async getLastSync(): Promise<Date> { async getLastSync(): Promise<Date> {
const userId = await this.userService.getUserId(); if (await this.stateService.getUserId() == null) {
if (userId == null) {
return null; return null;
} }
const lastSync = await this.storageService.get<any>(Keys.lastSyncPrefix + userId); const lastSync = await this.stateService.getLastSync();
if (lastSync) { if (lastSync) {
return new Date(lastSync); return new Date(lastSync);
} }
@ -66,18 +60,13 @@ export class SyncService implements SyncServiceAbstraction {
return null; return null;
} }
async setLastSync(date: Date): Promise<any> { async setLastSync(date: Date, userId?: string): Promise<any> {
const userId = await this.userService.getUserId(); await this.stateService.setLastSync(date.toJSON(), { userId: userId });
if (userId == null) {
return;
}
await this.storageService.save(Keys.lastSyncPrefix + userId, date.toJSON());
} }
async fullSync(forceSync: boolean, allowThrowOnError = false): Promise<boolean> { async fullSync(forceSync: boolean, allowThrowOnError = false): Promise<boolean> {
this.syncStarted(); this.syncStarted();
const isAuthenticated = await this.userService.isAuthenticated(); const isAuthenticated = await this.stateService.getIsAuthenticated();
if (!isAuthenticated) { if (!isAuthenticated) {
return this.syncCompleted(false); return this.syncCompleted(false);
} }
@ -97,7 +86,7 @@ export class SyncService implements SyncServiceAbstraction {
return this.syncCompleted(false); return this.syncCompleted(false);
} }
const userId = await this.userService.getUserId(); const userId = await this.stateService.getUserId();
try { try {
await this.apiService.refreshIdentityToken(); await this.apiService.refreshIdentityToken();
const response = await this.apiService.getSync(); const response = await this.apiService.getSync();
@ -107,7 +96,7 @@ export class SyncService implements SyncServiceAbstraction {
await this.syncCollections(response.collections); await this.syncCollections(response.collections);
await this.syncCiphers(userId, response.ciphers); await this.syncCiphers(userId, response.ciphers);
await this.syncSends(userId, response.sends); await this.syncSends(userId, response.sends);
await this.syncSettings(userId, response.domains); await this.syncSettings(response.domains);
await this.syncPolicies(response.policies); await this.syncPolicies(response.policies);
await this.setLastSync(now); await this.setLastSync(now);
@ -123,14 +112,14 @@ export class SyncService implements SyncServiceAbstraction {
async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise<boolean> { async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise<boolean> {
this.syncStarted(); this.syncStarted();
if (await this.userService.isAuthenticated()) { if (await this.stateService.getIsAuthenticated()) {
try { try {
const localFolder = await this.folderService.get(notification.id); const localFolder = await this.folderService.get(notification.id);
if ((!isEdit && localFolder == null) || if ((!isEdit && localFolder == null) ||
(isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate)) { (isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate)) {
const remoteFolder = await this.apiService.getFolder(notification.id); const remoteFolder = await this.apiService.getFolder(notification.id);
if (remoteFolder != null) { if (remoteFolder != null) {
const userId = await this.userService.getUserId(); const userId = await this.stateService.getUserId();
await this.folderService.upsert(new FolderData(remoteFolder, userId)); await this.folderService.upsert(new FolderData(remoteFolder, userId));
this.messagingService.send('syncedUpsertedFolder', { folderId: notification.id }); this.messagingService.send('syncedUpsertedFolder', { folderId: notification.id });
return this.syncCompleted(true); return this.syncCompleted(true);
@ -145,7 +134,7 @@ export class SyncService implements SyncServiceAbstraction {
async syncDeleteFolder(notification: SyncFolderNotification): Promise<boolean> { async syncDeleteFolder(notification: SyncFolderNotification): Promise<boolean> {
this.syncStarted(); this.syncStarted();
if (await this.userService.isAuthenticated()) { if (await this.stateService.getIsAuthenticated()) {
await this.folderService.delete(notification.id); await this.folderService.delete(notification.id);
this.messagingService.send('syncedDeletedFolder', { folderId: notification.id }); this.messagingService.send('syncedDeletedFolder', { folderId: notification.id });
this.syncCompleted(true); this.syncCompleted(true);
@ -156,7 +145,7 @@ export class SyncService implements SyncServiceAbstraction {
async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise<boolean> { async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise<boolean> {
this.syncStarted(); this.syncStarted();
if (await this.userService.isAuthenticated()) { if (await this.stateService.getIsAuthenticated()) {
try { try {
let shouldUpdate = true; let shouldUpdate = true;
const localCipher = await this.cipherService.get(notification.id); const localCipher = await this.cipherService.get(notification.id);
@ -195,7 +184,7 @@ export class SyncService implements SyncServiceAbstraction {
if (shouldUpdate) { if (shouldUpdate) {
const remoteCipher = await this.apiService.getCipher(notification.id); const remoteCipher = await this.apiService.getCipher(notification.id);
if (remoteCipher != null) { if (remoteCipher != null) {
const userId = await this.userService.getUserId(); const userId = await this.stateService.getUserId();
await this.cipherService.upsert(new CipherData(remoteCipher, userId)); await this.cipherService.upsert(new CipherData(remoteCipher, userId));
this.messagingService.send('syncedUpsertedCipher', { cipherId: notification.id }); this.messagingService.send('syncedUpsertedCipher', { cipherId: notification.id });
return this.syncCompleted(true); return this.syncCompleted(true);
@ -214,7 +203,7 @@ export class SyncService implements SyncServiceAbstraction {
async syncDeleteCipher(notification: SyncCipherNotification): Promise<boolean> { async syncDeleteCipher(notification: SyncCipherNotification): Promise<boolean> {
this.syncStarted(); this.syncStarted();
if (await this.userService.isAuthenticated()) { if (await this.stateService.getIsAuthenticated()) {
await this.cipherService.delete(notification.id); await this.cipherService.delete(notification.id);
this.messagingService.send('syncedDeletedCipher', { cipherId: notification.id }); this.messagingService.send('syncedDeletedCipher', { cipherId: notification.id });
return this.syncCompleted(true); return this.syncCompleted(true);
@ -224,14 +213,14 @@ export class SyncService implements SyncServiceAbstraction {
async syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise<boolean> { async syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise<boolean> {
this.syncStarted(); this.syncStarted();
if (await this.userService.isAuthenticated()) { if (await this.stateService.getIsAuthenticated()) {
try { try {
const localSend = await this.sendService.get(notification.id); const localSend = await this.sendService.get(notification.id);
if ((!isEdit && localSend == null) || if ((!isEdit && localSend == null) ||
(isEdit && localSend != null && localSend.revisionDate < notification.revisionDate)) { (isEdit && localSend != null && localSend.revisionDate < notification.revisionDate)) {
const remoteSend = await this.apiService.getSend(notification.id); const remoteSend = await this.apiService.getSend(notification.id);
if (remoteSend != null) { if (remoteSend != null) {
const userId = await this.userService.getUserId(); const userId = await this.stateService.getUserId();
await this.sendService.upsert(new SendData(remoteSend, userId)); await this.sendService.upsert(new SendData(remoteSend, userId));
this.messagingService.send('syncedUpsertedSend', { sendId: notification.id }); this.messagingService.send('syncedUpsertedSend', { sendId: notification.id });
return this.syncCompleted(true); return this.syncCompleted(true);
@ -246,7 +235,7 @@ export class SyncService implements SyncServiceAbstraction {
async syncDeleteSend(notification: SyncSendNotification): Promise<boolean> { async syncDeleteSend(notification: SyncSendNotification): Promise<boolean> {
this.syncStarted(); this.syncStarted();
if (await this.userService.isAuthenticated()) { if (await this.stateService.getIsAuthenticated()) {
await this.sendService.delete(notification.id); await this.sendService.delete(notification.id);
this.messagingService.send('syncedDeletedSend', { sendId: notification.id }); this.messagingService.send('syncedDeletedSend', { sendId: notification.id });
this.syncCompleted(true); this.syncCompleted(true);
@ -286,7 +275,7 @@ export class SyncService implements SyncServiceAbstraction {
} }
private async syncProfile(response: ProfileResponse) { private async syncProfile(response: ProfileResponse) {
const stamp = await this.userService.getSecurityStamp(); const stamp = await this.stateService.getSecurityStamp();
if (stamp != null && stamp !== response.securityStamp) { if (stamp != null && stamp !== response.securityStamp) {
if (this.logoutCallback != null) { if (this.logoutCallback != null) {
await this.logoutCallback(true); await this.logoutCallback(true);
@ -299,9 +288,9 @@ export class SyncService implements SyncServiceAbstraction {
await this.cryptoService.setEncPrivateKey(response.privateKey); await this.cryptoService.setEncPrivateKey(response.privateKey);
await this.cryptoService.setProviderKeys(response.providers); await this.cryptoService.setProviderKeys(response.providers);
await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations);
await this.userService.setSecurityStamp(response.securityStamp); await this.stateService.setSecurityStamp(response.securityStamp);
await this.userService.setEmailVerified(response.emailVerified); await this.stateService.setEmailVerified(response.emailVerified);
await this.userService.setForcePasswordReset(response.forcePasswordReset); await this.stateService.setForcePasswordReset(response.forcePasswordReset);
await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector); await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector);
const organizations: { [id: string]: OrganizationData; } = {}; const organizations: { [id: string]: OrganizationData; } = {};
@ -322,8 +311,8 @@ export class SyncService implements SyncServiceAbstraction {
}); });
await Promise.all([ await Promise.all([
this.userService.replaceOrganizations(organizations), this.organizationService.save(organizations),
this.userService.replaceProviders(providers), this.providerService.save(providers),
]); ]);
if (await this.keyConnectorService.userNeedsMigration()) { if (await this.keyConnectorService.userNeedsMigration()) {
@ -365,7 +354,7 @@ export class SyncService implements SyncServiceAbstraction {
return await this.sendService.replace(sends); return await this.sendService.replace(sends);
} }
private async syncSettings(userId: string, response: DomainsResponse) { private async syncSettings(response: DomainsResponse) {
let eqDomains: string[][] = []; let eqDomains: string[][] = [];
if (response != null && response.equivalentDomains != null) { if (response != null && response.equivalentDomains != null) {
eqDomains = eqDomains.concat(response.equivalentDomains); eqDomains = eqDomains.concat(response.equivalentDomains);

View File

@ -1,10 +1,7 @@
import { MessagingService } from '../abstractions/messaging.service'; import { MessagingService } from '../abstractions/messaging.service';
import { PlatformUtilsService } from '../abstractions/platformUtils.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 { SystemService as SystemServiceAbstraction } from '../abstractions/system.service';
import { VaultTimeoutService } from '../abstractions/vaultTimeout.service';
import { ConstantsService } from './constants.service';
import { Utils } from '../misc/utils'; import { Utils } from '../misc/utils';
@ -13,28 +10,27 @@ export class SystemService implements SystemServiceAbstraction {
private clearClipboardTimeout: any = null; private clearClipboardTimeout: any = null;
private clearClipboardTimeoutFunction: () => Promise<any> = null; private clearClipboardTimeoutFunction: () => Promise<any> = null;
constructor(private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService, constructor(private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService, private reloadCallback: () => Promise<void> = null, private stateService: StateService) {
private reloadCallback: () => Promise<void> = null) {
} }
startProcessReload(): void { async startProcessReload(): Promise<void> {
if (this.vaultTimeoutService.pinProtectedKey != null || if (await this.stateService.getDecryptedPinProtected() != null ||
this.vaultTimeoutService.biometricLocked || await this.stateService.getBiometricLocked() ||
this.reloadInterval != null) { this.reloadInterval != null) {
return; return;
} }
this.cancelProcessReload(); this.cancelProcessReload();
this.reloadInterval = setInterval(async () => { this.reloadInterval = setInterval(async () => {
let doRefresh = false; let doRefresh = false;
const lastActive = await this.storageService.get<number>(ConstantsService.lastActiveKey); const lastActive = await this.stateService.getLastActive();
if (lastActive != null) { if (lastActive != null) {
const diffSeconds = (new Date()).getTime() - lastActive; const diffSeconds = (new Date()).getTime() - lastActive;
// Don't refresh if they are still active in the window // Don't refresh if they are still active in the window
doRefresh = diffSeconds >= 5000; doRefresh = diffSeconds >= 5000;
} }
const biometricLockedFingerprintValidated = const biometricLockedFingerprintValidated =
await this.storageService.get<boolean>(ConstantsService.biometricFingerprintValidated) && this.vaultTimeoutService.biometricLocked; await this.stateService.getBiometricFingerprintValidated() && await this.stateService.getBiometricLocked();
if (doRefresh && !biometricLockedFingerprintValidated) { if (doRefresh && !biometricLockedFingerprintValidated) {
clearInterval(this.reloadInterval); clearInterval(this.reloadInterval);
this.reloadInterval = null; 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<void> {
if (this.clearClipboardTimeout != null) { if (this.clearClipboardTimeout != null) {
clearTimeout(this.clearClipboardTimeout); clearTimeout(this.clearClipboardTimeout);
this.clearClipboardTimeout = null; this.clearClipboardTimeout = null;
@ -61,7 +57,7 @@ export class SystemService implements SystemServiceAbstraction {
if (Utils.isNullOrWhitespace(clipboardValue)) { if (Utils.isNullOrWhitespace(clipboardValue)) {
return; return;
} }
this.storageService.get<number>(ConstantsService.clearClipboardKey).then(clearSeconds => { await this.stateService.getClearClipboard().then(clearSeconds => {
if (clearSeconds == null) { if (clearSeconds == null) {
return; return;
} }

View File

@ -1,26 +1,10 @@
import { ConstantsService } from './constants.service'; import { StateService } from '../abstractions/state.service';
import { StorageService } from '../abstractions/storage.service';
import { TokenService as TokenServiceAbstraction } from '../abstractions/token.service'; import { TokenService as TokenServiceAbstraction } from '../abstractions/token.service';
import { Utils } from '../misc/utils'; import { Utils } from '../misc/utils';
const Keys = {
accessToken: 'accessToken',
refreshToken: 'refreshToken',
twoFactorTokenPrefix: 'twoFactorToken_',
clientId: 'apikey_clientId',
clientSecret: 'apikey_clientSecret',
};
export class TokenService implements TokenServiceAbstraction { export class TokenService implements TokenServiceAbstraction {
token: string; constructor(private stateService: StateService) {
decodedToken: any;
refreshToken: string;
clientId: string;
clientSecret: string;
constructor(private storageService: StorageService) {
} }
async setTokens(accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]): Promise<any> { async setTokens(accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]): Promise<any> {
@ -33,60 +17,44 @@ export class TokenService implements TokenServiceAbstraction {
} }
async setClientId(clientId: string): Promise<any> { async setClientId(clientId: string): Promise<any> {
this.clientId = clientId; if (await this.skipTokenStorage() || clientId == null) {
return this.storeTokenValue(Keys.clientId, clientId); return;
}
return await this.stateService.setApiKeyClientId(clientId);
} }
async getClientId(): Promise<string> { async getClientId(): Promise<string> {
if (this.clientId != null) { return await this.stateService.getApiKeyClientId();
return this.clientId;
}
this.clientId = await this.storageService.get<string>(Keys.clientId);
return this.clientId;
} }
async setClientSecret(clientSecret: string): Promise<any> { async setClientSecret(clientSecret: string): Promise<any> {
this.clientSecret = clientSecret; if (await this.skipTokenStorage() || clientSecret == null) {
return this.storeTokenValue(Keys.clientSecret, clientSecret); return;
}
return await this.stateService.setApiKeyClientSecret(clientSecret);
} }
async getClientSecret(): Promise<string> { async getClientSecret(): Promise<string> {
if (this.clientSecret != null) { return await this.stateService.getApiKeyClientSecret();
return this.clientSecret;
}
this.clientSecret = await this.storageService.get<string>(Keys.clientSecret);
return this.clientSecret;
} }
async setToken(token: string): Promise<any> { async setToken(token: string): Promise<void> {
this.token = token; await this.stateService.setAccessToken(token);
this.decodedToken = null;
return this.storeTokenValue(Keys.accessToken, token);
} }
async getToken(): Promise<string> { async getToken(): Promise<string> {
if (this.token != null) { return await this.stateService.getAccessToken();
return this.token;
}
this.token = await this.storageService.get<string>(Keys.accessToken);
return this.token;
} }
async setRefreshToken(refreshToken: string): Promise<any> { async setRefreshToken(refreshToken: string): Promise<any> {
this.refreshToken = refreshToken; if (await this.skipTokenStorage()) {
return this.storeTokenValue(Keys.refreshToken, refreshToken); return;
}
return await this.stateService.setRefreshToken(refreshToken);
} }
async getRefreshToken(): Promise<string> { async getRefreshToken(): Promise<string> {
if (this.refreshToken != null) { return await this.stateService.getRefreshToken();
return this.refreshToken;
}
this.refreshToken = await this.storageService.get<string>(Keys.refreshToken);
return this.refreshToken;
} }
async toggleTokens(): Promise<any> { async toggleTokens(): Promise<any> {
@ -94,16 +62,12 @@ export class TokenService implements TokenServiceAbstraction {
const refreshToken = await this.getRefreshToken(); const refreshToken = await this.getRefreshToken();
const clientId = await this.getClientId(); const clientId = await this.getClientId();
const clientSecret = await this.getClientSecret(); const clientSecret = await this.getClientSecret();
const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); const timeout = await this.stateService.getVaultTimeout();
const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); const action = await this.stateService.getVaultTimeoutAction();
if ((timeout != null || timeout === 0) && action === 'logOut') { if ((timeout != null || timeout === 0) && action === 'logOut') {
// if we have a vault timeout and the action is log out, reset tokens // if we have a vault timeout and the action is log out, reset tokens
await this.clearToken(); await this.clearToken();
this.token = token;
this.refreshToken = refreshToken;
this.clientId = clientId;
this.clientSecret = clientSecret;
return;
} }
await this.setToken(token); await this.setToken(token);
@ -112,44 +76,41 @@ export class TokenService implements TokenServiceAbstraction {
await this.setClientSecret(clientSecret); await this.setClientSecret(clientSecret);
} }
setTwoFactorToken(token: string, email: string): Promise<any> { async setTwoFactorToken(token: string): Promise<any> {
return this.storageService.save(Keys.twoFactorTokenPrefix + email, token); return await this.stateService.setTwoFactorToken(token);
} }
getTwoFactorToken(email: string): Promise<string> { async getTwoFactorToken(): Promise<string> {
return this.storageService.get<string>(Keys.twoFactorTokenPrefix + email); return await this.stateService.getTwoFactorToken();
} }
clearTwoFactorToken(email: string): Promise<any> { async clearTwoFactorToken(): Promise<any> {
return this.storageService.remove(Keys.twoFactorTokenPrefix + email); return await this.stateService.setTwoFactorToken(null);
} }
async clearToken(): Promise<any> { async clearToken(userId?: string): Promise<any> {
this.token = null; await this.stateService.setAccessToken(null, { userId: userId });
this.decodedToken = null; await this.stateService.setRefreshToken(null, { userId: userId });
this.refreshToken = null; await this.stateService.setApiKeyClientId(null, { userId: userId });
this.clientId = null; await this.stateService.setApiKeyClientSecret(null, { userId: userId });
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);
} }
// jwthelper methods // jwthelper methods
// ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js // ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js
decodeToken(): any { async decodeToken(token?: string): Promise<any> {
if (this.decodedToken) { const storedToken = await this.stateService.getDecodedToken();
return this.decodedToken; 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.'); throw new Error('Token not found.');
} }
const parts = this.token.split('.'); const parts = token.split('.');
if (parts.length !== 3) { if (parts.length !== 3) {
throw new Error('JWT must have 3 parts'); throw new Error('JWT must have 3 parts');
} }
@ -159,12 +120,12 @@ export class TokenService implements TokenServiceAbstraction {
throw new Error('Cannot decode the token'); throw new Error('Cannot decode the token');
} }
this.decodedToken = JSON.parse(decoded); const decodedToken = JSON.parse(decoded);
return this.decodedToken; return decodedToken;
} }
getTokenExpirationDate(): Date { async getTokenExpirationDate(): Promise<Date> {
const decoded = this.decodeToken(); const decoded = await this.decodeToken();
if (typeof decoded.exp === 'undefined') { if (typeof decoded.exp === 'undefined') {
return null; return null;
} }
@ -174,8 +135,8 @@ export class TokenService implements TokenServiceAbstraction {
return d; return d;
} }
tokenSecondsRemaining(offsetSeconds: number = 0): number { async tokenSecondsRemaining(offsetSeconds: number = 0): Promise<number> {
const d = this.getTokenExpirationDate(); const d = await this.getTokenExpirationDate();
if (d == null) { if (d == null) {
return 0; return 0;
} }
@ -184,13 +145,13 @@ export class TokenService implements TokenServiceAbstraction {
return Math.round(msRemaining / 1000); return Math.round(msRemaining / 1000);
} }
tokenNeedsRefresh(minutes: number = 5): boolean { async tokenNeedsRefresh(minutes: number = 5): Promise<boolean> {
const sRemaining = this.tokenSecondsRemaining(); const sRemaining = await this.tokenSecondsRemaining();
return sRemaining < (60 * minutes); return sRemaining < (60 * minutes);
} }
getUserId(): string { async getUserId(): Promise<string> {
const decoded = this.decodeToken(); const decoded = await this.decodeToken();
if (typeof decoded.sub === 'undefined') { if (typeof decoded.sub === 'undefined') {
throw new Error('No user id found'); throw new Error('No user id found');
} }
@ -198,8 +159,8 @@ export class TokenService implements TokenServiceAbstraction {
return decoded.sub as string; return decoded.sub as string;
} }
getEmail(): string { async getEmail(): Promise<string> {
const decoded = this.decodeToken(); const decoded = await this.decodeToken();
if (typeof decoded.email === 'undefined') { if (typeof decoded.email === 'undefined') {
throw new Error('No email found'); throw new Error('No email found');
} }
@ -207,8 +168,8 @@ export class TokenService implements TokenServiceAbstraction {
return decoded.email as string; return decoded.email as string;
} }
getEmailVerified(): boolean { async getEmailVerified(): Promise<boolean> {
const decoded = this.decodeToken(); const decoded = await this.decodeToken();
if (typeof decoded.email_verified === 'undefined') { if (typeof decoded.email_verified === 'undefined') {
throw new Error('No email verification found'); throw new Error('No email verification found');
} }
@ -216,8 +177,8 @@ export class TokenService implements TokenServiceAbstraction {
return decoded.email_verified as boolean; return decoded.email_verified as boolean;
} }
getName(): string { async getName(): Promise<string> {
const decoded = this.decodeToken(); const decoded = await this.decodeToken();
if (typeof decoded.name === 'undefined') { if (typeof decoded.name === 'undefined') {
return null; return null;
} }
@ -225,8 +186,8 @@ export class TokenService implements TokenServiceAbstraction {
return decoded.name as string; return decoded.name as string;
} }
getPremium(): boolean { async getPremium(): Promise<boolean> {
const decoded = this.decodeToken(); const decoded = await this.decodeToken();
if (typeof decoded.premium === 'undefined') { if (typeof decoded.premium === 'undefined') {
return false; return false;
} }
@ -234,8 +195,8 @@ export class TokenService implements TokenServiceAbstraction {
return decoded.premium as boolean; return decoded.premium as boolean;
} }
getIssuer(): string { async getIssuer(): Promise<string> {
const decoded = this.decodeToken(); const decoded = await this.decodeToken();
if (typeof decoded.iss === 'undefined') { if (typeof decoded.iss === 'undefined') {
throw new Error('No issuer found'); throw new Error('No issuer found');
} }
@ -243,8 +204,8 @@ export class TokenService implements TokenServiceAbstraction {
return decoded.iss as string; return decoded.iss as string;
} }
getIsExternal(): boolean { async getIsExternal(): Promise<boolean> {
const decoded = this.decodeToken(); const decoded = await this.decodeToken();
if (!Array.isArray(decoded.amr)) { if (!Array.isArray(decoded.amr)) {
throw new Error('No amr found'); throw new Error('No amr found');
} }
@ -252,18 +213,9 @@ export class TokenService implements TokenServiceAbstraction {
return decoded.amr.includes('external'); 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<boolean> { private async skipTokenStorage(): Promise<boolean> {
const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey); const timeout = await this.stateService.getVaultTimeout();
const action = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey); const action = await this.stateService.getVaultTimeoutAction();
return timeout != null && action === 'logOut'; return timeout != null && action === 'logOut';
} }
} }

View File

@ -1,8 +1,6 @@
import { ConstantsService } from './constants.service';
import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; import { CryptoFunctionService } from '../abstractions/cryptoFunction.service';
import { LogService } from '../abstractions/log.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 { TotpService as TotpServiceAbstraction } from '../abstractions/totp.service';
import { Utils } from '../misc/utils'; import { Utils } from '../misc/utils';
@ -11,8 +9,8 @@ const B32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
const SteamChars = '23456789BCDFGHJKMNPQRTVWXY'; const SteamChars = '23456789BCDFGHJKMNPQRTVWXY';
export class TotpService implements TotpServiceAbstraction { export class TotpService implements TotpServiceAbstraction {
constructor(private storageService: StorageService, private cryptoFunctionService: CryptoFunctionService, constructor(private cryptoFunctionService: CryptoFunctionService, private logService: LogService,
private logService: LogService) { } private stateService: StateService) { }
async getCode(key: string): Promise<string> { async getCode(key: string): Promise<string> {
if (key == null) { if (key == null) {
@ -114,7 +112,7 @@ export class TotpService implements TotpServiceAbstraction {
} }
async isAutoCopyEnabled(): Promise<boolean> { async isAutoCopyEnabled(): Promise<boolean> {
return !(await this.storageService.get<boolean>(ConstantsService.disableAutoTotpCopyKey)); return !(await this.stateService.getDisableAutoTotpCopy());
} }
// Helpers // Helpers

View File

@ -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<any> {
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<any> {
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<string> {
if (this.userId == null) {
this.userId = await this.storageService.get<string>(Keys.userId);
}
return this.userId;
}
async getEmail(): Promise<string> {
if (this.email == null) {
this.email = await this.storageService.get<string>(Keys.userEmail);
}
return this.email;
}
async getSecurityStamp(): Promise<string> {
if (this.stamp == null) {
this.stamp = await this.storageService.get<string>(Keys.stamp);
}
return this.stamp;
}
async getKdf(): Promise<KdfType> {
if (this.kdf == null) {
this.kdf = await this.storageService.get<KdfType>(Keys.kdf);
}
return this.kdf;
}
async getKdfIterations(): Promise<number> {
if (this.kdfIterations == null) {
this.kdfIterations = await this.storageService.get<number>(Keys.kdfIterations);
}
return this.kdfIterations;
}
async getEmailVerified(): Promise<boolean> {
if (this.emailVerified == null) {
this.emailVerified = await this.storageService.get<boolean>(Keys.emailVerified);
}
return this.emailVerified;
}
async getForcePasswordReset(): Promise<boolean> {
if (this.forcePasswordReset == null) {
this.forcePasswordReset = await this.storageService.get<boolean>(Keys.forcePasswordReset);
}
return this.forcePasswordReset;
}
async clear(): Promise<any> {
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<boolean> {
const token = await this.tokenService.getToken();
if (token == null) {
return false;
}
const userId = await this.getUserId();
return userId != null;
}
async canAccessPremium(): Promise<boolean> {
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<boolean> {
const orgs = await this.getAllOrganizations();
return orgs.some(o => o.familySponsorshipAvailable || o.familySponsorshipFriendlyName !== null);
}
async getOrganization(id: string): Promise<Organization> {
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<Organization> {
const organizations = await this.getAllOrganizations();
if (organizations == null || organizations.length === 0) {
return null;
}
return organizations.find(o => o.identifier === identifier);
}
async getAllOrganizations(): Promise<Organization[]> {
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<any> {
const userId = await this.getUserId();
await this.storageService.save(Keys.organizationsPrefix + userId, organizations);
}
async clearOrganizations(userId: string): Promise<any> {
await this.storageService.remove(Keys.organizationsPrefix + userId);
}
async getProvider(id: string): Promise<Provider> {
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<Provider[]> {
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<any> {
const userId = await this.getUserId();
await this.storageService.save(Keys.providersPrefix + userId, providers);
}
async clearProviders(userId: string): Promise<any> {
await this.storageService.remove(Keys.providersPrefix + userId);
}
}

View File

@ -1,5 +1,3 @@
import { ConstantsService } from './constants.service';
import { CipherService } from '../abstractions/cipher.service'; import { CipherService } from '../abstractions/cipher.service';
import { CollectionService } from '../abstractions/collection.service'; import { CollectionService } from '../abstractions/collection.service';
import { CryptoService } from '../abstractions/crypto.service'; import { CryptoService } from '../abstractions/crypto.service';
@ -9,29 +7,31 @@ import { MessagingService } from '../abstractions/messaging.service';
import { PlatformUtilsService } from '../abstractions/platformUtils.service'; import { PlatformUtilsService } from '../abstractions/platformUtils.service';
import { PolicyService } from '../abstractions/policy.service'; import { PolicyService } from '../abstractions/policy.service';
import { SearchService } from '../abstractions/search.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 { TokenService } from '../abstractions/token.service';
import { UserService } from '../abstractions/user.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from '../abstractions/vaultTimeout.service'; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from '../abstractions/vaultTimeout.service';
import { KeySuffixOptions } from '../enums/keySuffixOptions';
import { PolicyType } from '../enums/policyType'; import { PolicyType } from '../enums/policyType';
import { EncString } from '../models/domain/encString';
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
pinProtectedKey: EncString = null;
biometricLocked: boolean = true;
everBeenUnlocked: boolean = false;
private inited = false; private inited = false;
constructor(private cipherService: CipherService, private folderService: FolderService, constructor(
private collectionService: CollectionService, private cryptoService: CryptoService, private cipherService: CipherService,
protected platformUtilsService: PlatformUtilsService, private storageService: StorageService, private folderService: FolderService,
private messagingService: MessagingService, private searchService: SearchService, private collectionService: CollectionService,
private userService: UserService, private tokenService: TokenService, private policyService: PolicyService, private cryptoService: CryptoService,
protected platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService,
private searchService: SearchService,
private tokenService: TokenService,
private policyService: PolicyService,
private keyConnectorService: KeyConnectorService, private keyConnectorService: KeyConnectorService,
private lockedCallback: () => Promise<void> = null, private loggedOutCallback: () => Promise<void> = null) { private stateService: StateService,
} private lockedCallback: () => Promise<void> = null,
private loggedOutCallback: (userId?: string) => Promise<void> = null
) {}
init(checkOnInterval: boolean) { init(checkOnInterval: boolean) {
if (this.inited) { 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. // Keys aren't stored for a device that is locked or logged out.
async isLocked(): Promise<boolean> { async isLocked(userId?: string): Promise<boolean> {
// Handle never lock startup situation const neverLock = await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId) &&
if (await this.cryptoService.hasKeyStored('auto') && !this.everBeenUnlocked) { !(await this.stateService.getEverBeenUnlocked({ userId: userId }));
await this.cryptoService.getKey('auto'); 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<void> { async checkVaultTimeout(): Promise<void> {
if (await this.platformUtilsService.isViewOpen()) { if (await this.platformUtilsService.isViewOpen()) {
// Do not lock
return; return;
} }
// "is logged out check" - similar to isLocked, below for (const userId in this.stateService.accounts.getValue()) {
const authed = await this.userService.isAuthenticated(); if (userId != null && await this.shouldLock(userId)) {
if (!authed) { await this.executeTimeoutAction(userId);
return; }
}
if (await this.isLocked()) {
return;
}
const vaultTimeout = await this.getVaultTimeout();
if (vaultTimeout == null || vaultTimeout < 0) {
return;
}
const lastActive = await this.storageService.get<number>(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<string>(ConstantsService.vaultTimeoutActionKey);
timeoutAction === 'logOut' ? await this.logOut() : await this.lock(true);
} }
} }
async lock(allowSoftLock = false): Promise<void> { async lock(allowSoftLock = false, userId?: string): Promise<void> {
const authed = await this.userService.isAuthenticated(); const authed = await this.stateService.getIsAuthenticated({ userId: userId });
if (!authed) { if (!authed) {
return; return;
} }
if (await this.keyConnectorService.getUsesKeyConnector()) { if (await this.keyConnectorService.getUsesKeyConnector()) {
const pinSet = await this.isPinLockSet(); 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()) { if (!pinLock && !await this.isBiometricLockSet()) {
await this.logOut(); await this.logOut();
} }
} }
this.biometricLocked = true; if (userId == null || userId === await this.stateService.getUserId()) {
this.everBeenUnlocked = true; this.searchService.clearIndex();
await this.cryptoService.clearKey(false); }
await this.cryptoService.clearOrgKeys(true);
await this.cryptoService.clearKeyPair(true); await this.stateService.setEverBeenUnlocked(true, { userId: userId });
await this.cryptoService.clearEncKey(true); 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) { if (this.lockedCallback != null) {
await this.lockedCallback(); await this.lockedCallback();
} }
} }
async logOut(): Promise<void> { async logOut(userId?: string): Promise<void> {
if (this.loggedOutCallback != null) { if (this.loggedOutCallback != null) {
await this.loggedOutCallback(); await this.loggedOutCallback(userId);
} }
} }
async setVaultTimeoutOptions(timeout: number, action: string): Promise<void> { async setVaultTimeoutOptions(timeout: number, action: string): Promise<void> {
await this.storageService.save(ConstantsService.vaultTimeoutKey, timeout); await this.stateService.setVaultTimeout(timeout);
await this.storageService.save(ConstantsService.vaultTimeoutActionKey, action); await this.stateService.setVaultTimeoutAction(action);
await this.cryptoService.toggleKey(); await this.cryptoService.toggleKey();
await this.tokenService.toggleTokens(); await this.tokenService.toggleTokens();
} }
async isPinLockSet(): Promise<[boolean, boolean]> { async isPinLockSet(): Promise<[boolean, boolean]> {
const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin); const protectedPin = await this.stateService.getProtectedPin();
const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey); const pinProtectedKey = await this.stateService.getEncryptedPinProtected();
return [protectedPin != null, pinProtectedKey != null]; return [protectedPin != null, pinProtectedKey != null];
} }
async isBiometricLockSet(): Promise<boolean> { async isBiometricLockSet(): Promise<boolean> {
return await this.storageService.get<boolean>(ConstantsService.biometricUnlockKey); return await this.stateService.getBiometricUnlock();
} }
async getVaultTimeout(): Promise<number> { async getVaultTimeout(userId?: string): Promise<number> {
const vaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey); const vaultTimeout = await this.stateService.getVaultTimeout( { userId: userId } );
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) { if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId)) {
const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout); const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout, userId);
// Remove negative values, and ensure it's smaller than maximum allowed value according to policy // Remove negative values, and ensure it's smaller than maximum allowed value according to policy
let timeout = Math.min(vaultTimeout, policy[0].data.minutes); 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. // We really shouldn't need to set the value here, but multiple services relies on this value being correct.
if (vaultTimeout !== timeout) { if (vaultTimeout !== timeout) {
await this.storageService.save(ConstantsService.vaultTimeoutKey, timeout); await this.stateService.setVaultTimeout(timeout, { userId: userId });
} }
return timeout; return timeout;
@ -172,9 +158,42 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
return vaultTimeout; return vaultTimeout;
} }
clear(): Promise<any> { async clear(userId?: string): Promise<void> {
this.everBeenUnlocked = false; await this.stateService.setEverBeenUnlocked(false, { userId: userId });
this.pinProtectedKey = null; await this.stateService.setDecryptedPinProtected(null, { userId: userId });
return this.storageService.remove(ConstantsService.protectedPin); await this.stateService.setProtectedPin(null, { userId: userId });
}
private async isLoggedOut(userId?: string): Promise<boolean> {
return !(await this.stateService.getIsAuthenticated({ userId: userId }));
}
private async shouldLock(userId: string): Promise<boolean> {
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<void> {
const timeoutAction = await this.stateService.getVaultTimeoutAction({ userId: userId });
timeoutAction === 'logOut' ? await this.logOut() : await this.lock(true, userId);
} }
} }

View File

@ -1,21 +1,18 @@
import { ipcMain, systemPreferences } from 'electron'; 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 { 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 { export default class BiometricDarwinMain implements BiometricMain {
isError: boolean = false; isError: boolean = false;
constructor(private storageService: StorageService, private i18nservice: I18nService) {} constructor(private i18nservice: I18nService, private stateService: StateService) {}
async init() { async init() {
this.storageService.save(ElectronConstants.enableBiometric, await this.supportsBiometric()); await this.stateService.setEnableBiometric(await this.supportsBiometric());
this.storageService.save(ConstantsService.biometricText, 'unlockWithTouchId'); await this.stateService.setBiometricText('unlockWithTouchId');
this.storageService.save(ElectronConstants.noAutoPromptBiometricsText, 'noAutoPromptTouchId'); await this.stateService.setNoAutoPromptBiometricsText('noAutoPromptTouchId');
ipcMain.on('biometric', async (event: any, message: any) => { ipcMain.on('biometric', async (event: any, message: any) => {
event.returnValue = await this.authenticateBiometric(); event.returnValue = await this.authenticateBiometric();

View File

@ -1,22 +1,20 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import forceFocus from 'forcefocus'; import forceFocus from 'forcefocus';
import { ElectronConstants } from './electronConstants';
import { WindowMain } from './window.main'; import { WindowMain } from './window.main';
import { BiometricMain } from 'jslib-common/abstractions/biometric.main'; import { BiometricMain } from 'jslib-common/abstractions/biometric.main';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service'; 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 { ConstantsService } from 'jslib-common/services/constants.service';
export default class BiometricWindowsMain implements BiometricMain { export default class BiometricWindowsMain implements BiometricMain {
isError: boolean = false; isError: boolean = false;
private windowsSecurityCredentialsUiModule: any; private windowsSecurityCredentialsUiModule: any;
constructor(private storageService: StorageService, private i18nservice: I18nService, private windowMain: WindowMain, constructor(private i18nservice: I18nService, private windowMain: WindowMain,
private logService: LogService) { } private stateService: StateService, private logService: LogService) { }
async init() { async init() {
this.windowsSecurityCredentialsUiModule = this.getWindowsSecurityCredentialsUiModule(); 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 // store error state so we can let the user know on the settings page
this.isError = true; this.isError = true;
} }
this.storageService.save(ElectronConstants.enableBiometric, supportsBiometric); await this.stateService.setEnableBiometric(supportsBiometric);
this.storageService.save(ConstantsService.biometricText, 'unlockWithWindowsHello'); await this.stateService.setBiometricText('unlockWithWindowsHello');
this.storageService.save(ElectronConstants.noAutoPromptBiometricsText, 'noAutoPromptWindowsHello'); await this.stateService.setNoAutoPromptBiometricsText('noAutoPromptWindowsHello');
ipcMain.on('biometric', async (event: any, message: any) => { ipcMain.on('biometric', async (event: any, message: any) => {
event.returnValue = await this.authenticateBiometric(); event.returnValue = await this.authenticateBiometric();

View File

@ -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';
}

View File

@ -1,16 +1,19 @@
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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 { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { CryptoService, Keys } from 'jslib-common/services/crypto.service';
export class ElectronCryptoService extends CryptoService { export class ElectronCryptoService extends CryptoService {
constructor(storageService: StorageService, secureStorageService: StorageService, constructor(cryptoFunctionService: CryptoFunctionService, platformUtilService: PlatformUtilsService,
cryptoFunctionService: CryptoFunctionService, platformUtilService: PlatformUtilsService, logService: LogService, stateService: StateService) {
logService: LogService) { super(cryptoFunctionService, platformUtilService, logService, stateService);
super(storageService, secureStorageService, cryptoFunctionService, platformUtilService, logService);
} }
async hasKeyStored(keySuffix: KeySuffixOptions): Promise<boolean> { async hasKeyStored(keySuffix: KeySuffixOptions): Promise<boolean> {
@ -18,17 +21,17 @@ export class ElectronCryptoService extends CryptoService {
return super.hasKeyStored(keySuffix); return super.hasKeyStored(keySuffix);
} }
protected async storeKey(key: SymmetricCryptoKey) { protected async storeKey(key: SymmetricCryptoKey, userId?: string) {
if (await this.shouldStoreKey('auto')) { if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) {
await this.secureStorageService.save(Keys.key, key.keyB64, { keySuffix: 'auto' }); await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId });
} else { } else {
this.clearStoredKey('auto'); this.clearStoredKey(KeySuffixOptions.Auto);
} }
if (await this.shouldStoreKey('biometric')) { if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) {
await this.secureStorageService.save(Keys.key, key.keyB64, { keySuffix: 'biometric' }); await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId });
} else { } else {
this.clearStoredKey('biometric'); this.clearStoredKey(KeySuffixOptions.Biometric);
} }
} }
@ -43,24 +46,24 @@ export class ElectronCryptoService extends CryptoService {
*/ */
private async upgradeSecurelyStoredKey() { private async upgradeSecurelyStoredKey() {
// attempt key upgrade, but if we fail just delete it. Keys will be stored property upon unlock anyway. // attempt key upgrade, but if we fail just delete it. Keys will be stored property upon unlock anyway.
const key = await this.secureStorageService.get<string>(Keys.key); const key = await this.stateService.getCryptoMasterKeyB64();
if (key == null) { if (key == null) {
return; return;
} }
try { try {
if (await this.shouldStoreKey('auto')) { if (await this.shouldStoreKey(KeySuffixOptions.Auto)) {
await this.secureStorageService.save(Keys.key, key, { keySuffix: 'auto' }); await this.stateService.setCryptoMasterKeyAuto(key);
} }
if (await this.shouldStoreKey('biometric')) { if (await this.shouldStoreKey(KeySuffixOptions.Biometric)) {
await this.secureStorageService.save(Keys.key, key, { keySuffix: 'biometric' }); await this.stateService.setCryptoMasterKeyBiometric(key);
} }
} catch (e) { } catch (e) {
this.logService.error(`Encountered error while upgrading obsolete Bitwarden secure storage item:`); this.logService.error(`Encountered error while upgrading obsolete Bitwarden secure storage item:`);
this.logService.error(e); this.logService.error(e);
} }
await this.secureStorageService.remove(Keys.key); await this.stateService.setCryptoMasterKeyB64(null);
} }
} }

View File

@ -15,11 +15,7 @@ import { ThemeType } from 'jslib-common/enums/themeType';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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 { ConstantsService } from 'jslib-common/services/constants.service';
import { ElectronConstants } from '../electronConstants';
export class ElectronPlatformUtilsService implements PlatformUtilsService { export class ElectronPlatformUtilsService implements PlatformUtilsService {
identityClientId: string; identityClientId: string;
@ -27,7 +23,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
private deviceCache: DeviceType = null; private deviceCache: DeviceType = null;
constructor(protected i18nService: I18nService, private messagingService: MessagingService, constructor(protected i18nService: I18nService, private messagingService: MessagingService,
private isDesktopApp: boolean, private storageService: StorageService) { private isDesktopApp: boolean, private stateService: StateService) {
this.identityClientId = isDesktopApp ? 'desktop' : 'connector'; this.identityClientId = isDesktopApp ? 'desktop' : 'connector';
} }
@ -178,8 +174,8 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
return Promise.resolve(clipboard.readText(type)); return Promise.resolve(clipboard.readText(type));
} }
supportsBiometric(): Promise<boolean> { async supportsBiometric(): Promise<boolean> {
return this.storageService.get(ElectronConstants.enableBiometric); return await this.stateService.getEnableBiometric();
} }
authenticateBiometric(): Promise<boolean> { authenticateBiometric(): Promise<boolean> {
@ -200,7 +196,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
} }
async getEffectiveTheme() { async getEffectiveTheme() {
const theme = await this.storageService.get<ThemeType>(ConstantsService.themeKey); const theme = await this.stateService.getTheme();
if (theme == null || theme === ThemeType.System) { if (theme == null || theme === ThemeType.System) {
return this.getDefaultSystemTheme(); return this.getDefaultSystemTheme();
} else { } else {

View File

@ -1,9 +1,11 @@
import { ipcRenderer } from 'electron'; 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 { export class ElectronRendererSecureStorageService implements StorageService {
async get<T>(key: string, options?: StorageServiceOptions): Promise<T> { async get<T>(key: string, options?: StorageOptions): Promise<T> {
const val = ipcRenderer.sendSync('keytar', { const val = ipcRenderer.sendSync('keytar', {
action: 'getPassword', action: 'getPassword',
key: key, key: key,
@ -12,7 +14,7 @@ export class ElectronRendererSecureStorageService implements StorageService {
return Promise.resolve(val != null ? JSON.parse(val) as T : null); return Promise.resolve(val != null ? JSON.parse(val) as T : null);
} }
async has(key: string, options?: StorageServiceOptions): Promise<boolean> { async has(key: string, options?: StorageOptions): Promise<boolean> {
const val = ipcRenderer.sendSync('keytar', { const val = ipcRenderer.sendSync('keytar', {
action: 'hasPassword', action: 'hasPassword',
key: key, key: key,
@ -21,7 +23,7 @@ export class ElectronRendererSecureStorageService implements StorageService {
return Promise.resolve(!!val); return Promise.resolve(!!val);
} }
async save(key: string, obj: any, options?: StorageServiceOptions): Promise<any> { async save(key: string, obj: any, options?: StorageOptions): Promise<any> {
ipcRenderer.sendSync('keytar', { ipcRenderer.sendSync('keytar', {
action: 'setPassword', action: 'setPassword',
key: key, key: key,
@ -31,7 +33,7 @@ export class ElectronRendererSecureStorageService implements StorageService {
return Promise.resolve(); return Promise.resolve();
} }
async remove(key: string, options?: StorageServiceOptions): Promise<any> { async remove(key: string, options?: StorageOptions): Promise<any> {
ipcRenderer.sendSync('keytar', { ipcRenderer.sendSync('keytar', {
action: 'deletePassword', action: 'deletePassword',
key: key, key: key,

View File

@ -2,7 +2,6 @@ import {
app, app,
BrowserWindow, BrowserWindow,
Menu, Menu,
MenuItem,
MenuItemConstructorOptions, MenuItemConstructorOptions,
nativeImage, nativeImage,
Tray, Tray,
@ -10,9 +9,8 @@ import {
import * as path from 'path'; import * as path from 'path';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; 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'; import { WindowMain } from './window.main';
export class TrayMain { export class TrayMain {
@ -24,7 +22,7 @@ export class TrayMain {
private pressedIcon: Electron.NativeImage; private pressedIcon: Electron.NativeImage;
constructor(private windowMain: WindowMain, private i18nService: I18nService, constructor(private windowMain: WindowMain, private i18nService: I18nService,
private storageService: StorageService) { private stateService: StateService) {
if (process.platform === 'win32') { if (process.platform === 'win32') {
this.icon = path.join(__dirname, '/images/icon.ico'); this.icon = path.join(__dirname, '/images/icon.ico');
} else if (process.platform === 'darwin') { } else if (process.platform === 'darwin') {
@ -55,21 +53,21 @@ export class TrayMain {
} }
this.contextMenu = Menu.buildFromTemplate(menuItemOptions); this.contextMenu = Menu.buildFromTemplate(menuItemOptions);
if (await this.storageService.get<boolean>(ElectronConstants.enableTrayKey)) { if (await this.stateService.getEnableTray()) {
this.showTray(); this.showTray();
} }
} }
setupWindowListeners(win: BrowserWindow) { setupWindowListeners(win: BrowserWindow) {
win.on('minimize', async (e: Event) => { win.on('minimize', async (e: Event) => {
if (await this.storageService.get<boolean>(ElectronConstants.enableMinimizeToTrayKey)) { if (await this.stateService.getEnableMinimizeToTray()) {
e.preventDefault(); e.preventDefault();
this.hideToTray(); this.hideToTray();
} }
}); });
win.on('close', async (e: Event) => { win.on('close', async (e: Event) => {
if (await this.storageService.get<boolean>(ElectronConstants.enableCloseToTrayKey)) { if (await this.stateService.getEnableCloseToTray()) {
if (!this.windowMain.isQuitting) { if (!this.windowMain.isQuitting) {
e.preventDefault(); e.preventDefault();
this.hideToTray(); this.hideToTray();
@ -78,7 +76,7 @@ export class TrayMain {
}); });
win.on('show', async (e: Event) => { win.on('show', async (e: Event) => {
const enableTray = await this.storageService.get<boolean>(ElectronConstants.enableTrayKey); const enableTray = await this.stateService.getEnableTray();
if (!enableTray) { if (!enableTray) {
setTimeout(() => this.removeTray(false), 100); setTimeout(() => this.removeTray(false), 100);
} }
@ -103,7 +101,7 @@ export class TrayMain {
if (this.windowMain.win != null) { if (this.windowMain.win != null) {
this.windowMain.win.hide(); this.windowMain.win.hide();
} }
if (this.isDarwin() && !await this.storageService.get<boolean>(ElectronConstants.alwaysShowDock)) { if (this.isDarwin() && !await this.stateService.getAlwaysShowDock()) {
this.hideDock(); this.hideDock();
} }
} }
@ -167,7 +165,7 @@ export class TrayMain {
} }
if (this.windowMain.win.isVisible()) { if (this.windowMain.win.isVisible()) {
this.windowMain.win.hide(); this.windowMain.win.hide();
if (this.isDarwin() && !await this.storageService.get<boolean>(ElectronConstants.alwaysShowDock)) { if (this.isDarwin() && !await this.stateService.getAlwaysShowDock()) {
this.hideDock(); this.hideDock();
} }
} else { } else {

View File

@ -25,8 +25,12 @@ export function isAppImage() {
return process.platform === 'linux' && 'APPIMAGE' in process.env; return process.platform === 'linux' && 'APPIMAGE' in process.env;
} }
export function isMac() {
return process.platform === 'darwin';
}
export function isMacAppStore() { export function isMacAppStore() {
return process.platform === 'darwin' && process.mas && process.mas === true; return isMac() && process.mas && process.mas === true;
} }
export function isWindowsStore() { export function isWindowsStore() {

View File

@ -7,9 +7,8 @@ import * as path from 'path';
import * as url from 'url'; import * as url from 'url';
import { LogService } from 'jslib-common/abstractions/log.service'; 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 { import {
cleanUserAgent, cleanUserAgent,
isDev, isDev,
@ -17,11 +16,8 @@ import {
isSnapStore, isSnapStore,
} from './utils'; } from './utils';
const mainWindowSizeKey = 'mainWindowSize';
const WindowEventHandlingDelay = 100; const WindowEventHandlingDelay = 100;
const Keys = {
mainWindowSize: 'mainWindowSize',
};
export class WindowMain { export class WindowMain {
win: BrowserWindow; win: BrowserWindow;
isQuitting: boolean = false; isQuitting: boolean = false;
@ -30,7 +26,7 @@ export class WindowMain {
private windowStates: { [key: string]: any; } = {}; private windowStates: { [key: string]: any; } = {};
private enableAlwaysOnTop: boolean = false; 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 hideTitleBar = false, private defaultWidth = 950, private defaultHeight = 600,
private argvCallback: (argv: string[]) => void = null, private argvCallback: (argv: string[]) => void = null,
private createWindowCallback: (win: BrowserWindow) => void) { } private createWindowCallback: (win: BrowserWindow) => void) { }
@ -107,18 +103,18 @@ export class WindowMain {
} }
async createWindow(): Promise<void> { async createWindow(): Promise<void> {
this.windowStates[Keys.mainWindowSize] = await this.getWindowState(Keys.mainWindowSize, this.defaultWidth, this.windowStates[mainWindowSizeKey] = await this.getWindowState(mainWindowSizeKey, this.defaultWidth,
this.defaultHeight); this.defaultHeight);
this.enableAlwaysOnTop = await this.storageService.get<boolean>(ElectronConstants.enableAlwaysOnTopKey); this.enableAlwaysOnTop = await this.stateService.getEnableAlwaysOnTop();
// Create the browser window. // Create the browser window.
this.win = new BrowserWindow({ this.win = new BrowserWindow({
width: this.windowStates[Keys.mainWindowSize].width, width: this.windowStates[mainWindowSizeKey].width,
height: this.windowStates[Keys.mainWindowSize].height, height: this.windowStates[mainWindowSizeKey].height,
minWidth: 680, minWidth: 680,
minHeight: 500, minHeight: 500,
x: this.windowStates[Keys.mainWindowSize].x, x: this.windowStates[mainWindowSizeKey].x,
y: this.windowStates[Keys.mainWindowSize].y, y: this.windowStates[mainWindowSizeKey].y,
title: app.name, title: app.name,
icon: process.platform === 'linux' ? path.join(__dirname, '/images/icon.png') : undefined, icon: process.platform === 'linux' ? path.join(__dirname, '/images/icon.png') : undefined,
titleBarStyle: this.hideTitleBar && process.platform === 'darwin' ? 'hiddenInset' : 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(); this.win.maximize();
} }
@ -158,7 +154,7 @@ export class WindowMain {
// Emitted when the window is closed. // Emitted when the window is closed.
this.win.on('closed', async () => { 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 // Dereference the window object, usually you would store window
// in an array if your app supports multi windows, this is the time // 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 () => { this.win.on('close', async () => {
await this.updateWindowState(Keys.mainWindowSize, this.win); await this.updateWindowState(mainWindowSizeKey, this.win);
}); });
this.win.on('maximize', async () => { this.win.on('maximize', async () => {
await this.updateWindowState(Keys.mainWindowSize, this.win); await this.updateWindowState(mainWindowSizeKey, this.win);
}); });
this.win.on('unmaximize', async () => { this.win.on('unmaximize', async () => {
await this.updateWindowState(Keys.mainWindowSize, this.win); await this.updateWindowState(mainWindowSizeKey, this.win);
}); });
this.win.on('resize', () => { this.win.on('resize', () => {
this.windowStateChangeHandler(Keys.mainWindowSize, this.win); this.windowStateChangeHandler(mainWindowSizeKey, this.win);
}); });
this.win.on('move', () => { this.win.on('move', () => {
this.windowStateChangeHandler(Keys.mainWindowSize, this.win); this.windowStateChangeHandler(mainWindowSizeKey, this.win);
}); });
this.win.on('focus', () => { this.win.on('focus', () => {
this.win.webContents.send('messagingService', { this.win.webContents.send('messagingService', {
@ -200,7 +196,7 @@ export class WindowMain {
async toggleAlwaysOnTop() { async toggleAlwaysOnTop() {
this.enableAlwaysOnTop = !this.win.isAlwaysOnTop(); this.enableAlwaysOnTop = !this.win.isAlwaysOnTop();
this.win.setAlwaysOnTop(this.enableAlwaysOnTop); 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) { private windowStateChangeHandler(configKey: string, win: BrowserWindow) {
@ -219,7 +215,7 @@ export class WindowMain {
const bounds = win.getBounds(); const bounds = win.getBounds();
if (this.windowStates[configKey] == null) { if (this.windowStates[configKey] == null) {
this.windowStates[configKey] = await this.storageService.get<any>(configKey); this.windowStates[configKey] = (await this.stateService.getWindow()).get(configKey);
if (this.windowStates[configKey] == null) { if (this.windowStates[configKey] == null) {
this.windowStates[configKey] = {}; this.windowStates[configKey] = {};
} }
@ -235,14 +231,19 @@ export class WindowMain {
this.windowStates[configKey].height = bounds.height; this.windowStates[configKey].height = bounds.height;
} }
await this.storageService.save(configKey, this.windowStates[configKey]); const cachedWindow = await this.stateService.getWindow() ?? new Map<string, any>();
cachedWindow.set(configKey, this.windowStates[configKey]);
await this.stateService.setWindow(cachedWindow);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
} }
private async getWindowState(configKey: string, defaultWidth: number, defaultHeight: number) { private async getWindowState(configKey: string, defaultWidth: number, defaultHeight: number) {
let state = await this.storageService.get<any>(configKey); const windowState = await this.stateService.getWindow() ?? new Map<string, any>();
let state = windowState.has(configKey) ?
windowState.get(configKey) :
null;
const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized); const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized);
let displayBounds: Electron.Rectangle = null; let displayBounds: Electron.Rectangle = null;

View File

@ -1,15 +1,15 @@
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import { StateService } from 'jslib-common/abstractions/state.service';
import { Response } from './models/response'; import { Response } from './models/response';
import { ListResponse } from './models/response/listResponse'; import { ListResponse } from './models/response/listResponse';
import { MessageResponse } from './models/response/messageResponse'; import { MessageResponse } from './models/response/messageResponse';
import { StringResponse } from './models/response/stringResponse'; import { StringResponse } from './models/response/stringResponse';
import { UserService } from 'jslib-common/abstractions/user.service';
export abstract class BaseProgram { export abstract class BaseProgram {
constructor( constructor(
private userService: UserService, private stateService: StateService,
private writeLn: (s: string, finalLine: boolean, error: boolean) => void) { } private writeLn: (s: string, finalLine: boolean, error: boolean) => void) { }
protected processResponse(response: Response, exitImmediately = false, dataProcessor: () => string = null) { protected processResponse(response: Response, exitImmediately = false, dataProcessor: () => string = null) {
@ -92,15 +92,15 @@ export abstract class BaseProgram {
} }
protected async exitIfAuthed() { protected async exitIfAuthed() {
const authed = await this.userService.isAuthenticated(); const authed = await this.stateService.getIsAuthenticated();
if (authed) { 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); this.processResponse(Response.error('You are already logged in as ' + email + '.'), true);
} }
} }
protected async exitIfNotAuthed() { protected async exitIfNotAuthed() {
const authed = await this.userService.isAuthenticated(); const authed = await this.stateService.getIsAuthenticated();
if (!authed) { if (!authed) {
this.processResponse(Response.error('You are not logged in.'), true); this.processResponse(Response.error('You are not logged in.'), true);
} }

View File

@ -18,8 +18,8 @@ import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.serv
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.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 { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { Response } from '../models/response'; import { Response } from '../models/response';
@ -49,7 +49,7 @@ export class LoginCommand {
protected i18nService: I18nService, protected environmentService: EnvironmentService, protected i18nService: I18nService, protected environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService, protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService, protected platformUtilsService: PlatformUtilsService, 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 policyService: PolicyService, clientId: string, private syncService: SyncService,
protected keyConnectorService: KeyConnectorService) { protected keyConnectorService: KeyConnectorService) {
this.clientId = clientId; this.clientId = clientId;
@ -300,7 +300,7 @@ export class LoginCommand {
} }
if (this.email == null || this.email === 'undefined') { if (this.email == null || this.email === 'undefined') {
this.email = await this.userService.getEmail(); this.email = await this.stateService.getEmail();
} }
// Get New Master Password // Get New Master Password
@ -350,8 +350,8 @@ export class LoginCommand {
// Retrieve details for key generation // Retrieve details for key generation
const enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); const enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
const kdf = await this.userService.getKdf(); const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.userService.getKdfIterations(); const kdfIterations = await this.stateService.getKdfIterations();
if (enforcedPolicyOptions != null && if (enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword( !this.policyService.evaluateMasterPassword(

View File

@ -7,8 +7,8 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from 'jslib-common/abstractions/log.service';
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from 'jslib-common/abstractions/search.service';
import { SettingsService } from 'jslib-common/abstractions/settings.service'; import { SettingsService } from 'jslib-common/abstractions/settings.service';
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StateService } from 'jslib-common/abstractions/state.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from 'jslib-common/misc/utils';
import { Cipher } from 'jslib-common/models/domain/cipher'; import { Cipher } from 'jslib-common/models/domain/cipher';
import { EncArrayBuffer } from 'jslib-common/models/domain/encArrayBuffer'; import { EncArrayBuffer } from 'jslib-common/models/domain/encArrayBuffer';
@ -22,11 +22,10 @@ const ENCRYPTED_BYTES = new EncArrayBuffer(Utils.fromUtf8ToArray(ENCRYPTED_TEXT)
describe('Cipher Service', () => { describe('Cipher Service', () => {
let cryptoService: SubstituteOf<CryptoService>; let cryptoService: SubstituteOf<CryptoService>;
let userService: SubstituteOf<UserService>; let stateService: SubstituteOf<StateService>;
let settingsService: SubstituteOf<SettingsService>; let settingsService: SubstituteOf<SettingsService>;
let apiService: SubstituteOf<ApiService>; let apiService: SubstituteOf<ApiService>;
let fileUploadService: SubstituteOf<FileUploadService>; let fileUploadService: SubstituteOf<FileUploadService>;
let storageService: SubstituteOf<StorageService>;
let i18nService: SubstituteOf<I18nService>; let i18nService: SubstituteOf<I18nService>;
let searchService: SubstituteOf<SearchService>; let searchService: SubstituteOf<SearchService>;
let logService: SubstituteOf<LogService>; let logService: SubstituteOf<LogService>;
@ -35,11 +34,10 @@ describe('Cipher Service', () => {
beforeEach(() => { beforeEach(() => {
cryptoService = Substitute.for<CryptoService>(); cryptoService = Substitute.for<CryptoService>();
userService = Substitute.for<UserService>(); stateService = Substitute.for<StateService>();
settingsService = Substitute.for<SettingsService>(); settingsService = Substitute.for<SettingsService>();
apiService = Substitute.for<ApiService>(); apiService = Substitute.for<ApiService>();
fileUploadService = Substitute.for<FileUploadService>(); fileUploadService = Substitute.for<FileUploadService>();
storageService = Substitute.for<StorageService>();
i18nService = Substitute.for<I18nService>(); i18nService = Substitute.for<I18nService>();
searchService = Substitute.for<SearchService>(); searchService = Substitute.for<SearchService>();
logService = Substitute.for<LogService>(); logService = Substitute.for<LogService>();
@ -47,12 +45,11 @@ describe('Cipher Service', () => {
cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES); cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES);
cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT)); cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT));
cipherService = new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService, cipherService = new CipherService(cryptoService, settingsService, apiService, fileUploadService,
storageService, i18nService, () => searchService, logService); i18nService, () => searchService, logService, stateService);
}); });
it('attachments upload encrypted file contents', async () => { it('attachments upload encrypted file contents', async () => {
const key = new SymmetricCryptoKey(new Uint8Array(32).buffer);
const fileName = 'filename'; const fileName = 'filename';
const fileData = new Uint8Array(10).buffer; const fileData = new Uint8Array(10).buffer;
cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer)); cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer));