diff --git a/lib/types/media-types.js b/lib/types/media-types.js --- a/lib/types/media-types.js +++ b/lib/types/media-types.js @@ -678,6 +678,7 @@ | { +success: false, +reason: 'digest_failed' } | { +success: false, +reason: 'thumbhash_failed' } | { +success: false, +reason: 'preload_image_failed' } + | { +success: false, +reason: 'missing_auth' } | DecryptionFailure; export type DecryptionFailure = { diff --git a/lib/utils/services-utils.js b/lib/utils/services-utils.js --- a/lib/utils/services-utils.js +++ b/lib/utils/services-utils.js @@ -23,8 +23,22 @@ return `Bearer ${base64EncodedPayload}`; } +function createDefaultHTTPRequestHeaders( + authMetadata: AuthMetadata, + base64EncodeFunction: string => string = btoa, +): { [string]: string } { + const authorization = createHTTPAuthorizationHeader( + authMetadata, + base64EncodeFunction, + ); + return { + Authorization: authorization, + }; +} + export { handleHTTPResponseError, usingCommServicesAccessToken, createHTTPAuthorizationHeader, + createDefaultHTTPRequestHeaders, }; diff --git a/native/media/encrypted-image.react.js b/native/media/encrypted-image.react.js --- a/native/media/encrypted-image.react.js +++ b/native/media/encrypted-image.react.js @@ -4,6 +4,7 @@ import * as React from 'react'; import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js'; +import { useCommServicesAuthMetadata } from 'lib/hooks/account-hooks.js'; import { connectionSelector } from 'lib/selectors/keyserver-selectors.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; @@ -34,6 +35,7 @@ thumbHash: encryptedThumbHash, } = props; + const authMetadata = useCommServicesAuthMetadata(); const mediaCache = React.useContext(MediaCacheContext); const [source, setSource] = React.useState(null); @@ -77,9 +79,14 @@ return; } - const { result } = await fetchAndDecryptMedia(blobURI, encryptionKey, { - destination: 'data_uri', - }); + const { result } = await fetchAndDecryptMedia( + blobURI, + encryptionKey, + authMetadata, + { + destination: 'data_uri', + }, + ); if (isMounted) { if (result.success) { @@ -96,7 +103,7 @@ return () => { isMounted = false; }; - }, [attempt, blobURI, encryptionKey, mediaCache]); + }, [attempt, blobURI, encryptionKey, mediaCache, authMetadata]); const onLoad = React.useCallback(() => { onLoadProp && onLoadProp(blobURI); diff --git a/native/media/encryption-utils.js b/native/media/encryption-utils.js --- a/native/media/encryption-utils.js +++ b/native/media/encryption-utils.js @@ -10,20 +10,27 @@ readableFilename, pathFromURI, } from 'lib/media/file-utils.js'; +import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; import type { MediaMissionFailure, MediaMissionStep, DecryptFileMediaMissionStep, EncryptFileMediaMissionStep, } from 'lib/types/media-types.js'; +import { isBlobServiceURI } from 'lib/utils/blob-service.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { pad, unpad, calculatePaddedLength } from 'lib/utils/pkcs7-padding.js'; +import { + usingCommServicesAccessToken, + createDefaultHTTPRequestHeaders, +} from 'lib/utils/services-utils.js'; import { temporaryDirectoryPath } from './file-utils.js'; import { getFetchableURI } from './identifier-utils.js'; import type { MediaResult } from './media-utils.js'; import { commUtilsModule } from '../native-modules.js'; import * as AES from '../utils/aes-crypto-module.js'; +import { base64EncodeString } from '../utils/base64-utils.js'; import { arrayBufferFromBlob } from '../utils/blob-utils-module.js'; const PADDING_THRESHOLD = 5000000; // we don't pad files larger than this @@ -248,6 +255,7 @@ async function fetchAndDecryptMedia( blobURI: string, encryptionKey: string, + authMetadata: ?AuthMetadata, options: { +destination: 'file' | 'data_uri', +destinationDirectory?: string, @@ -261,10 +269,22 @@ const steps: DecryptFileMediaMissionStep[] = []; // Step 1. Fetch the file and convert it to a Uint8Array + let headers; + if (isBlobServiceURI(blobURI)) { + if (authMetadata) { + headers = createDefaultHTTPRequestHeaders( + authMetadata, + base64EncodeString, + ); + } else if (usingCommServicesAccessToken && !authMetadata) { + return { steps, result: { success: false, reason: 'missing_auth' } }; + } + } + const fetchStartTime = Date.now(); let data; try { - const response = await fetch(getFetchableURI(blobURI)); + const response = await fetch(getFetchableURI(blobURI), { headers }); if (!response.ok) { throw new Error(`HTTP error ${response.status}: ${response.statusText}`); } diff --git a/native/media/save-media.js b/native/media/save-media.js --- a/native/media/save-media.js +++ b/native/media/save-media.js @@ -42,6 +42,7 @@ type FetchFileInfoResult, } from './file-utils.js'; import { getMediaLibraryIdentifier } from './identifier-utils.js'; +import { commCoreModule } from '../native-modules.js'; import { displayActionResultModal } from '../navigation/action-result-modal.js'; import { requestAndroidPermission } from '../utils/android-permissions.js'; @@ -377,8 +378,10 @@ ): Promise { const steps: Array = []; if (encryptionKey) { + const authMetadata = await commCoreModule.getCommServicesAuthMetadata(); + const { steps: decryptionSteps, result: decryptionResult } = - await fetchAndDecryptMedia(inputURI, encryptionKey, { + await fetchAndDecryptMedia(inputURI, encryptionKey, authMetadata, { destination: 'file', destinationDirectory: directory, }); diff --git a/native/media/video-playback-modal.react.js b/native/media/video-playback-modal.react.js --- a/native/media/video-playback-modal.react.js +++ b/native/media/video-playback-modal.react.js @@ -16,6 +16,7 @@ import Video from 'react-native-video'; import { MediaCacheContext } from 'lib/components/media-cache-provider.react.js'; +import { useCommServicesAuthMetadata } from 'lib/hooks/account-hooks.js'; import { useIsAppBackgroundedOrInactive } from 'lib/shared/lifecycle-utils.js'; import type { MediaInfo } from 'lib/types/media-types.js'; @@ -98,6 +99,7 @@ ); const mediaCache = React.useContext(MediaCacheContext); + const authMetadata = useCommServicesAuthMetadata(); React.useEffect(() => { // skip for unencrypted videos @@ -116,9 +118,14 @@ return; } - const { result } = await fetchAndDecryptMedia(blobURI, encryptionKey, { - destination: 'file', - }); + const { result } = await fetchAndDecryptMedia( + blobURI, + encryptionKey, + authMetadata, + { + destination: 'file', + }, + ); if (result.success) { const { uri } = result; const cacheSetPromise = mediaCache?.set(blobURI, uri); @@ -142,7 +149,7 @@ filesystem.unlink(uriToDispose); } }; - }, [blobURI, encryptionKey, mediaCache]); + }, [blobURI, encryptionKey, mediaCache, authMetadata]); const closeButtonX = useValue(-1); const closeButtonY = useValue(-1); diff --git a/native/utils/base64-utils.js b/native/utils/base64-utils.js new file mode 100644 --- /dev/null +++ b/native/utils/base64-utils.js @@ -0,0 +1,10 @@ +// @flow + +import { commUtilsModule } from '../native-modules.js'; + +function base64EncodeString(value: string): string { + const buffer = commUtilsModule.encodeStringToUTF8ArrayBuffer(value); + return commUtilsModule.base64EncodeBuffer(buffer); +} + +export { base64EncodeString };