diff --git a/keyserver/src/services/blob.js b/keyserver/src/services/blob.js --- a/keyserver/src/services/blob.js +++ b/keyserver/src/services/blob.js @@ -13,6 +13,7 @@ } from 'lib/utils/blob-service.js'; import { createHTTPAuthorizationHeader } from 'lib/utils/services-utils.js'; +import { clearIdentityInfo } from '../user/identity.js'; import { verifyUserLoggedIn } from '../user/login.js'; import { getContentSigningKey } from '../utils/olm-utils.js'; @@ -46,7 +47,11 @@ ): Promise { const { hash: blobHash, holder } = params; const headers = await createRequestHeaders(); - return assignBlobHolder({ blobHash, holder }, headers); + const assignResult = await assignBlobHolder({ blobHash, holder }, headers); + if (!assignResult.success && assignResult.reason === 'INVALID_CSAT') { + await clearIdentityInfo(); + } + return assignResult; } async function uploadBlobKeyserverWrapper( @@ -54,7 +59,11 @@ hash: string, ): Promise { const authHeaders = await createRequestHeaders(false); - return uploadBlob(blob, hash, authHeaders); + const uploadResult = await uploadBlob(blob, hash, authHeaders); + if (!uploadResult.success && uploadResult.reason === 'INVALID_CSAT') { + await clearIdentityInfo(); + } + return uploadResult; } async function upload( @@ -108,7 +117,14 @@ async function deleteBlob(params: BlobDescriptor, instant?: boolean) { const { hash: blobHash, holder } = params; const headers = await createRequestHeaders(); - await removeBlobHolder({ blobHash, holder }, headers, instant); + const removeResult = await removeBlobHolder( + { blobHash, holder }, + headers, + instant, + ); + if (!removeResult.success && removeResult.reason === 'INVALID_CSAT') { + await clearIdentityInfo(); + } } async function removeBlobHolders(holders: $ReadOnlyArray) { 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 @@ -32,7 +32,10 @@ } from '../utils/blob-service.js'; import { getMessageForException } from '../utils/errors.js'; import { useDispatch } from '../utils/redux-utils.js'; -import { createDefaultHTTPRequestHeaders } from '../utils/services-utils.js'; +import { + createDefaultHTTPRequestHeaders, + errorMessageIsInvalidCSAT, +} from '../utils/services-utils.js'; export type MultimediaUploadCallbacks = Partial<{ +onProgress: (percent: number) => void, @@ -129,6 +132,9 @@ defaultHeaders, ); if (!assignHolderResult.success) { + if (assignHolderResult.reason === 'INVALID_CSAT') { + throw new Error('invalid_csat'); + } const { status, statusText } = assignHolderResult; throw new Error(`Server responded with HTTP ${status}: ${statusText}`); } @@ -136,6 +142,9 @@ await assignHolderResult.response.json(); blobAlreadyExists = dataExistsResponse; } catch (e) { + if (errorMessageIsInvalidCSAT(e)) { + throw e; + } throw new Error( `Failed to assign holder: ${ getMessageForException(e) ?? 'unknown error' @@ -162,6 +171,9 @@ { ...callbacks }, ); } catch (e) { + if (errorMessageIsInvalidCSAT(e)) { + throw e; + } throw new Error( `Failed to upload blob: ${ getMessageForException(e) ?? 'unknown error' @@ -298,10 +310,16 @@ defaultHeaders, ); if (!assignHolderResult.success) { + if (assignHolderResult.reason === 'INVALID_CSAT') { + throw new Error('invalid_csat'); + } const { status, statusText } = assignHolderResult; throw new Error(`Server responded with HTTP ${status}: ${statusText}`); } } catch (e) { + if (errorMessageIsInvalidCSAT(e)) { + throw e; + } throw new Error( `Failed to assign holder: ${ getMessageForException(e) ?? 'unknown error' 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 @@ -53,6 +53,11 @@ if (failed) { return; } + if (xhr.status === 401 || xhr.status === 403) { + failed = true; + reject(new Error('invalid_csat')); + return; + } resolve(); }; xhr.onabort = () => { diff --git a/lib/utils/blob-service.js b/lib/utils/blob-service.js --- a/lib/utils/blob-service.js +++ b/lib/utils/blob-service.js @@ -4,6 +4,7 @@ import uuid from 'uuid'; import { toBase64URL } from './base64.js'; +import { httpResponseIsInvalidCSAT } from './services-utils.js'; import { replacePathParams, type URLPathParams } from './url-utils.js'; import { assertWithValidator } from './validation-utils.js'; import type { BlobServiceHTTPEndpoint } from '../facts/blob-service.js'; @@ -79,7 +80,7 @@ } | { +success: false, - +reason: 'HASH_IN_USE' | 'OTHER', + +reason: 'HASH_IN_USE' | 'INVALID_CSAT' | 'OTHER', +status: number, +statusText: string, }; @@ -131,7 +132,14 @@ if (!uploadBlobResponse.ok) { const { status, statusText } = uploadBlobResponse; - const reason = status === 409 ? 'HASH_IN_USE' : 'OTHER'; + + let reason = 'OTHER'; + if (status === 409) { + reason = 'HASH_IN_USE'; + } else if (httpResponseIsInvalidCSAT(uploadBlobResponse)) { + reason = 'INVALID_CSAT'; + } + return { success: false, reason, @@ -165,7 +173,15 @@ if (!response.ok) { const { status, statusText } = response; - return { success: false, reason: 'OTHER', status, statusText }; + const reason = httpResponseIsInvalidCSAT(response) + ? 'INVALID_CSAT' + : 'OTHER'; + return { + success: false, + reason, + status, + statusText, + }; } return { success: true, response }; @@ -193,7 +209,15 @@ if (!response.ok) { const { status, statusText } = response; - return { success: false, reason: 'OTHER', status, statusText }; + const reason = httpResponseIsInvalidCSAT(response) + ? 'INVALID_CSAT' + : 'OTHER'; + return { + success: false, + reason, + status, + statusText, + }; } return { success: true, response }; 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 @@ -2,6 +2,7 @@ import base64 from 'base-64'; +import { getMessageForException } from './errors.js'; import type { AuthMetadata } from '../shared/identity-client-context.js'; // If this is true then we're using the identity service for auth. After we @@ -40,6 +41,16 @@ }; } +function httpResponseIsInvalidCSAT(response: Response): boolean { + const { status } = response; + return status === 401 || status === 403; +} + +function errorMessageIsInvalidCSAT(exception: mixed): boolean { + const errorMessage = getMessageForException(exception); + return errorMessage === 'invalid_csat'; +} + export { usingCommServicesAccessToken, supportingMultipleKeyservers, @@ -47,4 +58,6 @@ usingRestoreFlow, createHTTPAuthorizationHeader, createDefaultHTTPRequestHeaders, + httpResponseIsInvalidCSAT, + errorMessageIsInvalidCSAT, };