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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,8 +11,6 @@ import { CipherView } from 'jslib-common/models/view/cipherView';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { Utils } from 'jslib-common/misc/utils';
const IconMap: any = {
@ -37,7 +35,7 @@ export class IconComponent implements OnChanges {
private iconsUrl: string;
constructor(environmentService: EnvironmentService, protected stateService: StateService) {
constructor(environmentService: EnvironmentService, private stateService: StateService) {
this.iconsUrl = environmentService.getIconsUrl();
}
@ -46,7 +44,7 @@ export class IconComponent implements OnChanges {
// to avoid this we reset all state variables.
this.image = null;
this.fallbackImage = null;
this.imageEnabled = !(await this.stateService.get<boolean>(ConstantsService.disableFaviconKey));
this.imageEnabled = !(await this.stateService.getDisableFavicon());
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 { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { EncString } from 'jslib-common/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
@ -25,6 +21,7 @@ import { SecretVerificationRequest } from 'jslib-common/models/request/secretVer
import { Utils } from 'jslib-common/misc/utils';
import { HashPurpose } from 'jslib-common/enums/hashPurpose';
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
@Directive()
export class LockComponent implements OnInit {
@ -41,38 +38,22 @@ export class LockComponent implements OnInit {
hideInput: boolean;
protected successRoute: string = 'vault';
protected onSuccessfulSubmit: () => void;
protected onSuccessfulSubmit: () => Promise<void>;
private invalidPinAttempts = 0;
private pinSet: [boolean, boolean];
constructor(protected router: Router, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService,
protected userService: UserService, protected cryptoService: CryptoService,
protected storageService: StorageService, protected vaultTimeoutService: VaultTimeoutService,
protected cryptoService: CryptoService, protected vaultTimeoutService: VaultTimeoutService,
protected environmentService: EnvironmentService, protected stateService: StateService,
protected apiService: ApiService, private logService: LogService,
private keyConnectorService: KeyConnectorService, protected ngZone: NgZone) { }
async ngOnInit() {
this.pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pinLock = (this.pinSet[0] && this.vaultTimeoutService.pinProtectedKey != null) || this.pinSet[1];
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometricLock = await this.vaultTimeoutService.isBiometricLockSet() &&
(await this.cryptoService.hasKeyStored('biometric') || !this.platformUtilsService.supportsSecureStorage());
this.biometricText = await this.storageService.get(ConstantsService.biometricText);
this.email = await this.userService.getEmail();
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.hideInput = usesKeyConnector && !this.pinLock;
// Users with key connector and without biometric or pin has no MP to unlock using
if (usesKeyConnector && !(this.biometricLock || this.pinLock)) {
await this.vaultTimeoutService.logOut();
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
const vaultUrl = webVaultUrl === 'https://vault.bitwarden.com' ? 'https://bitwarden.com' : webVaultUrl;
this.webVaultHostname = Utils.getHostname(vaultUrl);
this.stateService.activeAccount.subscribe(async _userId => {
await this.load();
});
}
async submit() {
@ -87,17 +68,17 @@ export class LockComponent implements OnInit {
return;
}
const kdf = await this.userService.getKdf();
const kdfIterations = await this.userService.getKdfIterations();
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
if (this.pinLock) {
let failed = true;
try {
if (this.pinSet[0]) {
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations,
this.vaultTimeoutService.pinProtectedKey);
await this.stateService.getDecryptedPinProtected());
const encKey = await this.cryptoService.getEncKey(key);
const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin);
const protectedPin = await this.stateService.getProtectedPin();
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
failed = decPin !== this.pin;
if (!failed) {
@ -148,13 +129,13 @@ export class LockComponent implements OnInit {
if (passwordValid) {
if (this.pinSet[0]) {
const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin);
const protectedPin = await this.stateService.getProtectedPin();
const encKey = await this.cryptoService.getEncKey(key);
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfIterations);
this.vaultTimeoutService.pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
await this.stateService.setDecryptedPinProtected(await this.cryptoService.encrypt(key.key, pinKey));
}
this.setKeyAndContinue(key);
await this.setKeyAndContinue(key);
} else {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidMasterPassword'));
@ -175,7 +156,7 @@ export class LockComponent implements OnInit {
return;
}
const success = (await this.cryptoService.getKey('biometric')) != null;
const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
if (success) {
await this.doContinue();
@ -196,19 +177,40 @@ export class LockComponent implements OnInit {
private async setKeyAndContinue(key: SymmetricCryptoKey) {
await this.cryptoService.setKey(key);
this.doContinue();
await this.doContinue();
}
private async doContinue() {
this.vaultTimeoutService.biometricLocked = false;
this.vaultTimeoutService.everBeenUnlocked = true;
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon);
await this.stateService.setBiometricLocked(false);
await this.stateService.setEverBeenUnlocked(true);
const disableFavicon = await this.stateService.getDisableFavicon();
await this.stateService.setDisableFavicon(!!disableFavicon);
this.messagingService.send('unlocked');
if (this.onSuccessfulSubmit != null) {
this.onSuccessfulSubmit();
await this.onSuccessfulSubmit();
} else if (this.router != null) {
this.router.navigate([this.successRoute]);
}
}
private async load() {
this.pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pinLock = (this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || this.pinSet[1];
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometricLock = await this.vaultTimeoutService.isBiometricLockSet() &&
(await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric) || !this.platformUtilsService.supportsSecureStorage());
this.biometricText = await this.stateService.getBiometricText();
this.email = await this.stateService.getEmail();
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.hideInput = usesKeyConnector && !this.pinLock;
// Users with key connector and without biometric or pin has no MP to unlock using
if (usesKeyConnector && !(this.biometricLock || this.pinLock)) {
await this.vaultTimeoutService.logOut();
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
const vaultUrl = webVaultUrl === 'https://vault.bitwarden.com' ? 'https://bitwarden.com' : webVaultUrl;
this.webVaultHostname = Utils.getHostname(vaultUrl);
}
}

View File

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

View File

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

View File

@ -141,7 +141,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
const request = new RegisterRequest(this.email, this.name, hashedPassword,
this.hint, encKey[1].encryptedString, kdf, kdfIterations, this.referenceData, this.captchaToken);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.get<any>('orgInvitation');
const orgInvite = await this.stateService.getOrganizationInvitation();
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
request.token = orgInvite.token;
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 { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { Organization } from 'jslib-common/models/domain/organization';
@ -27,14 +24,14 @@ export class RemovePasswordComponent implements OnInit {
organization: Organization;
email: string;
constructor(private router: Router, private userService: UserService,
constructor(private router: Router, private stateService: StateService,
private apiService: ApiService, private syncService: SyncService,
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private keyConnectorService: KeyConnectorService, private storageService: StorageService) { }
private keyConnectorService: KeyConnectorService) { }
async ngOnInit() {
this.organization = await this.keyConnectorService.getManagingOrganization();
this.email = await this.userService.getEmail();
this.email = await this.stateService.getEmail();
await this.syncService.fullSync(false);
this.loading = false;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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';
export abstract class CipherService {
decryptedCipherCache: CipherView[];
clearCache: () => void;
clearCache: (userId?: string) => Promise<void>;
encrypt: (model: CipherView, key?: SymmetricCryptoKey, originalCipher?: Cipher) => Promise<Cipher>;
encryptFields: (fieldsModel: 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';
export abstract class CollectionService {
decryptedCollectionCache: CollectionView[];
clearCache: () => void;
clearCache: (userId?: string) => Promise<void>;
encrypt: (model: CollectionView) => Promise<Collection>;
decryptMany: (collections: Collection[]) => Promise<CollectionView[]>;
get: (id: string) => Promise<Collection>;

View File

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

View File

@ -2,6 +2,6 @@ import { EventType } from '../enums/eventType';
export abstract class EventService {
collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise<any>;
uploadEvents: () => Promise<any>;
clearEvents: () => Promise<any>;
uploadEvents: (userId?: string) => 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';
export abstract class FolderService {
decryptedFolderCache: FolderView[];
clearCache: () => void;
clearCache: (userId?: string) => Promise<void>;
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
get: (id: string) => 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>;
getHistory: () => Promise<GeneratedPasswordHistory[]>;
addHistory: (password: string) => Promise<any>;
clear: () => Promise<any>;
clear: (userId?: string) => Promise<any>;
passwordStrength: (password: string, userInputs?: string[]) => zxcvbn.ZXCVBNResult;
normalizeOptions: (options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) => void;
}

View File

@ -10,17 +10,15 @@ import { PolicyResponse } from '../models/response/policyResponse';
import { PolicyType } from '../enums/policyType';
export abstract class PolicyService {
policyCache: Policy[];
clearCache: () => void;
getAll: (type?: PolicyType) => Promise<Policy[]>;
getAll: (type?: PolicyType, userId?: string) => Promise<Policy[]>;
getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise<Policy>;
replace: (policies: { [id: string]: PolicyData; }) => Promise<any>;
clear: (userId: string) => Promise<any>;
clear: (userId?: string) => Promise<any>;
getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise<MasterPasswordPolicyOptions>;
evaluateMasterPassword: (passwordStrength: number, newPassword: string,
enforcedPolicyOptions?: MasterPasswordPolicyOptions) => boolean;
getResetPasswordPolicyOptions: (policies: Policy[], orgId: string) => [ResetPasswordPolicyOptions, boolean];
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';
export abstract class SendService {
decryptedSendCache: SendView[];
clearCache: () => void;
clearCache: () => Promise<void>;
encrypt: (model: SendView, file: File | ArrayBuffer, password: string, key?: SymmetricCryptoKey) => Promise<[Send, EncArrayBuffer]>;
get: (id: string) => Promise<Send>;
getAll: () => Promise<Send[]>;

View File

@ -1,6 +1,6 @@
export abstract class SettingsService {
clearCache: () => void;
clearCache: () => Promise<void>;
getEquivalentDomains: () => 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 {
get: <T>(key: string) => Promise<T>;
save: (key: string, obj: any) => Promise<any>;
remove: (key: string) => Promise<any>;
purge: () => Promise<any>;
accounts: BehaviorSubject<{ [userId: string]: Account }>;
activeAccount: BehaviorSubject<string>;
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 {
get: <T>(key: string, options?: StorageServiceOptions) => Promise<T>;
has: (key: string, options?: StorageServiceOptions) => Promise<boolean>;
save: (key: string, obj: any, options?: StorageServiceOptions) => Promise<any>;
remove: (key: string, options?: StorageServiceOptions) => Promise<any>;
get: <T>(key: string, options?: StorageOptions) => Promise<T>;
has: (key: string, options?: StorageOptions) => Promise<boolean>;
save: (key: string, obj: any, options?: StorageOptions) => 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;
getLastSync: () => Promise<Date>;
setLastSync: (date: Date) => Promise<any>;
setLastSync: (date: Date, userId?: string) => Promise<any>;
fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise<boolean>;
syncUpsertFolder: (notification: SyncFolderNotification, isEdit: boolean) => Promise<boolean>;
syncDeleteFolder: (notification: SyncFolderNotification) => Promise<boolean>;

View File

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

View File

@ -1,7 +1,4 @@
export abstract class TokenService {
token: string;
decodedToken: any;
refreshToken: string;
setTokens: (accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]) => Promise<any>;
setToken: (token: string) => Promise<any>;
getToken: () => Promise<string>;
@ -15,16 +12,16 @@ export abstract class TokenService {
setTwoFactorToken: (token: string, email: string) => Promise<any>;
getTwoFactorToken: (email: string) => Promise<string>;
clearTwoFactorToken: (email: string) => Promise<any>;
clearToken: () => Promise<any>;
decodeToken: () => any;
getTokenExpirationDate: () => Date;
tokenSecondsRemaining: (offsetSeconds?: number) => number;
tokenNeedsRefresh: (minutes?: number) => boolean;
getUserId: () => string;
getEmail: () => string;
getEmailVerified: () => boolean;
getName: () => string;
getPremium: () => boolean;
getIssuer: () => string;
getIsExternal: () => boolean;
clearToken: (userId?: string) => Promise<any>;
decodeToken: (token?: string) => any;
getTokenExpirationDate: () => Promise<Date>;
tokenSecondsRemaining: (offsetSeconds?: number) => Promise<number>;
tokenNeedsRefresh: (minutes?: number) => Promise<boolean>;
getUserId: () => Promise<string>;
getEmail: () => Promise<string>;
getEmailVerified: () => Promise<boolean>;
getName: () => Promise<string>;
getPremium: () => Promise<boolean>;
getIssuer: () => Promise<string>;
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 {
biometricLocked: boolean;
everBeenUnlocked: boolean;
pinProtectedKey: EncString;
isLocked: () => Promise<boolean>;
isLocked: (userId?: string) => Promise<boolean>;
checkVaultTimeout: () => Promise<void>;
lock: (allowSoftLock?: boolean) => Promise<void>;
logOut: () => Promise<void>;
lock: (allowSoftLock?: boolean, userId?: string) => Promise<void>;
logOut: (userId?: string) => Promise<void>;
setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise<void>;
getVaultTimeout: () => Promise<number>;
isPinLockSet: () => Promise<[boolean, 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> {
let accessToken = await this.tokenService.getToken();
if (this.tokenService.tokenNeedsRefresh()) {
if (await this.tokenService.tokenNeedsRefresh()) {
await this.doAuthRefresh();
accessToken = await this.tokenService.getToken();
}
@ -1637,7 +1637,7 @@ export class ApiService implements ApiServiceAbstraction {
headers.set('User-Agent', this.customUserAgent);
}
const decodedToken = this.tokenService.decodeToken();
const decodedToken = await this.tokenService.decodeToken();
const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + '/connect/token', {
body: this.qsStringify({
grant_type: 'refresh_token',

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

View File

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

View File

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

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

View File

@ -7,18 +7,16 @@ import { EventRequest } from '../models/request/eventRequest';
import { ApiService } from '../abstractions/api.service';
import { CipherService } from '../abstractions/cipher.service';
import { EventService as EventServiceAbstraction } from '../abstractions/event.service';
import { StorageService } from '../abstractions/storage.service';
import { UserService } from '../abstractions/user.service';
import { LogService } from '../abstractions/log.service';
import { ConstantsService } from './constants.service';
import { OrganizationService } from '../abstractions/organization.service';
import { StateService } from '../abstractions/state.service';
export class EventService implements EventServiceAbstraction {
private inited = false;
constructor(private storageService: StorageService, private apiService: ApiService,
private userService: UserService, private cipherService: CipherService,
private logService: LogService) { }
constructor(private apiService: ApiService, private cipherService: CipherService,
private stateService: StateService, private logService: LogService,
private organizationService: OrganizationService) { }
init(checkOnInterval: boolean) {
if (this.inited) {
@ -33,11 +31,11 @@ export class EventService implements EventServiceAbstraction {
}
async collect(eventType: EventType, cipherId: string = null, uploadImmediately = false): Promise<any> {
const authed = await this.userService.isAuthenticated();
const authed = await this.stateService.getIsAuthenticated();
if (!authed) {
return;
}
const organizations = await this.userService.getAllOrganizations();
const organizations = await this.organizationService.getAll();
if (organizations == null) {
return;
}
@ -51,7 +49,7 @@ export class EventService implements EventServiceAbstraction {
return;
}
}
let eventCollection = await this.storageService.get<EventData[]>(ConstantsService.eventCollectionKey);
let eventCollection = await this.stateService.getEventCollection();
if (eventCollection == null) {
eventCollection = [];
}
@ -60,18 +58,18 @@ export class EventService implements EventServiceAbstraction {
event.cipherId = cipherId;
event.date = new Date().toISOString();
eventCollection.push(event);
await this.storageService.save(ConstantsService.eventCollectionKey, eventCollection);
await this.stateService.setEventCollection(eventCollection);
if (uploadImmediately) {
await this.uploadEvents();
}
}
async uploadEvents(): Promise<any> {
const authed = await this.userService.isAuthenticated();
async uploadEvents(userId?: string): Promise<any> {
const authed = await this.stateService.getIsAuthenticated({ userId: userId });
if (!authed) {
return;
}
const eventCollection = await this.storageService.get<EventData[]>(ConstantsService.eventCollectionKey);
const eventCollection = await this.stateService.getEventCollection({ userId: userId });
if (eventCollection == null || eventCollection.length === 0) {
return;
}
@ -84,13 +82,13 @@ export class EventService implements EventServiceAbstraction {
});
try {
await this.apiService.postEventsCollect(request);
this.clearEvents();
this.clearEvents(userId);
} catch (e) {
this.logService.error(e);
}
}
async clearEvents(): Promise<any> {
await this.storageService.remove(ConstantsService.eventCollectionKey);
async clearEvents(userId?: string): Promise<any> {
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 { FolderService as FolderServiceAbstraction } from '../abstractions/folder.service';
import { I18nService } from '../abstractions/i18n.service';
import { StorageService } from '../abstractions/storage.service';
import { UserService } from '../abstractions/user.service';
import { StateService } from '../abstractions/state.service';
import { CipherData } from '../models/data/cipherData';
import { ServiceUtils } from '../misc/serviceUtils';
import { Utils } from '../misc/utils';
const Keys = {
foldersPrefix: 'folders_',
ciphersPrefix: 'ciphers_',
};
const NestingDelimiter = '/';
export class FolderService implements FolderServiceAbstraction {
decryptedFolderCache: FolderView[];
constructor(private cryptoService: CryptoService, private apiService: ApiService,
private i18nService: I18nService, private cipherService: CipherService,
private stateService: StateService) { }
constructor(private cryptoService: CryptoService, private userService: UserService,
private apiService: ApiService, private storageService: StorageService,
private i18nService: I18nService, private cipherService: CipherService) { }
clearCache(): void {
this.decryptedFolderCache = null;
async clearCache(userId?: string): Promise<void> {
await this.stateService.setDecryptedFolders(null, { userId: userId });
}
async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise<Folder> {
@ -47,9 +41,7 @@ export class FolderService implements FolderServiceAbstraction {
}
async get(id: string): Promise<Folder> {
const userId = await this.userService.getUserId();
const folders = await this.storageService.get<{ [id: string]: FolderData; }>(
Keys.foldersPrefix + userId);
const folders = await this.stateService.getEncryptedFolders();
if (folders == null || !folders.hasOwnProperty(id)) {
return null;
}
@ -58,9 +50,7 @@ export class FolderService implements FolderServiceAbstraction {
}
async getAll(): Promise<Folder[]> {
const userId = await this.userService.getUserId();
const folders = await this.storageService.get<{ [id: string]: FolderData; }>(
Keys.foldersPrefix + userId);
const folders = await this.stateService.getEncryptedFolders();
const response: Folder[] = [];
for (const id in folders) {
if (folders.hasOwnProperty(id)) {
@ -71,8 +61,9 @@ export class FolderService implements FolderServiceAbstraction {
}
async getAllDecrypted(): Promise<FolderView[]> {
if (this.decryptedFolderCache != null) {
return this.decryptedFolderCache;
const decryptedFolders = await this.stateService.getDecryptedFolders();
if (decryptedFolders != null) {
return decryptedFolders;
}
const hasKey = await this.cryptoService.hasKey();
@ -94,8 +85,8 @@ export class FolderService implements FolderServiceAbstraction {
noneFolder.name = this.i18nService.t('noneFolder');
decFolders.push(noneFolder);
this.decryptedFolderCache = decFolders;
return this.decryptedFolderCache;
await this.stateService.setDecryptedFolders(decFolders);
return decFolders;
}
async getAllNested(): Promise<TreeNode<FolderView>[]> {
@ -127,15 +118,13 @@ export class FolderService implements FolderServiceAbstraction {
response = await this.apiService.putFolder(folder.id, request);
}
const userId = await this.userService.getUserId();
const userId = await this.stateService.getUserId();
const data = new FolderData(response, userId);
await this.upsert(data);
}
async upsert(folder: FolderData | FolderData[]): Promise<any> {
const userId = await this.userService.getUserId();
let folders = await this.storageService.get<{ [id: string]: FolderData; }>(
Keys.foldersPrefix + userId);
let folders = await this.stateService.getEncryptedFolders();
if (folders == null) {
folders = {};
}
@ -149,25 +138,22 @@ export class FolderService implements FolderServiceAbstraction {
});
}
await this.storageService.save(Keys.foldersPrefix + userId, folders);
this.decryptedFolderCache = null;
await this.stateService.setDecryptedFolders(null);
await this.stateService.setEncryptedFolders(folders);
}
async replace(folders: { [id: string]: FolderData; }): Promise<any> {
const userId = await this.userService.getUserId();
await this.storageService.save(Keys.foldersPrefix + userId, folders);
this.decryptedFolderCache = null;
await this.stateService.setDecryptedFolders(null);
await this.stateService.setEncryptedFolders(folders);
}
async clear(userId: string): Promise<any> {
await this.storageService.remove(Keys.foldersPrefix + userId);
this.decryptedFolderCache = null;
async clear(userId?: string): Promise<any> {
await this.stateService.setDecryptedFolders(null, { userId: userId });
await this.stateService.setEncryptedFolders(null, { userId: userId });
}
async delete(id: string | string[]): Promise<any> {
const userId = await this.userService.getUserId();
const folders = await this.storageService.get<{ [id: string]: FolderData; }>(
Keys.foldersPrefix + userId);
const folders = await this.stateService.getEncryptedFolders();
if (folders == null) {
return;
}
@ -183,11 +169,11 @@ export class FolderService implements FolderServiceAbstraction {
});
}
await this.storageService.save(Keys.foldersPrefix + userId, folders);
this.decryptedFolderCache = null;
await this.stateService.setDecryptedFolders(null);
await this.stateService.setEncryptedFolders(folders);
// Items in a deleted folder are re-assigned to "No Folder"
const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(Keys.ciphersPrefix + userId);
const ciphers = await this.stateService.getEncryptedCiphers();
if (ciphers != null) {
const updates: CipherData[] = [];
for (const cId in ciphers) {

View File

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

View File

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

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,
} from '../abstractions/passwordGeneration.service';
import { PolicyService } from '../abstractions/policy.service';
import { StorageService } from '../abstractions/storage.service';
import { StateService } from '../abstractions/state.service';
import { EEFLongWordList } from '../misc/wordlist';
@ -34,19 +34,11 @@ const DefaultOptions = {
includeNumber: false,
};
const Keys = {
options: 'passwordGenerationOptions',
history: 'generatedPasswordHistory',
};
const MaxPasswordsInHistory = 100;
export class PasswordGenerationService implements PasswordGenerationServiceAbstraction {
private optionsCache: any;
private history: GeneratedPasswordHistory[];
constructor(private cryptoService: CryptoService, private storageService: StorageService,
private policyService: PolicyService) { }
constructor(private cryptoService: CryptoService, private policyService: PolicyService,
private stateService: StateService) { }
async generatePassword(options: any): Promise<string> {
// overload defaults with given options
@ -188,17 +180,16 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
}
async getOptions(): Promise<[any, PasswordGeneratorPolicyOptions]> {
if (this.optionsCache == null) {
const options = await this.storageService.get(Keys.options);
if (options == null) {
this.optionsCache = DefaultOptions;
} else {
this.optionsCache = Object.assign({}, DefaultOptions, options);
}
let options = await this.stateService.getPasswordGenerationOptions();
if (options == null) {
options = DefaultOptions;
} else {
options = Object.assign({}, DefaultOptions, options);
}
const enforcedOptions = await this.enforcePasswordGeneratorPoliciesOnOptions(this.optionsCache);
this.optionsCache = enforcedOptions[0];
return [this.optionsCache, enforcedOptions[1]];
await this.stateService.setPasswordGenerationOptions(options);
const enforcedOptions = await this.enforcePasswordGeneratorPoliciesOnOptions(options);
options = enforcedOptions[0];
return [options, enforcedOptions[1]];
}
async enforcePasswordGeneratorPoliciesOnOptions(options: any): Promise<[any, PasswordGeneratorPolicyOptions]> {
@ -332,8 +323,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
}
async saveOptions(options: any) {
await this.storageService.save(Keys.options, options);
this.optionsCache = options;
await this.stateService.setPasswordGenerationOptions(options);
}
async getHistory(): Promise<GeneratedPasswordHistory[]> {
@ -342,12 +332,16 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
return new Array<GeneratedPasswordHistory>();
}
if (!this.history) {
const encrypted = await this.storageService.get<GeneratedPasswordHistory[]>(Keys.history);
this.history = await this.decryptHistory(encrypted);
if (await this.stateService.getDecryptedPasswordGenerationHistory() != null) {
const encrypted = await this.stateService.getEncryptedPasswordGenerationHistory();
const decrypted = await this.decryptHistory(encrypted);
await this.stateService.setDecryptedPasswordGenerationHistory(decrypted);
}
return this.history || new Array<GeneratedPasswordHistory>();
const passwordGenerationHistory = await this.stateService.getDecryptedPasswordGenerationHistory();
return passwordGenerationHistory != null ?
passwordGenerationHistory :
new Array<GeneratedPasswordHistory>();
}
async addHistory(password: string): Promise<any> {
@ -372,12 +366,12 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
}
const newHistory = await this.encryptHistory(currentHistory);
return await this.storageService.save(Keys.history, newHistory);
return await this.stateService.setEncryptedPasswordGenerationHistory(newHistory);
}
async clear(): Promise<any> {
this.history = [];
return await this.storageService.remove(Keys.history);
async clear(userId?: string): Promise<any> {
await this.stateService.setEncryptedPasswordGenerationHistory(null, { userId: userId });
await this.stateService.setDecryptedPasswordGenerationHistory(null, { userId: userId });
}
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 { StorageService } from '../abstractions/storage.service';
import { UserService } from '../abstractions/user.service';
import { StateService } from '../abstractions/state.service';
import { PolicyData } from '../models/data/policyData';
@ -17,43 +17,40 @@ import { ApiService } from '../abstractions/api.service';
import { ListResponse } from '../models/response/listResponse';
import { PolicyResponse } from '../models/response/policyResponse';
const Keys = {
policiesPrefix: 'policies_',
};
export class PolicyService implements PolicyServiceAbstraction {
policyCache: Policy[];
constructor(private userService: UserService, private storageService: StorageService,
constructor(private stateService: StateService, private organizationService: OrganizationService,
private apiService: ApiService) {
}
clearCache(): void {
this.policyCache = null;
async clearCache(): Promise<void> {
await this.stateService.setDecryptedPolicies(null);
}
async getAll(type?: PolicyType): Promise<Policy[]> {
if (this.policyCache == null) {
const userId = await this.userService.getUserId();
const policies = await this.storageService.get<{ [id: string]: PolicyData; }>(
Keys.policiesPrefix + userId);
const response: Policy[] = [];
for (const id in policies) {
if (policies.hasOwnProperty(id)) {
response.push(new Policy(policies[id]));
async getAll(type?: PolicyType, userId?: string): Promise<Policy[]> {
let response: Policy[] = [];
const decryptedPolicies = await this.stateService.getDecryptedPolicies({ userId: userId });
if (decryptedPolicies != null) {
response = decryptedPolicies;
} else {
const diskPolicies = await this.stateService.getEncryptedPolicies({ userId: userId });
for (const id in diskPolicies) {
if (diskPolicies.hasOwnProperty(id)) {
response.push(new Policy(diskPolicies[id]));
}
}
this.policyCache = response;
await this.stateService.setDecryptedPolicies(response, { userId: userId });
}
if (type != null) {
return this.policyCache.filter(p => p.type === type);
return response.filter(policy => policy.type === type);
} else {
return this.policyCache;
return response;
}
}
async getPolicyForOrganization(policyType: PolicyType, organizationId: string): Promise<Policy> {
const org = await this.userService.getOrganization(organizationId);
const org = await this.organizationService.get(organizationId);
if (org?.isProviderUser) {
const orgPolicies = await this.apiService.getPolicies(organizationId);
const policy = orgPolicies.data.find(p => p.organizationId === organizationId);
@ -70,14 +67,13 @@ export class PolicyService implements PolicyServiceAbstraction {
}
async replace(policies: { [id: string]: PolicyData; }): Promise<any> {
const userId = await this.userService.getUserId();
await this.storageService.save(Keys.policiesPrefix + userId, policies);
this.policyCache = null;
await this.stateService.setDecryptedPolicies(null);
await this.stateService.setEncryptedPolicies(policies);
}
async clear(userId: string): Promise<any> {
await this.storageService.remove(Keys.policiesPrefix + userId);
this.policyCache = null;
async clear(userId?: string): Promise<any> {
await this.stateService.setDecryptedPolicies(null, { userId: userId });
await this.stateService.setEncryptedPolicies(null, { userId: userId });
}
async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise<MasterPasswordPolicyOptions> {
@ -187,9 +183,9 @@ export class PolicyService implements PolicyServiceAbstraction {
return policiesData.map(p => new Policy(p));
}
async policyAppliesToUser(policyType: PolicyType, policyFilter?: (policy: Policy) => boolean) {
const policies = await this.getAll(policyType);
const organizations = await this.userService.getAllOrganizations();
async policyAppliesToUser(policyType: PolicyType, policyFilter?: (policy: Policy) => boolean, userId?: string) {
const policies = await this.getAll(policyType, userId);
const organizations = await this.organizationService.getAll(userId);
let filteredPolicies;
if (policyFilter != null) {

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

View File

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

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

View File

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

View File

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

View File

@ -1,8 +1,6 @@
import { ConstantsService } from './constants.service';
import { CryptoFunctionService } from '../abstractions/cryptoFunction.service';
import { LogService } from '../abstractions/log.service';
import { StorageService } from '../abstractions/storage.service';
import { StateService } from '../abstractions/state.service';
import { TotpService as TotpServiceAbstraction } from '../abstractions/totp.service';
import { Utils } from '../misc/utils';
@ -11,8 +9,8 @@ const B32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
const SteamChars = '23456789BCDFGHJKMNPQRTVWXY';
export class TotpService implements TotpServiceAbstraction {
constructor(private storageService: StorageService, private cryptoFunctionService: CryptoFunctionService,
private logService: LogService) { }
constructor(private cryptoFunctionService: CryptoFunctionService, private logService: LogService,
private stateService: StateService) { }
async getCode(key: string): Promise<string> {
if (key == null) {
@ -114,7 +112,7 @@ export class TotpService implements TotpServiceAbstraction {
}
async isAutoCopyEnabled(): Promise<boolean> {
return !(await this.storageService.get<boolean>(ConstantsService.disableAutoTotpCopyKey));
return !(await this.stateService.getDisableAutoTotpCopy());
}
// 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 { CollectionService } from '../abstractions/collection.service';
import { CryptoService } from '../abstractions/crypto.service';
@ -9,29 +7,31 @@ import { MessagingService } from '../abstractions/messaging.service';
import { PlatformUtilsService } from '../abstractions/platformUtils.service';
import { PolicyService } from '../abstractions/policy.service';
import { SearchService } from '../abstractions/search.service';
import { StorageService } from '../abstractions/storage.service';
import { StateService } from '../abstractions/state.service';
import { TokenService } from '../abstractions/token.service';
import { UserService } from '../abstractions/user.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from '../abstractions/vaultTimeout.service';
import { KeySuffixOptions } from '../enums/keySuffixOptions';
import { PolicyType } from '../enums/policyType';
import { EncString } from '../models/domain/encString';
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
pinProtectedKey: EncString = null;
biometricLocked: boolean = true;
everBeenUnlocked: boolean = false;
private inited = false;
constructor(private cipherService: CipherService, private folderService: FolderService,
private collectionService: CollectionService, private cryptoService: CryptoService,
protected platformUtilsService: PlatformUtilsService, private storageService: StorageService,
private messagingService: MessagingService, private searchService: SearchService,
private userService: UserService, private tokenService: TokenService, private policyService: PolicyService,
constructor(
private cipherService: CipherService,
private folderService: FolderService,
private collectionService: CollectionService,
private cryptoService: CryptoService,
protected platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService,
private searchService: SearchService,
private tokenService: TokenService,
private policyService: PolicyService,
private keyConnectorService: KeyConnectorService,
private lockedCallback: () => Promise<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) {
if (this.inited) {
@ -50,110 +50,96 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
}
// Keys aren't stored for a device that is locked or logged out.
async isLocked(): Promise<boolean> {
// Handle never lock startup situation
if (await this.cryptoService.hasKeyStored('auto') && !this.everBeenUnlocked) {
await this.cryptoService.getKey('auto');
async isLocked(userId?: string): Promise<boolean> {
const neverLock = await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId) &&
!(await this.stateService.getEverBeenUnlocked({ userId: userId }));
if (neverLock) {
// TODO: This also _sets_ the key so when we check memory in the next line it finds a key.
// We should refactor here.
await this.cryptoService.getKey(KeySuffixOptions.Auto, userId);
}
return !this.cryptoService.hasKeyInMemory();
return !(await this.cryptoService.hasKeyInMemory(userId));
}
async checkVaultTimeout(): Promise<void> {
if (await this.platformUtilsService.isViewOpen()) {
// Do not lock
return;
}
// "is logged out check" - similar to isLocked, below
const authed = await this.userService.isAuthenticated();
if (!authed) {
return;
}
if (await this.isLocked()) {
return;
}
const vaultTimeout = await this.getVaultTimeout();
if (vaultTimeout == null || vaultTimeout < 0) {
return;
}
const lastActive = await this.storageService.get<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);
for (const userId in this.stateService.accounts.getValue()) {
if (userId != null && await this.shouldLock(userId)) {
await this.executeTimeoutAction(userId);
}
}
}
async lock(allowSoftLock = false): Promise<void> {
const authed = await this.userService.isAuthenticated();
async lock(allowSoftLock = false, userId?: string): Promise<void> {
const authed = await this.stateService.getIsAuthenticated({ userId: userId });
if (!authed) {
return;
}
if (await this.keyConnectorService.getUsesKeyConnector()) {
const pinSet = await this.isPinLockSet();
const pinLock = (pinSet[0] && this.pinProtectedKey != null) || pinSet[1];
const pinLock = (pinSet[0] && await this.stateService.getDecryptedPinProtected() != null) || pinSet[1];
if (!pinLock && !await this.isBiometricLockSet()) {
await this.logOut();
}
}
this.biometricLocked = true;
this.everBeenUnlocked = true;
await this.cryptoService.clearKey(false);
await this.cryptoService.clearOrgKeys(true);
await this.cryptoService.clearKeyPair(true);
await this.cryptoService.clearEncKey(true);
if (userId == null || userId === await this.stateService.getUserId()) {
this.searchService.clearIndex();
}
await this.stateService.setEverBeenUnlocked(true, { userId: userId });
await this.stateService.setBiometricLocked(true, { userId: userId });
await this.cryptoService.clearKey(false, userId);
await this.cryptoService.clearOrgKeys(true, userId);
await this.cryptoService.clearKeyPair(true, userId);
await this.cryptoService.clearEncKey(true, userId);
await this.folderService.clearCache(userId);
await this.cipherService.clearCache(userId);
await this.collectionService.clearCache(userId);
this.messagingService.send('locked', { userId: userId });
this.folderService.clearCache();
this.cipherService.clearCache();
this.collectionService.clearCache();
this.searchService.clearIndex();
this.messagingService.send('locked');
if (this.lockedCallback != null) {
await this.lockedCallback();
}
}
async logOut(): Promise<void> {
async logOut(userId?: string): Promise<void> {
if (this.loggedOutCallback != null) {
await this.loggedOutCallback();
await this.loggedOutCallback(userId);
}
}
async setVaultTimeoutOptions(timeout: number, action: string): Promise<void> {
await this.storageService.save(ConstantsService.vaultTimeoutKey, timeout);
await this.storageService.save(ConstantsService.vaultTimeoutActionKey, action);
await this.stateService.setVaultTimeout(timeout);
await this.stateService.setVaultTimeoutAction(action);
await this.cryptoService.toggleKey();
await this.tokenService.toggleTokens();
}
async isPinLockSet(): Promise<[boolean, boolean]> {
const protectedPin = await this.storageService.get<string>(ConstantsService.protectedPin);
const pinProtectedKey = await this.storageService.get<string>(ConstantsService.pinProtectedKey);
const protectedPin = await this.stateService.getProtectedPin();
const pinProtectedKey = await this.stateService.getEncryptedPinProtected();
return [protectedPin != null, pinProtectedKey != null];
}
async isBiometricLockSet(): Promise<boolean> {
return await this.storageService.get<boolean>(ConstantsService.biometricUnlockKey);
return await this.stateService.getBiometricUnlock();
}
async getVaultTimeout(): Promise<number> {
const vaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
async getVaultTimeout(userId?: string): Promise<number> {
const vaultTimeout = await this.stateService.getVaultTimeout( { userId: userId } );
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) {
const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout);
if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId)) {
const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout, userId);
// Remove negative values, and ensure it's smaller than maximum allowed value according to policy
let timeout = Math.min(vaultTimeout, policy[0].data.minutes);
@ -163,7 +149,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
// We really shouldn't need to set the value here, but multiple services relies on this value being correct.
if (vaultTimeout !== timeout) {
await this.storageService.save(ConstantsService.vaultTimeoutKey, timeout);
await this.stateService.setVaultTimeout(timeout, { userId: userId });
}
return timeout;
@ -172,9 +158,42 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
return vaultTimeout;
}
clear(): Promise<any> {
this.everBeenUnlocked = false;
this.pinProtectedKey = null;
return this.storageService.remove(ConstantsService.protectedPin);
async clear(userId?: string): Promise<void> {
await this.stateService.setEverBeenUnlocked(false, { userId: userId });
await this.stateService.setDecryptedPinProtected(null, { userId: userId });
await this.stateService.setProtectedPin(null, { userId: userId });
}
private async isLoggedOut(userId?: string): Promise<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 { I18nService } from 'jslib-common/abstractions/i18n.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { BiometricMain } from 'jslib-common/abstractions/biometric.main';
import { ElectronConstants } from './electronConstants';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { StateService } from 'jslib-common/abstractions/state.service';
export default class BiometricDarwinMain implements BiometricMain {
isError: boolean = false;
constructor(private storageService: StorageService, private i18nservice: I18nService) {}
constructor(private i18nservice: I18nService, private stateService: StateService) {}
async init() {
this.storageService.save(ElectronConstants.enableBiometric, await this.supportsBiometric());
this.storageService.save(ConstantsService.biometricText, 'unlockWithTouchId');
this.storageService.save(ElectronConstants.noAutoPromptBiometricsText, 'noAutoPromptTouchId');
await this.stateService.setEnableBiometric(await this.supportsBiometric());
await this.stateService.setBiometricText('unlockWithTouchId');
await this.stateService.setNoAutoPromptBiometricsText('noAutoPromptTouchId');
ipcMain.on('biometric', async (event: any, message: any) => {
event.returnValue = await this.authenticateBiometric();

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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