1
0
mirror of https://github.com/bitwarden/jslib synced 2026-01-04 17:43:14 +00:00

Merge branch 'refactor/authService' of https://github.com/bitwarden/jslib into refactor/authService

This commit is contained in:
Thomas Rittson
2021-12-21 11:51:04 +10:00
18 changed files with 240 additions and 209 deletions

View File

@@ -67,7 +67,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
}
async ngOnInit() {
if (!this.authing || this.twoFactorService.providers == null) {
if (!this.authing || this.twoFactorService.getProviders() == null) {
this.router.navigate([this.loginRoute]);
return;
}
@@ -122,7 +122,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
this.cleanupWebAuthn();
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
const providerData = this.twoFactorService.providers.get(this.selectedProviderType);
const providerData = this.twoFactorService.getProviders().get(this.selectedProviderType);
switch (this.selectedProviderType) {
case TwoFactorProviderType.WebAuthn:
if (!this.webAuthnNewTab) {
@@ -150,7 +150,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
break;
case TwoFactorProviderType.Email:
this.twoFactorEmail = providerData.Email;
if (this.twoFactorService.providers.size > 1) {
if (this.twoFactorService.getProviders().size > 1) {
await this.sendEmail(false);
}
break;
@@ -250,7 +250,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
}
authWebAuthn() {
const providerData = this.twoFactorService.providers.get(this.selectedProviderType);
const providerData = this.twoFactorService.getProviders().get(this.selectedProviderType);
if (!this.webAuthnSupported || this.webAuthn == null) {
return;

View File

@@ -113,11 +113,10 @@ import { ValidationService } from "./validation.service";
PlatformUtilsServiceAbstraction,
MessagingServiceAbstraction,
LogService,
CryptoFunctionServiceAbstraction,
KeyConnectorServiceAbstraction,
EnvironmentServiceAbstraction,
TwoFactorServiceAbstraction,
StateServiceAbstraction,
TwoFactorServiceAbstraction,
],
},
{
@@ -430,6 +429,7 @@ import { ValidationService } from "./validation.service";
TokenServiceAbstraction,
LogService,
OrganizationServiceAbstraction,
CryptoFunctionServiceAbstraction,
],
},
{

View File

@@ -3,6 +3,8 @@ import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
import { TokenRequestTwoFactor } from "../models/request/identityToken/tokenRequest";
export abstract class AuthService {
masterPasswordHash: string;
email: string;
logIn: (
email: string,
masterPassword: string,

View File

@@ -24,11 +24,11 @@ import { CollectionView } from "../models/view/collectionView";
import { FolderView } from "../models/view/folderView";
import { SendView } from "../models/view/sendView";
export abstract class StateService {
accounts: BehaviorSubject<{ [userId: string]: Account }>;
export abstract class StateService<T extends Account = Account> {
accounts: BehaviorSubject<{ [userId: string]: T }>;
activeAccount: BehaviorSubject<string>;
addAccount: (account: Account) => Promise<void>;
addAccount: (account: T) => Promise<void>;
setActiveUser: (userId: string) => Promise<void>;
clean: (options?: StorageOptions) => Promise<void>;
init: () => Promise<void>;

View File

@@ -1,10 +1,13 @@
import { TwoFactorProviderType } from "../enums/twoFactorProviderType";
export abstract class TwoFactorService {
init: () => void;
getSupportedProviders: (win: Window) => any[];
getDefaultProvider: (webAuthnSupported: boolean) => TwoFactorProviderType;
setSelectedProvider: (type: TwoFactorProviderType) => void;
clearSelectedProvider: () => void;
setProviders: (data: any) => void;
clearProviders: () => void;
providers: Map<TwoFactorProviderType, { [key: string]: string }>;
getProviders: () => Map<TwoFactorProviderType, { [key: string]: string }>;
}

View File

@@ -130,9 +130,6 @@ export class AccountSettings {
enableMinimizeToTray?: boolean;
enableStartToTray?: boolean;
enableTray?: boolean;
environmentUrls?: any = {
server: "bitwarden.com",
};
equivalentDomains?: any;
minimizeOnCopyToClipboard?: boolean;
neverDomains?: { [id: string]: any };

View File

@@ -24,4 +24,7 @@ export class GlobalState {
noAutoPromptBiometrics?: boolean;
noAutoPromptBiometricsText?: string;
stateVersion: number;
environmentUrls?: any = {
server: "bitwarden.com",
};
}

View File

@@ -1,8 +1,8 @@
import { Account } from "./account";
import { GlobalState } from "./globalState";
export class State {
accounts: { [userId: string]: Account } = {};
export class State<TAccount extends Account = Account> {
accounts: { [userId: string]: TAccount } = {};
globals: GlobalState = new GlobalState();
activeUserId: string;
}

View File

@@ -8,7 +8,7 @@ import { Utils } from "../../../misc/utils";
export class PasswordTokenRequest extends TokenRequest implements CaptchaProtectedRequest {
constructor(
public email: string,
private masterPasswordHash: string,
public masterPasswordHash: string,
public captchaResponse: string,
protected twoFactor: TokenRequestTwoFactor,
device?: DeviceRequest

View File

@@ -2,7 +2,7 @@ import { HashPurpose } from "../enums/hashPurpose";
import { KdfType } from "../enums/kdfType";
import { TwoFactorProviderType } from "../enums/twoFactorProviderType";
import { AccountProfile, AccountTokens } from "../models/domain/account";
import { Account, AccountProfile, AccountTokens } from "../models/domain/account";
import { AuthResult } from "../models/domain/authResult";
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
@@ -84,7 +84,7 @@ export class AuthService implements AuthServiceAbstraction {
await this.buildTwoFactor(twoFactor),
await this.buildDeviceRequest()
);
}
}
const response = await this.apiService.postIdentityToken(tokenRequest);
@@ -93,7 +93,7 @@ export class AuthService implements AuthServiceAbstraction {
await this.cryptoService.setKey(key);
await this.cryptoService.setKeyHash(localHashedPassword);
}
}
};
const result = await this.processTokenResponse(response, false, onSuccess);
if (result.requiresTwoFactor) {
@@ -112,13 +112,15 @@ export class AuthService implements AuthServiceAbstraction {
): Promise<AuthResult> {
this.twoFactorService.clearSelectedProvider();
const tokenRequest = this.savedTokenRequest ?? new SsoTokenRequest(
code,
codeVerifier,
redirectUrl,
await this.buildTwoFactor(twoFactor),
await this.buildDeviceRequest()
);
const tokenRequest =
this.savedTokenRequest ??
new SsoTokenRequest(
code,
codeVerifier,
redirectUrl,
await this.buildTwoFactor(twoFactor),
await this.buildDeviceRequest()
);
const response = await this.apiService.postIdentityToken(tokenRequest);
const tokenResponse = response as IdentityTokenResponse;
@@ -137,7 +139,7 @@ export class AuthService implements AuthServiceAbstraction {
);
}
}
}
};
const result = await this.processTokenResponse(response, newSsoUser, onSuccess);
@@ -155,12 +157,14 @@ export class AuthService implements AuthServiceAbstraction {
): Promise<AuthResult> {
this.twoFactorService.clearSelectedProvider();
const tokenRequest = this.savedTokenRequest ?? new ApiTokenRequest(
clientId,
clientSecret,
await this.buildTwoFactor(twoFactor),
await this.buildDeviceRequest()
);
const tokenRequest =
this.savedTokenRequest ??
new ApiTokenRequest(
clientId,
clientSecret,
await this.buildTwoFactor(twoFactor),
await this.buildDeviceRequest()
);
const response = await this.apiService.postIdentityToken(tokenRequest);
@@ -173,7 +177,7 @@ export class AuthService implements AuthServiceAbstraction {
const keyConnectorUrl = this.environmentService.getKeyConnectorUrl();
await this.keyConnectorService.getAndSetKey(keyConnectorUrl);
}
}
};
const result = await this.processTokenResponse(response, false, onSuccess);
@@ -219,6 +223,14 @@ export class AuthService implements AuthServiceAbstraction {
return this.savedTokenRequest instanceof PasswordTokenRequest;
}
get email(): string {
return (this.savedTokenRequest as PasswordTokenRequest).email;
}
get masterPasswordHash(): string {
return (this.savedTokenRequest as PasswordTokenRequest).masterPasswordHash;
}
async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> {
email = email.trim().toLowerCase();
let kdf: KdfType = null;
@@ -259,7 +271,7 @@ export class AuthService implements AuthServiceAbstraction {
result.resetMasterPassword = tokenResponse.resetMasterPassword;
result.forcePasswordReset = tokenResponse.forcePasswordReset;
this.saveAccountInformation(tokenResponse);
await this.saveAccountInformation(tokenResponse);
if (tokenResponse.twoFactorToken != null) {
await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken);
@@ -267,7 +279,9 @@ export class AuthService implements AuthServiceAbstraction {
if (this.setCryptoKeys && !newSsoUser) {
await this.cryptoService.setEncKey(tokenResponse.key);
await this.cryptoService.setEncPrivateKey(tokenResponse.privateKey ?? await this.createKeyPairForOldAccount());
await this.cryptoService.setEncPrivateKey(
tokenResponse.privateKey ?? (await this.createKeyPairForOldAccount())
);
}
await onSuccess();
@@ -306,25 +320,27 @@ export class AuthService implements AuthServiceAbstraction {
private async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
const accountInformation = await this.tokenService.decodeToken(tokenResponse.accessToken);
await this.stateService.addAccount({
profile: {
...new AccountProfile(),
...{
userId: accountInformation.sub,
email: accountInformation.email,
hasPremiumPersonally: accountInformation.premium,
kdfIterations: tokenResponse.kdfIterations,
kdfType: tokenResponse.kdf,
await this.stateService.addAccount(
new Account({
profile: {
...new AccountProfile(),
...{
userId: accountInformation.sub,
email: accountInformation.email,
hasPremiumPersonally: accountInformation.premium,
kdfIterations: tokenResponse.kdfIterations,
kdfType: tokenResponse.kdf,
},
},
},
tokens: {
...new AccountTokens(),
...{
accessToken: tokenResponse.accessToken,
refreshToken: tokenResponse.refreshToken,
tokens: {
...new AccountTokens(),
...{
accessToken: tokenResponse.accessToken,
refreshToken: tokenResponse.refreshToken,
},
},
},
});
})
);
}
private async createKeyPairForOldAccount() {

View File

@@ -761,13 +761,13 @@ export class CryptoService implements CryptoServiceAbstraction {
// Helpers
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 });
if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) {
await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId });
} else if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) {
await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId });
} else {
await this.stateService.setCryptoMasterKeyB64(null, { userId: userId });
await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId });
await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId });
}
}

