1
0
mirror of https://github.com/bitwarden/jslib synced 2026-01-05 10:03:14 +00:00

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

This commit is contained in:
addison
2021-11-05 12:41:05 -04:00
parent b9aac4a404
commit 54a15a39b3
21 changed files with 98 additions and 98 deletions

View File

@@ -29,7 +29,7 @@ export abstract class CryptoService {
getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>;
getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>;
hasKey: () => Promise<boolean>;
hasKeyInMemory: () => Promise<boolean>;
hasKeyInMemory: (userId?: string) => Promise<boolean>;
hasKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
hasEncKey: () => Promise<boolean>;
clearKey: (clearSecretStorage?: boolean, userId?: string) => Promise<any>;
@@ -39,7 +39,7 @@ export abstract class CryptoService {
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

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

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

@@ -11,14 +11,14 @@ import { PolicyType } from '../enums/policyType';
export abstract class PolicyService {
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

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

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

@@ -12,7 +12,7 @@ export abstract class TokenService {
setTwoFactorToken: (token: string, email: string) => Promise<any>;
getTwoFactorToken: (email: string) => Promise<string>;
clearTwoFactorToken: (email: string) => Promise<any>;
clearToken: () => Promise<any>;
clearToken: (userId?: string) => Promise<any>;
decodeToken: (token?: string) => any;
getTokenExpirationDate: () => Promise<Date>;
tokenSecondsRemaining: (offsetSeconds?: number) => Promise<number>;

View File

@@ -2,10 +2,10 @@ export abstract class VaultTimeoutService {
isLocked: (userId?: string) => Promise<boolean>;
checkVaultTimeout: () => Promise<void>;
lock: (allowSoftLock?: boolean, userId?: string) => Promise<void>;
logOut: () => 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

@@ -709,9 +709,9 @@ export class CipherService implements CipherServiceAbstraction {
await this.stateService.setEncryptedCiphers(ciphers);
}
async clear(): Promise<any> {
await this.clearEncryptedCiphersState();
await this.clearCache();
async clear(userId?: string): Promise<any> {
await this.clearEncryptedCiphersState(userId);
await this.clearCache(userId);
}
async moveManyWithServer(ids: string[], folderId: string): Promise<any> {
@@ -1094,8 +1094,8 @@ export class CipherService implements CipherServiceAbstraction {
}
}
private async clearEncryptedCiphersState() {
await this.stateService.setEncryptedCiphers(null);
private async clearEncryptedCiphersState(userId?: string) {
await this.stateService.setEncryptedCiphers(null, { userId: userId });
}
private async clearDecryptedCiphersState(userId?: string) {

View File

@@ -133,9 +133,9 @@ export class CollectionService implements CollectionServiceAbstraction {
await this.stateService.setEncryptedCollections(collections);
}
async clear(): Promise<any> {
await this.clearCache();
await this.stateService.setEncryptedCollections(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> {

View File

@@ -328,8 +328,8 @@ export class CryptoService implements CryptoServiceAbstraction {
return await this.hasKeyInMemory() || await this.hasKeyStored(KeySuffixOptions.Auto) || await this.hasKeyStored(KeySuffixOptions.Biometric);
}
async hasKeyInMemory(): Promise<boolean> {
return await this.stateService.getCryptoMasterKey() != null;
async hasKeyInMemory(userId?: string): Promise<boolean> {
return await this.stateService.getCryptoMasterKey({ userId: userId }) != null;
}
async hasKeyStored(keySuffix: KeySuffixOptions, userId?: string): Promise<boolean> {

View File

@@ -64,12 +64,12 @@ export class EventService implements EventServiceAbstraction {
}
}
async uploadEvents(): Promise<any> {
const authed = await this.stateService.getIsAuthenticated();
async uploadEvents(userId?: string): Promise<any> {
const authed = await this.stateService.getIsAuthenticated({ userId: userId });
if (!authed) {
return;
}
const eventCollection = await this.stateService.getEventCollection();
const eventCollection = await this.stateService.getEventCollection({ userId: userId });
if (eventCollection == null || eventCollection.length === 0) {
return;
}
@@ -82,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.stateService.setEventCollection(null);
async clearEvents(userId?: string): Promise<any> {
await this.stateService.setEventCollection(null, { userId: userId });
}
}

View File

@@ -147,9 +147,9 @@ export class FolderService implements FolderServiceAbstraction {
await this.stateService.setEncryptedFolders(folders);
}
async clear(): Promise<any> {
await this.stateService.setDecryptedFolders(null);
await this.stateService.setEncryptedFolders(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> {

View File

@@ -27,8 +27,8 @@ export class OrganizationService implements OrganizationServiceAbstraction {
return organizations.find(o => o.identifier === identifier);
}
async getAll(): Promise<Organization[]> {
const organizations = await this.stateService.getOrganizations();
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) {

View File

@@ -369,9 +369,9 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
return await this.stateService.setEncryptedPasswordGenerationHistory(newHistory);
}
async clear(): Promise<any> {
await this.stateService.setEncryptedPasswordGenerationHistory(null);
await this.stateService.setDecryptedPasswordGenerationHistory(null);
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

@@ -28,19 +28,19 @@ export class PolicyService implements PolicyServiceAbstraction {
await this.stateService.setDecryptedPolicies(null);
}
async getAll(type?: PolicyType): Promise<Policy[]> {
async getAll(type?: PolicyType, userId?: string): Promise<Policy[]> {
let response: Policy[] = [];
const decryptedPolicies = await this.stateService.getDecryptedPolicies();
const decryptedPolicies = await this.stateService.getDecryptedPolicies({ userId: userId });
if (decryptedPolicies != null) {
response = decryptedPolicies;
} else {
const diskPolicies = await this.stateService.getEncryptedPolicies();
const diskPolicies = await this.stateService.getEncryptedPolicies({ userId: userId });
for (const id in diskPolicies) {
if (diskPolicies.hasOwnProperty(id)) {
response.push(new Policy(diskPolicies[id]));
}
}
await this.stateService.setDecryptedPolicies(response);
await this.stateService.setDecryptedPolicies(response, { userId: userId });
}
if (type != null) {
return response.filter(policy => policy.type === type);
@@ -71,9 +71,9 @@ export class PolicyService implements PolicyServiceAbstraction {
await this.stateService.setEncryptedPolicies(policies);
}
async clear(): Promise<any> {
await this.stateService.setDecryptedPolicies(null);
await this.stateService.setEncryptedPolicies(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> {
@@ -183,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.organizationService.getAll();
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

@@ -24,8 +24,8 @@ export class SettingsService implements SettingsServiceAbstraction {
await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains);
}
async clear(): Promise<void> {
await this.stateService.setSettings(null);
async clear(userId?: string): Promise<void> {
await this.stateService.setSettings(null, { userId: userId });
}
// Helpers

View File

@@ -59,12 +59,8 @@ export class SyncService implements SyncServiceAbstraction {
return null;
}
async setLastSync(date: Date): Promise<any> {
if (await this.stateService.getUserId()) {
return;
}
await this.stateService.setLastSync(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> {

View File

@@ -93,11 +93,11 @@ export class TokenService implements TokenServiceAbstraction {
return await this.stateService.setTwoFactorToken(null);
}
async clearToken(): Promise<any> {
await this.stateService.setAccessToken(null);
await this.stateService.setRefreshToken(null);
await this.stateService.setApiKeyClientId(null);
await this.stateService.setApiKeyClientSecret(null);
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

View File

@@ -12,8 +12,6 @@ import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from '../abstra
import { KeySuffixOptions } from '../enums/keySuffixOptions';
import { PolicyType } from '../enums/policyType';
import { StorageLocation } from '../enums/storageLocation';
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
private inited = false;
@@ -23,7 +21,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
protected platformUtilsService: PlatformUtilsService, private messagingService: MessagingService,
private searchService: SearchService, private tokenService: TokenService,
private policyService: PolicyService, private stateService: StateService,
private lockedCallback: () => Promise<void> = null, private loggedOutCallback: () => Promise<void> = null) {
private lockedCallback: () => Promise<void> = null, private loggedOutCallback: (userId?: string) => Promise<void> = null) {
}
init(checkOnInterval: boolean) {
@@ -50,7 +48,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
return (await this.cryptoService.getKey(KeySuffixOptions.Auto, userId)) != null;
}
return !(await this.cryptoService.hasKeyInMemory());
return !(await this.cryptoService.hasKeyInMemory(userId));
}
async checkVaultTimeout(): Promise<void> {
@@ -59,32 +57,34 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
return;
}
// "is logged out check" - similar to isLocked, below
const authed = await this.stateService.getIsAuthenticated();
if (!authed) {
return;
}
for (const userId in this.stateService.accounts.getValue()) {
if (userId != null) {
if (await this.isLoggedOut(userId)) {
return;
}
if (await this.isLocked()) {
return;
}
if (await this.isLocked(userId)) {
return;
}
const vaultTimeout = await this.getVaultTimeout();
if (vaultTimeout == null || vaultTimeout < 0) {
return;
}
const vaultTimeout = await this.getVaultTimeout(userId);
if (vaultTimeout == null || vaultTimeout < 0) {
return;
}
const lastActive = await this.stateService.getLastActive();
if (lastActive == null) {
return;
}
const lastActive = await this.stateService.getLastActive({ userId: userId });
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.stateService.getVaultTimeoutAction();
timeoutAction === 'logOut' ? await this.logOut() : await this.lock(true);
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.stateService.getVaultTimeoutAction({ userId: userId });
timeoutAction === 'logOut' ? await this.logOut() : await this.lock(true, userId);
}
}
}
}
@@ -114,9 +114,9 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
}
}
async logOut(): Promise<void> {
async logOut(userId?: string): Promise<void> {
if (this.loggedOutCallback != null) {
await this.loggedOutCallback();
await this.loggedOutCallback(userId);
}
}
@@ -137,11 +137,11 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
return await this.stateService.getBiometricUnlock();
}
async getVaultTimeout(): Promise<number> {
const vaultTimeout = await this.stateService.getVaultTimeout();
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);
@@ -151,7 +151,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.stateService.setVaultTimeout(timeout);
await this.stateService.setVaultTimeout(timeout, { userId: userId });
}
return timeout;
@@ -160,9 +160,13 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
return vaultTimeout;
}
async clear(): Promise<void> {
await this.stateService.setEverBeenUnlocked(false);
await this.stateService.setDecryptedPinProtected(null);
await this.stateService.setProtectedPin(null);
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 }));
}
}