Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / docs-core / lib / UseCase / DecryptComment.ts
blob3da23e12b936bff1b1e4cafc774a1cfc423bf1bd
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>) {}
19   async execute(
20     dto: CommentResponseDto,
21     markId: string,
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')
29     }
31     const decrypted = await this.encryption.decryptData(
32       base64StringToUint8Array(dto.Content),
33       aad,
34       keys.documentContentKey,
35     )
37     if (decrypted.isFailed()) {
38       metrics.docs_comments_download_error_total.increment({
39         reason: 'decryption_error',
40       })
42       return Result.fail(`Comment decryption failed: ${decrypted.getError()}`)
43     }
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)) {
47       if (!emailToUse) {
48         return Result.fail('Cannot verify; no author email or author address found in private comment')
49       }
51       const verificationKey = await this.encryption.getVerificationKey(emailToUse)
53       if (verificationKey.isFailed()) {
54         return Result.fail(`Comment verification key failed: ${verificationKey.getError()}`)
55       }
57       const verifyResult = await this.encryption.verifyData(
58         decrypted.getValue().content,
59         decrypted.getValue().signature,
60         aad,
61         verificationKey.getValue(),
62       )
64       if (verifyResult.isFailed()) {
65         metrics.docs_comments_download_error_total.increment({
66           reason: 'decryption_error',
67         })
69         return Result.fail(`Comment verifyResult is failed: ${verifyResult.getError()}`)
70       }
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',
77         })
79         return Result.fail(`Comment content verification failed: ${verifyValue}`)
80       }
81     }
83     return Result.ok(
84       new Comment(
85         dto.CommentID,
86         new ServerTime(dto.CreateTime),
87         new ServerTime(dto.ModifyTime),
88         utf8ArrayToString(decrypted.getValue().content),
89         dto.ParentCommentID,
90         emailToUse,
91         [],
92         false,
93         dto.Type,
94       ),
95     )
96   }