1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

Merge branch 'master' into feature/org-admin-refresh

This commit is contained in:
Vincent Salucci
2022-09-01 09:52:52 -05:00
114 changed files with 1422 additions and 519 deletions

View File

@@ -12,7 +12,8 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions";
import { Utils } from "@bitwarden/common/misc/utils";
@@ -49,6 +50,7 @@ export class LockComponent implements OnInit, OnDestroy {
protected messagingService: MessagingService,
protected cryptoService: CryptoService,
protected vaultTimeoutService: VaultTimeoutService,
protected vaultTimeoutSettingsService: VaultTimeoutSettingsService,
protected environmentService: EnvironmentService,
protected stateService: StateService,
protected apiService: ApiService,
@@ -262,13 +264,13 @@ export class LockComponent implements OnInit, OnDestroy {
}
private async load() {
this.pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pinSet = await this.vaultTimeoutSettingsService.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.vaultTimeoutSettingsService.isBiometricLockSet()) &&
((await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric)) ||
!this.platformUtilsService.supportsSecureStorage());
this.biometricText = await this.stateService.getBiometricText();

View File

@@ -7,7 +7,7 @@ import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { Organization } from "@bitwarden/common/models/domain/organization";
@Directive()

View File

@@ -12,7 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
import { DEFAULT_KDF_ITERATIONS, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums/kdfType";
import { Utils } from "@bitwarden/common/misc/utils";

View File

@@ -9,7 +9,7 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwo
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync.service";
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { EncString } from "@bitwarden/common/models/domain/encString";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";

View File

@@ -46,14 +46,15 @@ import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service";
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync.service";
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/abstractions/twoFactor.service";
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@bitwarden/common/abstractions/usernameGeneration.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { Account } from "@bitwarden/common/models/domain/account";
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
@@ -88,14 +89,15 @@ import { SendService } from "@bitwarden/common/services/send.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { StateService } from "@bitwarden/common/services/state.service";
import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service";
import { SyncService } from "@bitwarden/common/services/sync.service";
import { SyncService } from "@bitwarden/common/services/sync/sync.service";
import { TokenService } from "@bitwarden/common/services/token.service";
import { TotpService } from "@bitwarden/common/services/totp.service";
import { TwoFactorService } from "@bitwarden/common/services/twoFactor.service";
import { UserVerificationApiService } from "@bitwarden/common/services/userVerification/userVerification-api.service";
import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service";
import { UsernameGenerationService } from "@bitwarden/common/services/usernameGeneration.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service";
import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service";
import { AuthGuard } from "../guards/auth.guard";
@@ -344,6 +346,16 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
useClass: SettingsService,
deps: [StateServiceAbstraction],
},
{
provide: VaultTimeoutSettingsServiceAbstraction,
useClass: VaultTimeoutSettingsService,
deps: [
CryptoServiceAbstraction,
TokenServiceAbstraction,
PolicyServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: VaultTimeoutServiceAbstraction,
useClass: VaultTimeoutService,
@@ -355,11 +367,10 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
SearchServiceAbstraction,
TokenServiceAbstraction,
PolicyServiceAbstraction,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
AuthServiceAbstraction,
VaultTimeoutSettingsServiceAbstraction,
LOCKED_CALLBACK,
LOGOUT_CALLBACK,
],

View File

@@ -0,0 +1,64 @@
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { ContainerService } from "@bitwarden/common/services/container.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { StateService } from "@bitwarden/common/services/state.service";
describe("SettingsService", () => {
let settingsService: SettingsService;
let cryptoService: SubstituteOf<CryptoService>;
let stateService: SubstituteOf<StateService>;
let activeAccount: BehaviorSubject<string>;
let activeAccountUnlocked: BehaviorSubject<boolean>;
beforeEach(() => {
cryptoService = Substitute.for();
stateService = Substitute.for();
activeAccount = new BehaviorSubject("123");
activeAccountUnlocked = new BehaviorSubject(true);
stateService.getSettings().resolves({ equivalentDomains: [["test"], ["domains"]] });
stateService.activeAccount$.returns(activeAccount);
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
settingsService = new SettingsService(stateService);
});
afterEach(() => {
activeAccount.complete();
activeAccountUnlocked.complete();
});
describe("getEquivalentDomains", () => {
it("returns value", async () => {
const result = await firstValueFrom(settingsService.settings$);
expect(result).toEqual({
equivalentDomains: [["test"], ["domains"]],
});
});
});
it("setEquivalentDomains", async () => {
await settingsService.setEquivalentDomains([["test2"], ["domains2"]]);
stateService.received(1).setSettings(Arg.any());
expect((await firstValueFrom(settingsService.settings$)).equivalentDomains).toEqual([
["test2"],
["domains2"],
]);
});
it("clear", async () => {
await settingsService.clear();
stateService.received(1).setSettings(Arg.any(), Arg.any());
expect(await firstValueFrom(settingsService.settings$)).toEqual({});
});
});

View File

@@ -1,6 +1,10 @@
import { Observable } from "rxjs";
import { AccountSettingsSettings } from "../models/domain/account";
export abstract class SettingsService {
clearCache: () => Promise<void>;
getEquivalentDomains: () => Promise<any>;
settings$: Observable<AccountSettingsSettings>;
setEquivalentDomains: (equivalentDomains: string[][]) => Promise<any>;
clear: (userId?: string) => Promise<void>;
}

View File

@@ -12,7 +12,7 @@ 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 { Account, AccountSettingsSettings } from "../models/domain/account";
import { EncString } from "../models/domain/encString";
import { EnvironmentUrls } from "../models/domain/environmentUrls";
import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory";
@@ -286,8 +286,14 @@ export abstract class StateService<T extends Account = Account> {
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>;
/**
* @deprecated Do not call this directly, use SettingsService
*/
getSettings: (options?: StorageOptions) => Promise<AccountSettingsSettings>;
/**
* @deprecated Do not call this directly, use SettingsService
*/
setSettings: (value: AccountSettingsSettings, options?: StorageOptions) => Promise<void>;
getSsoCodeVerifier: (options?: StorageOptions) => Promise<string>;
setSsoCodeVerifier: (value: string, options?: StorageOptions) => Promise<void>;
getSsoOrgIdentifier: (options?: StorageOptions) => Promise<string>;

View File

@@ -1,12 +1,17 @@
import { Observable } from "rxjs";
import {
SyncCipherNotification,
SyncFolderNotification,
SyncSendNotification,
} from "../models/response/notificationResponse";
} from "../../models/response/notificationResponse";
import { SyncEventArgs } from "../../types/syncEventArgs";
export abstract class SyncService {
syncInProgress: boolean;
sync$: Observable<SyncEventArgs>;
getLastSync: () => Promise<Date>;
setLastSync: (date: Date, userId?: string) => Promise<any>;
fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise<boolean>;

View File

@@ -1,10 +0,0 @@
export abstract class VaultTimeoutService {
checkVaultTimeout: () => Promise<void>;
lock: (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: (userId?: string) => Promise<any>;
}

View File

@@ -0,0 +1,5 @@
export abstract class VaultTimeoutService {
checkVaultTimeout: () => Promise<void>;
lock: (userId?: string) => Promise<void>;
logOut: (userId?: string) => Promise<void>;
}

View File

@@ -0,0 +1,7 @@
export abstract class VaultTimeoutSettingsService {
setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise<void>;
getVaultTimeout: (userId?: string) => Promise<number>;
isPinLockSet: () => Promise<[boolean, boolean]>;
isBiometricLockSet: () => Promise<boolean>;
clear: (userId?: string) => Promise<void>;
}

View File

@@ -137,11 +137,15 @@ export class AccountSettings {
generatorOptions?: 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
settings?: AccountSettingsSettings; // TODO: Merge whatever is going on here into the AccountSettings model properly
vaultTimeout?: number;
vaultTimeoutAction?: string = "lock";
}
export type AccountSettingsSettings = {
equivalentDomains?: { [id: string]: any };
};
export class AccountTokens {
accessToken?: string;
decodedToken?: any;

View File

@@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { ApiService } from "../abstractions/api.service";
import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service";
import { CryptoService } from "../abstractions/crypto.service";
@@ -13,6 +15,7 @@ import { UriMatchType } from "../enums/uriMatchType";
import { sequentialize } from "../misc/sequentialize";
import { Utils } from "../misc/utils";
import { CipherData } from "../models/data/cipherData";
import { AccountSettingsSettings } from "../models/domain/account";
import { Attachment } from "../models/domain/attachment";
import { Card } from "../models/domain/card";
import { Cipher } from "../models/domain/cipher";
@@ -387,20 +390,22 @@ export class CipherService implements CipherServiceAbstraction {
const eqDomainsPromise =
domain == null
? Promise.resolve([])
: this.settingsService.getEquivalentDomains().then((eqDomains: any[][]) => {
let matches: any[] = [];
eqDomains.forEach((eqDomain) => {
if (eqDomain.length && eqDomain.indexOf(domain) >= 0) {
matches = matches.concat(eqDomain);
: firstValueFrom(this.settingsService.settings$).then(
(settings: AccountSettingsSettings) => {
let matches: any[] = [];
settings.equivalentDomains.forEach((eqDomain: any) => {
if (eqDomain.length && eqDomain.indexOf(domain) >= 0) {
matches = matches.concat(eqDomain);
}
});
if (!matches.length) {
matches.push(domain);
}
});
if (!matches.length) {
matches.push(domain);
return matches;
}
return matches;
});
);
const result = await Promise.all([eqDomainsPromise, this.getAllDecrypted()]);
const matchingDomains = result[0];

View File

@@ -8,7 +8,7 @@ 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 { SyncService } from "../abstractions/sync/sync.service.abstraction";
import { AuthenticationStatus } from "../enums/authenticationStatus";
import { NotificationType } from "../enums/notificationType";
import {

View File

@@ -1,56 +1,50 @@
import { BehaviorSubject, concatMap } from "rxjs";
import { SettingsService as SettingsServiceAbstraction } from "../abstractions/settings.service";
import { StateService } from "../abstractions/state.service";
const Keys = {
settingsPrefix: "settings_",
equivalentDomains: "equivalentDomains",
};
import { Utils } from "../misc/utils";
import { AccountSettingsSettings } from "../models/domain/account";
export class SettingsService implements SettingsServiceAbstraction {
constructor(private stateService: StateService) {}
private _settings: BehaviorSubject<AccountSettingsSettings> = new BehaviorSubject({});
async clearCache(): Promise<void> {
await this.stateService.setSettings(null);
}
settings$ = this._settings.asObservable();
getEquivalentDomains(): Promise<any> {
return this.getSettingsKey(Keys.equivalentDomains);
constructor(private stateService: StateService) {
this.stateService.activeAccountUnlocked$
.pipe(
concatMap(async (unlocked) => {
if (Utils.global.bitwardenContainerService == null) {
return;
}
if (!unlocked) {
this._settings.next({});
return;
}
const data = await this.stateService.getSettings();
this._settings.next(data);
})
)
.subscribe();
}
async setEquivalentDomains(equivalentDomains: string[][]): Promise<void> {
await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains);
const settings = this._settings.getValue() ?? {};
settings.equivalentDomains = equivalentDomains;
this._settings.next(settings);
await this.stateService.setSettings(settings);
}
async clear(userId?: string): Promise<void> {
if (userId == null || userId == (await this.stateService.getUserId())) {
this._settings.next({});
}
await this.stateService.setSettings(null, { userId: userId });
}
// Helpers
private async getSettings(): Promise<any> {
const settings = await this.stateService.getSettings();
if (settings == null) {
// eslint-disable-next-line
const userId = await this.stateService.getUserId();
}
return settings;
}
private async getSettingsKey(key: string): Promise<any> {
const settings = await this.getSettings();
if (settings != null && settings[key]) {
return settings[key];
}
return null;
}
private async setSettingsKey(key: string, value: any): Promise<void> {
let settings = await this.getSettings();
if (!settings) {
settings = {};
}
settings[key] = value;
await this.stateService.setSettings(settings);
}
}

View File

@@ -20,7 +20,12 @@ 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, AccountData, AccountSettings } from "../models/domain/account";
import {
Account,
AccountData,
AccountSettings,
AccountSettingsSettings,
} from "../models/domain/account";
import { EncString } from "../models/domain/encString";
import { EnvironmentUrls } from "../models/domain/environmentUrls";
import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory";
@@ -2075,13 +2080,13 @@ export class StateService<
);
}
async getSettings(options?: StorageOptions): Promise<any> {
async getSettings(options?: StorageOptions): Promise<AccountSettingsSettings> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()))
)?.settings?.settings;
}
async setSettings(value: string, options?: StorageOptions): Promise<void> {
async setSettings(value: AccountSettingsSettings, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())
);

View File

@@ -12,7 +12,7 @@ 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, AccountSettings } from "../models/domain/account";
import { Account, AccountSettings, AccountSettingsSettings } from "../models/domain/account";
import { EnvironmentUrls } from "../models/domain/environmentUrls";
import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory";
import { GlobalState } from "../models/domain/globalState";
@@ -319,7 +319,10 @@ export class StateMigrationService<
encrypted: await this.get<string>(v1Keys.pinProtected),
},
protectedPin: await this.get<string>(v1Keys.protectedPin),
settings: userId == null ? null : await this.get<any>(v1KeyPrefixes.settings + userId),
settings:
userId == null
? null
: await this.get<AccountSettingsSettings>(v1KeyPrefixes.settings + userId),
vaultTimeout:
(await this.get<number>(v1Keys.vaultTimeout)) ?? defaultAccount.settings.vaultTimeout,
vaultTimeoutAction:

