1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-21 10:43:23 +00:00

Merge branch 'master' into refactor/authService

This commit is contained in:
Thomas Rittson
2021-12-21 09:21:50 +10:00
33 changed files with 358 additions and 231 deletions

1
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1 @@
193434461dbd9c48fe5dcbad95693470aec422ac

View File

@@ -16,3 +16,23 @@ Common code referenced across Bitwarden JavaScript projects.
- _Microsoft Build Tools 2015_ in Visual Studio Installer - _Microsoft Build Tools 2015_ in Visual Studio Installer
- [Windows 10 SDK 17134](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) - [Windows 10 SDK 17134](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/)
either by downloading it seperately or through the Visual Studio Installer. either by downloading it seperately or through the Visual Studio Installer.
## Prettier
We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge 8b2dfc6cdcb8ff5b604364c2ea6d343473aee7cd`
3. Resolve any merge conflicts, commit.
4. Run `npm run prettier`
5. Commit
6. Run `git merge -Xours 193434461dbd9c48fe5dcbad95693470aec422ac`
7. Push
### Git blame
We also recommend that you configure git to ignore the prettier revision using:
```bash
git config blame.ignoreRevsFile .git-blame-ignore-revs
```

View File

@@ -18,6 +18,21 @@ const IconMap: any = {
"fa-apple": String.fromCharCode(0xf179), "fa-apple": String.fromCharCode(0xf179),
}; };
/**
* Provides a mapping from supported card brands to
* the filenames of icon that should be present in images/cards folder of clients.
*/
const cardIcons: Record<string, string> = {
Visa: "card-visa",
Mastercard: "card-mastercard",
Amex: "card-amex",
Discover: "card-discover",
"Diners Club": "card-diners-club",
JCB: "card-jcb",
Maestro: "card-maestro",
UnionPay: "card-union-pay",
};
@Component({ @Component({
selector: "app-vault-icon", selector: "app-vault-icon",
templateUrl: "icon.component.html", templateUrl: "icon.component.html",
@@ -59,6 +74,7 @@ export class IconComponent implements OnChanges {
break; break;
case CipherType.Card: case CipherType.Card:
this.icon = "fa-credit-card"; this.icon = "fa-credit-card";
this.setCardIcon();
break; break;
case CipherType.Identity: case CipherType.Identity:
this.icon = "fa-id-card-o"; this.icon = "fa-id-card-o";
@@ -102,4 +118,11 @@ export class IconComponent implements OnChanges {
this.image = null; this.image = null;
} }
} }
private setCardIcon() {
const brand = this.cipher.card.brand;
if (this.imageEnabled && brand in cardIcons) {
this.icon = "credit-card-icon " + cardIcons[brand];
}
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

View File

@@ -0,0 +1,44 @@
$card-icons-base: "~@bitwarden/jslib-angular/src/images/cards/";
$card-icons: (
"visa": $card-icons-base + "visa-light.png",
"amex": $card-icons-base + "amex-light.png",
"diners-club": $card-icons-base + "diners_club-light.png",
"discover": $card-icons-base + "discover-light.png",
"jcb": $card-icons-base + "jcb-light.png",
"maestro": $card-icons-base + "maestro-light.png",
"mastercard": $card-icons-base + "mastercard-light.png",
"union-pay": $card-icons-base + "union_pay-light.png",
);
$card-icons-dark: (
"visa": $card-icons-base + "visa-dark.png",
"amex": $card-icons-base + "amex-dark.png",
"diners-club": $card-icons-base + "diners_club-dark.png",
"discover": $card-icons-base + "discover-dark.png",
"jcb": $card-icons-base + "jcb-dark.png",
"maestro": $card-icons-base + "maestro-dark.png",
"mastercard": $card-icons-base + "mastercard-dark.png",
"union-pay": $card-icons-base + "union_pay-dark.png",
);
.credit-card-icon {
display: block; // Resolves the parent container being slighly to big
height: 19px;
width: 24px;
background-size: contain;
background-repeat: no-repeat;
}
@each $name, $url in $card-icons {
.card-#{$name} {
background-image: url("#{$url}");
}
}
@each $theme in $dark-icon-themes {
@each $name, $url in $card-icons-dark {
.#{$theme} .card-#{$name} {
background-image: url("#{$url}");
}
}
}

View File

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

View File

@@ -172,7 +172,11 @@ export class OnePassword1PifImporter extends BaseImporter implements Importer {
return; return;
} }
const fieldValue = field[valueKey].toString(); // TODO: when date FieldType exists, store this as a date field type instead of formatted Text if k is 'date'
const fieldValue =
field.k === "date"
? new Date(field[valueKey] * 1000).toUTCString()
: field[valueKey].toString();
const fieldDesignation = const fieldDesignation =
field[designationKey] != null ? field[designationKey].toString() : null; field[designationKey] != null ? field[designationKey].toString() : null;

View File

@@ -95,9 +95,6 @@ export class AccountProfile {
hasPremiumPersonally?: boolean; hasPremiumPersonally?: boolean;
lastActive?: number; lastActive?: number;
lastSync?: string; lastSync?: string;
ssoCodeVerifier?: string;
ssoOrganizationIdentifier?: string;
ssoState?: string;
userId?: string; userId?: string;
usesKeyConnector?: boolean; usesKeyConnector?: boolean;
keyHash?: string; keyHash?: string;
@@ -133,9 +130,6 @@ export class AccountSettings {
enableMinimizeToTray?: boolean; enableMinimizeToTray?: boolean;
enableStartToTray?: boolean; enableStartToTray?: boolean;
enableTray?: boolean; enableTray?: boolean;
environmentUrls?: any = {
server: "bitwarden.com",
};
equivalentDomains?: any; equivalentDomains?: any;
minimizeOnCopyToClipboard?: boolean; minimizeOnCopyToClipboard?: boolean;
neverDomains?: { [id: string]: any }; neverDomains?: { [id: string]: any };

View File

@@ -5,6 +5,9 @@ export class GlobalState {
locale?: string; locale?: string;
openAtLogin?: boolean; openAtLogin?: boolean;
organizationInvitation?: any; organizationInvitation?: any;
ssoCodeVerifier?: string;
ssoOrganizationIdentifier?: string;
ssoState?: string;
rememberedEmail?: string; rememberedEmail?: string;
theme?: string; theme?: string;
window?: Map<string, any> = new Map<string, any>(); window?: Map<string, any> = new Map<string, any>();
@@ -21,4 +24,7 @@ export class GlobalState {
noAutoPromptBiometrics?: boolean; noAutoPromptBiometrics?: boolean;
noAutoPromptBiometricsText?: string; noAutoPromptBiometricsText?: string;
stateVersion: number; stateVersion: number;
environmentUrls?: any = {
server: "bitwarden.com",
};
} }

View File

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

View File

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

View File

@@ -761,13 +761,13 @@ export class CryptoService implements CryptoServiceAbstraction {
// Helpers // Helpers
protected async storeKey(key: SymmetricCryptoKey, userId?: string) { protected async storeKey(key: SymmetricCryptoKey, userId?: string) {
if ( if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) {
(await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) || await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId });
(await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) } else if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) {
) { await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId });
await this.stateService.setCryptoMasterKeyB64(key.keyB64, { userId: userId });
} else { } 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> { async setUrlsFromStorage(): Promise<void> {
const urlsObj: any = await this.stateService.getEnvironmentUrls(); const urls: 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 envUrls = new EnvironmentUrls(); const envUrls = new EnvironmentUrls();
if (urls.base) { if (urls.base) {

View File

@@ -1,6 +1,12 @@
import { StateService as StateServiceAbstraction } from "../abstractions/state.service"; 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 { LogService } from "../abstractions/log.service";
import { StorageService } from "../abstractions/storage.service"; import { StorageService } from "../abstractions/storage.service";
@@ -34,19 +40,21 @@ import { SendData } from "../models/data/sendData";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { StateMigrationService } from "./stateMigration.service"; import { StateMigrationService } from "../abstractions/stateMigration.service";
export class StateService implements StateServiceAbstraction { export class StateService<TAccount extends Account = Account>
accounts = new BehaviorSubject<{ [userId: string]: Account }>({}); implements StateServiceAbstraction<TAccount>
{
accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({});
activeAccount = new BehaviorSubject<string>(null); activeAccount = new BehaviorSubject<string>(null);
private state: State = new State(); protected state: State<TAccount> = new State<TAccount>();
constructor( constructor(
private storageService: StorageService, protected storageService: StorageService,
private secureStorageService: StorageService, protected secureStorageService: StorageService,
private logService: LogService, protected logService: LogService,
private stateMigrationService: StateMigrationService protected stateMigrationService: StateMigrationService
) {} ) {}
async init(): Promise<void> { async init(): Promise<void> {
@@ -60,7 +68,7 @@ export class StateService implements StateServiceAbstraction {
async loadStateFromDisk() { async loadStateFromDisk() {
if ((await this.getActiveUserIdFromStorage()) != null) { if ((await this.getActiveUserIdFromStorage()) != null) {
const diskState = await this.storageService.get<State>( const diskState = await this.storageService.get<State<TAccount>>(
"state", "state",
await this.defaultOnDiskOptions() await this.defaultOnDiskOptions()
); );
@@ -71,10 +79,7 @@ export class StateService implements StateServiceAbstraction {
} }
} }
async addAccount(account: Account) { async addAccount(account: TAccount) {
if (account?.profile?.userId == null) {
return;
}
this.state.accounts[account.profile.userId] = account; this.state.accounts[account.profile.userId] = account;
await this.scaffoldNewAccountStorage(account); await this.scaffoldNewAccountStorage(account);
await this.setActiveUser(account.profile.userId); await this.setActiveUser(account.profile.userId);
@@ -83,7 +88,7 @@ export class StateService implements StateServiceAbstraction {
async setActiveUser(userId: string): Promise<void> { async setActiveUser(userId: string): Promise<void> {
this.state.activeUserId = userId; this.state.activeUserId = userId;
const storedState = await this.storageService.get<State>( const storedState = await this.storageService.get<State<TAccount>>(
"state", "state",
await this.defaultOnDiskOptions() await this.defaultOnDiskOptions()
); );
@@ -1321,47 +1326,64 @@ export class StateService implements StateServiceAbstraction {
} }
async getEntityId(options?: StorageOptions): Promise<string> { async getEntityId(options?: StorageOptions): Promise<string> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) return (
?.profile?.entityId; await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.profile?.entityId;
} }
async setEntityId(value: string, options?: StorageOptions): Promise<void> { async setEntityId(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions) this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
); );
account.profile.entityId = value; 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> { async getEntityType(options?: StorageOptions): Promise<any> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) return (
?.profile?.entityType; await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.profile?.entityType;
} }
async setEntityType(value: string, options?: StorageOptions): Promise<void> { async setEntityType(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions) this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
); );
account.profile.entityType = value; 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> { async getEnvironmentUrls(options?: StorageOptions): Promise<any> {
return ( return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.settings?.environmentUrls ?? { ?.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", server: "bitwarden.com",
} }
); );
} }
async setEnvironmentUrls(value: any, options?: StorageOptions): Promise<void> { async setEnvironmentUrls(value: any, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskOptions()) this.reconcileOptions(options, await this.defaultOnDiskOptions())
); );
account.settings.environmentUrls = value; globals.environmentUrls = value;
await this.saveAccount( await this.saveGlobals(
account, globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions()) this.reconcileOptions(options, await this.defaultOnDiskOptions())
); );
} }
@@ -1402,17 +1424,20 @@ export class StateService implements StateServiceAbstraction {
async getEverBeenUnlocked(options?: StorageOptions): Promise<boolean> { async getEverBeenUnlocked(options?: StorageOptions): Promise<boolean> {
return ( return (
(await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.everBeenUnlocked ?? false ?.profile?.everBeenUnlocked ?? false
); );
} }
async setEverBeenUnlocked(value: boolean, options?: StorageOptions): Promise<void> { async setEverBeenUnlocked(value: boolean, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions) this.reconcileOptions(options, await this.defaultOnDiskOptions())
); );
account.profile.everBeenUnlocked = value; 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> { async getForcePasswordReset(options?: StorageOptions): Promise<boolean> {
@@ -1874,46 +1899,54 @@ export class StateService implements StateServiceAbstraction {
} }
async getSsoCodeVerifier(options?: StorageOptions): Promise<string> { async getSsoCodeVerifier(options?: StorageOptions): Promise<string> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) return (
?.profile?.ssoCodeVerifier; await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.ssoCodeVerifier;
} }
async setSsoCodeVerifier(value: string, options?: StorageOptions): Promise<void> { async setSsoCodeVerifier(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const globals = await this.getGlobals(
this.reconcileOptions(options, this.defaultInMemoryOptions) this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
globals.ssoCodeVerifier = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
); );
account.profile.ssoCodeVerifier = value;
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
} }
async getSsoOrgIdentifier(options?: StorageOptions): Promise<string> { async getSsoOrgIdentifier(options?: StorageOptions): Promise<string> {
return ( return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.profile?.ssoOrganizationIdentifier; )?.ssoOrganizationIdentifier;
} }
async setSsoOrganizationIdentifier(value: string, options?: StorageOptions): Promise<void> { async setSsoOrganizationIdentifier(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
); );
account.profile.ssoOrganizationIdentifier = value; globals.ssoOrganizationIdentifier = value;
await this.saveAccount( await this.saveGlobals(
account, globals,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
); );
} }
async getSsoState(options?: StorageOptions): Promise<string> { async getSsoState(options?: StorageOptions): Promise<string> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) return (
?.profile?.ssoState; await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.ssoState;
} }
async setSsoState(value: string, options?: StorageOptions): Promise<void> { async setSsoState(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const globals = await this.getGlobals(
this.reconcileOptions(options, this.defaultInMemoryOptions) this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
globals.ssoState = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
); );
account.profile.ssoState = value;
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
} }
async getTheme(options?: StorageOptions): Promise<string> { async getTheme(options?: StorageOptions): Promise<string> {
@@ -1973,10 +2006,7 @@ export class StateService implements StateServiceAbstraction {
const accountVaultTimeout = ( const accountVaultTimeout = (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.settings?.vaultTimeout; )?.settings?.vaultTimeout;
const globalVaultTimeout = ( return accountVaultTimeout;
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.vaultTimeout;
return accountVaultTimeout ?? globalVaultTimeout ?? 15;
} }
async setVaultTimeout(value: number, options?: StorageOptions): Promise<void> { async setVaultTimeout(value: number, options?: StorageOptions): Promise<void> {
@@ -2039,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; let globals: GlobalState;
if (this.useMemory(options.storageLocation)) { if (this.useMemory(options.storageLocation)) {
globals = this.getGlobalsFromMemory(); globals = this.getGlobalsFromMemory();
@@ -2052,39 +2082,41 @@ export class StateService implements StateServiceAbstraction {
return globals ?? new GlobalState(); return globals ?? new GlobalState();
} }
private async saveGlobals(globals: GlobalState, options: StorageOptions) { protected async saveGlobals(globals: GlobalState, options: StorageOptions) {
return this.useMemory(options.storageLocation) return this.useMemory(options.storageLocation)
? this.saveGlobalsToMemory(globals) ? this.saveGlobalsToMemory(globals)
: await this.saveGlobalsToDisk(globals, options); : await this.saveGlobalsToDisk(globals, options);
} }
private getGlobalsFromMemory(): GlobalState { protected getGlobalsFromMemory(): GlobalState {
return this.state.globals; return this.state.globals;
} }
private async getGlobalsFromDisk(options: StorageOptions): Promise<GlobalState> { protected async getGlobalsFromDisk(options: StorageOptions): Promise<GlobalState> {
return (await this.storageService.get<State>("state", options))?.globals; return (await this.storageService.get<State<TAccount>>("state", options))?.globals;
} }
private saveGlobalsToMemory(globals: GlobalState): void { protected saveGlobalsToMemory(globals: GlobalState): void {
this.state.globals = globals; 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) { 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; state.globals = globals;
await this.secureStorageService.save("state", state, options); await this.secureStorageService.save("state", state, options);
} else { } 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; state.globals = globals;
await this.saveStateToStorage(state, options); await this.saveStateToStorage(state, options);
} }
} }
private async getAccount(options: StorageOptions): Promise<Account> { protected async getAccount(options: StorageOptions): Promise<TAccount> {
try { try {
let account: Account; let account: TAccount;
if (this.useMemory(options.storageLocation)) { if (this.useMemory(options.storageLocation)) {
account = this.getAccountFromMemory(options); account = this.getAccountFromMemory(options);
} }
@@ -2093,51 +2125,51 @@ export class StateService implements StateServiceAbstraction {
account = await this.getAccountFromDisk(options); account = await this.getAccountFromDisk(options);
} }
return account != null ? new Account(account) : null; return account;
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
} }
private getAccountFromMemory(options: StorageOptions): Account { protected getAccountFromMemory(options: StorageOptions): TAccount {
if (this.state.accounts == null) { if (this.state.accounts == null) {
return null; return null;
} }
return this.state.accounts[this.getUserIdFromMemory(options)]; return this.state.accounts[this.getUserIdFromMemory(options)];
} }
private getUserIdFromMemory(options: StorageOptions): string { protected getUserIdFromMemory(options: StorageOptions): string {
return options?.userId != null return options?.userId != null
? this.state.accounts[options.userId]?.profile?.userId ? this.state.accounts[options.userId]?.profile?.userId
: this.state.activeUserId; : 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) { if (options?.userId == null && this.state.activeUserId == null) {
return null; return null;
} }
const state = options?.useSecureStorage const state = options?.useSecureStorage
? (await this.secureStorageService.get<State>("state", options)) ?? ? (await this.secureStorageService.get<State<TAccount>>("state", options)) ??
(await this.storageService.get<State>( (await this.storageService.get<State<TAccount>>(
"state", "state",
this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local }) 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]; return state?.accounts[options?.userId ?? this.state.activeUserId];
} }
private useMemory(storageLocation: StorageLocation) { protected useMemory(storageLocation: StorageLocation) {
return storageLocation === StorageLocation.Memory || storageLocation === StorageLocation.Both; return storageLocation === StorageLocation.Memory || storageLocation === StorageLocation.Both;
} }
private useDisk(storageLocation: StorageLocation) { protected useDisk(storageLocation: StorageLocation) {
return storageLocation === StorageLocation.Disk || storageLocation === StorageLocation.Both; return storageLocation === StorageLocation.Disk || storageLocation === StorageLocation.Both;
} }
private async saveAccount( protected async saveAccount(
account: Account, account: TAccount,
options: StorageOptions = { options: StorageOptions = {
storageLocation: StorageLocation.Both, storageLocation: StorageLocation.Both,
useSecureStorage: false, useSecureStorage: false,
@@ -2148,86 +2180,75 @@ export class StateService implements StateServiceAbstraction {
: await this.saveAccountToDisk(account, options); : 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 const storageLocation = options.useSecureStorage
? this.secureStorageService ? this.secureStorageService
: this.storageService; : 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; state.accounts[account.profile.userId] = account;
await storageLocation.save("state", state, options); await storageLocation.save("state", state, options);
await this.pushAccounts(); 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) { if (this.getAccountFromMemory({ userId: account.profile.userId }) !== null) {
this.state.accounts[account.profile.userId] = account; this.state.accounts[account.profile.userId] = account;
} }
await this.pushAccounts(); await this.pushAccounts();
} }
private async scaffoldNewAccountStorage(account: Account): Promise<void> { protected async scaffoldNewAccountStorage(account: TAccount): Promise<void> {
await this.scaffoldNewAccountLocalStorage(account); await this.scaffoldNewAccountLocalStorage(account);
await this.scaffoldNewAccountSessionStorage(account); await this.scaffoldNewAccountSessionStorage(account);
await this.scaffoldNewAccountMemoryStorage(account); await this.scaffoldNewAccountMemoryStorage(account);
} }
private async scaffoldNewAccountLocalStorage(account: Account): Promise<void> { protected async scaffoldNewAccountLocalStorage(account: TAccount): Promise<void> {
const storedState = const storedState =
(await this.storageService.get<State>("state", await this.defaultOnDiskLocalOptions())) ?? (await this.storageService.get<State<TAccount>>(
new State(); "state",
await this.defaultOnDiskLocalOptions()
)) ?? new State<TAccount>();
const storedAccount = storedState.accounts[account.profile.userId]; const storedAccount = storedState.accounts[account.profile.userId];
if (storedAccount != null) { if (storedAccount != null) {
account = { account.settings = storedAccount.settings;
settings: storedAccount.settings,
profile: account.profile,
tokens: account.tokens,
keys: account.keys,
data: account.data,
};
} }
storedState.accounts[account.profile.userId] = account; storedState.accounts[account.profile.userId] = account;
await this.saveStateToStorage(storedState, await this.defaultOnDiskLocalOptions()); await this.saveStateToStorage(storedState, await this.defaultOnDiskLocalOptions());
} }
private async scaffoldNewAccountMemoryStorage(account: Account): Promise<void> { protected async scaffoldNewAccountMemoryStorage(account: TAccount): Promise<void> {
const storedState = const storedState =
(await this.storageService.get<State>("state", await this.defaultOnDiskMemoryOptions())) ?? (await this.storageService.get<State<TAccount>>(
new State(); "state",
await this.defaultOnDiskMemoryOptions()
)) ?? new State<TAccount>();
const storedAccount = storedState.accounts[account.profile.userId]; const storedAccount = storedState.accounts[account.profile.userId];
if (storedAccount != null) { if (storedAccount != null) {
account = { account.settings = storedAccount.settings;
settings: storedAccount.settings,
profile: account.profile,
tokens: account.tokens,
keys: account.keys,
data: account.data,
};
} }
storedState.accounts[account.profile.userId] = account; storedState.accounts[account.profile.userId] = account;
await this.saveStateToStorage(storedState, await this.defaultOnDiskMemoryOptions()); await this.saveStateToStorage(storedState, await this.defaultOnDiskMemoryOptions());
} }
private async scaffoldNewAccountSessionStorage(account: Account): Promise<void> { protected async scaffoldNewAccountSessionStorage(account: TAccount): Promise<void> {
const storedState = const storedState =
(await this.storageService.get<State>("state", await this.defaultOnDiskOptions())) ?? (await this.storageService.get<State<TAccount>>(
new State(); "state",
await this.defaultOnDiskOptions()
)) ?? new State<TAccount>();
const storedAccount = storedState.accounts[account.profile.userId]; const storedAccount = storedState.accounts[account.profile.userId];
if (storedAccount != null) { if (storedAccount != null) {
account = { account.settings = storedAccount.settings;
settings: storedAccount.settings,
profile: account.profile,
tokens: account.tokens,
keys: account.keys,
data: account.data,
};
} }
storedState.accounts[account.profile.userId] = account; storedState.accounts[account.profile.userId] = account;
await this.saveStateToStorage(storedState, await this.defaultOnDiskOptions()); await this.saveStateToStorage(storedState, await this.defaultOnDiskOptions());
} }
private async pushAccounts(): Promise<void> { protected async pushAccounts(): Promise<void> {
await this.pruneInMemoryAccounts(); await this.pruneInMemoryAccounts();
if (this.state?.accounts == null || Object.keys(this.state.accounts).length < 1) { if (this.state?.accounts == null || Object.keys(this.state.accounts).length < 1) {
this.accounts.next(null); this.accounts.next(null);
@@ -2237,7 +2258,7 @@ export class StateService implements StateServiceAbstraction {
this.accounts.next(this.state.accounts); this.accounts.next(this.state.accounts);
} }
private reconcileOptions( protected reconcileOptions(
requestedOptions: StorageOptions, requestedOptions: StorageOptions,
defaultOptions: StorageOptions defaultOptions: StorageOptions
): StorageOptions { ): StorageOptions {
@@ -2255,11 +2276,11 @@ export class StateService implements StateServiceAbstraction {
return requestedOptions; return requestedOptions;
} }
private get defaultInMemoryOptions(): StorageOptions { protected get defaultInMemoryOptions(): StorageOptions {
return { storageLocation: StorageLocation.Memory, userId: this.state.activeUserId }; return { storageLocation: StorageLocation.Memory, userId: this.state.activeUserId };
} }
private async defaultOnDiskOptions(): Promise<StorageOptions> { protected async defaultOnDiskOptions(): Promise<StorageOptions> {
return { return {
storageLocation: StorageLocation.Disk, storageLocation: StorageLocation.Disk,
htmlStorageLocation: HtmlStorageLocation.Session, htmlStorageLocation: HtmlStorageLocation.Session,
@@ -2268,7 +2289,7 @@ export class StateService implements StateServiceAbstraction {
}; };
} }
private async defaultOnDiskLocalOptions(): Promise<StorageOptions> { protected async defaultOnDiskLocalOptions(): Promise<StorageOptions> {
return { return {
storageLocation: StorageLocation.Disk, storageLocation: StorageLocation.Disk,
htmlStorageLocation: HtmlStorageLocation.Local, htmlStorageLocation: HtmlStorageLocation.Local,
@@ -2277,7 +2298,7 @@ export class StateService implements StateServiceAbstraction {
}; };
} }
private async defaultOnDiskMemoryOptions(): Promise<StorageOptions> { protected async defaultOnDiskMemoryOptions(): Promise<StorageOptions> {
return { return {
storageLocation: StorageLocation.Disk, storageLocation: StorageLocation.Disk,
htmlStorageLocation: HtmlStorageLocation.Memory, htmlStorageLocation: HtmlStorageLocation.Memory,
@@ -2286,7 +2307,7 @@ export class StateService implements StateServiceAbstraction {
}; };
} }
private async defaultSecureStorageOptions(): Promise<StorageOptions> { protected async defaultSecureStorageOptions(): Promise<StorageOptions> {
return { return {
storageLocation: StorageLocation.Disk, storageLocation: StorageLocation.Disk,
useSecureStorage: true, useSecureStorage: true,
@@ -2294,46 +2315,38 @@ export class StateService implements StateServiceAbstraction {
}; };
} }
private async getActiveUserIdFromStorage(): Promise<string> { protected async getActiveUserIdFromStorage(): Promise<string> {
const state = await this.storageService.get<State>("state"); const state = await this.storageService.get<State<TAccount>>("state");
return state?.activeUserId; return state?.activeUserId;
} }
private async removeAccountFromLocalStorage( protected async removeAccountFromLocalStorage(
userId: string = this.state.activeUserId userId: string = this.state.activeUserId
): Promise<void> { ): Promise<void> {
const state = await this.storageService.get<State>("state", { const state = await this.storageService.get<State<TAccount>>("state", {
htmlStorageLocation: HtmlStorageLocation.Local, htmlStorageLocation: HtmlStorageLocation.Local,
}); });
if (state?.accounts[userId] == null) { if (state?.accounts[userId] == null) {
return; return;
} }
state.accounts[userId] = this.resetAccount(state.accounts[userId]);
state.accounts[userId] = new Account({
settings: state.accounts[userId].settings,
});
await this.saveStateToStorage(state, await this.defaultOnDiskLocalOptions()); await this.saveStateToStorage(state, await this.defaultOnDiskLocalOptions());
} }
private async removeAccountFromSessionStorage( protected async removeAccountFromSessionStorage(
userId: string = this.state.activeUserId userId: string = this.state.activeUserId
): Promise<void> { ): Promise<void> {
const state = await this.storageService.get<State>("state", { const state = await this.storageService.get<State<TAccount>>("state", {
htmlStorageLocation: HtmlStorageLocation.Session, htmlStorageLocation: HtmlStorageLocation.Session,
}); });
if (state?.accounts[userId] == null) { if (state?.accounts[userId] == null) {
return; return;
} }
state.accounts[userId] = this.resetAccount(state.accounts[userId]);
state.accounts[userId] = new Account({
settings: state.accounts[userId].settings,
});
await this.saveStateToStorage(state, await this.defaultOnDiskOptions()); await this.saveStateToStorage(state, await this.defaultOnDiskOptions());
} }
private async removeAccountFromSecureStorage( protected async removeAccountFromSecureStorage(
userId: string = this.state.activeUserId userId: string = this.state.activeUserId
): Promise<void> { ): Promise<void> {
await this.setCryptoMasterKeyAuto(null, { userId: userId }); await this.setCryptoMasterKeyAuto(null, { userId: userId });
@@ -2341,15 +2354,18 @@ export class StateService implements StateServiceAbstraction {
await this.setCryptoMasterKeyB64(null, { userId: userId }); 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]; 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); 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 // 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) { for (const userId in this.state.accounts) {
if (!(await this.getIsAuthenticated({ userId: userId }))) { if (!(await this.getIsAuthenticated({ userId: userId }))) {
@@ -2357,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; readonly latestVersion: number = 2;
constructor( constructor(
private storageService: StorageService, protected storageService: StorageService,
private secureStorageService: StorageService protected secureStorageService: StorageService
) {} ) {}
async needsMigration(): Promise<boolean> { async needsMigration(): Promise<boolean> {
const currentStateVersion = (await this.storageService.get<State>("state"))?.globals const currentStateVersion = (
?.stateVersion; await this.storageService.get<State<Account>>("state", {
htmlStorageLocation: HtmlStorageLocation.Local,
})
)?.globals?.stateVersion;
return currentStateVersion == null || currentStateVersion < this.latestVersion; return currentStateVersion == null || currentStateVersion < this.latestVersion;
} }
async migrate(): Promise<void> { async migrate(): Promise<void> {
let currentStateVersion = 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) { while (currentStateVersion < this.latestVersion) {
switch (currentStateVersion) { switch (currentStateVersion) {
case 1: 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 options: StorageOptions = { htmlStorageLocation: HtmlStorageLocation.Local };
const userId = await this.storageService.get<string>("userId"); const userId = await this.storageService.get<string>("userId");
const initialState: State = const initialState: State<Account> =
userId == null userId == null
? { ? {
globals: { globals: {
@@ -174,6 +177,7 @@ export class StateMigrationService {
v1Keys.enableBiometric, v1Keys.enableBiometric,
options options
), ),
environmentUrls: await this.storageService.get<any>(v1Keys.environmentUrls, options),
installedVersion: await this.storageService.get<string>( installedVersion: await this.storageService.get<string>(
v1Keys.installedVersion, v1Keys.installedVersion,
options options
@@ -192,6 +196,15 @@ export class StateMigrationService {
), ),
openAtLogin: await this.storageService.get<boolean>(v1Keys.openAtLogin, options), openAtLogin: await this.storageService.get<boolean>(v1Keys.openAtLogin, options),
organizationInvitation: await this.storageService.get<string>("", options), organizationInvitation: await this.storageService.get<string>("", options),
ssoCodeVerifier: await this.storageService.get<string>(
v1Keys.ssoCodeVerifier,
options
),
ssoOrganizationIdentifier: await this.storageService.get<string>(
v1Keys.ssoIdentifier,
options
),
ssoState: null,
rememberedEmail: await this.storageService.get<string>( rememberedEmail: await this.storageService.get<string>(
v1Keys.rememberedEmail, v1Keys.rememberedEmail,
options options
@@ -327,15 +340,6 @@ export class StateMigrationService {
keyHash: await this.storageService.get<string>(v1Keys.keyHash, options), keyHash: await this.storageService.get<string>(v1Keys.keyHash, options),
lastActive: await this.storageService.get<number>(v1Keys.lastActive, options), lastActive: await this.storageService.get<number>(v1Keys.lastActive, options),
lastSync: null, lastSync: null,
ssoCodeVerifier: await this.storageService.get<string>(
v1Keys.ssoCodeVerifier,
options
),
ssoOrganizationIdentifier: await this.storageService.get<string>(
v1Keys.ssoIdentifier,
options
),
ssoState: null,
userId: userId, userId: userId,
usesKeyConnector: null, usesKeyConnector: null,
}, },
@@ -439,10 +443,6 @@ export class StateMigrationService {
options options
), ),
enableTray: await this.storageService.get<boolean>(v1Keys.enableTray, 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>( equivalentDomains: await this.storageService.get<any>(
v1Keys.equivalentDomains, v1Keys.equivalentDomains,
options options

View File

@@ -53,7 +53,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
async isLocked(userId?: string): Promise<boolean> { async isLocked(userId?: string): Promise<boolean> {
const neverLock = const neverLock =
(await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId)) && (await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId)) &&
!(await this.stateService.getEverBeenUnlocked({ userId: userId })); (await this.stateService.getEverBeenUnlocked({ userId: userId }));
if (neverLock) { if (neverLock) {
// TODO: This also _sets_ the key so when we check memory in the next line it finds a key. // TODO: This also _sets_ the key so when we check memory in the next line it finds a key.
// We should refactor here. // We should refactor here.

View File

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

View File

@@ -476,6 +476,19 @@ describe("1Password 1Pif Importer", () => {
// remaining fields as custom fields // remaining fields as custom fields
expect(cipher.fields.length).toEqual(6); expect(cipher.fields.length).toEqual(6);
const fields = cipher.fields;
expect(fields[0].name).toEqual("sex");
expect(fields[0].value).toEqual("male");
expect(fields[1].name).toEqual("birth date");
expect(fields[1].value).toEqual("Mon, 11 Mar 2019 12:01:00 GMT");
expect(fields[2].name).toEqual("occupation");
expect(fields[2].value).toEqual("Engineer");
expect(fields[3].name).toEqual("department");
expect(fields[3].value).toEqual("IT");
expect(fields[4].name).toEqual("job title");
expect(fields[4].value).toEqual("Developer");
expect(fields[5].name).toEqual("home");
expect(fields[5].value).toEqual("+49 333 222 111");
}); });
it("should create password history", async () => { it("should create password history", async () => {