1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-24 04:04:38 +00:00

[refactor] Split up the Account model

Until this point the account model has been very flat, holding many kinds of data.

In order to be able to prune data at appropriate times, for example clearing keys at logout without clearing QoL settings like locale,
the Account model has been divided into logical chunks.
This commit is contained in:
addison
2021-11-17 13:18:07 -05:00
parent ea662e1eac
commit 55691be56b
4 changed files with 387 additions and 350 deletions

View File

@@ -22,133 +22,154 @@ import { PolicyData } from '../data/policyData';
import { ProviderData } from '../data/providerData';
import { SendData } from '../data/sendData';
export class Account {
userId: string;
email: string;
accessToken: string;
decodedToken: any;
apiKeyClientId: string;
apiKeyClientSecret: string;
alwaysShowDock: boolean;
autoFillOnPageLoadDefault: boolean;
encryptedCiphers: { [id: string]: CipherData };
decryptedCiphers: CipherView[];
cryptoMasterKey: SymmetricCryptoKey;
cryptoMasterKeyBiometric: SymmetricCryptoKey;
cryptoMasterKeyAuto: SymmetricCryptoKey;
cryptoMasterKeyB64: string;
encryptedCryptoSymmetricKey: string;
decryptedCryptoSymmetricKey: SymmetricCryptoKey;
defaultUriMatch: UriMatchType;
disableAddLoginNotification: boolean;
disableAutoTotpCopy: boolean;
disableBadgeCounter: boolean;
disableChangedPasswordNotification: boolean;
disableContextMenuItem: boolean;
disableGa: boolean;
dontShowCardsCurrentTab: boolean;
dontShowIdentitiesCurrentTab: boolean;
emailVerified: boolean;
enableAlwaysOnTop: boolean;
enableAutoFillOnPageLoad: boolean;
enableBrowserIntegration: boolean;
enableBrowserIntegrationFingerprint: boolean;
enableCloseToTray: boolean;
enableMinimizeToTray: boolean;
enableStartToTray: boolean;
enableTray: boolean;
decryptedOrganizationKeys: Map<string, SymmetricCryptoKey>;
encryptedOrganizationKeys: any;
decryptedProviderKeys: Map<string, SymmetricCryptoKey>;
encryptedProviderKeys: any;
entityId: string;
entityType: string;
environmentUrls: any;
equivalentDomains: any;
eventCollection: EventData[];
encryptedFolders: { [id: string]: FolderData };
decryptedFolders: FolderView[];
forcePasswordReset: boolean;
installedVersion: string;
kdfIterations: number;
kdfType: KdfType;
keyHash: string;
lastActive: number;
lastSync: string;
legacyEtmKey: SymmetricCryptoKey;
localData: any;
loginRedirect: any;
mainWindowSize: number;
minimizeOnCopyToClipboard: boolean;
neverDomains: { [id: string]: any };
openAtLogin: boolean;
encryptedPasswordGenerationHistory: GeneratedPasswordHistory[];
decryptedPasswordGenerationHistory: GeneratedPasswordHistory[];
passwordGenerationOptions: any;
encryptedPinProtected: string;
decryptedPinProtected: EncString;
protectedPin: string;
encryptedPolicies: { [id: string]: PolicyData };
decryptedPolicies: Policy[];
providers: { [id: string]: ProviderData };
publicKey: ArrayBuffer;
refreshToken: string;
rememberedEmail: string;
securityStamp: string;
encryptedSends: { [id: string]: SendData };
decryptedSends: SendView[];
settings: any;
ssoCodeVerifier: string;
ssoState: string;
ssoOrganizationIdentifier: string;
vaultTimeout: number;
vaultTimeoutAction: string;
clearClipboard: number;
collapsedGroupings: Set<string>;
encryptedCollections: { [id: string]: CollectionData };
decryptedCollections: CollectionView[];
encryptedPrivateKey: string;
decryptedPrivateKey: ArrayBuffer;
locale: string;
organizations: { [id: string]: OrganizationData };
everBeenUnlocked: boolean;
enableGravitars: boolean;
addEditCipherInfo: any;
authenticationStatus: AuthenticationStatus;
autoConfirmFingerPrints: boolean;
disableAutoBiometricsPrompt: boolean;
noAutoPromptBiometrics: boolean;
biometricLocked: boolean;
biometricUnlock: boolean;
biometricText: string;
enableBiometric: boolean;
enableBiometrics: boolean;
noAutoPromptBiometricsText: string;
convertAccountToKeyConnector: boolean;
usesKeyConnector: boolean;
enableFullWidth: boolean;
hasPremiumPersonally: boolean;
export class EncryptionPair<TEncrypted, TDecrypted> {
encrypted: TEncrypted;
decrypted: TDecrypted;
}
constructor(userId: string, userEmail: string,
kdfType: KdfType, kdfIterations: number,
clientId: string, clientSecret: string,
accessToken: string, refreshToken: string,
hasPremium: boolean) {
this.userId = userId;
this.email = userEmail;
this.kdfType = kdfType;
this.kdfIterations = kdfIterations;
this.apiKeyClientId = clientId;
this.apiKeyClientSecret = clientSecret;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.hasPremiumPersonally = hasPremium;
this.vaultTimeoutAction = 'lock';
this.vaultTimeout = 15;
}
export class DataEncryptionPair<TEncrypted, TDecrypted> {
encrypted: { [id: string]: TEncrypted };
decrypted: TDecrypted[];
}
export class AccountData {
ciphers?: DataEncryptionPair<CipherData, CipherView> = new DataEncryptionPair<CipherData, CipherView>();
folders?: DataEncryptionPair<FolderData, FolderView> = new DataEncryptionPair<FolderData, FolderView>();
localData?: any;
sends?: DataEncryptionPair<SendData, SendView> = new DataEncryptionPair<SendData, SendView>();
collections?: DataEncryptionPair<CollectionData, CollectionView> = new DataEncryptionPair<CollectionData, CollectionView>();
kdfIterations?: number;
kdfType?: KdfType;
policies?: DataEncryptionPair<PolicyData, Policy> = new DataEncryptionPair<PolicyData, Policy>();
passwordGenerationHistory?: EncryptionPair<GeneratedPasswordHistory[], GeneratedPasswordHistory[]> = new EncryptionPair<GeneratedPasswordHistory[], GeneratedPasswordHistory[]>();
addEditCipherInfo?: any;
collapsedGroupings?: Set<string>;
eventCollection?: EventData[];
organizations?: { [id: string]: OrganizationData };
providers?: { [id: string]: ProviderData };
}
export class AccountKeys {
cryptoMasterKey?: SymmetricCryptoKey;
cryptoMasterKeyAuto?: SymmetricCryptoKey;
cryptoMasterKeyB64?: string;
cryptoMasterKeyBiometric?: SymmetricCryptoKey;
cryptoSymmetricKey?: EncryptionPair<string, SymmetricCryptoKey> = new EncryptionPair<string, SymmetricCryptoKey>();
organizationKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<any, Map<string, SymmetricCryptoKey>>();
privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>();
providerKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<any, Map<string, SymmetricCryptoKey>>();
keyHash?: string;
legacyEtmKey?: SymmetricCryptoKey;
publicKey?: ArrayBuffer;
}
export class AccountProfile {
apiKeyClientId?: string;
apiKeyClientSecret?: string;
authenticationStatus?: AuthenticationStatus;
convertAccountToKeyConnector?: boolean;
email?: string;
emailVerified?: boolean;
entityId?: string;
entityType?: string;
everBeenUnlocked?: boolean;
forcePasswordReset?: boolean;
hasPremiumPersonally?: boolean;
lastActive?: number;
lastSync?: string;
ssoCodeVerifier?: string;
ssoOrganizationIdentifier?: string;
ssoState?: string;
userId?: string;
usesKeyConnector?: boolean;
}
export class AccountSettings {
alwaysShowDock?: boolean;
autoConfirmFingerPrints?: boolean;
autoFillOnPageLoadDefault?: boolean;
biometricLocked?: boolean;
biometricText?: string;
biometricUnlock?: boolean;
clearClipboard?: number;
defaultUriMatch?: UriMatchType;
disableAddLoginNotification?: boolean;
disableAutoBiometricsPrompt?: boolean;
disableAutoTotpCopy?: boolean;
disableBadgeCounter?: boolean;
disableChangedPasswordNotification?: boolean;
disableContextMenuItem?: boolean;
disableGa?: boolean;
dontShowCardsCurrentTab?: boolean;
dontShowIdentitiesCurrentTab?: boolean;
enableAlwaysOnTop?: boolean;
enableAutoFillOnPageLoad?: boolean;
enableBiometric?: boolean;
enableBiometrics?: boolean;
enableBrowserIntegration?: boolean;
enableBrowserIntegrationFingerprint?: boolean;
enableCloseToTray?: boolean;
enableFullWidth?: boolean;
enableGravitars?: boolean;
enableMinimizeToTray?: boolean;
enableStartToTray?: boolean;
enableTray?: boolean;
environmentUrls?: any;
equivalentDomains?: any;
locale?: string;
minimizeOnCopyToClipboard?: boolean;
neverDomains?: { [id: string]: any };
noAutoPromptBiometrics?: boolean;
noAutoPromptBiometricsText?: string;
openAtLogin?: boolean;
passwordGenerationOptions?: any;
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>();
protectedPin?: string;
settings?: any; // TODO: Merge whatever is going on here into the AccountSettings model properly
vaultTimeout?: number;
vaultTimeoutAction?: string;
get serverUrl(): string {
return this.environmentUrls?.base ?? 'bitwarden.com';
return this.settings.environmentUrls?.base ?? 'bitwarden.com';
}
}
export class AccountTokens {
accessToken?: string;
decodedToken?: any;
refreshToken?: string;
securityStamp?: string;
}
export class Account {
data?: AccountData = new AccountData();
keys?: AccountKeys = new AccountKeys();
profile?: AccountProfile = new AccountProfile();
settings?: AccountSettings = new AccountSettings();
tokens?: AccountTokens = new AccountTokens();
constructor(init: Partial<Account>) {
Object.assign(this, {
data: {
...new AccountData(),
...init.data,
},
keys: {
...new AccountKeys(),
...init.keys,
},
profile: {
...new AccountProfile(),
...init.profile,
},
settings: {
...new AccountSettings(),
...init.settings,
},
tokens: {
...new AccountTokens(),
...init.tokens,
},
});
}
}

View File

@@ -5,7 +5,6 @@ export class GlobalState {
locale: string;
openAtLogin: boolean;
organizationInvitation: any;
rememberEmail: boolean;
rememberedEmail: string;
theme: string;
window: Map<string, any> = new Map<string, any>();
@@ -15,4 +14,6 @@ export class GlobalState {
biometricFingerprintValidated: boolean;
vaultTimeout: number;
vaultTimeoutAction: string;
loginRedirect: any;
mainWindowSize: number;
}

View File

@@ -353,11 +353,23 @@ export class AuthService implements AuthServiceAbstraction {
result.forcePasswordReset = tokenResponse.forcePasswordReset;
const accountInformation = await this.tokenService.decodeToken(tokenResponse.accessToken);
await this.stateService.addAccount(new Account(
accountInformation.sub, accountInformation.email,
tokenResponse.kdf, tokenResponse.kdfIterations,
clientId, clientSecret, tokenResponse.accessToken, tokenResponse.refreshToken,
accountInformation.premium));
await this.stateService.addAccount({
profile: {
userId: accountInformation.sub,
email: accountInformation.email,
apiKeyClientId: clientId,
apiKeyClientSecret: clientSecret,
hasPremiumPersonally: accountInformation.premium,
},
data: {
kdfIterations: tokenResponse.kdfIterations,
kdfType: tokenResponse.kdf,
},
tokens: {
accessToken: tokenResponse.accessToken,
refreshToken: tokenResponse.refreshToken,
},
});
if (tokenResponse.twoFactorToken != null) {
await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken, email);

File diff suppressed because it is too large Load Diff