View File

@@ -1,43 +1,50 @@
import { ApiService } from "../abstractions/api.service";
import { CipherService } from "../abstractions/cipher.service";
import { CollectionService } from "../abstractions/collection.service";
import { CryptoService } from "../abstractions/crypto.service";
import { FolderApiServiceAbstraction } from "../abstractions/folder/folder-api.service.abstraction";
import { InternalFolderService } from "../abstractions/folder/folder.service.abstraction";
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 { InternalPolicyService } from "../abstractions/policy/policy.service.abstraction";
import { ProviderService } from "../abstractions/provider.service";
import { SendService } from "../abstractions/send.service";
import { SettingsService } from "../abstractions/settings.service";
import { StateService } from "../abstractions/state.service";
import { SyncService as SyncServiceAbstraction } from "../abstractions/sync.service";
import { sequentialize } from "../misc/sequentialize";
import { CipherData } from "../models/data/cipherData";
import { CollectionData } from "../models/data/collectionData";
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 { CipherResponse } from "../models/response/cipherResponse";
import { CollectionDetailsResponse } from "../models/response/collectionResponse";
import { DomainsResponse } from "../models/response/domainsResponse";
import { FolderResponse } from "../models/response/folderResponse";
import { Subject } from "rxjs";
import { ApiService } from "../../abstractions/api.service";
import { CipherService } from "../../abstractions/cipher.service";
import { CollectionService } from "../../abstractions/collection.service";
import { CryptoService } from "../../abstractions/crypto.service";
import { FolderApiServiceAbstraction } from "../../abstractions/folder/folder-api.service.abstraction";
import { InternalFolderService } from "../../abstractions/folder/folder.service.abstraction";
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 { InternalPolicyService } from "../../abstractions/policy/policy.service.abstraction";
import { ProviderService } from "../../abstractions/provider.service";
import { SendService } from "../../abstractions/send.service";
import { SettingsService } from "../../abstractions/settings.service";
import { StateService } from "../../abstractions/state.service";
import { SyncService as SyncServiceAbstraction } from "../../abstractions/sync/sync.service.abstraction";
import { sequentialize } from "../../misc/sequentialize";
import { CipherData } from "../../models/data/cipherData";
import { CollectionData } from "../../models/data/collectionData";
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 { CipherResponse } from "../../models/response/cipherResponse";
import { CollectionDetailsResponse } from "../../models/response/collectionResponse";
import { DomainsResponse } from "../../models/response/domainsResponse";
import { FolderResponse } from "../../models/response/folderResponse";
import {
SyncCipherNotification,
SyncFolderNotification,
SyncSendNotification,
} from "../models/response/notificationResponse";
import { PolicyResponse } from "../models/response/policyResponse";
import { ProfileResponse } from "../models/response/profileResponse";
import { SendResponse } from "../models/response/sendResponse";
} from "../../models/response/notificationResponse";
import { PolicyResponse } from "../../models/response/policyResponse";
import { ProfileResponse } from "../../models/response/profileResponse";
import { SendResponse } from "../../models/response/sendResponse";
import { SyncEventArgs } from "../../types/syncEventArgs";
export class SyncService implements SyncServiceAbstraction {
syncInProgress = false;
private _sync = new Subject<SyncEventArgs>();
sync$ = this._sync.asObservable();
constructor(
private apiService: ApiService,
private settingsService: SettingsService,
@@ -265,11 +272,13 @@ export class SyncService implements SyncServiceAbstraction {
private syncStarted() {
this.syncInProgress = true;
this.messagingService.send("syncStarted");
this._sync.next({ status: "Started" });
}
private syncCompleted(successfully: boolean): boolean {
this.syncInProgress = false;
this.messagingService.send("syncCompleted", { successfully: successfully });
this._sync.next({ status: successfully ? "SuccessfullyCompleted" : "UnsuccessfullyCompleted" });
return successfully;
}

View File

@@ -1,205 +0,0 @@
import { AuthService } from "../abstractions/auth.service";
import { CipherService } from "../abstractions/cipher.service";
import { CollectionService } from "../abstractions/collection.service";
import { CryptoService } from "../abstractions/crypto.service";
import { FolderService } from "../abstractions/folder/folder.service.abstraction";
import { KeyConnectorService } from "../abstractions/keyConnector.service";
import { MessagingService } from "../abstractions/messaging.service";
import { PlatformUtilsService } from "../abstractions/platformUtils.service";
import { PolicyService } from "../abstractions/policy/policy.service.abstraction";
import { SearchService } from "../abstractions/search.service";
import { StateService } from "../abstractions/state.service";
import { TokenService } from "../abstractions/token.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vaultTimeout.service";
import { AuthenticationStatus } from "../enums/authenticationStatus";
import { PolicyType } from "../enums/policyType";
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
private inited = false;
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 stateService: StateService,
private authService: AuthService,
private lockedCallback: (userId?: string) => Promise<void> = null,
private loggedOutCallback: (expired: boolean, userId?: string) => Promise<void> = null
) {}
init(checkOnInterval: boolean) {
if (this.inited) {
return;
}
this.inited = true;
if (checkOnInterval) {
this.startCheck();
}
}
startCheck() {
this.checkVaultTimeout();
setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds
}
async checkVaultTimeout(): Promise<void> {
if (await this.platformUtilsService.isViewOpen()) {
return;
}
for (const userId in this.stateService.accounts.getValue()) {
if (userId != null && (await this.shouldLock(userId))) {
await this.executeTimeoutAction(userId);
}
}
}
async lock(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] && (await this.stateService.getDecryptedPinProtected()) != null) || pinSet[1];
if (!pinLock && !(await this.isBiometricLockSet())) {
await this.logOut(userId);
}
}
if (userId == null || userId === (await this.stateService.getUserId())) {
this.searchService.clearIndex();
await this.folderService.clearCache();
}
await this.stateService.setEverBeenUnlocked(true, { userId: userId });
await this.stateService.setCryptoMasterKeyAuto(null, { 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.cipherService.clearCache(userId);
await this.collectionService.clearCache(userId);
this.messagingService.send("locked", { userId: userId });
if (this.lockedCallback != null) {
await this.lockedCallback(userId);
}
}
async logOut(userId?: string): Promise<void> {
if (this.loggedOutCallback != null) {
await this.loggedOutCallback(false, userId);
}
}
async setVaultTimeoutOptions(timeout: number, action: string): Promise<void> {
await this.stateService.setVaultTimeout(timeout);
// We swap these tokens from being on disk for lock actions, and in memory for logout actions
// Get them here to set them to their new location after changing the timeout action and clearing if needed
const token = await this.tokenService.getToken();
const refreshToken = await this.tokenService.getRefreshToken();
const clientId = await this.tokenService.getClientId();
const clientSecret = await this.tokenService.getClientSecret();
const currentAction = await this.stateService.getVaultTimeoutAction();
if ((timeout != null || timeout === 0) && action === "logOut" && action !== currentAction) {
// if we have a vault timeout and the action is log out, reset tokens
await this.tokenService.clearToken();
}
await this.stateService.setVaultTimeoutAction(action);
await this.tokenService.setToken(token);
await this.tokenService.setRefreshToken(refreshToken);
await this.tokenService.setClientId(clientId);
await this.tokenService.setClientSecret(clientSecret);
await this.cryptoService.toggleKey();
}
async isPinLockSet(): Promise<[boolean, boolean]> {
const protectedPin = await this.stateService.getProtectedPin();
const pinProtectedKey = await this.stateService.getEncryptedPinProtected();
return [protectedPin != null, pinProtectedKey != null];
}
async isBiometricLockSet(): Promise<boolean> {
return await this.stateService.getBiometricUnlock();
}
async getVaultTimeout(userId?: string): Promise<number> {
const vaultTimeout = await this.stateService.getVaultTimeout({ userId: userId });
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);
if (vaultTimeout == null || timeout < 0) {
timeout = policy[0].data.minutes;
}
// We really shouldn't need to set the value here, but multiple services relies on this value being correct.
if (vaultTimeout !== timeout) {
await this.stateService.setVaultTimeout(timeout, { userId: userId });
}
return timeout;
}
return vaultTimeout;
}
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 shouldLock(userId: string): Promise<boolean> {
const authStatus = await this.authService.getAuthStatus(userId);
if (
authStatus === AuthenticationStatus.Locked ||
authStatus === AuthenticationStatus.LoggedOut
) {
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(userId) : await this.lock(userId);
}
}

