1- import { isEmpty } from 'lodash ' ;
2- import { SettingsKey } from '../../data/settings-key' ;
1+ import { randombytes_buf } from 'libsodium-wrappers-sumo ' ;
2+
33import { uploadFileToFsWithOnionV4 } from '../../session/apis/file_server_api/FileServerApi' ;
4- import { ConvoHub } from '../../session/conversations' ;
5- import { DecryptedAttachmentsManager } from '../../session/crypto/DecryptedAttachmentsManager' ;
6- import { UserUtils } from '../../session/utils' ;
7- import { fromHexToArray } from '../../session/utils/String' ;
8- import { urlToBlob } from '../../types/attachments/VisualAttachment' ;
94import { processNewAttachment } from '../../types/MessageAttachment' ;
10- import { IMAGE_JPEG } from '../../types/MIME' ;
115import { encryptProfile } from '../../util/crypto/profileEncrypter' ;
12- import { Storage } from '../../util/storage' ;
136import type { ConversationModel } from '../../models/conversation' ;
147import { processAvatarData } from '../../util/avatar/processAvatarData' ;
15- import { UserConfigWrapperActions } from '../../webworker/workers/browser/libsession_worker_interface' ;
16-
17- /**
18- * This function can be used for reupload our avatar to the file server.
19- * It will reuse the same profileKey and avatarContent if we have some, or do nothing if one of those is missing.
20- */
21- export async function reuploadCurrentAvatarUs ( ) {
22- const ourConvo = ConvoHub . use ( ) . get ( UserUtils . getOurPubKeyStrFromCache ( ) ) ;
23- if ( ! ourConvo ) {
24- window . log . warn ( 'ourConvo not found... This is not a valid case' ) ;
25- return null ;
26- }
27-
28- // this is a reupload. no need to generate a new profileKey
29- const ourConvoProfileKey =
30- ConvoHub . use ( ) . get ( UserUtils . getOurPubKeyStrFromCache ( ) ) ?. getProfileKey ( ) || null ;
31-
32- const profileKey = ourConvoProfileKey ? fromHexToArray ( ourConvoProfileKey ) : null ;
33- if ( ! profileKey || isEmpty ( profileKey ) ) {
34- window . log . info ( 'reuploadCurrentAvatarUs: our profileKey empty' ) ;
35-
36- return null ;
37- }
38- // Note: we do want to grab the current non-static avatar path here
39- // to reupload it, no matter if we are a pro user or not.
40- const currentNonStaticAvatarPath = ourConvo . getAvatarInProfilePath ( ) ;
41-
42- if ( ! currentNonStaticAvatarPath ) {
43- window . log . info ( 'No attachment currently set for our convo.. Nothing to do.' ) ;
44- return null ;
45- }
46-
47- const decryptedAvatarUrl = await DecryptedAttachmentsManager . getDecryptedMediaUrl (
48- currentNonStaticAvatarPath ,
49- IMAGE_JPEG ,
50- true
51- ) ;
52-
53- if ( ! decryptedAvatarUrl ) {
54- window . log . warn ( 'Could not decrypt avatar stored locally..' ) ;
55- return null ;
56- }
57- const blob = await urlToBlob ( decryptedAvatarUrl ) ;
58-
59- const decryptedAvatarData = await blob . arrayBuffer ( ) ;
60-
61- return uploadAndSetOurAvatarShared ( {
62- decryptedAvatarData,
63- ourConvo,
64- profileKey,
65- context : 'reuploadAvatar' ,
66- } ) ;
67- }
8+ import {
9+ MultiEncryptWrapperActions ,
10+ UserConfigWrapperActions ,
11+ } from '../../webworker/workers/browser/libsession_worker_interface' ;
12+ import { UserUtils } from '../../session/utils' ;
13+ import { fromBase64ToArray } from '../../session/utils/String' ;
6814
6915export async function uploadAndSetOurAvatarShared ( {
7016 decryptedAvatarData,
7117 ourConvo,
72- profileKey,
7318 context,
7419} : {
7520 ourConvo : ConversationModel ;
7621 decryptedAvatarData : ArrayBuffer ;
77- profileKey : Uint8Array ;
7822 context : 'uploadNewAvatar' | 'reuploadAvatar' ;
7923} ) {
8024 if ( ! decryptedAvatarData ?. byteLength ) {
8125 window . log . warn ( 'uploadAndSetOurAvatarShared: avatar content is empty' ) ;
8226 return null ;
8327 }
28+ // Note: we want to encrypt & upload the **processed** avatar
29+ // below (resized & converted), not the original one.
30+ const { avatarFallback, mainAvatarDetails } = await processAvatarData ( decryptedAvatarData , true ) ;
31+
32+ let encryptedData : ArrayBuffer ;
33+ let encryptionKey : Uint8Array ;
34+ const deterministicEncryption = window . sessionFeatureFlags ?. useDeterministicEncryption ;
35+ if ( deterministicEncryption ) {
36+ const encryptedContent = await MultiEncryptWrapperActions . attachmentEncrypt ( {
37+ allowLarge : false ,
38+ seed : await UserUtils . getUserEd25519Seed ( ) ,
39+ data : new Uint8Array ( mainAvatarDetails . outputBuffer ) ,
40+ domain : 'profilePic' ,
41+ } ) ;
42+ encryptedData = encryptedContent . encryptedData ;
43+ encryptionKey = encryptedContent . encryptionKey ;
44+ } else {
45+ // if this is a reupload, reuse the current profile key. Otherwise generate a new one
46+ const existingProfileKey = ourConvo . getProfileKey ( ) ;
47+ const profileKey =
48+ context === 'reuploadAvatar' && existingProfileKey
49+ ? fromBase64ToArray ( existingProfileKey )
50+ : randombytes_buf ( 32 ) ;
51+ encryptedData = await encryptProfile ( mainAvatarDetails . outputBuffer , profileKey ) ;
52+ encryptionKey = profileKey ;
53+ }
8454
85- const encryptedData = await encryptProfile ( decryptedAvatarData , profileKey ) ;
86-
87- const avatarPointer = await uploadFileToFsWithOnionV4 ( encryptedData ) ;
55+ const avatarPointer = await uploadFileToFsWithOnionV4 ( encryptedData , deterministicEncryption ) ;
8856 if ( ! avatarPointer ) {
8957 window . log . warn ( 'failed to upload avatar to file server' ) ;
9058 return null ;
9159 }
92- const { fileUrl, expiresMs } = avatarPointer ;
60+ // Note: we don't care about the expiry of the file anymore.
61+ // This is because we renew the expiry of the file itself, and only when that fails we reupload the avatar content.
62+ const { fileUrl } = avatarPointer ;
9363
9464 // Note: processing the avatar here doesn't change the buffer (unless the first one was uploaded as an image too big for an avatar.)
9565 // so, once we have deterministic encryption of avatars, the uploaded should always have the same hash
96- const { avatarFallback, mainAvatarDetails } = await processAvatarData ( decryptedAvatarData ) ;
9766
9867 // this encrypts and save the new avatar and returns a new attachment path
9968 const savedMainAvatar = await processNewAttachment ( {
@@ -106,7 +75,7 @@ export async function uploadAndSetOurAvatarShared({
10675 ? await processNewAttachment ( {
10776 isRaw : true ,
10877 data : avatarFallback . outputBuffer ,
109- contentType : avatarFallback . contentType , // contentType is mostly used to generate previews and screenshot. We do not care for those in this case.
78+ contentType : avatarFallback . contentType ,
11079 } )
11180 : null ;
11281
@@ -118,17 +87,16 @@ export async function uploadAndSetOurAvatarShared({
11887 displayName : null ,
11988 avatarPointer : fileUrl ,
12089 type : 'setAvatarDownloadedPrivate' ,
121- profileKey,
90+ profileKey : encryptionKey ,
12291 } ) ;
123- await Storage . put ( SettingsKey . ntsAvatarExpiryMs , expiresMs ) ;
12492 if ( context === 'uploadNewAvatar' ) {
12593 await UserConfigWrapperActions . setNewProfilePic ( {
126- key : profileKey ,
94+ key : encryptionKey ,
12795 url : fileUrl ,
12896 } ) ;
12997 } else if ( context === 'reuploadAvatar' ) {
13098 await UserConfigWrapperActions . setReuploadProfilePic ( {
131- key : profileKey ,
99+ key : encryptionKey ,
132100 url : fileUrl ,
133101 } ) ;
134102 }
0 commit comments