import Credentials from "../credentials/Credentials" ; import Entry from "../core/Entry" ; import Vault from "../core/Vault" ; import VaultSource from "../core/VaultSource" ; import { getSharedAppEnv } from "../env/appEnv" ; import { generateUUID } from "../tools/uuid" ; import { decryptAttachment , encryptAttachment , getBufferSize } from "../tools/attachments" ; import { AttachmentDetails } from "../types" ; const ATTACHMENTS_KEY_LENGTH = 48 ; memberof export default class AttachmentManager { static memberof static newAttachmentID ( ) { return generateUUID ( ) ; } _credentials : Credentials ; _source : VaultSource ; param vaultSource constructor ( vaultSource ) { this . _source = vaultSource ; } param entry param attachmentID throws memberof async getAttachment ( entry , attachmentID ) { this . _checkAttachmentSupport ( ) ; const details = await this . getAttachmentDetails ( entry , attachmentID ) ; if ( ! details ) { throw new Error ( `Attachment not available: ${ attachmentID }` ) ; } const credentials = await this . _getAttachmentsCredentials ( ) ; const data = await this . _source . _datasource . getAttachment ( this . _source . vault . id , attachmentID ) ; return decryptAttachment ( data , credentials ) ; } param entry param attachmentID returns memberof async getAttachmentDetails ( entry , attachmentID ) { this . _checkAttachmentSupport ( ) ; const attributeKey = `${ Entry . Attributes . AttachmentPrefix }${ attachmentID }` ; const payloadRaw = entry . getAttribute ( attributeKey ) as string ; return payloadRaw ? JSON . parse ( payloadRaw ) : null ; } param entry memberof async listAttachments ( entry ) { this . _checkAttachmentSupport ( ) ; const attributes = entry . getAttribute ( ) ; const attachmentKeys = Object . keys ( attributes ) . filter ( key => key . indexOf ( Entry . Attributes . AttachmentPrefix ) === 0 ) ; return attachmentKeys . map ( key => JSON . parse ( attributes [ key ] ) ) ; } param entry param attachmentID memberof async removeAttachment ( entry , attachmentID ) { this . _checkAttachmentSupport ( ) ; const attributeKey = `${ Entry . Attributes . AttachmentPrefix }${ attachmentID }` ; await this . _source . _datasource . removeAttachment ( this . _source . vault . id , attachmentID ) ; entry . deleteAttribute ( attributeKey ) ; } param entry param attachmentID param attachmentData param name param type param timestamp memberof async setAttachment ( entry , attachmentID , attachmentData , name , type , timestamp = new Date ( ) ) { this . _checkAttachmentSupport ( ) ; if ( ! name || ! type ) { throw new Error ( `Attachment properties required: name/type => ${ name }/${ type }` ) ; } const attributeKey = `${ Entry . Attributes . AttachmentPrefix }${ attachmentID }` ; const existingDetails = await this . getAttachmentDetails ( entry , attachmentID ) ; const credentials = await this . _getAttachmentsCredentials ( ) ; const encryptedAttachmentData = await encryptAttachment ( attachmentData , credentials ) ; const encryptedSize = getBufferSize ( encryptedAttachmentData ) ; const originalSize = getBufferSize ( attachmentData ) ; const sizeIncrease = existingDetails ? Math . max ( 0 , encryptedSize - existingDetails . sizeEncrypted ) : encryptedSize ; const spaceAvailable = await this . _source . _datasource . getAvailableStorage ( ) ; if ( spaceAvailable !== null && sizeIncrease > spaceAvailable ) { throw new Error ( `Not enough space to update attachment: needed = ${ sizeIncrease } B, available = ${ spaceAvailable } B` ) ; } const now = timestamp . toUTCString ( ) ; const payload = { id : attachmentID , name , type , sizeOriginal : originalSize , sizeEncrypted : encryptedSize , created : now , updated : now } ; await this . _source . _datasource . putAttachment ( this . _source . vault . id , attachmentID , encryptedAttachmentData , payload ) ; entry . setAttribute ( attributeKey , JSON . stringify ( payload ) ) ; } _checkAttachmentSupport ( ) { if ( ! this . _source . supportsAttachments ( ) ) { throw new Error ( `Attachments not supported on source: ${ this . _source . id }` ) ; } } async _getAttachmentsCredentials ( ) { const { vault } = this . _source ; let key = vault . getAttribute ( Vault . Attribute . AttachmentsKey ) as string ; if ( ! key ) { const generateRandomString = getSharedAppEnv ( ) . getProperty ( "crypto/v1/randomString" ) ; key = await generateRandomString ( ATTACHMENTS_KEY_LENGTH ) ; vault . setAttribute ( Vault . Attribute . AttachmentsKey , key ) ; await this . _source . save ( ) ; } return Credentials . fromPassword ( key ) ; } }