import VaultFormat from "./VaultFormat" ; import Vault from "../core/Vault" ; import { generateUUID } from "../tools/uuid" ; import { getSharedAppEnv } from "../env/appEnv" ; import { getCredentials } from "../credentials/channel" ; import Credentials from "../credentials/Credentials" ; import { historyArrayToString , historyStringToArray } from "./common" ; import { hasValidSignature , sign , stripSignature , vaultContentsEncrypted } from "./formatB/signing" ; import { historiesDiffer } from "./formatB/compare" ; import { mergeRawVaults } from "./formatB/merge" ; import { valuesObjectToKeyValueObject } from "./formatB/conversion" ; import { newRawValue , valueToHistoryItem } from "./formatB/history" ; import { getDateString , getTimestamp } from "../tools/date" ; import { EntryChange , EntryChangeType , EntryID , FormatBEntry , FormatBGroup , FormatBValueHistoryItem , FormatBVault , GroupID , History , PropertyKeyValueObject , VaultFormatID , VaultID } from "../types" ; function emptyVault ( ) { return { id : null , a : { } , g : [ ] , e : [ ] , c : getDateString ( ) } ; } export default class VaultFormatB extends VaultFormat { static encodeRaw ( rawContent , credentials ) { const compress = getSharedAppEnv ( ) . getProperty ( "compression/v2/compressText" ) ; const encrypt = getSharedAppEnv ( ) . getProperty ( "crypto/v1/encryptText" ) ; const { masterPassword } = getCredentials ( credentials . id ) ; return Promise . resolve ( ) . then ( ( ) => historyArrayToString ( rawContent ) ) . then ( history => compress ( history ) ) . then ( compressed => encrypt ( compressed , masterPassword ) ) . then ( sign ) ; } static extractSharesFromHistory ( history ) { return { } ; } static getFormatID ( ) { return VaultFormatID . B ; } static historiesDiffer ( historyA , historyB ) { return historiesDiffer ( historyA , historyB ) ; } static isEncrypted ( contents ) { return vaultContentsEncrypted ( contents ) ; } static parseEncrypted ( encryptedContent , credentials ) { const decompress = getSharedAppEnv ( ) . getProperty ( "compression/v2/decompressText" ) ; const decrypt = getSharedAppEnv ( ) . getProperty ( "crypto/v1/decryptText" ) ; const { masterPassword } = getCredentials ( credentials . id ) ; return Promise . resolve ( ) . then ( ( ) => { if ( ! hasValidSignature ( encryptedContent ) ) { throw new Error ( "No valid signature in vault" ) ; } return stripSignature ( encryptedContent ) ; } ) . then ( encryptedData => decrypt ( encryptedData , masterPassword ) ) . then ( async ( decrypted ) => { const decompressed = await decompress ( decrypted ) ; return historyStringToArray ( decompressed , VaultFormatID . B ) ; } ) ; } static prepareHistoryForMerge ( history ) { return history ; } static vaultFromMergedHistories ( local , incoming ) { const localRaw = JSON . parse ( local [ 0 ] ) as FormatBVault ; const incomingRaw = JSON . parse ( incoming [ 0 ] ) as FormatBVault ; const merged = mergeRawVaults ( localRaw , incomingRaw ) ; const vault = new Vault ( ) ; vault . _format = new VaultFormatB ( merged ) ; return vault ; } source : FormatBVault ; constructor ( source = emptyVault ( ) ) { super ( source ) ; } cloneEntry ( entry , targetGroupID ) { } cloneGroup ( group , targetGroupID ) { const newGroup = JSON . parse ( JSON . stringify ( group ) ) as FormatBGroup ; newGroup . id = generateUUID ( ) ; newGroup . g = targetGroupID ; this . source . g . push ( newGroup ) ; const childEntries = this . source . e . filter ( entry => entry . g === group . id ) . map ( entry => { const newEntry = JSON . parse ( JSON . stringify ( entry ) ) ; newEntry . g = newGroup . id ; return newEntry ; } ) ; this . source . e . push ( ... childEntries ) ; this . source . g . forEach ( childGroup => { if ( childGroup . g === group . id ) { this . cloneGroup ( childGroup , newGroup . id ) ; } } ) ; } createEntry ( groupID , entryID ) { const entry = { id : entryID , g : groupID , p : { } , a : { } } ; this . source . e . push ( entry ) ; } createGroup ( parentID , groupID ) { const group = { id : groupID , g : parentID , t : "" , a : { } } ; this . source . g . push ( group ) ; } deleteEntry ( entryID ) { const ind = this . source . e . findIndex ( entry => entry . id === entryID ) ; if ( ind >= 0 ) { this . source . e . splice ( ind , 1 ) ; } } deleteEntryAttribute ( entryID , attribute ) { const entry = this . source . e . find ( ( e ) => e . id === entryID ) ; if ( ! entry . a [ attribute ] ) return ; entry . a [ attribute ] . deleted = getTimestamp ( ) ; } deleteEntryProperty ( entryID , property ) { const entry = this . source . e . find ( e => e . id === entryID ) ; if ( ! entry . p [ property ] ) return ; entry . p [ property ] . deleted = getTimestamp ( ) ; } deleteGroup ( groupID ) { const ind = this . source . g . findIndex ( group => group . id === groupID ) ; if ( ind >= 0 ) { this . source . g . splice ( ind , 1 ) ; } } deleteGroupAttribute ( groupID , attribute ) { const group = this . source . g . find ( g => g . id === groupID ) ; if ( ! group . a [ attribute ] ) return ; group . a [ attribute ] . deleted = getTimestamp ( ) ; } deleteVaultAttribute ( attribute ) { if ( ! this . source . a [ attribute ] ) return ; this . source . a [ attribute ] . deleted = getTimestamp ( ) ; } erase ( ) { super . erase ( ) ; Object . assign ( this . source , emptyVault ( ) ) ; this . emit ( "erased" ) ; } execute ( commandOrCommands ) { let command ; if ( Array . isArray ( commandOrCommands ) ) { if ( commandOrCommands . length !== 1 ) { throw new Error ( `Format-B commands array must contain a single command, received: ${ commandOrCommands . length }` ) ; } command = commandOrCommands [ 0 ] ; } else { command = commandOrCommands ; } this . source = JSON . parse ( command ) ; this . dirty = true ; this . emit ( "commandsExecuted" ) ; } findEntryByID ( id ) { return this . source . e . find ( entry => entry . id === id ) || null ; } findGroupByID ( id ) { return this . source . g . find ( group => group . id === id ) || null ; } findGroupContainingEntryID ( id ) { const matchingEntry = this . getAllEntries ( ) . find ( entry => entry . id === id ) ; if ( matchingEntry ) { return this . getAllGroups ( ) . find ( group => group . id === matchingEntry . g ) || null ; } return null ; } findGroupContainingGroupID ( id ) { const groups = this . getAllGroups ( ) ; const matchingGroup = groups . find ( group => group . id === id ) ; if ( ! matchingGroup ) return null ; return groups . find ( group => group . id === matchingGroup . g ) || null ; } generateID ( ) { this . source . id = generateUUID ( ) ; } getAllEntries ( parentID = null ) { const source = this . source as FormatBVault ; return parentID === null ? source . e : source . e . filter ( entry => entry . g === parentID ) ; } getAllGroups ( parentID = null ) { const source = this . source as FormatBVault ; return parentID === null ? source . g : source . g . filter ( group => group . g === parentID ) ; } getEntryAttributes ( entrySource ) { return valuesObjectToKeyValueObject ( entrySource . a ) ; } getEntryChanges ( entrySource ) { return Object . keys ( entrySource . p ) . reduce ( ( changes , property ) => [ ... changes , ... entrySource . p [ property ] . history . map ( ( histItem ) => { const change = { property , type : histItem . updated === entrySource . p [ property ] . created ? EntryChangeType . Created : EntryChangeType . Modified , ts : histItem . updated , value : histItem . value } ; return change ; } ) , ... ( entrySource . p [ property ] . deleted ? [ { property , type : EntryChangeType . Deleted , ts : entrySource . p [ property ] . deleted , value : null } ] : [ ] ) ] , [ ] ) ; } getEntryProperties ( entrySource ) { return valuesObjectToKeyValueObject ( entrySource . p ) ; } getFormat ( ) { return VaultFormatB ; } getGroupAttributes ( groupSource ) { return valuesObjectToKeyValueObject ( groupSource . a ) ; } getGroupTitle ( groupSource ) { return groupSource . t ; } getHistory ( ) { const hist = < History > [ JSON . stringify ( this . source ) ] ; hist . format = VaultFormatID . B ; return hist ; } getItemID ( itemSource ) { return itemSource . id ; } getItemParentID ( itemSource ) { return itemSource . g ; } getVaultAttributes ( ) { return valuesObjectToKeyValueObject ( ( < FormatBVault > this . source ) . a ) ; } getVaultID ( ) { return this . source . id ; } initialise ( ) { Object . assign ( this . source , { a : this . source . a || { } , g : this . source . g || [ ] , e : this . source . e || [ ] } ) ; this . generateID ( ) ; } moveEntry ( entryID , groupID ) { const entry = this . source . e . find ( ( e ) => e . id === entryID ) ; entry . g = groupID ; } moveGroup ( groupID , newParentID ) { const group = this . source . g . find ( ( g ) => g . id === groupID ) ; group . g = newParentID ; } optimise ( ) { } setEntryAttribute ( entryID , attribute , value ) { const entry = this . source . e . find ( ( e ) => e . id === entryID ) ; if ( ! entry . a [ attribute ] ) { entry . a [ attribute ] = newRawValue ( value ) ; } else { const item = entry . a [ attribute ] ; item . history . unshift ( valueToHistoryItem ( item ) ) ; item . value = value ; item . updated = getTimestamp ( ) ; } } setEntryProperty ( entryID , property , value ) { const entry = this . source . e . find ( ( e ) => e . id === entryID ) ; if ( ! entry . p [ property ] ) { entry . p [ property ] = newRawValue ( value ) ; } else { const item = entry . p [ property ] ; item . history . unshift ( valueToHistoryItem ( item ) ) ; item . value = value ; item . updated = getTimestamp ( ) ; } } setGroupAttribute ( groupID , attribute , value ) { const group = this . source . g . find ( ( g ) => g . id === groupID ) ; if ( ! group . a [ attribute ] ) { group . a [ attribute ] = newRawValue ( value ) ; } else { const item = group . a [ attribute ] ; item . history . unshift ( valueToHistoryItem ( item ) ) ; item . value = value ; item . updated = getTimestamp ( ) ; } } setGroupTitle ( groupID , title ) { const group = this . source . g . find ( ( g ) => g . id === groupID ) ; group . t = title ; } setVaultAttribute ( key , value ) { if ( ! this . source . a [ key ] ) { this . source . a [ key ] = newRawValue ( value ) ; } else { const item = this . source . a [ key ] ; item . history . unshift ( valueToHistoryItem ( item ) ) ; item . value = value ; item . updated = getTimestamp ( ) ; } } }