1 import { utf8ArrayToString } from '@proton/crypto/lib/utils'
2 import { VERIFICATION_STATUS } from '@proton/crypto'
3 import type { UseCaseInterface } from '../Domain/UseCase/UseCaseInterface'
4 import { Result } from '@proton/docs-shared'
5 import type { EncryptionService } from '../Services/Encryption/EncryptionService'
6 import type { DocumentKeys } from '@proton/drive-store'
7 import { GetAssociatedEncryptionDataForComment, isAnonymousComment } from './GetAdditionalEncryptionData'
8 import type { EncryptionContext } from '../Services/Encryption/EncryptionContext'
9 import { Comment } from '../Models'
10 import { ServerTime } from '@proton/docs-shared'
11 import { base64StringToUint8Array } from '@proton/shared/lib/helpers/encoding'
12 import metrics from '@proton/metrics'
13 import type { CommentResponseDto } from '../Api/Types/CommentResponseDto'
14 import { isPrivateDocumentKeys, type PublicDocumentKeys } from '../Types/DocumentEntitlements'
16 export class DecryptComment implements UseCaseInterface<Comment> {
17 constructor(private encryption: EncryptionService<EncryptionContext.PersistentComment>) {}
20 dto: CommentResponseDto,
22 keys: DocumentKeys | PublicDocumentKeys,
23 ): Promise<Result<Comment>> {
24 const emailToUse = dto.AuthorEmail || dto.Author
25 const aad = GetAssociatedEncryptionDataForComment({ authorAddress: emailToUse, markId })
27 if (!emailToUse && !isAnonymousComment(aad)) {
28 return Result.fail('Decryption mismatch; no author email or author address found in private comment')
31 const decrypted = await this.encryption.decryptData(
32 base64StringToUint8Array(dto.Content),
34 keys.documentContentKey,
37 if (decrypted.isFailed()) {
38 metrics.docs_comments_download_error_total.increment({
39 reason: 'decryption_error',
42 return Result.fail(`Comment decryption failed: ${decrypted.getError()}`)
45 /** Cannot verify signatures if public viewier/editor, as API to retrieve public keys of signer is not available */
46 if (isPrivateDocumentKeys(keys) && !isAnonymousComment(aad)) {
48 return Result.fail('Cannot verify; no author email or author address found in private comment')
51 const verificationKey = await this.encryption.getVerificationKey(emailToUse)
53 if (verificationKey.isFailed()) {
54 return Result.fail(`Comment verification key failed: ${verificationKey.getError()}`)
57 const verifyResult = await this.encryption.verifyData(
58 decrypted.getValue().content,
59 decrypted.getValue().signature,
61 verificationKey.getValue(),
64 if (verifyResult.isFailed()) {
65 metrics.docs_comments_download_error_total.increment({
66 reason: 'decryption_error',
69 return Result.fail(`Comment verifyResult is failed: ${verifyResult.getError()}`)
72 const verifyValue = verifyResult.getValue()
74 if (verifyValue !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
75 metrics.docs_comments_download_error_total.increment({
76 reason: 'decryption_error',
79 return Result.fail(`Comment content verification failed: ${verifyValue}`)
86 new ServerTime(dto.CreateTime),
87 new ServerTime(dto.ModifyTime),
88 utf8ArrayToString(decrypted.getValue().content),