View File

@@ -109,18 +109,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
}
async setUrlsFromStorage(): Promise<void> {
const urlsObj: any = await this.stateService.getEnvironmentUrls();
const urls = urlsObj || {
base: null,
api: null,
identity: null,
icons: null,
notifications: null,
events: null,
webVault: null,
keyConnector: null,
};
const urls: any = await this.stateService.getEnvironmentUrls();
const envUrls = new EnvironmentUrls();
if (urls.base) {

View File

@@ -1,6 +1,12 @@
import { StateService as StateServiceAbstraction } from "../abstractions/state.service";
import { Account } from "../models/domain/account";
import {
Account,
AccountData,
AccountKeys,
AccountProfile,
AccountTokens,
} from "../models/domain/account";
import { LogService } from "../abstractions/log.service";
import { StorageService } from "../abstractions/storage.service";
@@ -34,19 +40,21 @@ import { SendData } from "../models/data/sendData";
import { BehaviorSubject } from "rxjs";
import { StateMigrationService } from "./stateMigration.service";
import { StateMigrationService } from "../abstractions/stateMigration.service";
export class StateService implements StateServiceAbstraction {
accounts = new BehaviorSubject<{ [userId: string]: Account }>({});
export class StateService<TAccount extends Account = Account>
implements StateServiceAbstraction<TAccount>
{
accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({});
activeAccount = new BehaviorSubject<string>(null);
private state: State = new State();
protected state: State<TAccount> = new State<TAccount>();
constructor(
private storageService: StorageService,
private secureStorageService: StorageService,
private logService: LogService,
private stateMigrationService: StateMigrationService
protected storageService: StorageService,
protected secureStorageService: StorageService,
protected logService: LogService,
protected stateMigrationService: StateMigrationService
) {}
async init(): Promise<void> {
@@ -60,7 +68,7 @@ export class StateService implements StateServiceAbstraction {
async loadStateFromDisk() {
if ((await this.getActiveUserIdFromStorage()) != null) {
const diskState = await this.storageService.get<State>(
const diskState = await this.storageService.get<State<TAccount>>(
"state",
await this.defaultOnDiskOptions()
);
@@ -71,10 +79,7 @@ export class StateService implements StateServiceAbstraction {
}
}
async addAccount(account: Account) {
if (account?.profile?.userId == null) {
return;
}
async addAccount(account: TAccount) {
this.state.accounts[account.profile.userId] = account;
await this.scaffoldNewAccountStorage(account);
await this.setActiveUser(account.profile.userId);
@@ -83,7 +88,7 @@ export class StateService implements StateServiceAbstraction {
async setActiveUser(userId: string): Promise<void> {
this.state.activeUserId = userId;
const storedState = await this.storageService.get<State>(
const storedState = await this.storageService.get<State<TAccount>>(
"state",
await this.defaultOnDiskOptions()
);
@@ -1321,47 +1326,64 @@ export class StateService implements StateServiceAbstraction {
}
async getEntityId(options?: StorageOptions): Promise<string> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
?.profile?.entityId;
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.profile?.entityId;
}
async setEntityId(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions)
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
);
account.profile.entityId = value;
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
);
}
async getEntityType(options?: StorageOptions): Promise<any> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
?.profile?.entityType;
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.profile?.entityType;
}
async setEntityType(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions)
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
);
account.profile.entityType = value;
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
);
}
async getEnvironmentUrls(options?: StorageOptions): Promise<any> {
return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.settings?.environmentUrls ?? {
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.environmentUrls ?? {
base: null,
api: null,
identity: null,
icons: null,
notifications: null,
events: null,
webVault: null,
keyConnector: null,
// TODO: this is a bug and we should use base instead for the server detail in the account switcher, otherwise self hosted urls will not show correctly
server: "bitwarden.com",
}
);
}
async setEnvironmentUrls(value: any, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
account.settings.environmentUrls = value;
await this.saveAccount(
account,
globals.environmentUrls = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
}
@@ -1402,17 +1424,20 @@ export class StateService implements StateServiceAbstraction {
async getEverBeenUnlocked(options?: StorageOptions): Promise<boolean> {
return (
(await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile
?.everBeenUnlocked ?? false
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.profile?.everBeenUnlocked ?? false
);
}
async setEverBeenUnlocked(value: boolean, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions)
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
account.profile.everBeenUnlocked = value;
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
}
async getForcePasswordReset(options?: StorageOptions): Promise<boolean> {
@@ -1981,10 +2006,7 @@ export class StateService implements StateServiceAbstraction {
const accountVaultTimeout = (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.settings?.vaultTimeout;
const globalVaultTimeout = (
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.vaultTimeout;
return accountVaultTimeout ?? globalVaultTimeout ?? 15;
return accountVaultTimeout;
}
async setVaultTimeout(value: number, options?: StorageOptions): Promise<void> {
@@ -2047,7 +2069,7 @@ export class StateService implements StateServiceAbstraction {
);
}
private async getGlobals(options: StorageOptions): Promise<GlobalState> {
protected async getGlobals(options: StorageOptions): Promise<GlobalState> {
let globals: GlobalState;
if (this.useMemory(options.storageLocation)) {
globals = this.getGlobalsFromMemory();
@@ -2060,39 +2082,41 @@ export class StateService implements StateServiceAbstraction {
return globals ?? new GlobalState();
}
private async saveGlobals(globals: GlobalState, options: StorageOptions) {
protected async saveGlobals(globals: GlobalState, options: StorageOptions) {
return this.useMemory(options.storageLocation)
? this.saveGlobalsToMemory(globals)
: await this.saveGlobalsToDisk(globals, options);
}
private getGlobalsFromMemory(): GlobalState {
protected getGlobalsFromMemory(): GlobalState {
return this.state.globals;
}
private async getGlobalsFromDisk(options: StorageOptions): Promise<GlobalState> {
return (await this.storageService.get<State>("state", options))?.globals;
protected async getGlobalsFromDisk(options: StorageOptions): Promise<GlobalState> {
return (await this.storageService.get<State<TAccount>>("state", options))?.globals;
}
private saveGlobalsToMemory(globals: GlobalState): void {
protected saveGlobalsToMemory(globals: GlobalState): void {
this.state.globals = globals;
}
private async saveGlobalsToDisk(globals: GlobalState, options: StorageOptions): Promise<void> {
protected async saveGlobalsToDisk(globals: GlobalState, options: StorageOptions): Promise<void> {
if (options.useSecureStorage) {
const state = (await this.secureStorageService.get<State>("state", options)) ?? new State();
const state =
(await this.secureStorageService.get<State<TAccount>>("state", options)) ?? new State();
state.globals = globals;
await this.secureStorageService.save("state", state, options);
} else {
const state = (await this.storageService.get<State>("state", options)) ?? new State();
const state =
(await this.storageService.get<State<TAccount>>("state", options)) ?? new State();
state.globals = globals;
await this.saveStateToStorage(state, options);
}
}
private async getAccount(options: StorageOptions): Promise<Account> {
protected async getAccount(options: StorageOptions): Promise<TAccount> {
try {
let account: Account;
let account: TAccount;
if (this.useMemory(options.storageLocation)) {
account = this.getAccountFromMemory(options);
}
@@ -2101,51 +2125,51 @@ export class StateService implements StateServiceAbstraction {
account = await this.getAccountFromDisk(options);
}
return account != null ? new Account(account) : null;
return account;
} catch (e) {
this.logService.error(e);
}
}
private getAccountFromMemory(options: StorageOptions): Account {
protected getAccountFromMemory(options: StorageOptions): TAccount {
if (this.state.accounts == null) {
return null;
}
return this.state.accounts[this.getUserIdFromMemory(options)];
}
private getUserIdFromMemory(options: StorageOptions): string {
protected getUserIdFromMemory(options: StorageOptions): string {
return options?.userId != null
? this.state.accounts[options.userId]?.profile?.userId
: this.state.activeUserId;
}
private async getAccountFromDisk(options: StorageOptions): Promise<Account> {
protected async getAccountFromDisk(options: StorageOptions): Promise<TAccount> {
if (options?.userId == null && this.state.activeUserId == null) {
return null;
}
const state = options?.useSecureStorage
? (await this.secureStorageService.get<State>("state", options)) ??
(await this.storageService.get<State>(
? (await this.secureStorageService.get<State<TAccount>>("state", options)) ??
(await this.storageService.get<State<TAccount>>(
"state",
this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local })
))
: await this.storageService.get<State>("state", options);
: await this.storageService.get<State<TAccount>>("state", options);
return state?.accounts[options?.userId ?? this.state.activeUserId];
}
private useMemory(storageLocation: StorageLocation) {
protected useMemory(storageLocation: StorageLocation) {
return storageLocation === StorageLocation.Memory || storageLocation === StorageLocation.Both;
}
private useDisk(storageLocation: StorageLocation) {
protected useDisk(storageLocation: StorageLocation) {
return storageLocation === StorageLocation.Disk || storageLocation === StorageLocation.Both;
}
private async saveAccount(
account: Account,
protected async saveAccount(
account: TAccount,
options: StorageOptions = {
storageLocation: StorageLocation.Both,
useSecureStorage: false,
@@ -2156,86 +2180,75 @@ export class StateService implements StateServiceAbstraction {
: await this.saveAccountToDisk(account, options);
}
private async saveAccountToDisk(account: Account, options: StorageOptions): Promise<void> {
protected async saveAccountToDisk(account: TAccount, options: StorageOptions): Promise<void> {
const storageLocation = options.useSecureStorage
? this.secureStorageService
: this.storageService;
const state = (await storageLocation.get<State>("state", options)) ?? new State();
const state =
(await storageLocation.get<State<TAccount>>("state", options)) ?? new State<TAccount>();
state.accounts[account.profile.userId] = account;
await storageLocation.save("state", state, options);
await this.pushAccounts();
}
private async saveAccountToMemory(account: Account): Promise<void> {
protected async saveAccountToMemory(account: TAccount): Promise<void> {
if (this.getAccountFromMemory({ userId: account.profile.userId }) !== null) {
this.state.accounts[account.profile.userId] = account;
}
await this.pushAccounts();
}
private async scaffoldNewAccountStorage(account: Account): Promise<void> {
protected async scaffoldNewAccountStorage(account: TAccount): Promise<void> {
await this.scaffoldNewAccountLocalStorage(account);
await this.scaffoldNewAccountSessionStorage(account);
await this.scaffoldNewAccountMemoryStorage(account);
}
private async scaffoldNewAccountLocalStorage(account: Account): Promise<void> {
protected async scaffoldNewAccountLocalStorage(account: TAccount): Promise<void> {
const storedState =
(await this.storageService.get<State>("state", await this.defaultOnDiskLocalOptions())) ??
new State();
(await this.storageService.get<State<TAccount>>(
"state",
await this.defaultOnDiskLocalOptions()
)) ?? new State<TAccount>();
const storedAccount = storedState.accounts[account.profile.userId];
if (storedAccount != null) {
account = {
settings: storedAccount.settings,
profile: account.profile,
tokens: account.tokens,
keys: account.keys,
data: account.data,
};
account.settings = storedAccount.settings;
}
storedState.accounts[account.profile.userId] = account;
await this.saveStateToStorage(storedState, await this.defaultOnDiskLocalOptions());
}
private async scaffoldNewAccountMemoryStorage(account: Account): Promise<void> {
protected async scaffoldNewAccountMemoryStorage(account: TAccount): Promise<void> {
const storedState =
(await this.storageService.get<State>("state", await this.defaultOnDiskMemoryOptions())) ??
new State();
(await this.storageService.get<State<TAccount>>(
"state",
await this.defaultOnDiskMemoryOptions()
)) ?? new State<TAccount>();
const storedAccount = storedState.accounts[account.profile.userId];
if (storedAccount != null) {
account = {
settings: storedAccount.settings,
profile: account.profile,
tokens: account.tokens,
keys: account.keys,
data: account.data,
};
account.settings = storedAccount.settings;
}
storedState.accounts[account.profile.userId] = account;
await this.saveStateToStorage(storedState, await this.defaultOnDiskMemoryOptions());
}
private async scaffoldNewAccountSessionStorage(account: Account): Promise<void> {
protected async scaffoldNewAccountSessionStorage(account: TAccount): Promise<void> {
const storedState =
(await this.storageService.get<State>("state", await this.defaultOnDiskOptions())) ??
new State();
(await this.storageService.get<State<TAccount>>(
"state",
await this.defaultOnDiskOptions()
)) ?? new State<TAccount>();
const storedAccount = storedState.accounts[account.profile.userId];
if (storedAccount != null) {
account = {
settings: storedAccount.settings,
profile: account.profile,
tokens: account.tokens,
keys: account.keys,
data: account.data,
};
account.settings = storedAccount.settings;
}
storedState.accounts[account.profile.userId] = account;
await this.saveStateToStorage(storedState, await this.defaultOnDiskOptions());
}
private async pushAccounts(): Promise<void> {
protected async pushAccounts(): Promise<void> {
await this.pruneInMemoryAccounts();
if (this.state?.accounts == null || Object.keys(this.state.accounts).length < 1) {
this.accounts.next(null);
@@ -2245,7 +2258,7 @@ export class StateService implements StateServiceAbstraction {
this.accounts.next(this.state.accounts);
}
private reconcileOptions(
protected reconcileOptions(
requestedOptions: StorageOptions,
defaultOptions: StorageOptions
): StorageOptions {
@@ -2263,11 +2276,11 @@ export class StateService implements StateServiceAbstraction {
return requestedOptions;
}
private get defaultInMemoryOptions(): StorageOptions {
protected get defaultInMemoryOptions(): StorageOptions {
return { storageLocation: StorageLocation.Memory, userId: this.state.activeUserId };
}
private async defaultOnDiskOptions(): Promise<StorageOptions> {
protected async defaultOnDiskOptions(): Promise<StorageOptions> {
return {
storageLocation: StorageLocation.Disk,
htmlStorageLocation: HtmlStorageLocation.Session,
@@ -2276,7 +2289,7 @@ export class StateService implements StateServiceAbstraction {
};
}
private async defaultOnDiskLocalOptions(): Promise<StorageOptions> {
protected async defaultOnDiskLocalOptions(): Promise<StorageOptions> {
return {
storageLocation: StorageLocation.Disk,
htmlStorageLocation: HtmlStorageLocation.Local,
@@ -2285,7 +2298,7 @@ export class StateService implements StateServiceAbstraction {
};
}
private async defaultOnDiskMemoryOptions(): Promise<StorageOptions> {
protected async defaultOnDiskMemoryOptions(): Promise<StorageOptions> {
return {
storageLocation: StorageLocation.Disk,
htmlStorageLocation: HtmlStorageLocation.Memory,
@@ -2294,7 +2307,7 @@ export class StateService implements StateServiceAbstraction {
};
}
private async defaultSecureStorageOptions(): Promise<StorageOptions> {
protected async defaultSecureStorageOptions(): Promise<StorageOptions> {
return {
storageLocation: StorageLocation.Disk,
useSecureStorage: true,
@@ -2302,46 +2315,38 @@ export class StateService implements StateServiceAbstraction {
};
}
private async getActiveUserIdFromStorage(): Promise<string> {
const state = await this.storageService.get<State>("state");
protected async getActiveUserIdFromStorage(): Promise<string> {
const state = await this.storageService.get<State<TAccount>>("state");
return state?.activeUserId;
}
private async removeAccountFromLocalStorage(
protected async removeAccountFromLocalStorage(
userId: string = this.state.activeUserId
): Promise<void> {
const state = await this.storageService.get<State>("state", {
const state = await this.storageService.get<State<TAccount>>("state", {
htmlStorageLocation: HtmlStorageLocation.Local,
});
if (state?.accounts[userId] == null) {
return;
}
state.accounts[userId] = new Account({
settings: state.accounts[userId].settings,
});
state.accounts[userId] = this.resetAccount(state.accounts[userId]);
await this.saveStateToStorage(state, await this.defaultOnDiskLocalOptions());
}
private async removeAccountFromSessionStorage(
protected async removeAccountFromSessionStorage(
userId: string = this.state.activeUserId
): Promise<void> {
const state = await this.storageService.get<State>("state", {
const state = await this.storageService.get<State<TAccount>>("state", {
htmlStorageLocation: HtmlStorageLocation.Session,
});
if (state?.accounts[userId] == null) {
return;
}
state.accounts[userId] = new Account({
settings: state.accounts[userId].settings,
});
state.accounts[userId] = this.resetAccount(state.accounts[userId]);
await this.saveStateToStorage(state, await this.defaultOnDiskOptions());
}
private async removeAccountFromSecureStorage(
protected async removeAccountFromSecureStorage(
userId: string = this.state.activeUserId
): Promise<void> {
await this.setCryptoMasterKeyAuto(null, { userId: userId });
@@ -2349,15 +2354,18 @@ export class StateService implements StateServiceAbstraction {
await this.setCryptoMasterKeyB64(null, { userId: userId });
}
private removeAccountFromMemory(userId: string = this.state.activeUserId): void {
protected removeAccountFromMemory(userId: string = this.state.activeUserId): void {
delete this.state.accounts[userId];
}
private async saveStateToStorage(state: State, options: StorageOptions): Promise<void> {
protected async saveStateToStorage(
state: State<TAccount>,
options: StorageOptions
): Promise<void> {
await this.storageService.save("state", state, options);
}
private async pruneInMemoryAccounts() {
protected async pruneInMemoryAccounts() {
// We preserve settings for logged out accounts, but we don't want to consider them when thinking about active account state
for (const userId in this.state.accounts) {
if (!(await this.getIsAuthenticated({ userId: userId }))) {
@@ -2365,4 +2373,13 @@ export class StateService implements StateServiceAbstraction {
}
}
}
// settings persist even on reset
protected resetAccount(account: TAccount) {
account.data = new AccountData();
account.keys = new AccountKeys();
account.profile = new AccountProfile();
account.tokens = new AccountTokens();
return account;
}
}

View File

@@ -114,19 +114,22 @@ export class StateMigrationService {
readonly latestVersion: number = 2;
constructor(
private storageService: StorageService,
private secureStorageService: StorageService
protected storageService: StorageService,
protected secureStorageService: StorageService
) {}
async needsMigration(): Promise<boolean> {
const currentStateVersion = (await this.storageService.get<State>("state"))?.globals
?.stateVersion;
const currentStateVersion = (
await this.storageService.get<State<Account>>("state", {
htmlStorageLocation: HtmlStorageLocation.Local,
})
)?.globals?.stateVersion;
return currentStateVersion == null || currentStateVersion < this.latestVersion;
}
async migrate(): Promise<void> {
let currentStateVersion =
(await this.storageService.get<State>("state"))?.globals?.stateVersion ?? 1;
(await this.storageService.get<State<Account>>("state"))?.globals?.stateVersion ?? 1;
while (currentStateVersion < this.latestVersion) {
switch (currentStateVersion) {
case 1:
@@ -138,10 +141,10 @@ export class StateMigrationService {
}
}
private async migrateStateFrom1To2(): Promise<void> {
protected async migrateStateFrom1To2(): Promise<void> {
const options: StorageOptions = { htmlStorageLocation: HtmlStorageLocation.Local };
const userId = await this.storageService.get<string>("userId");
const initialState: State =
const initialState: State<Account> =
userId == null
? {
globals: {
@@ -174,6 +177,7 @@ export class StateMigrationService {
v1Keys.enableBiometric,
options
),
environmentUrls: await this.storageService.get<any>(v1Keys.environmentUrls, options),
installedVersion: await this.storageService.get<string>(
v1Keys.installedVersion,
options
@@ -439,10 +443,6 @@ export class StateMigrationService {
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

View File

@@ -159,6 +159,10 @@ export class TwoFactorService implements TwoFactorServiceAbstraction {
return providerType;
}
setSelectedProvider(type: TwoFactorProviderType) {
this.selectedTwoFactorProviderType = type;
}
clearSelectedProvider() {
this.selectedTwoFactorProviderType = null;
}
@@ -171,7 +175,7 @@ export class TwoFactorService implements TwoFactorServiceAbstraction {
this.twoFactorProvidersData = null;
}
get providers() {
getProviders() {
return this.twoFactorProvidersData;
}
}

View File

@@ -53,7 +53,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
async isLocked(userId?: string): Promise<boolean> {
const neverLock =
(await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId)) &&
!(await this.stateService.getEverBeenUnlocked({ userId: 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.

View File

@@ -9,7 +9,7 @@ import { StringResponse } from "./models/response/stringResponse";
export abstract class BaseProgram {
constructor(
private stateService: StateService,
protected stateService: StateService,
private writeLn: (s: string, finalLine: boolean, error: boolean) => void
) {}

View File

@@ -86,10 +86,10 @@ module.exports = (config) => {
var githubAction =
process.env.GITHUB_WORKFLOW != null && process.env.GITHUB_WORKFLOW !== "";
// if (githubAction) {
removeBrowser("Firefox");
removeBrowser("Safari");
// }
if (githubAction) {
removeBrowser("Firefox");
removeBrowser("Safari");
}
return result;
},