diff --git a/libs/common/src/vault/abstractions/cipher-encryption.service.ts b/libs/common/src/vault/abstractions/cipher-encryption.service.ts index 35becd4b0e7..1016e95ea78 100644 --- a/libs/common/src/vault/abstractions/cipher-encryption.service.ts +++ b/libs/common/src/vault/abstractions/cipher-encryption.service.ts @@ -93,4 +93,6 @@ export abstract class CipherEncryptionService { encryptedContent: Uint8Array, userId: UserId, ): Promise; + + abstract migrateCiphers(ciphers: Cipher[], userId: UserId): Promise; } diff --git a/libs/common/src/vault/models/data/cipher.data.ts b/libs/common/src/vault/models/data/cipher.data.ts index 7554f23f6a0..deb854bd4fd 100644 --- a/libs/common/src/vault/models/data/cipher.data.ts +++ b/libs/common/src/vault/models/data/cipher.data.ts @@ -42,6 +42,8 @@ export class CipherData { deletedDate: string | null; reprompt: CipherRepromptType; key: string; + version?: number; + data?: string; constructor(response?: CipherResponse, collectionIds?: string[]) { if (response == null) { @@ -65,6 +67,8 @@ export class CipherData { this.deletedDate = response.deletedDate; this.reprompt = response.reprompt; this.key = response.key; + // this.version = response.version || 1; + this.data = response.data || null; switch (this.type) { case CipherType.Login: diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index f884dc32cce..5861ac78b05 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -59,6 +59,8 @@ export class Cipher extends Domain implements Decryptable { deletedDate: Date; reprompt: CipherRepromptType; key: EncString; + // version: number; + data: string; constructor(obj?: CipherData, localData: LocalData = null) { super(); @@ -96,6 +98,8 @@ export class Cipher extends Domain implements Decryptable { this.creationDate = obj.creationDate != null ? new Date(obj.creationDate) : null; this.deletedDate = obj.deletedDate != null ? new Date(obj.deletedDate) : null; this.reprompt = obj.reprompt; + // this.version = obj.version || 1; + this.data = obj.data; switch (this.type) { case CipherType.Login: @@ -376,6 +380,8 @@ export class Cipher extends Domain implements Decryptable { card: undefined, secureNote: undefined, sshKey: undefined, + // version: 1, + data: typeof this.data === "string" ? this.data : JSON.stringify(this.data), }; switch (this.type) { @@ -444,6 +450,8 @@ export class Cipher extends Domain implements Decryptable { cipher.identity = Identity.fromSdkIdentity(sdkCipher.identity); cipher.sshKey = SshKey.fromSdkSshKey(sdkCipher.sshKey); + cipher.data = sdkCipher.data; + return cipher; } } diff --git a/libs/common/src/vault/models/response/cipher.response.ts b/libs/common/src/vault/models/response/cipher.response.ts index d4c907ae2b0..cf82ef5a24c 100644 --- a/libs/common/src/vault/models/response/cipher.response.ts +++ b/libs/common/src/vault/models/response/cipher.response.ts @@ -40,6 +40,9 @@ export class CipherResponse extends BaseResponse { deletedDate: string; reprompt: CipherRepromptType; key: string; + // Cipher versioning POC + data: string; + // version: number; constructor(response: any) { super(response); @@ -105,5 +108,8 @@ export class CipherResponse extends BaseResponse { this.reprompt = this.getResponseProperty("Reprompt") || CipherRepromptType.None; this.key = this.getResponseProperty("Key") || null; + // Cipher versioning POC + // this.version = this.getResponseProperty("Version") || 1; + this.data = this.getResponseProperty("Data") || null; } } diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index c91d6e21ca2..f9688efa743 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -340,6 +340,7 @@ export class CipherView implements View, InitializerMetadata { identity: undefined, secureNote: undefined, sshKey: undefined, + // version: undefined, }; switch (this.type) { diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index d3513792727..b05d2b5d541 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1125,6 +1125,14 @@ export class CipherService implements CipherServiceAbstraction { } async replace(ciphers: { [id: string]: CipherData }, userId: UserId): Promise { + // use cipher from the sdk and convert to cipher data to store in the state + // uncomment to test migration + // const cipherObjects = Object.values(ciphers).map((cipherData) => new Cipher(cipherData)); + // const migratedCiphers = await this.cipherEncryptionService.migrateCiphers( + // cipherObjects, + // userId, + // ); + await this.updateEncryptedCipherState(() => ciphers, userId); } diff --git a/libs/common/src/vault/services/default-cipher-encryption.service.ts b/libs/common/src/vault/services/default-cipher-encryption.service.ts index 2cef4ca1ca1..3f82766d4a7 100644 --- a/libs/common/src/vault/services/default-cipher-encryption.service.ts +++ b/libs/common/src/vault/services/default-cipher-encryption.service.ts @@ -283,6 +283,43 @@ export class DefaultCipherEncryptionService implements CipherEncryptionService { ); } + migrateCiphers(ciphers: Cipher[], userId: UserId): Promise { + return firstValueFrom( + this.sdkService.userClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + this.logService.warning( + "SDK not available for cipher migration, returning original ciphers", + ); + return ciphers; + } + + try { + using ref = sdk.take(); + const migratedSdkCiphers = ref.value + .vault() + .ciphers() + .migrate(ciphers.map((cipher) => cipher.toSdkCipher())); + + const migratedCiphers = migratedSdkCiphers.map( + (sdkCipher) => Cipher.fromSdkCipher(sdkCipher)!, + ); + + this.logService.info(`Successfully migrated ${ciphers.length} ciphers`); + return migratedCiphers; + } catch (error) { + this.logService.error(`Cipher migration failed: ${error}`); + return ciphers; + } + }), + catchError((error: unknown) => { + this.logService.error(`Failed to access SDK for cipher migration: ${error}`); + // Return original ciphers as fallback + return [ciphers]; + }), + ), + ); + } /** * Helper method to convert a CipherView model to an SDK CipherView. Has special handling for Fido2 credentials * that need to be encrypted before being sent to the SDK.