View File

@@ -0,0 +1,135 @@
import { AuthService } from "../../abstractions/auth.service";
import { CipherService } from "../../abstractions/cipher.service";
import { CollectionService } from "../../abstractions/collection.service";
import { CryptoService } from "../../abstractions/crypto.service";
import { FolderService } from "../../abstractions/folder/folder.service.abstraction";
import { KeyConnectorService } from "../../abstractions/keyConnector.service";
import { MessagingService } from "../../abstractions/messaging.service";
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
import { SearchService } from "../../abstractions/search.service";
import { StateService } from "../../abstractions/state.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../../abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "../../abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { AuthenticationStatus } from "../../enums/authenticationStatus";
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
private inited = false;
constructor(
private cipherService: CipherService,
private folderService: FolderService,
private collectionService: CollectionService,
private cryptoService: CryptoService,
protected platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService,
private searchService: SearchService,
private keyConnectorService: KeyConnectorService,
private stateService: StateService,
private authService: AuthService,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private lockedCallback: (userId?: string) => Promise<void> = null,
private loggedOutCallback: (expired: boolean, userId?: string) => Promise<void> = null
) {}
init(checkOnInterval: boolean) {
if (this.inited) {
return;
}
this.inited = true;
if (checkOnInterval) {
this.startCheck();
}
}
startCheck() {
this.checkVaultTimeout();
setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds
}
async checkVaultTimeout(): Promise<void> {
if (await this.platformUtilsService.isViewOpen()) {
return;
}
for (const userId in this.stateService.accounts.getValue()) {
if (userId != null && (await this.shouldLock(userId))) {
await this.executeTimeoutAction(userId);
}
}
}
async lock(userId?: string): Promise<void> {
const authed = await this.stateService.getIsAuthenticated({ userId: userId });
if (!authed) {
return;
}
if (await this.keyConnectorService.getUsesKeyConnector()) {
const pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
const pinLock =
(pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || pinSet[1];
if (!pinLock && !(await this.vaultTimeoutSettingsService.isBiometricLockSet())) {
await this.logOut(userId);
}
}
if (userId == null || userId === (await this.stateService.getUserId())) {
this.searchService.clearIndex();
await this.folderService.clearCache();
}
await this.stateService.setEverBeenUnlocked(true, { userId: userId });
await this.stateService.setCryptoMasterKeyAuto(null, { 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.cipherService.clearCache(userId);
await this.collectionService.clearCache(userId);
this.messagingService.send("locked", { userId: userId });
if (this.lockedCallback != null) {
await this.lockedCallback(userId);
}
}
async logOut(userId?: string): Promise<void> {
if (this.loggedOutCallback != null) {
await this.loggedOutCallback(false, userId);
}
}
private async shouldLock(userId: string): Promise<boolean> {
const authStatus = await this.authService.getAuthStatus(userId);
if (
authStatus === AuthenticationStatus.Locked ||
authStatus === AuthenticationStatus.LoggedOut
) {
return false;
}
const vaultTimeout = await this.vaultTimeoutSettingsService.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(userId) : await this.lock(userId);
}
}

View File

@@ -0,0 +1,82 @@
import { CryptoService } from "../../abstractions/crypto.service";
import { PolicyService } from "../../abstractions/policy/policy.service.abstraction";
import { StateService } from "../../abstractions/state.service";
import { TokenService } from "../../abstractions/token.service";
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { PolicyType } from "../../enums/policyType";
export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceAbstraction {
constructor(
private cryptoService: CryptoService,
private tokenService: TokenService,
private policyService: PolicyService,
private stateService: StateService
) {}
async setVaultTimeoutOptions(timeout: number, action: string): Promise<void> {
await this.stateService.setVaultTimeout(timeout);
// We swap these tokens from being on disk for lock actions, and in memory for logout actions
// Get them here to set them to their new location after changing the timeout action and clearing if needed
const token = await this.tokenService.getToken();
const refreshToken = await this.tokenService.getRefreshToken();
const clientId = await this.tokenService.getClientId();
const clientSecret = await this.tokenService.getClientSecret();
const currentAction = await this.stateService.getVaultTimeoutAction();
if ((timeout != null || timeout === 0) && action === "logOut" && action !== currentAction) {
// if we have a vault timeout and the action is log out, reset tokens
await this.tokenService.clearToken();
}
await this.stateService.setVaultTimeoutAction(action);
await this.tokenService.setToken(token);
await this.tokenService.setRefreshToken(refreshToken);
await this.tokenService.setClientId(clientId);
await this.tokenService.setClientSecret(clientSecret);
await this.cryptoService.toggleKey();
}
async isPinLockSet(): Promise<[boolean, boolean]> {
const protectedPin = await this.stateService.getProtectedPin();
const pinProtectedKey = await this.stateService.getEncryptedPinProtected();
return [protectedPin != null, pinProtectedKey != null];
}
async isBiometricLockSet(): Promise<boolean> {
return await this.stateService.getBiometricUnlock();
}
async getVaultTimeout(userId?: string): Promise<number> {
const vaultTimeout = await this.stateService.getVaultTimeout({ userId: userId });
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);
if (vaultTimeout == null || timeout < 0) {
timeout = policy[0].data.minutes;
}
// We really shouldn't need to set the value here, but multiple services relies on this value being correct.
if (vaultTimeout !== timeout) {
await this.stateService.setVaultTimeout(timeout, { userId: userId });
}
return timeout;
}
return vaultTimeout;
}
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 });
}
}

View File

@@ -0,0 +1,15 @@
import { filter } from "rxjs";
export type SyncStatus = "Started" | "SuccessfullyCompleted" | "UnsuccessfullyCompleted";
export type SyncEventArgs = {
status: SyncStatus;
};
/**
* Helper function to filter only on successfully completed syncs
* @returns a function that can be used in a `.pipe()` from an observable
*/
export function onlySuccessfullyCompleted() {
return filter<SyncEventArgs>((syncEvent) => syncEvent.status === "SuccessfullyCompleted");
}