mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-17 20:31:50 +01:00
[PM-5468] Ensure prototypes available on memory stored objects (#7399)
* Hide account switcher in addEdit generator * Handle AddEditCipher deserialization * Opaque types are not serializable * Better handle jsonification of login uris * Ensure we don't overwrite original with clone * Ensure cipherView prototype is always restored if it exists
This commit is contained in:
parent
90b794c74d
commit
a682f2a0ef
@ -5,7 +5,7 @@
|
|||||||
<ng-content select=".center"></ng-content>
|
<ng-content select=".center"></ng-content>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<ng-content select=".right"></ng-content>
|
<ng-content select=".right"></ng-content>
|
||||||
<ng-container *ngIf="authedAccounts$ | async">
|
<ng-container *ngIf="(authedAccounts$ | async) && !hideAccountSwitcher">
|
||||||
<app-current-account></app-current-account>
|
<app-current-account></app-current-account>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,6 +12,7 @@ import { flagEnabled } from "../flags";
|
|||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent {
|
||||||
@Input() noTheme = false;
|
@Input() noTheme = false;
|
||||||
|
@Input() hideAccountSwitcher = false;
|
||||||
authedAccounts$: Observable<boolean>;
|
authedAccounts$: Observable<boolean>;
|
||||||
constructor(accountService: AccountService) {
|
constructor(accountService: AccountService) {
|
||||||
this.authedAccounts$ = accountService.accounts$.pipe(
|
this.authedAccounts$ = accountService.accounts$.pipe(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<app-header>
|
<app-header [hideAccountSwitcher]="comingFromAddEdit">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<app-pop-out [show]="!comingFromAddEdit"></app-pop-out>
|
<app-pop-out [show]="!comingFromAddEdit"></app-pop-out>
|
||||||
<button type="button" (click)="close()" *ngIf="comingFromAddEdit">
|
<button type="button" (click)="close()" *ngIf="comingFromAddEdit">
|
||||||
|
@ -27,6 +27,7 @@ import { CollectionData } from "../../../vault/models/data/collection.data";
|
|||||||
import { FolderData } from "../../../vault/models/data/folder.data";
|
import { FolderData } from "../../../vault/models/data/folder.data";
|
||||||
import { CipherView } from "../../../vault/models/view/cipher.view";
|
import { CipherView } from "../../../vault/models/view/cipher.view";
|
||||||
import { CollectionView } from "../../../vault/models/view/collection.view";
|
import { CollectionView } from "../../../vault/models/view/collection.view";
|
||||||
|
import { AddEditCipherInfo } from "../../../vault/types/add-edit-cipher-info";
|
||||||
import { KdfType } from "../../enums";
|
import { KdfType } from "../../enums";
|
||||||
import { Utils } from "../../misc/utils";
|
import { Utils } from "../../misc/utils";
|
||||||
import { ServerConfigData } from "../../models/data/server-config.data";
|
import { ServerConfigData } from "../../models/data/server-config.data";
|
||||||
@ -101,10 +102,23 @@ export class AccountData {
|
|||||||
GeneratedPasswordHistory[],
|
GeneratedPasswordHistory[],
|
||||||
GeneratedPasswordHistory[]
|
GeneratedPasswordHistory[]
|
||||||
> = new EncryptionPair<GeneratedPasswordHistory[], GeneratedPasswordHistory[]>();
|
> = new EncryptionPair<GeneratedPasswordHistory[], GeneratedPasswordHistory[]>();
|
||||||
addEditCipherInfo?: any;
|
addEditCipherInfo?: AddEditCipherInfo;
|
||||||
eventCollection?: EventData[];
|
eventCollection?: EventData[];
|
||||||
organizations?: { [id: string]: OrganizationData };
|
organizations?: { [id: string]: OrganizationData };
|
||||||
providers?: { [id: string]: ProviderData };
|
providers?: { [id: string]: ProviderData };
|
||||||
|
|
||||||
|
static fromJSON(obj: DeepJsonify<AccountData>): AccountData {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(new AccountData(), obj, {
|
||||||
|
addEditCipherInfo: {
|
||||||
|
cipher: CipherView.fromJSON(obj?.addEditCipherInfo?.cipher),
|
||||||
|
collectionIds: obj?.addEditCipherInfo?.collectionIds,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AccountKeys {
|
export class AccountKeys {
|
||||||
@ -152,7 +166,7 @@ export class AccountKeys {
|
|||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return Object.assign(new AccountKeys(), {
|
return Object.assign(new AccountKeys(), obj, {
|
||||||
userKey: SymmetricCryptoKey.fromJSON(obj?.userKey),
|
userKey: SymmetricCryptoKey.fromJSON(obj?.userKey),
|
||||||
masterKey: SymmetricCryptoKey.fromJSON(obj?.masterKey),
|
masterKey: SymmetricCryptoKey.fromJSON(obj?.masterKey),
|
||||||
deviceKey: obj?.deviceKey,
|
deviceKey: obj?.deviceKey,
|
||||||
@ -451,6 +465,7 @@ export class Account {
|
|||||||
|
|
||||||
return Object.assign(new Account({}), json, {
|
return Object.assign(new Account({}), json, {
|
||||||
keys: AccountKeys.fromJSON(json?.keys),
|
keys: AccountKeys.fromJSON(json?.keys),
|
||||||
|
data: AccountData.fromJSON(json?.data),
|
||||||
profile: AccountProfile.fromJSON(json?.profile),
|
profile: AccountProfile.fromJSON(json?.profile),
|
||||||
settings: AccountSettings.fromJSON(json?.settings),
|
settings: AccountSettings.fromJSON(json?.settings),
|
||||||
tokens: AccountTokens.fromJSON(json?.tokens),
|
tokens: AccountTokens.fromJSON(json?.tokens),
|
||||||
|
@ -40,7 +40,7 @@ export class EncString implements Encrypted {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return this.encryptedString;
|
return this.encryptedString as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON(obj: Jsonify<EncString>): EncString {
|
static fromJSON(obj: Jsonify<EncString>): EncString {
|
||||||
|
@ -31,8 +31,8 @@ export class MemoryStorageService extends AbstractMemoryStorageService {
|
|||||||
}
|
}
|
||||||
// TODO: Remove once foreground/background contexts are separated in browser
|
// TODO: Remove once foreground/background contexts are separated in browser
|
||||||
// Needed to ensure ownership of all memory by the context running the storage service
|
// Needed to ensure ownership of all memory by the context running the storage service
|
||||||
obj = structuredClone(obj);
|
const toStore = structuredClone(obj);
|
||||||
this.store.set(key, obj);
|
this.store.set(key, toStore);
|
||||||
this.updatesSubject.next({ key, updateType: "save" });
|
this.updatesSubject.next({ key, updateType: "save" });
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
@ -280,9 +280,20 @@ export class StateService<
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getAddEditCipherInfo(options?: StorageOptions): Promise<AddEditCipherInfo> {
|
async getAddEditCipherInfo(options?: StorageOptions): Promise<AddEditCipherInfo> {
|
||||||
return (
|
const account = await this.getAccount(
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
|
||||||
)?.data?.addEditCipherInfo;
|
);
|
||||||
|
// ensure prototype on cipher
|
||||||
|
const raw = account?.data?.addEditCipherInfo;
|
||||||
|
return raw == null
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
cipher:
|
||||||
|
raw?.cipher.toJSON != null
|
||||||
|
? raw.cipher
|
||||||
|
: CipherView.fromJSON(raw?.cipher as Jsonify<CipherView>),
|
||||||
|
collectionIds: raw?.collectionIds,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAddEditCipherInfo(value: AddEditCipherInfo, options?: StorageOptions): Promise<void> {
|
async setAddEditCipherInfo(value: AddEditCipherInfo, options?: StorageOptions): Promise<void> {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Jsonify } from "type-fest";
|
|
||||||
|
|
||||||
import { View } from "../../../models/view/view";
|
import { View } from "../../../models/view/view";
|
||||||
import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface";
|
import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface";
|
||||||
import { InitializerKey } from "../../../platform/services/cryptography/initializer-key";
|
import { InitializerKey } from "../../../platform/services/cryptography/initializer-key";
|
||||||
|
import { DeepJsonify } from "../../../types/deep-jsonify";
|
||||||
import { LinkedIdType } from "../../enums";
|
import { LinkedIdType } from "../../enums";
|
||||||
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
|
||||||
import { CipherType } from "../../enums/cipher-type";
|
import { CipherType } from "../../enums/cipher-type";
|
||||||
@ -141,7 +140,16 @@ export class CipherView implements View, InitializerMetadata {
|
|||||||
return this.linkedFieldOptions.get(id)?.i18nKey;
|
return this.linkedFieldOptions.get(id)?.i18nKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON(obj: Partial<Jsonify<CipherView>>): CipherView {
|
// This is used as a marker to indicate that the cipher view object still has its prototype
|
||||||
|
toJSON() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(obj: Partial<DeepJsonify<CipherView>>): CipherView {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const view = new CipherView();
|
const view = new CipherView();
|
||||||
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
||||||
const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);
|
const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Jsonify } from "type-fest";
|
|
||||||
|
|
||||||
import { Utils } from "../../../platform/misc/utils";
|
import { Utils } from "../../../platform/misc/utils";
|
||||||
|
import { DeepJsonify } from "../../../types/deep-jsonify";
|
||||||
import { LoginLinkedId as LinkedId, UriMatchType } from "../../enums";
|
import { LoginLinkedId as LinkedId, UriMatchType } from "../../enums";
|
||||||
import { linkedFieldOption } from "../../linked-field-option.decorator";
|
import { linkedFieldOption } from "../../linked-field-option.decorator";
|
||||||
import { Login } from "../domain/login";
|
import { Login } from "../domain/login";
|
||||||
@ -81,10 +80,10 @@ export class LoginView extends ItemView {
|
|||||||
return this.uris.some((uri) => uri.matchesUri(targetUri, equivalentDomains, defaultUriMatch));
|
return this.uris.some((uri) => uri.matchesUri(targetUri, equivalentDomains, defaultUriMatch));
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON(obj: Partial<Jsonify<LoginView>>): LoginView {
|
static fromJSON(obj: Partial<DeepJsonify<LoginView>>): LoginView {
|
||||||
const passwordRevisionDate =
|
const passwordRevisionDate =
|
||||||
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
|
||||||
const uris = obj.uris.map((uri: any) => LoginUriView.fromJSON(uri));
|
const uris = obj.uris.map((uri) => LoginUriView.fromJSON(uri));
|
||||||
const fido2Credentials = obj.fido2Credentials?.map((key) => Fido2CredentialView.fromJSON(key));
|
const fido2Credentials = obj.fido2Credentials?.map((key) => Fido2CredentialView.fromJSON(key));
|
||||||
|
|
||||||
return Object.assign(new LoginView(), obj, {
|
return Object.assign(new LoginView(), obj, {
|
||||||
|
Loading…
Reference in New Issue
Block a user