diff --git a/lib/actions/upload-actions.js b/lib/actions/upload-actions.js --- a/lib/actions/upload-actions.js +++ b/lib/actions/upload-actions.js @@ -5,6 +5,7 @@ import blobService from '../facts/blob-service.js'; import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; import type { CallKeyserverEndpoint } from '../keyserver-conn/keyserver-conn-types.js'; +import type { AuthMetadata } from '../shared/identity-client-context.js'; import type { UploadMultimediaResult, Dimensions } from '../types/media-types'; import { toBase64URL } from '../utils/base64.js'; import { @@ -15,7 +16,10 @@ import type { CallSingleKeyserverEndpoint } from '../utils/call-single-keyserver-endpoint.js'; import { getMessageForException } from '../utils/errors.js'; import { useKeyserverCall } from '../utils/keyserver-call.js'; -import { handleHTTPResponseError } from '../utils/services-utils.js'; +import { + handleHTTPResponseError, + createDefaultHTTPRequestHeaders, +} from '../utils/services-utils.js'; import { type UploadBlob } from '../utils/upload-blob.js'; export type MultimediaUploadCallbacks = Partial<{ @@ -24,6 +28,7 @@ +uploadBlob: UploadBlob, +blobServiceUploadHandler: BlobServiceUploadHandler, +timeout: ?number, + +base64EncodeString: (input: string) => string, }>; export type MultimediaUploadExtras = $ReadOnly< Partial<{ @@ -140,18 +145,26 @@ export type BlobServiceUploadAction = (input: { +uploadInput: BlobServiceUploadInput, +keyserverOrThreadID: string, + +authMetadata: ?AuthMetadata, +callbacks?: MultimediaUploadCallbacks, }) => Promise; const blobServiceUpload = (callKeyserverEndpoint: CallKeyserverEndpoint): BlobServiceUploadAction => async input => { - const { uploadInput, callbacks, keyserverOrThreadID } = input; + const { uploadInput, callbacks, keyserverOrThreadID, authMetadata } = input; const { encryptionKey, loop, dimensions, thumbHash, blobInput } = uploadInput; const blobHolder = uuid.v4(); const blobHash = toBase64URL(uploadInput.blobHash); + const defaultHeaders = authMetadata + ? createDefaultHTTPRequestHeaders( + authMetadata, + callbacks?.base64EncodeString, + ) + : {}; + // 1. Assign new holder for blob with given blobHash let blobAlreadyExists: boolean; try { @@ -165,6 +178,7 @@ blob_hash: blobHash, }), headers: { + ...defaultHeaders, 'content-type': 'application/json', }, }, @@ -196,6 +210,7 @@ blobHash, blobInput, }, + authMetadata, { ...callbacks }, ); } catch (e) { diff --git a/lib/utils/blob-service-upload.js b/lib/utils/blob-service-upload.js --- a/lib/utils/blob-service-upload.js +++ b/lib/utils/blob-service-upload.js @@ -3,10 +3,12 @@ import invariant from 'invariant'; import _throttle from 'lodash/throttle.js'; +import { createHTTPAuthorizationHeader } from './services-utils.js'; import type { MultimediaUploadCallbacks, BlobServiceUploadFile, } from '../actions/upload-actions.js'; +import type { AuthMetadata } from '../shared/identity-client-context.js'; function blobServiceUploadHandler( url: string, @@ -15,6 +17,7 @@ blobHash: string, blobInput: BlobServiceUploadFile, }, + authMetadata: ?AuthMetadata, options?: ?MultimediaUploadCallbacks, ): Promise { if (input.blobInput.type !== 'file') { @@ -28,6 +31,14 @@ const xhr = new XMLHttpRequest(); xhr.open(method, url); + if (authMetadata) { + const authHeader = createHTTPAuthorizationHeader( + authMetadata, + options?.base64EncodeString, + ); + xhr.setRequestHeader('Authorization', authHeader); + } + const { timeout, onProgress, abortHandler } = options ?? {}; if (timeout) { diff --git a/native/avatars/avatar-hooks.js b/native/avatars/avatar-hooks.js --- a/native/avatars/avatar-hooks.js +++ b/native/avatars/avatar-hooks.js @@ -14,6 +14,7 @@ } from 'lib/actions/upload-actions.js'; import { EditThreadAvatarContext } from 'lib/components/base-edit-thread-avatar-provider.react.js'; import { EditUserAvatarContext } from 'lib/components/edit-user-avatar-provider.react.js'; +import { useCommServicesAuthMetadata } from 'lib/hooks/account-hooks.js'; import { extensionFromFilename, filenameFromPathOrURI, @@ -55,6 +56,7 @@ } function useUploadProcessedMedia(): MediaResult => Promise { + const authMetadata = useCommServicesAuthMetadata(); const callUploadMultimedia = useLegacyAshoatKeyserverCall(uploadMultimedia); const callBlobServiceUpload = useBlobServiceUpload(); const uploadProcessedMultimedia: MediaResult => Promise = @@ -108,6 +110,7 @@ thumbHash, loop: false, }, + authMetadata, keyserverOrThreadID: ashoatKeyserverID, callbacks: { blobServiceUploadHandler }, }); @@ -116,7 +119,7 @@ } return { type: 'encrypted_image', uploadID: id }; }, - [callUploadMultimedia, callBlobServiceUpload], + [callUploadMultimedia, callBlobServiceUpload, authMetadata], ); return uploadProcessedMultimedia; } diff --git a/native/input/input-state-container.react.js b/native/input/input-state-container.react.js --- a/native/input/input-state-container.react.js +++ b/native/input/input-state-container.react.js @@ -29,6 +29,7 @@ useBlobServiceUpload, } from 'lib/actions/upload-actions.js'; import commStaffCommunity from 'lib/facts/comm-staff-community.js'; +import { useCommServicesAuthMetadata } from 'lib/hooks/account-hooks.js'; import { pathFromURI, replaceExtension } from 'lib/media/file-utils.js'; import { getNextLocalUploadID, @@ -39,6 +40,7 @@ combineLoadingStatuses, createLoadingStatusSelector, } from 'lib/selectors/loading-selectors.js'; +import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; import { createMediaMessageInfo, useMessageCreationSideEffectsFunc, @@ -115,6 +117,7 @@ import { displayActionResultModal } from '../navigation/action-result-modal.js'; import { useCalendarQuery } from '../navigation/nav-selectors.js'; import { useSelector } from '../redux/redux-utils.js'; +import { base64EncodeString } from '../utils/base64-utils.js'; import blobServiceUploadHandler from '../utils/blob-service-upload.js'; import { useStaffCanSee } from '../utils/staff-utils.js'; @@ -158,6 +161,7 @@ +sendTextMessage: (input: SendTextMessageInput) => Promise, +newThread: (request: ClientNewThreadRequest) => Promise, +textMessageCreationSideEffectsFunc: CreationSideEffectsFunc, + +authMetadata: ?AuthMetadata, }; type State = { +pendingUploads: PendingMultimediaUploads, @@ -820,7 +824,7 @@ const { uploadURI, filename, mime } = processedMedia; - const { hasWiFi } = this.props; + const { hasWiFi, authMetadata } = this.props; const uploadStart = Date.now(); let uploadExceptionMessage, @@ -849,8 +853,10 @@ ? processedMedia.thumbHash : null, }, + authMetadata, keyserverOrThreadID: threadInfo.id, callbacks: { + base64EncodeString, blobServiceUploadHandler, onProgress: (percent: number) => { this.setProgress( @@ -882,8 +888,10 @@ dimensions: processedMedia.dimensions, thumbHash: processedMedia.thumbHash, }, + authMetadata, keyserverOrThreadID: threadInfo.id, callbacks: { + base64EncodeString, blobServiceUploadHandler, }, }); @@ -1720,6 +1728,7 @@ const staffCanSee = useStaffCanSee(); const textMessageCreationSideEffectsFunc = useMessageCreationSideEffectsFunc(messageTypes.TEXT); + const authMetadata = useCommServicesAuthMetadata(); return ( ); }); diff --git a/native/utils/blob-service-upload.js b/native/utils/blob-service-upload.js --- a/native/utils/blob-service-upload.js +++ b/native/utils/blob-service-upload.js @@ -6,11 +6,15 @@ import { pathFromURI } from 'lib/media/file-utils.js'; import type { BlobServiceUploadHandler } from 'lib/utils/blob-service-upload.js'; import { getMessageForException } from 'lib/utils/errors.js'; +import { createDefaultHTTPRequestHeaders } from 'lib/utils/services-utils.js'; + +import { base64EncodeString } from './base64-utils.js'; const blobServiceUploadHandler: BlobServiceUploadHandler = async ( url, method, input, + authMetadata, options, ) => { if (input.blobInput.type !== 'uri') { @@ -23,6 +27,9 @@ path = resolvedPath; } } + const headers = + authMetadata && + createDefaultHTTPRequestHeaders(authMetadata, base64EncodeString); const uploadTask = FileSystem.createUploadTask( url, path, @@ -31,6 +38,7 @@ fieldName: 'blob_data', httpMethod: method, parameters: { blob_hash: input.blobHash }, + headers, }, uploadProgress => { if (options?.onProgress) { diff --git a/web/avatars/avatar-hooks.react.js b/web/avatars/avatar-hooks.react.js --- a/web/avatars/avatar-hooks.react.js +++ b/web/avatars/avatar-hooks.react.js @@ -6,6 +6,7 @@ uploadMultimedia, useBlobServiceUpload, } from 'lib/actions/upload-actions.js'; +import { useCommServicesAuthMetadata } from 'lib/hooks/account-hooks.js'; import type { UpdateUserAvatarRequest } from 'lib/types/avatar-types.js'; import { useLegacyAshoatKeyserverCall } from 'lib/utils/action-utils.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; @@ -18,6 +19,7 @@ const useBlobServiceUploads = false; function useUploadAvatarMedia(): File => Promise { + const authMetadata = useCommServicesAuthMetadata(); const callUploadMultimedia = useLegacyAshoatKeyserverCall(uploadMultimedia); const callBlobServiceUpload = useBlobServiceUpload(); const uploadAvatarMedia = React.useCallback( @@ -68,13 +70,14 @@ loop: false, thumbHash, }, + authMetadata, keyserverOrThreadID: ashoatKeyserverID, callbacks: {}, }); return { type: 'encrypted_image', uploadID: id }; }, - [callBlobServiceUpload, callUploadMultimedia], + [callBlobServiceUpload, callUploadMultimedia, authMetadata], ); return uploadAvatarMedia; } diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js --- a/web/input/input-state-container.react.js +++ b/web/input/input-state-container.react.js @@ -39,8 +39,10 @@ } from 'lib/components/modal-provider.react.js'; import blobService from 'lib/facts/blob-service.js'; import commStaffCommunity from 'lib/facts/comm-staff-community.js'; +import { useCommServicesAuthMetadata } from 'lib/hooks/account-hooks.js'; import { getNextLocalUploadID } from 'lib/media/media-utils.js'; import { pendingToRealizedThreadIDsSelector } from 'lib/selectors/thread-selectors.js'; +import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; import { createMediaMessageInfo, localIDPrefix, @@ -95,6 +97,7 @@ } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { generateReportID } from 'lib/utils/report-utils.js'; +import { createDefaultHTTPRequestHeaders } from 'lib/utils/services-utils.js'; import { type BaseInputState, @@ -151,6 +154,7 @@ +registerSendCallback: (() => mixed) => void, +unregisterSendCallback: (() => mixed) => void, +textMessageCreationSideEffectsFunc: CreationSideEffectsFunc, + +authMetadata: ?AuthMetadata, }; type WritableState = { pendingUploads: { @@ -902,6 +906,7 @@ (upload.mediaType === 'encrypted_photo' || upload.mediaType === 'encrypted_video') ) { + const { authMetadata } = this.props; const { blobHash, dimensions, thumbHash } = upload; invariant( encryptionKey && blobHash && dimensions, @@ -920,6 +925,7 @@ thumbHash, }, keyserverOrThreadID: threadID, + authMetadata, callbacks, }); } else { @@ -1209,6 +1215,10 @@ const endpoint = blobService.httpEndpoints.DELETE_BLOB; const holder = pendingUpload.blobHolder; const blobHash = blobHashFromBlobServiceURI(pendingUpload.uri); + const authMetadata = this.props.authMetadata; + const defaultHeaders = authMetadata + ? createDefaultHTTPRequestHeaders(authMetadata) + : {}; void fetch(makeBlobServiceEndpointURL(endpoint), { method: endpoint.method, body: JSON.stringify({ @@ -1216,6 +1226,7 @@ blob_hash: blobHash, }), headers: { + ...defaultHeaders, 'content-type': 'application/json', }, }); @@ -1656,6 +1667,7 @@ const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const modalContext = useModalContext(); + const authMetadata = useCommServicesAuthMetadata(); const [sendCallbacks, setSendCallbacks] = React.useState< $ReadOnlyArray<() => mixed>, @@ -1696,6 +1708,7 @@ registerSendCallback={registerSendCallback} unregisterSendCallback={unregisterSendCallback} textMessageCreationSideEffectsFunc={textMessageCreationSideEffectsFunc} + authMetadata={authMetadata} /> ); });