Files
standardnotes-app-web/packages/snjs/lib/Domain/UseCase/GetRevision/GetRevision.ts
Karol Sójko 880a537774 feat(snjs): add revisions api v2 (#2154)
* feat(snjs): add revisions api v2

* fix(snjs): reference listing and getting revisions in specs

* fix(snjs): revisions specs

* fix(web): usage of revision metadata

* fix(snjs): add specs for decryption revision

* fix(snjs): issue with building mocked specs

* fix(snjs): adjust revision creation delay
2023-01-18 09:20:06 +01:00

81 lines
3.0 KiB
TypeScript

import { RevisionClientInterface } from '@standardnotes/services'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import {
EncryptedPayload,
HistoryEntry,
isErrorDecryptingPayload,
isRemotePayloadAllowed,
NoteContent,
PayloadTimestampDefaults,
} from '@standardnotes/models'
import { ContentType } from '@standardnotes/common'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { GetRevisionDTO } from './GetRevisionDTO'
export class GetRevision implements UseCaseInterface<HistoryEntry> {
constructor(private revisionManager: RevisionClientInterface, private protocolService: EncryptionProviderInterface) {}
async execute(dto: GetRevisionDTO): Promise<Result<HistoryEntry>> {
const itemUuidOrError = Uuid.create(dto.itemUuid)
if (itemUuidOrError.isFailed()) {
return Result.fail(`Could not get revision: ${itemUuidOrError.getError()}`)
}
const itemUuid = itemUuidOrError.getValue()
const revisionUuidOrError = Uuid.create(dto.revisionUuid)
if (revisionUuidOrError.isFailed()) {
return Result.fail(`Could not get revision: ${revisionUuidOrError.getError()}`)
}
const revisionUuid = revisionUuidOrError.getValue()
const revision = await this.revisionManager.getRevision(itemUuid, revisionUuid)
if (revision === null) {
return Result.fail('Could not get revision: Revision not found')
}
const serverPayload = new EncryptedPayload({
...PayloadTimestampDefaults(),
uuid: revision.uuid,
content: revision.content as string,
enc_item_key: revision.enc_item_key as string,
items_key_id: revision.items_key_id as string,
auth_hash: revision.auth_hash as string,
content_type: revision.content_type as ContentType,
updated_at: new Date(revision.updated_at),
created_at: new Date(revision.created_at),
waitingForKey: false,
errorDecrypting: false,
})
/**
* When an item is duplicated, its revisions also carry over to the newly created item.
* However since the new item has a different UUID than the source item, we must decrypt
* these olders revisions (which have not been mutated after copy) with the source item's
* uuid.
*/
const embeddedParams = this.protocolService.getEmbeddedPayloadAuthenticatedData(serverPayload)
const sourceItemUuid = embeddedParams?.u as string | undefined
const payload = serverPayload.copy({
uuid: sourceItemUuid || revision.item_uuid,
})
if (!isRemotePayloadAllowed(payload)) {
return Result.fail(`Remote payload is disallowed: ${JSON.stringify(payload)}`)
}
const encryptedPayload = new EncryptedPayload(payload)
const decryptedPayload = await this.protocolService.decryptSplitSingle<NoteContent>({
usesItemsKeyWithKeyLookup: { items: [encryptedPayload] },
})
if (isErrorDecryptingPayload(decryptedPayload)) {
return Result.fail('Could not decrypt revision.')
}
return Result.ok(new HistoryEntry(decryptedPayload))
}
}