From d58f9defb6c4cdb0d1bb0720e96e72a33cbaf15b Mon Sep 17 00:00:00 2001 From: Vincent Voyer Date: Mon, 14 Oct 2024 14:58:16 +0200 Subject: [PATCH] fix(blob): provide custom errors for expired client tokens and pathname mismatch (#777) * fix(blob): provide custom error for client token pathname mismatch Before this commit, you would not know why an upload or put failed when you messed up the pathname value in upload() versus the one computed when generating the client token * fix(blob): Add specific client token expired error Before this commit, it would be impossible to know that a request failed because of an expired client token * remove * fix(blob): add BlobFileTooLargeError --- .changeset/modern-keys-brush.md | 5 +++ packages/blob/src/api.node.test.ts | 4 +-- packages/blob/src/api.ts | 57 +++++++++++++++++++++++++++--- packages/blob/src/index.ts | 5 ++- 4 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 .changeset/modern-keys-brush.md diff --git a/.changeset/modern-keys-brush.md b/.changeset/modern-keys-brush.md new file mode 100644 index 000000000..005801203 --- /dev/null +++ b/.changeset/modern-keys-brush.md @@ -0,0 +1,5 @@ +--- +"@vercel/blob": patch +--- + +fix(blob): provide custom errors for expired client tokens and pathname mismatch diff --git a/packages/blob/src/api.node.test.ts b/packages/blob/src/api.node.test.ts index f23799ceb..7b071098a 100644 --- a/packages/blob/src/api.node.test.ts +++ b/packages/blob/src/api.node.test.ts @@ -6,7 +6,7 @@ import { BlobStoreNotFoundError, BlobStoreSuspendedError, BlobUnknownError, - BlobContentTypeNotAllowed, + BlobContentTypeNotAllowedError, requestApi, } from './api'; import { BlobError } from './helpers'; @@ -107,7 +107,7 @@ describe('api', () => { [ 400, 'forbidden', - BlobContentTypeNotAllowed, + BlobContentTypeNotAllowedError, '"contentType" text/plain is not allowed', ], [500, 'not_found', BlobNotFoundError], diff --git a/packages/blob/src/api.ts b/packages/blob/src/api.ts index 7780378ed..c67057466 100644 --- a/packages/blob/src/api.ts +++ b/packages/blob/src/api.ts @@ -17,9 +17,29 @@ export class BlobAccessError extends BlobError { } } -export class BlobContentTypeNotAllowed extends BlobError { +export class BlobContentTypeNotAllowedError extends BlobError { constructor(message: string) { - super(`Content type mismatch, ${message}`); + super(`Content type mismatch, ${message}.`); + } +} + +export class BlobPathnameMismatchError extends BlobError { + constructor(message: string) { + super( + `Pathname mismatch, ${message}. Check the pathname used in upload() or put() matches the one from the client token.`, + ); + } +} + +export class BlobClientTokenExpiredError extends BlobError { + constructor() { + super('Client token has expired.'); + } +} + +export class BlobFileTooLargeError extends BlobError { + constructor(message: string) { + super(`File is too large, ${message}.`); } } @@ -83,7 +103,10 @@ type BlobApiErrorCodes = | 'not_allowed' | 'service_unavailable' | 'rate_limited' - | 'content_type_not_allowed'; + | 'content_type_not_allowed' + | 'client_token_pathname_mismatch' + | 'client_token_expired' + | 'file_too_large'; export interface BlobApiError { error?: { code?: BlobApiErrorCodes; message?: string }; @@ -166,6 +189,21 @@ async function getBlobError( code = 'content_type_not_allowed'; } + if ( + message?.includes('"pathname"') && + message.includes('does not match the token payload') + ) { + code = 'client_token_pathname_mismatch'; + } + + if (message === 'Token expired') { + code = 'client_token_expired'; + } + + if (message?.includes('the file length cannot be greater than')) { + code = 'file_too_large'; + } + let error: BlobError; switch (code) { case 'store_suspended': @@ -176,7 +214,18 @@ async function getBlobError( break; case 'content_type_not_allowed': // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- TS, be smarter - error = new BlobContentTypeNotAllowed(message!); + error = new BlobContentTypeNotAllowedError(message!); + break; + case 'client_token_pathname_mismatch': + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- TS, be smarter + error = new BlobPathnameMismatchError(message!); + break; + case 'client_token_expired': + error = new BlobClientTokenExpiredError(); + break; + case 'file_too_large': + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- TS, be smarter + error = new BlobFileTooLargeError(message!); break; case 'not_found': error = new BlobNotFoundError(); diff --git a/packages/blob/src/index.ts b/packages/blob/src/index.ts index 69674a202..c9c48eed2 100644 --- a/packages/blob/src/index.ts +++ b/packages/blob/src/index.ts @@ -21,7 +21,10 @@ export { BlobServiceNotAvailable, BlobRequestAbortedError, BlobServiceRateLimited, - BlobContentTypeNotAllowed as BlobContentTypeNotAllowedError, + BlobContentTypeNotAllowedError, + BlobPathnameMismatchError, + BlobClientTokenExpiredError, + BlobFileTooLargeError, } from './api'; // vercelBlob.put()