* 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
81 lines
3.0 KiB
TypeScript
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))
|
|
}
|
|
}
|