Skip to content

Commit

Permalink
[native][web] Handle invalid CSAT in fetchAndDecryptMedia
Browse files Browse the repository at this point in the history
Summary:
Part of [[ https://linear.app/comm/issue/ENG-9526/invalidate-csat-if-blob-service-http-calls-fail | ENG-9526 ]]

Depends on D13990

Test Plan: Manual testing with mocked invalid access token

Reviewers: kamil, varun, ashoat

Reviewed By: ashoat

Subscribers: ashoat, tomek

Differential Revision: https://phab.comm.dev/D13991
  • Loading branch information
barthap committed Nov 29, 2024
1 parent f4e33a1 commit 6ae56d0
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 8 deletions.
1 change: 1 addition & 0 deletions lib/types/media-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ export type MediaMissionFailure =
| { +success: false, +reason: 'digest_failed' }
| { +success: false, +reason: 'thumbhash_failed' }
| { +success: false, +reason: 'preload_image_failed' }
| { +success: false, +reason: 'invalid_csat' }
| DecryptionFailure;

export type DecryptionFailure = {
Expand Down
20 changes: 17 additions & 3 deletions native/media/encryption-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import invariant from 'invariant';
import * as React from 'react';

import { useInvalidCSATLogOut } from 'lib/actions/user-actions.js';
import { uintArrayToHexString, hexToUintArray } from 'lib/media/data-utils.js';
import {
replaceExtension,
Expand All @@ -25,6 +26,7 @@ import { pad, unpad, calculatePaddedLength } from 'lib/utils/pkcs7-padding.js';
import {
createDefaultHTTPRequestHeaders,
usingCommServicesAccessToken,
httpResponseIsInvalidCSAT,
} from 'lib/utils/services-utils.js';

import { temporaryDirectoryPath } from './file-utils.js';
Expand Down Expand Up @@ -274,8 +276,10 @@ async function fetchAndDecryptMedia(
const steps: DecryptFileMediaMissionStep[] = [];

// Step 1. Fetch the file and convert it to a Uint8Array
const isBlobServiceHosted = isBlobServiceURI(blobURI);

let headers;
if (isBlobServiceURI(blobURI) && authMetadata) {
if (isBlobServiceHosted && authMetadata) {
headers = createDefaultHTTPRequestHeaders(authMetadata);
}

Expand All @@ -284,6 +288,9 @@ async function fetchAndDecryptMedia(
try {
const response = await fetch(getFetchableURI(blobURI), { headers });
if (!response.ok) {
if (isBlobServiceHosted && httpResponseIsInvalidCSAT(response)) {
return { steps, result: { success: false, reason: 'invalid_csat' } };
}
throw new Error(`HTTP error ${response.status}: ${response.statusText}`);
}
const blob = await response.blob();
Expand Down Expand Up @@ -412,6 +419,8 @@ function useFetchAndDecryptMedia(): (
invariant(identityContext, 'Identity context should be set');
const { getAuthMetadata } = identityContext;

const invalidTokenLogOut = useInvalidCSATLogOut();

return React.useCallback(
async (blobURI, encryptionKey, options) => {
let authMetadata;
Expand All @@ -422,14 +431,19 @@ function useFetchAndDecryptMedia(): (
console.warn('Failed to get auth metadata:', err);
}
}
return fetchAndDecryptMedia(
const output = await fetchAndDecryptMedia(
blobURI,
encryptionKey,
authMetadata,
options,
);

if (!output.result.success && output.result.reason === 'invalid_csat') {
void invalidTokenLogOut();
}
return output;
},
[getAuthMetadata],
[getAuthMetadata, invalidTokenLogOut],
);
}

Expand Down
7 changes: 6 additions & 1 deletion native/media/save-media.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Platform, PermissionsAndroid } from 'react-native';
import filesystem from 'react-native-fs';

import { queueReportsActionType } from 'lib/actions/report-actions.js';
import { useInvalidCSATLogOut } from 'lib/actions/user-actions.js';
import { readableFilename, pathFromURI } from 'lib/media/file-utils.js';
import { isLocalUploadID } from 'lib/media/media-utils.js';
import type {
Expand Down Expand Up @@ -58,6 +59,7 @@ export type IntentionalSaveMedia = (
function useIntentionalSaveMedia(): IntentionalSaveMedia {
const dispatch = useDispatch();
const mediaReportsEnabled = useIsReportEnabled('mediaReports');
const invalidTokenLogOut = useInvalidCSATLogOut();
return React.useCallback(
async (
mediaInfo: MediaInfo,
Expand Down Expand Up @@ -87,6 +89,9 @@ function useIntentionalSaveMedia(): IntentionalSaveMedia {
let message;
if (result.success) {
message = 'saved!';
} else if (result.reason === 'invalid_csat') {
void invalidTokenLogOut();
return;
} else if (result.reason === 'save_unsupported') {
const os: string = Platform.select({
ios: 'iOS',
Expand Down Expand Up @@ -134,7 +139,7 @@ function useIntentionalSaveMedia(): IntentionalSaveMedia {
payload: { reports: [report] },
});
},
[dispatch, mediaReportsEnabled],
[dispatch, mediaReportsEnabled, invalidTokenLogOut],
);
}

Expand Down
28 changes: 24 additions & 4 deletions web/media/encryption-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import invariant from 'invariant';
import * as React from 'react';
import { thumbHashToDataURL } from 'thumbhash';

import { useInvalidCSATLogOut } from 'lib/actions/user-actions.js';
import * as AES from 'lib/media/aes-crypto-utils-common.js';
import { hexToUintArray, uintArrayToHexString } from 'lib/media/data-utils.js';
import { fileInfoFromData } from 'lib/media/file-utils.js';
Expand All @@ -17,7 +18,10 @@ import type {
import { isBlobServiceURI } from 'lib/utils/blob-service.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { calculatePaddedLength, pad, unpad } from 'lib/utils/pkcs7-padding.js';
import { createDefaultHTTPRequestHeaders } from 'lib/utils/services-utils.js';
import {
createDefaultHTTPRequestHeaders,
httpResponseIsInvalidCSAT,
} from 'lib/utils/services-utils.js';

import { base64DecodeBuffer } from '../utils/base64-utils.js';

Expand Down Expand Up @@ -157,8 +161,10 @@ async function fetchAndDecryptMedia(
const steps: DecryptFileStep[] = [];

// Step 1 - Fetch the encrypted media and convert it to a Uint8Array
const isBlobServiceHosted = isBlobServiceURI(blobURI);

let headers;
if (isBlobServiceURI(blobURI)) {
if (isBlobServiceHosted) {
headers = createDefaultHTTPRequestHeaders(authMetadata);
}

Expand All @@ -168,6 +174,9 @@ async function fetchAndDecryptMedia(
try {
const response = await fetch(url, { headers });
if (!response.ok) {
if (isBlobServiceHosted && httpResponseIsInvalidCSAT(response)) {
return { steps, result: { success: false, reason: 'invalid_csat' } };
}
throw new Error(`HTTP error ${response.status}: ${response.statusText}`);
}
const buffer = await response.arrayBuffer();
Expand Down Expand Up @@ -259,12 +268,23 @@ function useFetchAndDecryptMedia(): (
invariant(identityContext, 'Identity context should be set');
const { getAuthMetadata } = identityContext;

const invalidTokenLogOut = useInvalidCSATLogOut();

return React.useCallback(
async (blobURI, encryptionKey) => {
const authMetadata = await getAuthMetadata();
return fetchAndDecryptMedia(blobURI, encryptionKey, authMetadata);
const output = await fetchAndDecryptMedia(
blobURI,
encryptionKey,
authMetadata,
);

if (!output.result.success && output.result.reason === 'invalid_csat') {
void invalidTokenLogOut();
}
return output;
},
[getAuthMetadata],
[getAuthMetadata, invalidTokenLogOut],
);
}

Expand Down

0 comments on commit 6ae56d0

Please sign in to comment.