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<{ @@ -140,17 +144,19 @@ 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 = createDefaultHTTPRequestHeaders(authMetadata); // 1. Assign new holder for blob with given blobHash let blobAlreadyExists: boolean; @@ -165,6 +171,7 @@ blob_hash: blobHash, }), headers: { + ...defaultHeaders, 'content-type': 'application/json', }, }, @@ -196,6 +203,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,9 @@ const xhr = new XMLHttpRequest(); xhr.open(method, url); + const authHeader = createHTTPAuthorizationHeader(authMetadata); + 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 @@ -18,6 +18,7 @@ extensionFromFilename, filenameFromPathOrURI, } from 'lib/media/file-utils.js'; +import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import type { AvatarDBContent, UpdateUserAvatarRequest, @@ -55,6 +56,10 @@ } function useUploadProcessedMedia(): MediaResult => Promise { + const identityContext = React.useContext(IdentityClientContext); + invariant(identityContext, 'Identity context should be set'); + const { getAuthMetadata } = identityContext; + const callUploadMultimedia = useLegacyAshoatKeyserverCall(uploadMultimedia); const callBlobServiceUpload = useBlobServiceUpload(); const uploadProcessedMultimedia: MediaResult => Promise = @@ -94,6 +99,7 @@ dimensions, thumbHash, } = encryptionResult; + const authMetadata = await getAuthMetadata(); const { id } = await callBlobServiceUpload({ uploadInput: { blobInput: { @@ -108,6 +114,7 @@ thumbHash, loop: false, }, + authMetadata, keyserverOrThreadID: ashoatKeyserverID, callbacks: { blobServiceUploadHandler }, }); @@ -116,7 +123,7 @@ } return { type: 'encrypted_image', uploadID: id }; }, - [callUploadMultimedia, callBlobServiceUpload], + [callUploadMultimedia, callBlobServiceUpload, getAuthMetadata], ); 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 @@ -39,6 +39,8 @@ combineLoadingStatuses, createLoadingStatusSelector, } from 'lib/selectors/loading-selectors.js'; +import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; +import type { IdentityClientContextType } from 'lib/shared/identity-client-context.js'; import { createMediaMessageInfo, useMessageCreationSideEffectsFunc, @@ -158,6 +160,7 @@ +sendTextMessage: (input: SendTextMessageInput) => Promise, +newThread: (request: ClientNewThreadRequest) => Promise, +textMessageCreationSideEffectsFunc: CreationSideEffectsFunc, + +identityContext: ?IdentityClientContextType, }; type State = { +pendingUploads: PendingMultimediaUploads, @@ -820,7 +823,7 @@ const { uploadURI, filename, mime } = processedMedia; - const { hasWiFi } = this.props; + const { hasWiFi, identityContext } = this.props; const uploadStart = Date.now(); let uploadExceptionMessage, @@ -833,41 +836,52 @@ (processedMedia.mediaType === 'encrypted_photo' || processedMedia.mediaType === 'encrypted_video') ) { - const uploadPromise = this.props.blobServiceUpload({ - uploadInput: { - blobInput: { - type: 'uri', - uri: uploadURI, - filename: filename, - mimeType: mime, + invariant(identityContext, 'Identity context should be set'); + const uploadPromise = (async () => { + invariant( + processedMedia.mediaType === 'encrypted_photo' || + processedMedia.mediaType === 'encrypted_video', + 'encryptionKey should be set', + ); + const authMetadata = await identityContext.getAuthMetadata(); + return await this.props.blobServiceUpload({ + uploadInput: { + blobInput: { + type: 'uri', + uri: uploadURI, + filename: filename, + mimeType: mime, + }, + blobHash: processedMedia.blobHash, + encryptionKey: processedMedia.encryptionKey, + dimensions: processedMedia.dimensions, + thumbHash: + processedMedia.mediaType === 'encrypted_photo' + ? processedMedia.thumbHash + : null, }, - blobHash: processedMedia.blobHash, - encryptionKey: processedMedia.encryptionKey, - dimensions: processedMedia.dimensions, - thumbHash: - processedMedia.mediaType === 'encrypted_photo' - ? processedMedia.thumbHash - : null, - }, - keyserverOrThreadID: threadInfo.id, - callbacks: { - blobServiceUploadHandler, - onProgress: (percent: number) => { - this.setProgress( - localMessageID, - localMediaID, - 'uploading', - percent, - ); + authMetadata, + keyserverOrThreadID: threadInfo.id, + callbacks: { + blobServiceUploadHandler, + onProgress: (percent: number) => { + this.setProgress( + localMessageID, + localMediaID, + 'uploading', + percent, + ); + }, }, - }, - }); + }); + })(); const uploadThumbnailPromise: Promise = (async () => { if (processedMedia.mediaType !== 'encrypted_video') { return undefined; } + const authMetadata = await identityContext.getAuthMetadata(); return await this.props.blobServiceUpload({ uploadInput: { blobInput: { @@ -882,6 +896,7 @@ dimensions: processedMedia.dimensions, thumbHash: processedMedia.thumbHash, }, + authMetadata, keyserverOrThreadID: threadInfo.id, callbacks: { blobServiceUploadHandler, @@ -956,6 +971,7 @@ mediaMissionResult = { success: true }; } catch (e) { uploadExceptionMessage = getMessageForException(e); + console.log('CO JEST KURWA', e); onUploadFailed(localMediaID, 'upload failed'); mediaMissionResult = { success: false, @@ -1720,6 +1736,7 @@ const staffCanSee = useStaffCanSee(); const textMessageCreationSideEffectsFunc = useMessageCreationSideEffectsFunc(messageTypes.TEXT); + const identityContext = React.useContext(IdentityClientContext); 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,13 @@ 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'; const blobServiceUploadHandler: BlobServiceUploadHandler = async ( url, method, input, + authMetadata, options, ) => { if (input.blobInput.type !== 'uri') { @@ -23,6 +25,7 @@ path = resolvedPath; } } + const headers = authMetadata && createDefaultHTTPRequestHeaders(authMetadata); const uploadTask = FileSystem.createUploadTask( url, path, @@ -31,6 +34,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 @@ -1,11 +1,13 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { uploadMultimedia, useBlobServiceUpload, } from 'lib/actions/upload-actions.js'; +import { IdentityClientContext } from 'lib/shared/identity-client-context.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 +20,10 @@ const useBlobServiceUploads = false; function useUploadAvatarMedia(): File => Promise { + const identityContext = React.useContext(IdentityClientContext); + invariant(identityContext, 'Identity context should be set'); + const { getAuthMetadata } = identityContext; + const callUploadMultimedia = useLegacyAshoatKeyserverCall(uploadMultimedia); const callBlobServiceUpload = useBlobServiceUpload(); const uploadAvatarMedia = React.useCallback( @@ -56,6 +62,7 @@ ? thumbHashResult.thumbHash : null; + const authMetadata = await getAuthMetadata(); const { id } = await callBlobServiceUpload({ uploadInput: { blobInput: { @@ -68,13 +75,14 @@ loop: false, thumbHash, }, + authMetadata, keyserverOrThreadID: ashoatKeyserverID, callbacks: {}, }); return { type: 'encrypted_image', uploadID: id }; }, - [callBlobServiceUpload, callUploadMultimedia], + [callBlobServiceUpload, callUploadMultimedia, getAuthMetadata], ); 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 @@ -41,6 +41,8 @@ import commStaffCommunity from 'lib/facts/comm-staff-community.js'; import { getNextLocalUploadID } from 'lib/media/media-utils.js'; import { pendingToRealizedThreadIDsSelector } from 'lib/selectors/thread-selectors.js'; +import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; +import type { IdentityClientContextType } 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, + +identityContext: ?IdentityClientContextType, }; type WritableState = { pendingUploads: { @@ -867,6 +871,10 @@ const steps = [...upload.steps]; let userTime; + const { identityContext } = this.props; + invariant(identityContext, 'Identity context should be set'); + const authMetadata = await identityContext.getAuthMetadata(); + const sendReport = (missionResult: MediaMissionResult) => { const newThreadID = this.getRealizedOrPendingThreadID(threadID); const latestUpload = this.state.pendingUploads[newThreadID][localID]; @@ -907,6 +915,7 @@ encryptionKey && blobHash && dimensions, 'incomplete encrypted upload', ); + uploadResult = await this.props.blobServiceUpload({ uploadInput: { blobInput: { @@ -920,6 +929,7 @@ thumbHash, }, keyserverOrThreadID: threadID, + authMetadata, callbacks, }); } else { @@ -1011,7 +1021,10 @@ }); if (encryptionKey) { - const { steps: preloadSteps } = await preloadMediaResource(result.uri); + const { steps: preloadSteps } = await preloadMediaResource( + result.uri, + authMetadata, + ); steps.push(...preloadSteps); } else { const { steps: preloadSteps } = await preloadImage(result.uri); @@ -1202,6 +1215,8 @@ keyserverOrThreadID: threadID, }); if (isBlobServiceURI(pendingUpload.uri)) { + const identityContext = this.props.identityContext; + invariant(identityContext, 'Identity context should be set'); invariant( pendingUpload.blobHolder, 'blob service upload has no holder', @@ -1209,16 +1224,22 @@ const endpoint = blobService.httpEndpoints.DELETE_BLOB; const holder = pendingUpload.blobHolder; const blobHash = blobHashFromBlobServiceURI(pendingUpload.uri); - void fetch(makeBlobServiceEndpointURL(endpoint), { - method: endpoint.method, - body: JSON.stringify({ - holder, - blob_hash: blobHash, - }), - headers: { - 'content-type': 'application/json', - }, - }); + void (async () => { + const authMetadata = await identityContext.getAuthMetadata(); + const defaultHeaders = + createDefaultHTTPRequestHeaders(authMetadata); + await fetch(makeBlobServiceEndpointURL(endpoint), { + method: endpoint.method, + body: JSON.stringify({ + holder, + blob_hash: blobHash, + }), + headers: { + ...defaultHeaders, + 'content-type': 'application/json', + }, + }); + })(); } } const newPendingUploads = _omit([localUploadID])(currentPendingUploads); @@ -1656,6 +1677,7 @@ const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const modalContext = useModalContext(); + const identityContext = React.useContext(IdentityClientContext); const [sendCallbacks, setSendCallbacks] = React.useState< $ReadOnlyArray<() => mixed>, @@ -1696,6 +1718,7 @@ registerSendCallback={registerSendCallback} unregisterSendCallback={unregisterSendCallback} textMessageCreationSideEffectsFunc={textMessageCreationSideEffectsFunc} + identityContext={identityContext} /> ); }); diff --git a/web/media/media-utils.js b/web/media/media-utils.js --- a/web/media/media-utils.js +++ b/web/media/media-utils.js @@ -5,13 +5,16 @@ import { thumbHashToDataURL } from 'thumbhash'; import { fetchableMediaURI } from 'lib/media/media-utils.js'; +import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; import type { MediaType, Dimensions, MediaMissionStep, MediaMissionFailure, } from 'lib/types/media-types.js'; +import { isBlobServiceURI } from 'lib/utils/blob-service.js'; import { getMessageForException } from 'lib/utils/errors.js'; +import { createDefaultHTTPRequestHeaders } from 'lib/utils/services-utils.js'; import { probeFile } from './blob-utils.js'; import { decryptThumbhashToDataURL } from './encryption-utils.js'; @@ -64,15 +67,23 @@ * @returns Steps and the result of the preload. The preload is successful * if the HTTP response is OK (20x). */ -async function preloadMediaResource(uri: string): Promise<{ +async function preloadMediaResource( + uri: string, + authMetadata: AuthMetadata, +): Promise<{ steps: $ReadOnlyArray, result: { +success: boolean }, }> { + let headers; + if (isBlobServiceURI(uri)) { + headers = createDefaultHTTPRequestHeaders(authMetadata); + } + const start = Date.now(); const mediaURI = fetchableMediaURI(uri); let success, exceptionMessage; try { - const response = await fetch(mediaURI); + const response = await fetch(mediaURI, { headers }); // we need to read the blob to make sure the browser caches it await response.blob(); success = response.ok;